From c16a7eb13d65c612fee66d13d2f4800e9c053dcd Mon Sep 17 00:00:00 2001 From: kitsuyui Date: Fri, 12 Jun 2026 00:03:13 +0900 Subject: [PATCH] ci: add release verification workflow --- .github/workflows/release.yml | 52 ++++++++++++++++++++++++++++++ README.md | 12 +++++++ package.json | 3 +- scripts/verify-release-version.mjs | 49 ++++++++++++++++++++++++++++ 4 files changed, 115 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/release.yml create mode 100644 scripts/verify-release-version.mjs diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..8aca2db --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,52 @@ +name: Release + +on: + release: + types: [published, prereleased, released] + workflow_dispatch: + inputs: + tag: + description: Existing release tag to verify, such as v0.9. + required: true + type: string + +permissions: + contents: read + +jobs: + verify-release: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + with: + fetch-depth: 0 + ref: ${{ github.event.release.tag_name || inputs.tag }} + + - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2 + with: + bun-version: 1.3.11 + + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 + with: + node-version: 24 + + - name: Install dependencies + run: bun install --frozen-lockfile + + - name: Lint + run: bun run lint + + - name: Test + run: bun run test + + - name: Build + run: bun run build + + - name: Verify dist is up to date + run: git diff --exit-code -- dist + + - name: Verify release tag and package version + env: + RELEASE_TAG: ${{ github.event.release.tag_name || inputs.tag }} + run: bun run release:verify diff --git a/README.md b/README.md index 2a3684d..374126c 100644 --- a/README.md +++ b/README.md @@ -125,6 +125,18 @@ This sets up the following Git hooks: CI still runs the complete suite on every pull request and push to main; the hooks bring that feedback earlier in your workflow. +## Release process + +Releases are published from GitHub Releases and version tags. The release +workflow verifies the tagged commit by installing dependencies, running lint and +tests, rebuilding `dist/`, and checking that the release tag matches +`package.json`. + +Before publishing a release, update `package.json` to the release version, +rebuild and commit `dist/`, and create a GitHub Release for the matching tag. +Use the manual Release workflow dispatch to re-run the same verification for an +existing tag. + ## License MIT diff --git a/package.json b/package.json index 970aa61..33dd9b8 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,8 @@ "test": "vitest run --coverage", "lint": "biome check ./", "format": "biome check --write ./", - "generate-json-schema": "typescript-json-schema --strictNullChecks true --noExtraProps true --required src/rules.ts RuleStringPatterns > ./schema.json" + "generate-json-schema": "typescript-json-schema --strictNullChecks true --noExtraProps true --required src/rules.ts RuleStringPatterns > ./schema.json", + "release:verify": "bun ./scripts/verify-release-version.mjs" }, "dependencies": { "@actions/core": "^3.0.0", diff --git a/scripts/verify-release-version.mjs b/scripts/verify-release-version.mjs new file mode 100644 index 0000000..11fe2cb --- /dev/null +++ b/scripts/verify-release-version.mjs @@ -0,0 +1,49 @@ +#!/usr/bin/env bun + +import { existsSync, readFileSync } from 'node:fs' + +const rawTag = process.env.RELEASE_TAG || process.env.GITHUB_REF_NAME || '' + +function fail(message) { + console.error(message) + process.exit(1) +} + +if (!rawTag) { + fail('RELEASE_TAG or GITHUB_REF_NAME is required.') +} + +if (!/^v?\d+\.\d+(?:\.\d+)?(?:[-+][0-9A-Za-z.-]+)?$/.test(rawTag)) { + fail( + `Release tag must look like vMAJOR.MINOR or vMAJOR.MINOR.PATCH: ${rawTag}` + ) +} + +const tagVersion = rawTag.replace(/^v/, '') +const packageJson = JSON.parse(readFileSync('package.json', 'utf8')) +const packageVersion = packageJson.version +const acceptedVersions = new Set([packageVersion]) + +if (packageVersion.endsWith('.0')) { + acceptedVersions.add(packageVersion.slice(0, -2)) +} + +if (!acceptedVersions.has(tagVersion)) { + fail( + `Release tag ${rawTag} does not match package.json version ${packageVersion}.` + ) +} + +const actionYaml = readFileSync('action.yml', 'utf8') + +if (!/main:\s*['"]?dist\/index\.mjs['"]?/.test(actionYaml)) { + fail('action.yml must point runs.main at dist/index.mjs.') +} + +if (!existsSync('dist/index.mjs')) { + fail('dist/index.mjs is missing from the release commit.') +} + +console.log( + `Release tag ${rawTag} matches package.json version ${packageVersion}.` +)