From 563026452418687402f797224c353bbd82c84777 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Thu, 15 Oct 2026 11:54:02 +0000 Subject: [PATCH 1/4] feat(release): add dist-tag and make-latest inputs Allow releasing older majors from maintenance branches without moving the npm `latest` dist-tag or the GitHub latest release. Co-Authored-By: Claude Opus 5.5 (1M context) --- .github/workflows/reusable-release.yml | 15 +++++++++++++-- README.md | 18 ++++++++++++++++++ 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/.github/workflows/reusable-release.yml b/.github/workflows/reusable-release.yml index 20050df..3f72d12 100644 --- a/.github/workflows/reusable-release.yml +++ b/.github/workflows/reusable-release.yml @@ -22,6 +22,16 @@ on: required: false default: 'release' type: string + dist-tag: + description: 'The npm dist-tag to publish under. Use a dedicated tag (e.g. "two") when releasing from a maintenance branch.' + required: false + default: 'latest' + type: string + make-latest: + description: 'Whether to mark the GitHub release as the latest one. Set to false when releasing from a maintenance branch.' + required: false + default: true + type: boolean permissions: {} @@ -35,6 +45,7 @@ jobs: contents: write # required to push the release commit and the tag env: BUMP_TYPE: ${{ inputs.semver }} + DIST_TAG: ${{ inputs.dist-tag }} steps: - name: Validate the semver input run: | @@ -77,7 +88,7 @@ jobs: run: git push origin HEAD - name: Publish to npm - run: npm publish --provenance --access public + run: npm publish --provenance --access public --tag "$DIST_TAG" - name: Create GitHub release uses: softprops/action-gh-release@efb35369e0ad2afab669f228072c1b0d510eae64 # v3.0.3 @@ -86,4 +97,4 @@ jobs: target_commitish: ${{ github.ref_name }} name: v${{ env.NEW_VERSION }} generate_release_notes: true # "What's Changed" from merged PRs + contributors + Full Changelog link - make_latest: true + make_latest: ${{ inputs.make-latest }} diff --git a/README.md b/README.md index 689a1dd..a8df8c3 100644 --- a/README.md +++ b/README.md @@ -150,6 +150,24 @@ maintainers that are allowed to approve a release. | `node-version` | false | string | `lts/*` | The Node.js version used to build and publish the package. | | `runs-on` | false | string | `ubuntu-latest`| The runner used to publish the package. | | `environment` | false | string | `release` | The deployment environment that gates the release. | +| `dist-tag` | false | string | `latest` | The npm dist-tag to publish under. | +| `make-latest` | false | boolean| `true` | Whether to mark the GitHub release as the latest one. | + +### Releasing from a maintenance branch + +When releasing an older major from a maintenance branch (e.g. `v2.x`), publish +under a dedicated dist-tag and do not mark the GitHub release as the latest, so +the current major keeps the `latest` tag: + +```yml + uses: fastify/workflows/.github/workflows/reusable-release.yml@v7 + with: + semver: ${{ inputs.semver }} + dist-tag: two + make-latest: false +``` + +Run the workflow from the *Actions* tab selecting the maintenance branch. ### Required permissions From 8d9806df830f96fc0f3e055649f43bda9b785dbd Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Thu, 15 Oct 2026 14:39:10 +0000 Subject: [PATCH 2/4] feat(release): support prerelease bump types and preid Accept prepatch, preminor, premajor and prerelease, with an optional preid input. Prereleases are flagged as such on GitHub, never marked as latest, and are refused under the 'latest' dist-tag. --- .github/workflows/reusable-release.yml | 25 ++++++++++++--- README.md | 43 +++++++++++++++++++++++++- 2 files changed, 62 insertions(+), 6 deletions(-) diff --git a/.github/workflows/reusable-release.yml b/.github/workflows/reusable-release.yml index 3f72d12..c8adae8 100644 --- a/.github/workflows/reusable-release.yml +++ b/.github/workflows/reusable-release.yml @@ -4,9 +4,14 @@ on: workflow_call: inputs: semver: - description: 'Release bump type: patch, minor or major.' + description: 'Release bump type: patch, minor, major, prepatch, preminor, premajor or prerelease.' required: true type: string + preid: + description: 'The prerelease identifier (e.g. "rc", "beta") used by the pre* bump types. Leave empty for a numeric-only prerelease.' + required: false + default: '' + type: string node-version: description: 'The Node.js version used to build and publish the package.' required: false @@ -46,13 +51,22 @@ jobs: env: BUMP_TYPE: ${{ inputs.semver }} DIST_TAG: ${{ inputs.dist-tag }} + PREID: ${{ inputs.preid }} steps: - - name: Validate the semver input + - name: Validate the inputs run: | case "$BUMP_TYPE" in patch|minor|major) ;; - *) echo "::error::Invalid semver input '$BUMP_TYPE': expected patch, minor or major." && exit 1 ;; + prepatch|preminor|premajor|prerelease) + if [ "$DIST_TAG" = "latest" ]; then + echo "::error::Refusing to publish a prerelease under the 'latest' dist-tag: set the dist-tag input (e.g. 'next')." && exit 1 + fi + ;; + *) echo "::error::Invalid semver input '$BUMP_TYPE': expected patch, minor, major, prepatch, preminor, premajor or prerelease." && exit 1 ;; esac + if [ -n "$PREID" ] && ! printf '%s' "$PREID" | grep -Eq '^[0-9A-Za-z-]+$'; then + echo "::error::Invalid preid input '$PREID': only alphanumerics and hyphens are allowed." && exit 1 + fi - name: Check out repo uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 @@ -72,7 +86,7 @@ jobs: run: | git config --global user.name "github-actions[bot]" git config --global user.email "github-actions[bot]@users.noreply.github.com" - NEW_VERSION=$(npm version "$(printf '%s' "$BUMP_TYPE")" --no-git-tag-version) + NEW_VERSION=$(npm version "$(printf '%s' "$BUMP_TYPE")" ${PREID:+--preid "$PREID"} --no-git-tag-version) git add -u # stages package.json and the lockfile, whichever the repo tracks git commit -m "Bumped v$(printf '%s' "${NEW_VERSION#v}")" SAFE_NEW_VERSION=$(printf '%s' "${NEW_VERSION#v}" | tr -d '\r\n') @@ -97,4 +111,5 @@ jobs: target_commitish: ${{ github.ref_name }} name: v${{ env.NEW_VERSION }} generate_release_notes: true # "What's Changed" from merged PRs + contributors + Full Changelog link - make_latest: ${{ inputs.make-latest }} + prerelease: ${{ startsWith(inputs.semver, 'pre') }} + make_latest: ${{ inputs.make-latest && !startsWith(inputs.semver, 'pre') }} diff --git a/README.md b/README.md index a8df8c3..168232e 100644 --- a/README.md +++ b/README.md @@ -146,7 +146,8 @@ maintainers that are allowed to approve a release. | Input Name | Required | Type | Default | Description | | -------------- | -------- | ------ | -------------- | -------------------------------------------------------------------- | -| `semver` | true | string | | The release bump type: `patch`, `minor` or `major`. | +| `semver` | true | string | | The release bump type: `patch`, `minor`, `major`, `prepatch`, `preminor`, `premajor` or `prerelease`. | +| `preid` | false | string | `''` | The prerelease identifier (e.g. `rc`) used by the `pre*` bump types. | | `node-version` | false | string | `lts/*` | The Node.js version used to build and publish the package. | | `runs-on` | false | string | `ubuntu-latest`| The runner used to publish the package. | | `environment` | false | string | `release` | The deployment environment that gates the release. | @@ -169,6 +170,46 @@ the current major keeps the `latest` tag: Run the workflow from the *Actions* tab selecting the maintenance branch. +### Prereleases + +The `prepatch`, `preminor`, `premajor` and `prerelease` bump types publish a +prerelease (e.g. `5.0.0-rc.0` with `preid: rc`). The GitHub release is flagged +as a prerelease and never marked as the latest one. Publishing a prerelease +under the `latest` dist-tag is refused, so set `dist-tag` (e.g. `next`) as well: + +```yml +on: + workflow_dispatch: + inputs: + semver: + description: 'Release bump type' + required: true + type: choice + options: + - patch + - minor + - major + - prepatch + - preminor + - premajor + - prerelease + preid: + description: 'Prerelease identifier (e.g. rc), used by the pre* bump types' + required: false + type: string + +jobs: + release: + permissions: + id-token: write + contents: write + uses: fastify/workflows/.github/workflows/reusable-release.yml@v7 + with: + semver: ${{ inputs.semver }} + preid: ${{ inputs.preid }} + dist-tag: ${{ startsWith(inputs.semver, 'pre') && 'next' || 'latest' }} +``` + ### Required permissions The calling job must grant `id-token: write` (npm provenance via OIDC) and From 7f4818dc4265b12cf2e59a39ecf4330843d77ffb Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Fri, 16 Oct 2026 08:47:21 +0000 Subject: [PATCH 3/4] feat(release): validate the dist-tag input before bumping Co-Authored-By: Claude Opus 5.5 (1M context) --- .github/workflows/reusable-release.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/reusable-release.yml b/.github/workflows/reusable-release.yml index c8adae8..31e75ef 100644 --- a/.github/workflows/reusable-release.yml +++ b/.github/workflows/reusable-release.yml @@ -67,6 +67,12 @@ jobs: if [ -n "$PREID" ] && ! printf '%s' "$PREID" | grep -Eq '^[0-9A-Za-z-]+$'; then echo "::error::Invalid preid input '$PREID': only alphanumerics and hyphens are allowed." && exit 1 fi + # npm rejects empty tags and tags that parse as a semver range (e.g. "2", "v2", "x"), + # but only at publish time, after the bump commit has been pushed: fail fast instead. + if ! printf '%s' "$DIST_TAG" | grep -Eq '^[A-Za-z][0-9A-Za-z._-]*$' \ + || printf '%s' "$DIST_TAG" | grep -Eq '^[vV]?([0-9]+|[xX])(\.([0-9]+|[xX])){0,2}$'; then + echo "::error::Invalid dist-tag input '$DIST_TAG': it must start with a letter, contain only alphanumerics, '.', '_' or '-', and not look like a semver range." && exit 1 + fi - name: Check out repo uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 From 03e203fe084d64f14254533ac4cd4d3982370b4f Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Fri, 16 Oct 2026 14:07:48 +0000 Subject: [PATCH 4/4] feat(release): reject prerelease-version-like dist-tags Co-Authored-By: Claude Opus 5.5 (1M context) --- .github/workflows/reusable-release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/reusable-release.yml b/.github/workflows/reusable-release.yml index 31e75ef..a3e8c3d 100644 --- a/.github/workflows/reusable-release.yml +++ b/.github/workflows/reusable-release.yml @@ -70,7 +70,7 @@ jobs: # npm rejects empty tags and tags that parse as a semver range (e.g. "2", "v2", "x"), # but only at publish time, after the bump commit has been pushed: fail fast instead. if ! printf '%s' "$DIST_TAG" | grep -Eq '^[A-Za-z][0-9A-Za-z._-]*$' \ - || printf '%s' "$DIST_TAG" | grep -Eq '^[vV]?([0-9]+|[xX])(\.([0-9]+|[xX])){0,2}$'; then + || printf '%s' "$DIST_TAG" | grep -Eq '^[vV]?(([0-9]+|[xX])(\.([0-9]+|[xX])){0,2}|[0-9]+\.[0-9]+\.[0-9]+-[0-9A-Za-z.-]+)$'; then echo "::error::Invalid dist-tag input '$DIST_TAG': it must start with a letter, contain only alphanumerics, '.', '_' or '-', and not look like a semver range." && exit 1 fi