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
50 changes: 12 additions & 38 deletions .github/workflows/ci-superdoc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -120,46 +120,20 @@ jobs:
# tree (those are tracked under SD-2863 follow-up tickets).
run: pnpm --filter superdoc run check:jsdoc

- 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
- name: SuperDoc public interface check
# Single wrapper covering all SuperDoc public-surface gates:
# - typecheck-matrix.mjs (packs superdoc, runs every scenario)
# - deep-type-audit.mjs --strict-supported-root
# - package-shape-gate.mjs (publint + attw)
# - snapshot.mjs --all --check (super-editor / legacy / root)
# - check-root-classification-closure.mjs (SD-3212 A1b)
# All stages run from the same packed-and-installed fixture, so
# ordering matters: matrix produces the install; the rest reuse
# it. --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
# Local equivalent: `pnpm check:public:superdoc` (with the build
# stage included).
run: pnpm check:public-contract --skip-build

- name: Package shape gates
# External package-shape linters (publint + attw) running against
# the packed tarball. Catches manifest issues that the in-repo
# consumer matrix does not see: condition ordering, masquerading
# ESM, missing CDN files, unpublished `source` paths.
run: node tests/consumer-typecheck/package-shape-gate.mjs

- name: Public surface no-growth snapshots (SD-3176, SD-3212)
# Unified entry point for the three snapshot families:
# - super-editor-package: @superdoc/super-editor package.json#exports keys
# - legacy: resolved exports for superdoc/* legacy subpaths
# - root: 4-source inventory (types.import, types.require, import,
# require) for the superdoc root entry. Cross-source mismatches
# are reported in the companion .md but are not blockers on their
# own.
# Runs after the matrix step so the packed-and-installed fixture is
# available. See tests/consumer-typecheck/snapshots/README.md.
run: node tests/consumer-typecheck/snapshot.mjs --all --check

- name: Root classification closure gate (SD-3212 PR A1b)
# Asserts the dependency-closure rule from the A1 classification:
# no supported-root or legacy-root exported root symbol may reference
# an internal-candidate root symbol in its public declared type.
# Catches the failure class behind Phase 4a's 31-failure dry-run.
run: node tests/consumer-typecheck/check-root-classification-closure.mjs
run: pnpm check:public:superdoc --skip-build

unit-tests:
needs: build
Expand Down
27 changes: 7 additions & 20 deletions .github/workflows/release-stable.yml
Original file line number Diff line number Diff line change
Expand Up @@ -114,26 +114,13 @@ jobs:
# `latest` tag, so a regression that bypassed PR CI would otherwise
# ship to every consumer pinned to `^1` or `latest`. Run identical
# checks here so the stable lane cannot silently relax the gate.
- name: Consumer typecheck (matrix)
run: |
cd tests/consumer-typecheck
node typecheck-matrix.mjs

- name: Deep public-type audit (supported-root strict, SD-3213e)
# Single invocation: broad inventory + supported-root strict gate.
# Same gate as PR CI. Catches releases that bypass PR CI.
run: node tests/consumer-typecheck/deep-type-audit.mjs --strict-supported-root

- name: Package shape gates
run: node tests/consumer-typecheck/package-shape-gate.mjs

- name: Public surface no-growth snapshots (SD-3176, SD-3212)
# Unified entry point for all three snapshot families
# (super-editor-package, legacy, root). Same gate as PR CI.
run: node tests/consumer-typecheck/snapshot.mjs --all --check

- name: Root classification closure gate (SD-3212 PR A1b)
run: node tests/consumer-typecheck/check-root-classification-closure.mjs
- name: SuperDoc public interface check
# Wraps typecheck-matrix, deep-type-audit, package-shape-gate,
# snapshot --all --check, and check-root-classification-closure.
# Same coverage as PR CI; runs before stable release so any
# bypass path (manual republish, hotfix) still catches
# regressions. --skip-build because Build above already ran.
run: pnpm check:public:superdoc --skip-build

- name: Release stable packages (orchestrator)
id: stable_release
Expand Down
33 changes: 7 additions & 26 deletions .github/workflows/release-superdoc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -128,34 +128,15 @@ jobs:
- name: Build packages
run: pnpm run build

# Public-type contract gates: same gates as PR CI (ci-superdoc.yml).
# Public-type contract gate: same coverage as PR CI (ci-superdoc.yml).
# Runs before publishing so a release cannot ship a regression that
# bypassed PR CI (manual republish, hotfix branch, recovery flow).
- name: Consumer typecheck (matrix)
run: |
cd tests/consumer-typecheck
node typecheck-matrix.mjs

- name: Deep public-type audit (supported-root strict, SD-3213e)
# Same gate as PR CI. One invocation prints the broad inventory
# AND runs the supported-root strict gate against the committed
# allowlist. Catches releases that bypass PR CI.
run: node tests/consumer-typecheck/deep-type-audit.mjs --strict-supported-root

- name: Package shape gates
# External package-shape linters (publint + attw) running against
# the packed tarball. Same step as PR CI.
run: node tests/consumer-typecheck/package-shape-gate.mjs

- name: Public surface no-growth snapshots (SD-3176, SD-3212)
# Same gate as PR CI. Catches releases that bypass PR CI.
# Runs the unified entry point for all three snapshot families
# (super-editor-package, legacy, root).
run: node tests/consumer-typecheck/snapshot.mjs --all --check

- name: Root classification closure gate (SD-3212 PR A1b)
# Same gate as PR CI. Catches releases that bypass PR CI.
run: node tests/consumer-typecheck/check-root-classification-closure.mjs
- name: SuperDoc public interface check
# Wraps typecheck-matrix, deep-type-audit, package-shape-gate,
# snapshot --all --check, and check-root-classification-closure.
# --skip-build because the Build step above already ran
# `pnpm run build` (which includes build:superdoc).
run: pnpm check:public:superdoc --skip-build

# PR preview: publish with pr-<number> dist-tag
- name: Publish PR preview
Expand Down
8 changes: 4 additions & 4 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,10 @@ Do not hand-edit `COMMAND_CATALOG`, `OPERATION_MEMBER_PATH_MAP`, `OPERATION_REFE
- `pnpm build` - build all packages
- `pnpm test` - unit tests
- `pnpm dev` - dev server from `examples/`
- `pnpm check:types` - raw TS compile across all referenced projects (alias of `pnpm run type-check`). Does NOT run the public-interface chain.
- `pnpm check:public` - **canonical pre-merge command for typed public surfaces.** Validates both `superdoc` (vite build + postbuild chain + consumer typecheck matrix + deep-type audit) and Document API (contract parity + output staleness + examples + overview). ~5 min. Non-mutating. Combines `check:public:superdoc` + `check:public:docapi`.
- `pnpm check:public:superdoc` - SuperDoc public package surface only (alias of `check:public-contract`).
- `pnpm check:public:docapi` - Document API public surface only (alias of `docapi:check`). Requires generated artifacts to be current; if it fails on staleness, run `pnpm generate:docapi`.
- `pnpm check:types` - raw TS compile across all referenced projects (`tsc -b tsconfig.references.json`). Does NOT run the public-interface chain. Legacy alias: `pnpm run type-check`.
- `pnpm check:public` - **canonical pre-merge command for typed public surfaces.** Validates both `superdoc` (vite build + postbuild chain + consumer typecheck matrix + deep-type audit + package-shape + snapshots + classification closure) and Document API (contract parity + output staleness + examples + overview). ~5 min. Non-mutating. Combines `check:public:superdoc` + `check:public:docapi`.
- `pnpm check:public:superdoc` - SuperDoc public package surface only. Wraps six stages: build + matrix + deep-type audit + package-shape + snapshots + closure. Legacy alias: `pnpm run check:public-contract`.
- `pnpm check:public:docapi` - Document API public surface only. Requires generated artifacts to be current; if it fails on missing files, run `pnpm generate:docapi`. Legacy alias: `pnpm run docapi:check`.
- `pnpm generate:docapi` - regenerate Document API outputs after editing the contract (alias of `docapi:sync`). Writes gitignored generated artifacts under the Document API package. Run before `check:public:docapi` if it fails on missing files.
- `pnpm generate:all` - regenerate schemas, SDK clients, tool catalogs, reference docs.
- `pnpm report:public:superdoc` - print public-contract tier metadata (supported / legacy / asset / deprecated). Read-only, not a gate. Source of truth: `packages/superdoc/scripts/type-surface.config.cjs`.
Expand Down
22 changes: 11 additions & 11 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
"test:behavior:headed": "pnpm --filter @superdoc-testing/behavior test:headed",
"test:behavior:ui": "pnpm --filter @superdoc-testing/behavior test:ui",
"test:behavior:html": "pnpm --filter @superdoc-testing/behavior test:html",
"type-check": "tsc -b tsconfig.references.json",
"type-check": "pnpm run check:types",
"type-check:force": "tsc -b --force tsconfig.references.json",
"rebuild:types": "pnpm --workspace-concurrency=1 run --filter=@superdoc/common --filter=@superdoc/word-layout --filter=@superdoc/contracts --filter=@superdoc/dom-contract --filter=@superdoc/layout-resolved --filter=@superdoc/geometry-utils --filter=@superdoc/style-engine --filter=@superdoc/pm-adapter --filter=@superdoc/measuring-dom --filter=@superdoc/layout-engine --filter=@superdoc/layout-bridge build && pnpm --filter=@superdoc/painter-dom build",
"validate:commands": "node scripts/validate-command-types.mjs",
Expand Down Expand Up @@ -71,23 +71,23 @@
"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",
"report:public-contract": "node scripts/report-public-contract.mjs",
"check:public-contract": "pnpm run check:public:superdoc",
"report:public-contract": "pnpm run report:public:superdoc",
"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",
"manual-clean-tag": "bash scripts/manual-clean-tag.sh",
"generate:all": "node scripts/generate-all.mjs",
"docapi:all": "pnpm run generate:all && pnpm exec tsc -b packages/document-api && pnpm --prefix apps/cli run build && pnpm --prefix packages/sdk run build:node",
"docapi:sync": "pnpm exec tsx packages/document-api/scripts/generate-contract-outputs.ts",
"docapi:check": "pnpm exec tsx packages/document-api/scripts/check-contract-parity.ts && pnpm exec tsx packages/document-api/scripts/check-contract-outputs.ts && pnpm exec tsx packages/document-api/scripts/check-examples.ts && pnpm exec tsx packages/document-api/scripts/check-overview-alignment.ts",
"docapi:sync:check": "pnpm run docapi:sync && pnpm run docapi:check",
"check:types": "pnpm run type-check",
"check:public:superdoc": "pnpm run check:public-contract",
"check:public:docapi": "pnpm run docapi:check",
"docapi:sync": "pnpm run generate:docapi",
"docapi:check": "pnpm run check:public:docapi",
"docapi:sync:check": "pnpm run generate:docapi && pnpm run check:public:docapi",
"check:types": "tsc -b tsconfig.references.json",
"check:public:superdoc": "node scripts/check-public-contract.mjs",
"check:public:docapi": "pnpm exec tsx packages/document-api/scripts/check-contract-parity.ts && pnpm exec tsx packages/document-api/scripts/check-contract-outputs.ts && pnpm exec tsx packages/document-api/scripts/check-examples.ts && pnpm exec tsx packages/document-api/scripts/check-overview-alignment.ts",
"check:public": "pnpm run check:public:superdoc && pnpm run check:public:docapi",
"generate:docapi": "pnpm run docapi:sync",
"report:public:superdoc": "pnpm run report:public-contract",
"generate:docapi": "pnpm exec tsx packages/document-api/scripts/generate-contract-outputs.ts",
"report:public:superdoc": "node scripts/report-public-contract.mjs",
"test:cli": "pnpm --prefix apps/cli run test",
"cli:prepare": "pnpm run test:cli && pnpm --prefix apps/cli run build:prepublish",
"cli:publish:raw": "pnpm run cli:prepare && pnpm --prefix apps/cli run publish:platforms",
Expand Down
34 changes: 21 additions & 13 deletions packages/superdoc/scripts/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,11 +121,16 @@ what an actual consumer would see — not the workspace source.
| `package-shape-gate.mjs` | External package-shape linters (publint + attw) against the packed tarball. | Catches condition ordering, masquerading exports, missing field declarations. |
| `check-root-classification-closure.mjs` | Asserts no `supported-root` or `legacy-root` export references an `internal-candidate` symbol in its public declared type. | Closure rule from SD-3212. |

`check:public:superdoc` runs `typecheck-matrix` and `deep-type-audit`
directly. `package-shape-gate`, `snapshot --all --check`, and
`check-root-classification-closure` currently run as separate CI steps —
folding them into `check:public:superdoc` is tracked as a follow-up so
release workflows can call one command without losing coverage.
`check:public:superdoc` runs all six in order. `typecheck-matrix` packs
`superdoc.tgz` and installs it into the consumer fixture. The rest
reuse what matrix produced: `deep-type-audit`, `snapshot --all
--check`, and `check-root-classification-closure` read from the
installed fixture in `node_modules/superdoc/`; `package-shape-gate`
runs `publint` / `attw` against the packed tarball at
`packages/superdoc/superdoc.tgz` directly. CI (`ci-superdoc.yml`) and
release workflows (`release-superdoc.yml`, `release-stable.yml`) call
`pnpm check:public:superdoc --skip-build` directly — no duplicated step
lists.

---

Expand Down Expand Up @@ -178,14 +183,17 @@ packages/document-api/src/contract/operation-definitions.ts

## CI vs local

- **`ci-superdoc.yml`** runs `pnpm check:public-contract --skip-build` after
its own Build step. This is the single command for the SuperDoc public
surface in CI.
- **`release-superdoc.yml`** currently runs the consumer-typecheck matrix,
deep-type audit, package-shape gate, snapshot check, and classification
closure as separate steps. Migrating to `check:public:superdoc` once that
command covers all the gates is tracked separately.
All three SuperDoc lanes call the same wrapper:

- **`ci-superdoc.yml`** (PR CI) — `pnpm check:public:superdoc --skip-build` after the Build step.
- **`release-superdoc.yml`** (preview/dev release) — same.
- **`release-stable.yml`** (stable release) — same.

The wrapper enforces every SuperDoc public-surface gate in one place.
A change to the validation chain (adding a stage, reordering, renaming)
lands in `scripts/check-public-contract.mjs` and propagates to all
three lanes automatically.

Local pre-commit: just run `pnpm check:public`. If anything fails, the
failure message tells you which script and (for `check:public:docapi`)
which command to run to regenerate stale artifacts.
which command to run to regenerate missing or stale artifacts.
51 changes: 46 additions & 5 deletions scripts/check-public-contract.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,32 @@
* 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`).
* 4. package-shape - publint + attw against the packed manifest
* (reuses the install from stage 2).
* 5. snapshots - super-editor / legacy / root no-growth
* snapshots (reuses the install).
* 6. closure - root-classification closure gate:
* no supported-root/legacy-root export
* references an internal-candidate type.
*
* 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.
* Matrix runs BEFORE stages 3-6 on purpose: it packs `superdoc.tgz`
* and installs the tarball into the consumer fixture once. Stages 3,
* 5, and 6 (deep-type-audit, snapshots, closure) reuse the installed
* fixture; stage 4 (package-shape-gate) reuses the packed tarball
* directly. Without this ordering each downstream stage would
* `--pack` separately and multiply the work.
*
* Local usage:
* pnpm check:public-contract
* pnpm check:public (umbrella, runs SuperDoc + Document API)
* pnpm check:public:superdoc (SuperDoc only, this script)
*
* CI usage (Build step already ran):
* pnpm check:public-contract --skip-build
* pnpm check:public:superdoc --skip-build
*
* SD-3256 Phase 1 (initial wrapper) / SD-673 Phase 1 (CI wiring).
* Extended in the typecheck-wrapper consolidation PR to subsume the
* package-shape / snapshots / closure steps that release-superdoc.yml,
* release-stable.yml, and ci-superdoc.yml previously ran separately.
*/

import { spawnSync } from 'node:child_process';
Expand Down Expand Up @@ -70,6 +84,33 @@ const stages = [
'Strict gate on the supported-root public surface (must be 0 findings). ' +
'Reuses the install produced by typecheck-matrix.',
},
{
name: 'package-shape-gate',
cwd: resolve(REPO_ROOT, 'tests/consumer-typecheck'),
cmd: 'node',
args: ['package-shape-gate.mjs'],
blurb:
'External npm-package linters (publint + attw) against the packed manifest. ' +
'Reuses the tarball produced by typecheck-matrix (not the installed fixture).',
},
{
name: 'snapshot --all --check',
cwd: resolve(REPO_ROOT, 'tests/consumer-typecheck'),
cmd: 'node',
args: ['snapshot.mjs', '--all', '--check'],
blurb:
'No-growth snapshots for super-editor / legacy / root export inventories. ' +
'Run with `node snapshot.mjs --family <name> --write` to regenerate intentionally.',
},
{
name: 'check-root-classification-closure',
cwd: resolve(REPO_ROOT, 'tests/consumer-typecheck'),
cmd: 'node',
args: ['check-root-classification-closure.mjs'],
blurb:
'Closure gate: no supported-root or legacy-root export references an ' +
'internal-candidate type in its public declared shape (SD-3212 A1b).',
},
];

const HR = '='.repeat(72);
Expand Down
Loading