Files
fusion/packages/desktop/scripts/workspace-tools.ts
gsxdsm 4148f43ce4 fix(release): honor --publish never, guard Windows signing, fix desktop spawn
Second pass after the cache/arch fix unblocked `pnpm build` and surfaced
later-stage failures:

- Desktop packaging called `pnpm --filter @fusion/desktop dist:mac -- <args>`,
  but pnpm leaks the `--` separator into the script args. electron-builder
  stops parsing at `--`, so `--publish never` was ignored — it auto-published
  to api.github.com/repos/gsxdsm/fusion/releases and 404'd. The same leak
  dropped Linux's `--x64 --arm64`. Switch all four desktop packaging steps to
  `pnpm --filter @fusion/desktop exec electron-builder ...`, which forwards
  args cleanly (verified locally).
- Windows CLI signing now skips when WINDOWS_CERTIFICATE_BASE64 is absent,
  mirroring the macOS guard (was hard-failing the bun-windows-x64 job).
- Desktop build spawns workspace .cmd bins with shell:true on Windows; Node
  rejects .cmd/.bat spawns without a shell (EINVAL) since CVE-2024-27980,
  which broke `@fusion/desktop build` on the Windows runner.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-30 00:30:39 -07:00

60 lines
2.2 KiB
TypeScript

import { spawn } from "node:child_process";
import { dirname, resolve } from "node:path";
import { existsSync } from "node:fs";
import { fileURLToPath } from "node:url";
const __dirname = dirname(fileURLToPath(import.meta.url));
export const packageRoot = resolve(__dirname, "..");
export const workspaceRoot = resolve(packageRoot, "..", "..");
function resolveBin(command: string, cwd: string): string {
const suffix = process.platform === "win32" ? ".cmd" : "";
const localBin = resolve(cwd, "node_modules", ".bin", `${command}${suffix}`);
if (existsSync(localBin)) {
return localBin;
}
return resolve(workspaceRoot, "node_modules", ".bin", `${command}${suffix}`);
}
export function runWorkspaceBin(command: string, args: string[], cwd: string): Promise<void> {
return new Promise((resolvePromise, rejectPromise) => {
const child = spawn(resolveBin(command, cwd), args, {
cwd,
stdio: "inherit",
env: process.env,
// On Windows the resolved bin is a .cmd shim; Node refuses to spawn
// .cmd/.bat without a shell (EINVAL) since CVE-2024-27980. resolveBin
// produces an absolute, space-free path, so shell quoting is safe here.
shell: process.platform === "win32",
});
child.on("error", rejectPromise);
child.on("exit", (code) => {
if (code === 0) {
resolvePromise();
return;
}
rejectPromise(new Error(`${command} ${args.join(" ")} exited with code ${code ?? "unknown"}`));
});
});
}
export async function buildCore(): Promise<void> {
await runWorkspaceBin("tsc", [], resolve(workspaceRoot, "packages", "core"));
}
export async function buildDashboard(): Promise<void> {
const dashboardRoot = resolve(workspaceRoot, "packages", "dashboard");
await runWorkspaceBin("vite", ["build"], dashboardRoot);
await runWorkspaceBin("tsc", [], dashboardRoot);
}
export async function buildDashboardClient(): Promise<void> {
// Desktop loads index.html via file:// from inside the asar, so absolute
// asset paths (/assets/...) resolve to the filesystem root and fail. Build
// with a relative base so the bundled HTML references ./assets/... instead.
await runWorkspaceBin("vite", ["build", "--base", "./"], resolve(workspaceRoot, "packages", "dashboard"));
}