diff --git a/.github/actions/desktop-build-setup/action.yml b/.github/actions/desktop-build-setup/action.yml index 2c1d3931b6..30827aef95 100644 --- a/.github/actions/desktop-build-setup/action.yml +++ b/.github/actions/desktop-build-setup/action.yml @@ -16,12 +16,19 @@ runs: - name: Install dependencies shell: bash - run: pnpm install --node-linker=hoisted --config.enable-global-virtual-store=false - env: - NODE_OPTIONS: --max-old-space-size=8192 + run: pnpm install --node-linker=hoisted + + # 移除国内 electron 镜像配置,GitHub Actions 使用官方源更快 + - name: Remove China electron mirror from .npmrc + shell: bash + run: | + NPMRC_FILE="./apps/desktop/.npmrc" + if [ -f "$NPMRC_FILE" ]; then + sed -i.bak '/^electron_mirror=/d; /^electron_builder_binaries_mirror=/d' "$NPMRC_FILE" + rm -f "${NPMRC_FILE}.bak" + echo "✅ Removed electron mirror config from .npmrc" + fi - name: Install deps on Desktop shell: bash - run: pnpm --dir ./apps/desktop install --node-linker=hoisted --config.enable-global-virtual-store=false - env: - NODE_OPTIONS: --max-old-space-size=8192 + run: npm run install-isolated --prefix=./apps/desktop diff --git a/.github/workflows/pr-build-desktop.yml b/.github/workflows/pr-build-desktop.yml index 4907999b1c..bfceed9974 100644 --- a/.github/workflows/pr-build-desktop.yml +++ b/.github/workflows/pr-build-desktop.yml @@ -31,7 +31,7 @@ jobs: uses: ./.github/actions/setup-env - name: Install deps - run: pnpm install --config.enable-global-virtual-store=false + run: pnpm install env: NODE_OPTIONS: --max-old-space-size=8192 diff --git a/.github/workflows/release-desktop-beta.yml b/.github/workflows/release-desktop-beta.yml index 12d66c623c..4a45e5a86d 100644 --- a/.github/workflows/release-desktop-beta.yml +++ b/.github/workflows/release-desktop-beta.yml @@ -68,7 +68,7 @@ jobs: node-version: ${{ env.NODE_VERSION }} - name: Install deps - run: pnpm install --config.enable-global-virtual-store=false + run: pnpm install - name: Lint run: bun run lint diff --git a/.github/workflows/release-desktop-canary.yml b/.github/workflows/release-desktop-canary.yml index e03dfb87df..01e7a94536 100644 --- a/.github/workflows/release-desktop-canary.yml +++ b/.github/workflows/release-desktop-canary.yml @@ -200,7 +200,7 @@ jobs: node-version: ${{ env.NODE_VERSION }} - name: Install deps - run: pnpm install --config.enable-global-virtual-store=false + run: pnpm install - name: Lint run: bun run lint diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index cdc949a942..55baf26eb0 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -213,7 +213,7 @@ jobs: uses: ./.github/actions/setup-env - name: Install deps - run: pnpm i --config.enable-global-virtual-store=false + run: pnpm i - name: Lint run: bun run lint diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000000..999be96503 --- /dev/null +++ b/.npmrc @@ -0,0 +1,21 @@ +lockfile=false +resolution-mode=highest +dedupe-peer-dependents=true + +ignore-workspace-root-check=true +enable-pre-post-scripts=true + + +public-hoist-pattern[]=*@umijs/lint* +public-hoist-pattern[]=*changelog* +public-hoist-pattern[]=*commitlint* +public-hoist-pattern[]=*eslint* +public-hoist-pattern[]=*postcss* +public-hoist-pattern[]=*prettier* +public-hoist-pattern[]=*remark* +public-hoist-pattern[]=*semantic-release* +public-hoist-pattern[]=*stylelint* + +public-hoist-pattern[]=@auth/core +public-hoist-pattern[]=pdfjs-dist +public-hoist-pattern[]=@napi-rs/canvas-* diff --git a/apps/cli/.npmrc b/apps/cli/.npmrc new file mode 100644 index 0000000000..6cc5e46826 --- /dev/null +++ b/apps/cli/.npmrc @@ -0,0 +1,14 @@ +lockfile=false +ignore-workspace-root-check=true + +public-hoist-pattern[]=*@umijs/lint* +public-hoist-pattern[]=*unicorn* +public-hoist-pattern[]=*changelog* +public-hoist-pattern[]=*commitlint* +public-hoist-pattern[]=*eslint* +public-hoist-pattern[]=*postcss* +public-hoist-pattern[]=*prettier* +public-hoist-pattern[]=*remark* +public-hoist-pattern[]=*semantic-release* +public-hoist-pattern[]=*stylelint* + diff --git a/apps/cli/pnpm-workspace.yaml b/apps/cli/pnpm-workspace.yaml index 1603bbae2c..0ee104d019 100644 --- a/apps/cli/pnpm-workspace.yaml +++ b/apps/cli/pnpm-workspace.yaml @@ -4,18 +4,3 @@ packages: - '../../packages/local-file-shell' - '../../packages/file-loaders' - '.' - -lockfile: false -ignoreWorkspaceRootCheck: true - -publicHoistPattern: - - '*@umijs/lint*' - - '*unicorn*' - - '*changelog*' - - '*commitlint*' - - '*eslint*' - - '*postcss*' - - '*prettier*' - - '*remark*' - - '*semantic-release*' - - '*stylelint*' diff --git a/apps/desktop/.npmrc b/apps/desktop/.npmrc new file mode 100644 index 0000000000..6245cc6527 --- /dev/null +++ b/apps/desktop/.npmrc @@ -0,0 +1,20 @@ +lockfile=false +shamefully-hoist=true +ignore-workspace-root-check=true + +electron_mirror=https://npmmirror.com/mirrors/electron/ +electron_builder_binaries_mirror=https://npmmirror.com/mirrors/electron-builder-binaries/ + +public-hoist-pattern[]=*@umijs/lint* +public-hoist-pattern[]=*unicorn* +public-hoist-pattern[]=*changelog* +public-hoist-pattern[]=*commitlint* +public-hoist-pattern[]=*eslint* +public-hoist-pattern[]=*postcss* +public-hoist-pattern[]=*prettier* +public-hoist-pattern[]=*remark* +public-hoist-pattern[]=*semantic-release* +public-hoist-pattern[]=*stylelint* + +public-hoist-pattern[]=@auth/core +public-hoist-pattern[]=pdfjs-dist diff --git a/apps/desktop/electron-builder.mjs b/apps/desktop/electron-builder.mjs index ed012a118b..736a65a454 100644 --- a/apps/desktop/electron-builder.mjs +++ b/apps/desktop/electron-builder.mjs @@ -130,11 +130,6 @@ const config = { ); console.info('✅ CLI bundle copied to resources/bin/lobe-cli.js'); }, - // Native dependencies are rebuilt during the isolated desktop install and - // copied explicitly by the hooks here. Returning false marks node_modules as - // externally handled, so electron-builder does not invoke its pnpm collector - // during packaging. - beforeBuild: () => false, /** * AfterPack hook for post-processing: * 1. Copy native modules to asar.unpacked (resolving pnpm symlinks) @@ -295,6 +290,7 @@ const config = { { arch: [arch === 'arm64' ? 'arm64' : 'x64'], target: 'zip' }, ], }, + npmRebuild: true, nsis: { allowToChangeInstallationDirectory: true, artifactName: '${productName}-${version}-setup.${ext}', diff --git a/apps/desktop/native-deps.config.mjs b/apps/desktop/native-deps.config.mjs index 1ba59a763a..8cf353d0cd 100644 --- a/apps/desktop/native-deps.config.mjs +++ b/apps/desktop/native-deps.config.mjs @@ -40,39 +40,6 @@ export const nativeModules = [ 'node-screenshots', ]; -const optionalNativePackagePrefixes = new Map([ - ['@napi-rs/canvas', ['@napi-rs/canvas-']], - ['node-screenshots', ['node-screenshots-']], -]); - -const runtimeOptionalDependencies = new Map([['get-windows', ['@mapbox/node-pre-gyp']]]); - -const runtimeDependencyOverrides = new Map([ - // get-windows imports @mapbox/node-pre-gyp at runtime only for find(). - // Keep the narrow find() dependency path and avoid shipping its install, - // publish, and CLI dependency tree into app.asar. - ['@mapbox/node-pre-gyp', ['detect-libc', 'nopt', 'npmlog', 'semver']], -]); - -function getRuntimeDependencies(moduleName, dependencies) { - return runtimeDependencyOverrides.get(moduleName) || Object.keys(dependencies); -} - -function getRuntimeOptionalDependencies(moduleName, optionalDependencies) { - const prefixes = optionalNativePackagePrefixes.get(moduleName); - const dependencies = [...(runtimeOptionalDependencies.get(moduleName) || [])]; - - if (prefixes) { - dependencies.push( - ...Object.keys(optionalDependencies).filter((dep) => - prefixes.some((prefix) => dep.startsWith(prefix)), - ), - ); - } - - return dependencies; -} - /** * Recursively resolve all dependencies of a module * @param {string} moduleName - The module to resolve @@ -106,15 +73,13 @@ function resolveDependencies( const optionalDependencies = packageJson.optionalDependencies || {}; // Resolve regular dependencies - for (const dep of getRuntimeDependencies(moduleName, dependencies)) { + for (const dep of Object.keys(dependencies)) { resolveDependencies(dep, visited, nodeModulesPath); } - // Only include optional packages that are runtime native binaries for the - // module itself. Native packages can also list install-time toolchains such - // as node-gyp or node-pre-gyp as optional deps; those must not be copied or - // externalized into the packaged Electron app. - for (const dep of getRuntimeOptionalDependencies(moduleName, optionalDependencies)) { + // Also resolve optional dependencies (important for native modules like @napi-rs/canvas + // which have platform-specific binaries in optional deps) + for (const dep of Object.keys(optionalDependencies)) { resolveDependencies(dep, visited, nodeModulesPath); } } catch { diff --git a/apps/desktop/package.json b/apps/desktop/package.json index a2bfc26257..2033cf82d4 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -114,5 +114,17 @@ }, "optionalDependencies": { "node-mac-permissions": "^2.5.0" + }, + "pnpm": { + "onlyBuiltDependencies": [ + "@napi-rs/canvas", + "electron", + "electron-builder", + "node-mac-permissions" + ], + "overrides": { + "react": "19.2.4", + "react-dom": "19.2.4" + } } } diff --git a/apps/desktop/pnpm-workspace.yaml b/apps/desktop/pnpm-workspace.yaml index f5107810fa..d63bfb41b5 100644 --- a/apps/desktop/pnpm-workspace.yaml +++ b/apps/desktop/pnpm-workspace.yaml @@ -11,34 +11,3 @@ packages: - './stubs/business-const' - './stubs/types' - '.' - -lockfile: false -shamefullyHoist: true -ignoreWorkspaceRootCheck: true - -publicHoistPattern: - - '*@umijs/lint*' - - '*unicorn*' - - '*changelog*' - - '*commitlint*' - - '*eslint*' - - '*postcss*' - - '*prettier*' - - '*remark*' - - '*semantic-release*' - - '*stylelint*' - - '@auth/core' - - 'pdfjs-dist' - -allowBuilds: - '@napi-rs/canvas': true - 'electron': true - 'electron-builder': true - 'electron-winstaller': true - 'esbuild': true - 'get-windows': true - 'node-mac-permissions': true - -overrides: - 'react': 19.2.4 - 'react-dom': 19.2.4 diff --git a/package.json b/package.json index 0264acd039..808028bc48 100644 --- a/package.json +++ b/package.json @@ -536,9 +536,29 @@ "vite-tsconfig-paths": "^6.1.1", "vitest": "^3.2.4" }, - "packageManager": "pnpm@11.0.0+sha512.5bd187500e49cc6c3d891d973b432c02b844a5eb7209172c90a517a3ef4f579ed5c23d409b699e6a9dc418ff7b2b1890e63f6d74f1d3fc49848f37779c89c84c", + "packageManager": "pnpm@10.33.0+sha512.10568bb4a6afb58c9eb3630da90cc9516417abebd3fabbe6739f0ae795728da1491e9db5a544c76ad8eb7570f5c4bb3d6c637b2cb41bfdcdb47fa823c8649319", "publishConfig": { "access": "public", "registry": "https://registry.npmjs.org" + }, + "pnpm": { + "onlyBuiltDependencies": [ + "@lobehub/editor", + "ffmpeg-static" + ], + "overrides": { + "@react-pdf/image": "3.0.4", + "@types/react": "19.2.13", + "better-auth": "1.4.6", + "better-call": "1.1.8", + "drizzle-orm": "^0.45.1", + "fast-xml-parser": "5.4.2", + "lexical": "0.42.0", + "pdfjs-dist": "5.4.530", + "stylelint-config-clean-order": "7.0.0" + }, + "patchedDependencies": { + "@upstash/qstash": "patches/@upstash__qstash.patch" + } } } diff --git a/patches/@upstash__qstash.patch b/patches/@upstash__qstash.patch index 7950fcb9e0..b64d7ad866 100644 --- a/patches/@upstash__qstash.patch +++ b/patches/@upstash__qstash.patch @@ -1,8 +1,8 @@ -diff --git a/chunk-35B33QW3.mjs b/chunk-35B33QW3.mjs -index 06da5e4fb496772eceb471b3b51135e3e0c3574b..6b21ab7a4f3c3bb23b0c678f279f62496be81762 100644 ---- a/chunk-35B33QW3.mjs -+++ b/chunk-35B33QW3.mjs -@@ -1019,6 +1019,20 @@ var HttpClient = class { +diff --git a/chunk-RQPZUJXG.mjs b/chunk-RQPZUJXG.mjs +index d1a9b4a460efc59304ec30e6bc63a127f1aac6d6..4303089796e63297f79926fc9f9bd976029b1a8e 100644 +--- a/chunk-RQPZUJXG.mjs ++++ b/chunk-RQPZUJXG.mjs +@@ -326,6 +326,20 @@ var HttpClient = class { } if (response.status < 200 || response.status >= 300) { const body = await response.text(); @@ -23,7 +23,7 @@ index 06da5e4fb496772eceb471b3b51135e3e0c3574b..6b21ab7a4f3c3bb23b0c678f279f6249 throw new QstashError( body.length > 0 ? body : `Error: status=${response.status}`, response.status -@@ -2264,8 +2278,10 @@ var AutoExecutor = class _AutoExecutor { +@@ -1841,8 +1855,10 @@ var AutoExecutor = class _AutoExecutor { if (error instanceof QStashWorkflowAbort) { throw error; } diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index b6dbbacf73..73431d841b 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -4,69 +4,17 @@ packages: - e2e - apps/desktop/src/main -lockfile: false -resolutionMode: highest -dedupePeerDependents: true -ignoreWorkspaceRootCheck: true -enablePrePostScripts: true -enableGlobalVirtualStore: true - -publicHoistPattern: - - '*@umijs/lint*' - - '*changelog*' - - '*commitlint*' - - '*eslint*' - - '*postcss*' - - '*prettier*' - - '*remark*' - - '*semantic-release*' - - '*stylelint*' - - '@auth/core' - - 'pdfjs-dist' - - '@napi-rs/canvas-*' - -packageExtensions: - '@ant-design/pro-descriptions': - dependencies: - '@ant-design/icons': ^6.2.1 - '@azure-rest/ai-inference': - dependencies: - '@azure/core-util': ^1.13.1 - '@cucumber/cucumber': - dependencies: - tsx: ^4.21.0 - rc-util: - dependencies: - react: 19.2.4 - stylelint: - dependencies: - postcss-styled-syntax: ^0.7.1 - -allowBuilds: - '@lobehub/editor': true - '@vercel/speed-insights': true - core-js: true - electron: true - es5-ext: true - esbuild: true - 'ffmpeg-static': true - protobufjs: true - sharp: true - unrs-resolver: true +onlyBuiltDependencies: + - '@vercel/speed-insights' + - '@lobehub/editor' overrides: + jose: ^6.1.3 + stylelint-config-clean-order: 7.0.0 + pdfjs-dist: 5.4.530 + react: 19.2.4 + react-dom: 19.2.4 '@react-pdf/image': 3.0.4 - '@types/react': 19.2.13 - 'better-auth': 1.4.6 - 'better-call': 1.1.8 - 'drizzle-orm': ^0.45.1 - 'fast-xml-parser': 5.4.2 - 'jose': ^6.1.3 - 'lexical': 0.42.0 - 'pdfjs-dist': 5.4.530 - 'react': 19.2.4 - 'react-dom': 19.2.4 - 'stylelint-config-clean-order': 7.0.0 patchedDependencies: '@upstash/qstash': patches/@upstash__qstash.patch diff --git a/src/libs/next/config/define-config.ts b/src/libs/next/config/define-config.ts index d42ef95fa3..413438f728 100644 --- a/src/libs/next/config/define-config.ts +++ b/src/libs/next/config/define-config.ts @@ -1,67 +1,7 @@ -import { existsSync, realpathSync } from 'node:fs'; -import { createRequire } from 'node:module'; -import { dirname, relative, resolve, sep } from 'node:path'; - import { codeInspectorPlugin } from 'code-inspector-plugin'; import { type NextConfig } from 'next'; import { type Header, type Redirect } from 'next/dist/lib/load-custom-routes'; -const require = createRequire(import.meta.url); - -const getPathSegments = (value: string) => - resolve(value) - .split(/[\\/]+/) - .filter(Boolean); - -const getCommonDirectory = (paths: string[]) => { - const [firstPath = [], ...remainingPaths] = paths.map(getPathSegments); - const commonSegments: string[] = []; - - for (const [index, segment] of firstPath.entries()) { - if (remainingPaths.every((pathSegments) => pathSegments[index] === segment)) { - commonSegments.push(segment); - continue; - } - - break; - } - - return commonSegments.length > 0 ? resolve(sep, ...commonSegments) : process.cwd(); -}; - -const getTurbopackRoot = () => { - const nextPackageDirectory = dirname(require.resolve('next/package.json')); - - return getCommonDirectory([realpathSync(process.cwd()), realpathSync(nextPackageDirectory)]); -}; - -const resolvePackageDirectory = (packageName: string) => { - const candidateDirectories = [ - resolve(process.cwd(), 'node_modules', packageName), - resolve(process.cwd(), 'node_modules/.pnpm/node_modules', packageName), - ]; - - return candidateDirectories.find((directory) => existsSync(resolve(directory, 'package.json'))); -}; - -const toTurbopackAliasPath = (directory: string) => - `./${relative(process.cwd(), directory).replaceAll(sep, '/')}`; - -const createTurbopackPeerAliases = () => - Object.fromEntries( - [ - '@azure/core-util', - '@opentelemetry/context-async-hooks', - 'drizzle-orm', - 'vscode-jsonrpc', - 'vscode-languageserver-types', - ].flatMap((packageName) => { - const packageDirectory = resolvePackageDirectory(packageName); - - return packageDirectory ? [[packageName, toTurbopackAliasPath(packageDirectory)]] : []; - }), - ); - interface CustomNextConfig { experimental?: NextConfig['experimental']; headers?: Header[]; @@ -75,9 +15,6 @@ interface CustomNextConfig { export function defineConfig(config: CustomNextConfig) { const isProd = process.env.NODE_ENV === 'production'; const buildWithDocker = process.env.DOCKER === 'true'; - const { resolveAlias: customTurbopackResolveAlias, ...customTurbopackConfig } = - config.turbopack ?? {}; - const turbopackRoot = getTurbopackRoot(); const shouldUseCSP = process.env.ENABLED_CSP === '1'; @@ -428,12 +365,6 @@ export function defineConfig(config: CustomNextConfig) { transpilePackages: ['mermaid', 'better-auth-harmony'], turbopack: { - ...customTurbopackConfig, - resolveAlias: { - ...createTurbopackPeerAliases(), - ...customTurbopackResolveAlias, - }, - root: turbopackRoot, rules: { ...(isTest ? void 0 @@ -445,8 +376,8 @@ export function defineConfig(config: CustomNextConfig) { as: '*.js', loaders: ['raw-loader'], }, - ...customTurbopackConfig.rules, }, + ...config.turbopack, }, typescript: { diff --git a/vite.config.ts b/vite.config.ts index 7aa68efe07..eb83f70663 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,6 +1,5 @@ import { spawn } from 'node:child_process'; import fs from 'node:fs'; -import { builtinModules, createRequire } from 'node:module'; import path from 'node:path'; import type { PluginOption, ViteDevServer } from 'vite'; @@ -23,37 +22,6 @@ Object.assign(process.env, loadEnv(mode, process.cwd(), '')); const isDev = process.env.NODE_ENV !== 'production'; const platform = isMobile ? 'mobile' : 'web'; -const require = createRequire(import.meta.url); - -const isBareModuleId = (id: string) => - !id.startsWith('.') && !id.startsWith('/') && !id.includes('\0'); -const nodeBuiltinModules = new Set([ - ...builtinModules, - ...builtinModules.map((id) => `node:${id}`), -]); - -const globalVirtualStorePeerFallback = (): PluginOption => { - const fallbackPaths = [ - path.resolve(__dirname, 'node_modules'), - path.resolve(__dirname, 'node_modules/.pnpm/node_modules'), - ]; - - return { - enforce: 'post', - name: 'global-virtual-store-peer-fallback', - resolveId(source, importer) { - if (!importer || !isBareModuleId(source)) return; - if (nodeBuiltinModules.has(source)) return; - if (!importer.includes('/v11/links/')) return; - - try { - return require.resolve(source, { paths: fallbackPaths }); - } catch { - return; - } - }, - }; -}; const resolveCommandExecutable = (cmd: string) => { const pathValue = process.env.PATH; @@ -148,7 +116,6 @@ export default defineConfig({ }, optimizeDeps: sharedOptimizeDeps, plugins: [ - globalVirtualStorePeerFallback(), vercelSkewProtection(), viteEnvRestartKeys(['APP_URL']), ...sharedRendererPlugins({ platform }),