diff --git a/.coderabbit.yaml b/.coderabbit.yaml index 79c0d2e..b4a2d9d 100644 --- a/.coderabbit.yaml +++ b/.coderabbit.yaml @@ -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. @@ -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/**" @@ -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 diff --git a/AGENTS.md b/AGENTS.md index eb98f13..8862c97 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -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 diff --git a/admin/index.html b/admin/index.html index 8829e03..6425731 100644 --- a/admin/index.html +++ b/admin/index.html @@ -124,7 +124,7 @@

Rules: every save becomes a new version you can revert

- +
No unsaved changes. Edit any rule to save a new version.

@@ -157,7 +157,7 @@

Custom characters

-

Uploading characters isn't available yet.

diff --git a/docs/CONTRACTS.md b/docs/CONTRACTS.md index 0be59d6..4f508f7 100644 --- a/docs/CONTRACTS.md +++ b/docs/CONTRACTS.md @@ -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, @@ -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 @@ -599,7 +598,7 @@ bun run port-check # node scripts/port-check.ts --base origin/main. Exit node scripts/port-rename.ts # 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 diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md index e51434e..bbd4d5b 100644 --- a/docs/DEVELOPMENT.md +++ b/docs/DEVELOPMENT.md @@ -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, @@ -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 @@ -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 - ` 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 + ` 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 --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 : 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: ` must set