Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions packages/gatekeeper-context/build-app.mjs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
// Build the Context Library SPA into generated single-file HTML for startAppUi().

import { execFileSync } from "node:child_process";
import { resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { execPnpm } from "../../scripts/pnpm-command.mjs";

const pkgDir = resolve(fileURLToPath(import.meta.url), "..");
const watch = process.argv.includes("--watch");
Expand All @@ -12,8 +12,7 @@ console.log(
? "watching context library app for changes…"
: "building context library app single-file bundle…",
);
execFileSync(
"pnpm",
execPnpm(
["exec", "vite", "build", "-c", "vite.config.ts", ...(watch ? ["--watch"] : [])],
{ cwd: pkgDir, stdio: "inherit" },
);
5 changes: 2 additions & 3 deletions packages/gatekeeper-scheduler/build-app.mjs
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import { execFileSync } from "node:child_process";
import { resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { execPnpm } from "../../scripts/pnpm-command.mjs";

const packageDirectory = resolve(fileURLToPath(import.meta.url), "..");
const watch = process.argv.includes("--watch");

execFileSync(
"pnpm",
execPnpm(
["exec", "vite", "build", "-c", "vite.config.ts", ...(watch ? ["--watch"] : [])],
{ cwd: packageDirectory, stdio: "inherit" },
);
3 changes: 2 additions & 1 deletion run-dev-server.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { execFileSync, spawn } from "node:child_process";
import { join, dirname } from "node:path";
import { fileURLToPath } from "node:url";
import { parse } from "jsonc-parser";
import { execPnpm } from "./scripts/pnpm-command.mjs";
import { getWranglerPortFromBackendHost } from "./scripts/dev-server-config.js";

const ROOT = dirname(fileURLToPath(import.meta.url));
Expand Down Expand Up @@ -322,7 +323,7 @@ if (backendHost) {
console.log(`\nStarting: wrangler dev ${args.join(" ")}\n`);

try {
execFileSync("pnpm", ["exec", "wrangler", "dev", ...args],
execPnpm(["exec", "wrangler", "dev", ...args],
{ stdio: "inherit", cwd: ROOT });
} catch (e) {
// wrangler was killed or exited with an error; the output was already shown
Expand Down
8 changes: 5 additions & 3 deletions scripts/generate-worker-types.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@
*
* `--check` is non-mutating: writes a sibling temp file, compares, deletes it.
*/
import { spawnSync } from "node:child_process";
import { readdir, readFile, rm, writeFile } from "node:fs/promises";
import { dirname, join, relative, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { spawnPnpmSync } from "./pnpm-command.mjs";

const root = resolve(dirname(fileURLToPath(import.meta.url)), "..");
const packagesDir = join(root, "packages");
Expand Down Expand Up @@ -126,11 +126,13 @@ async function generateOne(pkgDir) {
if (runtimeOnlyConfig) {
args.push("--config", runtimeOnlyConfig, "--include-env", "false");
}
const result = spawnSync(
"pnpm",
const result = spawnPnpmSync(
args,
{ cwd: pkgDir, encoding: "utf8", env: process.env },
);
if (result.error) {
throw new Error(`failed to spawn pnpm in ${rel}: ${result.error.message}`);
}
if (result.status !== 0) {
console.error(result.stdout);
console.error(result.stderr);
Expand Down
37 changes: 37 additions & 0 deletions scripts/pnpm-command.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Resolve how to invoke pnpm on Windows without a shell.
//
// On Windows, `pnpm` on PATH is a `.cmd` shim. Node refuses to spawn `.cmd`/`.bat` without a
// shell (EINVAL, CVE-2024-27980), and `shell: true` re-splits arguments so absolute paths
// containing spaces break (see cloudflare/cloudflare-os#19).
//
// When the current process was launched by `pnpm run`, `npm_execpath` points at pnpm's JS entry
// (`.cjs` or `.mjs` depending on install). Running `node <execpath> …` keeps argv intact with no
// shell. Under `npm run` the same variable points at npm-cli.js — the guard below rejects that
// so the failure stays loud rather than silently using the wrong package manager.

import { execFileSync, spawnSync } from "node:child_process";

/**
* Return `[executable, argv]` for spawning pnpm with the given arguments.
* @param {string[]} args
* @returns {[string, string[]]}
*/
export function pnpmCommand(args) {
const execPath = process.env.npm_execpath ?? "";
if (process.platform === "win32" && /[\\/]pnpm\.[cm]?js$/i.test(execPath)) {
return [process.execPath, [execPath, ...args]];
}
return ["pnpm", args];
}

/** @type {typeof execFileSync} */
export function execPnpm(args, options) {
const [file, argv] = pnpmCommand(args);
return execFileSync(file, argv, options);
}

/** @type {typeof spawnSync} */
export function spawnPnpmSync(args, options) {
const [file, argv] = pnpmCommand(args);
return spawnSync(file, argv, options);
}
5 changes: 5 additions & 0 deletions scripts/release/build-release.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
import {
findDeployablePackages, generateManifest, readDeployInputs, readWranglerConfig,
} from "./manifest-lib.mjs";
import { execPnpm } from "../pnpm-command.mjs";

const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..", "..");
const PACKAGES_DIR = join(ROOT, "packages");
Expand All @@ -42,6 +43,10 @@ function parseArgs(argv) {

function run(command, argv, options = {}) {
console.log(`running: ${command} ${argv.join(" ")} ${options.cwd ? `(in ${options.cwd})` : ""}`);
if (command === "pnpm") {
execPnpm(argv, { stdio: "inherit", cwd: ROOT, ...options });
return;
}
execFileSync(command, argv, { stdio: "inherit", cwd: ROOT, ...options });
}

Expand Down
13 changes: 7 additions & 6 deletions scripts/run-local.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { execFileSync, spawn } from "node:child_process";
import { existsSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
import { dirname, join, relative, sep } from "node:path";
import { fileURLToPath } from "node:url";
import { execPnpm } from "./pnpm-command.mjs";

const ROOT = dirname(dirname(fileURLToPath(import.meta.url)));
const STAMP_PATH = join(ROOT, ".run-local-stamp");
Expand Down Expand Up @@ -113,21 +114,21 @@ const outputsPresent = existsSync(FRONTEND_DIST) && existsSync(TYPED_STORAGE_DIS
const needsBuild = stamp !== sourceHash || !outputsPresent;
const needsInstall = needsBuild || !existsSync(NODE_MODULES);

function run(cmd, args) {
console.log(`\n> ${cmd} ${args.join(" ")}`);
execFileSync(cmd, args, { stdio: "inherit", cwd: ROOT });
function runPnpm(args) {
console.log(`\n> pnpm ${args.join(" ")}`);
execPnpm(args, { stdio: "inherit", cwd: ROOT });
}

if (needsInstall) {
run("pnpm", ["install"]);
runPnpm(["install"]);
} else {
console.log("Dependencies up to date; skipping install.");
}

if (needsBuild) {
// Build only what's required to run locally (no full-repo type-check / no frontend tsc).
run("pnpm", ["--filter", "@gadgets/typed-storage", "build"]);
run("pnpm", ["--filter", "@gadgets/workshop-frontend", "exec", "vite", "build"]);
runPnpm(["--filter", "@gadgets/typed-storage", "build"]);
runPnpm(["--filter", "@gadgets/workshop-frontend", "exec", "vite", "build"]);

// Record the stamp only after a successful build so an interrupted build retries next time.
writeFileSync(STAMP_PATH, sourceHash + "\n");
Expand Down
Loading