From 59eb3b76c8ec0659fbf02ea71005cc5a48a57b50 Mon Sep 17 00:00:00 2001 From: merencia Date: Mon, 22 Jun 2026 17:00:29 -0300 Subject: [PATCH] ci: automate releases with release-please, OIDC publish and PR-title lint Mirror the node-cron release setup, adapted to this repo: - release-please maintains a release PR (version + CHANGELOG) from Conventional Commits on master; merging it tags vX.Y.Z, creates the GitHub Release and calls the publish workflow. - publish.yml replaces release.yml: OIDC trusted publishing (no NPM_TOKEN), triggered both manually (workflow_dispatch) and automatically by release-please (workflow_call). - pr-title-lint enforces Conventional Commit PR titles (squash-merge). - include-component-in-tag=false so tags are vX.Y.Z. - Bootstrap: manifest baseline is 1.0.0 (the published version); release-as 2.0.0 forces the first release to cut the unpublished rewrite as 2.0.0. --- .github/workflows/pr-title-lint.yml | 39 ++++++++++++++++++ .github/workflows/publish.yml | 61 ++++++++++++++++++++++++++++ .github/workflows/release-please.yml | 40 ++++++++++++++++++ .github/workflows/release.yml | 27 ------------ .release-please-manifest.json | 3 ++ release-please-config.json | 25 ++++++++++++ 6 files changed, 168 insertions(+), 27 deletions(-) create mode 100644 .github/workflows/pr-title-lint.yml create mode 100644 .github/workflows/publish.yml create mode 100644 .github/workflows/release-please.yml delete mode 100644 .github/workflows/release.yml create mode 100644 .release-please-manifest.json create mode 100644 release-please-config.json diff --git a/.github/workflows/pr-title-lint.yml b/.github/workflows/pr-title-lint.yml new file mode 100644 index 0000000..4bd55c7 --- /dev/null +++ b/.github/workflows/pr-title-lint.yml @@ -0,0 +1,39 @@ +name: Lint PR Title + +# PRs are squash-merged, so the PR title becomes the commit message on master. +# Enforcing Conventional Commits here keeps the history clean and lets +# release-please categorise changes into the changelog correctly. +on: + pull_request_target: + types: + - opened + - edited + - synchronize + - reopened + +permissions: + pull-requests: write + +jobs: + validate: + runs-on: ubuntu-latest + steps: + - uses: amannn/action-semantic-pull-request@v5 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + # Keep in sync with the changelog-sections in release-please-config.json. + types: | + feat + fix + perf + refactor + revert + docs + chore + ci + test + build + style + # Scopes are allowed (e.g. feat(parser)) but not required. + requireScope: false diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..e624172 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,61 @@ +name: Publish to npm + +on: + # Manual publish, with a choice of dist-tag (for prereleases etc.). + workflow_dispatch: + inputs: + dist_tag: + description: 'npm dist-tag to publish under' + type: choice + default: latest + required: true + options: + - latest + - beta + - next + + # Called by release-please.yml right after it cuts a release, so merging the + # `chore(main): release X.Y.Z` PR publishes automatically. A reusable-workflow + # call (rather than `on: release`) is used because a Release created with the + # default GITHUB_TOKEN does not trigger event-based workflows. + workflow_call: + inputs: + dist_tag: + description: 'npm dist-tag to publish under' + type: string + default: latest + required: false + +jobs: + publish: + runs-on: ubuntu-latest + + # OIDC trusted publishing: the id-token is exchanged with npm at publish + # time, so no NPM_TOKEN secret is needed. It also enables provenance. + permissions: + id-token: write + contents: read + + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: 22 + registry-url: https://registry.npmjs.org + cache: npm + + # Trusted publishing requires npm >= 11.5.1; node 22 ships an older npm. + - name: Upgrade npm + run: npm install -g npm@latest + + - run: npm ci + - run: npm run lint + - run: npm run typecheck + - run: npm run test:coverage + - run: npm run build + + - name: Publish to npm + # `inputs.dist_tag` comes from workflow_dispatch (chosen) or workflow_call + # (defaults to latest). + run: npm publish --provenance --access public --tag "${{ inputs.dist_tag }}" diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml new file mode 100644 index 0000000..6c8e5c5 --- /dev/null +++ b/.github/workflows/release-please.yml @@ -0,0 +1,40 @@ +name: Release Please + +# Maintains a "release PR" that bumps the version and updates CHANGELOG.md from +# Conventional Commits as they land on master. Merging that PR creates the git +# tag (vX.Y.Z) and the GitHub Release, and then publishes to npm via the publish +# workflow (called below only when a release was actually created). +on: + push: + branches: + - master + +permissions: + contents: write + pull-requests: write + +jobs: + release-please: + runs-on: ubuntu-latest + outputs: + release_created: ${{ steps.release.outputs.release_created }} + steps: + - uses: googleapis/release-please-action@v4 + id: release + with: + # Reads release-please-config.json and .release-please-manifest.json + # from the repo root. + token: ${{ secrets.GITHUB_TOKEN }} + + # Runs only when the previous job just cut a release (i.e. the release PR was + # merged). Calls the publish workflow as a reusable workflow so npm publishing + # stays defined in one place (publish.yml) and keeps OIDC trusted publishing. + publish: + needs: release-please + if: ${{ needs.release-please.outputs.release_created == 'true' }} + permissions: + id-token: write + contents: read + uses: ./.github/workflows/publish.yml + with: + dist_tag: latest diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index 3177eda..0000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,27 +0,0 @@ -name: Release - -on: - release: - types: [published] - -jobs: - publish: - runs-on: ubuntu-latest - 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 - cache: npm - - run: npm ci - - run: npm run lint - - run: npm run typecheck - - run: npm run test:coverage - - run: npm run build - - run: npm publish --provenance --access public - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.release-please-manifest.json b/.release-please-manifest.json new file mode 100644 index 0000000..37fcefa --- /dev/null +++ b/.release-please-manifest.json @@ -0,0 +1,3 @@ +{ + ".": "1.0.0" +} diff --git a/release-please-config.json b/release-please-config.json new file mode 100644 index 0000000..170e7c7 --- /dev/null +++ b/release-please-config.json @@ -0,0 +1,25 @@ +{ + "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json", + "packages": { + ".": { + "release-type": "node", + "changelog-path": "CHANGELOG.md", + "include-component-in-tag": false, + "include-v-in-tag": true, + "release-as": "2.0.0", + "changelog-sections": [ + { "type": "feat", "section": "Added" }, + { "type": "fix", "section": "Fixed" }, + { "type": "perf", "section": "Performance" }, + { "type": "refactor", "section": "Changed" }, + { "type": "revert", "section": "Reverted" }, + { "type": "docs", "section": "Documentation", "hidden": true }, + { "type": "chore", "hidden": true }, + { "type": "ci", "hidden": true }, + { "type": "test", "hidden": true }, + { "type": "build", "hidden": true }, + { "type": "style", "hidden": true } + ] + } + } +}