fix(i18n): drop shebangs that break test collection on Windows - #2378
Conversation
check-i18n-strings.mjs and i18n-translate.mjs opened with #!/usr/bin/env node. Four i18n tests import them, and on Windows Vite's CJS interop prepends its generated requires to line 1, which leaves the shebang sitting after executable code: const fileURLToPath = __vite__cjsImport3_node_url["fileURLToPath"];#!/usr/bin/env node Rolldown fails the parse on the "!", and the importing file then collects ZERO tests and reports "no tests" rather than a red assertion. So unitLiterals, localeFormatting, untranslatedRatchet and translateDriver stop gating anything on Windows, silently. CI is Linux-only, where the transform leaves the shebang on its own line, so nothing catches it. The shebangs were decorative. Both files are mode 100644, not executable, and every invocation goes through `node scripts/<name>.mjs` from package.json. The modules already extracted so gates can be shared with tests, scripts/lib/*.mjs, carry none. Add src/test/scriptsShebang.test.ts to hold the line: a script under scripts/ imported from src/ must not open with "#!". It is mutation-checked, so restoring either shebang fails it and names the file. It also asserts the import set is non-empty, so a future move behind scripts/lib/ cannot make it vacuously green. Verified on both platforms. The four suites plus the guard go from 0 collected to 119 passed on Windows and on Linux, and `npm run lint:i18n` and `node scripts/i18n-translate.mjs` still run.
UX Review (Fable 5, fork) — ✅ PASSAdvisory UX-level review of UX-Verdict: PASS Developer-tooling-only change — removes two script shebangs and adds a test guard; no user-facing surface, string, or rendered pixel is touched. [UX-REVIEWED] 541da89 |
Opus 4.8 Review (fork) — ✅ no blocking findingsReviewed |
Design Review (Fable 5, fork) — ✅ PASSAdvisory design-level review of Design-Verdict: PASS Root-cause fix (shebang is only legal on line 1), verified decorative (mode 644, [DESIGN-REVIEWED] 541da89 |
GPT 5.6 Review (fork) — ✅ no blocking findingsReviewed Review detailsNo findings. |
Squashed to one commit to satisfy the Enforce single commit hygiene gate; the per-pick provenance is preserved below. Each pick is patch-identical to the commit already merged on main (verified with git patch-id --stable). Requested for this RC: - feat: opt-in Slack setup and multi-channel repositioning (#2340) kirocrew setup no longer walks through Slack credentials unless --slack is passed, plus multi-channel repositioning across docs, in-app copy, and all 13 locales. (cherry picked from commit 8f5c937) - fix(i18n): drop shebangs that break test collection on Windows (#2378) (cherry picked from commit ab20b4e) Required to make this branch green, not optional additions: - fix(i18n): resync ko Storage catalog with the inventory model (#2306) release/0.2.0 already failed catalogParity > ko at 5fe4bd5 with 29 missing and 36 orphaned pages.sessionStorage.* keys. #2208 rewrote the English Storage keys on this branch while its Korean resync stayed on main, so Korean users saw raw key names on the Storage page and every PR touching website/ was red. (cherry picked from commit 841e8b4) - fix(i18n): match ko sort_largest spacing to the catalog convention (#2370) (cherry picked from commit 49aa776) Added here: - docs: note opt-in Slack setup in the 0.2.0 changelog. #2340 changes first-run behavior and the existing [0.2.0] section did not mention it. A feat on a cut release branch is a deliberate maintainer decision, not an oversight: CONTRIBUTING.md reserves a cut branch for bug fixes, and the multi-channel positioning was judged the accurate story to ship in 0.2.0.
…2395) Squashed to one commit to satisfy the Enforce single commit hygiene gate; the per-pick provenance is preserved below. Each pick is patch-identical to the commit already merged on main (verified with git patch-id --stable). Requested for this RC: - feat: opt-in Slack setup and multi-channel repositioning (#2340) kirocrew setup no longer walks through Slack credentials unless --slack is passed, plus multi-channel repositioning across docs, in-app copy, and all 13 locales. (cherry picked from commit 8f5c937) - fix(i18n): drop shebangs that break test collection on Windows (#2378) (cherry picked from commit ab20b4e) Required to make this branch green, not optional additions: - fix(i18n): resync ko Storage catalog with the inventory model (#2306) release/0.2.0 already failed catalogParity > ko at 5fe4bd5 with 29 missing and 36 orphaned pages.sessionStorage.* keys. #2208 rewrote the English Storage keys on this branch while its Korean resync stayed on main, so Korean users saw raw key names on the Storage page and every PR touching website/ was red. (cherry picked from commit 841e8b4) - fix(i18n): match ko sort_largest spacing to the catalog convention (#2370) (cherry picked from commit 49aa776) Added here: - docs: note opt-in Slack setup in the 0.2.0 changelog. #2340 changes first-run behavior and the existing [0.2.0] section did not mention it. A feat on a cut release branch is a deliberate maintainer decision, not an oversight: CONTRIBUTING.md reserves a cut branch for bug fixes, and the multi-channel positioning was judged the accurate story to ship in 0.2.0.
…otdev#2378) check-i18n-strings.mjs and i18n-translate.mjs opened with #!/usr/bin/env node. Four i18n tests import them, and on Windows Vite's CJS interop prepends its generated requires to line 1, which leaves the shebang sitting after executable code: const fileURLToPath = __vite__cjsImport3_node_url["fileURLToPath"];#!/usr/bin/env node Rolldown fails the parse on the "!", and the importing file then collects ZERO tests and reports "no tests" rather than a red assertion. So unitLiterals, localeFormatting, untranslatedRatchet and translateDriver stop gating anything on Windows, silently. CI is Linux-only, where the transform leaves the shebang on its own line, so nothing catches it. The shebangs were decorative. Both files are mode 100644, not executable, and every invocation goes through `node scripts/<name>.mjs` from package.json. The modules already extracted so gates can be shared with tests, scripts/lib/*.mjs, carry none. Add src/test/scriptsShebang.test.ts to hold the line: a script under scripts/ imported from src/ must not open with "#!". It is mutation-checked, so restoring either shebang fails it and names the file. It also asserts the import set is non-empty, so a future move behind scripts/lib/ cannot make it vacuously green. Verified on both platforms. The four suites plus the guard go from 0 collected to 119 passed on Windows and on Linux, and `npm run lint:i18n` and `node scripts/i18n-translate.mjs` still run.
Problem / Motivation
scripts/check-i18n-strings.mjsandscripts/i18n-translate.mjsopen with#!/usr/bin/env node. Four i18n tests import them. On Windows, Vite's CJS interopprepends its generated requires to line 1, which leaves the shebang sitting after
executable code:
const fileURLToPath = __vite__cjsImport3_node_url["fileURLToPath"];#!/usr/bin/env node
^ Invalid Character !
Rolldown fails the parse, and the importing file collects zero tests, reporting
no testsrather than a red assertion.Why it matters
Four gates stop gating on Windows, silently:
unitLiterals, the number+unit literal ceilinglocaleFormattinguntranslatedRatchettranslateDriverA green local run on Windows means nothing for those four, and the failure announces
itself as
no testsrather than as an error. CI is Linux-only, where the transformleaves the shebang on its own line, so nothing catches it there either. Given Dev Fleet Pull + Build breaks the venv on Windows: pip cannot replace the locked kirocrew.exe and leaves the editable install stripped #2297
and Windows flake: pipe-close test asserts a global CloseHandle count, so unrelated handles make it fail 2 == 1 #2301, Windows contributors are a real constituency.
What changed (motivation → approach → change)
Root cause: a shebang is only legal on line 1, and the CJS transform takes line 1.
Change: delete both shebangs. They were decorative. Both files are mode
100644(not executable) and every invocation goes through
node scripts/<name>.mjsfrompackage.json, so nothing could have executed them directly. The modules alreadyextracted so gates can be shared with tests,
scripts/lib/*.mjs, carry none, which isthe pattern this follows.
Guard:
src/test/scriptsShebang.test.tsasserts that no script underscripts/imported from
src/opens with#!. A Linux-only CI cannot see this class of bug, soa convention would not survive it.
Considered and not done: moving the imported exports into
scripts/lib/the wayqa-checks.mjsdid. That is arguably the better end state, but it touches four testfiles and two scripts to fix a two-line defect. Happy to follow up if preferred.
Tests
src/test/scriptsShebang.test.ts, two assertions:shebang fails it and names the file.
scripts/lib/cannot make the guard vacuously green.
Manual verification
src/i18n/suitenpm run checknpm run lint:i18nnode scripts/i18n-translate.mjspackage.jsonand the docs invoke them.unitLiteralstimed out at 19.7 s under--coverageon a 16-core box. It passes in isolation and is untouched by this diff.Screenshots / video
N/A. No UI change.
Related Issues
None open for this. Adjacent Windows issues: #2297, #2301.
Checklist