Skip to content

bug(COMPONENT): SyntaxError: Unexpected token '<' #33713

Description

@avishrey-beep

Is this a regression?

  • Yes, this behavior used to work in the previous version

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:

  1. Remove the type annotation since it's a .mjs file: const targetsToRun = new Set();
  2. 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:

  1. 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:

  1. 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.

  2. Enable shell strict modesh.set('-e') makes the shelljs instance stop on the first error encountered in later shell operations.

  3. 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.
  4. 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:

  1. 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.

  2. 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.
  3. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    area: build & ciRelated the build and CI infrastructure of the projectgemini-triagedLabel noting that an issue has been triaged by geminineeds triageThis issue needs to be triaged by the team

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions