Skip to content

Commit c327ada

Browse files
aspiersclaude
andcommitted
fix(scripts): codegen via node … | xargs -0 -r under pipefail, not $(…)
Copilot was right that command substitution is the wrong shape here. A `$(…)` arg list silently swallows a producer failure (the script could exit non-zero and lex would still run on partial/empty input and "succeed"). Switch to the pipe + xargs form the original gen-* used: node ./scripts/codegen-lexicon-files.js | xargs -0 -r lex gen-… - `set -o pipefail` makes a producer failure abort the pipeline (verified: a plain pipe reports exit 0 on producer failure; pipefail reports 1). dash — the sh on Ubuntu CI and here — supports it. - `-r` skips lex on empty input; `-0` (NUL-delimited from the script) is robust to any path chars; xargs also sidesteps ARG_MAX. - The script now exits non-zero + writes nothing if it finds no lexicons, so an internal bug surfaces rather than codegenning nothing. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 7cb3ce4 commit c327ada

2 files changed

Lines changed: 19 additions & 6 deletions

File tree

package.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,12 @@
4242
"check": "npm run gen-api && npm run lint && npm run typecheck && npm run build && npm run test",
4343
"build": "rollup -c && npm run build:types",
4444
"build:types": "tsc --project tsconfig.build.json",
45-
"//permissions": "permission-set lexicons are published as-is but have no TS shape; lex gen-* cannot codegen them. scripts/codegen-lexicon-files.js lists the lexicons to codegen, excluding permission-sets by content (single source of truth shared with generate-exports.js).",
46-
"gen-api": "lex gen-api --yes ./generated $(node ./scripts/codegen-lexicon-files.js) && npm run gen-index",
47-
"gen-md": "lex gen-md --yes ./lexicons.md $(node ./scripts/codegen-lexicon-files.js)",
45+
"//permissions": "permission-set lexicons are published as-is but have no TS shape; lex gen-* cannot codegen them. scripts/codegen-lexicon-files.js emits the NUL-delimited lexicon list to codegen, excluding permission-sets by content (single source of truth shared with generate-exports.js). Piped into `xargs -0 -r` under `set -o pipefail`: pipefail propagates a producer failure (xargs alone cannot — it would run lex on partial/empty input and report success); -r skips lex if the list is somehow empty; xargs avoids ARG_MAX.",
46+
"gen-api": "set -o pipefail; node ./scripts/codegen-lexicon-files.js | xargs -0 -r lex gen-api --yes ./generated && npm run gen-index",
47+
"gen-md": "set -o pipefail; node ./scripts/codegen-lexicon-files.js | xargs -0 -r lex gen-md --yes ./lexicons.md",
4848
"gen-schemas-md": "node ./scripts/generate-schemas.js",
4949
"//gen-ts": "UNUSED - use gen-api instead",
50-
"gen-ts": "lex gen-ts-obj $(node ./scripts/codegen-lexicon-files.js) > generated/DO-NOT-USE-lexicons.ts",
50+
"gen-ts": "set -o pipefail; node ./scripts/codegen-lexicon-files.js | xargs -0 -r lex gen-ts-obj > generated/DO-NOT-USE-lexicons.ts",
5151
"gen-index": "node ./scripts/generate-exports.js",
5252
"lex": "lex",
5353
"test": "vitest run",

scripts/codegen-lexicon-files.js

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,11 @@
1313
* `main.type === "permission-set"` check) skip the same files by **content**,
1414
* not by path, so they cannot drift as files move.
1515
*
16-
* Usage (in package.json): `lex gen-api --yes ./generated $(node ./scripts/codegen-lexicon-files.js)`
16+
* Usage (in package.json): pipe into `xargs -0`, e.g.
17+
* `node ./scripts/codegen-lexicon-files.js | xargs -0 lex gen-api --yes ./generated`
18+
* Piping (not `$(…)`) is deliberate: a failure or empty output here propagates
19+
* through the pipeline instead of being swallowed by command substitution, and
20+
* `xargs` avoids ARG_MAX limits. Output is NUL-delimited for `xargs -0`.
1721
*/
1822

1923
import { readdirSync, readFileSync } from "node:fs";
@@ -47,4 +51,13 @@ const files = findJsonFiles(lexiconsDir)
4751
.filter((f) => !isPermissionSet(f))
4852
.sort();
4953

50-
process.stdout.write(files.join("\n") + "\n");
54+
// Fail loudly rather than let the codegen run on an empty arg list (which could
55+
// otherwise "succeed" producing nothing). The pipeline (`node … | xargs lex …`)
56+
// already propagates this non-zero exit, unlike a `$(…)` substitution.
57+
if (files.length === 0) {
58+
process.stderr.write("codegen-lexicon-files: no lexicons found\n");
59+
process.exit(1);
60+
}
61+
62+
// NUL-delimited for `xargs -0` — robust against any path characters.
63+
process.stdout.write(files.join("\0") + "\0");

0 commit comments

Comments
 (0)