Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .coderabbit.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ reviews:
instructions: >-
Cloudflare Worker and the ChannelRoom SQLite Durable Object, on the Workers Free plan only (no paid
products). Every promise is awaited or handed to ctx.waitUntil; a dropped one can be cancelled. Chat mod
checks use ev.mod from MOD_BADGES in eventsub.js (a lead moderator has only the lead_moderator badge);
checks use ev.mod from MOD_BADGES in server/eventsub.ts (a lead moderator has only the lead_moderator badge);
flag any other badge read. Refusing the bot as a target stays the single BOT_TARGET_ACTIONS check.
Channels, logins and domains come from site.config.js only. Secrets, OAuth tokens and session values must
never reach logs, chat replies or error bodies.
Expand All @@ -40,7 +40,7 @@ reviews:
on the overlay but fighters, nameplates, bubbles and duel effects.
- path: "**/*.{ts,mts,cts,tsx}"
instructions: >-
Strict TypeScript (tsconfig.strict.json lists each new or ported file) with .ts import paths and erasable syntax
Strict TypeScript (every file is in a tsconfig program that bun run typecheck checks) with .ts import paths and erasable syntax
only. A pull request from a port/ branch changes no runtime code; port-check proves it in CI. Flag every
any, as cast or ! assertion without a reason in a comment on that line or the line above.
- path: "tests/**"
Expand All @@ -63,7 +63,7 @@ reviews:
mode: warning
instructions: >-
If the pull request changes behavior in server/, src/ or public/ (not only formatting, comments or
types), it must also change or add a test in tests/*.test.{mjs,ts} and update the matching doc in docs/
types), it must also change or add a test in tests/*.test.ts and update the matching doc in docs/
(CONTRACTS.md for routes, rules and config; STREAMER_SETUP.md or DUELS.md for what a streamer sees).
Pass if both are present or the change has no behavior effect.
- name: Config lives in site.config.js
Expand Down
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ It is not wrangler: never `wrangler deploy` or `--env`.
refuses on its own.
- **Every behavior change gets a unit test** in `tests/*.test.ts` and a doc update: routes, rules and config in
`docs/CONTRACTS.md`; anything a streamer sees in `docs/STREAMER_SETUP.md` or `docs/DUELS.md`.
- **Strict TypeScript.** New and ported code is `.ts` with `.ts` imports. A port PR changes no runtime code
- **Strict TypeScript.** New code is `.ts` with `.ts` imports. A port PR changes no runtime code
(`bun run port-check`). Each `any`, `as` or `!` says why on that line or above (docs/DEVELOPMENT.md).
- **Browser tests run headed with GPU flags** through `chromeOptions()` in `tests/chrome.ts`. Don't set `HEADLESS=1`.
- **Design:** read `DESIGN.md` before changing any page or the overlay. It documents the system in
Expand Down
4 changes: 2 additions & 2 deletions admin/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ <h2 id="config-title">Rules: every save becomes a new version you can revert</h2
<form id="config-form" novalidate>
<div id="config-fields"></div>
<div class="section"><label for="config-note">Note for the history (optional)</label><input id="config-note" maxlength="200" placeholder="Why this change"></div>
<!-- stays at the bottom of the screen while there are unsaved changes (class "dirty", set by admin.js) -->
<!-- stays at the bottom of the screen while there are unsaved changes (class "dirty", set by admin.ts) -->
<div class="config-bar" id="config-bar">
<div class="toolbar"><button class="btn" id="config-save" type="submit" disabled>Save new version</button><button class="btn" id="config-discard" type="button" disabled>Discard changes</button><span class="hint config-clean">No unsaved changes. Edit any rule to save a new version.</span></div>
<p class="status" id="config-status" role="status"></p>
Expand Down Expand Up @@ -157,7 +157,7 @@ <h2 id="custom-title">Custom characters</h2>
</figure>
<div class="small muted" id="custom-list"></div>
</div>
<!-- MOUNT POINT for Lane D's upload UI. admin.js imports /upload.js (public/upload.js) if it exists and calls
<!-- MOUNT POINT for Lane D's upload UI. admin.ts imports /upload.js (src/served/upload.ts) if it exists and calls
mountUpload(root, {channel, usage, limits, refresh}). Until then this box links nothing and says so. -->
<div class="mount section" id="upload-root" data-mount="upload"><p class="muted small">Uploading characters isn't available yet.</p></div>
</section>
Expand Down
23 changes: 11 additions & 12 deletions docs/CONTRACTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -563,16 +563,15 @@ gitleaks git --config .gitleaks.toml --redact . # secret scan; CI scans the wh
bun run audit # bun audit --audit-level=high; CI fails on a high or critical advisory
bun run format # oxfmt (.oxfmtrc.json); format:check only reports
bun run lint # oxlint --deny-warnings, type-aware (.oxlintrc.json)
bun run typecheck # tsc: tsconfig.worker/web/node.json (JS, loose), then four strict programs split by
# globals: tsconfig.strict.json (Worker and Node types: server/, scripts/,
# site.config.js, shared types, unit tests of Worker modules and tests/fakes.ts),
# tsconfig.strict-web.json (DOM types: src/, types/dom.ts), tsconfig.strict-tests.json
# (DOM and Node: unit tests of DOM modules) and tsconfig.strict-tests-shared.json
# (Worker, DOM and Node: unit tests that import both); then port-check --strict-list.
# It requires every TypeScript file in the program tsc reads from some strict config,
# except cloudflare.config.ts (tsconfig.node.json) and types/browser.d.ts
# (tsconfig.web.json). It fails a tsc error reading any of them, a strict option off
# in any, a .d.ts, .d.mts or .d.cts
bun run typecheck # tsc on five strict programs split by globals (strict options in tsconfig.base.json):
# tsconfig.worker.json (Workers types: server/), tsconfig.web.json (DOM types: src/,
# types/dom.ts), tsconfig.node.json (Workers and Node: scripts/, build configs, unit
# tests of Worker modules and tests/fakes.ts), tsconfig.node-dom.json (DOM and Node:
# unit tests of DOM modules, Playwright code, the character build) and
# tsconfig.node-all.json (Workers, DOM and Node: tests that import both); then
# port-check --strict-list. It requires every TypeScript file in the program tsc reads
# from some strict config. It fails a tsc error reading any of them, a strict option
# off in any, a .d.ts, .d.mts or .d.cts
# under skipLibCheck, and in any code file in those programs a comment with
# @ts-nocheck (any letter case) or eslint-disable / oxlint-disable (any form).
bun run port-check # node scripts/port-check.ts --base origin/main. Exits 1 on a difference or problem,
Expand All @@ -584,7 +583,7 @@ bun run port-check # node scripts/port-check.ts --base origin/main. Exit
# bunx cf build, offline, and compares every .cloudflare/output file (JS without
# comments, the rest byte for byte).
# - Leaves tsconfig*.json changes to that build diff, but fails a new root
# tsconfig.json. For each strict config the merge base has (any of the four),
# tsconfig.json. For each strict config the merge base has (any of the five),
# the head's resolved compilerOptions for it
# (tsc --showConfig) must equal the base's; only its files list may change.
# - In tests/, scripts/, cloudflare.config.ts and vite.config.ts, passes a string
Expand All @@ -599,7 +598,7 @@ bun run port-check # node scripts/port-check.ts --base origin/main. Exit
node scripts/port-rename.ts <file.js> # starts a port: renames to .ts, rewrites imports and doc paths;
# refuses files under public/ and never rewrites one: it lists each
# line there that names a renamed file as file:line
bun run test:unit # node --import ./tests/register.ts --test "tests/*.test.{mjs,ts}"
bun run test:unit # node --import ./tests/register.ts --test "tests/*.test.ts"
bun run test:port # node --test tests/port/port-check.test.ts: port-check and port-rename against fixture
# repos in one new OS temp folder per run (the build tests run bunx cf build); not in
# check, for speed; CI runs it next to bun run check
Expand Down
80 changes: 41 additions & 39 deletions docs/DEVELOPMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,14 +43,11 @@ bun run dev
- `bun run lint` runs [oxlint](https://oxc.rs/docs/guide/usage/linter) with type-aware rules
(`.oxlintrc.json`). Warnings fail it. In `server/` every promise must be awaited or handed to
`waitUntil`, because the Worker can cancel one that is dropped.
- `bun run typecheck` runs `tsc` seven times. `tsconfig.worker.json` (server/, all TypeScript, with Workers types only),
`tsconfig.web.json` (src/ with the served modules in src/served/, DOM types plus `types/browser.d.ts`) and `tsconfig.node.json`
(the build configs) check loosely; `tsconfig.strict.json` (Worker and Node types),
`tsconfig.strict-web.json` (DOM types), `tsconfig.strict-tests.json` (DOM and Node types) and
`tsconfig.strict-tests-shared.json` (Worker, DOM and Node types) check every TypeScript file, the unit
tests, browser tests, scripts and `vite.config.ts` included, with `strict`, `noUncheckedIndexedAccess` and
`exactOptionalPropertyTypes`. In a `.js` file, add JSDoc where inference falls short
(`/** @param {{ … }} opts */`) rather than casting the problem away.
- `bun run typecheck` runs `tsc` on five strict programs (below), then `node scripts/port-check.ts --strict-list`.
`tsconfig.base.json` sets `strict`, `noUncheckedIndexedAccess` and `exactOptionalPropertyTypes` for all of
them, so every TypeScript file is checked the same way: the Worker, the pages, the unit tests, browser tests,
scripts and build configs. The one JavaScript file in a program is `site.config.js`, which has tsc check it
through its own `// @ts-check`.
- `bun run audit` checks every installed package against the advisory database and fails on a high or
critical one. When the fix sits behind a dependency that pins the old version, add an exact version to
`overrides` in `package.json` (as for `sharp`) once that release is 7 days old. Before bumping a package,
Expand All @@ -60,31 +57,36 @@ bun run dev

