diff --git a/.changeset/per-package-typecheck-coverage.md b/.changeset/per-package-typecheck-coverage.md new file mode 100644 index 0000000000..933d56f37d --- /dev/null +++ b/.changeset/per-package-typecheck-coverage.md @@ -0,0 +1,4 @@ +--- +--- + +ci(dx): every workspace package now either declares a `typecheck` script (`tsc --noEmit`, run by the new `turbo run typecheck` task in the lint workflow) or carries a measured DEBT/EXEMPT entry in `scripts/check-type-check-coverage.mjs`, whose coverage ratchet runs in CI (#4311). 66 of 77 packages build with tsup, which transpiles without type-checking, and `vitest run` does not type-check either — 48 packages were already clean and are locked in; the 29 failing ones are frozen in the ledger with their raw error counts and the issue's code/config/noise triage. Dev scripts and CI only; releases nothing. diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 2cfc49c79d..fcf1aa38f3 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -236,6 +236,18 @@ jobs: - name: Install dependencies run: pnpm install --frozen-lockfile + # Coverage meta-gate (#4311): 66 of 77 workspace packages build with tsup + # (esbuild transpile, no type check) and `vitest run` does not type-check + # either, so a package without a `typecheck` script has NO tsc reading its + # src/ or its tests at all — 380 real errors had accumulated that way, + # invisible to every green build. This gate makes the hole itself the + # failure: every package either declares `typecheck` (run by the turbo + # step below) or carries a measured DEBT/EXEMPT entry in the script's + # ledger, reconciled in both directions so the ledger can only shrink. + # Reads package.json files only; no build, sub-second. + - name: Check every package is type-check covered or ledgered + run: pnpm check:type-check-coverage + - name: Type check (@objectstack/spec) run: pnpm --filter @objectstack/spec exec tsc --noEmit @@ -351,6 +363,19 @@ jobs: - name: Build workspace packages run: pnpm exec turbo run build --filter='./packages/*' --filter='./examples/*^...' + # The per-package `typecheck` scripts the coverage gate above requires + # (#4311). tsc resolves workspace imports through each dependency's built + # dist/*.d.ts, so the task carries `dependsOn: ^build` in turbo.json — + # which also builds the handful of nested packages (packages/plugins/*, + # packages/services/*, …) the build step's direct-children glob misses + # when no example depends on them. Three filters because the bare + # `./packages/*` glob only matches direct children (see the build step's + # comment): the nested group dirs and apps/ (docs) need their own globs. + # Examples and the downstream contract keep their dedicated steps below — + # they are distinct gates, not bulk coverage. + - name: Type check workspace packages + run: pnpm exec turbo run typecheck --filter='./packages/*' --filter='./packages/*/*' --filter='./apps/*' + - name: Type check example apps run: pnpm --filter './examples/*' run typecheck diff --git a/AGENTS.md b/AGENTS.md index 99706d9ecd..17e1a368dd 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -20,9 +20,15 @@ pnpm install # deps pnpm setup # first-time: install + build spec pnpm build # turbo build (excludes docs) pnpm test # turbo test +pnpm typecheck # turbo typecheck — per-package `tsc --noEmit`; tsup/vitest never type-check (#4311) pnpm docs:dev # docs site ``` +Type-check coverage is ratcheted (`pnpm check:type-check-coverage`, CI-gated): every +workspace package declares a `typecheck` script or carries a measured DEBT/EXEMPT entry +in `scripts/check-type-check-coverage.mjs`. New packages must arrive covered; a package +that graduates deletes its ledger entry in the same PR. + ### Running the dev server | Scenario | Command | Notes | @@ -408,7 +414,7 @@ composition with its real services, or do not claim an answer. ## Post-Task Checklist -1. `pnpm test` — verify nothing broke. +1. `pnpm test` — verify nothing broke. Touched a type-check-covered package? `pnpm typecheck` too. 2. **Land it — don't leave passing work in the working tree.** Once tests pass, create a feature branch, commit, push, open a PR, and merge it after remote CI is fully green (see Multi-agent discipline: never straight to `main`, diff --git a/apps/docs/package.json b/apps/docs/package.json index 2ace0268aa..a7aa62b29a 100644 --- a/apps/docs/package.json +++ b/apps/docs/package.json @@ -10,7 +10,8 @@ "site:start": "next start", "site:lint": "next lint", "types:check": "fumadocs-mdx && next typegen && tsc --noEmit", - "postinstall": "fumadocs-mdx" + "postinstall": "fumadocs-mdx", + "typecheck": "tsc --noEmit" }, "dependencies": { "fumadocs-core": "16.12.1", diff --git a/package.json b/package.json index 3a9fba8e7d..7538c24cc9 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "spec:rebuild": "turbo run build --filter=...@objectstack/spec", "test": "turbo run test", "test:e2e": "turbo run test:e2e", + "typecheck": "turbo run typecheck", "clean": "turbo run clean && rm -rf dist", "setup": "pnpm install && pnpm --filter @objectstack/spec build", "version": "changeset version && node scripts/sync-protocol-version.mjs && node scripts/sync-template-versions.mjs", @@ -41,7 +42,8 @@ "check:console-sha": "node scripts/check-console-sha.mjs", "check:release-notes": "node scripts/check-release-notes.mjs", "check:node-version": "node scripts/check-node-version.mjs", - "check:published-files": "node scripts/check-published-files.mjs --self-test && node scripts/check-published-files.mjs" + "check:published-files": "node scripts/check-published-files.mjs --self-test && node scripts/check-published-files.mjs", + "check:type-check-coverage": "node scripts/check-type-check-coverage.mjs --self-test && node scripts/check-type-check-coverage.mjs" }, "keywords": [ "objectstack", diff --git a/packages/apps/account/package.json b/packages/apps/account/package.json index a3d21658ea..135660b4f2 100644 --- a/packages/apps/account/package.json +++ b/packages/apps/account/package.json @@ -13,7 +13,8 @@ } }, "scripts": { - "build": "tsup" + "build": "tsup", + "typecheck": "tsc --noEmit" }, "dependencies": { "@objectstack/platform-objects": "workspace:*", diff --git a/packages/apps/setup/package.json b/packages/apps/setup/package.json index d883b7c3b9..cd4691dfef 100644 --- a/packages/apps/setup/package.json +++ b/packages/apps/setup/package.json @@ -13,7 +13,8 @@ } }, "scripts": { - "build": "tsup" + "build": "tsup", + "typecheck": "tsc --noEmit" }, "dependencies": { "@objectstack/platform-objects": "workspace:*", diff --git a/packages/apps/studio/package.json b/packages/apps/studio/package.json index 2404c711e4..ce77eed6fb 100644 --- a/packages/apps/studio/package.json +++ b/packages/apps/studio/package.json @@ -13,7 +13,8 @@ } }, "scripts": { - "build": "tsup" + "build": "tsup", + "typecheck": "tsc --noEmit" }, "dependencies": { "@objectstack/platform-objects": "workspace:*", diff --git a/packages/cli/package.json b/packages/cli/package.json index 5bec4a9a13..87532fb77c 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -11,7 +11,8 @@ "scripts": { "build": "if [ -n \"$OS_SKIP_DTS\" ]; then tsc -p tsconfig.build.json --noCheck --declaration false --declarationMap false; else tsc -p tsconfig.build.json; fi", "dev": "tsc -p tsconfig.build.json --watch", - "test": "vitest run" + "test": "vitest run", + "typecheck": "tsc --noEmit" }, "keywords": [ "objectstack", diff --git a/packages/client-react/package.json b/packages/client-react/package.json index 407147c97c..fa66036621 100644 --- a/packages/client-react/package.json +++ b/packages/client-react/package.json @@ -13,7 +13,8 @@ } }, "scripts": { - "build": "tsup src/index.tsx --config ../../tsup.config.ts" + "build": "tsup src/index.tsx --config ../../tsup.config.ts", + "typecheck": "tsc --noEmit" }, "peerDependencies": { "react": ">=18.0.0" diff --git a/packages/client/package.json b/packages/client/package.json index 095bfd6305..2a32634a36 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -15,7 +15,8 @@ "scripts": { "build": "tsup --config ../../tsup.config.ts", "test": "vitest run", - "test:integration": "vitest run --config vitest.integration.config.ts" + "test:integration": "vitest run --config vitest.integration.config.ts", + "typecheck": "tsc --noEmit" }, "dependencies": { "@objectstack/core": "workspace:*", diff --git a/packages/connectors/connector-mcp/package.json b/packages/connectors/connector-mcp/package.json index 0787ce7cdf..dfed03662a 100644 --- a/packages/connectors/connector-mcp/package.json +++ b/packages/connectors/connector-mcp/package.json @@ -14,7 +14,8 @@ }, "scripts": { "build": "tsup --config ../../../tsup.config.ts", - "test": "vitest run --passWithNoTests" + "test": "vitest run --passWithNoTests", + "typecheck": "tsc --noEmit" }, "dependencies": { "@modelcontextprotocol/sdk": "^1.29.0", diff --git a/packages/connectors/connector-openapi/package.json b/packages/connectors/connector-openapi/package.json index 73fa332edb..d572fbf609 100644 --- a/packages/connectors/connector-openapi/package.json +++ b/packages/connectors/connector-openapi/package.json @@ -14,7 +14,8 @@ }, "scripts": { "build": "tsup --config ../../../tsup.config.ts", - "test": "vitest run --passWithNoTests" + "test": "vitest run --passWithNoTests", + "typecheck": "tsc --noEmit" }, "dependencies": { "@objectstack/core": "workspace:*", diff --git a/packages/connectors/connector-rest/package.json b/packages/connectors/connector-rest/package.json index 02ca8059d1..758d5d3435 100644 --- a/packages/connectors/connector-rest/package.json +++ b/packages/connectors/connector-rest/package.json @@ -14,7 +14,8 @@ }, "scripts": { "build": "tsup --config ../../../tsup.config.ts", - "test": "vitest run --passWithNoTests" + "test": "vitest run --passWithNoTests", + "typecheck": "tsc --noEmit" }, "dependencies": { "@objectstack/core": "workspace:*", diff --git a/packages/connectors/connector-slack/package.json b/packages/connectors/connector-slack/package.json index 47752f801a..c4f42dcc4c 100644 --- a/packages/connectors/connector-slack/package.json +++ b/packages/connectors/connector-slack/package.json @@ -14,7 +14,8 @@ }, "scripts": { "build": "tsup --config ../../../tsup.config.ts", - "test": "vitest run --passWithNoTests" + "test": "vitest run --passWithNoTests", + "typecheck": "tsc --noEmit" }, "dependencies": { "@objectstack/core": "workspace:*", diff --git a/packages/formula/package.json b/packages/formula/package.json index 78cb50a4f4..6fdc44c591 100644 --- a/packages/formula/package.json +++ b/packages/formula/package.json @@ -14,7 +14,8 @@ }, "scripts": { "build": "tsup --config ../../tsup.config.ts", - "test": "vitest run" + "test": "vitest run", + "typecheck": "tsc --noEmit" }, "dependencies": { "@marcbachmann/cel-js": "^8.0.0", diff --git a/packages/lint/package.json b/packages/lint/package.json index 0a7f1843bd..a6dcd2d3ae 100644 --- a/packages/lint/package.json +++ b/packages/lint/package.json @@ -16,7 +16,8 @@ "scripts": { "build": "tsup --config ../../tsup.config.ts", "dev": "tsc -w", - "test": "vitest run" + "test": "vitest run", + "typecheck": "tsc --noEmit" }, "dependencies": { "@objectstack/formula": "workspace:*", diff --git a/packages/mcp/package.json b/packages/mcp/package.json index 772047cd2e..b1fb5b8d40 100644 --- a/packages/mcp/package.json +++ b/packages/mcp/package.json @@ -15,7 +15,8 @@ }, "scripts": { "build": "tsup --config ../../tsup.config.ts", - "test": "vitest run" + "test": "vitest run", + "typecheck": "tsc --noEmit" }, "dependencies": { "@modelcontextprotocol/sdk": "^1.29.0", diff --git a/packages/metadata-core/package.json b/packages/metadata-core/package.json index 5181fe3dfd..033afee556 100644 --- a/packages/metadata-core/package.json +++ b/packages/metadata-core/package.json @@ -28,7 +28,8 @@ "dev": "tsc --watch", "clean": "rm -rf dist", "test": "vitest run", - "test:watch": "vitest" + "test:watch": "vitest", + "typecheck": "tsc --noEmit" }, "keywords": [ "objectstack", diff --git a/packages/metadata-fs/package.json b/packages/metadata-fs/package.json index b2a86dd6b1..c72fb33fb2 100644 --- a/packages/metadata-fs/package.json +++ b/packages/metadata-fs/package.json @@ -23,7 +23,8 @@ "dev": "tsc --watch", "clean": "rm -rf dist", "test": "vitest run", - "test:watch": "vitest" + "test:watch": "vitest", + "typecheck": "tsc --noEmit" }, "keywords": [ "objectstack", diff --git a/packages/objectql/package.json b/packages/objectql/package.json index 101e9b9769..5c178aeaa5 100644 --- a/packages/objectql/package.json +++ b/packages/objectql/package.json @@ -19,7 +19,8 @@ }, "scripts": { "build": "tsup", - "test": "vitest run" + "test": "vitest run", + "typecheck": "tsc --noEmit" }, "dependencies": { "@objectstack/core": "workspace:*", diff --git a/packages/platform-objects/package.json b/packages/platform-objects/package.json index 9c5189dec8..7594a17952 100644 --- a/packages/platform-objects/package.json +++ b/packages/platform-objects/package.json @@ -64,7 +64,8 @@ }, "scripts": { "build": "tsup", - "test": "vitest run --passWithNoTests" + "test": "vitest run --passWithNoTests", + "typecheck": "tsc --noEmit" }, "dependencies": { "@objectstack/metadata-core": "workspace:*", diff --git a/packages/plugins/driver-mongodb/package.json b/packages/plugins/driver-mongodb/package.json index 3f8b2ccbc7..1f303ee28b 100644 --- a/packages/plugins/driver-mongodb/package.json +++ b/packages/plugins/driver-mongodb/package.json @@ -15,7 +15,8 @@ "scripts": { "build": "tsup --config ../../../tsup.config.ts", "dev": "tsc -w", - "test": "vitest run" + "test": "vitest run", + "typecheck": "tsc --noEmit" }, "dependencies": { "@objectstack/core": "workspace:*", diff --git a/packages/plugins/embedder-openai/package.json b/packages/plugins/embedder-openai/package.json index ed8f935f69..edca5beb20 100644 --- a/packages/plugins/embedder-openai/package.json +++ b/packages/plugins/embedder-openai/package.json @@ -15,7 +15,8 @@ "scripts": { "build": "tsup --config ../../../tsup.config.ts", "dev": "tsc -w", - "test": "vitest run" + "test": "vitest run", + "typecheck": "tsc --noEmit" }, "dependencies": { "@objectstack/spec": "workspace:*" diff --git a/packages/plugins/knowledge-memory/package.json b/packages/plugins/knowledge-memory/package.json index 535b1c8439..0618f12556 100644 --- a/packages/plugins/knowledge-memory/package.json +++ b/packages/plugins/knowledge-memory/package.json @@ -15,7 +15,8 @@ "scripts": { "build": "tsup --config ../../../tsup.config.ts", "dev": "tsc -w", - "test": "vitest run" + "test": "vitest run", + "typecheck": "tsc --noEmit" }, "dependencies": { "@objectstack/core": "workspace:*", diff --git a/packages/plugins/plugin-audit/package.json b/packages/plugins/plugin-audit/package.json index c4c1431e49..10c4a6d793 100644 --- a/packages/plugins/plugin-audit/package.json +++ b/packages/plugins/plugin-audit/package.json @@ -14,7 +14,8 @@ }, "scripts": { "build": "tsup --config ../../../tsup.config.ts", - "test": "vitest run --passWithNoTests" + "test": "vitest run --passWithNoTests", + "typecheck": "tsc --noEmit" }, "dependencies": { "@objectstack/core": "workspace:*", diff --git a/packages/plugins/plugin-auth/package.json b/packages/plugins/plugin-auth/package.json index 593e463a8f..ac424e37e8 100644 --- a/packages/plugins/plugin-auth/package.json +++ b/packages/plugins/plugin-auth/package.json @@ -15,7 +15,8 @@ }, "scripts": { "build": "tsup --config ../../../tsup.config.ts", - "test": "vitest run" + "test": "vitest run", + "typecheck": "tsc --noEmit" }, "dependencies": { "@better-auth/core": "1.7.0-rc.2", diff --git a/packages/plugins/plugin-dev/package.json b/packages/plugins/plugin-dev/package.json index 94810d0e1f..27843c085c 100644 --- a/packages/plugins/plugin-dev/package.json +++ b/packages/plugins/plugin-dev/package.json @@ -14,7 +14,8 @@ }, "scripts": { "build": "tsup --config ../../../tsup.config.ts", - "test": "vitest run" + "test": "vitest run", + "typecheck": "tsc --noEmit" }, "dependencies": { "@objectstack/account": "workspace:^", diff --git a/packages/plugins/plugin-email/package.json b/packages/plugins/plugin-email/package.json index dd30a0f7a7..74f6c13dc6 100644 --- a/packages/plugins/plugin-email/package.json +++ b/packages/plugins/plugin-email/package.json @@ -14,7 +14,8 @@ }, "scripts": { "build": "tsup --config ../../../tsup.config.ts", - "test": "vitest run --passWithNoTests" + "test": "vitest run --passWithNoTests", + "typecheck": "tsc --noEmit" }, "dependencies": { "@objectstack/core": "workspace:*", diff --git a/packages/plugins/plugin-hono-server/package.json b/packages/plugins/plugin-hono-server/package.json index 70c807d604..d8cfe6f3ee 100644 --- a/packages/plugins/plugin-hono-server/package.json +++ b/packages/plugins/plugin-hono-server/package.json @@ -7,7 +7,8 @@ "types": "dist/index.d.ts", "scripts": { "build": "tsup --config ../../../tsup.config.ts", - "test": "vitest run" + "test": "vitest run", + "typecheck": "tsc --noEmit" }, "dependencies": { "@hono/node-server": "^2.0.12", diff --git a/packages/plugins/plugin-pinyin-search/package.json b/packages/plugins/plugin-pinyin-search/package.json index 918b6a3042..a3f2607509 100644 --- a/packages/plugins/plugin-pinyin-search/package.json +++ b/packages/plugins/plugin-pinyin-search/package.json @@ -14,7 +14,8 @@ }, "scripts": { "build": "tsup --config ../../../tsup.config.ts", - "test": "vitest run --passWithNoTests" + "test": "vitest run --passWithNoTests", + "typecheck": "tsc --noEmit" }, "dependencies": { "@objectstack/core": "workspace:*", diff --git a/packages/plugins/plugin-reports/package.json b/packages/plugins/plugin-reports/package.json index 36177693b7..a85d7ae6a4 100644 --- a/packages/plugins/plugin-reports/package.json +++ b/packages/plugins/plugin-reports/package.json @@ -14,7 +14,8 @@ }, "scripts": { "build": "tsup --config ../../../tsup.config.ts", - "test": "vitest run --passWithNoTests" + "test": "vitest run --passWithNoTests", + "typecheck": "tsc --noEmit" }, "dependencies": { "@objectstack/core": "workspace:*", diff --git a/packages/plugins/plugin-security/package.json b/packages/plugins/plugin-security/package.json index 43509bf6cf..b24a527cc3 100644 --- a/packages/plugins/plugin-security/package.json +++ b/packages/plugins/plugin-security/package.json @@ -14,7 +14,8 @@ }, "scripts": { "build": "tsup --config ../../../tsup.config.ts", - "test": "vitest run" + "test": "vitest run", + "typecheck": "tsc --noEmit" }, "dependencies": { "@objectstack/core": "workspace:*", diff --git a/packages/plugins/plugin-sharing/package.json b/packages/plugins/plugin-sharing/package.json index 1198b63071..349189351a 100644 --- a/packages/plugins/plugin-sharing/package.json +++ b/packages/plugins/plugin-sharing/package.json @@ -14,7 +14,8 @@ }, "scripts": { "build": "tsup --config ../../../tsup.config.ts", - "test": "vitest run --passWithNoTests" + "test": "vitest run --passWithNoTests", + "typecheck": "tsc --noEmit" }, "dependencies": { "@objectstack/core": "workspace:*", diff --git a/packages/plugins/plugin-webhooks/package.json b/packages/plugins/plugin-webhooks/package.json index 6337cc14d8..5d64976315 100644 --- a/packages/plugins/plugin-webhooks/package.json +++ b/packages/plugins/plugin-webhooks/package.json @@ -20,7 +20,8 @@ }, "scripts": { "build": "tsup", - "test": "vitest run" + "test": "vitest run", + "typecheck": "tsc --noEmit" }, "dependencies": { "@objectstack/core": "workspace:*", diff --git a/packages/qa/http-conformance/package.json b/packages/qa/http-conformance/package.json index 714a2562a2..3bc06d3a53 100644 --- a/packages/qa/http-conformance/package.json +++ b/packages/qa/http-conformance/package.json @@ -6,7 +6,8 @@ "description": "HTTP transport-port conformance gate (ADR-0076 D11/OQ#10, #2462) \u2014 a zero-dependency node:http reference implementation of IHttpServer plus a cross-adapter suite that boots the dispatcher bridge and REST generator on it AND on plugin-hono-server, pinning that the port stays free of framework-isms. Not published; validation instrument, not a product server.", "type": "module", "scripts": { - "test": "vitest run" + "test": "vitest run", + "typecheck": "tsc --noEmit" }, "dependencies": { "@objectstack/core": "workspace:*" diff --git a/packages/sdui-parser/package.json b/packages/sdui-parser/package.json index 5ba9319d3e..bf74b7fda3 100644 --- a/packages/sdui-parser/package.json +++ b/packages/sdui-parser/package.json @@ -14,7 +14,8 @@ }, "scripts": { "build": "tsup --config ../../tsup.config.ts", - "test": "vitest run" + "test": "vitest run", + "typecheck": "tsc --noEmit" }, "devDependencies": { "typescript": "^6.0.3", diff --git a/packages/services/service-cluster-redis/package.json b/packages/services/service-cluster-redis/package.json index 474679f263..659986bda9 100644 --- a/packages/services/service-cluster-redis/package.json +++ b/packages/services/service-cluster-redis/package.json @@ -15,7 +15,8 @@ }, "scripts": { "build": "tsup", - "test": "vitest run" + "test": "vitest run", + "typecheck": "tsc --noEmit" }, "dependencies": { "@objectstack/service-cluster": "workspace:*", diff --git a/packages/services/service-package/package.json b/packages/services/service-package/package.json index 1720ac4bb7..b3c6221383 100644 --- a/packages/services/service-package/package.json +++ b/packages/services/service-package/package.json @@ -15,7 +15,8 @@ }, "scripts": { "build": "tsup --config ../../../tsup.config.ts", - "test": "vitest run --passWithNoTests" + "test": "vitest run --passWithNoTests", + "typecheck": "tsc --noEmit" }, "dependencies": { "@objectstack/core": "workspace:*", diff --git a/packages/services/service-sms/package.json b/packages/services/service-sms/package.json index 52a62a00c1..3b2d6d4272 100644 --- a/packages/services/service-sms/package.json +++ b/packages/services/service-sms/package.json @@ -14,7 +14,8 @@ }, "scripts": { "build": "tsup --config ../../../tsup.config.ts", - "test": "vitest run --passWithNoTests" + "test": "vitest run --passWithNoTests", + "typecheck": "tsc --noEmit" }, "dependencies": { "@objectstack/core": "workspace:*", diff --git a/packages/spec/package.json b/packages/spec/package.json index fb6045179f..9bdf810122 100644 --- a/packages/spec/package.json +++ b/packages/spec/package.json @@ -214,7 +214,8 @@ "gen:react-blocks": "tsx scripts/build-react-blocks-contract.ts", "check:react-blocks": "tsx scripts/build-react-blocks-contract.ts --check", "check:react-conformance": "tsx scripts/check-react-blocks-conformance.ts", - "check:skill-examples": "tsx scripts/check-skill-examples.ts" + "check:skill-examples": "tsx scripts/check-skill-examples.ts", + "typecheck": "tsc --noEmit" }, "keywords": [ "objectstack", diff --git a/packages/triggers/trigger-api/package.json b/packages/triggers/trigger-api/package.json index b72523bf54..21ed12f2ce 100644 --- a/packages/triggers/trigger-api/package.json +++ b/packages/triggers/trigger-api/package.json @@ -14,7 +14,8 @@ }, "scripts": { "build": "tsup --config ../../../tsup.config.ts", - "test": "vitest run --passWithNoTests" + "test": "vitest run --passWithNoTests", + "typecheck": "tsc --noEmit" }, "dependencies": { "@objectstack/core": "workspace:*", diff --git a/packages/triggers/trigger-record-change/package.json b/packages/triggers/trigger-record-change/package.json index ed973daa0c..893bfd1a92 100644 --- a/packages/triggers/trigger-record-change/package.json +++ b/packages/triggers/trigger-record-change/package.json @@ -14,7 +14,8 @@ }, "scripts": { "build": "tsup --config ../../../tsup.config.ts", - "test": "vitest run --passWithNoTests" + "test": "vitest run --passWithNoTests", + "typecheck": "tsc --noEmit" }, "dependencies": { "@objectstack/core": "workspace:*", diff --git a/packages/triggers/trigger-schedule/package.json b/packages/triggers/trigger-schedule/package.json index 7bccaf44dd..9fa828e894 100644 --- a/packages/triggers/trigger-schedule/package.json +++ b/packages/triggers/trigger-schedule/package.json @@ -14,7 +14,8 @@ }, "scripts": { "build": "tsup --config ../../../tsup.config.ts", - "test": "vitest run --passWithNoTests" + "test": "vitest run --passWithNoTests", + "typecheck": "tsc --noEmit" }, "dependencies": { "@objectstack/core": "workspace:*", diff --git a/packages/verify/package.json b/packages/verify/package.json index ba2c2789f8..f845d08ff6 100644 --- a/packages/verify/package.json +++ b/packages/verify/package.json @@ -16,7 +16,8 @@ "scripts": { "build": "tsup --config ../../tsup.config.ts", "dev": "tsc -w", - "test": "vitest run" + "test": "vitest run", + "typecheck": "tsc --noEmit" }, "dependencies": { "@objectstack/core": "workspace:*", diff --git a/packages/vscode-objectstack/package.json b/packages/vscode-objectstack/package.json index 6928651159..62bece1b95 100644 --- a/packages/vscode-objectstack/package.json +++ b/packages/vscode-objectstack/package.json @@ -56,7 +56,8 @@ "scripts": { "build": "tsc -p ./tsconfig.json", "watch": "tsc -watch -p ./tsconfig.json", - "package": "vsce package" + "package": "vsce package", + "typecheck": "tsc --noEmit" }, "devDependencies": { "@types/vscode": "^1.125.0", diff --git a/scripts/check-type-check-coverage.mjs b/scripts/check-type-check-coverage.mjs new file mode 100644 index 0000000000..535b084221 --- /dev/null +++ b/scripts/check-type-check-coverage.mjs @@ -0,0 +1,547 @@ +#!/usr/bin/env node +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. +// +// check-type-check-coverage -- every workspace package's TypeScript is read by +// tsc somewhere, or its absence is a recorded, tracked decision (#4311). +// +// 66 of 77 workspace packages build with tsup, which transpiles with esbuild +// and never type-checks. `vitest run` does not type-check either. And the CI +// typecheck job covered exactly four targets (spec, examples, +// downstream-contract, docs code blocks) -- so for most packages NOTHING read +// src/ or the tests with a type checker at all. #4311 measured the hole: +// 380 code-tier errors across 18 packages, 241 of them in driver-sql tests +// passing authored-shape literals into the freshly narrowed QueryAST. A green +// test suite no tsc has ever read is not evidence of a contract; this gate +// makes the coverage hole itself the failure, so it can only shrink. +// +// node scripts/check-type-check-coverage.mjs +// node scripts/check-type-check-coverage.mjs --self-test +// +// Invariants, per workspace package (the root workspace package included -- +// #4311's audit counted its top-level TypeScript like any other package's): +// +// COVERED the package declares a `typecheck` script, OR carries a DEBT +// entry (measured tsc error count + tracking issue) or an EXEMPT +// entry (why type-checking cannot apply) below. A new package +// must arrive covered -- the ledger is closed to new debt. +// REAL a declared `typecheck` script actually invokes tsc. A script +// that echoes, lints, or runs tests is not type coverage. +// RUNNABLE turbo.json declares the `typecheck` task, the root `typecheck` +// script aggregates it (`turbo run typecheck`, the build/test +// convention), and lint.yml invokes it -- a script CI never +// executes is not coverage either (#4203: gates that only run +// where nobody runs them, rot). +// RECONCILED in both directions: a DEBT/EXEMPT entry for a package that now +// declares `typecheck`, or that no longer exists, is an error. +// A ledger that can only accrete rots into a list nobody trusts. +// +// The root is the one asymmetry: its `typecheck` script is the workspace +// aggregator, so its OWN top-level TypeScript is covered by a `typecheck:root` +// script (tsc, invoked from lint.yml) or by a ledger entry like anyone else. +// +// DEBT is frozen debt, not a permission slip. Every entry below was measured +// by running the package's own `tsc --noEmit` on main (see the issue for the +// code-tier / config-tier / noise split -- raw counts here include all three). +// To onboard a package: fix (or config-fix) its errors, add +// `"typecheck": "tsc --noEmit"` to its package.json, and delete its entry +// here in the same PR. Deleting the entry without the script fails COVERED; +// keeping the entry alongside the script fails RECONCILED. + +import { existsSync, readFileSync, readdirSync } from 'node:fs'; +import { join, posix, resolve } from 'node:path'; + +// Anchored to the script, not to cwd: the verdict must not depend on where the +// guard was invoked from. +const ROOT = resolve(import.meta.dirname, '..'); +const SELF = 'scripts/check-type-check-coverage.mjs'; +const WORKSPACE_FILE = 'pnpm-workspace.yaml'; +const TRACKING_ISSUE = 'https://github.com/objectstack-ai/objectstack/issues/4311'; + +// Package name -> { errors, note? }. `errors` is the raw `tsc --noEmit` count +// measured per package on main @ e5a4d26 (2026-07-31, the #4311 audit re-run +// one commit later; aggregates matched exactly: 48 clean / 29 failing). +// Raw counts include all three of #4311's tiers -- code-tier (real defects), +// config-tier (the check itself misconfigured: TS2591/TS2584 missing +// `types:["node"]`, TS2835/TS2307 module resolution) and noise (TS7006 +// implicit-any params, TS6133 unused) -- so each note says what the pile is +// made of. Nobody should mistake a config-tier pile for real breakage, or -- +// worse -- the reverse: `core` at 91 raw has 3 real errors, while +// `driver-sql`'s 241 are ALL real. +const DEBT = { + '@objectstack/cloud-connection': { + errors: 13, + note: 'code-tier 11 (TS2493 tuple indexing) + 2 config-tier.', + }, + '@objectstack/core': { + errors: 91, + note: 'code-tier 3; the rest is config-tier (TS2835/TS2347 module resolution) and noise (TS7006).', + }, + '@objectstack/dogfood': { + errors: 12, + note: 'code-tier 8 (TS2322/TS2554) + 3 config-tier + 1 noise.', + }, + '@objectstack/driver-memory': { + errors: 23, + note: 'all code-tier: TS2345/TS2741/TS2339, the QueryAST authored-vs-parsed playbook.', + }, + '@objectstack/driver-sql': { + errors: 241, + note: 'ALL code-tier (TS2345 x123, TS2353 x118): tests pass authored-shape literals into the parsed QueryAST type across 8+ test files; #4196/#4286 narrowed QueryAST and no tsc ever read these tests. 63% of the whole audit; single playbook, fix as one batch.', + }, + '@objectstack/driver-sqlite-wasm': { + errors: 27, + note: 'all code-tier (TS2345/TS2353), same QueryAST playbook as driver-sql.', + }, + '@objectstack/hono': { + errors: 3, + note: 'all code-tier (TS2769/TS18046).', + }, + '@objectstack/knowledge-ragflow': { + errors: 4, + note: 'code-tier 3 (TS2353) + 1 config-tier (TS2550 lib).', + }, + '@objectstack/metadata': { + errors: 87, + note: 'code-tier 31 (TS2345/TS2353); the rest is config-tier (TS2835) and noise (TS7006).', + }, + '@objectstack/metadata-protocol': { + errors: 21, + note: 'code-tier 2; the rest is config-tier (TS2835) and noise (TS7006).', + }, + '@objectstack/observability': { + errors: 11, + note: 'all code-tier (TS2554 wrong arity x10, TS2552).', + }, + '@objectstack/plugin-approvals': { + errors: 1, + note: 'config-tier only (TS2550 lib); no code-tier finding in #4311.', + }, + '@objectstack/rest': { + errors: 2, + note: 'code-tier 2 (TS2345).', + }, + '@objectstack/runtime': { + errors: 18, + note: 'noise only (TS6133 unused); no code-tier finding in #4311.', + }, + '@objectstack/service-analytics': { + errors: 3, + note: 'code-tier 2 (TS7053) + 1 noise.', + }, + '@objectstack/service-automation': { + errors: 2, + note: 'code-tier 2 (TS2741: engine.test.ts misses resumeAuthority, the #4198 discovery that opened #4311).', + }, + '@objectstack/service-cache': { + errors: 3, + note: 'config-tier only (TS2835); no code-tier finding in #4311.', + }, + '@objectstack/service-cluster': { + errors: 1, + note: 'code-tier 1 (TS2322).', + }, + '@objectstack/service-i18n': { + errors: 14, + note: 'config-tier (TS2835) and noise (TS7006) only; no code-tier finding in #4311.', + }, + '@objectstack/service-job': { + errors: 6, + note: 'config-tier (TS2835) and noise only; no code-tier finding in #4311.', + }, + '@objectstack/service-knowledge': { + errors: 8, + note: 'code-tier 3 (TS2339/TS2352/TS2493); the rest config-tier and noise.', + }, + '@objectstack/service-messaging': { + errors: 1, + note: 'noise only (TS6133); no code-tier finding in #4311.', + }, + '@objectstack/service-queue': { + errors: 9, + note: 'config-tier (TS2835) and noise (TS7006) only; no code-tier finding in #4311.', + }, + '@objectstack/service-realtime': { + errors: 9, + note: 'config-tier (TS2835) and noise (TS7006) only; no code-tier finding in #4311.', + }, + '@objectstack/service-settings': { + errors: 44, + note: 'config-tier (TS2307/TS2835 module resolution) and noise (TS7006) only; no code-tier finding in #4311.', + }, + '@objectstack/service-storage': { + errors: 42, + note: 'code-tier 5 (TS2339/TS2347); the rest is config-tier (TS2835) and noise (TS7006).', + }, + '@objectstack/spec-monorepo': { + errors: 50, + note: 'the workspace root itself: code-tier 2 (TS2304); the rest is config-tier (TS2307/TS2591/TS2584 -- the root tsconfig has no `types:["node"]`) and noise.', + }, + '@objectstack/types': { + errors: 107, + note: 'config-tier only (TS2591/TS2584: missing `types:["node"]`/lib in the check config); no code-tier finding in #4311.', + }, + 'create-objectstack': { + errors: 5, + note: 'config-tier only (TS2307 module resolution of template files); no code-tier finding in #4311.', + }, +}; + +// Package name -> why running tsc over it is not applicable at all. An EXEMPT +// entry is a statement about the package's nature, not about its debt; if the +// nature changes, the RECONCILED direction forces this entry out. +const EXEMPT = { + '@objectstack/console': + 'Published objectui build artifact -- package.json/README/CHANGELOG plus a dist/ pulled in by `pnpm objectui:refresh`. No TypeScript sources, no tsconfig; the sources are type-checked in the objectui repo.', +}; + +/** + * The `packages:` globs from pnpm-workspace.yaml. Blank lines and comments are + * skipped rather than treated as the end of the list: stopping early would + * drop members from the scan and report a clean run over a partial workspace. + */ +function workspaceGlobs() { + const lines = readFileSync(join(ROOT, WORKSPACE_FILE), 'utf8').split(/\r?\n/); + const start = lines.findIndex((l) => /^packages\s*:\s*$/.test(l)); + if (start === -1) throw new Error(`${WORKSPACE_FILE}: no top-level \`packages:\` block`); + const globs = []; + for (let i = start + 1; i < lines.length; i++) { + const line = lines[i].replace(/#.*$/, '').trimEnd(); + if (!line.trim()) continue; + const m = line.match(/^\s+-\s+['"]?([^'"\s]+)['"]?\s*$/); + if (m) { + globs.push(m[1]); + continue; + } + if (/^\S/.test(line)) break; // the next top-level key ends the block + } + if (globs.length === 0) throw new Error(`${WORKSPACE_FILE}: \`packages:\` block is empty`); + return globs; +} + +/** Every workspace member as { name, dir, scripts, hasTsconfig }. */ +function workspacePackages() { + const dirs = []; + for (const glob of workspaceGlobs()) { + // Every pattern in this repo is `