authorgravatar for 107218376+Prgm-code@users.noreply.github.comPatricio Gómez Meneses <107218376+Prgm-code@users.noreply.github.com> 2026-07-08 23:55:33-04:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-07-09 03:55:33+00:00
log18a41388eb694a8590c2239424bf8ddec55c1c72
treefee8719334fd0aec0c197ec2735a96ca5d3bb572
parent0c665658544c9d60b29b59699631d7f496e914e6
signature Signed by PGP key B5690EEEBB952194

Fix desktop native optional dependency packaging (#3816)


2 files changed, 45 insertions(+), 11 deletions(-)

scripts/build-desktop-artifact.test.ts+15
...@@ -33,6 +33,7 @@ import {...@@ -33,6 +33,7 @@ import {
33 resolveGitHubPublishConfig,33 resolveGitHubPublishConfig,
34 resolveMockUpdateServerPort,34 resolveMockUpdateServerPort,
35 resolveMockUpdateServerUrl,35 resolveMockUpdateServerUrl,
36 resolvePackageManagerUserAgent,
36 stageLinuxIconSize,37 stageLinuxIconSize,
37 STAGE_INSTALL_ARGS,38 STAGE_INSTALL_ARGS,
38} from "./build-desktop-artifact.ts";39} from "./build-desktop-artifact.ts";
...@@ -208,6 +209,13 @@ it.layer(NodeServices.layer)("build-desktop-artifact", (it) => {...@@ -208,6 +209,13 @@ it.layer(NodeServices.layer)("build-desktop-artifact", (it) => {
208 cpu: ["x64"],209 cpu: ["x64"],
209 },210 },
210 });211 });
212 assert.deepStrictEqual(createStageWorkspaceConfig({ platform: "linux", arch: "x64" }), {
213 supportedArchitectures: {
214 os: ["linux"],
215 cpu: ["x64"],
216 libc: ["glibc"],
217 },
218 });
211 // Windows artifacts also bundle the same-architecture WSL (Linux, glibc) backend, so the219 // Windows artifacts also bundle the same-architecture WSL (Linux, glibc) backend, so the
212 // staged install must fetch its native optional deps (e.g. ffi-rs) too.220 // staged install must fetch its native optional deps (e.g. ffi-rs) too.
213 assert.deepStrictEqual(createStageWorkspaceConfig({ platform: "win", arch: "x64" }), {221 assert.deepStrictEqual(createStageWorkspaceConfig({ platform: "win", arch: "x64" }), {
...@@ -253,6 +261,7 @@ it.layer(NodeServices.layer)("build-desktop-artifact", (it) => {...@@ -253,6 +261,7 @@ it.layer(NodeServices.layer)("build-desktop-artifact", (it) => {
253 supportedArchitectures: {261 supportedArchitectures: {
254 os: ["linux"],262 os: ["linux"],
255 cpu: ["x64"],263 cpu: ["x64"],
264 libc: ["glibc"],
256 },265 },
257 allowBuilds: {266 allowBuilds: {
258 electron: true,267 electron: true,
...@@ -529,6 +538,12 @@ it.layer(NodeServices.layer)("build-desktop-artifact", (it) => {...@@ -529,6 +538,12 @@ it.layer(NodeServices.layer)("build-desktop-artifact", (it) => {
529 assert.equal(resolveMockUpdateServerUrl(4123), "http://localhost:4123");538 assert.equal(resolveMockUpdateServerUrl(4123), "http://localhost:4123");
530 });539 });
531540
541 it("derives the electron-builder package manager user agent from packageManager", () => {
542 assert.equal(resolvePackageManagerUserAgent("pnpm@11.10.0"), "pnpm/11.10.0");
543 assert.equal(resolvePackageManagerUserAgent(" yarn@4.9.2 "), "yarn/4.9.2");
544 assert.equal(resolvePackageManagerUserAgent("pnpm"), "pnpm");
545 });
546
532 it.effect("normalizes mock update server ports from env-style strings", () =>547 it.effect("normalizes mock update server ports from env-style strings", () =>
533 Effect.gen(function* () {548 Effect.gen(function* () {
534 assert.equal(yield* resolveMockUpdateServerPort(undefined), undefined);549 assert.equal(yield* resolveMockUpdateServerPort(undefined), undefined);
scripts/build-desktop-artifact.ts+30-11
...@@ -890,22 +890,27 @@ export function createStageWorkspaceConfig(input: {...@@ -890,22 +890,27 @@ export function createStageWorkspaceConfig(input: {
890 const { platform, arch, allowBuilds, patchedDependencies, overrides } = input;890 const { platform, arch, allowBuilds, patchedDependencies, overrides } = input;
891 const hostOs = platform === "mac" ? "darwin" : platform === "win" ? "win32" : "linux";891 const hostOs = platform === "mac" ? "darwin" : platform === "win" ? "win32" : "linux";
892 const hostCpu = arch === "universal" ? ["arm64", "x64"] : [arch];892 const hostCpu = arch === "universal" ? ["arm64", "x64"] : [arch];
893 // Windows artifacts also bundle the same-architecture WSL Linux backend, which loads893 // Linux AppImages and Windows WSL backends both execute a Linux/glibc Node
894 // Linux-native optional deps at runtime (e.g. @yuuang/ffi-rs-linux-x64-gnu).894 // process that loads Linux-native optional deps at runtime (e.g.
895 // Pull the Linux (glibc) variants in addition to the host platform's so895 // @yuuang/ffi-rs-linux-x64-gnu). Keep libc explicit so pnpm includes those
896 // they ship in the asar; without them the WSL backend crash-loops on require896 // optional packages in the staged production install.
897 // ("Cannot find module '@yuuang/ffi-rs-linux-x64-gnu'").
898 const supportedArchitectures =897 const supportedArchitectures =
899 platform === "win"898 platform === "linux"
900 ? {899 ? {
901 os: Array.from(new Set([hostOs, "linux"])),900 os: [hostOs],
902 cpu: hostCpu,901 cpu: hostCpu,
903 libc: ["glibc"],902 libc: ["glibc"],
904 }903 }
905 : {904 : platform === "win"
906 os: [hostOs],905 ? {
907 cpu: hostCpu,906 os: Array.from(new Set([hostOs, "linux"])),
908 };907 cpu: hostCpu,
908 libc: ["glibc"],
909 }
910 : {
911 os: [hostOs],
912 cpu: hostCpu,
913 };
909914
910 return {915 return {
911 supportedArchitectures,916 supportedArchitectures,
...@@ -1339,6 +1344,19 @@ export function resolveMockUpdateServerUrl(mockUpdateServerPort: number | undefi...@@ -1339,6 +1344,19 @@ export function resolveMockUpdateServerUrl(mockUpdateServerPort: number | undefi
1339 return `http://localhost:${mockUpdateServerPort ?? 3000}`;1344 return `http://localhost:${mockUpdateServerPort ?? 3000}`;
1340}1345}
13411346
1347// Electron Builder detects pnpm from npm_config_user_agent, whose value uses
1348// user-agent syntax (pnpm/11.10.0) rather than packageManager syntax
1349// (pnpm@11.10.0).
1350export function resolvePackageManagerUserAgent(packageManager: string): string {
1351 const trimmed = packageManager.trim();
1352 const versionSeparator = trimmed.lastIndexOf("@");
1353 if (versionSeparator <= 0 || versionSeparator === trimmed.length - 1) {
1354 return trimmed;
1355 }
1356
1357 return `${trimmed.slice(0, versionSeparator)}/${trimmed.slice(versionSeparator + 1)}`;
1358}
1359
1342export function resolveDesktopProductName(version: string): string {1360export function resolveDesktopProductName(version: string): string {
1343 return resolveDesktopUpdateChannel(version) === "nightly"1361 return resolveDesktopUpdateChannel(version) === "nightly"
1344 ? "T3 Code (Nightly)"1362 ? "T3 Code (Nightly)"
...@@ -1799,6 +1817,7 @@ const buildDesktopArtifact = Effect.fn("buildDesktopArtifact")(function* (...@@ -1799,6 +1817,7 @@ const buildDesktopArtifact = Effect.fn("buildDesktopArtifact")(function* (
1799 const buildEnv: NodeJS.ProcessEnv = {1817 const buildEnv: NodeJS.ProcessEnv = {
1800 ...process.env,1818 ...process.env,
1801 };1819 };
1820 buildEnv.npm_config_user_agent = resolvePackageManagerUserAgent(rootPackageJson.packageManager);
1802 for (const [key, value] of Object.entries(buildEnv)) {1821 for (const [key, value] of Object.entries(buildEnv)) {
1803 if (value === "") {1822 if (value === "") {
1804 delete buildEnv[key];1823 delete buildEnv[key];