Skip to content

Commit 212cc40

Browse files
authored
Merge pull request #19 from aredotna/fixes-permissions
Fixes up release/publish workflow
2 parents 1c41352 + 0cd7e25 commit 212cc40

3 files changed

Lines changed: 171 additions & 55 deletions

File tree

.github/workflows/publish.yml

Lines changed: 48 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,90 +1,86 @@
11
name: Publish
22

33
on:
4-
pull_request_target:
5-
types: [closed]
6-
7-
concurrency:
8-
group: release-main
9-
cancel-in-progress: false
4+
push:
5+
tags: ["v*"]
6+
workflow_dispatch:
7+
inputs:
8+
tag:
9+
description: Tag to publish (for example v0.7.1)
10+
required: true
11+
type: string
1012

1113
jobs:
1214
publish:
13-
if: github.event.pull_request.merged == true && github.event.pull_request.base.ref == 'main'
1415
runs-on: ubuntu-latest
1516
permissions:
1617
contents: write
1718
id-token: write
1819

1920
steps:
20-
- name: Determine release bump from labels
21-
id: release_meta
22-
uses: actions/github-script@v7
23-
with:
24-
script: |
25-
const labels = (context.payload.pull_request.labels || []).map((label) => label.name);
26-
const candidates = ["major", "minor", "patch"];
27-
const matched = candidates.filter((candidate) => labels.includes(candidate));
28-
29-
if (matched.length === 0) {
30-
core.notice("No release label found (major|minor|patch). Skipping publish.");
31-
core.setOutput("should_release", "false");
32-
return;
33-
}
34-
35-
if (matched.length > 1) {
36-
core.setFailed(`Multiple release labels found: ${matched.join(", ")}. Keep exactly one of major|minor|patch.`);
37-
return;
38-
}
39-
40-
core.info(`Selected release bump: ${matched[0]}`);
41-
core.setOutput("should_release", "true");
42-
core.setOutput("bump", matched[0]);
21+
- name: Resolve tag to publish
22+
id: publish_meta
23+
shell: bash
24+
run: |
25+
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
26+
TAG="${{ inputs.tag }}"
27+
else
28+
TAG="${GITHUB_REF_NAME}"
29+
fi
30+
case "$TAG" in
31+
v*) ;;
32+
*)
33+
echo "Tag must start with v: $TAG"
34+
exit 1
35+
;;
36+
esac
37+
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
4338
4439
- uses: actions/checkout@v4
45-
if: steps.release_meta.outputs.should_release == 'true'
4640
with:
47-
ref: main
41+
ref: ${{ steps.publish_meta.outputs.tag }}
4842
fetch-depth: 0
4943

5044
- uses: actions/setup-node@v4
51-
if: steps.release_meta.outputs.should_release == 'true'
5245
with:
5346
node-version: 20
5447
cache: npm
5548
registry-url: https://registry.npmjs.org
5649

5750
- run: npm ci
58-
if: steps.release_meta.outputs.should_release == 'true'
5951

6052
- run: npm run check
61-
if: steps.release_meta.outputs.should_release == 'true'
6253
env:
6354
ARENA_VCR_MODE: replay
6455
ARENA_API_URL: https://staging-api.are.na
6556

66-
- name: Configure git author
67-
if: steps.release_meta.outputs.should_release == 'true'
57+
- name: Read package version
58+
id: package_meta
59+
shell: bash
6860
run: |
69-
git config user.name "github-actions[bot]"
70-
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
61+
VERSION="$(node -p "require('./package.json').version")"
62+
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
7163
72-
- name: Bump version and create tag
73-
id: version
74-
if: steps.release_meta.outputs.should_release == 'true'
64+
- name: Check whether version is already on npm
65+
id: npm_state
66+
shell: bash
7567
run: |
76-
NEW_TAG="$(npm version "${{ steps.release_meta.outputs.bump }}" -m "release: %s")"
77-
echo "tag=${NEW_TAG}" >> "$GITHUB_OUTPUT"
78-
79-
- name: Push release commit and tags
80-
if: steps.release_meta.outputs.should_release == 'true'
81-
run: git push origin HEAD:main --follow-tags
68+
if npm view "@aredotna/cli@${{ steps.package_meta.outputs.version }}" version >/dev/null 2>&1; then
69+
echo "already_published=true" >> "$GITHUB_OUTPUT"
70+
else
71+
echo "already_published=false" >> "$GITHUB_OUTPUT"
72+
fi
8273
8374
- run: npm publish --provenance --access public
84-
if: steps.release_meta.outputs.should_release == 'true'
75+
if: steps.npm_state.outputs.already_published != 'true'
8576

86-
- name: Create GitHub Release
87-
if: steps.release_meta.outputs.should_release == 'true'
77+
- name: Ensure GitHub Release exists
8878
env:
8979
GH_TOKEN: ${{ github.token }}
90-
run: gh release create "${{ steps.version.outputs.tag }}" --generate-notes
80+
shell: bash
81+
run: |
82+
if gh release view "${{ steps.publish_meta.outputs.tag }}" >/dev/null 2>&1; then
83+
echo "GitHub Release already exists for ${{ steps.publish_meta.outputs.tag }}"
84+
else
85+
gh release create "${{ steps.publish_meta.outputs.tag }}" --generate-notes
86+
fi

