feat: Migrate to TypeScript 6.0.3 (prepare for 7) - #11767
Conversation
`tsc --build` only knew the dependency order from the order of the 43 entries in the root `tsconfig.json`, because almost no package declared its own `references`. A sequential builder tolerates that; anything that builds projects in parallel does not, and it was already leaking into TS 6 as the cold-build-only failure that the "keep cubejs-testing last" workaround papered over. References are now generated from each package's workspace dependencies (142 edges, no cycles), so the root list is back to its original order and the workaround is gone. `rust/cubestore` and `cubejs-client-ws-transport` gain `composite: true` — both are referenced but were never valid reference targets (TS6306). Three inferred types in schema-compiler's `.js` were wrong rather than merely loose, and the generated declarations spread them across every consumer: | Member | Emitted before | Now | | --- | --- | --- | | `BaseQuery.resolveFullMemberPathFn` | `(arg0: member) => string`, where `member` names nothing | `(member: string) => string` | | `BaseQuery.collectLeafMeasures` | `unknown[]` | `string[]` | | `TestUtil.debugLog` | `(...args: any[]) => void` inferred from `arguments` | declared rest parameter | Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Adding `composite` to cubejs-client-ws-transport carried `declarationDir`'s trailing comment along with it, where it describes something else entirely. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Answers a review question. Redirecting `antlr4` through `paths` and keeping nodenext was measured: 280 errors, because the failing extensionless specifiers are inside antlr4's own `.d.ts` files, whose ESM format comes from antlr4's `type: module` and cannot be changed from the consumer side. The comment now also states the cost of `bundler` — type-checking against the `import` condition while emitting require() — and the one dependency it currently affects. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
`docker build` failed with TS6053 for three of the references added in 94276df. The images copy a subset of the workspace: `testing-drivers.Dockerfile` leaves out the frontend packages and both it and `dev.Dockerfile` leave out `cubejs-testing`, so a reference to one of those resolves to a directory with no tsconfig.json. | Dropped reference | Why it is absent | | --- | --- | | server-core -> templates | not copied by testing-drivers.Dockerfile; `@cubejs-backend/templates` comes from the registry there | | dbt-schema-extension -> testing | `cubejs-testing` is in no image, and it is a devDependency | | materialize-driver -> testing | same | Verified by hiding both packages' tsconfig.json and compiling the three packages, which is the state the image build sees. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`getDriver` threw `driverModule.default is not a constructor` in every drivers-tests job.
Replacing the dynamic import with `require` dropped the `esModuleInterop` wrapper that used to
put the module on `.default`, and the driver packages come in two shapes:
| Shape | Packages | Constructor |
| --- | --- | --- |
| `module.exports = DriverClass` from the `index.js` shim | athena, bigquery, postgres, trino, clickhouse, mssql, mysql, snowflake, redshift, oracle, … | the module itself |
| TypeScript `export default` | databricks-jdbc (`main: dist/src/index.js`) | `.default` |
Now branches on `__esModule`, which is what `__importStar` did. Verified by running the
compiled `getDriver('postgres')`, and the same interop in the `cube.js` loader was re-checked
against both a `module.exports = {...}` and an `export default {...}` config.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Reverts the require() form. Two things were in the way and both are now handled: | Problem | Fix | | --- | --- | | nodenext demands a `.js` suffix on relative specifiers, which jest's resolver does not map back to the `.ts` source | `moduleNameMapper` entry in the shared jest config, inherited by all 31 package configs | | the compiled `dist` is loaded untransformed by other packages' jest runs, where a native `import()` needs `--experimental-vm-modules` | set on `test-driver`, through which all 59 driver suites run | Measured against the real compiled BaseDriver from another package's jest run: without the flag `A dynamic import callback was invoked without --experimental-vm-modules`, with it the import resolves and the call reaches the GCS SDK. Unit sweep across all 28 packages matches the baseline. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The Cypress step of the dev-image job failed before running a single test: ``` Your configFile is invalid: packages/cubejs-testing/cypress.config.ts error TS5107: Option 'moduleResolution=node10' is deprecated and will stop functioning in TypeScript 7.0. ``` Cypress 14 compiles the config with its bundled ts-node while hard-coding `moduleResolution: 'node'`, and injects `downlevelIteration` into ts-loader for the specs — then hands both to whichever TypeScript the project resolves, now 6.0.3. Cypress 15.14.0 added official TypeScript 6 support: the config loader no longer forces node10 and the bundled `webpack-batteries-included-preprocessor` stopped injecting `downlevelIteration`. Bumping to 15.21.1 removes both, so nothing has to be silenced with `ignoreDeprecations` and every Cypress file stays TypeScript. That left one real error, ours: `cypress/tsconfig.json` still carried the `baseUrl` this branch removed everywhere else — my earlier sweep globbed `packages/*/tsconfig*.json` and missed a directory level. It was dead anyway: no tsconfig on the Cypress chain defines `paths`, and every spec and support import is relative or a bare package name. Verified locally against a stub server on :3080 — config loads, `cypress/support/*.ts` and the specs compile with no TS diagnostics; the only failures are the assertions that need a real Cube app. The plugin packages' `cypress` peer ranges were already stale at 14 and warn the same way. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Addresses a review comment. `cypress/tsconfig.json` inherited `include: ["src", "test"]` from the package config, but that directory holds `e2e`, `support`, `plugins`, `fixtures`, `queries.ts` and `global.d.ts` — so the project had zero input files and `yarn tsc` never checked a single Cypress file. That is why the deprecated `baseUrl` sitting in it could only surface as a red dev-image job after a full Docker build and birdbox startup. The project now points at the directories that exist and extends `tsconfig.base.json` directly rather than the package config, so it does not inherit `nodenext` — these files are bundled by Cypress's webpack preprocessor for a browser, hence `module: esnext` + `moduleResolution: bundler` and a DOM lib. It is registered in the root reference list, and root `clean` now also removes its build output. Three type errors were hiding behind the dead `include`: | Error | Cause | | --- | --- | | TS2322 ×2 in `support/commands.ts` | `getByTestId`/`getByQa` were declared as `Chainable<Element>`, but `cy.get()` returns `Chainable<JQuery<HTMLElement>>` | | TS7016 in `support/e2e.ts` | `cypress-image-snapshot` ships no types; declared as an ambient module rather than adding `@types/cypress-image-snapshot`, which types v3 while we run v4 and would collide with the hand-written `matchImageSnapshot` augmentation | Verified: cold `yarn tsc` and `yarn lint` green, and a Cypress run against a stub server still loads the config and compiles the support files and specs. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Addresses a review comment. The `require()` I had switched to is what `module: commonjs`
downlevelled `import()` into on master (`Promise.resolve(...).then(s => __importStar(require(s)))`
in the 5.2.2 output), so it was not a regression — but it does keep the loader from ever
supporting an ESM config. `pathToFileURL` solves the actual problem, which was handing the ESM
loader a bare absolute path (fatal on Windows), so the dynamic import stays.
The namespace shape differs from `__importStar`'s, so the unwrap is now two steps: a CommonJS
`cube.js` arrives with `module.exports` on `default`, which puts a transpiled `exports.default`
one level deeper.
Verified against every config shape by calling the compiled loader directly:
| `cube.js` | Result |
| --- | --- |
| `module.exports = {...}` | loaded |
| `export default {...}` in a `"type": "module"` project | loaded |
| `export default await ...` (top-level await) | loaded — this threw `ERR_REQUIRE_ASYNC_MODULE` on master |
| transpiled `exports.__esModule` + `exports.default` | loaded |
| ESM with named exports only | `Configuration file must export the configuration as default.`, as before |
No jest suite reaches this path (birdbox starts Cube as its own process), so the native import
needs no VM flag.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`types: ["*"]` restored the pre-TS-6 behaviour, but it also means any `@types/*` package that lands in the hoisted root `node_modules` injects its globals into all 43 projects — a package can compile against globals it never declared a dependency on. Each project now lists what it actually needs: `["node", "jest"]` for the backend packages that compile their tests in-project, `["vitest/globals"]` for client-core, and `[]` for the frontend packages, which get no `node`. `tsconfig.jest.json` needs the `jest` entry of its own — ts-jest compiles through that file rather than the per-package tsconfig, so `tsc --build` staying green says nothing about whether the suites can run. mssql-driver pinned `typeRoots` to a local `node_modules/@types` that does not exist, which made `node` unresolvable (TS2688). Its `src/types/mssql.d.ts` is a `declare module 'mssql'` augmentation already picked up through `include: ["src"]`, so the typeRoots entry was load-bearing for nothing. The 23 package.json changes declare the `@types/*` packages those projects were previously getting for free from root hoisting. Both ranges were already resolved in yarn.lock, so the lockfile is unchanged. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`cypress-image-snapshot` is not updatable — 4.0.1 is latest, published in 2021, with no `types` field, and DefinitelyTyped only ever covered v3. So the bodyless `declare module` was the only way to keep the import compiling, at the cost of typing the whole module as `any`. `@simonsmith/cypress-image-snapshot` is the maintained fork and ships first-party types, including the `Cypress.Chainable.matchImageSnapshot` augmentation this file used to hand-write. Its `exports` map has `./command` and `./plugin` subpaths, which resolve under the project's `bundler` resolution, and it peers `cypress >=15.10.0` against our 15.21.1. `addMatchImageSnapshotPlugin` no longer takes `config`. The `addMatchImageSnapshotCommand` call in support/e2e.ts is checked again — verified by breaking it both ways: | edit | before | after | | ----------------------- | ----------- | ----------------------------------------- | | `captures: 'viewport'` | compiles | TS2561, suggests `capture` | | `capture: 'viewPort'` | compiles | TS2820, suggests `'viewport'` | No committed image baselines exist (CI uploads `cypress/screenshots` as an artifact rather than diffing against a checked-in snapshot), so the fork's different snapshot directory layout has nothing to regress. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two projects were still reaching ambient globals transitively, through a `/// <reference types="..." />` inside a dependency's `.d.ts` — `types` does not gate reference directives, so an unrelated `@types/*` bump can take the globals away and break the build with a confusing error. | package | before | after | | --- | --- | --- | | `@cubejs-client/ws-transport` | `[]` | `["node"]` | | `@cubejs-backend/testing` | `["cypress", "@4tw/cypress-drag-drop"]` | `["node", "jest"]` | `ws-transport` uses `console`, `setInterval`, `setTimeout` and `clearInterval` with `lib: ["es2017"]` and no `dom`, so it needs `@types/node`, now an explicit devDependency (already resolved by the existing `@types/node@^22` lockfile entry). `testing` no longer contains any Cypress file — 4774f69 moved those to `cypress/tsconfig.json`, which declares the Cypress types itself — so that override only stripped `node` and `jest` from the ~40 jest suites in `test/`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Follow-up on the review of #11767. Five configs stated something other than what the code does: - `cubejs-materialize-driver` had no reference for `@cubejs-backend/testing-shared`, which `test/MaterializeDriver.test.ts` imports and `include` covers; it built only because the root reference list happens to put `cubejs-testing-shared` before it. Its `@cubejs-backend/testing` devDependency is imported nowhere, so it is replaced by the one that is. - `cubejs-server-core` dropped `../cubejs-templates`, but `src/core/DevServer.ts` imports it and it is a real `dependencies` entry. The reference is back and `testing-drivers.Dockerfile` now copies the package — it is a backend dependency of server-core, so it does not belong under the "we dont need client libraries" banner. - `cubejs-playground` turned off `noUncheckedSideEffectImports` for the whole package to cover exactly two CSS imports (`App.tsx:2`, `GraphiQLSandbox.tsx:6`). A `declare module '*.css'` next to the existing `svg.d.ts` covers them with the check left on. - `cypress/tsconfig.json` listed `e2e` in `include`, where every file is JavaScript and `allowJs` is off — the entry matched nothing. - `BaseQuery.js` stacked two JSDoc blocks on `resolveFullMemberPathFn`; TypeScript reads the nearest one, so the description was dropped from the emitted `.d.ts`. Merged into one block. Verified with a cold `yarn tsc` (all tsbuildinfo removed), a forced build of the cypress project, `tsc -p packages/cubejs-playground`, `yarn lint` in materialize-driver and `yarn lint:npm`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`module: nodenext` keeps `await import()` in the CommonJS emit instead of downleveling it to `require`, so BaseDriver's lazy storage-fs loads throw `A dynamic import callback was invoked without --experimental-vm-modules` under Jest's CJS runtime. Enable the flag for the jest suites that reach those loads. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
af36f03 to
389b650
Compare
|
Claude finished @ovr's task in 2m 17s —— View job Re-review of the rebase onto
|
| Thread | Status |
|---|---|
databricks-jdbc unit-tests — the one suite that reaches the lazy import() didn't get the flag |
Untouched (sharpest of the three) |
testing-drivers/getDriver.ts:13 — third divergent hand-rolled interop |
Untouched |
MemberLabelText.tsx:6 — ComponentType<Props> erases prop typing |
Untouched |
Nothing became stale, so nothing to resolve, and no duplicate inline comments posted. The force-push marked several already-resolved threads outdated; I left those alone.
Not checked
No node_modules in this job, so no yarn tsc / yarn build / yarn lint. Because the previous head's objects aren't fetchable here, "no new authored content" rests on the commit titles plus author dates rather than a tree-level diff against af36f03 — if any of those commits was amended rather than merely replayed, I would not have seen it. The rebase-quality checks above are the part I verified directly against the working tree. CI on the new base is the authority on the build claim.
· · branch migrate-typescript-6-0-3 ·
Its unload tests reach BaseDriver's lazy `import('./storage-fs/azure.fs.js')`,
which nodenext keeps as a real dynamic import in the CommonJS emit.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Claude finished @ovr's task in 53s —— View job Re-review of
|
| Thread | Status |
|---|---|
databricks-jdbc unit-tests — the one suite that reaches the lazy import() lacked the flag |
Resolved — this commit |
testing-drivers/getDriver.ts:13 — third divergent hand-rolled interop |
Untouched |
MemberLabelText.tsx:6 — ComponentType<Props> erases prop typing |
Untouched |
No duplicate inline comments posted, and no new ones needed.
Not checked
No node_modules in this job, so no yarn tsc and no jest run — the commit message's claim that the databricks suite now passes is taken at face value, and the sweep above is from reading the tree. CI is the authority on the build.
· · branch migrate-typescript-6-0-3 ·
`@cubejs-backend/linter` was an eslintrc shareable config extending `airbnb-base`, consumed by 45 packages plus `rust/cubestore` through a per-package `eslintConfig` block. CI ran `lerna run --concurrency 1 lint`, i.e. 49 sequential ESLint 8 processes. That toolchain is also at the end of the road: ESLint 8.57.1 is out of support, and #11767 already had to shuffle four formatting rules into `@stylistic/eslint-plugin-ts` because typescript-eslint 8 dropped them. The package now ships an oxlint config instead. `airbnb-base.json` is the mechanical port of the old rule set, generated from `eslint --print-config` so it is verifiable rather than hand-transcribed; `.oxlintrc.json` sits on top of it and holds the deliberate departures, each with its reason. The repo-root `.oxlintrc.json` extends that and owns the ignore list, so linting is a single root operation and the 49 per-package `lint` scripts and `eslintConfig` blocks are gone. oxlint implements no formatting rules, so `@stylistic/eslint-plugin` is loaded through `jsPlugins` to keep the 48 whitespace rules airbnb-base relies on. | | before (ESLint) | after (oxlint) | | --- | --- | --- | | invocations | 49 sequential processes | 1 | | wall clock, whole repo | minutes | 2.7s (`yarn lint` 4.3s incl. `lint:npm`) | | files linted | 739 | 873 | | errors | 0 | 0 | | warnings | 178 | 149 | Rule coverage of the 192 rules the old config resolved to: 125 map onto oxlint natively with identical options, 48 move to `@stylistic/*`, 8 are renamed or re-homed (`no-new-object` to `no-object-constructor`, `global-require` to `node/global-require`, `no-buffer-constructor` to `unicorn/no-new-buffer`, and the `@typescript-eslint` twins of `no-unused-vars` / `no-shadow` / `semi` collapse onto the TS-aware core rules; the four formatting rules #11767 moved to `@stylistic/ts/*` land on the same `@stylistic/*` targets, so that change is absorbed). 14 have no equivalent and are listed at the top of `airbnb-base.json`; the ones worth chasing later are `camelcase`, `import/order`, `import/no-extraneous-dependencies`, and `consistent-return` / `dot-notation` / `no-return-await`, which exist only as type-aware `typescript/*` rules. Parity was checked by diffing per-file diagnostics against an ESLint run on the pre-migration tree. Of the 50 previously-linted files involved, 46 match exactly; the 4 that differ are all oxlint being more lenient about destructuring placeholders and rest siblings. The 40 `quotes` warnings in the baseline are gone because `--fix` resolved them. Three things needed configuring rather than porting, all commented in `packages/cubejs-linter/.oxlintrc.json`: eslint-plugin-import had no TypeScript resolver, so `import/no-cycle`, `import/export` and the two `no-named-as-default` rules never actually ran (they now report 173 dependency cycles and 2 real duplicate exports -- left off, to be fixed separately); oxlint honours neither `/* globals ... */` nor `/* eslint-env jest */`, so the jest globals come from an `overrides` entry; and `@stylistic/quotes` gets `allowTemplateLiterals` because `--fix` otherwise rewrites the driver parameter-escaping tests into backslash soup. The source changes are `oxlint --fix` output. They are all formatting, and they exist because `@stylistic` understands TypeScript syntax where ESLint's core rules did not -- `indent` in generic argument lists and type annotations, `object-curly-spacing` and `quote-props` in type literals, `space-before-blocks` on interface bodies. Four files carry a hand-written `eslint-disable` for intentional code: bit twiddling in the zip helper test, a never-resolving promise in the native test server, and a lazy `require` in the cypress config. Also drops the stale `.eslintrc.js` path filters from the workflows (that file has not existed for some time) and fixes the linter package's `repository.directory`, which pointed at `packages/cubejs-mssql-driver`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`@cubejs-backend/linter` was an eslintrc shareable config extending `airbnb-base`, consumed by 45 packages plus `rust/cubestore` through a per-package `eslintConfig` block. CI ran `lerna run --concurrency 1 lint`, i.e. 49 sequential ESLint 8 processes. That toolchain is also at the end of the road: ESLint 8.57.1 is out of support, and #11767 already had to shuffle four formatting rules into `@stylistic/eslint-plugin-ts` because typescript-eslint 8 dropped them. The package now ships an oxlint config instead. `airbnb-base.json` is the mechanical port of the old rule set, generated from `eslint --print-config` so it is verifiable rather than hand-transcribed; `.oxlintrc.json` sits on top of it and holds the deliberate departures, each with its reason. The repo-root `.oxlintrc.json` extends that and owns the ignore list, so linting is a single root operation and the 49 per-package `lint` scripts and `eslintConfig` blocks are gone. oxlint implements no formatting rules, so `@stylistic/eslint-plugin` is loaded through `jsPlugins` to keep the 48 whitespace rules airbnb-base relies on. | | before (ESLint) | after (oxlint) | | --- | --- | --- | | invocations | 49 sequential processes | 1 | | wall clock, whole repo | minutes | 2.7s (`yarn lint` 4.3s incl. `lint:npm`) | | files linted | 739 | 873 | | errors | 0 | 0 | | warnings | 178 | 149 | Rule coverage of the 192 rules the old config resolved to: 125 map onto oxlint natively with identical options, 48 move to `@stylistic/*`, 8 are renamed or re-homed (`no-new-object` to `no-object-constructor`, `global-require` to `node/global-require`, `no-buffer-constructor` to `unicorn/no-new-buffer`, and the `@typescript-eslint` twins of `no-unused-vars` / `no-shadow` / `semi` collapse onto the TS-aware core rules; the four formatting rules #11767 moved to `@stylistic/ts/*` land on the same `@stylistic/*` targets, so that change is absorbed). 14 have no equivalent and are listed at the top of `airbnb-base.json`; the ones worth chasing later are `camelcase`, `import/order`, `import/no-extraneous-dependencies`, and `consistent-return` / `dot-notation` / `no-return-await`, which exist only as type-aware `typescript/*` rules. Parity was checked by diffing per-file diagnostics against an ESLint run on the pre-migration tree. Of the 50 previously-linted files involved, 46 match exactly; the 4 that differ are all oxlint being more lenient about destructuring placeholders and rest siblings. The 40 `quotes` warnings in the baseline are gone because `--fix` resolved them. Three things needed configuring rather than porting, all commented in `packages/cubejs-linter/.oxlintrc.json`: eslint-plugin-import had no TypeScript resolver, so `import/no-cycle`, `import/export` and the two `no-named-as-default` rules never actually ran (they now report 173 dependency cycles and 2 real duplicate exports -- left off, to be fixed separately); oxlint honours neither `/* globals ... */` nor `/* eslint-env jest */`, so the jest globals come from an `overrides` entry; and `@stylistic/quotes` gets `allowTemplateLiterals` because `--fix` otherwise rewrites the driver parameter-escaping tests into backslash soup. The source changes are `oxlint --fix` output. They are all formatting, and they exist because `@stylistic` understands TypeScript syntax where ESLint's core rules did not -- `indent` in generic argument lists and type annotations, `object-curly-spacing` and `quote-props` in type literals, `space-before-blocks` on interface bodies. Four files carry a hand-written `eslint-disable` for intentional code: bit twiddling in the zip helper test, a never-resolving promise in the native test server, and a lazy `require` in the cypress config. Also drops the stale `.eslintrc.js` path filters from the workflows (that file has not existed for some time) and fixes the linter package's `repository.directory`, which pointed at `packages/cubejs-mssql-driver`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Check List
Description of Changes Made
Bumps
typescriptfrom~5.2.2to~6.0.3in all 43 packages that pin it (cubejs-client-ngxstays on~5.4.5, pinned by Angular 18) and clears the tsconfig options TypeScript 6.0 turns into hard errors:moduleResolution: nodeandbaseUrlare gone in favour ofmodule/moduleResolution: nodenext,types: ["*"]restores the previous@types/*auto-include now that the default is[], androotDiris stated explicitly where TS 6 requires it (TS5011). Two packages need a different mode —cubejs-schema-compilerusesmoduleResolution: bundlerwithmodule: commonjsbecause antlr4's.d.ctsre-exports.d.tsfiles that are ESM under its owntype: module(TS2834, soantlr4resolves to nothing), and the bundledclient-react/playgroundusebundlertoo. Becausenodenextemitsimport()verbatim instead of downlevelling it, the two live dynamic-import sites (container.tsloading the user'scube.js,getDriver.ts) are back onrequire— jest cannot execute a native dynamic import in its CJS VM, an absolute-path ESM import is fatal on Windows, and a native import would put the whole CommonJS module on.default. The remaining source changes are a handful of root causes rather than a long tail (getEnv'sParameters<Vars[T]>alone accounted for 1592 of the 2074 initial errors),@typescript-eslintmoves to^8witheslint^8.57.1and@stylistic/eslint-plugin-tsfor the four rules v8 dropped, andcubejs-testingmoves to the end of the root reference list because TS 6 keeps a failed module resolution in the cache shared acrosstsc --build, which broke materialize/crate/redshift on cold builds only.Verified with a clean
yarn tsc,yarn build,yarn lint,packages/cubejs-playgroundyarn build:liband per-packageyarn unit; the only failures are pre-existing (schema-compilererror-reporter,raw-time-dimension-timezone,client-vue3) or environmental locally (backend-nativeport conflict, sqlite3 native binding).🤖 Generated with Claude Code