From 4468eb55676baf4201b8da44d9cd40c36305458c Mon Sep 17 00:00:00 2001 From: Caio Pizzol Date: Fri, 22 May 2026 10:51:53 -0300 Subject: [PATCH] feat(scripts): add check:public-contract wrapper command (SD-3256 Phase 1) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Today, validating the published superdoc public type surface requires three separate invocations across different working directories: pnpm run build:superdoc cd tests/consumer-typecheck && node deep-type-audit.mjs --pack --strict-supported-root cd tests/consumer-typecheck && node typecheck-matrix.mjs This wrapper orchestrates them as one command: pnpm check:public-contract Behavior: - Runs each stage in order, prints a section header per stage - Fails fast on the first failure so the real error stays visible - Prints a PASS / FAIL summary with elapsed time at the end - On FAIL, prints the exact command to re-run the failing stage for iteration - Zero behavior change for the validators themselves; pure DX Phase 1 of the SD-3256 umbrella (public-contract consolidation + ./super-editor facade curation). Phase 1 deliberately adds no new enforcement and no config changes; it just collapses three invocations into one obvious entry point. Phases 2-4 (tier metadata, ./super-editor facade, ratchet) are tracked separately under SD-3256 and need design / team alignment before implementation. Verified locally: pnpm check:public-contract → PASS (3 stages, 189s) on post-SD-2980 main. Each stage's exit code is preserved. --- AGENTS.md | 1 + package.json | 1 + scripts/check-public-contract.mjs | 93 +++++++++++++++++++++++++++++++ 3 files changed, 95 insertions(+) create mode 100755 scripts/check-public-contract.mjs diff --git a/AGENTS.md b/AGENTS.md index 831c5438d0..0601db803c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -72,6 +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. ## Testing diff --git a/package.json b/package.json index 6b8f4369cf..447d1699fa 100644 --- a/package.json +++ b/package.json @@ -71,6 +71,7 @@ "watch": "pnpm --prefix packages/superdoc run watch:es", "check:all": "pnpm run format && pnpm run lint:fix && pnpm --prefix packages/super-editor run types:build && pnpm run test", "check:examples-demos": "bun scripts/validate-examples-demos.ts", + "check:public-contract": "node scripts/check-public-contract.mjs", "local:publish": "pnpm --prefix packages/superdoc version prerelease --preid=local && pnpm --prefix packages/superdoc publish --registry http://localhost:4873", "update-preset-geometry": "ROOT=$(pwd) && cd ../superdoc-devtools/preset-geometry && pnpm run build && cp ./dist/index.js ./dist/index.js.map ./dist/index.d.ts \"$ROOT/packages/preset-geometry/\"", "manual-tag": "bash scripts/manual-tag.sh", diff --git a/scripts/check-public-contract.mjs b/scripts/check-public-contract.mjs new file mode 100755 index 0000000000..9cd3098b2b --- /dev/null +++ b/scripts/check-public-contract.mjs @@ -0,0 +1,93 @@ +#!/usr/bin/env node +/** + * SD-3256 Phase 1: single command to validate the public type contract. + * + * 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: + * + * 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) + * + * 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: + * pnpm check:public-contract + * + * Tracking: SD-3256 (umbrella). Phase 1. + */ + +import { spawnSync } from 'node:child_process'; +import { dirname, resolve } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const REPO_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..'); + +const stages = [ + { + name: 'build:superdoc', + cwd: REPO_ROOT, + cmd: 'pnpm', + args: ['run', 'build:superdoc'], + blurb: + 'Build dist + run postbuild validators (audit-bundle, audit-declarations, ' + + 'check-export-coverage, verify-public-facade-emit, ensure-types, ...).', + }, + { + name: 'deep-type-audit --strict-supported-root', + 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).', + }, + { + name: 'typecheck-matrix', + cwd: resolve(REPO_ROOT, 'tests/consumer-typecheck'), + cmd: 'node', + args: ['typecheck-matrix.mjs'], + blurb: 'Consumer-perspective scenarios against the packed tarball.', + }, +]; + +const HR = '='.repeat(72); +const start = Date.now(); + +let failed = null; +for (const [i, s] of stages.entries()) { + console.log(''); + console.log(HR); + console.log(`[${i + 1}/${stages.length}] ${s.name}`); + console.log(s.blurb); + console.log(HR); + const result = spawnSync(s.cmd, s.args, { cwd: s.cwd, stdio: 'inherit' }); + if (result.status !== 0) { + failed = { stage: s.name, status: result.status ?? 1 }; + break; + } +} + +const elapsed = ((Date.now() - start) / 1000).toFixed(1); +console.log(''); +console.log(HR); +if (failed) { + console.log(`FAIL: stage "${failed.stage}" exited ${failed.status} (after ${elapsed}s)`); + console.log(''); + console.log('Re-run the failing stage directly to iterate:'); + const failedStage = stages.find((s) => s.name === failed.stage); + console.log(` cd ${failedStage.cwd}`); + console.log(` ${failedStage.cmd} ${failedStage.args.join(' ')}`); + process.exit(failed.status); +} else { + console.log(`PASS: ${stages.length} stages, ${elapsed}s`); +}