Skip to content

Merge pull request #729 from devswha/dev #23

Merge pull request #729 from devswha/dev

Merge pull request #729 from devswha/dev #23

Workflow file for this run

name: Release
on:
push:
tags:
- 'v*.*.*'
workflow_dispatch:
inputs:
publish:
description: Publish npm packages instead of running a dry-run.
type: boolean
default: false
publish_ghcr:
description: Also publish the experimental GHCR image. Keep false for npm-only releases.
type: boolean
default: false
permissions:
contents: read
jobs:
verify:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Set up Node 24
uses: actions/setup-node@v6
with:
node-version: 24
cache: npm
- name: Install dependencies
run: npm ci
- name: ESLint, typecheck, and codespell
run: npm run lint
- name: Release metadata check
run: npm run release:check
- name: Unit and e2e tests
run: npm test
- name: Benchmark report schema
run: npm run benchmark:report
- name: Detector comparison harness
# Runs before the drift check so the regenerated
# docs/benchmarks/detector-comparison.{json,md} are drift-checked too.
run: npm run benchmark:compare
- name: Benchmark report drift check
# Same deterministic regen as test.yml's quality job (timestamp and
# node-version metadata lines ignored); a failure here means the
# tagged commit ships a stale public benchmark page.
run: |
git diff --exit-code -I '"generatedAt":' -I '"benchmarkGeneratedAt":' -I 'Generated at:' -I '"nodeVersion":' -I '^- Node: ' -- docs/benchmarks || {
echo '::error::docs/benchmarks is stale. Run `npm run benchmark:report && npm run benchmark:compare` and commit the result.'
exit 1
}
- name: Dogfood public docs
run: npm run dogfood
- name: Private asset leak gate
run: npm run check:no-private-assets
- name: Root npm package dry run
run: npm pack --dry-run
- name: Alias npm package dry run
run: cd packages/patina-humanizer && npm pack --dry-run
authorize-source:
needs: verify
runs-on: ubuntu-latest
if: startsWith(github.ref, 'refs/tags/v') || (github.event_name == 'workflow_dispatch' && (inputs.publish == true || inputs.publish_ghcr == true))
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Require a main-reachable release commit
run: |
git fetch --no-tags origin main:refs/remotes/origin/main
if [ "$GITHUB_EVENT_NAME" = "workflow_dispatch" ] && [ "$GITHUB_REF" != "refs/heads/main" ]; then
echo "::error::Manual publication must be dispatched from main."
exit 1
fi
if ! git merge-base --is-ancestor "$GITHUB_SHA" origin/main; then
echo "::error::Release commit is not reachable from origin/main."
exit 1
fi
npm:
needs: [verify, authorize-source]
runs-on: ubuntu-latest
if: startsWith(github.ref, 'refs/tags/v') || (github.event_name == 'workflow_dispatch' && inputs.publish == true)
permissions:
contents: read
id-token: write
steps:
- uses: actions/checkout@v6
- name: Set up Node 24 for npm
uses: actions/setup-node@v6
with:
node-version: 24
registry-url: https://registry.npmjs.org
cache: npm
- name: Install dependencies
run: npm ci
- name: Verify release metadata
run: npm run release:check
- name: Publish patina-cli
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
run: npm publish --access public --provenance
- name: Publish patina-humanizer alias
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
run: cd packages/patina-humanizer && npm publish --access public --provenance
github-release:
needs: [verify, npm]
runs-on: ubuntu-latest
# Tag pushes only: workflow_dispatch dry-runs must not create releases,
# and a release is only recorded after npm publish actually succeeded.
if: startsWith(github.ref, 'refs/tags/v')
permissions:
contents: write
steps:
- uses: actions/checkout@v6
- name: Extract changelog notes for this version
run: |
node <<'EOF' > release-notes.md
const { readFileSync } = require("node:fs");
const changelog = readFileSync("CHANGELOG.md", "utf8");
const version = process.env.GITHUB_REF_NAME.replace(/^v/, "");
const start = changelog.indexOf("## " + version + " — ");
if (start === -1) { console.error("no CHANGELOG section for " + version); process.exit(1); }
let end = changelog.indexOf("\n## ", start + 1);
if (end === -1) end = changelog.length;
console.log(changelog.slice(start, end).trim());
EOF
cat release-notes.md | head -3
- name: Create GitHub release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
VERSION="${GITHUB_REF_NAME#v}"
gh release create "$GITHUB_REF_NAME" \
--title "v${VERSION}" \
--notes-file release-notes.md \
--latest
ghcr:
needs: [verify, authorize-source]
runs-on: ubuntu-latest
if: github.event_name == 'workflow_dispatch' && inputs.publish_ghcr == true
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v6
- name: Log in to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Docker metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ghcr.io/devswha/patina
tags: |
type=raw,value=latest
type=semver,pattern={{version}}
- name: Build and publish image
uses: docker/build-push-action@v6
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}