diff --git a/.github/workflows/ci-superdoc.yml b/.github/workflows/ci-superdoc.yml index 8229f34b44..aac05c9fd8 100644 --- a/.github/workflows/ci-superdoc.yml +++ b/.github/workflows/ci-superdoc.yml @@ -120,28 +120,20 @@ jobs: # tree (those are tracked under SD-2863 follow-up tickets). run: pnpm --filter superdoc run check:jsdoc - - name: Consumer typecheck (matrix) - # The matrix script owns the published-package validation path: - # it packs superdoc, installs the tarball into the standalone - # fixture (`pnpm install --ignore-workspace`), then runs every - # scenario under its declared resolution mode and strictness - # settings. Replaces the pre-SD-2831 bare `tsc --noEmit` step - # so the new pack-and-install scenarios are actually exercised - # in CI, not just locally. - run: | - cd tests/consumer-typecheck - node typecheck-matrix.mjs - - - name: Deep public-type audit (supported-root strict, SD-3213e) - # Single invocation does both: (1) prints the broad inventory - # (tier counts, top files, entry/root-bucket attribution) for - # visibility, and (2) gates on the supported-root subset against - # `deep-type-audit.supported-root-allowlist.json`. Fails on any - # new `any` finding reachable from root `.` via an export - # classified as `supported-root`, AND on stale entries (a drain - # landed but the allowlist wasn't shrunk). The broad audit - # remains report-only because it includes legacy/raw reach. - run: node tests/consumer-typecheck/deep-type-audit.mjs --strict-supported-root + - name: Public-contract check (matrix + supported-root strict audit) + # SD-673 Phase 1: collapse the previous two CI steps + # ('Consumer typecheck (matrix)' + 'Deep public-type audit') into + # the single wrapper command. Same coverage: + # - typecheck-matrix.mjs packs superdoc, installs the tarball + # into the consumer fixture, runs every scenario. + # - deep-type-audit.mjs --strict-supported-root reuses that + # install and gates on the supported-root any allowlist + # (SD-3213e). Broad inventory still printed for visibility. + # --skip-build because the Build step above already ran + # `pnpm run build` (which includes build:superdoc). + # Local equivalent: `pnpm check:public-contract` (with the build + # stage included). + run: pnpm check:public-contract --skip-build - name: Package shape gates # External package-shape linters (publint + attw) running against diff --git a/AGENTS.md b/AGENTS.md index 15f8ac699b..32de3f1ba7 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -72,7 +72,7 @@ Do not hand-edit `COMMAND_CATALOG`, `OPERATION_MEMBER_PATH_MAP`, `OPERATION_REFE - `pnpm test` - unit tests - `pnpm dev` - dev server from `examples/` - `pnpm run generate:all` - regenerate schemas, SDK clients, tool catalogs, reference docs -- `pnpm check:public-contract` - validate the published public type contract: wraps build + strict supported-root audit + consumer typecheck matrix. ~3 min. Scoped to the public type surface, not a replacement for `pnpm test` or `pnpm build`. SD-3256. +- `pnpm check:public-contract` - validate the published public type contract: wraps build + consumer typecheck matrix + strict supported-root audit. ~3 min. Scoped to the public type surface, not a replacement for `pnpm test` or `pnpm build`. Also the single command CI runs (with `--skip-build` after its own Build step). SD-3256 / SD-673. - `pnpm report:public-contract` - print the public-contract tier metadata (supported / legacy / legacy-raw / asset / deprecated). Read-only. Source of truth: `packages/superdoc/scripts/type-surface.config.cjs` (`publicContract` export). SD-3256. ## Testing diff --git a/scripts/check-public-contract.mjs b/scripts/check-public-contract.mjs index 9cd3098b2b..3477f50169 100755 --- a/scripts/check-public-contract.mjs +++ b/scripts/check-public-contract.mjs @@ -1,31 +1,34 @@ #!/usr/bin/env node /** - * SD-3256 Phase 1: single command to validate the public type contract. + * Single command to validate the published superdoc package's public + * TypeScript surface end-to-end. * - * Runs the validators that, together, answer "is the published - * superdoc package's public TypeScript surface healthy?" Today these - * run as three separate invocations across CI: + * Stages: + * 1. build:superdoc - vite build + the postbuild validator chain + * (check-tsconfig-type-surface, ensure-types, + * audit-bundle, audit-declarations, + * check-export-coverage, verify-public-facade-emit, + * report-declaration-reachability). + * Skipped when `--skip-build` is passed (CI calls + * `pnpm run build` separately in its own step). + * 2. typecheck-matrix - packs superdoc + installs the tarball into + * tests/consumer-typecheck/node_modules/, then + * runs every consumer scenario. + * 3. deep-type-audit - strict gate on the supported-root public + * surface (must be 0 findings). Reuses the + * install that stage 2 produced (no `--pack`). * - * pnpm run build:superdoc - * # vite build + the postbuild validator chain - * # (check-tsconfig-type-surface, ensure-types, audit-bundle, - * # audit-declarations, check-export-coverage, - * # verify-public-facade-emit, report-declaration-reachability) + * Matrix runs BEFORE audit on purpose: matrix packs + installs the + * tarball once, and the audit then reuses that install. Without this + * order the audit would `--pack` separately and double the work. * - * node tests/consumer-typecheck/deep-type-audit.mjs --pack --strict-supported-root - * # strict gate on the supported public surface; must be 0 findings - * - * node tests/consumer-typecheck/typecheck-matrix.mjs - * # consumer-perspective scenarios compiled against the packed tarball - * - * This wrapper orchestrates them in order, prints section headers, - * fails fast on the first failure, and gives an at-a-glance verdict. - * Zero behavior change for the validators themselves; this is pure DX. - * - * Usage: + * Local usage: * pnpm check:public-contract * - * Tracking: SD-3256 (umbrella). Phase 1. + * CI usage (Build step already ran): + * pnpm check:public-contract --skip-build + * + * SD-3256 Phase 1 (initial wrapper) / SD-673 Phase 1 (CI wiring). */ import { spawnSync } from 'node:child_process'; @@ -34,6 +37,9 @@ import { fileURLToPath } from 'node:url'; const REPO_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..'); +const flags = new Set(process.argv.slice(2)); +const skipBuild = flags.has('--skip-build'); + const stages = [ { name: 'build:superdoc', @@ -43,20 +49,26 @@ const stages = [ blurb: 'Build dist + run postbuild validators (audit-bundle, audit-declarations, ' + 'check-export-coverage, verify-public-facade-emit, ensure-types, ...).', + skipIf: skipBuild, + skipReason: '--skip-build passed; CI Build step already ran this', }, { - name: 'deep-type-audit --strict-supported-root', + name: 'typecheck-matrix', cwd: resolve(REPO_ROOT, 'tests/consumer-typecheck'), cmd: 'node', - args: ['deep-type-audit.mjs', '--pack', '--strict-supported-root'], - blurb: 'Strict gate on the supported-root public surface (must be 0 findings).', + args: ['typecheck-matrix.mjs'], + blurb: + 'Packs superdoc + installs the tarball into the consumer fixture, ' + + 'then runs every typecheck scenario.', }, { - name: 'typecheck-matrix', + name: 'deep-type-audit --strict-supported-root', cwd: resolve(REPO_ROOT, 'tests/consumer-typecheck'), cmd: 'node', - args: ['typecheck-matrix.mjs'], - blurb: 'Consumer-perspective scenarios against the packed tarball.', + args: ['deep-type-audit.mjs', '--strict-supported-root'], + blurb: + 'Strict gate on the supported-root public surface (must be 0 findings). ' + + 'Reuses the install produced by typecheck-matrix.', }, ]; @@ -64,13 +76,20 @@ const HR = '='.repeat(72); const start = Date.now(); let failed = null; +let ranCount = 0; for (const [i, s] of stages.entries()) { console.log(''); console.log(HR); console.log(`[${i + 1}/${stages.length}] ${s.name}`); + if (s.skipIf) { + console.log(`SKIP: ${s.skipReason}`); + console.log(HR); + continue; + } console.log(s.blurb); console.log(HR); const result = spawnSync(s.cmd, s.args, { cwd: s.cwd, stdio: 'inherit' }); + ranCount += 1; if (result.status !== 0) { failed = { stage: s.name, status: result.status ?? 1 }; break; @@ -89,5 +108,7 @@ if (failed) { console.log(` ${failedStage.cmd} ${failedStage.args.join(' ')}`); process.exit(failed.status); } else { - console.log(`PASS: ${stages.length} stages, ${elapsed}s`); + const skipped = stages.length - ranCount; + const ranLabel = skipped > 0 ? `${ranCount} ran, ${skipped} skipped` : `${ranCount} stages`; + console.log(`PASS: ${ranLabel}, ${elapsed}s`); }