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";
1111import {
1212 BuildCommandFailedError,
1313 createStageWorkspaceConfig,
14 createStagePnpmConfig,
14 createStagePatchedDependencies,
1515 createBuildConfig,
1616 DESKTOP_ASAR_UNPACK,
1717 InvalidMacPasskeyRpDomainError,
......@@ -168,7 +168,7 @@ it.layer(NodeServices.layer)("build-desktop-artifact", (it) => {
168168
169169 it("carries only staged dependency patch metadata into staged desktop installs", () => {
170170 assert.deepStrictEqual(
171 createStagePnpmConfig(
171 createStagePatchedDependencies(
172172 {
173173 "@expo/metro-config@56.0.13": "patches/@expo%2Fmetro-config@56.0.13.patch",
174174 "@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) => {
183183 },
184184 ),
185185 {
186 patchedDependencies: {
187 "@ff-labs/fff-node@0.9.4": "patches/@ff-labs__fff-node@0.9.4.patch",
188 "@pierre/diffs@1.1.20": "patches/@pierre%2Fdiffs@1.1.20.patch",
189 "effect@4.0.0-beta.73": "patches/effect@4.0.0-beta.73.patch",
190 },
186 "@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 "effect@4.0.0-beta.73": "patches/effect@4.0.0-beta.73.patch",
191189 },
192190 );
193191
194 assert.equal(
195 createStagePnpmConfig(
192 assert.deepStrictEqual(
193 createStagePatchedDependencies(
196194 {
197195 "@expo/metro-config@56.0.13": "patches/@expo%2Fmetro-config@56.0.13.patch",
198196 },
199197 { effect: "4.0.0-beta.73" },
200198 ),
201 undefined,
199 {},
202200 );
203201 });
204202
205203 it("installs optional native dependencies for the target desktop architecture", () => {
206204 assert.deepStrictEqual(STAGE_INSTALL_ARGS, ["install", "--prod"]);
207 assert.deepStrictEqual(createStageWorkspaceConfig("mac", "x64"), {
205 assert.deepStrictEqual(createStageWorkspaceConfig({ platform: "mac", arch: "x64" }), {
208206 supportedArchitectures: {
209207 os: ["darwin"],
210208 cpu: ["x64"],
......@@ -212,21 +210,21 @@ it.layer(NodeServices.layer)("build-desktop-artifact", (it) => {
212210 });
213211 // Windows artifacts also bundle the same-architecture WSL (Linux, glibc) backend, so the
214212 // 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" }), {
216214 supportedArchitectures: {
217215 os: ["win32", "linux"],
218216 cpu: ["x64"],
219217 libc: ["glibc"],
220218 },
221219 });
222 assert.deepStrictEqual(createStageWorkspaceConfig("win", "arm64"), {
220 assert.deepStrictEqual(createStageWorkspaceConfig({ platform: "win", arch: "arm64" }), {
223221 supportedArchitectures: {
224222 os: ["win32", "linux"],
225223 cpu: ["arm64"],
226224 libc: ["glibc"],
227225 },
228226 });
229 assert.deepStrictEqual(createStageWorkspaceConfig("mac", "universal"), {
227 assert.deepStrictEqual(createStageWorkspaceConfig({ platform: "mac", arch: "universal" }), {
230228 supportedArchitectures: {
231229 os: ["darwin"],
232230 cpu: ["arm64", "x64"],
......@@ -234,6 +232,62 @@ it.layer(NodeServices.layer)("build-desktop-artifact", (it) => {
234232 });
235233 });
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
237291 it("unpacks the fff shared library for filesystem and FFI access", () => {
238292 assert.deepStrictEqual(DESKTOP_ASAR_UNPACK, ["node_modules/@ff-labs/fff-bin-*/**/*"]);
239293 });
scripts/build-desktop-artifact.ts+50-33
......@@ -40,6 +40,7 @@ const WorkspaceConfig = Schema.Struct({
4040 catalog: Schema.optional(Schema.Record(Schema.String, Schema.String)),
4141 overrides: Schema.optional(Schema.Record(Schema.String, Schema.String)),
4242 patchedDependencies: Schema.optional(Schema.Record(Schema.String, Schema.String)),
43 allowBuilds: Schema.optional(Schema.Record(Schema.String, Schema.Boolean)),
4344});
4445type WorkspaceConfig = typeof WorkspaceConfig.Type;
4546
......@@ -49,7 +50,14 @@ const StageWorkspaceConfig = Schema.Struct({
4950 cpu: Schema.Array(Schema.String),
5051 libc: Schema.optional(Schema.Array(Schema.String)),
5152 }),
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)),
5259});
60type StageWorkspaceConfig = typeof StageWorkspaceConfig.Type;
5361
5462const RepoRoot = Effect.service(Path.Path).pipe(
5563 Effect.flatMap((path) => path.fromFileUrl(new URL("..", import.meta.url))),
......@@ -563,10 +571,6 @@ interface StagePackageJson {
563571 readonly devDependencies: {
564572 readonly electron: string;
565573 };
566 readonly overrides: Record<string, unknown>;
567 readonly pnpm?: {
568 readonly patchedDependencies?: Record<string, string>;
569 };
570574}
571575
572576export const STAGE_INSTALL_ARGS = ["install", "--prod"] as const;
......@@ -876,10 +880,14 @@ const stageClerkPasskeyNativeBinaries = Effect.fn("stageClerkPasskeyNativeBinari
876880 }
877881});
878882
879export function createStageWorkspaceConfig(
880 platform: typeof BuildPlatform.Type,
881 arch: typeof BuildArch.Type,
882): typeof StageWorkspaceConfig.Type {
883export function createStageWorkspaceConfig(input: {
884 readonly platform: typeof BuildPlatform.Type;
885 readonly arch: typeof BuildArch.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;
883891 const hostOs = platform === "mac" ? "darwin" : platform === "win" ? "win32" : "linux";
884892 const hostCpu = arch === "universal" ? ["arm64", "x64"] : [arch];
885893 // Windows artifacts also bundle the same-architecture WSL Linux backend, which loads
......@@ -887,36 +895,37 @@ export function createStageWorkspaceConfig(
887895 // Pull the Linux (glibc) variants in addition to the host platform's so
888896 // they ship in the asar; without them the WSL backend crash-loops on require
889897 // ("Cannot find module '@yuuang/ffi-rs-linux-x64-gnu'").
890 if (platform === "win") {
891 return {
892 supportedArchitectures: {
893 os: Array.from(new Set([hostOs, "linux"])),
894 cpu: hostCpu,
895 libc: ["glibc"],
896 },
897 };
898 }
898 const supportedArchitectures =
899 platform === "win"
900 ? {
901 os: Array.from(new Set([hostOs, "linux"])),
902 cpu: hostCpu,
903 libc: ["glibc"],
904 }
905 : {
906 os: [hostOs],
907 cpu: hostCpu,
908 };
909
899910 return {
900 supportedArchitectures: {
901 os: [hostOs],
902 cpu: hostCpu,
903 },
911 supportedArchitectures,
912 ...(allowBuilds && Object.keys(allowBuilds).length > 0 ? { allowBuilds } : {}),
913 ...(patchedDependencies && Object.keys(patchedDependencies).length > 0
914 ? { patchedDependencies }
915 : {}),
916 ...(overrides && Object.keys(overrides).length > 0 ? { overrides } : {}),
904917 };
905918}
906919
907export function createStagePnpmConfig(
920export function createStagePatchedDependencies(
908921 patchedDependencies: Record<string, string>,
909922 dependencies: Record<string, unknown>,
910): StagePackageJson["pnpm"] | undefined {
911 const stagePatchedDependencies = Object.fromEntries(
923): Record<string, string> {
924 return Object.fromEntries(
912925 Object.entries(patchedDependencies).filter(([patchKey]) =>
913926 Object.hasOwn(dependencies, getPatchedDependencyPackageName(patchKey)),
914927 ),
915928 );
916
917 return Object.keys(stagePatchedDependencies).length > 0
918 ? { patchedDependencies: stagePatchedDependencies }
919 : undefined;
920929}
921930
922931function getPatchedDependencyPackageName(patchKey: string): string {
......@@ -1540,6 +1549,7 @@ const buildDesktopArtifact = Effect.fn("buildDesktopArtifact")(function* (
15401549 const workspaceCatalog = workspaceConfig.catalog ?? {};
15411550 const workspaceOverrides = workspaceConfig.overrides ?? {};
15421551 const workspacePatchedDependencies = workspaceConfig.patchedDependencies ?? {};
1552 const workspaceAllowBuilds = workspaceConfig.allowBuilds ?? {};
15431553
15441554 const platformConfig = PLATFORM_CONFIG[options.platform];
15451555 if (!platformConfig) {
......@@ -1709,7 +1719,10 @@ const buildDesktopArtifact = Effect.fn("buildDesktopArtifact")(function* (
17091719 )
17101720 : {}),
17111721 };
1712 const stagePnpmConfig = createStagePnpmConfig(workspacePatchedDependencies, stageDependencies);
1722 const stagePatchedDependencies = createStagePatchedDependencies(
1723 workspacePatchedDependencies,
1724 stageDependencies,
1725 );
17131726 const stagePackageJson: StagePackageJson = {
17141727 name: "t3code",
17151728 version: appVersion,
......@@ -1738,20 +1751,24 @@ const buildDesktopArtifact = Effect.fn("buildDesktopArtifact")(function* (
17381751 devDependencies: {
17391752 electron: electronVersion,
17401753 },
1741 overrides: resolvedOverrides,
1742 ...(stagePnpmConfig ? { pnpm: stagePnpmConfig } : {}),
17431754 };
17441755
17451756 const stagePackageJsonString = yield* encodeJsonString(stagePackageJson);
17461757 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 });
17481765 const stageWorkspaceConfigString = yield* encodeStageWorkspaceConfig(stageWorkspaceConfig);
17491766 yield* fs.writeFileString(
17501767 path.join(stageAppDir, "pnpm-workspace.yaml"),
17511768 stageWorkspaceConfigString,
17521769 );
17531770
1754 if (Object.keys(workspacePatchedDependencies).length > 0) {
1771 if (Object.keys(stagePatchedDependencies).length > 0) {
17551772 yield* fs.copy(path.join(repoRoot, "patches"), path.join(stageAppDir, "patches"));
17561773 }
17571774