Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 14 additions & 22 deletions .github/workflows/ci-superdoc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
77 changes: 49 additions & 28 deletions scripts/check-public-contract.mjs
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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',
Expand All @@ -43,34 +49,47 @@ 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.',
},
];

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;
Expand All @@ -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`);
}
Loading