Move workflows to Node 24.15.0 — 22.22.2 toolcache ships a broken npm… #9
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 }} |