authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-07-07 15:55:18-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-07-07 15:55:18-07:00
log03ac1f0cd9b5ee94c66f40d330912cb4db394024
tree4c56ddaa54d2819459b482b1cf22834233fae36b
parent7778a1cea72884e3bf7ced589b73c5f17fc4c17d
signature Signed by PGP key B5690EEEBB952194

fix(release): stage pnpm 11 allowBuilds for desktop installs (#3781)

Co-authored-by: Cursor Agent <cursoragent@cursor.com>

2 files changed, 118 insertions(+), 47 deletions(-)

scripts/build-desktop-artifact.test.ts+68-14
...@@ -11,7 +11,7 @@ import { ChildProcessSpawner } from "effect/unstable/process";...@@ -11,7 +11,7 @@ import { ChildProcessSpawner } from "effect/unstable/process";
11import {11import {
12 BuildCommandFailedError,12 BuildCommandFailedError,
13 createStageWorkspaceConfig,13 createStageWorkspaceConfig,
14 createStagePnpmConfig,14 createStagePatchedDependencies,
15 createBuildConfig,15 createBuildConfig,
16 DESKTOP_ASAR_UNPACK,16 DESKTOP_ASAR_UNPACK,
17 InvalidMacPasskeyRpDomainError,17 InvalidMacPasskeyRpDomainError,
...@@ -168,7 +168,7 @@ it.layer(NodeServices.layer)("build-desktop-artifact", (it) => {...@@ -168,7 +168,7 @@ it.layer(NodeServices.layer)("build-desktop-artifact", (it) => {
168168
169 it("carries only staged dependency patch metadata into staged desktop installs", () => {169 it("carries only staged dependency patch metadata into staged desktop installs", () => {
170 assert.deepStrictEqual(170 assert.deepStrictEqual(
171 createStagePnpmConfig(171 createStagePatchedDependencies(
172 {172 {
173 "@expo/metro-config@56.0.13": "patches/@expo%2Fmetro-config@56.0.13.patch",173 "@expo/metro-config@56.0.13": "patches/@expo%2Fmetro-config@56.0.13.patch",
174 "@ff-labs/fff-node@0.9.4": "patches/@ff-labs__fff-node@0.9.4.patch",174 "@ff-labs/fff-node@0.9.4": "patches/@ff-labs__fff-node@0.9.4.patch",
...@@ -183,28 +183,26 @@ it.layer(NodeServices.layer)("build-desktop-artifact", (it) => {...@@ -183,28 +183,26 @@ it.layer(NodeServices.layer)("build-desktop-artifact", (it) => {
183 },183 },
184 ),184 ),
185 {185 {
186 patchedDependencies: {186 "@ff-labs/fff-node@0.9.4": "patches/@ff-labs__fff-node@0.9.4.patch",
187 "@ff-labs/fff-node@0.9.4": "patches/@ff-labs__fff-node@0.9.4.patch",187 "@pierre/diffs@1.1.20": "patches/@pierre%2Fdiffs@1.1.20.patch",
188 "@pierre/diffs@1.1.20": "patches/@pierre%2Fdiffs@1.1.20.patch",188 "effect@4.0.0-beta.73": "patches/effect@4.0.0-beta.73.patch",
189 "effect@4.0.0-beta.73": "patches/effect@4.0.0-beta.73.patch",
190 },
191 },189 },
192 );190 );
193191
194 assert.equal(192 assert.deepStrictEqual(
195 createStagePnpmConfig(193 createStagePatchedDependencies(
196 {194 {
197 "@expo/metro-config@56.0.13": "patches/@expo%2Fmetro-config@56.0.13.patch",195 "@expo/metro-config@56.0.13": "patches/@expo%2Fmetro-config@56.0.13.patch",
198 },196 },
199 { effect: "4.0.0-beta.73" },197 { effect: "4.0.0-beta.73" },
200 ),198 ),
201 undefined,199 {},
202 );200 );
203 });201 });
204202
205 it("installs optional native dependencies for the target desktop architecture", () => {203 it("installs optional native dependencies for the target desktop architecture", () => {
206 assert.deepStrictEqual(STAGE_INSTALL_ARGS, ["install", "--prod"]);204 assert.deepStrictEqual(STAGE_INSTALL_ARGS, ["install", "--prod"]);
207 assert.deepStrictEqual(createStageWorkspaceConfig("mac", "x64"), {205 assert.deepStrictEqual(createStageWorkspaceConfig({ platform: "mac", arch: "x64" }), {
208 supportedArchitectures: {206 supportedArchitectures: {
209 os: ["darwin"],207 os: ["darwin"],
210 cpu: ["x64"],208 cpu: ["x64"],
...@@ -212,21 +210,21 @@ it.layer(NodeServices.layer)("build-desktop-artifact", (it) => {...@@ -212,21 +210,21 @@ it.layer(NodeServices.layer)("build-desktop-artifact", (it) => {
212 });210 });
213 // Windows artifacts also bundle the same-architecture WSL (Linux, glibc) backend, so the211 // Windows artifacts also bundle the same-architecture WSL (Linux, glibc) backend, so the
214 // staged install must fetch its native optional deps (e.g. ffi-rs) too.212 // staged install must fetch its native optional deps (e.g. ffi-rs) too.
215 assert.deepStrictEqual(createStageWorkspaceConfig("win", "x64"), {213 assert.deepStrictEqual(createStageWorkspaceConfig({ platform: "win", arch: "x64" }), {
216 supportedArchitectures: {214 supportedArchitectures: {
217 os: ["win32", "linux"],215 os: ["win32", "linux"],
218 cpu: ["x64"],216 cpu: ["x64"],
219 libc: ["glibc"],217 libc: ["glibc"],
220 },218 },
221 });219 });
222 assert.deepStrictEqual(createStageWorkspaceConfig("win", "arm64"), {220 assert.deepStrictEqual(createStageWorkspaceConfig({ platform: "win", arch: "arm64" }), {
223 supportedArchitectures: {221 supportedArchitectures: {
224 os: ["win32", "linux"],222 os: ["win32", "linux"],
225 cpu: ["arm64"],223 cpu: ["arm64"],
226 libc: ["glibc"],224 libc: ["glibc"],
227 },225 },
228 });226 });
229 assert.deepStrictEqual(createStageWorkspaceConfig("mac", "universal"), {227 assert.deepStrictEqual(createStageWorkspaceConfig({ platform: "mac", arch: "universal" }), {
230 supportedArchitectures: {228 supportedArchitectures: {
231 os: ["darwin"],229 os: ["darwin"],
232 cpu: ["arm64", "x64"],230 cpu: ["arm64", "x64"],
...@@ -234,6 +232,62 @@ it.layer(NodeServices.layer)("build-desktop-artifact", (it) => {...@@ -234,6 +232,62 @@ it.layer(NodeServices.layer)("build-desktop-artifact", (it) => {
234 });232 });
235 });233 });
236234
235 it("stages pnpm 11 allowBuilds and patchedDependencies in the workspace yaml", () => {
236 assert.deepStrictEqual(
237 createStageWorkspaceConfig({
238 platform: "linux",
239 arch: "x64",
240 allowBuilds: {
241 electron: true,
242 "node-pty": true,
243 "browser-tabs-lock": false,
244 },
245 patchedDependencies: {
246 "effect@4.0.0-beta.73": "patches/effect@4.0.0-beta.73.patch",
247 },
248 overrides: {
249 effect: "4.0.0-beta.73",
250 },
251 }),
252 {
253 supportedArchitectures: {
254 os: ["linux"],
255 cpu: ["x64"],
256 },
257 allowBuilds: {
258 electron: true,
259 "node-pty": true,
260 "browser-tabs-lock": false,
261 },
262 patchedDependencies: {
263 "effect@4.0.0-beta.73": "patches/effect@4.0.0-beta.73.patch",
264 },
265 overrides: {
266 effect: "4.0.0-beta.73",
267 },
268 },
269 );
270
271 // Empty maps must not be written — pnpm would still require reviewed
272 // packages if allowBuilds is present but incomplete, and omitting empty
273 // patchedDependencies keeps the stage yaml minimal.
274 assert.deepStrictEqual(
275 createStageWorkspaceConfig({
276 platform: "mac",
277 arch: "arm64",
278 allowBuilds: {},
279 patchedDependencies: {},
280 overrides: {},
281 }),
282 {
283 supportedArchitectures: {
284 os: ["darwin"],
285 cpu: ["arm64"],
286 },
287 },
288 );
289 });
290
237 it("unpacks the fff shared library for filesystem and FFI access", () => {291 it("unpacks the fff shared library for filesystem and FFI access", () => {
238 assert.deepStrictEqual(DESKTOP_ASAR_UNPACK, ["node_modules/@ff-labs/fff-bin-*/**/*"]);292 assert.deepStrictEqual(DESKTOP_ASAR_UNPACK, ["node_modules/@ff-labs/fff-bin-*/**/*"]);
239 });293 });
scripts/build-desktop-artifact.ts+50-33
...@@ -40,6 +40,7 @@ const WorkspaceConfig = Schema.Struct({...@@ -40,6 +40,7 @@ const WorkspaceConfig = Schema.Struct({
40 catalog: Schema.optional(Schema.Record(Schema.String, Schema.String)),40 catalog: Schema.optional(Schema.Record(Schema.String, Schema.String)),
41 overrides: Schema.optional(Schema.Record(Schema.String, Schema.String)),41 overrides: Schema.optional(Schema.Record(Schema.String, Schema.String)),
42 patchedDependencies: Schema.optional(Schema.Record(Schema.String, Schema.String)),42 patchedDependencies: Schema.optional(Schema.Record(Schema.String, Schema.String)),
43 allowBuilds: Schema.optional(Schema.Record(Schema.String, Schema.Boolean)),
43});44});
44type WorkspaceConfig = typeof WorkspaceConfig.Type;45type WorkspaceConfig = typeof WorkspaceConfig.Type;
4546
...@@ -49,7 +50,14 @@ const StageWorkspaceConfig = Schema.Struct({...@@ -49,7 +50,14 @@ const StageWorkspaceConfig = Schema.Struct({
49 cpu: Schema.Array(Schema.String),50 cpu: Schema.Array(Schema.String),
50 libc: Schema.optional(Schema.Array(Schema.String)),51 libc: Schema.optional(Schema.Array(Schema.String)),
51 }),52 }),
53 // pnpm 11 only reads these from pnpm-workspace.yaml (not package.json#pnpm).
54 // Without allowBuilds the staged `vp install --prod` fails with
55 // ERR_PNPM_IGNORED_BUILDS for packages that have lifecycle scripts.
56 allowBuilds: Schema.optional(Schema.Record(Schema.String, Schema.Boolean)),
57 patchedDependencies: Schema.optional(Schema.Record(Schema.String, Schema.String)),
58 overrides: Schema.optional(Schema.Record(Schema.String, Schema.String)),
52});59});
60type StageWorkspaceConfig = typeof StageWorkspaceConfig.Type;
5361
54const RepoRoot = Effect.service(Path.Path).pipe(62const RepoRoot = Effect.service(Path.Path).pipe(
55 Effect.flatMap((path) => path.fromFileUrl(new URL("..", import.meta.url))),63 Effect.flatMap((path) => path.fromFileUrl(new URL("..", import.meta.url))),
...@@ -563,10 +571,6 @@ interface StagePackageJson {...@@ -563,10 +571,6 @@ interface StagePackageJson {
563 readonly devDependencies: {571 readonly devDependencies: {
564 readonly electron: string;572 readonly electron: string;
565 };573 };
566 readonly overrides: Record<string, unknown>;
567 readonly pnpm?: {
568 readonly patchedDependencies?: Record<string, string>;
569 };
570}574}
571575
572export const STAGE_INSTALL_ARGS = ["install", "--prod"] as const;576export const STAGE_INSTALL_ARGS = ["install", "--prod"] as const;
...@@ -876,10 +880,14 @@ const stageClerkPasskeyNativeBinaries = Effect.fn("stageClerkPasskeyNativeBinari...@@ -876,10 +880,14 @@ const stageClerkPasskeyNativeBinaries = Effect.fn("stageClerkPasskeyNativeBinari
876 }880 }
877});881});
878882
879export function createStageWorkspaceConfig(883export function createStageWorkspaceConfig(input: {
880 platform: typeof BuildPlatform.Type,884 readonly platform: typeof BuildPlatform.Type;
881 arch: typeof BuildArch.Type,885 readonly arch: typeof BuildArch.Type;
882): typeof StageWorkspaceConfig.Type {886 readonly allowBuilds?: Record<string, boolean>;
887 readonly patchedDependencies?: Record<string, string>;
888 readonly overrides?: Record<string, string>;
889}): StageWorkspaceConfig {
890 const { platform, arch, allowBuilds, patchedDependencies, overrides } = input;
883 const hostOs = platform === "mac" ? "darwin" : platform === "win" ? "win32" : "linux";891 const hostOs = platform === "mac" ? "darwin" : platform === "win" ? "win32" : "linux";
884 const hostCpu = arch === "universal" ? ["arm64", "x64"] : [arch];892 const hostCpu = arch === "universal" ? ["arm64", "x64"] : [arch];
885 // Windows artifacts also bundle the same-architecture WSL Linux backend, which loads893 // Windows artifacts also bundle the same-architecture WSL Linux backend, which loads
...@@ -887,36 +895,37 @@ export function createStageWorkspaceConfig(...@@ -887,36 +895,37 @@ export function createStageWorkspaceConfig(
887 // Pull the Linux (glibc) variants in addition to the host platform's so895 // Pull the Linux (glibc) variants in addition to the host platform's so
888 // they ship in the asar; without them the WSL backend crash-loops on require896 // they ship in the asar; without them the WSL backend crash-loops on require
889 // ("Cannot find module '@yuuang/ffi-rs-linux-x64-gnu'").897 // ("Cannot find module '@yuuang/ffi-rs-linux-x64-gnu'").
890 if (platform === "win") {898 const supportedArchitectures =
891 return {899 platform === "win"
892 supportedArchitectures: {900 ? {
893 os: Array.from(new Set([hostOs, "linux"])),901 os: Array.from(new Set([hostOs, "linux"])),
894 cpu: hostCpu,902 cpu: hostCpu,
895 libc: ["glibc"],903 libc: ["glibc"],
896 },904 }
897 };905 : {
898 }906 os: [hostOs],
907 cpu: hostCpu,
908 };
909
899 return {910 return {
900 supportedArchitectures: {911 supportedArchitectures,
901 os: [hostOs],912 ...(allowBuilds && Object.keys(allowBuilds).length > 0 ? { allowBuilds } : {}),
902 cpu: hostCpu,913 ...(patchedDependencies && Object.keys(patchedDependencies).length > 0
903 },914 ? { patchedDependencies }
915 : {}),
916 ...(overrides && Object.keys(overrides).length > 0 ? { overrides } : {}),
904 };917 };
905}918}
906919
907export function createStagePnpmConfig(920export function createStagePatchedDependencies(
908 patchedDependencies: Record<string, string>,921 patchedDependencies: Record<string, string>,
909 dependencies: Record<string, unknown>,922 dependencies: Record<string, unknown>,
910): StagePackageJson["pnpm"] | undefined {923): Record<string, string> {
911 const stagePatchedDependencies = Object.fromEntries(924 return Object.fromEntries(
912 Object.entries(patchedDependencies).filter(([patchKey]) =>925 Object.entries(patchedDependencies).filter(([patchKey]) =>
913 Object.hasOwn(dependencies, getPatchedDependencyPackageName(patchKey)),926 Object.hasOwn(dependencies, getPatchedDependencyPackageName(patchKey)),
914 ),927 ),
915 );928 );
916
917 return Object.keys(stagePatchedDependencies).length > 0
918 ? { patchedDependencies: stagePatchedDependencies }
919 : undefined;
920}929}
921930
922function getPatchedDependencyPackageName(patchKey: string): string {931function getPatchedDependencyPackageName(patchKey: string): string {
...@@ -1540,6 +1549,7 @@ const buildDesktopArtifact = Effect.fn("buildDesktopArtifact")(function* (...@@ -1540,6 +1549,7 @@ const buildDesktopArtifact = Effect.fn("buildDesktopArtifact")(function* (
1540 const workspaceCatalog = workspaceConfig.catalog ?? {};1549 const workspaceCatalog = workspaceConfig.catalog ?? {};
1541 const workspaceOverrides = workspaceConfig.overrides ?? {};1550 const workspaceOverrides = workspaceConfig.overrides ?? {};
1542 const workspacePatchedDependencies = workspaceConfig.patchedDependencies ?? {};1551 const workspacePatchedDependencies = workspaceConfig.patchedDependencies ?? {};
1552 const workspaceAllowBuilds = workspaceConfig.allowBuilds ?? {};
15431553
1544 const platformConfig = PLATFORM_CONFIG[options.platform];1554 const platformConfig = PLATFORM_CONFIG[options.platform];
1545 if (!platformConfig) {1555 if (!platformConfig) {
...@@ -1709,7 +1719,10 @@ const buildDesktopArtifact = Effect.fn("buildDesktopArtifact")(function* (...@@ -1709,7 +1719,10 @@ const buildDesktopArtifact = Effect.fn("buildDesktopArtifact")(function* (
1709 )1719 )
1710 : {}),1720 : {}),
1711 };1721 };
1712 const stagePnpmConfig = createStagePnpmConfig(workspacePatchedDependencies, stageDependencies);1722 const stagePatchedDependencies = createStagePatchedDependencies(
1723 workspacePatchedDependencies,
1724 stageDependencies,
1725 );
1713 const stagePackageJson: StagePackageJson = {1726 const stagePackageJson: StagePackageJson = {
1714 name: "t3code",1727 name: "t3code",
1715 version: appVersion,1728 version: appVersion,
...@@ -1738,20 +1751,24 @@ const buildDesktopArtifact = Effect.fn("buildDesktopArtifact")(function* (...@@ -1738,20 +1751,24 @@ const buildDesktopArtifact = Effect.fn("buildDesktopArtifact")(function* (
1738 devDependencies: {1751 devDependencies: {
1739 electron: electronVersion,1752 electron: electronVersion,
1740 },1753 },
1741 overrides: resolvedOverrides,
1742 ...(stagePnpmConfig ? { pnpm: stagePnpmConfig } : {}),
1743 };1754 };
17441755
1745 const stagePackageJsonString = yield* encodeJsonString(stagePackageJson);1756 const stagePackageJsonString = yield* encodeJsonString(stagePackageJson);
1746 yield* fs.writeFileString(path.join(stageAppDir, "package.json"), `${stagePackageJsonString}\n`);1757 yield* fs.writeFileString(path.join(stageAppDir, "package.json"), `${stagePackageJsonString}\n`);
1747 const stageWorkspaceConfig = createStageWorkspaceConfig(options.platform, options.arch);1758 const stageWorkspaceConfig = createStageWorkspaceConfig({
1759 platform: options.platform,
1760 arch: options.arch,
1761 allowBuilds: workspaceAllowBuilds,
1762 patchedDependencies: stagePatchedDependencies,
1763 overrides: resolvedOverrides,
1764 });
1748 const stageWorkspaceConfigString = yield* encodeStageWorkspaceConfig(stageWorkspaceConfig);1765 const stageWorkspaceConfigString = yield* encodeStageWorkspaceConfig(stageWorkspaceConfig);
1749 yield* fs.writeFileString(1766 yield* fs.writeFileString(
1750 path.join(stageAppDir, "pnpm-workspace.yaml"),1767 path.join(stageAppDir, "pnpm-workspace.yaml"),
1751 stageWorkspaceConfigString,1768 stageWorkspaceConfigString,
1752 );1769 );
17531770
1754 if (Object.keys(workspacePatchedDependencies).length > 0) {1771 if (Object.keys(stagePatchedDependencies).length > 0) {
1755 yield* fs.copy(path.join(repoRoot, "patches"), path.join(stageAppDir, "patches"));1772 yield* fs.copy(path.join(repoRoot, "patches"), path.join(stageAppDir, "patches"));
1756 }1773 }
17571774