H-6679: Migrate monorepo to TypeScript 6.0.3#8999
Conversation
Bumps the workspace-wide typescript pin from 5.9.3 to 6.0.3 across all package.json manifests and moves the typescript-eslint resolution to 8.62.1. The petrinaut-core LSP worker deliberately keeps bundling TypeScript 5.9.3 via a new `typescript5` npm alias (plus a matching Vite resolve.alias): the in-browser language service relies on the TypeScript JS API, which the Go-native TypeScript 7 no longer provides. Includes a changeset for @blockprotocol/graph, whose codegen module uses typescript as a runtime dependency.
- Drop `baseUrl` from the legacy base tsconfig (deprecated bare-`paths` resolution is the default now) and move workspaces from `moduleResolution: node` (node10) to `bundler`. - TypeScript 6.0 no longer auto-includes every node_modules/@types package, so declare `types` explicitly (`node` in the base config; workspaces with extra ambient globals such as `chrome` or `gapi` extend it). - Keep TS 5.x behavior for `noUncheckedSideEffectImports` (asset side-effect imports) for now. - Remove the obsolete `ignoreDeprecations: "5.0"` escape hatch and the root-level `ts-node` block, set explicit `rootDir` in build configs, and fix the stale `@local/status/type-defs/*` path mapping.
- Replace deep `dist/cjs` type imports (react-pdf, @blockprotocol/type-system) with public / esm entry points, which the stricter exports-map resolution under `moduleResolution: bundler` requires. - Derive echarts GraphNode/GraphEdge types from the public `GraphSeriesOption` instead of reaching into `echarts/types/src/*`, which the echarts exports map blocks. - Temporarily disable @typescript-eslint/no-unnecessary-type-assertion: typescript-eslint 8.62 mis-reports assertions as unnecessary under TypeScript 6.0 (removing them breaks tsc). Re-enable once typescript-eslint officially supports TS 6.0 (>= 8.63).
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #8999 +/- ##
==========================================
+ Coverage 59.20% 59.68% +0.48%
==========================================
Files 1358 1371 +13
Lines 132674 134930 +2256
Branches 6019 6067 +48
==========================================
+ Hits 78546 80534 +1988
- Misses 53198 53465 +267
- Partials 930 931 +1 Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Merging this PR will degrade performance by 15.38%
|
| Benchmark | BASE |
HEAD |
Efficiency | |
|---|---|---|---|---|
| ❌ | bit_matrix/dense/iter_row[64] |
140.8 ns | 170 ns | -17.16% |
| ❌ | bit_matrix/dense/iter_row[200] |
185.8 ns | 215 ns | -13.57% |
Tip
Investigate this regression by commenting @codspeedbot fix this regression on this PR, or directly use the CodSpeed MCP with your agent.
Comparing claude/h-6679-migrate-to-typescript-6 (73d13fb) with main (232f15c)
| "compilerOptions": { | ||
| "moduleResolution": "bundler" | ||
| } |
| DocumentCallback, | ||
| OnDocumentLoadSuccess, | ||
| } from "react-pdf/dist/cjs/shared/types"; | ||
| } from "react-pdf/dist/esm/shared/types.js"; |
There was a problem hiding this comment.
These import updates need manual checking (by adding a PDF file and checking that the preview renders)
| "jsx": "preserve", | ||
| "lib": ["dom", "dom.iterable", "esnext"], | ||
| "moduleResolution": "bundler", | ||
| "types": ["node", "gapi", "google.accounts", "google.picker"], |
There was a problem hiding this comment.
What motivated this? These types are already in dev dependencies.
| /** | ||
| * "DOM" is needed because the program pulls in `@blockprotocol/graph` sources | ||
| * (via the shared `paths` aliases), which reference DOM types such as `HTMLElement`. | ||
| */ | ||
| "lib": ["ES2024", "DOM"], |
There was a problem hiding this comment.
This makes DOM APIs available across the files, which will not work in a Node environment (or be different to their browser equivalents). This is a potential footgun for developers. Is there a more targeted fix to the issue?
| // Temporarily disabled: typescript-eslint 8.62 mis-reports assertions as | ||
| // unnecessary under TypeScript 6.0 (removing them breaks `tsc`). | ||
| // Re-enable once typescript-eslint >= 8.63 (official TS 6.0 support) is in. | ||
| "@typescript-eslint/no-unnecessary-type-assertion": "off", |
There was a problem hiding this comment.
Why not just add more disable lines? Then they will be flagged for removal when the linter is upgraded and agrees with tsc.
This just completely removes the linting entirely, for true as well as false positives.
| /** | ||
| * TypeScript 6.0 no longer contributes globals from every `node_modules/@types` | ||
| * package automatically. Most workspaces extending this config rely on Node.js | ||
| * globals (`process`, `Buffer`, `URL`, `import.meta.url`, ...), so include them | ||
| * explicitly. Workspaces needing further ambient globals (e.g. `chrome`, | ||
| * `google.maps`) add them in their own `types` array (which must repeat "node"). | ||
| */ | ||
| "types": ["node"], |
There was a problem hiding this comment.
I would not set any default here. A mix of browser and Node packages extend this. This change has blanket added Node APIs to everything. What's wrong with just adding 'node' to the ones that need it?
| }, | ||
| "outDir": "./dist" | ||
| }, | ||
| "ts-node": { | ||
| "transpileOnly": true | ||
| } |
| "qs": "6.15.2", | ||
| "react": "19.2.6", | ||
| "react-dom": "19.2.6", | ||
| "typescript-eslint@npm:^8.46.4": "8.62.1", |
There was a problem hiding this comment.
Just run yarn up -R typescript-eslint? We shouldn't need to put a valid version for the range in resolutions, we can just put it in yarn.lock directly.
Benchmark results
|
| Function | Value | Mean | Flame graphs |
|---|---|---|---|
| resolve_policies_for_actor | user: empty, selectivity: high, policies: 2002 | Flame Graph | |
| resolve_policies_for_actor | user: empty, selectivity: low, policies: 1 | Flame Graph | |
| resolve_policies_for_actor | user: empty, selectivity: medium, policies: 1002 | Flame Graph | |
| resolve_policies_for_actor | user: seeded, selectivity: high, policies: 3314 | Flame Graph | |
| resolve_policies_for_actor | user: seeded, selectivity: low, policies: 1 | Flame Graph | |
| resolve_policies_for_actor | user: seeded, selectivity: medium, policies: 1527 | Flame Graph | |
| resolve_policies_for_actor | user: system, selectivity: high, policies: 2078 | Flame Graph | |
| resolve_policies_for_actor | user: system, selectivity: low, policies: 1 | Flame Graph | |
| resolve_policies_for_actor | user: system, selectivity: medium, policies: 1033 | Flame Graph |
policy_resolution_medium
| Function | Value | Mean | Flame graphs |
|---|---|---|---|
| resolve_policies_for_actor | user: empty, selectivity: high, policies: 102 | Flame Graph | |
| resolve_policies_for_actor | user: empty, selectivity: low, policies: 1 | Flame Graph | |
| resolve_policies_for_actor | user: empty, selectivity: medium, policies: 52 | Flame Graph | |
| resolve_policies_for_actor | user: seeded, selectivity: high, policies: 269 | Flame Graph | |
| resolve_policies_for_actor | user: seeded, selectivity: low, policies: 1 | Flame Graph | |
| resolve_policies_for_actor | user: seeded, selectivity: medium, policies: 108 | Flame Graph | |
| resolve_policies_for_actor | user: system, selectivity: high, policies: 133 | Flame Graph | |
| resolve_policies_for_actor | user: system, selectivity: low, policies: 1 | Flame Graph | |
| resolve_policies_for_actor | user: system, selectivity: medium, policies: 63 | Flame Graph |
policy_resolution_none
| Function | Value | Mean | Flame graphs |
|---|---|---|---|
| resolve_policies_for_actor | user: empty, selectivity: high, policies: 2 | Flame Graph | |
| resolve_policies_for_actor | user: empty, selectivity: low, policies: 1 | Flame Graph | |
| resolve_policies_for_actor | user: empty, selectivity: medium, policies: 2 | Flame Graph | |
| resolve_policies_for_actor | user: system, selectivity: high, policies: 8 | Flame Graph | |
| resolve_policies_for_actor | user: system, selectivity: low, policies: 1 | Flame Graph | |
| resolve_policies_for_actor | user: system, selectivity: medium, policies: 3 | Flame Graph |
policy_resolution_small
| Function | Value | Mean | Flame graphs |
|---|---|---|---|
| resolve_policies_for_actor | user: empty, selectivity: high, policies: 52 | Flame Graph | |
| resolve_policies_for_actor | user: empty, selectivity: low, policies: 1 | Flame Graph | |
| resolve_policies_for_actor | user: empty, selectivity: medium, policies: 26 | Flame Graph | |
| resolve_policies_for_actor | user: seeded, selectivity: high, policies: 94 | Flame Graph | |
| resolve_policies_for_actor | user: seeded, selectivity: low, policies: 1 | Flame Graph | |
| resolve_policies_for_actor | user: seeded, selectivity: medium, policies: 27 | Flame Graph | |
| resolve_policies_for_actor | user: system, selectivity: high, policies: 66 | Flame Graph | |
| resolve_policies_for_actor | user: system, selectivity: low, policies: 1 | Flame Graph | |
| resolve_policies_for_actor | user: system, selectivity: medium, policies: 29 | Flame Graph |
read_scaling_complete
| Function | Value | Mean | Flame graphs |
|---|---|---|---|
| entity_by_id;one_depth | 1 entities | Flame Graph | |
| entity_by_id;one_depth | 10 entities | Flame Graph | |
| entity_by_id;one_depth | 25 entities | Flame Graph | |
| entity_by_id;one_depth | 5 entities | Flame Graph | |
| entity_by_id;one_depth | 50 entities | Flame Graph | |
| entity_by_id;two_depth | 1 entities | Flame Graph | |
| entity_by_id;two_depth | 10 entities | Flame Graph | |
| entity_by_id;two_depth | 25 entities | Flame Graph | |
| entity_by_id;two_depth | 5 entities | Flame Graph | |
| entity_by_id;two_depth | 50 entities | Flame Graph | |
| entity_by_id;zero_depth | 1 entities | Flame Graph | |
| entity_by_id;zero_depth | 10 entities | Flame Graph | |
| entity_by_id;zero_depth | 25 entities | Flame Graph | |
| entity_by_id;zero_depth | 5 entities | Flame Graph | |
| entity_by_id;zero_depth | 50 entities | Flame Graph |
read_scaling_linkless
| Function | Value | Mean | Flame graphs |
|---|---|---|---|
| entity_by_id | 1 entities | Flame Graph | |
| entity_by_id | 10 entities | Flame Graph | |
| entity_by_id | 100 entities | Flame Graph | |
| entity_by_id | 1000 entities | Flame Graph | |
| entity_by_id | 10000 entities | Flame Graph |
representative_read_entity
| Function | Value | Mean | Flame graphs |
|---|---|---|---|
| entity_by_id | entity type ID: https://blockprotocol.org/@alice/types/entity-type/block/v/1
|
Flame Graph | |
| entity_by_id | entity type ID: https://blockprotocol.org/@alice/types/entity-type/book/v/1
|
Flame Graph | |
| entity_by_id | entity type ID: https://blockprotocol.org/@alice/types/entity-type/building/v/1
|
Flame Graph | |
| entity_by_id | entity type ID: https://blockprotocol.org/@alice/types/entity-type/organization/v/1
|
Flame Graph | |
| entity_by_id | entity type ID: https://blockprotocol.org/@alice/types/entity-type/page/v/2
|
Flame Graph | |
| entity_by_id | entity type ID: https://blockprotocol.org/@alice/types/entity-type/person/v/1
|
Flame Graph | |
| entity_by_id | entity type ID: https://blockprotocol.org/@alice/types/entity-type/playlist/v/1
|
Flame Graph | |
| entity_by_id | entity type ID: https://blockprotocol.org/@alice/types/entity-type/song/v/1
|
Flame Graph | |
| entity_by_id | entity type ID: https://blockprotocol.org/@alice/types/entity-type/uk-address/v/1
|
Flame Graph |
representative_read_entity_type
| Function | Value | Mean | Flame graphs |
|---|---|---|---|
| get_entity_type_by_id | Account ID: bf5a9ef5-dc3b-43cf-a291-6210c0321eba
|
Flame Graph |
representative_read_multiple_entities
| Function | Value | Mean | Flame graphs |
|---|---|---|---|
| entity_by_property | traversal_paths=0 | 0 | |
| entity_by_property | traversal_paths=255 | 1,resolve_depths=inherit:1;values:255;properties:255;links:127;link_dests:126;type:true | |
| entity_by_property | traversal_paths=2 | 1,resolve_depths=inherit:0;values:0;properties:0;links:0;link_dests:0;type:false | |
| entity_by_property | traversal_paths=2 | 1,resolve_depths=inherit:0;values:0;properties:0;links:1;link_dests:0;type:true | |
| entity_by_property | traversal_paths=2 | 1,resolve_depths=inherit:0;values:0;properties:2;links:1;link_dests:0;type:true | |
| entity_by_property | traversal_paths=2 | 1,resolve_depths=inherit:0;values:2;properties:2;links:1;link_dests:0;type:true | |
| link_by_source_by_property | traversal_paths=0 | 0 | |
| link_by_source_by_property | traversal_paths=255 | 1,resolve_depths=inherit:1;values:255;properties:255;links:127;link_dests:126;type:true | |
| link_by_source_by_property | traversal_paths=2 | 1,resolve_depths=inherit:0;values:0;properties:0;links:0;link_dests:0;type:false | |
| link_by_source_by_property | traversal_paths=2 | 1,resolve_depths=inherit:0;values:0;properties:0;links:1;link_dests:0;type:true | |
| link_by_source_by_property | traversal_paths=2 | 1,resolve_depths=inherit:0;values:0;properties:2;links:1;link_dests:0;type:true | |
| link_by_source_by_property | traversal_paths=2 | 1,resolve_depths=inherit:0;values:2;properties:2;links:1;link_dests:0;type:true |
scenarios
| Function | Value | Mean | Flame graphs |
|---|---|---|---|
| full_test | query-limited | Flame Graph | |
| full_test | query-unlimited | Flame Graph | |
| linked_queries | query-limited | Flame Graph | |
| linked_queries | query-unlimited | Flame Graph |
The module handlers reference HTMLElement in their public API. Downstream Node.js workspaces type-check these sources via the shared tsconfig paths aliases, and under TypeScript 6 no longer pick up the empty HTMLElement fallback that @types/react used to contribute (TS <= 6 auto-included every node_modules/@types package; TS 6 only includes packages listed in types). Declare a minimal empty-interface fallback in the package itself instead of adding the whole DOM lib to Node workspaces. This fixes the failing Package (@local/hash-graph-sdk), Package (@local/hash-backend-utils) and Package (@apps/hash-api) CI jobs, and lets hash-integration-worker drop the DOM lib it had been given to work around the same error.
…base Review feedback: the shared legacy base tsconfig is extended by both browser and Node workspaces, so it should not blanket-add Node globals via a default types entry. Remove the base-level "types": ["node"] and declare the ambient @types packages each workspace actually needs in its own tsconfig (TypeScript 6 only contributes globals from packages listed in types). Also drop the redundant moduleResolution overrides that duplicated the base config value in apps/hash-api and apps/hash-frontend.
Review feedback on the TypeScript 6 migration: - Re-enable @typescript-eslint/no-unnecessary-type-assertion instead of turning it off wholesale. Remove the assertions it correctly flags as unnecessary under TypeScript 6 (and the imports they alone used), and keep the ~30 assertions that are false positives of typescript-eslint 8.62 (removing them breaks tsc) with per-line eslint-disable comments, so --report-unused-disable-directives flags them for cleanup once typescript-eslint >= 8.63 (official TS 6.0 support) is in. - Drop the typescript-eslint resolutions entry in favour of resolving the eslint-config-sheriff ^8.46.4 range directly in yarn.lock via yarn up -R typescript-eslint. (8.63.0 is 3 days old and still behind the npmMinimalAgeGate: 5d, so the lockfile stays on 8.62.1 for now.)
Requested by Chris Feijoo · Slack thread
🌟 What is the purpose of this PR?
Before: the monorepo type-checked with TypeScript 5.9.3, leaning on long-deprecated compiler options —
baseUrl-basedpathsresolution,moduleResolution: "node"(node10), anignoreDeprecations: "5.0"escape hatch — and on TS 5.x's implicit inclusion of everynode_modules/@typespackage.After: the whole repo type-checks, lints, and builds with TypeScript 6.0.3 (typescript-eslint 8.62.1). The legacy base tsconfig is cleaned of deprecated options, workspaces resolve modules with
bundler/nodenext, and ambient type packages are declared explicitly. This absorbs the TS 6 defaults now, so the follow-up TS 7 (Go-native) side-by-side compiler flip is a much smaller diff.🔗 Related links
typescriptto v6 #8942 (Renovate's baretypescriptv6 bump)🔍 What does this change?
Dependency bumps (
chore: bump TypeScript to 6.0.3 and typescript-eslint to 8.62.1)typescript5.9.3 → 6.0.3 in every workspace manifest;typescript-eslintresolution → 8.62.1.@blockprotocol/graph, whose codegen module depends ontypescriptat runtime.@hashintel/petrinaut-corenow depends on"typescript5": "npm:typescript@5.9.3"with a matching Viteresolve.alias(/^typescript(\/.*)?$/→typescript5$1), because the in-browser language service uses the TypeScript JS API (createLanguageService), which TS 7 no longer ships. Source files keep importing plaintypescriptso they type-check against the workspace-wide version.yarn.config.cjs'sallowedUnscriptedDevDependenciescarve-out updated accordingly, and the root workspace now pinstypescriptsonode_modules/.bin/tscdeterministically resolves to 6.0.3 rather than the alias'stscbin.tsconfig burn-down (
chore: rework tsconfigs for TypeScript 6.0)libs/@local/tsconfig/legacy-base-tsconfig-to-refactor.json: droppedbaseUrl(bare-pathsresolution retired),moduleResolution: "node"→"bundler", removed the rootts-nodeblock andoutDir, fixed the stale@local/status/type-defs/*path mapping.node_modules/@typespackage, so the base config declares"types": ["node"]explicitly; workspaces needing more ambient globals extend it (e.g. frontend addsgapi/google.accounts/google.picker, plugin-browser addschrome).noUncheckedSideEffectImports: false(bundler-handled asset imports likeimport "./styles.css").ignoreDeprecations: "5.0"from@hashintel/ds-components; added explicitrootDir: "./src"to build configs.Source fixes (
fix: adjust sources for TypeScript 6 and typescript-eslint 8.62)dist/cjstype imports (react-pdf,@blockprotocol/type-system) with public/esm entry points — stricter exports-map enforcement undermoduleResolution: "bundler"blocks the old paths (apps/hash-frontend/src/pages/shared/pdf-preview.tsxand friends).libs/@hashintel/design-system/src/e-chart.tsx: deriveGraphNode/GraphEdgefrom the publicGraphSeriesOptioninstead of reaching intoecharts/types/src/*.@typescript-eslint/no-unnecessary-type-assertionin@local/eslint: 8.62 mis-reports assertions as unnecessary under TS 6.0 (removing them breakstsc). To be re-enabled once typescript-eslint officially supports TS 6.0.What surfaced
@local/effect-dns-hickorylint failed in the migration container only because thenapiCLI (provided in CI via mise,npm:@napi-rs/cli) isn't available there, so the napi-generateddist/impl.d.tswas missing and the package import resolved to an error type. With the build artifact present,lint:tscandlint:eslintpass unchanged under TS 6.0.3 — no code change needed.@apps/plugin-browser:buildrequiresSENTRY_DSN/SENTRY_AUTH_TOKENfor production builds (hard error inwebpack.config.js); these are CI-provided secrets (Vault, seedeploy.yml) absent in the migration container.tsc --noEmitfor plugin-browser passes under 6.0.3.Pre-Merge Checklist 🚀
🚢 Has this modified a publishable library?
This PR:
@blockprotocol/graph(runtimetypescriptdependency bump — changeset added);@hashintel/petrinaut-coreis touched but thetypescript5alias is a bundled devDependency, not a published-consumer-facing change.📜 Does this require a change to the docs?
The changes in this PR:
🕸️ Does this require a change to the Turbo Graph?
The changes in this PR:
@typescript-eslint/no-unnecessary-type-assertionis temporarily off repo-wide (false positives against TS 6.0); re-enable when typescript-eslint ≥ 8.63 lands.🐾 Next steps
no-unnecessary-type-assertiononce typescript-eslint officially supports TS 6.🛡 What tests cover this?
turborun (build / lint:tsc / lint:eslint / unit tests) across all workspaces under TS 6.0.3 — green except the two environment-only items called out above.@hashintel/petrinaut-core: 665 unit tests pass;vite buildoutput verified to bundle TypeScript 5.9 (versionMajorMinor = "5.9") into the LSP worker;tsgo --noEmitclean.yarn constraintspasses.❓ How to test this?
yarn install.yarn tsc --version→ 6.0.3; runturbo run lint:tsc lint:eslint— repo-wide green.yarn workspace @hashintel/petrinaut-core test:unit --run && yarn workspace @hashintel/petrinaut-core build— LSP worker still bundles TS 5.9.3.Generated by Claude Code