Skip to content

chore(main): release 1.2.1 (#8) #4

chore(main): release 1.2.1 (#8)

chore(main): release 1.2.1 (#8) #4

Workflow file for this run

name: Release
on:
push:
tags:
- "v*.*.*"
workflow_dispatch:
inputs:
tag:
description: "Existing tag to release (e.g. v1.2.0). Required for manual runs."
required: true
type: string
permissions:
contents: read
concurrency:
group: release-${{ github.ref }}
cancel-in-progress: false
jobs:
# ---------- Stage 1: full verify ----------
verify:
name: Verify (Node ${{ matrix.node }})
runs-on: ubuntu-latest
timeout-minutes: 15
strategy:
fail-fast: true
matrix:
node: ["22", "24"]
steps:
- name: Checkout (tag)
uses: actions/checkout@v4
with:
ref: ${{ github.event.inputs.tag || github.ref }}
- name: Install pnpm
# Version derived from packageManager field in package.json.
uses: pnpm/action-setup@v4
- name: Set up Node.js ${{ matrix.node }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
cache: pnpm
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Verify
run: pnpm verify
# ---------- Stage 2: tag-vs-package.json sanity check ----------
guard:
name: Tag matches package.json
runs-on: ubuntu-latest
needs: verify
timeout-minutes: 5
outputs:
version: ${{ steps.extract.outputs.version }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: ${{ github.event.inputs.tag || github.ref }}
- name: Extract version from tag
id: extract
run: |
REF="${{ github.event.inputs.tag || github.ref_name }}"
# Strip refs/tags/ if present
TAG="${REF#refs/tags/}"
VERSION="${TAG#v}"
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
echo "Tag: $TAG → version: $VERSION"
- name: Compare against package.json
run: |
PKG_VERSION=$(node -p "require('./package.json').version")
if [ "$PKG_VERSION" != "${{ steps.extract.outputs.version }}" ]; then
echo "::error::Tag v${{ steps.extract.outputs.version }} does not match package.json version $PKG_VERSION"
exit 1
fi
echo "OK: tag v$PKG_VERSION matches package.json"
# ---------- Stage 3: GitHub Release ----------
github-release:
name: GitHub Release
runs-on: ubuntu-latest
needs: guard
timeout-minutes: 10
permissions:
contents: write
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: ${{ github.event.inputs.tag || github.ref }}
fetch-depth: 0
- name: Install pnpm
# Version derived from packageManager field in package.json.
uses: pnpm/action-setup@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: "22"
cache: pnpm
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build
run: pnpm build
- name: Pack tarball
id: pack
run: |
TARBALL=$(pnpm pack | tail -n1)
echo "tarball=$TARBALL" >> "$GITHUB_OUTPUT"
ls -lh "$TARBALL"
- name: Smoke-test the tarball (install + run --version + run --help)
run: |
TARBALL="${{ steps.pack.outputs.tarball }}"
SMOKE_DIR=$(mktemp -d)
# Install in an isolated directory, NOT linked to the workspace.
# `npm install` (not pnpm) — closer to what end-users running `npm i -g gsccli` will get.
cd "$SMOKE_DIR"
npm init -y >/dev/null
npm install --no-audit --no-fund "$GITHUB_WORKSPACE/$TARBALL"
# Resolve the bin and execute it. If shebang/bin field is broken, this fails here, not on npm.
BIN_PATH="$SMOKE_DIR/node_modules/.bin/gsccli"
test -x "$BIN_PATH" || { echo "::error::gsccli bin not executable at $BIN_PATH"; exit 1; }
echo "--- gsccli --version ---"
"$BIN_PATH" --version
echo "--- gsccli --help (first 20 lines) ---"
"$BIN_PATH" --help | head -20
- name: Extract release notes from CHANGELOG
id: notes
run: |
VERSION="${{ needs.guard.outputs.version }}"
# Awk: print lines between ## [VERSION] and the next ## [
awk -v v="$VERSION" '
$0 ~ "^## \\["v"\\]" { p=1; next }
p && /^## \[/ { exit }
p { print }
' CHANGELOG.md > release-notes.md
if [ ! -s release-notes.md ]; then
echo "Release v$VERSION — see CHANGELOG.md for details." > release-notes.md
fi
echo "--- release notes ---"
cat release-notes.md
# Idempotent: if the release already exists (e.g. previous run failed at the
# later npm-publish stage and we're re-triggering via workflow_dispatch), skip.
# The CHANGELOG/notes are already attached from the first run.
- name: Create GitHub Release (skip if already exists)
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
TAG="${{ github.event.inputs.tag || github.ref_name }}"
if gh release view "$TAG" >/dev/null 2>&1; then
echo "::notice::Release $TAG already exists — skipping create."
else
gh release create "$TAG" \
--title "$TAG" \
--notes-file release-notes.md \
--verify-tag \
"${{ steps.pack.outputs.tarball }}"
fi
# ---------- Stage 4: npm publish via OIDC trusted publishing ----------
# No NPM_TOKEN, no --provenance flag, no .npmrc auth token.
# The trusted-publisher relationship configured at npmjs.com/package/gsccli/access
# validates this workflow's OIDC claim (org+repo+workflow filename+environment) and
# auto-attaches a provenance attestation to the published artifact.
#
# PREREQUISITE: package must already exist on npm. First publish is a one-time
# manual `npm publish --otp=XXXXXX` from a maintainer's machine — see CONTRIBUTING.md.
npm-publish:
name: npm publish (OIDC trusted publishing)
runs-on: ubuntu-latest
needs: [guard, github-release]
timeout-minutes: 10
permissions:
contents: read
# The lever: npm registry verifies this OIDC token to authorize the publish.
id-token: write
environment:
name: npm
url: https://www.npmjs.com/package/@nalyk/gsccli
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: ${{ github.event.inputs.tag || github.ref }}
- name: Install pnpm
# Version derived from packageManager field in package.json.
uses: pnpm/action-setup@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: "22"
registry-url: "https://registry.npmjs.org"
cache: pnpm
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build
run: pnpm build
- name: Verify package contents (publint)
run: pnpm dlx publint
# Idempotency: a re-run of the same tag (or workflow_dispatch on a tagged ref)
# would EVERSIONEXISTS otherwise. Skip publish + leave existing artifact alone.
- name: Skip publish if version already on npm
id: check
run: |
NAME=$(node -p "require('./package.json').name")
VERSION=$(node -p "require('./package.json').version")
if npm view "${NAME}@${VERSION}" version >/dev/null 2>&1; then
echo "already_published=true" >> "$GITHUB_OUTPUT"
echo "::notice::${NAME}@${VERSION} already on npm — skipping publish."
else
echo "already_published=false" >> "$GITHUB_OUTPUT"
fi
# OIDC trusted publishing requires npm >= 11.5.1. Node 22 ships with npm 10.x.
# We deliberately do NOT `npm install -g npm@latest` here: pnpm/action-setup
# earlier in the job hoisted some packages into the global node_modules path,
# and a global npm reinstall over that tree leaves @npmcli/arborist with
# missing transitive deps (`Cannot find module 'promise-retry'`). Instead,
# invoke npm 11+ ephemerally via npx — same OIDC token, no global mutation.
- name: Publish via OIDC (npx-isolated npm 11+)
if: steps.check.outputs.already_published == 'false'
run: npx --yes npm@latest publish --access public