From 67c1da4f6808c2b595c407830300a7769faf87bf Mon Sep 17 00:00:00 2001 From: simaonogueira101 Date: Mon, 11 May 2026 23:44:35 +0100 Subject: [PATCH] ci(release): auto-publish on package.json version change MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaces the 'tag pushed → publish' trigger with 'push to main with version change → publish + tag'. Removes the manual git tag + push step from the release flow. Behaviour: - Push to main that bumps version in package.json → publish + tag - Push to main that doesn't touch version → workflow does nothing - workflow_dispatch with force=true → publish at current version - Tag already exists on origin → fail (safety against double publish) Safety properties preserved: - Only intentional version bumps publish (no accidental release from routine main pushes) - Lint + check-types + tests + build still gate the publish - Provenance still enabled Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/release.yml | 93 +++++++++++++++++++++++++++++++---- 1 file changed, 83 insertions(+), 10 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 717d6c8..b763198 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,35 +1,108 @@ name: release -# Publishes @noticed/cli to npm with provenance whenever a `v*` tag is pushed. -# Tag convention: `v0.2.0`. Bump version in package.json + push tag in one go: -# npm version patch --no-git-tag-version -# git commit -am "chore: release v$(node -p "require('./package.json').version")" -# git tag v$(node -p "require('./package.json').version") -# git push origin main --tags +# Publishes @noticed/cli to npm with provenance whenever the `version` in +# package.json changes on main. The workflow does the version check first, +# skips entirely if the version is unchanged from the previous commit, and +# creates + pushes the matching `v` git tag after a successful +# publish so the release is also marked in git history. +# +# Why this shape (vs. the older "tag pushed → publish" trigger): +# - Bumping the version in the same PR that ships the change keeps the +# release intent visible in code review. +# - No manual `git tag && git push --tags` step after merging. +# - Merges that don't touch the version are no-ops, so feature PRs that +# don't bump version never accidentally publish. on: push: - tags: - - "v*" + branches: + - main + paths: + - "package.json" + workflow_dispatch: + inputs: + force: + description: "Force publish at the current package.json version (skip version-change check)." + type: boolean + default: false jobs: publish: runs-on: ubuntu-latest permissions: - contents: read - id-token: write # required for npm provenance + contents: write # for creating/pushing the version tag + id-token: write # for npm provenance steps: - uses: actions/checkout@v4 + with: + fetch-depth: 2 # need previous commit to diff package.json + + - name: Detect version change + id: version + run: | + set -euo pipefail + CURR=$(node -p "require('./package.json').version") + echo "curr=$CURR" >> "$GITHUB_OUTPUT" + FORCE='${{ inputs.force }}' + if [ "$FORCE" = "true" ]; then + echo "Forced publish at version $CURR." + echo "changed=true" >> "$GITHUB_OUTPUT" + exit 0 + fi + if git rev-parse HEAD~1 >/dev/null 2>&1; then + PREV=$(git show HEAD~1:package.json | node -e 'process.stdout.write(JSON.parse(require("fs").readFileSync(0,"utf8")).version)') + else + PREV="" + fi + if [ "$PREV" = "$CURR" ]; then + echo "Version unchanged ($CURR) — skipping publish." + echo "changed=false" >> "$GITHUB_OUTPUT" + else + echo "Version bumped: $PREV → $CURR" + echo "changed=true" >> "$GITHUB_OUTPUT" + fi + + - name: Check tag does not already exist + if: steps.version.outputs.changed == 'true' + run: | + VERSION="v${{ steps.version.outputs.curr }}" + if git ls-remote --tags --exit-code origin "refs/tags/$VERSION" >/dev/null 2>&1; then + echo "::error::Tag $VERSION already exists on origin — refusing to re-publish. Bump the version in package.json." + exit 1 + fi + - uses: actions/setup-node@v4 + if: steps.version.outputs.changed == 'true' with: node-version: "22" registry-url: "https://registry.npmjs.org" cache: npm + - run: npm ci + if: steps.version.outputs.changed == 'true' + - run: npm run lint + if: steps.version.outputs.changed == 'true' + - run: npm run check-types + if: steps.version.outputs.changed == 'true' + - run: npm test + if: steps.version.outputs.changed == 'true' + - run: npm run build + if: steps.version.outputs.changed == 'true' + - run: npm publish --provenance --access public + if: steps.version.outputs.changed == 'true' env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + + - name: Tag the release + if: steps.version.outputs.changed == 'true' + run: | + VERSION="v${{ steps.version.outputs.curr }}" + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git tag "$VERSION" + git push origin "$VERSION"