Skip to content

Make the project install and typecheck from a clean clone - #8

Merged
maylonmcmurry-max merged 2 commits into
mainfrom
fix/build-health
Sep 1, 2026
Merged

maylonmcmurry-max merged 2 commits into
mainfrom
fix/build-health

Conversation

@maylonmcmurry-max

Copy link
Copy Markdown
Collaborator

Closes #4. Closes #5.

Both on one branch because both touch package.json — as two PRs they would conflict with each other.

#4 — npm install failed outright

wrangler@^4.20.0 now floats to 4.127.1, which peer-depends on @cloudflare/workers-types@^5, while package.json pinned ^4. Unsatisfiable. This is drift, not anything anyone did — it installed cleanly when the pins were written.

Bumped the types package to ^5.20260828.1. Verified: a plain npm install, no flags, now exits 0, and wrangler deploy --dry-run still bundles with all five bindings intact (271 KiB / 61 KiB gzip).

#5 — 4 type errors, and nothing ran the typechecker

Now zero. Causes and fixes:

  • VOICE_IDS (3 errors). VOICES is as const, so the Set inferred as Set<"luna" | "orion" | …> and rejected .has() on the arbitrary strings that actually arrive from tool arguments. Typed it Set<string> — widening the type, not the data.
  • speaker in tts.ts. The model declares a fixed speaker union; an arbitrary voiceId is now asserted across it. Safe because the value is already validated against VOICES upstream — I diffed all 10 house voices against the 40 the model accepts, and every one is valid.

The fourth error was a trap, and I want to flag it explicitly

The published type for the TTS model claims a string return, which makes result instanceof ArrayBuffer unreachable. The obvious "fix" is to rewrite the audio handling — that would break working TTS.

I checked production before touching it:

$ curl -s https://rapbattle.lol/speak/verse-rift-001 -o verse.bin
content-type: audio/mpeg   size: 291744 bytes
magic: ff f3 64 c4 …
file:  MPEG ADTS, layer III, v2, 48 kbps, 24 kHz, Monaural

Valid MP3. The runtime value is not a plain string — the published type is just wrong. So result is widened to unknown to keep both branches live, and the runtime path is untouched.

Proof the audio path is unchanged: I compiled tts.ts before and after and diffed the emitted JavaScript. After stripping comments it is byte-identical. Every change in that file is type-level only.

Prevention

Added "typecheck": "tsc --noEmit" and a CI workflow that runs it on push and PR. None of the above surfaced anywhere before: build is echo 'no build step', and wrangler deploy bundles via esbuild without full type checking. CI runs a plain npm install on purpose — that failing is exactly what the workflow exists to catch. I ran both steps locally (install exit 0, typecheck 0 errors); I obviously can't run Actions from here, so the workflow itself is unproven until it runs on GitHub.

Also added a .gitignore (the repo had none), so a successful install no longer litters git status with node_modules/.

One decision left to you

I did not commit a lockfile. It's the thing that would actually stop this class of drift from recurring, but it's a policy call for the repo — and a large diff. If you want it:

npm install && git add package-lock.json

CI would then be safe to switch from npm install to npm ci. Happy to do it if you'd prefer.

Relationship to the other PRs

Independent of #6 and #7 — different files, no conflicts, merges in any order.

🤖 Generated with Claude Code

Two independent breakages meant a fresh clone could neither install nor
typecheck, and nothing in the repo would have told you.

npm install failed outright. wrangler@^4.20.0 now floats to 4.127.1,
which peer-depends on @cloudflare/workers-types@^5, while package.json
pinned ^4 — an unsatisfiable pair, and drift rather than anything anyone
did. Bumping the types package to ^5.20260828.1 makes a plain npm
install exit 0 again.

tsc --noEmit reported four errors. VOICES is `as const`, so VOICE_IDS
inferred as Set<"luna" | "orion" | …> and rejected .has() on the
arbitrary strings that actually arrive from tool arguments; the Set is
now typed Set<string>, widening the type rather than the data. In tts.ts
the model declares a fixed speaker union, so an arbitrary voiceId is
asserted across — the value is already validated against VOICES upstream.

The last error was the interesting one. The published type for the TTS
model claims a string return, which makes `result instanceof ArrayBuffer`
unreachable and would tempt a reader into "fixing" the audio handling.
That would be a mistake: live output through the Response path is a valid
MP3 (verified against production, ff f3 frame sync, 291KB). So `result`
is widened to unknown to keep both branches live and the runtime path
untouched — the emitted JavaScript for tts.ts is byte-identical to before.

Adds a typecheck script and a CI workflow that runs it, since none of the
above surfaced anywhere: build is `echo 'no build step'` and wrangler
deploy bundles with esbuild without full type checking. CI runs a plain
npm install on purpose — that failing is exactly what this is here to
catch. Also adds a .gitignore, which the repo lacked, so a successful
install no longer litters git status.

Closes #4
Closes #5
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Sep 1, 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 Updated (UTC)
✅ Deployment successful!
View logs
rapbattle d9e0138 Sep 01 2026, 09:32 PM

Without a lockfile every install re-resolves from scratch, which is how
#4 happened: wrangler floated to a version whose peer range no longer
matched the pinned types package, and nothing recorded the set that had
last worked.

Generated fresh from the corrected package.json — 95 packages, all
resolved from registry.npmjs.org, no credentials.

CI now runs npm ci rather than npm install. It installs exactly this
tree, and it fails loudly if package.json and the lockfile ever drift
apart, which is the failure mode worth catching early. Verified locally:
npm ci exits 0 and typecheck reports no errors against it.
@maylonmcmurry-max

Copy link
Copy Markdown
Collaborator Author

Lockfile now committed, per your call — this supersedes the "one decision left to you" section in the description above.

package-lock.json regenerated from scratch against the corrected package.json (I threw away the one my --legacy-peer-deps runs had produced, so it isn't carrying residue). 95 packages, lockfileVersion: 3. Audited before committing: every resolved URL points at registry.npmjs.org and there are no auth tokens or private-registry references in it.

CI switched from npm install to npm ci. That's the actual payoff — npm ci installs exactly this tree and fails loudly the moment package.json and the lockfile drift apart, which is precisely how #4 happened in the first place. Verified the exact CI sequence locally: npm ci exits 0, npm run typecheck reports 0 errors against it.

@maylonmcmurry-max
maylonmcmurry-max merged commit dfae6b6 into main Sep 1, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants