diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..dd39aa4 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,73 @@ +name: Publish package + +# Publishes a workspace package to npm when a matching tag is pushed. +# +# Tag conventions (also documented in docs/releasing.md): +# - docs-invariants-v → publishes @dash0hq/docs-invariants +# +# Prerequisites: +# - `DASH0_NPMJS_PUBLISH_TOKEN` repository secret, an npm automation token with publish access on +# the @dash0hq scope. +# - The corresponding package must have `"private": false` and a `publishConfig` block. + +on: + push: + tags: + - "docs-invariants-v[0-9]+.[0-9]+.[0-9]+" + - "docs-invariants-v[0-9]+.[0-9]+.[0-9]+-*" + +jobs: + publish-docs-invariants: + if: startsWith(github.ref_name, 'docs-invariants-v') + runs-on: ubuntu-latest + timeout-minutes: 10 + + # `id-token: write` is required for npm provenance attestations, which prove the package was + # built from this repository at this commit. `contents: read` is the default; explicit here so + # the whole permission set stays visible. + permissions: + contents: read + id-token: write + + steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + + - name: Install Node.js + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version-file: .nvmrc + registry-url: https://registry.npmjs.org + + - name: Install pnpm + uses: pnpm/action-setup@8912a9102ac27614460f54aedde9e1e7f9aec20d # v6.0.5 + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Verify tag matches package version + env: + TAG: ${{ github.ref_name }} + run: | + # Strip the `docs-invariants-v` prefix from the tag to derive the expected version. + expected="${TAG#docs-invariants-v}" + actual=$(node -p "require('./packages/docs-invariants/package.json').version") + if [ "$expected" != "$actual" ]; then + echo "Tag version ($expected) does not match package.json version ($actual). Aborting." >&2 + exit 1 + fi + echo "Tag version matches package.json ($actual)." + + - name: Typecheck all packages + run: pnpm run typecheck + + - name: Test all packages + run: pnpm run test + + - name: Publish @dash0hq/docs-invariants to npm + env: + NODE_AUTH_TOKEN: ${{ secrets.DASH0_NPMJS_PUBLISH_TOKEN }} + NPM_CONFIG_PROVENANCE: "true" + run: pnpm --filter @dash0hq/docs-invariants publish --access public --no-git-checks diff --git a/docs/releasing.md b/docs/releasing.md index aa230cd..3c951df 100644 --- a/docs/releasing.md +++ b/docs/releasing.md @@ -29,21 +29,34 @@ Semver rules for `action.yml`: ### 2. `@dash0hq/docs-invariants` **Consumed by** `dash0-website`'s own CI via npm. -Not published in the initial scaffold; the process below applies once the package is unmasked (`"private": false`). **Versioning.** Semver on the `version` field in `packages/docs-invariants/package.json`. +Package tags are namespaced (`docs-invariants-v`) so they don't collide with the composite action's tags on the same repo. -**When to publish.** Whenever the CLI or programmatic API surface stabilises to the point that downstream can rely on it, and whenever a bug fix or behaviour change wants to reach consumers. +**When to publish.** Whenever the API surface stabilises to the point that downstream can rely on it, and whenever a bug fix or behaviour change wants to reach consumers. -**How to publish** (once the workflow lands): +**How to publish** (once the tag lands): -1. Bump `version` in `packages/docs-invariants/package.json` on `main`. -2. Push a tag `docs-invariants-v` (namespaced so it doesn't collide with the composite action's tags). -3. CI runs `pnpm publish --filter @dash0hq/docs-invariants` from the tagged commit, authenticated via the `DASH0_NPMJS_PUBLISH_TOKEN` repository secret. -4. Update `dash0-website`'s dependency to the new version (separate PR in that repo). +1. Bump `version` in `packages/docs-invariants/package.json` on `main` via a normal PR. + The publish workflow refuses to publish if the tag version does not match the package.json version. +2. After merge, tag from `main`: + ```bash + git tag -a docs-invariants-v -m "Release @dash0hq/docs-invariants v" + git push origin docs-invariants-v + ``` +3. `.github/workflows/publish.yml` fires on the tag push. + It reinstalls, verifies the version match, runs typecheck + tests, then `pnpm --filter @dash0hq/docs-invariants publish --access public --no-git-checks` with npm provenance enabled. +4. Update `dash0-website`'s dependency to the new version in a separate PR in that repo. -The `DASH0_NPMJS_PUBLISH_TOKEN` secret is an org-scoped npm automation token; it must be present on this repository before the first release workflow run. -Rotate it whenever a maintainer with access to the token leaves the org. +**Prerequisites** the publish workflow expects on this repository: + +- **`DASH0_NPMJS_PUBLISH_TOKEN`** repository secret — an npm automation token with publish access on the `@dash0hq` scope. + Rotate it whenever a maintainer with access to the token leaves the org. +- The npm `@dash0hq` scope must exist and grant the token publish permission. +- The workflow uses `id-token: write` to attach provenance attestations, so npm can verify the tarball was built from this exact commit. + +**Pre-release tags** (e.g. `docs-invariants-v0.1.0-alpha.1`) are supported: the workflow's tag pattern matches both plain semver and pre-release suffixes. +Use pre-release tags to iterate before cutting a stable release. ## CHANGELOG diff --git a/packages/docs-invariants/LICENSE b/packages/docs-invariants/LICENSE new file mode 120000 index 0000000..30cff74 --- /dev/null +++ b/packages/docs-invariants/LICENSE @@ -0,0 +1 @@ +../../LICENSE \ No newline at end of file diff --git a/packages/docs-invariants/package.json b/packages/docs-invariants/package.json index 464f726..761858c 100644 --- a/packages/docs-invariants/package.json +++ b/packages/docs-invariants/package.json @@ -1,10 +1,32 @@ { "name": "@dash0hq/docs-invariants", - "version": "0.0.0", - "private": true, + "version": "0.0.1", "type": "module", "description": "Invariants library for dash0-website's docs tree: redirect uniqueness, dangling-URL detection, deletion-without-redirect check. Consumed by both dash0hq/sync-docs-action and dash0hq/dash0-website.", "license": "Apache-2.0", + "repository": { + "type": "git", + "url": "https://github.com/dash0hq/sync-docs-action.git", + "directory": "packages/docs-invariants" + }, + "homepage": "https://github.com/dash0hq/sync-docs-action/tree/main/packages/docs-invariants", + "bugs": { + "url": "https://github.com/dash0hq/sync-docs-action/issues" + }, + "publishConfig": { + "access": "public", + "provenance": true + }, + "files": [ + "src/**/*.ts", + "!src/**/*.test.ts", + "README.md", + "LICENSE" + ], + "main": "src/index.ts", + "exports": { + ".": "./src/index.ts" + }, "engines": { "node": ">=24.0.0" },