From 6cd0073bca11871d8e578a1057e32236275659e4 Mon Sep 17 00:00:00 2001 From: Callum Dunster Date: Tue, 2 Jun 2026 18:00:31 +0200 Subject: [PATCH 1/6] feat: add reusable workflow to prepare a Node.js release --- .github/workflows/nodejs-prepare-release.yml | 116 +++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 .github/workflows/nodejs-prepare-release.yml diff --git a/.github/workflows/nodejs-prepare-release.yml b/.github/workflows/nodejs-prepare-release.yml new file mode 100644 index 0000000..36270be --- /dev/null +++ b/.github/workflows/nodejs-prepare-release.yml @@ -0,0 +1,116 @@ +name: Prepare Node.js Release + +on: + workflow_call: + inputs: + cliff_config_url: + type: string + description: "URL to a git-cliff configuration file" + required: true + force_version: + type: string + description: "Exact semver version for the next release, overriding auto-detection from commits" + default: "" + required: false + node_version: + type: string + description: "Node.js version to use" + default: "22" + required: false + package_manager: + type: string + description: "Package manager used by the project: npm, yarn, or pnpm" + default: "npm" + required: false + secrets: + GH_TOKEN: + description: "GitHub token with permission to create pull requests" + required: true + +jobs: + prepare: + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + steps: + - name: Validate inputs + env: + PACKAGE_MANAGER: ${{ inputs.package_manager }} + run: | + case "$PACKAGE_MANAGER" in + npm|yarn|pnpm) ;; + *) echo "::error::Unsupported package_manager '$PACKAGE_MANAGER'. Must be one of: npm, yarn, pnpm." && exit 1 ;; + esac + + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + persist-credentials: false + + - name: Setup pnpm + if: inputs.package_manager == 'pnpm' + uses: pnpm/action-setup@v4 + + - uses: actions/setup-node@v4 + with: + node-version: ${{ inputs.node_version }} + + - name: Install git-cliff + uses: taiki-e/install-action@v2 + with: + tool: git-cliff@2.13.1 + + - name: Determine next version from commits + if: inputs.force_version == '' + id: cliff-version + env: + CLIFF_CONFIG_URL: ${{ inputs.cliff_config_url }} + run: | + VERSION=$(git-cliff --config-url="$CLIFF_CONFIG_URL" --bumped-version 2>/dev/null) + echo "version=${VERSION#v}" >> "$GITHUB_OUTPUT" + + - name: Determine version + id: version + env: + INPUT_VERSION: ${{ inputs.force_version }} + CLIFF_VERSION: ${{ steps.cliff-version.outputs.version }} + run: | + RAW_VERSION="${INPUT_VERSION:-$CLIFF_VERSION}" + if [ -z "$RAW_VERSION" ]; then + echo "::error::Version could not be determined. Provide a force_version input or ensure git tags exist for auto-detection." + exit 1 + fi + echo "value=${RAW_VERSION#v}" >> "$GITHUB_OUTPUT" + + - name: Bump package version + env: + RELEASE_VERSION: ${{ steps.version.outputs.value }} + PACKAGE_MANAGER: ${{ inputs.package_manager }} + run: | + case "$PACKAGE_MANAGER" in + yarn) yarn version --new-version "$RELEASE_VERSION" --no-git-tag-version ;; + pnpm) pnpm version "$RELEASE_VERSION" --no-git-tag-version ;; + npm) npm version "$RELEASE_VERSION" --no-git-tag-version ;; + esac + + - name: Generate changelog + env: + RELEASE_VERSION: ${{ steps.version.outputs.value }} + CLIFF_CONFIG_URL: ${{ inputs.cliff_config_url }} + run: git-cliff --config-url="$CLIFF_CONFIG_URL" --tag "v$RELEASE_VERSION" --unreleased --prepend CHANGELOG.md + + - name: Create release PR + uses: peter-evans/create-pull-request@v7 + with: + token: ${{ secrets.GH_TOKEN }} + commit-message: "chore: release v${{ steps.version.outputs.value }}" + branch: automated-release-${{ steps.version.outputs.value }} + title: "chore: release v${{ steps.version.outputs.value }}" + body: | + Automated release PR for **v${{ steps.version.outputs.value }}**. + + Review the changelog and version bump, then merge to publish to npm. + labels: hra-release + team-reviewers: holochain/holochain-devs + delete-branch: true From 022d96f72f69d59a7736e832e98257b4fbc68bb9 Mon Sep 17 00:00:00 2001 From: Callum Dunster Date: Tue, 2 Jun 2026 18:03:21 +0200 Subject: [PATCH 2/6] feat: add reusable workflow to publish the prepared Node.js release --- .github/workflows/nodejs-publish-release.yml | 158 +++++++++++++++++++ 1 file changed, 158 insertions(+) create mode 100644 .github/workflows/nodejs-publish-release.yml diff --git a/.github/workflows/nodejs-publish-release.yml b/.github/workflows/nodejs-publish-release.yml new file mode 100644 index 0000000..cab187c --- /dev/null +++ b/.github/workflows/nodejs-publish-release.yml @@ -0,0 +1,158 @@ +name: Publish Node.js Release + +on: + workflow_call: + inputs: + cliff_config_url: + type: string + description: "URL to a git-cliff configuration file" + required: true + node_version: + type: string + description: "Node.js version to use" + default: "22" + required: false + package_manager: + type: string + description: "Package manager used by the project: npm, yarn, or pnpm" + default: "npm" + required: false + build_script: + type: string + description: "npm script name to run for building the project" + default: "build" + required: false + secrets: + NPM_TOKEN: + description: "npm authentication token for publishing" + required: true + +jobs: + check: + runs-on: ubuntu-latest + outputs: + is-release: ${{ steps.label.outputs.result }} + steps: + - name: Check for hra-release label on merged PR + id: label + env: + GH_TOKEN: ${{ github.token }} + run: | + RESULT=$(gh api repos/${{ github.repository }}/commits/${{ github.sha }}/pulls \ + --jq 'any(.[].labels[].name; . == "hra-release")' 2>/dev/null || echo "false") + echo "result=$RESULT" >> "$GITHUB_OUTPUT" + + publish: + needs: check + if: needs.check.outputs.is-release == 'true' + runs-on: ubuntu-latest + permissions: + contents: write + id-token: write + steps: + - name: Validate inputs + env: + PACKAGE_MANAGER: ${{ inputs.package_manager }} + run: | + case "$PACKAGE_MANAGER" in + npm|yarn|pnpm) ;; + *) echo "::error::Unsupported package_manager '$PACKAGE_MANAGER'. Must be one of: npm, yarn, pnpm." && exit 1 ;; + esac + + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup pnpm + if: inputs.package_manager == 'pnpm' + uses: pnpm/action-setup@v4 + + - uses: actions/setup-node@v4 + with: + node-version: ${{ inputs.node_version }} + registry-url: "https://registry.npmjs.org" + cache: ${{ inputs.package_manager }} + + - name: Install dependencies + env: + PACKAGE_MANAGER: ${{ inputs.package_manager }} + run: | + case "$PACKAGE_MANAGER" in + yarn) yarn install --frozen-lockfile ;; + pnpm) pnpm install --frozen-lockfile ;; + npm) npm ci ;; + esac + + - name: Build + env: + PACKAGE_MANAGER: ${{ inputs.package_manager }} + BUILD_SCRIPT: ${{ inputs.build_script }} + run: | + case "$PACKAGE_MANAGER" in + yarn) yarn "$BUILD_SCRIPT" ;; + pnpm) pnpm run "$BUILD_SCRIPT" ;; + npm) npm run "$BUILD_SCRIPT" ;; + esac + + - name: Get version + id: version + run: echo "value=$(node -p "require('./package.json').version")" >> "$GITHUB_OUTPUT" + + - name: Create and push tag + env: + RELEASE_VERSION: ${{ steps.version.outputs.value }} + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + if git ls-remote --tags origin "v$RELEASE_VERSION" | grep -q .; then + echo "::error::Tag v$RELEASE_VERSION already exists. Was this release already published?" + exit 1 + fi + git tag "v$RELEASE_VERSION" + git push origin "v$RELEASE_VERSION" + + - name: Install git-cliff + uses: taiki-e/install-action@v2 + with: + tool: git-cliff@2.13.1 + + - name: Generate release notes + env: + RELEASE_VERSION: ${{ steps.version.outputs.value }} + CLIFF_CONFIG_URL: ${{ inputs.cliff_config_url }} + run: | + git-cliff --config-url="$CLIFF_CONFIG_URL" --tag "v$RELEASE_VERSION" --latest --strip all > release-notes.md + + - name: Pack npm artifact + run: npm pack + + - name: Create GitHub release + env: + GH_TOKEN: ${{ github.token }} + RELEASE_VERSION: ${{ steps.version.outputs.value }} + run: | + PRERELEASE_FLAG="" + if [[ "$RELEASE_VERSION" == *"-"* ]]; then + PRERELEASE_FLAG="--prerelease" + fi + gh release create "v$RELEASE_VERSION" \ + --title "v$RELEASE_VERSION" \ + --notes-file release-notes.md \ + $PRERELEASE_FLAG \ + ./*.tgz + + - name: Publish to npm + env: + RELEASE_VERSION: ${{ steps.version.outputs.value }} + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + run: | + PACKAGE_NAME=$(node -p "require('./package.json').name") + if npm view "$PACKAGE_NAME@$RELEASE_VERSION" > /dev/null 2>&1; then + echo "::error::$PACKAGE_NAME@$RELEASE_VERSION is already published to npm. Was this release already published?" + exit 1 + fi + if [[ "$RELEASE_VERSION" == *"-"* ]]; then + npm publish --access public --provenance --tag next + else + npm publish --access public --provenance + fi From 14e8e562a8df82af443f602690e8966e0824244f Mon Sep 17 00:00:00 2001 From: Callum Dunster Date: Wed, 3 Jun 2026 14:44:24 +0200 Subject: [PATCH 3/6] feat: update git-cliff to v2.13.1 --- .github/workflows/changelog-preview-comment.yml | 2 +- .github/workflows/prepare-release.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/changelog-preview-comment.yml b/.github/workflows/changelog-preview-comment.yml index 0804838..7731087 100644 --- a/.github/workflows/changelog-preview-comment.yml +++ b/.github/workflows/changelog-preview-comment.yml @@ -27,7 +27,7 @@ jobs: - name: Install git-cliff uses: taiki-e/install-action@v2 with: - tool: git-cliff@2.10.1 + tool: git-cliff@2.13.1 - name: Run git cliff run: | diff --git a/.github/workflows/prepare-release.yml b/.github/workflows/prepare-release.yml index 8c3d71c..2ea8886 100644 --- a/.github/workflows/prepare-release.yml +++ b/.github/workflows/prepare-release.yml @@ -60,7 +60,7 @@ jobs: - name: Install git-cliff uses: taiki-e/install-action@v2 with: - tool: git-cliff@2.12.0 + tool: git-cliff@2.13.1 - name: Install cargo-workspaces uses: taiki-e/install-action@v2 From 6f937e4eb52c678bf9506c5cb31872254aab5bda Mon Sep 17 00:00:00 2001 From: Callum Dunster Date: Wed, 3 Jun 2026 15:06:07 +0200 Subject: [PATCH 4/6] feat: don't persist GitHub token with write permissions in publish CI --- .github/workflows/nodejs-publish-release.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/nodejs-publish-release.yml b/.github/workflows/nodejs-publish-release.yml index cab187c..6f6b166 100644 --- a/.github/workflows/nodejs-publish-release.yml +++ b/.github/workflows/nodejs-publish-release.yml @@ -62,6 +62,7 @@ jobs: - uses: actions/checkout@v4 with: fetch-depth: 0 + persist-credentials: false - name: Setup pnpm if: inputs.package_manager == 'pnpm' @@ -101,9 +102,11 @@ jobs: - name: Create and push tag env: RELEASE_VERSION: ${{ steps.version.outputs.value }} + GH_TOKEN: ${{ github.token }} run: | git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" + git remote set-url origin "https://x-access-token:${GH_TOKEN}@github.com/${{ github.repository }}.git" if git ls-remote --tags origin "v$RELEASE_VERSION" | grep -q .; then echo "::error::Tag v$RELEASE_VERSION already exists. Was this release already published?" exit 1 From 5e6307ee20f1a2acb49fa49fd57f0a7859168b80 Mon Sep 17 00:00:00 2001 From: Callum Dunster Date: Wed, 3 Jun 2026 16:33:54 +0200 Subject: [PATCH 5/6] feat: in nodejs publish release, extract release notes from changelog Avoids re-generating with git-cliff and keeps any manual edits. --- .github/workflows/nodejs-publish-release.yml | 24 ++++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/.github/workflows/nodejs-publish-release.yml b/.github/workflows/nodejs-publish-release.yml index 6f6b166..cc72ee5 100644 --- a/.github/workflows/nodejs-publish-release.yml +++ b/.github/workflows/nodejs-publish-release.yml @@ -3,10 +3,6 @@ name: Publish Node.js Release on: workflow_call: inputs: - cliff_config_url: - type: string - description: "URL to a git-cliff configuration file" - required: true node_version: type: string description: "Node.js version to use" @@ -114,17 +110,21 @@ jobs: git tag "v$RELEASE_VERSION" git push origin "v$RELEASE_VERSION" - - name: Install git-cliff - uses: taiki-e/install-action@v2 - with: - tool: git-cliff@2.13.1 - - - name: Generate release notes + - name: Extract release notes from changelog env: RELEASE_VERSION: ${{ steps.version.outputs.value }} - CLIFF_CONFIG_URL: ${{ inputs.cliff_config_url }} run: | - git-cliff --config-url="$CLIFF_CONFIG_URL" --tag "v$RELEASE_VERSION" --latest --strip all > release-notes.md + awk -v version="$RELEASE_VERSION" ' + /^## / { + if (found) exit + if (index($0, version) > 0) { found = 1; next } + } + found { print } + ' CHANGELOG.md > release-notes.md + if [ ! -s release-notes.md ]; then + echo "::error::Could not extract release notes for v$RELEASE_VERSION from CHANGELOG.md" + exit 1 + fi - name: Pack npm artifact run: npm pack From bf1cbf72ad03c9e175b67c6fcfcc1447c3acffe2 Mon Sep 17 00:00:00 2001 From: Callum Dunster Date: Thu, 4 Jun 2026 12:27:41 +0200 Subject: [PATCH 6/6] feat: update actions in nodejs release workflows to latest version --- .github/workflows/nodejs-prepare-release.yml | 8 ++++---- .github/workflows/nodejs-publish-release.yml | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/nodejs-prepare-release.yml b/.github/workflows/nodejs-prepare-release.yml index 36270be..9d8ab1c 100644 --- a/.github/workflows/nodejs-prepare-release.yml +++ b/.github/workflows/nodejs-prepare-release.yml @@ -43,16 +43,16 @@ jobs: *) echo "::error::Unsupported package_manager '$PACKAGE_MANAGER'. Must be one of: npm, yarn, pnpm." && exit 1 ;; esac - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: fetch-depth: 0 persist-credentials: false - name: Setup pnpm if: inputs.package_manager == 'pnpm' - uses: pnpm/action-setup@v4 + uses: pnpm/action-setup@v6 - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v6 with: node-version: ${{ inputs.node_version }} @@ -101,7 +101,7 @@ jobs: run: git-cliff --config-url="$CLIFF_CONFIG_URL" --tag "v$RELEASE_VERSION" --unreleased --prepend CHANGELOG.md - name: Create release PR - uses: peter-evans/create-pull-request@v7 + uses: peter-evans/create-pull-request@v8 with: token: ${{ secrets.GH_TOKEN }} commit-message: "chore: release v${{ steps.version.outputs.value }}" diff --git a/.github/workflows/nodejs-publish-release.yml b/.github/workflows/nodejs-publish-release.yml index cc72ee5..e046b23 100644 --- a/.github/workflows/nodejs-publish-release.yml +++ b/.github/workflows/nodejs-publish-release.yml @@ -55,16 +55,16 @@ jobs: *) echo "::error::Unsupported package_manager '$PACKAGE_MANAGER'. Must be one of: npm, yarn, pnpm." && exit 1 ;; esac - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: fetch-depth: 0 persist-credentials: false - name: Setup pnpm if: inputs.package_manager == 'pnpm' - uses: pnpm/action-setup@v4 + uses: pnpm/action-setup@v6 - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v6 with: node-version: ${{ inputs.node_version }} registry-url: "https://registry.npmjs.org"