From fdba78e3429207308318d1ee30f7f2e16b1088f2 Mon Sep 17 00:00:00 2001 From: dzucconi Date: Fri, 13 Mar 2026 14:43:55 -0400 Subject: [PATCH] Sets up publishing workflow --- .github/workflows/publish.yml | 90 +++++++++++++++++++++++++++++++++++ RELEASE.md | 69 +++++++++------------------ 2 files changed, 112 insertions(+), 47 deletions(-) create mode 100644 .github/workflows/publish.yml diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..26e72b1 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,90 @@ +name: Publish + +on: + pull_request_target: + types: [closed] + +concurrency: + group: release-main + cancel-in-progress: false + +jobs: + publish: + if: github.event.pull_request.merged == true && github.event.pull_request.base.ref == 'main' + runs-on: ubuntu-latest + permissions: + contents: write + id-token: write + + steps: + - name: Determine release bump from labels + id: release_meta + uses: actions/github-script@v7 + with: + script: | + const labels = (context.payload.pull_request.labels || []).map((label) => label.name); + const candidates = ["major", "minor", "patch"]; + const matched = candidates.filter((candidate) => labels.includes(candidate)); + + if (matched.length === 0) { + core.notice("No release label found (major|minor|patch). Skipping publish."); + core.setOutput("should_release", "false"); + return; + } + + if (matched.length > 1) { + core.setFailed(`Multiple release labels found: ${matched.join(", ")}. Keep exactly one of major|minor|patch.`); + return; + } + + core.info(`Selected release bump: ${matched[0]}`); + core.setOutput("should_release", "true"); + core.setOutput("bump", matched[0]); + + - uses: actions/checkout@v4 + if: steps.release_meta.outputs.should_release == 'true' + with: + ref: main + fetch-depth: 0 + + - uses: actions/setup-node@v4 + if: steps.release_meta.outputs.should_release == 'true' + with: + node-version: 20 + cache: npm + registry-url: https://registry.npmjs.org + + - run: npm ci + if: steps.release_meta.outputs.should_release == 'true' + + - run: npm run check + if: steps.release_meta.outputs.should_release == 'true' + env: + ARENA_VCR_MODE: replay + ARENA_API_URL: https://staging-api.are.na + + - name: Configure git author + if: steps.release_meta.outputs.should_release == 'true' + run: | + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + + - name: Bump version and create tag + id: version + if: steps.release_meta.outputs.should_release == 'true' + run: | + NEW_TAG="$(npm version "${{ steps.release_meta.outputs.bump }}" -m "release: %s")" + echo "tag=${NEW_TAG}" >> "$GITHUB_OUTPUT" + + - name: Push release commit and tags + if: steps.release_meta.outputs.should_release == 'true' + run: git push origin HEAD:main --follow-tags + + - run: npm publish --provenance --access public + if: steps.release_meta.outputs.should_release == 'true' + + - name: Create GitHub Release + if: steps.release_meta.outputs.should_release == 'true' + env: + GH_TOKEN: ${{ github.token }} + run: gh release create "${{ steps.version.outputs.tag }}" --generate-notes diff --git a/RELEASE.md b/RELEASE.md index c5595ec..1f8d36e 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -2,67 +2,41 @@ Use this checklist to cut a new npm release for `@aredotna/cli`. +Publishing is automated by GitHub Actions when a PR with a release label is merged to `main`. +Use exactly one label on the PR: `major`, `minor`, or `patch`. +The workflow bumps the version, publishes to npm with trusted publishing (OIDC), pushes the release commit and tag, and creates a GitHub Release. + ## 1) Preflight -- [ ] Ensure you are on the intended release branch: +- [ ] Ensure release label is present on the PR: ```bash -git branch --show-current +gh pr view --json labels --jq '.labels[].name' ``` -- [ ] Ensure working tree is clean: - -```bash -git status --short -``` +- [ ] Verify exactly one of `major|minor|patch` is set. -- [ ] Run full checks: +- [ ] (Optional) Run full checks locally before merge: ```bash npm run check ``` -## 2) Confirm npm auth - -- [ ] Verify npm login: +## 2) Merge the PR to `main` -```bash -npm whoami -``` - -If this fails with `401 Unauthorized`, authenticate first: - -```bash -npm login -``` +- [ ] Merge the labeled PR. -## 3) Bump version - -- [ ] Choose version type (`patch`, `minor`, `major`) and bump: - -```bash -npm version patch -``` - -This updates `package.json`, creates a release commit, and creates a git tag. - -## 4) Publish package - -- [ ] Publish to npm: - -```bash -npm publish -``` +This triggers `.github/workflows/publish.yml`. -## 5) Push commit + tag +## 3) Confirm publish workflow success -- [ ] Push branch and tags: +- [ ] Wait for the "Publish" workflow on the pushed tag to complete successfully: ```bash -git push origin "$(git branch --show-current)" --follow-tags +gh run list --workflow publish.yml --limit 5 ``` -## 6) Verify release +## 4) Verify release - [ ] Check published version: @@ -70,13 +44,14 @@ git push origin "$(git branch --show-current)" --follow-tags npm view @aredotna/cli version ``` -- [ ] Confirm tag exists remotely: +- [ ] Verify GitHub Release exists for the tag: ```bash -git ls-remote --tags origin | tail +VERSION="$(npm view @aredotna/cli version)" +gh release view "v${VERSION}" ``` -## 7) Optional post-release smoke test +## 5) Optional post-release smoke test - [ ] Install latest globally: @@ -93,6 +68,6 @@ arena whoami --json ## Troubleshooting -- `npm whoami` fails with 401: run `npm login` and retry. -- `npm publish` says version exists: bump again (`npm version patch`) and republish. -- Push fails due to remote changes: rebase/merge branch, rerun checks, then push again. +- Publish job fails with OIDC/trusted publishing error: verify trusted publisher settings on npm exactly match `aredotna/cli` and workflow file `publish.yml`. +- Workflow skips publishing: merged PR did not contain one of `major`, `minor`, or `patch` labels. +- Workflow fails with multiple release labels: keep exactly one of `major|minor|patch` on the PR.