Skip to content

Commit 9c95a2b

Browse files
committed
Fail fast when bootstrapping workspaces
The root `bootstrap` script ran `npm run bootstrap --workspaces --if-present`, but npm does not fail fast across workspaces: when one workspace's bootstrap fails, npm keeps running the bootstrap script for the remaining workspaces and only reports a non-zero exit code at the end. Because later workspaces depend on artifacts produced by earlier ones (e.g. the weak-node-api xcframework consumed by node-addon-examples, node-tests and ferric-example), a failure while building weak-node-api produced a cascade of confusing secondary "missing xcframework" errors that buried the actual root cause. Replace the `--workspaces` iteration with a small script that bootstraps each workspace in declared order and stops at the first failing one, so CI surfaces the real error instead of the downstream cascade. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MFfJiaaqbgMqnrNUB6e76m
1 parent 8421e88 commit 9c95a2b

2 files changed

Lines changed: 69 additions & 1 deletion

File tree

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
"prettier:check": "prettier --experimental-cli --check .",
2828
"prettier:write": "prettier --experimental-cli --write .",
2929
"test": "npm test --workspace react-native-node-api --workspace cmake-rn --workspace gyp-to-cmake --workspace node-addon-examples",
30-
"bootstrap": "node --run build && npm run bootstrap --workspaces --if-present",
30+
"bootstrap": "node --run build && node scripts/bootstrap.ts",
3131
"changeset": "changeset",
3232
"release": "changeset publish",
3333
"prerelease": "node --run build && npm run prerelease --workspaces --if-present && node --run publint",

scripts/bootstrap.ts

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import path from "node:path";
2+
import assert from "node:assert/strict";
3+
import cp from "node:child_process";
4+
import fs from "node:fs";
5+
6+
// Bootstrap every workspace sequentially, failing fast on the first error.
7+
//
8+
// We deliberately don't use "npm run bootstrap --workspaces --if-present" here:
9+
// npm keeps iterating the remaining workspaces even after one of them fails and
10+
// only surfaces a non-zero exit code once they've all run. Because later
11+
// workspaces depend on artifacts produced by earlier ones (e.g. the
12+
// weak-node-api xcframework consumed by node-addon-examples, node-tests and
13+
// ferric-example), a failure in an early workspace produces a cascade of
14+
// confusing secondary errors, hiding the actual root cause. Iterating ourselves
15+
// lets us stop at the first failing workspace.
16+
17+
const rootDir = path.resolve(import.meta.dirname, "..");
18+
19+
function readPackageJson(packageDir: string): Record<string, unknown> {
20+
const contents = fs.readFileSync(
21+
path.join(packageDir, "package.json"),
22+
"utf8",
23+
);
24+
const parsed = JSON.parse(contents) as unknown;
25+
assert(
26+
typeof parsed === "object" && parsed !== null,
27+
`Expected an object in ${packageDir}/package.json`,
28+
);
29+
return parsed as Record<string, unknown>;
30+
}
31+
32+
const rootPackage = readPackageJson(rootDir);
33+
assert(
34+
Array.isArray(rootPackage.workspaces),
35+
"Expected a 'workspaces' array in the root package.json",
36+
);
37+
38+
for (const workspace of rootPackage.workspaces) {
39+
assert(typeof workspace === "string");
40+
// The workspaces are declared as concrete directories – reject globs rather
41+
// than silently skipping workspaces they were meant to expand to.
42+
assert(
43+
!workspace.includes("*"),
44+
`Glob workspace patterns are not supported ('${workspace}')`,
45+
);
46+
47+
const workspaceDir = path.resolve(rootDir, workspace);
48+
const { name, scripts } = readPackageJson(workspaceDir);
49+
assert(typeof name === "string");
50+
51+
const hasBootstrap =
52+
typeof scripts === "object" && scripts !== null && "bootstrap" in scripts;
53+
if (!hasBootstrap) {
54+
continue;
55+
}
56+
57+
console.log(`Bootstrapping '${name}'`);
58+
const { status } = cp.spawnSync(
59+
"npm",
60+
["run", "bootstrap", "--workspace", name],
61+
{ stdio: "inherit" },
62+
);
63+
assert.equal(
64+
status,
65+
0,
66+
`Bootstrapping '${name}' failed (status = ${status})`,
67+
);
68+
}

0 commit comments

Comments
 (0)