From 4565a627e6b9fc1d5818c5c84cbe29b456eac33f Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 13 Aug 2026 06:42:10 +0000 Subject: [PATCH] chore(build): fields and plugin-editor stop publishing test declarations (#4006) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both packages' build tsconfigs set `include: ["src"]` with no test exclude, so every test file entered the declaration program and its `.d.ts` was written into `dist/`. Both are published, so those declarations shipped: 85 from `@object-ui/fields` and one (`dist/index.test.d.ts`) from `@object-ui/plugin-editor`. Measured on origin/main @ f56541826 — 86, not the 73 the card recorded on 2026-08-09; the tests grew, the defect did not change shape. Adding the exclude alone would have been a regression, and the card said so. These two packages counted as "tests type-checked" in scripts/check-type-check-coverage.mjs precisely BECAUSE the build program read the tests — a correct verdict reached through an emit nobody wanted. Measured: the exclude on its own fails the gate's section 5c, naming all 86 files. So the coverage moves in the same commit — a `tsconfig.test.json` per package, chained from each `type-check` script, on the plugin-dashboard (#4530) template. The gate reports 41/41 packages compiling their tests with 0 declared debt on BOTH sides. Discrimination proof, direction-checked, per package: with a type error planted in one test file, the pre-change build config reds (TS2322), the post-change build config passes it SILENTLY, and only the new chained test project reds on the exact line. Coverage moved rather than vanished. The published artifact is otherwise untouched. Built each package both ways from a cleared `dist/`: fields 163 files to 78, plugin-editor 6 to 5, all 86 disappearances are `*.test.d.ts`, nothing appears, and every one of the 83 surviving files is byte-identical by sha256 — including both entry `dist/index.d.ts`. 19 downstream consumers type-check clean. `packages/fields`' `rootDir: ".."` is left alone deliberately, with the reason recorded in the file: it is load-bearing. The config inherits the root tsconfig's `paths`, so sibling `packages/core/src` and `packages/types/src` are real program inputs; narrowing it turns 122 of them into TS6059, even under `--noEmit`. Fixes #4006 --- ...ugin-editor-dist-test-declarations-4006.md | 12 ++++ packages/fields/package.json | 2 +- packages/fields/tsconfig.json | 18 +++++- packages/fields/tsconfig.test.json | 62 +++++++++++++++++++ packages/plugin-editor/package.json | 2 +- packages/plugin-editor/tsconfig.json | 11 +++- packages/plugin-editor/tsconfig.test.json | 45 ++++++++++++++ 7 files changed, 148 insertions(+), 4 deletions(-) create mode 100644 .changeset/fields-plugin-editor-dist-test-declarations-4006.md create mode 100644 packages/fields/tsconfig.test.json create mode 100644 packages/plugin-editor/tsconfig.test.json diff --git a/.changeset/fields-plugin-editor-dist-test-declarations-4006.md b/.changeset/fields-plugin-editor-dist-test-declarations-4006.md new file mode 100644 index 0000000000..8cea0f1131 --- /dev/null +++ b/.changeset/fields-plugin-editor-dist-test-declarations-4006.md @@ -0,0 +1,12 @@ +--- +'@object-ui/fields': patch +'@object-ui/plugin-editor': patch +--- + +`@object-ui/fields` and `@object-ui/plugin-editor` stop publishing their test declarations + +Both packages' build tsconfigs set `include: ["src"]` with no test exclude, so every test file entered the declaration program and its `.d.ts` was written into `dist/`. Both are published (`private` is false, `files` contains `dist`), so those declarations shipped: 85 from `@object-ui/fields` and one from `@object-ui/plugin-editor`. Adding the test exclude the other twenty-odd packages already use removes them. + +Nothing else about either artifact moves. Measured by building each package both ways from a cleared `dist/`, then diffing the file lists: `@object-ui/fields` goes from 163 files to 78 and `@object-ui/plugin-editor` from 6 to 5, every one of the 86 disappearances is a `*.test.d.ts`, no file appears, and all 83 surviving files are byte-identical by sha256 — including each package's entry `dist/index.d.ts`. The entry type surface is therefore unchanged and no import can break; this is the tarball shedding files nothing resolved. + +The type coverage those files were a side effect of did not go with them. Because the build program read the tests, these two packages counted as "tests type-checked" in `scripts/check-type-check-coverage.mjs` — a correct verdict reached through an emit nobody wanted. Excluding the tests alone would have silently dropped 86 test files out of every `tsc` program, so the same change adds a `tsconfig.test.json` per package, chained from each package's `type-check` script, and the coverage gate stays at 41 of 41 packages compiling their tests with zero declared debt on both sides of the change. diff --git a/packages/fields/package.json b/packages/fields/package.json index cdf74e08cd..4d99a56ede 100644 --- a/packages/fields/package.json +++ b/packages/fields/package.json @@ -26,7 +26,7 @@ "scripts": { "build": "tsc && vite build && node scripts/build-css.mjs", "clean": "rm -rf dist", - "type-check": "tsc --noEmit", + "type-check": "tsc --noEmit && tsc -p tsconfig.test.json", "test": "vitest run", "lint": "eslint ." }, diff --git a/packages/fields/tsconfig.json b/packages/fields/tsconfig.json index 6a44fb0fd5..8e8c26971a 100644 --- a/packages/fields/tsconfig.json +++ b/packages/fields/tsconfig.json @@ -2,8 +2,24 @@ "extends": "../../tsconfig.json", "compilerOptions": { "outDir": "dist", + // `..` (i.e. `packages/`), NOT `src` — load-bearing, measured in + // objectui#4006. This config inherits the root tsconfig's `paths`, which map + // `@object-ui/core` / `@object-ui/types` to their sibling `packages/*/src` + // trees, so those sources are real program inputs. Narrowing `rootDir` to + // `src` (or dropping it, which defaults it to the common source directory) + // turns all 122 of them into TS6059 "not under rootDir" — and it does so + // even under `--noEmit`, because TS6059 is a program-level verdict. It does + // not affect what is emitted: this config inherits the root's + // `noEmit: true`, so `tsc` here only CHECKS; `dist` is written by + // vite-plugin-dts, which overrides `rootDir` to `src` and clears `paths`. "rootDir": "..", "jsx": "react-jsx" }, - "include": ["src"] + "include": ["src"], + // Tests are excluded from the BUILD program so they stop being emitted into + // the published `dist` (objectui#4006 — 85 `*.test.d.ts` shipped from here). + // Their type coverage did not go away with them: it moved to the + // `tsconfig.test.json` chained off this package's `type-check` script, which + // is what scripts/check-type-check-coverage.mjs verifies. + "exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.test.tsx"] } diff --git a/packages/fields/tsconfig.test.json b/packages/fields/tsconfig.test.json new file mode 100644 index 0000000000..383c6c6bf4 --- /dev/null +++ b/packages/fields/tsconfig.test.json @@ -0,0 +1,62 @@ +{ + // Type-checks this package's TESTS, which `tsconfig.json` now excludes. + // See `packages/plugin-dashboard/tsconfig.test.json` for the template and + // `packages/types/tsconfig.test.json` for why the exclusion would otherwise + // be a hole: a test nothing compiles can assert a contract the compiler never + // checked and then read as evidence that the contract holds (objectui#3009). + // + // The hole is NEW here, and that is the whole point of objectui#4006. Until + // this file existed these 85 tests were checked — but only as a SIDE EFFECT + // of the build config having no test exclude at all, which is also why 85 + // `*.test.d.ts` were emitted into the published `dist/`. Removing the emit + // therefore had to move the coverage in the same commit, not after it: + // scripts/check-type-check-coverage.mjs fails 5c the moment the exclude + // lands alone (measured — it names all 85 files). + // + // Chained from this package's `type-check` script, which is what the CI + // `Type Check` job runs; the coverage gate enforces the chaining — a config + // nothing runs is the objectui#3009 failure itself. + "extends": "../../tsconfig.json", + "compilerOptions": { + // A checking project, never an emitting one. The build config inherits the + // root's `noEmit: true` as well (this package emits `dist` from + // vite-plugin-dts, not from `tsc`), but stating it here is what the gate + // requires and what keeps this project from ever growing an output. + "noEmit": true, + "composite": false, + "declaration": false, + "jsx": "react-jsx", + // Deliberately NOT raised to ES2022. Measured: no test file in this package + // reaches for `Array.prototype.at` or any other ES2021+ builtin, so the + // tests are held to the same lib the shipped SOURCE targets. + "lib": ["ES2020", "DOM", "DOM.Iterable"], + // + // `types` is deliberately NOT named, unlike plugin-dashboard's. Naming it + // switches off automatic `@types/*` inclusion, and this package's build + // program does not name it either — so leaving it unset is what reproduces + // the exact program these tests were checked by until now, which is the + // property objectui#4006 had to preserve while moving the coverage. + // Measured: no test file here uses `global`, and the seven suites that use + // `@testing-library/jest-dom` matchers `import '@testing-library/jest-dom'` + // explicitly, so its global augmentation reaches the whole program through + // that import (plugin-list's lesson). + // + // `paths` drops the root tsconfig's source-tree mappings so `@object-ui/*` + // and `@objectstack/spec/*` resolve through each workspace dependency's + // built `.d.ts` rather than pulling sibling package sources in as program + // inputs. `type-check` dependsOn `^build` (turbo.json), so those `.d.ts` + // exist by the time this runs. This is the one place the project is + // deliberately STRICTER than the program it replaces: the build config + // keeps the root `paths` (and the `rootDir: ".."` that lets sibling sources + // sit inside the root), so until now these tests were checked against + // sibling SOURCE. Checking them against the built declarations is what the + // published contract actually is, and it is what every other chained test + // project in this repo does. Measured: zero new errors either way. + "paths": {} + }, + // Only the test files. Note there is no ambient `*.d.ts` under `src/` in this + // package today — if one is ever added it must be NAMED here, because being + // imported is not enough to make an ambient declaration a program input, and + // nothing imports one (plugin-map's lesson, objectui#4270). + "include": ["src/**/*.test.ts", "src/**/*.test.tsx"] +} diff --git a/packages/plugin-editor/package.json b/packages/plugin-editor/package.json index d986f21aa1..2b5cfa90ec 100644 --- a/packages/plugin-editor/package.json +++ b/packages/plugin-editor/package.json @@ -27,7 +27,7 @@ "build": "vite build", "test": "vitest run", "test:watch": "vitest", - "type-check": "tsc --noEmit", + "type-check": "tsc --noEmit && tsc -p tsconfig.test.json", "lint": "eslint ." }, "dependencies": { diff --git a/packages/plugin-editor/tsconfig.json b/packages/plugin-editor/tsconfig.json index 9ffe21aef4..2b3802a5b0 100644 --- a/packages/plugin-editor/tsconfig.json +++ b/packages/plugin-editor/tsconfig.json @@ -13,5 +13,14 @@ "composite": true, "skipLibCheck": true }, - "include": ["src"] + "include": ["src"], + // Tests are excluded from the BUILD program so they stop being emitted into + // the published `dist` (objectui#4006 — `dist/index.test.d.ts` shipped from + // here). The emitter is vite-plugin-dts, whose own `include: ['src']` in + // `vite.config.ts` does NOT displace this `exclude`: measured, the exclude + // alone removes `dist/index.test.d.ts`, so no dts-plugin `exclude` is needed. + // Their type coverage did not go away with them: it moved to the + // `tsconfig.test.json` chained off this package's `type-check` script, which + // is what scripts/check-type-check-coverage.mjs verifies. + "exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.test.tsx"] } diff --git a/packages/plugin-editor/tsconfig.test.json b/packages/plugin-editor/tsconfig.test.json new file mode 100644 index 0000000000..b9e41af3ad --- /dev/null +++ b/packages/plugin-editor/tsconfig.test.json @@ -0,0 +1,45 @@ +{ + // Type-checks this package's TESTS, which `tsconfig.json` now excludes. + // Template: `packages/plugin-dashboard/tsconfig.test.json` (objectui#4530); + // rationale: `packages/types/tsconfig.test.json`. A test nothing compiles can + // assert a contract the compiler never checked and then read as evidence that + // the contract holds (objectui#3009). + // + // As in `@object-ui/fields`, the hole is new and deliberate: until + // objectui#4006 this suite was checked only as a side effect of the build + // config having no test exclude, which is also how `dist/index.test.d.ts` + // came to be published. The exclude and this project are one change — + // scripts/check-type-check-coverage.mjs fails 5c on the exclude alone + // (measured). + // + // Chained from this package's `type-check` script, which is what the CI + // `Type Check` job runs; the coverage gate enforces the chaining. + "extends": "../../tsconfig.json", + "compilerOptions": { + // A checking project, never an emitting one. This matters more here than in + // most packages: this package's BUILD config deliberately sets + // `noEmit: false` with `declaration` and `composite`, so those three must be + // turned back off rather than inherited — this project extends the ROOT + // config, not the build one, but stating them keeps that true if the + // `extends` target ever changes. + "noEmit": true, + "composite": false, + "declaration": false, + "jsx": "react-jsx", + // Measured: the single suite compiles clean against the root's ES2020 + // baseline, so the test is held to the same lib the shipped SOURCE targets. + "lib": ["ES2020", "DOM", "DOM.Iterable"], + // `paths` drops the root tsconfig's source-tree mappings so `@object-ui/*` + // resolves through each workspace dependency's built `.d.ts` rather than + // pulling sibling package sources in as program inputs. `type-check` + // dependsOn `^build` (turbo.json), so those `.d.ts` exist by the time this + // runs. The suite imports exactly one workspace specifier, + // `@object-ui/core`, plus its own `./index`. + "paths": {} + }, + // Only the test files. There is no ambient `*.d.ts` under `src/` in this + // package today — if one is ever added it must be NAMED here, because being + // imported is not enough to make an ambient declaration a program input + // (plugin-map's lesson, objectui#4270). + "include": ["src/**/*.test.ts", "src/**/*.test.tsx"] +}