Public: anyone can read this; the crew can change it.
Typecheck with the native TypeScript compiler (TS 7 / tsgo): tsc is now the forge's top CPU consumer #1870
Check · Review · Land · Live done — Landed and live in production.
torque/forge:torque/1864-typecheck-with-the-native-typescript-com → main 1 commit fork
9 files
changed +86
−14
CLAUDE.md +3 −1
| @@ -13,7 +13,9 @@ isn't understood yet. | ||
| 13 | 13 | |
| 14 | 14 | 1. **Zero runtime dependencies.** `package.json` `dependencies` is empty and |
| 15 | 15 | stays empty. Never `bun add` a runtime dep. Need a 20-line utility? Write it |
| 16 | as a 20-line file. `devDependencies` is types + `tsc` only. | |
| 16 | as a 20-line file. `devDependencies` is types + `tsc` only (`typescript` is | |
| 17 | the native 7.x checker; `typescript-api` is 5.x for `scripts/hollow.ts`'s | |
| 18 | AST walk). | |
| 17 | 19 | 2. **Bun, not npm/yarn/pnpm.** Install, run, test, build all go through `bun`. |
| 18 | 20 | The runtime provides everything: `Bun.serve`, `Bun.spawn`, `bun:sqlite`, |
| 19 | 21 | `Bun.password` (argon2), WebCrypto, gzip. |
README.md +7 −3
| @@ -33,7 +33,7 @@ feeling on a foundation sized for one person. | ||
| 33 | 33 | ## Run it |
| 34 | 34 | |
| 35 | 35 | sh |
| 36 | bun install # dev-only: types + tsc, no runtime deps | |
| 36 | bun install # dev-only: types + tsc (native, 7.x), no runtime deps | |
| 37 | 37 | bun run src/main.ts # web on :3000, ssh on :2222 (FORGE_SSH_PORT=0 turns it off; a taken port turns it off with one warning) |
| 38 | 38 | bun src/main.ts --preflight # can this tree boot? config parses, migrations run on a copy of the database; one line, exit 0 or 1, no socket opened. Run it before the swap, not under load: the copy is not atomic against a live writer, and a `preflight-*` directory a killed run left behind is swept by the next run once an hour old, and a missing data directory is created, as boot would |
| 39 | 39 | |
| @@ -676,8 +676,12 @@ across how many files, in how long, then the slowest and fastest shard. | ||
| 676 | 676 | The check's first step is the gate-list audit (`bun scripts/gate.ts`), so a |
| 677 | 677 | test file listed in neither `GATE` nor `EXCLUDED` fails in under a second, |
| 678 | 678 | before install-heavy work. `tsc` then runs `--incremental` with a 2 GB heap, |
| 679 | its build info in `.forge-cache/`: the forge restores that directory from the | |
| 680 | base's own green check and keeps it only from green checks of the default | |
| 679 | its build info in `.forge-cache/`. It is TypeScript 7, the native compiler (a | |
| 680 | Go binary, so the bound is `GOMEMLIMIT=2GiB`): on this tree a cold check is | |
| 681 | ~3 s and ~17 CPU-s where 5.9 took ~30 s and ~50 CPU-s (#1864). 5.9 stays as | |
| 682 | the dev dependency `typescript-api` only because `scripts/hollow.ts` walks | |
| 683 | its AST, which 7.0 has no stable JS API for. The forge restores | |
| 684 | `.forge-cache/` from the base's own green check and keeps it only from green checks of the default | |
| 681 | 685 | branch (`src/checks/cache.ts`), so a pull's code never fills a cache another |
| 682 | 686 | check reads. The full gate also writes every file's time there, and the next |
| 683 | 687 | split weighs every file by measurement rather than the 41 the table named. |
bun.lock +45 −2
| @@ -7,7 +7,8 @@ | ||
| 7 | 7 | "devDependencies": { |
| 8 | 8 | "@anthropic-ai/sandbox-runtime": "^0.0.79", |
| 9 | 9 | "@types/bun": "^1.2.0", |
| 10 | "typescript": "^5.9.0", | |
| 10 | "typescript": "^7.0.2", | |
| 11 | "typescript-api": "npm:typescript@^5.9.0", | |
| 11 | 12 | }, |
| 12 | 13 | }, |
| 13 | 14 | }, |
| @@ -20,13 +21,55 @@ | ||
| 20 | 21 | |
| 21 | 22 | "@types/node": ["@types/[email protected]", "", { "dependencies": { "undici-types": "~8.3.0" } }, "sha512-Vu4a5UFA9rIIFJ7rB/Vaafh9lrCQszopTCx6KjFboXTGQbPNasehVR5TEiithSDGyd1DEiUByggTZsg8jukeIg=="], |
| 22 | 23 | |
| 24 | "@typescript/typescript-aix-ppc64": ["@typescript/[email protected]", "", { "os": "aix", "cpu": "ppc64" }, "sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ=="], | |
| 25 | ||
| 26 | "@typescript/typescript-darwin-arm64": ["@typescript/[email protected]", "", { "os": "darwin", "cpu": "arm64" }, "sha512-gowzar9MwS/aRWp6f3a4KUqzRjAZjOsmGNCM6LcTgXum+dBfgsBVMN+AgvOCCbguXyick6LJhpBszxMebJ8syA=="], | |
| 27 | ||
| 28 | "@typescript/typescript-darwin-x64": ["@typescript/[email protected]", "", { "os": "darwin", "cpu": "x64" }, "sha512-SZ9xZInqApNlNGc9s0W1VSsktYSOe9cFqNOIqmN1Gs8SmkjKZYFt017G4VwPxASInODuAdbTW7sXiFUf893RgA=="], | |
| 29 | ||
| 30 | "@typescript/typescript-freebsd-arm64": ["@typescript/[email protected]", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-W5NH4y/J0plIIS5b2xvTEkU7JFxyqdMAOgf+Ilhl0vHQXKO5dZoxd+C/jEtq56c4F3wk71RB4BMRQ2XdI+bwYQ=="], | |
| 31 | ||
| 32 | "@typescript/typescript-freebsd-x64": ["@typescript/[email protected]", "", { "os": "freebsd", "cpu": "x64" }, "sha512-UMGDx5sTpzNw3WiPebH7l90IWfJggEd+egHt/q6p7/Cm3zqoV7VxkGXt+3DxPIw8CcmvAB0j3sVVfbhX+M4Tpw=="], | |
| 33 | ||
| 34 | "@typescript/typescript-linux-arm": ["@typescript/[email protected]", "", { "os": "linux", "cpu": "arm" }, "sha512-gffT3xPz9sR7j/YJExkyPntrI0P2EP9XbOyWzth2/Gs0RstK+90RBcO0ncXoXy/beYll1SXw846Nf2zdnEz0QQ=="], | |
| 35 | ||
| 36 | "@typescript/typescript-linux-arm64": ["@typescript/[email protected]", "", { "os": "linux", "cpu": "arm64" }, "sha512-Qh4eU4/y3yDjnfjjyPYihMj5/ODIlmt+Bzu17OI+fiSRDW57QmU5SiN63exPRNJPKUzcc1INa1NXdrJ+MqHjUQ=="], | |
| 37 | ||
| 38 | "@typescript/typescript-linux-loong64": ["@typescript/[email protected]", "", { "os": "linux", "cpu": "none" }, "sha512-uEHck9i8hoAzXPiYRib1O7miOnz23SxIeVl6F4LXox+qov1K35jHcEW6VHKvZI+pyvl7fZEP4MCU5LYvIq1GuQ=="], | |
| 39 | ||
| 40 | "@typescript/typescript-linux-mips64el": ["@typescript/[email protected]", "", { "os": "linux", "cpu": "none" }, "sha512-R4KvAMnE43W5Qeqb0Ly56O3mWMWIAgsMyz36DCaycd5nbg/9kzm0liw3JocfRqyJY0KPmzFjbswozXyW0DnIYA=="], | |
| 41 | ||
| 42 | "@typescript/typescript-linux-ppc64": ["@typescript/[email protected]", "", { "os": "linux", "cpu": "ppc64" }, "sha512-DORx5b3sd/4S7eayxm4FQv+A7CrkUIGRaHiwI8oiHTAI1fAPWhF4J0vAlkC8biAlHSVVwxMQ3tjZ2/DVbnQiiA=="], | |
| 43 | ||
| 44 | "@typescript/typescript-linux-riscv64": ["@typescript/[email protected]", "", { "os": "linux", "cpu": "none" }, "sha512-wf0jqEDOjrPRnKwYRyyJDRo11KMbvMFrU+q4zqKyChODBzvlkbhNQfKvLxQCcwTpdDaXSHZTVuh0JoCrKCUMHQ=="], | |
| 45 | ||
| 46 | "@typescript/typescript-linux-s390x": ["@typescript/[email protected]", "", { "os": "linux", "cpu": "s390x" }, "sha512-IkwJc3L7yhytWd/ewjyxNDfOmswCm9GWMJT/ue/dU4aZNbwZeYAetq42VyLmsmSjvoX7z74X6ZaYCtzAr0EuGw=="], | |
| 47 | ||
| 48 | "@typescript/typescript-linux-x64": ["@typescript/[email protected]", "", { "os": "linux", "cpu": "x64" }, "sha512-EYdf2cNg7rgCWJnxCdJ+F3V39O8ihb37eHAu1LK8oAFizgTQbPOK7zHHXbPt8rX24COqODXeI3sIf0fCXG7H/A=="], | |
| 49 | ||
| 50 | "@typescript/typescript-netbsd-arm64": ["@typescript/[email protected]", "", { "os": "none", "cpu": "arm64" }, "sha512-+polYF4MF04aPpO5FTkHran9yUQDSXqy5GiSDKpsll5jy3l3+g9QLhpf39T+ePtefhXLOGrLl0QIjkQP6VnelA=="], | |
| 51 | ||
| 52 | "@typescript/typescript-netbsd-x64": ["@typescript/[email protected]", "", { "os": "none", "cpu": "x64" }, "sha512-8YIT0EHM/3dq10ZOVF/A7pc/YSMtbcecct4rWtexrnSCHOPcpC2KTLXfTCR6vDpnSiY12heNb1GiN/wu+T/FyA=="], | |
| 53 | ||
| 54 | "@typescript/typescript-openbsd-arm64": ["@typescript/[email protected]", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-APT8+ClYnuYm1u9+kgGXoMj2VzWzcymwh2gNSQVySHfkRDGOTVkoWLjCmOQSaO+PoqQ57B0flRp9SA+7GnnkzQ=="], | |
| 55 | ||
| 56 | "@typescript/typescript-openbsd-x64": ["@typescript/[email protected]", "", { "os": "openbsd", "cpu": "x64" }, "sha512-yX7s+Q0Dln0Dt9tEzZsAjXXR/+ytBM7AlglaqyeMPxQszJ1JhlJdZ6jLA+IzldHtflX81em7lDao1xXu+aRRkg=="], | |
| 57 | ||
| 58 | "@typescript/typescript-sunos-x64": ["@typescript/[email protected]", "", { "os": "sunos", "cpu": "x64" }, "sha512-dLJDGaLZ1D4HPQn62u1n8mBDkJREwMsAkCdkwd4Ieqw+x3TUyTsqY0YiBCtE6H6OzzgGk3iuZ3vFWRS+E8/d1g=="], | |
| 59 | ||
| 60 | "@typescript/typescript-win32-arm64": ["@typescript/[email protected]", "", { "os": "win32", "cpu": "arm64" }, "sha512-Gyl1Vy6OsWesLzmq+EP0Fb7b4Nid5232AvcA2SFcdYreldpNtYFFofPjnt62y9hQy7VTaZp65ICJjuAQRaVcIQ=="], | |
| 61 | ||
| 62 | "@typescript/typescript-win32-x64": ["@typescript/[email protected]", "", { "os": "win32", "cpu": "x64" }, "sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g=="], | |
| 63 | ||
| 23 | 64 | "bun-types": ["[email protected]", "", { "dependencies": { "@types/node": "*" } }, "sha512-4N0ig0fEomHt5R0KCFWjovxow98rIoRwKolrYdCcknNwMekCXRnWEUvgu5soYV8QXtVsrUD8B95MBOZGPvr6KQ=="], |
| 24 | 65 | |
| 25 | 66 | "commander": ["[email protected]", "", {}, "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA=="], |
| 26 | 67 | |
| 27 | 68 | "node-forge": ["[email protected]", "", {}, "sha512-LarFH0+6VfriEhqMMcLX2F7SwSXeWwnEAJEsYm5QKWchiVYVvJyV9v7UDvUv+w5HO23ZpQTXDv/GxdDdMyOuoQ=="], |
| 28 | 69 | |
| 29 | "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], | |
| 70 | "typescript": ["typescript@7.0.2", "", { "optionalDependencies": { "@typescript/typescript-aix-ppc64": "7.0.2", "@typescript/typescript-darwin-arm64": "7.0.2", "@typescript/typescript-darwin-x64": "7.0.2", "@typescript/typescript-freebsd-arm64": "7.0.2", "@typescript/typescript-freebsd-x64": "7.0.2", "@typescript/typescript-linux-arm": "7.0.2", "@typescript/typescript-linux-arm64": "7.0.2", "@typescript/typescript-linux-loong64": "7.0.2", "@typescript/typescript-linux-mips64el": "7.0.2", "@typescript/typescript-linux-ppc64": "7.0.2", "@typescript/typescript-linux-riscv64": "7.0.2", "@typescript/typescript-linux-s390x": "7.0.2", "@typescript/typescript-linux-x64": "7.0.2", "@typescript/typescript-netbsd-arm64": "7.0.2", "@typescript/typescript-netbsd-x64": "7.0.2", "@typescript/typescript-openbsd-arm64": "7.0.2", "@typescript/typescript-openbsd-x64": "7.0.2", "@typescript/typescript-sunos-x64": "7.0.2", "@typescript/typescript-win32-arm64": "7.0.2", "@typescript/typescript-win32-x64": "7.0.2" }, "bin": { "tsc": "bin/tsc" } }, "sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA=="], | |
| 71 | ||
| 72 | "typescript-api": ["[email protected]", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], | |
| 30 | 73 | |
| 31 | 74 | "undici-types": ["[email protected]", "", {}, "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ=="], |
| 32 | 75 |
forge.json +2 −2
| @@ -14,8 +14,8 @@ | ||
| 14 | 14 | } |
| 15 | 15 | }, |
| 16 | 16 | "check": { |
| 17 | "fast": "bun scripts/gate.ts && node --max-old-space-size=2048 ./node_modules/typescript/lib/tsc.js --noEmit --incremental --tsBuildInfoFile .forge-cache/tsc.tsbuildinfo && bun scripts/test-shards.ts --timeout 60000 --tier fast --diff $FORGE_CHECK_BASE..HEAD --learned \"$FORGE_CHECK_LEARNED\"", | |
| 18 | "run": "bun scripts/gate.ts && node --max-old-space-size=2048 ./node_modules/typescript/lib/tsc.js --noEmit --incremental --tsBuildInfoFile .forge-cache/tsc.tsbuildinfo && bun scripts/test-shards.ts --timeout 60000 --gate --impact", | |
| 17 | "fast": "bun scripts/gate.ts && GOMEMLIMIT=2GiB ./node_modules/typescript/bin/tsc --noEmit --incremental --tsBuildInfoFile .forge-cache/tsc.tsbuildinfo && bun scripts/test-shards.ts --timeout 60000 --tier fast --diff $FORGE_CHECK_BASE..HEAD --learned \"$FORGE_CHECK_LEARNED\"", | |
| 18 | "run": "bun scripts/gate.ts && GOMEMLIMIT=2GiB ./node_modules/typescript/bin/tsc --noEmit --incremental --tsBuildInfoFile .forge-cache/tsc.tsbuildinfo && bun scripts/test-shards.ts --timeout 60000 --gate --impact", | |
| 19 | 19 | "timeout": 7200, |
| 20 | 20 | "limits": { |
| 21 | 21 | "memory": 8192, |
package.json +2 −1
| @@ -14,6 +14,7 @@ | ||
| 14 | 14 | "devDependencies": { |
| 15 | 15 | "@anthropic-ai/sandbox-runtime": "^0.0.79", |
| 16 | 16 | "@types/bun": "^1.2.0", |
| 17 | "typescript": "^5.9.0" | |
| 17 | "typescript": "^7.0.2", | |
| 18 | "typescript-api": "npm:typescript@^5.9.0" | |
| 18 | 19 | } |
| 19 | 20 | } |
scripts/hollow.ts +3 −1
| @@ -46,7 +46,9 @@ | ||
| 46 | 46 | |
| 47 | 47 | import { readdirSync, readFileSync } from "node:fs"; |
| 48 | 48 | import { posix } from "node:path"; |
| 49 | import ts from "typescript"; | |
| 49 | // TypeScript 5's compiler API, under an alias: `typescript` is the native | |
| 50 | // 7.x checker (a Go binary), and 7.0 ships no stable JS API to walk an AST. | |
| 51 | import ts from "typescript-api"; | |
| 50 | 52 | import { ALLOWED, type Allowed } from "./hollow-allow.ts"; |
| 51 | 53 | |
| 52 | 54 | const ACTIONS = "src/actions/"; |
src/web/og/card.ts +1 −1
| @@ -50,7 +50,7 @@ const KIND_COLOR: Record<CardKind, string> = { | ||
| 50 | 50 | function clean(s: unknown): string { |
| 51 | 51 | return String(s ?? "") |
| 52 | 52 | .replace(/\s+/g, " ") |
| 53 | .replace(/[^\u0009\u000A\u000D --�\u{10000}-\u{10FFFF}]/gu, "") | |
| 53 | .replace(/[^\u0009\u000A\u000D -\uD7FF\uE000-\uFFFD\u{10000}-\u{10FFFF}]/gu, "") | |
| 54 | 54 | .trim(); |
| 55 | 55 | } |
| 56 | 56 |
test/og-card.test.ts +10
| @@ -56,6 +56,16 @@ describe("og card", () => { | ||
| 56 | 56 | wellFormed(renderCard({ kind: "app", title: "" })); |
| 57 | 57 | }); |
| 58 | 58 | |
| 59 | test("XML 1.0's Char ranges keep both ends and drop what lies between", () => { | |
| 60 | // https://www.w3.org/TR/xml/#charsets — #x20-#xD7FF | #xE000-#xFFFD | #x10000-#x10FFFF | |
| 61 | const kept = "\u0020\uD7FF\uE000\uFFFD\u{10000}\u{10FFFF}"; | |
| 62 | for (const dropped of ["\u0001", "\u001F", "\uD800", "\uDFFF", "\uFFFE", "\uFFFF"]) { | |
| 63 | const svg = renderCard({ kind: "repo", title: `a${kept}${dropped}b` }); | |
| 64 | wellFormed(svg); | |
| 65 | expect(svg).toContain(`a${kept}b`); | |
| 66 | } | |
| 67 | }); | |
| 68 | ||
| 59 | 69 | test("fit and escapeXml", () => { |
| 60 | 70 | expect(fit("short", 30, 1000)).toBe("short"); |
| 61 | 71 | expect(fit("a".repeat(200), 30, 300).endsWith("…")).toBe(true); |
test/shards.test.ts +13 −3
| @@ -508,16 +508,26 @@ describe("the gate audit alone, first in the check", () => { | ||
| 508 | 508 | expect(green.err).toBe(""); |
| 509 | 509 | }, 30_000); |
| 510 | 510 | |
| 511 | test("forge.json audits before tsc, and tsc runs incremental from the check's cache", async () => { | |
| 511 | test("forge.json audits before tsc, and the native tsc runs incremental from the check's cache", async () => { | |
| 512 | 512 | const check = parseCheck(readFileSync(new URL("../forge.json", import.meta.url), "utf8"), undefined); |
| 513 | 513 | if (!check.ok || check.value === undefined) throw new Error("no check"); |
| 514 | 514 | for (const command of [check.value.run, check.value.fast ?? ""]) { |
| 515 | 515 | expect(command.startsWith("bun scripts/gate.ts && ")).toBe(true); |
| 516 | expect(command.indexOf("scripts/gate.ts")).toBeLessThan(command.indexOf("tsc.js")); | |
| 517 | expect(command).toContain("--max-old-space-size=2048"); | |
| 516 | // TypeScript 7 (#1864): a Go binary, so its heap bound is GOMEMLIMIT, not node's. | |
| 517 | expect(command).toContain(" && GOMEMLIMIT=2GiB ./node_modules/typescript/bin/tsc --noEmit "); | |
| 518 | expect(command.indexOf("scripts/gate.ts")).toBeLessThan(command.indexOf("bin/tsc")); | |
| 518 | 519 | expect(command).toContain(`--incremental --tsBuildInfoFile ${CHECK_CACHE}/tsc.tsbuildinfo`); |
| 519 | 520 | } |
| 520 | 521 | }); |
| 522 | ||
| 523 | test("`typescript` is the native 7.x checker; 5.x stays only as the compiler API hollow.ts walks", async () => { | |
| 524 | const pkg = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")); | |
| 525 | expect(pkg.dependencies ?? {}).toEqual({}); | |
| 526 | expect(pkg.devDependencies["typescript"]).toMatch(/^\^7\./); | |
| 527 | expect(pkg.devDependencies["typescript-api"]).toMatch(/^npm:typescript@\^5\./); | |
| 528 | const checker = await import("typescript/package.json"); | |
| 529 | expect(checker.version).toMatch(/^7\./); | |
| 530 | }); | |
| 521 | 531 | }); |
| 522 | 532 | |
| 523 | 533 | describe("one file per worker, timed", () => { |
new above
offline