Skip to content

chore: run TypeScript scripts with node instead of tsx - #201

Merged
nulltask merged 2 commits into
develfrom
chore/tsx-to-node
Sep 6, 2026
Merged

nulltask merged 2 commits into
develfrom
chore/tsx-to-node

Conversation

@nulltask

@nulltask nulltask commented Sep 6, 2026

Copy link
Copy Markdown
Owner

Node 26 (this repo's engines.node floor) makes type stripping stable and default, so every script/CLI entry that used to run through tsx --tsconfig ... now runs as node <file>.ts directly, and tsx is dropped from devDependencies.

Fixes required to make that work under Node's stricter, bundler-free resolution

  • Root-level scripts (scripts/bench/*, scripts/release/*, scripts/build-sea.ts) import workspace packages by bare specifier (@be-music/json, etc.). tsx resolved these via tsconfig.typecheck.json paths, but node needs an actual node_modules link, so the root package.json now depends on every @be-music/* workspace package.
  • Node's type stripping rejects parameter properties as non-erasable syntax (ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX). Rewrote the 9 constructors that used them (RulesetGauge, LuaRuntimeOwner, and 7 player-web view classes) as plain field declarations + assignment; generated .d.ts constructor signatures are unchanged.
  • fengari-web ships a webpack UMD bundle whose module.exports is built at runtime, so Node's static cjs-module-lexer can't discover its named exports the way tsx's esbuild-based loader did. Switched lua.ts to a default import and updated the hand-written .d.ts to match.
  • audio-renderer's package.json advertised a ./triggers subpath export that tsdown.config.ts never actually built (tsx's tsconfig paths quietly bypassed the gap by resolving straight to source). Added the missing tsdown entry so the built dist/ matches the declared exports.
  • Dropped the TSX_TSCONFIG_PATH env var the three worker-spawning helpers (node-ui-runtime, node-gameplay-runtime, bga-video) set for their child workers — it configured tsx's own tsconfig lookup, which no longer runs. The --conditions=source exec flag stays: it's Node's own flag and still lets an in-source run resolve workspace packages via their exports "source" condition.

Testing

  • Full pnpm run build / lint / typecheck / test green.
  • Manual smoke tests of every converted entry point: parser / stringifier / audio-renderer / editor CLIs, all player-tui commands including a real TUI session under a pseudo-tty (script), build-sea.ts, the root and per-package bench scripts, and both release scripts.

Pre-existing bug found in passing (not fixed, out of scope)

packages/beatoraja-skin/scripts/bench.ts is referenced by its package.json but has never existed in git history. CI never invokes it (only the root bench script, which enumerates all packages independently), so it's dead outside a manual pnpm --filter @be-music/beatoraja-skin run bench.

🤖 Generated with Claude Code

Node 26 (this repo's engines.node floor) makes type stripping stable and
default, so every script/CLI entry that used to run through tsx --tsconfig
now runs as `node <file>.ts` directly, and tsx is dropped from
devDependencies.

Fixes required to make that work under Node's stricter, bundler-free
resolution:

- Root-level scripts (bench/*, release/*, build-sea) import workspace
  packages by bare specifier (`@be-music/json`, etc.); tsx resolved these
  via tsconfig paths, but node needs an actual node_modules link, so the
  root package.json now depends on every @be-music/* workspace package.
- Node's type stripping rejects parameter properties as non-erasable
  syntax (ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX). Rewrote the 9 constructors
  that used them (RulesetGauge, LuaRuntimeOwner, and 7 player-web view
  classes) as plain field declarations + assignment; generated .d.ts
  constructor signatures are unchanged.
- fengari-web ships a webpack UMD bundle whose module.exports is built at
  runtime, so Node's static cjs-module-lexer can't discover its named
  exports the way tsx's esbuild-based loader did. Switched lua.ts to a
  default import and updated the hand-written .d.ts to match.
- audio-renderer's package.json advertised a `./triggers` subpath export
  that tsdown.config.ts never actually built (tsx's tsconfig paths quietly
  bypassed the gap by resolving straight to source). Added the missing
  tsdown entry so the built dist/ matches the declared exports.
- Dropped the TSX_TSCONFIG_PATH env var the three worker-spawning helpers
  (node-ui-runtime, node-gameplay-runtime, bga-video) set for their child
  workers — it configured tsx's own tsconfig lookup, which no longer runs.
  The --conditions=source exec flag stays: it's Node's own flag and still
  lets an in-source run resolve workspace packages via their exports
  'source' condition.

Verified: full pnpm run build/lint/typecheck/test green, plus manual
smoke tests of every converted entry point (parser/stringifier/
audio-renderer/editor CLIs, all player-tui commands including a real TUI
session under a pseudo-tty, build-sea.ts, the root and per-package bench
scripts, and both release scripts).

Pre-existing bug found in passing (not fixed, out of scope): packages/
beatoraja-skin/scripts/bench.ts is referenced by its package.json but has
never existed in git history. CI never invokes it (only the root bench
script, which enumerates all packages independently), so it's dead outside
manual `pnpm --filter @be-music/beatoraja-skin run bench`.
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Sep 6, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
be-music-player-demo eea6ba7 Commit Preview URL

Branch Preview URL
Sep 06 2026, 07:07 AM

The CI benchmark job installs dependencies but never runs pnpm run
build, so the previous commit's dist-only resolution broke it:
node scripts/bench/exports.ts couldn't find @be-music/json/dist/
index.js because nothing had built it yet. The same gap silently broke
every dev/bench/playlog/diag:* command run without a prior build too —
tsx never had this problem because its tsconfig paths resolved straight
to source.

Every workspace package's exports already carries a "source" condition
pointing at the .ts file (used by tsx before this migration), so
passing Node's own --conditions=source flag to every node-invoked
script/CLI restores that behavior without needing a build first.
Applied to every package.json script that runs a .ts entry point, plus
the two CI workflow steps that call node directly (build-sea.ts, the
two release scripts).

Resolving straight to source instead of dist surfaced two more
parameter-property constructors that a single-line grep missed because
their argument lists span multiple lines (PlaylogSimulation,
BeatorajaSceneBgmPlayer) — a proper paren-depth-aware rescan confirms
these were the last two. Rewrote them the same way as the previous
commit's nine.

Verified by fully cleaning dist/, doing a frozen-lockfile install with
no build step (matching the CI benchmark job exactly), and re-running
every entry point that failed before: root and per-package bench, all
player-tui commands including a real TUI session under a pseudo-tty,
and both SEA builds. Then rebuilt normally and re-ran the full
build/lint/typecheck/test suite green.
@github-actions

github-actions Bot commented Sep 6, 2026

Copy link
Copy Markdown

Exports Benchmark

  • Base SHA: db99b59a8acf
  • Head SHA: eea6ba78193e
  • Comparable cases: 243
  • Regression threshold: 8.00%
  • Overall verdict uses the median change across cases.
  • Per-case lists compare median ops/s, not mean.
  • Base runs: median of 3
  • Head runs: median of 3

Summary

Metric Value
Overall improved
Median change +66.29%
Mean change +131.67%
Cases improved (>= threshold) 208
Cases regressed (<= -threshold) 1
Cases unchanged 34
Head benchmarked cases 243
Head skipped cases 21

Top Regressions

API Base median ops/s Head median ops/s Change
editor.saveJsonFile 4773.91 4384.68 -8.15%

Top Improvements

API Base median ops/s Head median ops/s Change
player-web.GameplayRecorder 735294.12 20833332.60 +2733.33%
chart.createBeatResolver 1302083.33 20833332.60 +1500.00%
player-web.ChartPreviewEngine 482625.48 6249999.87 +1195.00%
player-web.applyBeatorajaStretchRect 1404494.39 13888889.80 +888.89%
lr2-skin.informationTypeToKind 2272727.27 20833332.60 +816.67%
lr2-skin.informationTypeToPlayVariant 2016129.03 17857143.77 +785.71%
player-tui.beginKittyKeyboardProtocolOptIn 2272727.27 15625000.04 +587.50%
player-tui.beginWin32InputModeOptIn 2272727.27 15625000.04 +587.50%
player-web.blendCodeToPixi 3289473.67 20833332.60 +533.33%
player-tui.beginStatefulKeyboardProtocolOptIn 2232142.86 13888889.80 +522.22%
player-web.pickBeatorajaPlayableVariant 3378378.40 20833332.60 +516.67%
player.judging.findClosestCandidateInWindow 2358490.56 13888888.40 +488.89%

@nulltask
nulltask merged commit cb4c24a into devel Sep 6, 2026
9 checks passed
@nulltask
nulltask deleted the chore/tsx-to-node branch September 6, 2026 07:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant