From 1cb6da954095f346f717a827a687024704f46856 Mon Sep 17 00:00:00 2001 From: Adrian Elton-Browning Date: Tue, 6 Oct 2026 14:13:28 +0100 Subject: [PATCH 1/3] docs: one CI/CD path from drift check to gated release (#10) --- .bumpy/ci-cd-integration.md | 5 ++ README.md | 2 + packages/mdcode/README.md | 97 +++++++++++++++++++++++++++++++++++++ 3 files changed, 104 insertions(+) create mode 100644 .bumpy/ci-cd-integration.md diff --git a/.bumpy/ci-cd-integration.md b/.bumpy/ci-cd-integration.md new file mode 100644 index 0000000..00f55f5 --- /dev/null +++ b/.bumpy/ci-cd-integration.md @@ -0,0 +1,5 @@ +--- +mdcode-ts: patch +--- + +Added a CI/CD Integration section to the README. It puts the docs-sync check, the runnable-snippet check and release gating in one sequence, and shows how to publish only after they pass, either with `prepublishOnly` or with a GitHub Actions publish job that needs a checks job. diff --git a/README.md b/README.md index 503cdfd..a11c34d 100644 --- a/README.md +++ b/README.md @@ -71,6 +71,8 @@ To keep several documents in sync without repeating their paths, list them in `m To check that snippets actually run, mark them `runnable=true` and copy [`examples/ci/validate-snippets.mjs`](examples/ci/validate-snippets.mjs). It extracts only those blocks into a temporary workspace, runs your test or lint command there, and fails CI naming the block that broke. See [Validating Runnable Snippets in CI](packages/mdcode/README.md#validating-runnable-snippets-in-ci). +To publish only after both checks pass, see [CI/CD Integration](packages/mdcode/README.md#cicd-integration). It shows the three stages in order and how to gate a release on them with `prepublishOnly` or a GitHub Actions publish job. + ## Commands | Command | What it does | diff --git a/packages/mdcode/README.md b/packages/mdcode/README.md index 82e73c5..a2c55e3 100644 --- a/packages/mdcode/README.md +++ b/packages/mdcode/README.md @@ -1346,6 +1346,103 @@ jobs: --- +## CI/CD Integration + +mdcode fits into CI in three stages. Each one stands alone, and each builds on the one before: + +1. **Check the docs are in sync.** Fail when a block has drifted from the `file=` it points at. See + [Checking Docs in CI](#checking-docs-in-ci), or [Project Configuration](#locally-and-in-ci) to + list the documents once in `mdcode.config.json`. +2. **Run the runnable snippets.** Fail when a block written in the Markdown and marked + `runnable=true` no longer runs. See [Validating Runnable Snippets in CI](#validating-runnable-snippets-in-ci). +3. **Gate the release on both.** Publish only after your tests and both docs checks pass, as below. + +Stages 1 and 2 give you two npm scripts, `docs:check` and `docs:snippets`. Run them on every pull +request so drift fails the change that caused it, then run them again before publishing so nothing +stale reaches npm. + +### Gating a Release + +The smallest gate is `prepublishOnly`. `npm publish` runs it first and stops without publishing if +it fails: + +```json +{ + "scripts": { + "test": "node --test", + "docs:check": "node scripts/check-docs-sync.mjs README.md docs/guide.md", + "docs:snippets": "node scripts/validate-snippets.mjs README.md docs/guide.md -- node {file}", + "prepublishOnly": "npm test && npm run docs:check && npm run docs:snippets" + }, + "devDependencies": { + "mdcode-ts": "^0.1.0" + } +} +``` + +When GitHub Actions publishes, put the checks in their own job and make the publish job need it. +Each check is a named step, so a failed run names the gate that stopped it, and the publish job never +starts. This workflow publishes when you push a `v*` tag, using +[npm trusted publishing](https://docs.npmjs.com/trusted-publishers/), so no npm token is stored: + +```yaml +name: Release + +on: + push: + tags: ['v*'] + +permissions: + contents: read + +jobs: + checks: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 22 + - run: npm ci + - name: 'Gate: tests' + run: npm test + - name: 'Gate: docs in sync' + run: npm run docs:check + - name: 'Gate: runnable snippets' + run: npm run docs:snippets + + publish: + needs: checks + runs-on: ubuntu-latest + environment: npm + permissions: + contents: read + id-token: write + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 22 + registry-url: https://registry.npmjs.org + # Trusted publishing needs npm 11.5.1 or later; Node 22 ships npm 10 + - run: npm install -g npm@latest + - run: npm ci + - run: npm publish +``` + +Before the first run, add a trusted publisher for your package on npmjs.com, naming this repository, +the workflow file and the `npm` environment. To publish with a token instead, store it as a +repository secret, drop `id-token: write`, and pass it to `npm publish` as `NODE_AUTH_TOKEN`. + +Only the `publish` job can mint an npm credential. The `checks` job runs the snippets, which is +running code from the Markdown, so it gets a read-only token and no `id-token`. If you also keep the +`prepublishOnly` gate, `npm publish` runs the checks a second time, which is harmless. + +mdcode-ts gates its own releases the same way; see its +[RELEASING.md](https://github.com/adrianbrowning/mdcode-ts/blob/main/RELEASING.md). + +--- + ## CLI Flags Reference All commands support these common flags: From 7497eb4ed444d153a46de62500ccf9d4a85068b3 Mon Sep 17 00:00:00 2001 From: Adrian Elton-Browning Date: Tue, 6 Oct 2026 14:15:15 +0100 Subject: [PATCH 2/3] docs(ci-cd): use prepublishOnly or the Actions gate, not both --- packages/mdcode/README.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/mdcode/README.md b/packages/mdcode/README.md index a2c55e3..018d15d 100644 --- a/packages/mdcode/README.md +++ b/packages/mdcode/README.md @@ -1363,8 +1363,8 @@ stale reaches npm. ### Gating a Release -The smallest gate is `prepublishOnly`. `npm publish` runs it first and stops without publishing if -it fails: +When you publish from your own machine, the smallest gate is `prepublishOnly`. `npm publish` runs it +first and stops without publishing if it fails: ```json { @@ -1435,8 +1435,9 @@ the workflow file and the `npm` environment. To publish with a token instead, st repository secret, drop `id-token: write`, and pass it to `npm publish` as `NODE_AUTH_TOKEN`. Only the `publish` job can mint an npm credential. The `checks` job runs the snippets, which is -running code from the Markdown, so it gets a read-only token and no `id-token`. If you also keep the -`prepublishOnly` gate, `npm publish` runs the checks a second time, which is harmless. +running code from the Markdown, so it gets a read-only token and no `id-token`. Use one gate or the +other, not both: `npm publish` runs `prepublishOnly`, so a `prepublishOnly` that runs `docs:snippets` +would execute the snippets again inside the `publish` job, where they could mint the credential. mdcode-ts gates its own releases the same way; see its [RELEASING.md](https://github.com/adrianbrowning/mdcode-ts/blob/main/RELEASING.md). From 5954f9f0edb57cab77b47f5963f6fa1ad49a7f43 Mon Sep 17 00:00:00 2001 From: Adrian Elton-Browning Date: Tue, 6 Oct 2026 14:15:53 +0100 Subject: [PATCH 3/3] docs(ci-cd): publish job skips lifecycle scripts so snippets never run with id-token --- packages/mdcode/README.md | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/mdcode/README.md b/packages/mdcode/README.md index 018d15d..692ca94 100644 --- a/packages/mdcode/README.md +++ b/packages/mdcode/README.md @@ -1427,7 +1427,9 @@ jobs: # Trusted publishing needs npm 11.5.1 or later; Node 22 ships npm 10 - run: npm install -g npm@latest - run: npm ci - - run: npm publish + - run: npm run build --if-present + # The checks job already passed; skip prepublishOnly so snippets never run here + - run: npm publish --ignore-scripts ``` Before the first run, add a trusted publisher for your package on npmjs.com, naming this repository, @@ -1435,9 +1437,11 @@ the workflow file and the `npm` environment. To publish with a token instead, st repository secret, drop `id-token: write`, and pass it to `npm publish` as `NODE_AUTH_TOKEN`. Only the `publish` job can mint an npm credential. The `checks` job runs the snippets, which is -running code from the Markdown, so it gets a read-only token and no `id-token`. Use one gate or the -other, not both: `npm publish` runs `prepublishOnly`, so a `prepublishOnly` that runs `docs:snippets` -would execute the snippets again inside the `publish` job, where they could mint the credential. +running code from the Markdown, so it gets a read-only token and no `id-token`. That is why the +`publish` job builds explicitly and publishes with `--ignore-scripts`: a plain `npm publish` would run +a `prepublishOnly` like the one above, executing the snippets again in the job that can mint the +credential. `--ignore-scripts` also skips `prepare` and `prepack`, so run any step they did as its +own step before publishing. mdcode-ts gates its own releases the same way; see its [RELEASING.md](https://github.com/adrianbrowning/mdcode-ts/blob/main/RELEASING.md).