## TypeScript port

The code moves from JSDoc-typed JavaScript to strict TypeScript one file at a time. Node 22.18+ runs
`.ts` scripts with no flags, and Vite and `cf build` strip the types from the pages and the Worker.
The code moved from JSDoc-typed JavaScript to strict TypeScript one file at a time. Every file outside
`public/`, except `site.config.js`, is now `.ts`. Node 22.18+ runs `.ts` scripts with no flags, and Vite
and `cf build` strip the types from the pages and the Worker.

- New and ported files are `.ts`, import local files with the `.ts` extension and use only syntax
that strips to JavaScript (no `enum`, `namespace` or parameter properties; `erasableSyntaxOnly`).
Type-only imports say `import type` (`verbatimModuleSyntax`).
- Every TypeScript file (`.ts`, `.mts`, `.cts`, `.tsx`) is listed in `files` in one of the four
strict programs, split by runtime so each sees only its own globals:
- `tsconfig.strict.json`: the Worker (`server/`), the scripts that use no DOM or Buffer methods (`scripts/`),
`vite.config.ts`, `site.config.js`, the shared types, the Node-only test runners (`tests/chrome.ts`,
`tests/seed-local.ts`, `tests/build-versions.ts`, `tests/upload-workerd.ts`), and the unit tests that import only Worker and shared modules (with their
helpers: `tests/fakes.ts`, `tests/register.ts` and the others). Worker and Node types, no DOM, so
server code can't use `document` or `window`, and Web Crypto and streams keep the Workers types.
- `tsconfig.strict-web.json`: browser code (`src/`, and `types/dom.ts`). DOM types and
`vite/client`, no Worker or Node types. A browser file that imports a server module pulls it
into this program too, so such a module (today `server/upgrades.ts`, and through type imports
in `src/dashboard.ts` and `src/parse.ts`, `server/cosmetics.ts` and `server/model.ts`) must
type-check with DOM types as well.
- `tsconfig.strict-tests.json`: unit tests that import a DOM module but no Worker module
- Every TypeScript file (`.ts`, `.mts`, `.cts`, `.tsx`) is in one of five strict programs, split by
runtime so each sees only its own globals. The Worker types, the DOM and Node clash (the Worker types declare
`Buffer` as `any`; they and the DOM type streams, Web Crypto and canvas contexts differently), so one program
can't hold them all:
- `tsconfig.worker.json`: the Worker, every file in `server/`. Workers types only, so server code can't
use Node, `document` or `window`, and Web Crypto and streams keep the Workers types.
- `tsconfig.web.json`: browser code, every file in `src/` (the served modules in `src/served/` included),
and `types/dom.ts`. DOM types and `vite/client`, no Worker or Node types. A browser file that imports a
server module pulls it into this program too, so such a module (today `server/upgrades.ts`, and through
type imports in `src/dashboard.ts` and `src/parse.ts`, `server/cosmetics.ts` and `server/model.ts`)
must type-check with DOM types as well.
- `tsconfig.node.json`: the scripts that use no DOM or Buffer methods (`scripts/`), `vite.config.ts` and
`cloudflare.config.ts`, the Node-only test runners (`tests/chrome.ts`, `tests/seed-local.ts`,
`tests/build-versions.ts`, `tests/upload-workerd.ts`), and the unit tests that import only Worker and
shared modules (with their helpers: `tests/fakes.ts`, `tests/register.ts` and the others). Worker and
Node types, no DOM.
- `tsconfig.node-dom.json`: unit tests that import a DOM module but no Worker module
(`tests/browser-parse.test.ts`, `tests/overlay.test.ts`, `tests/served.test.ts`), and Playwright code that imports none
(`tests/dev-ui.ts`, `scripts/og-image.ts`, `scripts/live-chat-e2e.ts`), whose page callbacks
run in the browser. DOM and Node types. The character build (`scripts/build-characters.ts`,
`scripts/lib/png-io.ts`, `scripts/lib/zip-read.ts`) is here too: the Worker types declare `Buffer`
as `any`, which hides its methods in `tsconfig.strict.json`.
- `tsconfig.strict-tests-shared.json`: unit tests that import both a Worker module and a DOM
as `any`, which hides its methods in `tsconfig.node.json`.
- `tsconfig.node-all.json`: unit tests that import both a Worker module and a DOM
module (`tests/arena-client.test.ts`, `tests/atlas.test.ts`, `tests/cosmetics.test.ts`,
`tests/spritify.test.ts`), and the browser tests that import a Worker module (`tests/smoke.ts`,
`tests/ui.ts`, `tests/arena-browser.ts`, `tests/e2e-local.ts`). Worker, DOM and Node types. Keep a test file on one side where
Expand All @@ -96,24 +98,24 @@ The code moves from JSDoc-typed JavaScript to strict TypeScript one file at a ti
value the test expects to find, and `Loose` (`any`) for JSON bodies and rows a test asserts on.
Each of their casts carries its reason there once.

