Is this a regression?
The previous version in which this bug was not present was
No response
Description
This error occurs because the script is written as a .mjs file — plain JavaScript executed directly by Node.js — but it contains TypeScript-only syntax: new Set<string>(). The <string> part is a generic type parameter, which is a TypeScript construct that Node's native JavaScript parser doesn't recognize.
When Node tries to parse this line, it hits the < character where it doesn't expect one (JavaScript has no generics syntax), and immediately fails to parse the file — before any code even executes. Since this happens at parse time, the script won't run at all, not even to print No package name has been passed in...; you'll just get a stack trace pointing at that line.
Root cause: mixing TypeScript type syntax into a file that's being run as plain JavaScript, instead of through a TypeScript-aware runner (ts-node, tsx) or a build step that strips types first.
Fix options:
- Remove the type annotation since it's a
.mjs file: const targetsToRun = new Set();
- Or, if type safety is wanted, rename the file to
.mts/.ts and run it through a TypeScript-capable tool instead of raw node.
Reproduction
StackBlitz link:
Steps to reproduce:
- The bug is this line:
const targetsToRun = new Set<string>();
This is a .mjs file (plain JavaScript, run directly by Node), but new Set<string>() uses TypeScript generic-type syntax. Node's JS parser doesn't understand <string> there — it will throw a SyntaxError (it tries to parse <string> as a comparison/JSX-like expression and fails), so the script will crash immediately on load with something like:
SyntaxError: Unexpected token '<'
Fix: just drop the type annotation, since plain .mjs has no type system:
const targetsToRun = new Set();
If you actually want type-checking here, the file needs to be a .ts/.mts file compiled or run through ts-node/tsx, not executed as .mjs directly with Node.
2.
Expected Behavior
Once the syntax error is fixed (new Set() instead of new Set<string>()), the script is expected to behave as follows:
-
Argument check — If no package names are passed as CLI arguments (process.argv.length < 3), it prints a red error message saying no package name was provided for API golden approval, and exits with code 1.
-
Enable shell strict mode — sh.set('-e') makes the shelljs instance stop on the first error encountered in later shell operations.
-
Resolve each requested package — For every package name argument passed in:
- It calls
guessPackageName(searchPackageName, ...) to try to locate the actual package inside the src directory.
- If no match is found, it prints a red error listing the package name it couldn't find and which paths/packages it attempted to match against, then exits with code
1.
- If a match is found, it extracts the top-level package name (before the first
/) and builds a Bazel target string like //goldens:<package_name>_api.accept, replacing any hyphens in the name with underscores (since Bazel target names can't contain hyphens). This target is added to a Set (targetsToRun) to avoid duplicate targets if multiple inputs resolve to the same package.
-
Run the golden update for each target — For every unique Bazel target collected, it runs:
pnpm -s bazel run //goldens:<package_name>_api.accept
using execFileSync with stdio: 'inherit', so the child process's output (Bazel's build/run logs) streams directly to the terminal in real time.
End result: the script acts as a CLI utility that takes one or more package name fragments, resolves each to its actual package, and runs the corresponding Bazel "accept" target to regenerate/approve the API golden files for those packages — deduplicating so the same target isn't run twice, and failing fast (with a helpful message) if a package name can't be resolved or a Bazel run fails.
Actual Behavior
Given the code as originally written (with the new Set<string>() TypeScript syntax still in place), the actual behavior differs sharply from the expected behavior:
-
Immediate parse failure — Since this file is executed directly as an .mjs file by Node.js (a plain JavaScript runtime with no TypeScript support), Node's parser encounters <string> in new Set<string>() and cannot make sense of it as valid JavaScript.
-
Script never actually runs — Because this is a syntax error, it happens at parse time, before any line of the script executes. This means:
- The
process.argv.length < 3 check never runs.
- No package resolution happens.
- No Bazel targets are ever computed or executed.
- Even the very first
console.error for missing arguments never fires.
-
Output — Instead of any of the intended CLI behavior, the user just gets a stack trace thrown straight from Node's parser, roughly:
SyntaxError: Unexpected token '<'
at ...internal/modules/esm/...
with the process exiting with a non-zero exit code (typically 1), but not due to the script's own process.exit(1) calls — it never reaches that logic.
Summary: instead of resolving package names and running Bazel golden-approval targets, the script fails outright and unconditionally on startup, regardless of what arguments are passed (or not passed) — the intended CLI logic is entirely unreachable.
Environment
Based on the script's imports and structure, here's the environment it's designed to run in:
Runtime
- Node.js with ES Modules support (the file uses
import syntax and has a .mjs extension, signaling native ESM rather than CommonJS).
- Executed directly via
node script.mjs <args> (or similar), not through a TypeScript-aware runner like ts-node or tsx — which is exactly why the Set<string>() syntax breaks it.
Package manager / monorepo tooling
- pnpm — the script shells out to
pnpm -s bazel run <target>, implying this lives inside a pnpm-managed workspace/monorepo.
- Bazel — the actual golden-approval targets (
//goldens:<package>_api.accept) are Bazel build targets, so the repo uses Bazel as its build system, invoked here through a pnpm script wrapper.
Dependencies used
chalk — for colored terminal output (red error messages).
shelljs (sh) — used here just to call sh.set('-e'), enabling "fail on error" shell semantics for any subsequent shelljs shell calls.
child_process.execFileSync (Node built-in) — used to run the actual pnpm bazel run command synchronously, streaming output live via stdio: 'inherit'.
- A local utility module
./util.mjs exporting guessPackageName, presumably custom logic to resolve a partial/fuzzy package name to a real package path under src/.
Expected repo layout
- Run from the root of a repo that has a
src/ directory containing packages, and a goldens Bazel package with _api.accept targets defined per sub-package (this strongly resembles patterns used in Angular's monorepo for API "golden file" testing, where .accept targets update expected/golden API surface snapshots).
Why the environment matters for the bug
- Because it's run as raw
.mjs under plain Node, the environment has no TypeScript compilation step in front of execution — so any TypeScript-only syntax (like the generic type parameter in new Set<string>()) is invalid and unparseable in this environment, causing the immediate SyntaxError.
Is this a regression?
The previous version in which this bug was not present was
No response
Description
This error occurs because the script is written as a
.mjsfile — plain JavaScript executed directly by Node.js — but it contains TypeScript-only syntax:new Set<string>(). The<string>part is a generic type parameter, which is a TypeScript construct that Node's native JavaScript parser doesn't recognize.When Node tries to parse this line, it hits the
<character where it doesn't expect one (JavaScript has no generics syntax), and immediately fails to parse the file — before any code even executes. Since this happens at parse time, the script won't run at all, not even to printNo package name has been passed in...; you'll just get a stack trace pointing at that line.Root cause: mixing TypeScript type syntax into a file that's being run as plain JavaScript, instead of through a TypeScript-aware runner (
ts-node,tsx) or a build step that strips types first.Fix options:
.mjsfile:const targetsToRun = new Set();.mts/.tsand run it through a TypeScript-capable tool instead of rawnode.Reproduction
StackBlitz link:
Steps to reproduce:
This is a
.mjsfile (plain JavaScript, run directly by Node), butnew Set<string>()uses TypeScript generic-type syntax. Node's JS parser doesn't understand<string>there — it will throw aSyntaxError(it tries to parse<string>as a comparison/JSX-like expression and fails), so the script will crash immediately on load with something like:Fix: just drop the type annotation, since plain
.mjshas no type system:If you actually want type-checking here, the file needs to be a
.ts/.mtsfile compiled or run throughts-node/tsx, not executed as.mjsdirectly with Node.2.
Expected Behavior
Once the syntax error is fixed (
new Set()instead ofnew Set<string>()), the script is expected to behave as follows:Argument check — If no package names are passed as CLI arguments (
process.argv.length < 3), it prints a red error message saying no package name was provided for API golden approval, and exits with code1.Enable shell strict mode —
sh.set('-e')makes theshelljsinstance stop on the first error encountered in later shell operations.Resolve each requested package — For every package name argument passed in:
guessPackageName(searchPackageName, ...)to try to locate the actual package inside thesrcdirectory.1./) and builds a Bazel target string like//goldens:<package_name>_api.accept, replacing any hyphens in the name with underscores (since Bazel target names can't contain hyphens). This target is added to aSet(targetsToRun) to avoid duplicate targets if multiple inputs resolve to the same package.Run the golden update for each target — For every unique Bazel target collected, it runs:
using
execFileSyncwithstdio: 'inherit', so the child process's output (Bazel's build/run logs) streams directly to the terminal in real time.End result: the script acts as a CLI utility that takes one or more package name fragments, resolves each to its actual package, and runs the corresponding Bazel "accept" target to regenerate/approve the API golden files for those packages — deduplicating so the same target isn't run twice, and failing fast (with a helpful message) if a package name can't be resolved or a Bazel run fails.
Actual Behavior
Given the code as originally written (with the
new Set<string>()TypeScript syntax still in place), the actual behavior differs sharply from the expected behavior:Immediate parse failure — Since this file is executed directly as an
.mjsfile by Node.js (a plain JavaScript runtime with no TypeScript support), Node's parser encounters<string>innew Set<string>()and cannot make sense of it as valid JavaScript.Script never actually runs — Because this is a syntax error, it happens at parse time, before any line of the script executes. This means:
process.argv.length < 3check never runs.console.errorfor missing arguments never fires.Output — Instead of any of the intended CLI behavior, the user just gets a stack trace thrown straight from Node's parser, roughly:
with the process exiting with a non-zero exit code (typically
1), but not due to the script's ownprocess.exit(1)calls — it never reaches that logic.Summary: instead of resolving package names and running Bazel golden-approval targets, the script fails outright and unconditionally on startup, regardless of what arguments are passed (or not passed) — the intended CLI logic is entirely unreachable.
Environment
Based on the script's imports and structure, here's the environment it's designed to run in:
Runtime
importsyntax and has a.mjsextension, signaling native ESM rather than CommonJS).node script.mjs <args>(or similar), not through a TypeScript-aware runner likets-nodeortsx— which is exactly why theSet<string>()syntax breaks it.Package manager / monorepo tooling
pnpm -s bazel run <target>, implying this lives inside a pnpm-managed workspace/monorepo.//goldens:<package>_api.accept) are Bazel build targets, so the repo uses Bazel as its build system, invoked here through a pnpm script wrapper.Dependencies used
chalk— for colored terminal output (red error messages).shelljs(sh) — used here just to callsh.set('-e'), enabling "fail on error" shell semantics for any subsequent shelljs shell calls.child_process.execFileSync(Node built-in) — used to run the actualpnpm bazel runcommand synchronously, streaming output live viastdio: 'inherit'../util.mjsexportingguessPackageName, presumably custom logic to resolve a partial/fuzzy package name to a real package path undersrc/.Expected repo layout
src/directory containing packages, and agoldensBazel package with_api.accepttargets defined per sub-package (this strongly resembles patterns used in Angular's monorepo for API "golden file" testing, where.accepttargets update expected/golden API surface snapshots).Why the environment matters for the bug
.mjsunder plain Node, the environment has no TypeScript compilation step in front of execution — so any TypeScript-only syntax (like the generic type parameter innew Set<string>()) is invalid and unparseable in this environment, causing the immediateSyntaxError.