Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 83 additions & 10 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -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<version>` 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"
Loading