`types/lib-guards.ts`, `types/site.ts` and `site.config.js` are on every list. A `.d.ts` file on
any fails (below), so new types go in `.ts` files. Two TypeScript files are checked by
another program instead: `cloudflare.config.ts` by `tsconfig.node.json` and
`types/browser.d.ts` by `tsconfig.web.json`. `bun run typecheck` (so `bun run check`) enforces
`tsconfig.worker.json` and `tsconfig.web.json` take a folder (`include`), so a new file in `server/`
or `src/` is checked as soon as it exists; the three Node programs list each file in `files`. `types/lib-guards.ts`, `types/site.ts` and
`site.config.js` are in every program. A `.d.ts` file in any fails (below), so new types go in `.ts`
files. `bun run typecheck` (so `bun run check`) enforces
this by ending with `node scripts/port-check.ts --strict-list`, which reads every tracked and
untracked file that git doesn't ignore and prints
`tsconfig.strict.json, tsconfig.strict-web.json, tsconfig.strict-tests.json and tsconfig.strict-tests-shared.json cover every TypeScript file`,
`tsconfig.worker.json, tsconfig.web.json, tsconfig.node.json, tsconfig.node-dom.json and tsconfig.node-all.json cover every TypeScript file`,
or one line per file it misses, as
`<file> is not in tsconfig.strict.json, tsconfig.strict-web.json, tsconfig.strict-tests.json or tsconfig.strict-tests-shared.json`.
The lines name `tsconfig.strict.json` and each of the other three the repo has (two read
`a and b` / `a or b`), so a repo with only `tsconfig.strict.json` names it alone. It asks tsc for
`<file> is not in tsconfig.worker.json, tsconfig.web.json, tsconfig.node.json, tsconfig.node-dom.json or tsconfig.node-all.json`.
The lines name `tsconfig.worker.json` and each of the other four the repo has (two read
`a and b` / `a or b`), so a repo with only `tsconfig.worker.json` names it alone. It asks tsc for
each program (`tsc -p <config> --listFilesOnly`), so `files`, `include` and `exclude` count as
tsc reads them, and each error tsc prints reading a config fails it too, as
`tsc -p <config>: error TS…`.
- The strict list also fails when any strict config checks less, as tsc reads it after
`extends` (`tsc --showConfig`; the rules take about 0.25 s). Each line names the config; the
problems print in the order `tsconfig.strict.json`, `tsconfig.strict-web.json`,
`tsconfig.strict-tests.json`, `tsconfig.strict-tests-shared.json`:
problems print in the order `tsconfig.worker.json`, `tsconfig.web.json`, `tsconfig.node.json`,
`tsconfig.node-dom.json`, `tsconfig.node-all.json`:
- `strict`, `noUncheckedIndexedAccess`, `exactOptionalPropertyTypes`, `verbatimModuleSyntax` and
`erasableSyntaxOnly` must be true:
`<config> must set <option> to true (it is <value or "not set">)`.
Expand Down Expand Up @@ -157,10 +159,10 @@ A port pull request changes types only, from a `port/<name>` branch:
oxc-stripped JavaScript of its `.js` or `.ts` source at `/<name>.js` (docs/CONTRACTS.md §9). oxc
keeps comments, so a port that adds only types serves the same code under the same `?v=` hash,
while a changed comment changes the hash and open overlays reload once.
2. Add types until `bun run typecheck` passes, and add the file to `tsconfig.strict.json` (Worker,
scripts, unit tests of Worker modules), `tsconfig.strict-web.json` (browser code in `src/`),
`tsconfig.strict-tests.json` (unit tests of DOM modules, Playwright code) or
`tsconfig.strict-tests-shared.json` (tests that import both).
2. Add types until `bun run typecheck` passes. A file in `server/` or `src/` is already in its program;
add any other to `files` in `tsconfig.node.json` (scripts, unit tests of Worker modules),
`tsconfig.node-dom.json` (unit tests of DOM modules, Playwright code) or `tsconfig.node-all.json`
(tests that import both).
3. `bun run port-check` (`node scripts/port-check.ts --base origin/main`) checks every file that
differs between the working tree and where the branch left `main`. Code files are `.js`, `.mjs`,
`.cjs`, `.jsx`, `.ts`, `.mts`, `.cts` and `.tsx`. Run without `--base <ref>` or `--strict-list`,
Expand Down
Loading
Loading