|
1 | 1 | name: Release |
2 | 2 |
|
3 | 3 | on: |
4 | | - workflow_dispatch: |
5 | | - inputs: |
6 | | - increment: |
7 | | - description: "Version bump (patch, minor, major)" |
8 | | - required: true |
9 | | - default: "patch" |
10 | | - type: choice |
11 | | - options: |
12 | | - - patch |
13 | | - - minor |
14 | | - - major |
| 4 | + push: |
| 5 | + tags: |
| 6 | + - "v*.*.*" |
15 | 7 |
|
16 | 8 | concurrency: |
17 | 9 | group: release-${{ github.workflow }}-${{ github.ref }} |
|
30 | 22 | # "Unsupported GitHub Actions runner environment: self-hosted". |
31 | 23 | runs-on: ubuntu-latest |
32 | 24 | permissions: |
33 | | - contents: write |
| 25 | + contents: read |
34 | 26 | id-token: write |
35 | 27 | steps: |
36 | 28 | - uses: actions/checkout@v6 |
|
47 | 39 | check-latest: true |
48 | 40 | cache: pnpm |
49 | 41 |
|
50 | | - - run: git config user.name "github-actions[bot]" |
51 | | - - run: git config user.email "github-actions[bot]@users.noreply.github.com" |
52 | 42 | - run: pnpm install --frozen-lockfile |
53 | 43 |
|
54 | 44 | - name: Validate package metadata for trusted publishing |
@@ -92,47 +82,73 @@ jobs: |
92 | 82 | console.log("Package metadata validated."); |
93 | 83 | NODE |
94 | 84 |
|
95 | | - - run: pnpm run lint |
96 | | - - run: pnpm run typecheck |
97 | | - - run: pnpm run build |
98 | | - |
99 | | - # Sync the working tree to the currently published version, then let |
100 | | - # release-it perform the requested bump from that baseline. |
101 | | - - name: Bump version from npm registry |
| 85 | + - name: Validate release tag |
| 86 | + env: |
| 87 | + RELEASE_SHA: ${{ github.sha }} |
| 88 | + RELEASE_TAG: ${{ github.ref_name }} |
102 | 89 | run: | |
103 | | - LATEST=$(npm view acpx version 2>/dev/null || echo "0.0.0") |
104 | | - echo "Latest on npm: $LATEST" |
105 | | - TARGET=$(node - "$LATEST" "${{ inputs.increment }}" <<'NODE' |
106 | | - const [version, increment] = process.argv.slice(2); |
107 | | - const match = /^(\d+)\.(\d+)\.(\d+)$/.exec(version); |
108 | | - if (!match) { |
109 | | - throw new Error(`Unsupported semver: ${version}`); |
110 | | - } |
111 | | - const [major, minor, patch] = match.slice(1).map(Number); |
| 90 | + set -euo pipefail |
| 91 | + git fetch --no-tags origin main --depth=1 |
112 | 92 |
|
113 | | - if (increment === "patch") { |
114 | | - console.log(`${major}.${minor}.${patch + 1}`); |
115 | | - process.exit(0); |
| 93 | + node - <<'NODE' |
| 94 | + const { execFileSync } = require("node:child_process"); |
| 95 | + const { readFileSync } = require("node:fs"); |
| 96 | +
|
| 97 | + const releaseTag = process.env.RELEASE_TAG ?? ""; |
| 98 | + const releaseSha = process.env.RELEASE_SHA ?? ""; |
| 99 | + const semverTag = /^v\d+\.\d+\.\d+$/; |
| 100 | +
|
| 101 | + if (!semverTag.test(releaseTag)) { |
| 102 | + console.error( |
| 103 | + `Release tags must match vX.Y.Z; received ${releaseTag || "<missing>"}.` |
| 104 | + ); |
| 105 | + process.exit(1); |
116 | 106 | } |
117 | | - if (increment === "minor") { |
118 | | - console.log(`${major}.${minor + 1}.0`); |
119 | | - process.exit(0); |
| 107 | +
|
| 108 | + const pkg = JSON.parse(readFileSync("package.json", "utf8")); |
| 109 | + const expectedTag = `v${pkg.version}`; |
| 110 | +
|
| 111 | + if (releaseTag !== expectedTag) { |
| 112 | + console.error( |
| 113 | + `Release tag ${releaseTag} does not match package.json version ${pkg.version}; expected ${expectedTag}.` |
| 114 | + ); |
| 115 | + process.exit(1); |
120 | 116 | } |
121 | | - if (increment === "major") { |
122 | | - console.log(`${major + 1}.0.0`); |
123 | | - process.exit(0); |
| 117 | +
|
| 118 | + try { |
| 119 | + execFileSync( |
| 120 | + "git", |
| 121 | + ["merge-base", "--is-ancestor", releaseSha, "origin/main"], |
| 122 | + { stdio: "ignore" } |
| 123 | + ); |
| 124 | + } catch { |
| 125 | + console.error( |
| 126 | + `Tagged commit ${releaseSha} is not contained in origin/main.` |
| 127 | + ); |
| 128 | + process.exit(1); |
124 | 129 | } |
125 | 130 |
|
126 | | - throw new Error(`Unsupported increment: ${increment}`); |
| 131 | + console.log( |
| 132 | + `Release tag ${releaseTag} matches package.json and points to a commit on origin/main.` |
| 133 | + ); |
127 | 134 | NODE |
128 | | - ) |
129 | | - npm version --no-git-tag-version "$LATEST" --allow-same-version |
130 | | - echo "VERSION=$TARGET" >> "$GITHUB_ENV" |
131 | | - echo "Releasing: $TARGET" |
132 | 135 |
|
133 | | - - name: Release |
| 136 | + - name: Ensure version is not already published |
134 | 137 | run: | |
135 | | - pnpm exec release-it "$VERSION" --ci |
136 | | - env: |
137 | | - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} |
138 | | - NPM_CONFIG_PROVENANCE: "true" |
| 138 | + set -euo pipefail |
| 139 | + PACKAGE_VERSION=$(node -p "require('./package.json').version") |
| 140 | + PUBLISHED_VERSION=$(npm view acpx version 2>/dev/null || true) |
| 141 | +
|
| 142 | + if [ "$PUBLISHED_VERSION" = "$PACKAGE_VERSION" ]; then |
| 143 | + echo "acpx@$PACKAGE_VERSION is already published on npm." |
| 144 | + exit 1 |
| 145 | + fi |
| 146 | +
|
| 147 | + echo "Publishing acpx@$PACKAGE_VERSION" |
| 148 | +
|
| 149 | + - run: pnpm run lint |
| 150 | + - run: pnpm run typecheck |
| 151 | + - run: pnpm run build |
| 152 | + |
| 153 | + - name: Publish |
| 154 | + run: npm publish --access public --provenance |
0 commit comments