Repository navigation
types(tools): the last 39 scripts are TypeScript, and the rules found real work - #532
Merged
Merged
Conversation
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
aadhar-sh | ed96394 | Aug 24 2026, 01:44 AM |
… real work Final slice. Every non-test file in tools/ is now .ts: 39 scripts renamed, specifiers rewritten across 82 files including package.json's scripts, the workflows and the photo shell scripts. Zero .mjs left outside the test suite. THE JSDOC TRAP STRUCK TWICE MORE, in both cases undoing work from earlier today. check-infra's DnsResolved/DnsUnreachable typedef — the one that took that file from 25 errors to 0 in #500 — went inert and took all 25 back. contract-shared's `testGlobals` cast went inert too, which quietly re-broke every fetch and cache double in the suite at once. Both are real syntax now. This is the fourth and fifth instance; renaming a JSDoc-annotated file is a DISARM, not a conversion. WHAT THE TYPE-AWARE RULES FOUND once they could finally see these files: - no-misused-spread on apply-infra's `cf()`. `init: RequestInit` allows headers to be a Headers instance or an array of pairs, and `{ ...init.headers }` silently yields index keys or nothing for either. That is the same spread-on-the-wrong-shape class that broke webmention ids in gotcha 34, and it was sitting in the one script allowed to mutate Cloudflare. - seven bare `.sort()` calls, now explicit. They are string sorts and the default was right, but the comparator is deliberately byte-stable rather than localeCompare, which is locale-dependent and would make the output of a drift checker depend on the machine running it. - ten template expressions interpolating unknown, each fixed by naming the shape it came from rather than by stringifying at the seam. Four contract tests failed and every one was doing its job: the dev-stage path, the check-*.mjs wrapper scan, my own package-manager scan (whose FLOOR fired, "expected the tools directory, got 0 files"), and the manifest projection whose generated banner names its generator. Updated, not loosened. 353/353 under bun, 352 under node, build/routes/photos green, six type programs clean, tools baseline still 0, lint clean with the ten rules now covering every script in tools/.
oddharsh
force-pushed
the
types/tools-rest-ts
branch
from
August 24, 2026 01:37
26923f7 to
b00c184
Compare
CI caught what the root tree could not. lens-reader's package.json runs its own typecheck through `node ../tools/check-test-types.mjs`, and lwe-ask carries the same shape — neither is reachable from the root sweep, so both still named .mjs and the Reader lens step died on MODULE_NOT_FOUND. This is the sub-project boundary CLAUDE.md already documents from the other direction: a green local suite proves nothing about CI when the two have different dependency sets. Here it is different SCRIPTS, in a manifest the root never reads. Also corrected the stale tool names in nine files of prose: wrangler configs, .gitignore, the manifest note and several Worker comments all pointed at .mjs files that no longer exist.
The wire-size job copies perf-snapshot into .perf-measure and runs it with node. My sweep updated the SOURCE path to .ts and left the DESTINATION as .mjs, and node type-strips by EXTENSION — so the copy was TypeScript wearing a .mjs name and failed to parse. The job caught it at its own stash proof rather than downstream, which is that step existing for exactly this: it runs the script with no arguments and requires exit 2, so anything else means the copy is short a file or cannot parse. Reproduced locally the same way before and after. Also corrected one comment naming perf-budget.mjs.
Contributor
Wire-size diff vs merge base
Brotli q11, the quality the edge serves. Every number here is deterministic: Client assetsTotal Brotli: 77.74 KiB → 77.73 KiB (-0.00 KiB, -0.0%) across 22 files, 3 changed.
PagesTotal Brotli: 531.29 KiB → 531.29 KiB (+0.00 KiB, +0.0%) across 50 files, 1 changed. No single file moved by 0.13 KiB or more. Plus 1 file under the 0.13 KiB floor, net +0.00 KiB — usually content-hash churn in Worker bundleNo change. 237.86 KiB gzip. Dictionary deltas (
|
This was referenced Aug 24, 2026
oddharsh
added a commit
that referenced
this pull request
Aug 24, 2026
Second tier, and the seventh program to declare the flag. #530's ratchet test asserts every program setting strictNullChecks appears in its declared list; this adds tools/ to it. RATCHETED RATHER THAN CLEAN, which is the difference from tier 1. tools/ carried 395 diagnostics under strictNullChecks; 101 are fixed here and the rest are seeded into config/ts-tools-baseline.json, where check-tool-types.mjs has held a per-file count since it was written. That ratchet fails on a new file, on a file that got worse, and on a file that got BETTER without the baseline being updated, so the number is monotone. The alternative was leaving the flag off until all 395 were done, which protects nothing in the meantime. THE SEEDED NUMBER IS 600 RATHER THAN THE 294 THIS BRANCH FIRST MEASURED, and the gap is #532 rather than a regression here. That PR converted 39 more tools scripts from .mjs to .ts while this was in flight, and JS-mode leniency withdraws with the extension, so the same code answers for more under the flag. The ratchet is what surfaced it: rebasing tripped both its arms, one file at a time. IT ALSO MADE THREE OF MY OWN FIXES INERT, which is the trap this migration keeps hitting and the fifth time it has fired. deploy-promote had its bail helper typed as never, and two nullable holders typed, all three in JSDoc. Correct while the file was .mjs and ignored the moment it became .ts: that file went 0 back to 37. All three are real annotations now, plus four accumulators the conversion newly exposed, and it is clean again. Worth a follow-up rather than widening this: 16 JSDoc type annotations across 5 tools .ts files on main are inert for exactly the same reason. Three files cleared outright: tools/lib/html-to-md.ts 45 → 0 talks/…/build-deck.mjs 30 → 0 tools/deploy-promote.mjs 26 → 0 THE .mjs AND .ts RULES ARE EXACT INVERSES, and getting this backwards costs a compile. A .ts file ignores JSDoc types; a .mjs file rejects a TypeScript annotation outright with TS8010. tools/ is 88 .mjs files, so nearly everything in this tier is JSDoc, and the three .ts files under tools/lib take annotations. Worth knowing before the next tier, since the same directory holds both. TYPING `die` AS `never` CLEARED A CLUSTER, and it is the more general lesson. deploy-promote.mjs uses `die()` as control flow throughout ("bail unless X"), and without `@returns {never}` TypeScript keeps the impossible branch alive and reports the value the caller just refused as still nullable. One annotation took that file from 11 remaining to 9, and the same shape is waiting in every other script with a bail helper. html-to-md.ts gained real types rather than annotations of convenience: `Token` as a discriminated union whose raw-text arm carries `raw` and `owner` together (they are one fact, and pairing them is what lets the tree builder drop a <script> body without proving the field is there), and `TextNode | ElementNode` with the stack declared as elements only, which is the invariant that makes `top().children` total. VERIFIED, because this converter publishes every Markdown twin. Built the twins with this branch's converter and with origin/main's, hashing all 58: identical at 568b28e024de910a957c6bc382e0a3e241764fd91e068297b68a1b1ede0ecbc9. And `deploy:promote --dry-run` runs end to end, resolves a target, and prints its freshness guard, which is the release path exercised rather than assumed. One behavioural addition, in deploy-promote: the sampling loop's result is read after the loop, so a zero SAMPLE_ATTEMPTS would have ramped to the next step having verified nothing. That now dies by name. It is a guard on a constant rather than on the network, and it is the one outcome the script exists to prevent. typecheck (10 programs), lint, 354 contract tests, build, and the site bundle at 237.34 KiB gzip.
oddharsh
added a commit
that referenced
this pull request
Aug 24, 2026
Second tier, and the seventh program to declare the flag. #530's ratchet test asserts every program setting strictNullChecks appears in its declared list; this adds tools/ to it. RATCHETED RATHER THAN CLEAN, which is the difference from tier 1. tools/ carried 395 diagnostics under strictNullChecks; 101 are fixed here and the rest are seeded into config/ts-tools-baseline.json, where check-tool-types.mjs has held a per-file count since it was written. That ratchet fails on a new file, on a file that got worse, and on a file that got BETTER without the baseline being updated, so the number is monotone. The alternative was leaving the flag off until all 395 were done, which protects nothing in the meantime. THE SEEDED NUMBER IS 600 RATHER THAN THE 294 THIS BRANCH FIRST MEASURED, and the gap is #532 rather than a regression here. That PR converted 39 more tools scripts from .mjs to .ts while this was in flight, and JS-mode leniency withdraws with the extension, so the same code answers for more under the flag. The ratchet is what surfaced it: rebasing tripped both its arms, one file at a time. IT ALSO MADE THREE OF MY OWN FIXES INERT, which is the trap this migration keeps hitting and the fifth time it has fired. deploy-promote had its bail helper typed as never, and two nullable holders typed, all three in JSDoc. Correct while the file was .mjs and ignored the moment it became .ts: that file went 0 back to 37. All three are real annotations now, plus four accumulators the conversion newly exposed, and it is clean again. Worth a follow-up rather than widening this: 16 JSDoc type annotations across 5 tools .ts files on main are inert for exactly the same reason. Three files cleared outright: tools/lib/html-to-md.ts 45 → 0 talks/…/build-deck.mjs 30 → 0 tools/deploy-promote.mjs 26 → 0 THE .mjs AND .ts RULES ARE EXACT INVERSES, and getting this backwards costs a compile. A .ts file ignores JSDoc types; a .mjs file rejects a TypeScript annotation outright with TS8010. tools/ is 88 .mjs files, so nearly everything in this tier is JSDoc, and the three .ts files under tools/lib take annotations. Worth knowing before the next tier, since the same directory holds both. TYPING `die` AS `never` CLEARED A CLUSTER, and it is the more general lesson. deploy-promote.mjs uses `die()` as control flow throughout ("bail unless X"), and without `@returns {never}` TypeScript keeps the impossible branch alive and reports the value the caller just refused as still nullable. One annotation took that file from 11 remaining to 9, and the same shape is waiting in every other script with a bail helper. html-to-md.ts gained real types rather than annotations of convenience: `Token` as a discriminated union whose raw-text arm carries `raw` and `owner` together (they are one fact, and pairing them is what lets the tree builder drop a <script> body without proving the field is there), and `TextNode | ElementNode` with the stack declared as elements only, which is the invariant that makes `top().children` total. VERIFIED, because this converter publishes every Markdown twin. Built the twins with this branch's converter and with origin/main's, hashing all 58: identical at 568b28e024de910a957c6bc382e0a3e241764fd91e068297b68a1b1ede0ecbc9. And `deploy:promote --dry-run` runs end to end, resolves a target, and prints its freshness guard, which is the release path exercised rather than assumed. One behavioural addition, in deploy-promote: the sampling loop's result is read after the loop, so a zero SAMPLE_ATTEMPTS would have ramped to the next step having verified nothing. That now dies by name. It is a guard on a constant rather than on the network, and it is the one outcome the script exists to prevent. typecheck (10 programs), lint, 354 contract tests, build, and the site bundle at 237.34 KiB gzip.
oddharsh
added a commit
that referenced
this pull request
Aug 24, 2026
…onfig (#538) cf-garage/wrangler.toml becomes cf-garage/cloudflare.config.ts, behind wrangler 4.124.0's hidden --x-new-config. cf-garage is the trial and the other three Workers are untouched: it is a demo Worker deployed by hand, so being wrong costs /garage/cf/* rather than the site. BUN LEVERAGE IS UNCHANGED, which was the condition. The loader refuses bun by name, and the refusal turns on the invocation rather than on the tool: bun x --no-install wrangler … --x-new-config loads, deploys bun run <script> (same .bin shim) loads bun ./node_modules/wrangler/bin/wrangler.js … REFUSED bun x and bun run resolve node_modules/.bin/wrangler, whose node shebang hands the process to node, so bun never reaches the loader. Only invoking wrangler's entry file under bun does, which gotcha 38 already stopped doing everywhere. bun still installs, runs every script and runs the tests. CI's step keeps `bun x` and changes one flag. VERIFIED BYTE-IDENTICAL, measured against the toml at the commit this branched from: 8.83 KiB / 3.07 KiB gzip and the same four bindings, from both configs. It reads 8.84 KiB after the rebase because #532 edited cf-garage/src/index.ts; that is main's number, not this change's. And `wrangler build --x-cf-build-output` writes the resolved config out so every field can be read back: the fetch trigger with its zone, observability plus traces at head_sampling_rate 1, all four bindings, the DO export. Three translations worth knowing. accountId sits on a separate `settings` export, so check-infra.ts now carries a pattern per aux config rather than one regex over three files in two formats. Bindings live under `env`, which is what makes the generated types read InferEnv<typeof config.default> instead of a snapshot (unreached here: `wrangler types` does not take the flag). And [[migrations]] has no equivalent and needs none, because a DO class declares its lifecycle as state on its export and wrangler sends the exports form the moment a config declares one. [dependencies_instrumentation] could not come across and cost nothing: the new schema has no field for it, and wrangler reads it as `?.enabled !== false`, so an absent block behaves exactly like the explicit true. WHAT A DRY RUN CANNOT CHECK, per the DO note: whether the API accepts the exports form for a class already carrying migration tag v1. The first real deploy from that directory is the measurement, and a revert restores the toml if it refuses. Deleting the toml also dropped cf-garage out of the quarantine test's own registry of deployable Workers, which discovers them by config filename. Its floor caught that; the predicate now knows both filenames. Green on the rebased base: 354 contract + 353 node tests, typecheck (10 programs, 246 files), oxlint type-aware, tools:check, infra:check --offline, build. Co-authored-by: Aadharsh Pannirselvam <19518661+oddharsh@users.noreply.github.com>
oddharsh
added a commit
that referenced
this pull request
Sep 1, 2026
…left behind (#684) #532 renamed 39 tools scripts from .mjs to .ts and the prose did not follow. `build.mjs` was the visible one, 143 times across 69 files. Chasing it turned up 10 more stale paths from the same commit, every one with a .ts counterpart that exists, so this sweeps all 11. One of them was a live defect rather than a doc nit: tools/build.ts's own homepage-bake error told the reader to run `node tools/photos/build-histogram-index.mjs`, a file that has not existed since #532. Four mentions are deliberately KEPT as .mjs, since a path rewrite run blind is how this repository falsifies its own history (the www/ and holding/ note in CLAUDE.md is the standing warning): - tools/build.ts's repo-paths comment cites radar-sample.mjs as its example of a citation that went stale, and the sentence ends "long after that file became .ts". Rewriting it deletes the point. - src/dict/a-dict/lens-browser.62f8bf05.js is a committed dictionary snapshot. It is bytes a browser already holds, so it is history by construction and editing it would break the tier it exists to serve. - design/explore-bac-map.md and design/PORTING.md are July-2026 design passes that design/README.md and CLAUDE.md both declare stale on purpose. Making their citations resolve would make them read as current. Why the existing gate never caught this: build.ts's repo-paths invariant scans the STAGED tree, so docs, tools, configs and workflows are outside it entirely, and its REPO_PATH regex is anchored on a top-level directory, so a bare `build.mjs` carries nothing for it to match. The one anchored citation that survives, `tools/build.mjs`, sits in a history doc it never reads. Byte impact, measured against a baseline build rather than assumed, because gotcha 35 prices a moved content hash at ~1400 files: - all 48 hashed /a/ URLs unchanged. Comments in nav.js, notepad.js and luna.css are stripped by the minifiers, exactly as gotcha 35 records. - 13 staged files changed, 12 of them .src.* readable twins, which are supposed to carry comments. - one genuinely-served file: garage/wire.html, whose `<code>build.mjs</code>` is visible page prose about the build. public/llms.txt likewise. - page-family.<hash8>.dict moves because wire.html's bytes moved. Ordinary for any page edit. - per-page dcz deltas go 54 to 55, and the extra one is garage__wire. build.ts skips a p-dict snapshot byte-identical to the current build, so that page had no per-page delta while it matched its snapshot and has a useful one now. config/derivations.json re-records shell/desktop: its input digest covers all of config/site-manifest.json, and two `note` fields in it named build.mjs. Regeneration was verified to change no output byte first. Co-authored-by: Aadharsh Pannirselvam <19518661+oddharsh@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Final slice. Every non-test file in
tools/is now.ts— 39 scripts renamed, specifiers rewritten across 82 files including package.json's scripts, the workflows, and the photo shell scripts. Zero.mjsleft outside the test suite.The JSDoc trap struck twice more, both undoing today's work
check-infra'sDnsResolved/DnsUnreachabletypedefcontract-shared'stestGlobalscastFourth and fifth instance. Renaming a JSDoc-annotated file is a disarm, not a conversion.
What the type-aware rules found
no-misused-spreadonapply-infra'scf().init: RequestInitallowsheadersto be aHeadersinstance or an array of pairs, and{ ...init.headers }silently yields index keys or nothing for either. That's the same spread-on-the-wrong-shape class that broke webmention ids in gotcha 34 — sitting in the one script allowed to mutate Cloudflare.Seven bare
.sort()calls, now explicit. They're string sorts and the default was right, but the comparator is deliberately byte-stable rather thanlocaleCompare, which is locale-dependent and would make a drift checker's output depend on the machine running it.Ten template expressions interpolating
unknown, each fixed by naming the shape it came from rather than stringifying at the seam.Four contract tests failed and every one was doing its job
The dev-stage path, the
check-*.mjswrapper scan, my own package-manager scan (whose floor fired: "expected the tools directory, got 0 files"), and the manifest projection whose generated banner names its generator. Updated, not loosened.353/353 under bun, 352 under node, build/routes/photos green, six type programs clean, tools baseline still 0, lint clean with all ten rules now covering every script in
tools/.