From bd24bd4c82c8441d7956324b4515622283098695 Mon Sep 17 00:00:00 2001 From: gsxdsm Date: Thu, 2 Jul 2026 15:20:17 -0700 Subject: [PATCH] fix(build): make root workspace build run on Windows MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two Windows-only breakages in the build path: - scripts/build-workspace.mjs: the "run as main" guard compared import.meta.url to `file://${process.argv[1]}`, which never matches on Windows (import.meta.url is file:///C:/… with forward slashes and a triple slash; argv[1] is C:\… with backslashes and no scheme). So `pnpm build` at the repo root silently no-opped (exit 0, no output, no dist) and packaging shipped empty/stale dist. Compare against pathToFileURL(process.argv[1]).href instead. - scripts/build-workspace.mjs runPlannedBuilds and packages/cli/tsup.config.ts runWorkspaceCommand spawned `pnpm` without shell:true; on Windows pnpm is a .cmd shim Node refuses to spawn without a shell (ENOENT/EINVAL, CVE-2024-27980), failing with `spawn pnpm ENOENT`. Pass shell on win32 (args are fixed repo build invocations with no shell metacharacters). Co-Authored-By: Claude Opus 4.8 --- packages/cli/tsup.config.ts | 9 +++++++++ scripts/build-workspace.mjs | 22 +++++++++++++++++++--- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/packages/cli/tsup.config.ts b/packages/cli/tsup.config.ts index f10b068199..652450eac9 100644 --- a/packages/cli/tsup.config.ts +++ b/packages/cli/tsup.config.ts @@ -200,6 +200,15 @@ function runWorkspaceCommand(command: string, args: string[], cwd: string, timeo cwd, stdio: "inherit", env: process.env, + /* + * FNXC:DesktopPackaging 2026-07-02-15:10: + * On Windows `pnpm` (and other npm bins) resolve to a `.cmd` shim that Node refuses to + * spawn without a shell (ENOENT / EINVAL since CVE-2024-27980). Without shell:true the CLI + * package build failed with `spawn pnpm ENOENT` on Windows at the "building @fusion/desktop" + * step. The command/args here are fixed repo build invocations (no untrusted input), so shell + * quoting is safe. + */ + shell: process.platform === "win32", }); const timer = setTimeout(() => { child.kill("SIGTERM"); diff --git a/scripts/build-workspace.mjs b/scripts/build-workspace.mjs index 01cb0854c3..90dca00533 100644 --- a/scripts/build-workspace.mjs +++ b/scripts/build-workspace.mjs @@ -7,7 +7,7 @@ Root builds may skip unchanged plugin workspaces to keep local and CI feedback f import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"; import path from "node:path"; import { spawnSync } from "node:child_process"; -import { fileURLToPath } from "node:url"; +import { fileURLToPath, pathToFileURL } from "node:url"; import fg from "fast-glob"; import YAML from "yaml"; import { @@ -370,7 +370,14 @@ export function runPlannedBuilds(plannedPackages, rootDir, spawnFn = spawnSync) if (plannedPackages.length === 0) return { status: 0, packageNames: [] }; const packageNames = plannedPackages.map((pkg) => pkg.name); const args = [...packageNames.flatMap((name) => ["--filter", name]), "build"]; - const result = spawnFn("pnpm", args, { cwd: rootDir, stdio: "inherit" }); + /* + * FNXC:WorkspaceBuild 2026-07-02-15:10: + * On Windows `pnpm` resolves to a `.cmd` shim; Node refuses to spawn .cmd/.bat without a + * shell (ENOENT / EINVAL since CVE-2024-27980). Without shell:true the root build failed + * with `spawn pnpm ENOENT` on Windows. The args are workspace filters + package names + * (no spaces or shell metacharacters), so shell quoting is safe. + */ + const result = spawnFn("pnpm", args, { cwd: rootDir, stdio: "inherit", shell: process.platform === "win32" }); return { status: result.status ?? 1, packageNames }; } @@ -422,6 +429,15 @@ export function main({ rootDir = repoRoot, spawnFn = spawnSync, gitFn = defaultG return 0; } -if (import.meta.url === `file://${process.argv[1]}`) { +/* + * FNXC:WorkspaceBuild 2026-07-02-15:10: + * Cross-platform "run as main" guard. The old `import.meta.url === \`file://${process.argv[1]}\`` + * check NEVER matched on Windows: import.meta.url is `file:///C:/…/build-workspace.mjs` + * (triple slash, forward slashes) while process.argv[1] is `C:\…\build-workspace.mjs` + * (backslashes, no scheme). So `pnpm build` at the repo root silently no-opped on Windows + * (exit 0, no output, no dist) — packaging then shipped empty/stale dist. Compare against the + * file URL of argv[1] so the guard is correct on Windows, macOS, and Linux. + */ +if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) { process.exit(main()); }