From 093228c520e22da82eaa177efde977d60f7745e3 Mon Sep 17 00:00:00 2001 From: Sin-Kang Date: Fri, 17 Jul 2026 11:55:05 +0900 Subject: [PATCH] =?UTF-8?q?ci:=20workflow=5Fdispatch=20release=20=E2=80=94?= =?UTF-8?q?=20no=20more=20local=20tag=20pushes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Releasing previously required a local tag push, which the session permission layer blocked twice this week, falling back to manual terminal work. The workflow now supports dispatch with a version input: verifies the version against package.json, runs the full check suite, publishes to npm, and creates the tag AND the GitHub Release with notes extracted from that version's CHANGELOG section (bilingual notes preserved — this also automates what was a manual release-create step for 0.1.0). Tag-push releases still work as a fallback and now get the same auto-created Release. permissions.contents: write is required for the Release step. --- .github/workflows/publish.yml | 44 +++++++++++++++++++++++++++++++---- 1 file changed, 40 insertions(+), 4 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 0d0e203..d55a141 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -1,18 +1,32 @@ name: Publish to npm +# Two ways to release: +# 1. workflow_dispatch with a version input (preferred — no local tag work): +# gh workflow run publish.yml -f version=v0.2.0 +# Creates the tag AND the GitHub Release after publishing. +# 2. Pushing a v*.*.* tag (manual fallback) — publishes and creates the +# GitHub Release on the existing tag. +# Either way the version must match package.json. + on: push: tags: - 'v*.*.*' workflow_dispatch: + inputs: + version: + description: 'Release version incl. the v prefix, must match package.json (e.g. v0.2.0)' + required: true permissions: - contents: read + contents: write id-token: write jobs: publish: runs-on: ubuntu-latest + env: + RELEASE_TAG: ${{ github.event_name == 'push' && github.ref_name || inputs.version }} steps: - uses: actions/checkout@v5 @@ -27,12 +41,34 @@ jobs: - run: npm run test - run: npm run build - - name: Check release tag matches package version - if: startsWith(github.ref, 'refs/tags/') + - name: Check release version matches package.json + run: | + node -e "const pkg=require('./package.json'); const expected='v'+pkg.version; const actual=process.env.RELEASE_TAG; if (actual !== expected) { console.error('Release version ' + actual + ' does not match package version ' + expected); process.exit(1); }" + + - name: Extract release notes from CHANGELOG run: | - node -e "const pkg=require('./package.json'); const expected='v'+pkg.version; const actual=process.env.GITHUB_REF_NAME; if (actual !== expected) { console.error('Tag ' + actual + ' does not match package version ' + expected); process.exit(1); }" + node -e " + const fs = require('fs'); + const version = process.env.RELEASE_TAG.slice(1); + const changelog = fs.readFileSync('CHANGELOG.md', 'utf8'); + const lines = changelog.split('\n'); + const start = lines.findIndex(l => l.startsWith('## ' + version)); + if (start === -1) { console.error('No CHANGELOG section for ' + version); process.exit(1); } + let end = lines.length; + for (let i = start + 1; i < lines.length; i++) { + if (lines[i].startsWith('## ')) { end = i; break; } + } + fs.writeFileSync('release-notes.md', lines.slice(start + 1, end).join('\n').trim() + '\n'); + " - name: Publish package run: npm publish --access public --provenance env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + + - name: Create GitHub Release (creates the tag on workflow_dispatch) + uses: softprops/action-gh-release@v2 + with: + tag_name: ${{ env.RELEASE_TAG }} + name: ${{ env.RELEASE_TAG }} + body_path: release-notes.md