.github/workflows/release.yml

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
name: Release
2+
3+
on:
4+
pull_request_target:
5+
types: [closed]
6+
workflow_dispatch:
7+
inputs:
8+
bump:
9+
description: Version bump type
10+
required: true
11+
type: choice
12+
options: [patch, minor, major]
13+
14+
concurrency:
15+
group: release-main
16+
cancel-in-progress: false
17+
18+
jobs:
19+
release:
20+
if: >-
21+
github.event_name == 'workflow_dispatch' ||
22+
(github.event.pull_request.merged == true &&
23+
github.event.pull_request.base.ref == 'main')
24+
runs-on: ubuntu-latest
25+
permissions:
26+
contents: write
27+
28+
steps:
29+
- name: Determine release bump from labels or input
30+
id: release_meta
31+
uses: actions/github-script@v7
32+
with:
33+
script: |
34+
if (context.eventName === "workflow_dispatch") {
35+
const bump = context.payload.inputs.bump;
36+
core.info(`Manual dispatch: releasing with bump=${bump}`);
37+
core.setOutput("should_release", "true");
38+
core.setOutput("bump", bump);
39+
return;
40+
}
41+
42+
const labels = (context.payload.pull_request.labels || []).map((label) => label.name);
43+
const candidates = ["major", "minor", "patch"];
44+
const matched = candidates.filter((candidate) => labels.includes(candidate));
45+
46+
if (matched.length === 0) {
47+
core.notice("No release label found (major|minor|patch). Skipping release.");
48+
core.setOutput("should_release", "false");
49+
return;
50+
}
51+
52+
if (matched.length > 1) {
53+
core.setFailed(`Multiple release labels found: ${matched.join(", ")}. Keep exactly one of major|minor|patch.`);
54+
return;
55+
}
56+
57+
core.info(`Selected release bump: ${matched[0]}`);
58+
core.setOutput("should_release", "true");
59+
core.setOutput("bump", matched[0]);
60+
61+
- uses: actions/checkout@v4
62+
if: steps.release_meta.outputs.should_release == 'true'
63+
with:
64+
ref: main
65+
fetch-depth: 0
66+
token: ${{ secrets.RELEASE_TOKEN != '' && secrets.RELEASE_TOKEN || github.token }}
67+
68+
- name: Ensure main has not advanced past merged PR
69+
if: >-
70+
steps.release_meta.outputs.should_release == 'true' &&
71+
github.event_name == 'pull_request_target'
72+
run: |
73+
CURRENT_HEAD="$(git rev-parse HEAD)"
74+
EXPECTED_HEAD="${{ github.event.pull_request.merge_commit_sha }}"
75+
if [ "$CURRENT_HEAD" != "$EXPECTED_HEAD" ]; then
76+
echo "main advanced after this PR merged."
77+
echo "Expected: $EXPECTED_HEAD"
78+
echo "Current: $CURRENT_HEAD"
79+
echo "Re-run the Release workflow manually after reviewing current main."
80+
exit 1
81+
fi
82+
83+
- uses: actions/setup-node@v4
84+
if: steps.release_meta.outputs.should_release == 'true'
85+
with:
86+
node-version: 20
87+
cache: npm
88+
89+
- run: npm ci
90+
if: steps.release_meta.outputs.should_release == 'true'
91+
92+
- run: npm run check
93+
if: steps.release_meta.outputs.should_release == 'true'
94+
env:
95+
ARENA_VCR_MODE: replay
96+
ARENA_API_URL: https://staging-api.are.na
97+
98+
- name: Configure git author
99+
if: steps.release_meta.outputs.should_release == 'true'
100+
run: |
101+
git config user.name "github-actions[bot]"
102+
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
103+
104+
- name: Bump version and create tag
105+
if: steps.release_meta.outputs.should_release == 'true'
106+
run: |
107+
npm version "${{ steps.release_meta.outputs.bump }}" -m "release: %s"
108+
109+
- name: Push release commit and tags
110+
if: steps.release_meta.outputs.should_release == 'true'
111+
run: git push origin HEAD:main --follow-tags

RELEASE.md

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ Use this checklist to cut a new npm release for `@aredotna/cli`.
44

55
Publishing is automated by GitHub Actions when a PR with a release label is merged to `main`.
66
Use exactly one label on the PR: `major`, `minor`, or `patch`.
7-
The workflow bumps the version, publishes to npm with trusted publishing (OIDC), pushes the release commit and tag, and creates a GitHub Release.
7+
The `Release` workflow bumps the version and pushes the release commit and tag.
8+
That tag then triggers the `Publish` workflow, which publishes to npm with trusted publishing (OIDC) and creates the GitHub Release.
89

910
## 1) Preflight
1011

@@ -26,11 +27,17 @@ npm run check
2627

2728
- [ ] Merge the labeled PR.
2829

29-
This triggers `.github/workflows/publish.yml`.
30+
This triggers `.github/workflows/release.yml`.
3031

3132
## 3) Confirm publish workflow success
3233

33-
- [ ] Wait for the "Publish" workflow on the pushed tag to complete successfully:
34+
- [ ] Wait for the `Release` workflow to complete successfully:
35+
36+
```bash
37+
gh run list --workflow release.yml --limit 5
38+
```
39+
40+
- [ ] Wait for the `Publish` workflow on the created tag to complete successfully:
3441

3542
```bash
3643
gh run list --workflow publish.yml --limit 5
@@ -71,3 +78,5 @@ arena whoami --json
7178
- Publish job fails with OIDC/trusted publishing error: verify trusted publisher settings on npm exactly match `aredotna/cli` and workflow file `publish.yml`.
7279
- Workflow skips publishing: merged PR did not contain one of `major`, `minor`, or `patch` labels.
7380
- Workflow fails with multiple release labels: keep exactly one of `major|minor|patch` on the PR.
81+
- Release job says `main` advanced after the PR merged: re-run the `Release` workflow manually after reviewing current `main`.
82+
- Publish job fails after the tag already exists: re-run the `Publish` workflow with the existing tag instead of creating a new version bump.

0 commit comments

Comments
 (0)