Skip to content

Move workflows to Node 24.15.0 — 22.22.2 toolcache ships a broken npm… #9

Move workflows to Node 24.15.0 — 22.22.2 toolcache ships a broken npm…

Move workflows to Node 24.15.0 — 22.22.2 toolcache ships a broken npm… #9

Workflow file for this run

name: Publish Package
on:
push:
tags:
- 'v*'
permissions: {}
jobs:
publish:
# npm provenance publishing only supports GitHub-hosted Actions runners.
# Keep this job on ubuntu-latest unless npm adds self-hosted provenance support.
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write # Required for OIDC trusted publishing
# Workflow-context values are bound to env here and referenced as
# shell variables ($TAG/$REPO/$COMMIT_SHA) in run: blocks instead of
# `${{ }}` interpolation, so an attacker-controlled tag name cannot be
# injected into a script body. Tag pushes are attacker-controllable:
# anyone able to push a v* tag triggers this workflow.
env:
TAG: ${{ github.ref_name }}
REPO: ${{ github.repository }}
COMMIT_SHA: ${{ github.sha }}
steps:
- name: Validate tag format
# Fail closed before any other step runs. A strict semver gate
# rejects a tag containing shell metacharacters ($(), backticks,
# ;, |) so it never reaches a later run: block.
run: |
if [[ ! "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?(\+[0-9A-Za-z.-]+)?$ ]]; then
echo "❌ Error: Tag '$TAG' is not a valid vMAJOR.MINOR.PATCH semver tag"
echo "Releases must be triggered by a strict semver tag, e.g. v1.2.3 or v1.2.3-rc.1"
exit 1
fi
echo "✅ Tag format validated: $TAG"
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
- name: Verify tag is on main branch
run: |
git fetch origin main
if ! git merge-base --is-ancestor "$COMMIT_SHA" origin/main; then
echo "❌ Tag is not on the main branch — aborting release"
exit 1
fi
echo "✅ Tag is on main branch"
- name: Setup pnpm
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v4.4.0
- name: Setup Node
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version: "24.15.0"
cache: 'pnpm'
# No registry-url - using OIDC trusted publishing instead
- name: Update npm for trusted publishing
run: npm install -g npm@latest
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Extract version from tag
id: version
run: |
# $GITHUB_REF is a runner-provided env var (safe shell
# expansion, not template interpolation); the tag was strictly
# validated above, so VERSION is a clean semver string.
VERSION="${GITHUB_REF#refs/tags/v}"
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
echo "Publishing version: $VERSION"
- name: Verify tag matches package.json version
env:
VERSION: ${{ steps.version.outputs.version }}
run: |
PKG_VERSION=$(node -p "require('./package.json').version")
if [ "$VERSION" != "$PKG_VERSION" ]; then
echo "❌ Tag v$VERSION does not match package.json version $PKG_VERSION"
exit 1
fi
echo "✅ Version confirmed: $VERSION"
- name: Build
run: pnpm build
- name: Test
run: pnpm test
env:
TEST_TOKEN: ${{ secrets.TEST_TOKEN }}
- name: Publish to npm
run: npm publish --provenance
# Explicitly use --provenance flag for clarity
# OIDC trusted publishing (id-token: write) enables automatic provenance generation
- name: Generate release notes
id: release_notes
env:
VERSION: ${{ steps.version.outputs.version }}
run: |
PREV_TAG=$(git tag -l 'v*' --sort=-version:refname | grep -v "^${TAG}$" | head -1)
RELEASE_DATE=$(date +%Y-%m-%d)
if [ -n "$PREV_TAG" ]; then
COMMITS=$(git log ${PREV_TAG}..HEAD --pretty=format:"%s %h" --no-merges)
else
COMMITS=$(git log --pretty=format:"%s %h" --no-merges)
fi
FEATURES=""
FIXES=""
OTHER=""
while IFS=$'\t' read -r message hash; do
[ -z "$message" ] && continue
if [[ $message =~ \(#([0-9]+)\) ]]; then
PR_NUM="${BASH_REMATCH[1]}"
CLEAN_MESSAGE=$(echo "$message" | sed -E 's/ ?\(#[0-9]+\)//')
PR_LINK="[#$PR_NUM](https://github.com/${REPO}/pull/$PR_NUM)"
COMMIT_LINK="[$hash](https://github.com/${REPO}/commit/$hash)"
ITEM="$CLEAN_MESSAGE ($PR_LINK) ($COMMIT_LINK)"
else
COMMIT_LINK="[$hash](https://github.com/${REPO}/commit/$hash)"
ITEM="$message ($COMMIT_LINK)"
fi
if [[ $message =~ ^feat(\([^\)]+\))?: ]]; then
STRIPPED=$(echo "$ITEM" | sed -E 's/^feat(\([^)]+\))?: //')
FEATURES="${FEATURES}- ${STRIPPED}
"
elif [[ $message =~ ^fix(\([^\)]+\))?: ]]; then
STRIPPED=$(echo "$ITEM" | sed -E 's/^fix(\([^)]+\))?: //')
FIXES="${FIXES}- ${STRIPPED}
"
else
OTHER="${OTHER}- ${ITEM}
"
fi
done <<< "$COMMITS"
cat > release_notes.md <<EOF
$VERSION ($RELEASE_DATE)
EOF
if [ -n "$FEATURES" ]; then
cat >> release_notes.md <<EOF
## Features
$FEATURES
EOF
fi
if [ -n "$FIXES" ]; then
cat >> release_notes.md <<EOF
## Bug Fixes
$FIXES
EOF
fi
if [ -n "$OTHER" ]; then
cat >> release_notes.md <<EOF
## Changes
$OTHER
EOF
fi
cat >> release_notes.md <<EOF
## Install
\`\`\`bash
npm install -g @formo/cli@$VERSION
\`\`\`
EOF
- name: Create GitHub Release
uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0
with:
body_path: release_notes.md
draft: false
prerelease: false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}