authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-09-13 21:24:20-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-09-13 21:24:20-07:00
log683aa87096899aa191e150429415ecc78966f538
tree7c9414892116ee106b0f6c44e025590d3dc9afca
parentd81278aa6b272bb3d3314266c6d17f22504d19d6
signature Signed by PGP key B5690EEEBB952194

build(desktop): bundle the main process and stage only its native externals (#11410)

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

4 files changed, 142 insertions(+), 45 deletions(-)

apps/desktop/vite.config.ts+12-1
...@@ -1,9 +1,18 @@...@@ -1,9 +1,18 @@
1import "vite-plus/test/config";1import "vite-plus/test/config";
2import { defineConfig } from "vite-plus";2import { defineConfig } from "vite-plus";
33
4import { isDesktopRuntimeExternalDependency } from "../../scripts/lib/desktop-external-packages.ts";
4import { loadRepoEnv } from "../../scripts/lib/public-config.ts";5import { loadRepoEnv } from "../../scripts/lib/public-config.ts";
56
6const repoEnv = loadRepoEnv();7const repoEnv = loadRepoEnv();
8
9// The main process is bundled the same way the server CLI is: every JS
10// dependency is inlined and only packages Node must load from disk stay
11// external. The packaged app then installs just those externals, instead of a
12// full production install of apps/desktop's dependency tree next to a server
13// bundle that already carries its own copy of the same libraries.
14const isMainProcessExternal = (id: string) =>
15 id === "electron" || id.startsWith("electron/") || isDesktopRuntimeExternalDependency(id);
7const shouldLaunchElectronAfterPack = process.env.T3CODE_DESKTOP_DEV === "1";16const shouldLaunchElectronAfterPack = process.env.T3CODE_DESKTOP_DEV === "1";
8const publicConfigDefine = {17const publicConfigDefine = {
9 __T3CODE_BUILD_CLERK_PUBLISHABLE_KEY__: JSON.stringify(18 __T3CODE_BUILD_CLERK_PUBLISHABLE_KEY__: JSON.stringify(
...@@ -55,7 +64,9 @@ export default defineConfig({...@@ -55,7 +64,9 @@ export default defineConfig({
55 ],64 ],
56 clean: true,65 clean: true,
57 deps: {66 deps: {
58 alwaysBundle: (id) => id.startsWith("@t3tools/"),67 alwaysBundle: (id) => !id.startsWith("node:") && !isMainProcessExternal(id),
68 neverBundle: isMainProcessExternal,
69 onlyBundle: false,
59 },70 },
60 ...(shouldLaunchElectronAfterPack ? { onSuccess: "node scripts/dev-electron.mjs" } : {}),71 ...(shouldLaunchElectronAfterPack ? { onSuccess: "node scripts/dev-electron.mjs" } : {}),
61 },72 },
scripts/build-desktop-artifact.test.ts+51-14
...@@ -28,6 +28,7 @@ import {...@@ -28,6 +28,7 @@ import {
28 DESKTOP_EXTRA_RESOURCES,28 DESKTOP_EXTRA_RESOURCES,
29 LINUX_CAPTURE_EXTRA_RESOURCES,29 LINUX_CAPTURE_EXTRA_RESOURCES,
30 LINUX_BROWSER_SECRET_EXTRA_RESOURCES,30 LINUX_BROWSER_SECRET_EXTRA_RESOURCES,
31 LINUX_FILE_EXCLUSIONS,
31 MAC_FILE_EXCLUSIONS,32 MAC_FILE_EXCLUSIONS,
32 InvalidMacPasskeyRpDomainError,33 InvalidMacPasskeyRpDomainError,
33 InvalidMacPasskeyPublishableKeyError,34 InvalidMacPasskeyPublishableKeyError,
...@@ -47,7 +48,7 @@ import {...@@ -47,7 +48,7 @@ import {
47 resolveClerkPasskeyNativeArtifacts,48 resolveClerkPasskeyNativeArtifacts,
48 resolveMacPasskeySigningConfiguration,49 resolveMacPasskeySigningConfiguration,
49 resolveDesktopRuntimeDependencies,50 resolveDesktopRuntimeDependencies,
50 resolveMacStageDependencies,51 resolveMergedStageDependencies,
51 resolveFffNativeDependencies,52 resolveFffNativeDependencies,
52 resolveBuildOptions,53 resolveBuildOptions,
53 resolveDesktopBuildIconAssets,54 resolveDesktopBuildIconAssets,
...@@ -367,26 +368,37 @@ it.layer(NodeServices.layer)("build-desktop-artifact", (it) => {...@@ -367,26 +368,37 @@ it.layer(NodeServices.layer)("build-desktop-artifact", (it) => {
367 ),368 ),
368 );369 );
369370
370 it("omits bundled workspace packages from staged desktop dependencies", () => {371 it("stages only the desktop main-process externals", () => {
371 assert.deepStrictEqual(372 assert.deepStrictEqual(
372 resolveDesktopRuntimeDependencies(373 resolveDesktopRuntimeDependencies(
373 {374 {
375 "@clerk/electron": "catalog:",
376 "@clerk/electron-passkeys": "catalog:",
377 "@crowecawcaw/xa11y": "0.13.0",
374 "@effect/platform-node": "catalog:",378 "@effect/platform-node": "catalog:",
379 "@napi-rs/keyring": "^1.3.0",
375 "@t3tools/contracts": "workspace:*",380 "@t3tools/contracts": "workspace:*",
376 "@t3tools/shared": "workspace:*",381 "@t3tools/shared": "workspace:*",
377 "@t3tools/ssh": "workspace:*",382 "dbus-next": "0.10.2",
378 "@t3tools/tailscale": "workspace:*",
379 effect: "catalog:",383 effect: "catalog:",
380 electron: "41.5.0",384 electron: "41.5.0",
385 "electron-updater": "^6.6.2",
386 "ffi-rs": "1.3.2",
387 "playwright-core": "1.60.0",
381 },388 },
382 {389 {
390 "@clerk/electron": "0.0.37",
391 "@clerk/electron-passkeys": "0.0.3",
383 "@effect/platform-node": "4.0.0-beta.59",392 "@effect/platform-node": "4.0.0-beta.59",
384 effect: "4.0.0-beta.59",393 effect: "4.0.0-beta.59",
385 },394 },
386 ),395 ),
387 {396 {
388 "@effect/platform-node": "4.0.0-beta.59",397 "@clerk/electron-passkeys": "0.0.3",
389 effect: "4.0.0-beta.59",398 "@crowecawcaw/xa11y": "0.13.0",
399 "@napi-rs/keyring": "^1.3.0",
400 "ffi-rs": "1.3.2",
401 "playwright-core": "1.60.0",
390 },402 },
391 );403 );
392 });404 });
...@@ -559,6 +571,8 @@ it.layer(NodeServices.layer)("build-desktop-artifact", (it) => {...@@ -559,6 +571,8 @@ it.layer(NodeServices.layer)("build-desktop-artifact", (it) => {
559571
560 assert.deepStrictEqual(DESKTOP_FILE_EXCLUSIONS, [572 assert.deepStrictEqual(DESKTOP_FILE_EXCLUSIONS, [
561 "!**/node_modules/@anthropic-ai/claude-agent-sdk-*/**/*",573 "!**/node_modules/@anthropic-ai/claude-agent-sdk-*/**/*",
574 "!**/*.map",
575 "!**/*.d.cts",
562 "!apps/desktop/resources/browser-secret",576 "!apps/desktop/resources/browser-secret",
563 "!apps/desktop/resources/browser-secret/**/*",577 "!apps/desktop/resources/browser-secret/**/*",
564 "!apps/desktop/prod-resources/browser-secret",578 "!apps/desktop/prod-resources/browser-secret",
...@@ -661,6 +675,7 @@ it.layer(NodeServices.layer)("build-desktop-artifact", (it) => {...@@ -661,6 +675,7 @@ it.layer(NodeServices.layer)("build-desktop-artifact", (it) => {
661 "**/node_modules/@anthropic-ai/claude-agent-sdk-*/**",675 "**/node_modules/@anthropic-ai/claude-agent-sdk-*/**",
662 "**/node_modules/.bin",676 "**/node_modules/.bin",
663 "**/node_modules/.bin/**",677 "**/node_modules/.bin/**",
678 "**/*.map",
664 ]);679 ]);
665 assert.deepStrictEqual(mac.dmg, {680 assert.deepStrictEqual(mac.dmg, {
666 title: "T3 Code (Alpha) 1.2.3 Installer",681 title: "T3 Code (Alpha) 1.2.3 Installer",
...@@ -679,7 +694,7 @@ it.layer(NodeServices.layer)("build-desktop-artifact", (it) => {...@@ -679,7 +694,7 @@ it.layer(NodeServices.layer)("build-desktop-artifact", (it) => {
679 { name: "T3 Code", schemes: ["t3code", "t3code-dev"] },694 { name: "T3 Code", schemes: ["t3code", "t3code-dev"] },
680 ]);695 ]);
681 assert.deepStrictEqual(mac.files, [...DESKTOP_FILE_EXCLUSIONS, ...MAC_FILE_EXCLUSIONS]);696 assert.deepStrictEqual(mac.files, [...DESKTOP_FILE_EXCLUSIONS, ...MAC_FILE_EXCLUSIONS]);
682 assert.deepStrictEqual(linux.files, DESKTOP_FILE_EXCLUSIONS);697 assert.deepStrictEqual(linux.files, [...DESKTOP_FILE_EXCLUSIONS, ...LINUX_FILE_EXCLUSIONS]);
683 assert.deepStrictEqual(win.files, DESKTOP_FILE_EXCLUSIONS);698 assert.deepStrictEqual(win.files, DESKTOP_FILE_EXCLUSIONS);
684 assert.deepStrictEqual(winWithoutWslPrebuild.files, win.files);699 assert.deepStrictEqual(winWithoutWslPrebuild.files, win.files);
685 assert.notProperty(mac.mac as Record<string, unknown>, "sign");700 assert.notProperty(mac.mac as Record<string, unknown>, "sign");
...@@ -690,11 +705,15 @@ it.layer(NodeServices.layer)("build-desktop-artifact", (it) => {...@@ -690,11 +705,15 @@ it.layer(NodeServices.layer)("build-desktop-artifact", (it) => {
690 }).pipe(Effect.provide(ConfigProvider.layer(ConfigProvider.fromEnv({ env: {} })))),705 }).pipe(Effect.provide(ConfigProvider.layer(ConfigProvider.fromEnv({ env: {} })))),
691 );706 );
692707
693 it("excludes Windows terminal binaries only from macOS packages", () => {708 it("excludes foreign node-pty prebuilds from macOS and Linux packages", () => {
694 assert.deepStrictEqual(MAC_FILE_EXCLUSIONS, [709 assert.deepStrictEqual(MAC_FILE_EXCLUSIONS, [
695 "!**/node_modules/node-pty/prebuilds/win32-*/**/*",710 "!**/node_modules/node-pty/prebuilds/win32-*/**/*",
696 "!**/node_modules/node-pty/third_party/conpty/**/*",711 "!**/node_modules/node-pty/third_party/conpty/**/*",
697 ]);712 ]);
713 assert.deepStrictEqual(LINUX_FILE_EXCLUSIONS, [
714 ...MAC_FILE_EXCLUSIONS,
715 "!**/node_modules/node-pty/prebuilds/darwin-*/**/*",
716 ]);
698 });717 });
699718
700 it("unpacks native binaries while keeping their JavaScript and metadata archived", () => {719 it("unpacks native binaries while keeping their JavaScript and metadata archived", () => {
...@@ -725,9 +744,10 @@ it.layer(NodeServices.layer)("build-desktop-artifact", (it) => {...@@ -725,9 +744,10 @@ it.layer(NodeServices.layer)("build-desktop-artifact", (it) => {
725 }744 }
726 });745 });
727746
728 it("stages only server runtime externals in macOS packages", () => {747 it("stages only the externals of both bundles in merged packages", () => {
729 assert.deepStrictEqual(748 assert.deepStrictEqual(
730 resolveMacStageDependencies({749 resolveMergedStageDependencies({
750 platform: "mac",
731 serverDependencies: {751 serverDependencies: {
732 "@anthropic-ai/claude-agent-sdk": "^0.3.170",752 "@anthropic-ai/claude-agent-sdk": "^0.3.170",
733 "@ff-labs/fff-node": "0.9.4",753 "@ff-labs/fff-node": "0.9.4",
...@@ -737,8 +757,8 @@ it.layer(NodeServices.layer)("build-desktop-artifact", (it) => {...@@ -737,8 +757,8 @@ it.layer(NodeServices.layer)("build-desktop-artifact", (it) => {
737 "node-pty": "1.1.0",757 "node-pty": "1.1.0",
738 },758 },
739 desktopDependencies: {759 desktopDependencies: {
740 "@clerk/electron": "0.0.34",760 "@napi-rs/keyring": "1.3.0",
741 effect: "4.0.0-beta.103",761 "playwright-core": "1.60.0",
742 },762 },
743 arch: "arm64",763 arch: "arm64",
744 fffNodeVersion: "0.9.4",764 fffNodeVersion: "0.9.4",
...@@ -747,11 +767,28 @@ it.layer(NodeServices.layer)("build-desktop-artifact", (it) => {...@@ -747,11 +767,28 @@ it.layer(NodeServices.layer)("build-desktop-artifact", (it) => {
747 "@ff-labs/fff-node": "0.9.4",767 "@ff-labs/fff-node": "0.9.4",
748 "msgpackr-extract": "3.0.4",768 "msgpackr-extract": "3.0.4",
749 "node-pty": "1.1.0",769 "node-pty": "1.1.0",
750 "@clerk/electron": "0.0.34",770 "@napi-rs/keyring": "1.3.0",
751 effect: "4.0.0-beta.103",771 "playwright-core": "1.60.0",
752 "@ff-labs/fff-bin-darwin-arm64": "0.9.4",772 "@ff-labs/fff-bin-darwin-arm64": "0.9.4",
753 },773 },
754 );774 );
775
776 assert.deepStrictEqual(
777 resolveMergedStageDependencies({
778 platform: "linux",
779 serverDependencies: { "@ff-labs/fff-node": "0.9.4", "node-pty": "1.1.0", effect: "4.0.0" },
780 desktopDependencies: { "@crowecawcaw/xa11y": "0.13.0" },
781 arch: "x64",
782 fffNodeVersion: "0.9.4",
783 }),
784 {
785 "@ff-labs/fff-node": "0.9.4",
786 "node-pty": "1.1.0",
787 "@crowecawcaw/xa11y": "0.13.0",
788 "@ff-labs/fff-bin-linux-x64-gnu": "0.9.4",
789 "@ff-labs/fff-bin-linux-x64-musl": "0.9.4",
790 },
791 );
755 });792 });
756793
757 it("excludes node-pty binaries for the other Windows architecture", () => {794 it("excludes node-pty binaries for the other Windows architecture", () => {
scripts/build-desktop-artifact.ts+41-30
...@@ -34,6 +34,7 @@ import {...@@ -34,6 +34,7 @@ import {
34 selectCliRuntimeExternalDependencies,34 selectCliRuntimeExternalDependencies,
35} from "./lib/cli-external-packages.ts";35} from "./lib/cli-external-packages.ts";
36import { loadRepoEnv } from "./lib/public-config.ts";36import { loadRepoEnv } from "./lib/public-config.ts";
37import { selectDesktopRuntimeExternalDependencies } from "./lib/desktop-external-packages.ts";
37import { resolveCatalogDependencies } from "./lib/resolve-catalog.ts";38import { resolveCatalogDependencies } from "./lib/resolve-catalog.ts";
3839
39import * as NodeRuntime from "@effect/platform-node/NodeRuntime";40import * as NodeRuntime from "@effect/platform-node/NodeRuntime";
...@@ -959,6 +960,10 @@ export const DESKTOP_FILE_EXCLUSIONS = [...@@ -959,6 +960,10 @@ export const DESKTOP_FILE_EXCLUSIONS = [
959 // so the SDK's optional platform packages (each a ~200MB bundled executable)960 // so the SDK's optional platform packages (each a ~200MB bundled executable)
960 // are dead weight. The trailing dash keeps the SDK's own JS package.961 // are dead weight. The trailing dash keeps the SDK's own JS package.
961 "!**/node_modules/@anthropic-ai/claude-agent-sdk-*/**/*",962 "!**/node_modules/@anthropic-ai/claude-agent-sdk-*/**/*",
963 // Nothing in the packaged app enables source maps or serves them: the web
964 // client's maps alone were 50 MB of app.asar that no request ever read.
965 "!**/*.map",
966 "!**/*.d.cts",
962 "!apps/desktop/resources/browser-secret",967 "!apps/desktop/resources/browser-secret",
963 "!apps/desktop/resources/browser-secret/**/*",968 "!apps/desktop/resources/browser-secret/**/*",
964 "!apps/desktop/prod-resources/browser-secret",969 "!apps/desktop/prod-resources/browser-secret",
...@@ -978,6 +983,12 @@ export const MAC_FILE_EXCLUSIONS = [...@@ -978,6 +983,12 @@ export const MAC_FILE_EXCLUSIONS = [
978 "!**/node_modules/node-pty/prebuilds/win32-*/**/*",983 "!**/node_modules/node-pty/prebuilds/win32-*/**/*",
979 "!**/node_modules/node-pty/third_party/conpty/**/*",984 "!**/node_modules/node-pty/third_party/conpty/**/*",
980] as const;985] as const;
986// Linux builds node-pty from source, so every prebuild in the package is for
987// another platform (58 MB of it Windows debug symbols).
988export const LINUX_FILE_EXCLUSIONS = [
989 ...MAC_FILE_EXCLUSIONS,
990 "!**/node_modules/node-pty/prebuilds/darwin-*/**/*",
991] as const;
981992
982// node-pty publishes both Darwin prebuilds in one package. Single-architecture993// node-pty publishes both Darwin prebuilds in one package. Single-architecture
983// apps only need the native target; universal apps need both. An omitted arch994// apps only need the native target; universal apps need both. An omitted arch
...@@ -1013,6 +1024,7 @@ export const WINDOWS_SERVER_ASAR_IGNORE_GLOBS = [...@@ -1013,6 +1024,7 @@ export const WINDOWS_SERVER_ASAR_IGNORE_GLOBS = [
1013 "**/node_modules/@anthropic-ai/claude-agent-sdk-*/**",1024 "**/node_modules/@anthropic-ai/claude-agent-sdk-*/**",
1014 "**/node_modules/.bin",1025 "**/node_modules/.bin",
1015 "**/node_modules/.bin/**",1026 "**/node_modules/.bin/**",
1027 "**/*.map",
1016] as const;1028] as const;
10171029
1018export function resolveWindowsServerAsarIgnoreGlobs(arch: typeof BuildArch.Type) {1030export function resolveWindowsServerAsarIgnoreGlobs(arch: typeof BuildArch.Type) {
...@@ -1357,7 +1369,10 @@ export function resolveFffNativeDependencies(...@@ -1357,7 +1369,10 @@ export function resolveFffNativeDependencies(
1357 );1369 );
1358}1370}
13591371
1360export function resolveMacStageDependencies(input: {1372// macOS and Linux run both processes from one app.asar, so the stage installs
1373// the union of what each bundle leaves external and nothing else.
1374export function resolveMergedStageDependencies(input: {
1375 readonly platform: "mac" | "linux";
1361 readonly serverDependencies: Record<string, string>;1376 readonly serverDependencies: Record<string, string>;
1362 readonly desktopDependencies: Record<string, string>;1377 readonly desktopDependencies: Record<string, string>;
1363 readonly arch: typeof BuildArch.Type;1378 readonly arch: typeof BuildArch.Type;
...@@ -1366,7 +1381,7 @@ export function resolveMacStageDependencies(input: {...@@ -1366,7 +1381,7 @@ export function resolveMacStageDependencies(input: {
1366 return {1381 return {
1367 ...selectCliRuntimeExternalDependencies(input.serverDependencies),1382 ...selectCliRuntimeExternalDependencies(input.serverDependencies),
1368 ...input.desktopDependencies,1383 ...input.desktopDependencies,
1369 ...resolveFffNativeDependencies("mac", input.arch, input.fffNodeVersion),1384 ...resolveFffNativeDependencies(input.platform, input.arch, input.fffNodeVersion),
1370 };1385 };
1371}1386}
13721387
...@@ -2551,6 +2566,11 @@ function validateBundledClientAssets(clientDir: string) {...@@ -2551,6 +2566,11 @@ function validateBundledClientAssets(clientDir: string) {
2551 });2566 });
2552}2567}
25532568
2569// The main-process bundle inlines every JS dependency (see
2570// apps/desktop/vite.config.ts), so the packaged app only installs the packages
2571// that bundle leaves external: native addons and playwright-core. Everything
2572// else already lives inside dist-electron and would only duplicate what the
2573// server bundle carries too.
2554export function resolveDesktopRuntimeDependencies(2574export function resolveDesktopRuntimeDependencies(
2555 dependencies: Record<string, string> | undefined,2575 dependencies: Record<string, string> | undefined,
2556 catalog: Record<string, string>,2576 catalog: Record<string, string>,
...@@ -2559,14 +2579,11 @@ export function resolveDesktopRuntimeDependencies(...@@ -2559,14 +2579,11 @@ export function resolveDesktopRuntimeDependencies(
2559 return {};2579 return {};
2560 }2580 }
25612581
2562 const runtimeDependencies = Object.fromEntries(2582 return resolveCatalogDependencies(
2563 Object.entries(dependencies).filter(2583 selectDesktopRuntimeExternalDependencies(dependencies),
2564 ([dependencyName, dependencySpec]) =>2584 catalog,
2565 dependencyName !== "electron" && !dependencySpec.startsWith("workspace:"),2585 "apps/desktop",
2566 ),
2567 );2586 );
2568
2569 return resolveCatalogDependencies(runtimeDependencies, catalog, "apps/desktop");
2570}2587}
25712588
2572export const resolveGitHubPublishConfig = Effect.fn("resolveGitHubPublishConfig")(function* (2589export const resolveGitHubPublishConfig = Effect.fn("resolveGitHubPublishConfig")(function* (
...@@ -2672,7 +2689,11 @@ export const createBuildConfig = Effect.fn("createBuildConfig")(function* (...@@ -2672,7 +2689,11 @@ export const createBuildConfig = Effect.fn("createBuildConfig")(function* (
2672 electronLanguages: [...DESKTOP_ELECTRON_LANGUAGES],2689 electronLanguages: [...DESKTOP_ELECTRON_LANGUAGES],
2673 files: [2690 files: [
2674 ...DESKTOP_FILE_EXCLUSIONS,2691 ...DESKTOP_FILE_EXCLUSIONS,
2675 ...(platform === "mac" ? resolveMacFileExclusions(arch) : []),2692 ...(platform === "mac"
2693 ? resolveMacFileExclusions(arch)
2694 : platform === "linux"
2695 ? LINUX_FILE_EXCLUSIONS
2696 : []),
2676 ],2697 ],
2677 directories: {2698 directories: {
2678 buildResources: "apps/desktop/resources",2699 buildResources: "apps/desktop/resources",
...@@ -3735,29 +3756,19 @@ const buildDesktopArtifact = Effect.fn("buildDesktopArtifact")(function* (...@@ -3735,29 +3756,19 @@ const buildDesktopArtifact = Effect.fn("buildDesktopArtifact")(function* (
3735 }3756 }
37363757
3737 // Windows splits dependencies per process: app.asar carries only the3758 // Windows splits dependencies per process: app.asar carries only the
3738 // desktop main-process runtime deps, while the server bundle's deps live in3759 // desktop main-process externals, while the server bundle's externals live
3739 // the server.asar sidecar (see stageWindowsServerSidecar). macOS adds only3760 // in the server.asar sidecar (see stageWindowsServerSidecar). macOS and
3740 // server packages that remain external to its merged app.asar. Linux retains3761 // Linux merge both sets into one app.asar.
3741 // its existing full dependency tree.
3742 const stageDependencies =3762 const stageDependencies =
3743 options.platform === "win"3763 options.platform === "win"
3744 ? { ...resolvedDesktopRuntimeDependencies }3764 ? { ...resolvedDesktopRuntimeDependencies }
3745 : options.platform === "mac"3765 : resolveMergedStageDependencies({
3746 ? resolveMacStageDependencies({3766 platform: options.platform,
3747 serverDependencies: resolvedServerDependencies,3767 serverDependencies: resolvedServerDependencies,
3748 desktopDependencies: resolvedDesktopRuntimeDependencies,3768 desktopDependencies: resolvedDesktopRuntimeDependencies,
3749 arch: options.arch,3769 arch: options.arch,
3750 fffNodeVersion: serverPackageJson.dependencies["@ff-labs/fff-node"],3770 fffNodeVersion: serverPackageJson.dependencies["@ff-labs/fff-node"],
3751 })3771 });
3752 : {
3753 ...resolvedServerDependencies,
3754 ...resolvedDesktopRuntimeDependencies,
3755 ...resolveFffNativeDependencies(
3756 options.platform,
3757 options.arch,
3758 serverPackageJson.dependencies["@ff-labs/fff-node"],
3759 ),
3760 };
3761 const stagePatchedDependencies = createStagePatchedDependencies(3772 const stagePatchedDependencies = createStagePatchedDependencies(
3762 workspacePatchedDependencies,3773 workspacePatchedDependencies,
3763 stageDependencies,3774 stageDependencies,
scripts/lib/desktop-external-packages.ts created+38
...@@ -0,0 +1,38 @@
1/**
2 * Packages the desktop main-process bundle must NOT inline.
3 *
4 * The desktop bundle follows the same policy as the server CLI bundle (see
5 * cli-external-packages.ts): everything is inlined except what Node has to
6 * load from the real filesystem. Both `apps/desktop/vite.config.ts` and the
7 * artifact stage in scripts/build-desktop-artifact.ts derive from this list,
8 * so a package that is external is also the only kind of package the staged
9 * production install carries. Anything not listed here ships inside
10 * `dist-electron/*.cjs` and has no `node_modules` presence at all.
11 *
12 * Entries are matched as prefixes so platform-specific siblings are covered.
13 */
14export const DESKTOP_RUNTIME_EXTERNAL_PREFIXES = [
15 // Native addons and the wrappers that dlopen them by real path.
16 "@napi-rs/keyring",
17 "@crowecawcaw/xa11y",
18 "@clerk/electron-passkeys",
19 "ffi-rs",
20 "@yuuang/",
21 // Reads its own bundle from disk by resolving `playwright-core/package.json`
22 // at runtime and ships the browser driver alongside; there is nothing to
23 // gain from inlining a 10 MB file the code re-reads as text.
24 "playwright-core",
25] as const;
26
27export function isDesktopRuntimeExternalDependency(id: string): boolean {
28 return DESKTOP_RUNTIME_EXTERNAL_PREFIXES.some((prefix) => id.startsWith(prefix));
29}
30
31/** Select the desktop dependency roots whose runtime closure the stage must install. */
32export function selectDesktopRuntimeExternalDependencies(
33 dependencies: Readonly<Record<string, string>>,
34): Record<string, string> {
35 return Object.fromEntries(
36 Object.entries(dependencies).filter(([name]) => isDesktopRuntimeExternalDependency(name)),
37 );
38}