fix: v9 module path release workflow (#559) #38
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Release Please | |
| on: | |
| push: | |
| branches: | |
| - main | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| jobs: | |
| release-please: | |
| runs-on: ubuntu-latest | |
| outputs: | |
| pr: ${{ steps.release.outputs.pr }} | |
| steps: | |
| - name: Generate GitHub App token | |
| id: app-token | |
| uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 | |
| with: | |
| app-id: ${{ vars.SDK_BOT_APP_ID }} | |
| private-key: ${{ secrets.SDK_BOT_PRIVATE_KEY }} | |
| # skip-github-release means release-please opens/updates release | |
| # PRs and updates CHANGELOG.md as usual, but does NOT tag or create | |
| # the GitHub Release on merge. Those are owned by publish-release | |
| # below so we can set the Release body from the rich CHANGELOG.md | |
| # section instead of release-please's terse default rendering. | |
| - uses: googleapis/release-please-action@45996ed1f6d02564a971a2fa1b5860e934307cf7 # v5.0.0 | |
| id: release | |
| with: | |
| token: ${{ steps.app-token.outputs.token }} | |
| skip-github-release: true | |
| - name: Checkout release PR branch | |
| if: steps.release.outputs.pr | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| ref: ${{ fromJSON(steps.release.outputs.pr).headBranchName }} | |
| token: ${{ steps.app-token.outputs.token }} | |
| # Go modules require the module path and all import statements to | |
| # include the major version (e.g. /v7). release-please doesn't handle | |
| # this, so when a release PR bumps the major version we automatically | |
| # rewrite go.mod, imports, and READMEs on the release PR branch. | |
| - name: Migrate Go module path for major version bump | |
| if: steps.release.outputs.pr | |
| run: | | |
| CURRENT_MAJOR=$(grep -oP 'module github\.com/workos/workos-go/v\K[0-9]+' go.mod) | |
| NEW_VERSION=$(jq -r '."."' .release-please-manifest.json) | |
| NEW_MAJOR="${NEW_VERSION%%.*}" | |
| if [ "$CURRENT_MAJOR" = "$NEW_MAJOR" ]; then | |
| echo "No major version change ($CURRENT_MAJOR → $NEW_MAJOR), skipping" | |
| exit 0 | |
| fi | |
| echo "Major version bump detected: v$CURRENT_MAJOR → v$NEW_MAJOR" | |
| sed -i "s|module github.com/workos/workos-go/v${CURRENT_MAJOR}|module github.com/workos/workos-go/v${NEW_MAJOR}|" go.mod | |
| find . -name '*.go' -not -path './.git/*' -type f \ | |
| -exec sed -i "s|github.com/workos/workos-go/v${CURRENT_MAJOR}|github.com/workos/workos-go/v${NEW_MAJOR}|g" {} + | |
| find . -name 'README.md' -not -path './.git/*' -type f \ | |
| -exec sed -i "s|workos-go/v${CURRENT_MAJOR}|workos-go/v${NEW_MAJOR}|g" {} + | |
| git config user.name "workos-sdk-automation[bot]" | |
| git config user.email "255426317+workos-sdk-automation[bot]@users.noreply.github.com" | |
| git add -A | |
| if git diff --staged --quiet; then | |
| echo "No changes to commit" | |
| else | |
| git commit -m "chore: update go.mod and import paths for v${NEW_MAJOR}" | |
| git push | |
| fi | |
| # Inline pending changelog fragments under the version heading | |
| # release-please just wrote in CHANGELOG.md. For PRs that have a | |
| # fragment (the autogen flow always writes one), drop the line | |
| # release-please rendered and use the fragment instead. For PRs | |
| # without a fragment (typical for human-authored PRs), keep what | |
| # release-please wrote. Fragments are deleted in the same commit. | |
| # Idempotent: if no fragments exist, skip silently. | |
| - name: Inline rich changelog fragments | |
| if: steps.release.outputs.pr | |
| env: | |
| PR_JSON: ${{ steps.release.outputs.pr }} | |
| run: | | |
| set -euo pipefail | |
| shopt -s nullglob | |
| fragments=(.changelog-pending/*.md) | |
| if [ ${#fragments[@]} -eq 0 ]; then | |
| echo "No .changelog-pending fragments; leaving release-please CHANGELOG.md as-is." | |
| exit 0 | |
| fi | |
| VERSION=$(echo "$PR_JSON" | jq -r '.title' | grep -oE '[0-9]+\.[0-9]+\.[0-9]+') | |
| export VERSION | |
| python3 - <<'PY' | |
| import os, re, pathlib, glob | |
| version = os.environ["VERSION"] | |
| # Load fragments + extract the PR number each one covers from | |
| # its top-line "* [#NN](url) ...". | |
| fragments = [] | |
| covered = set() | |
| for path in sorted(glob.glob(".changelog-pending/*.md")): | |
| body = pathlib.Path(path).read_text().rstrip() | |
| m = re.search(r'\[#(\d+)\]', body) | |
| if m: | |
| covered.add(m.group(1)) | |
| fragments.append(body) | |
| changelog = pathlib.Path("CHANGELOG.md") | |
| text = changelog.read_text() | |
| section_re = re.compile( | |
| r'(^## \[' + re.escape(version) + r'\][^\n]*\n)(.*?)(?=^## |\Z)', | |
| re.MULTILINE | re.DOTALL, | |
| ) | |
| match = section_re.search(text) | |
| if not match: | |
| raise SystemExit(f"Could not find '## [{version}]' heading in CHANGELOG.md") | |
| heading, body = match.group(1), match.group(2) | |
| # Drop any release-please line that references a PR we have a | |
| # fragment for. | |
| kept = [] | |
| for line in body.split("\n"): | |
| if any(pr in covered for pr in re.findall(r'\[#(\d+)\]', line)): | |
| continue | |
| kept.append(line) | |
| filtered = "\n".join(kept) | |
| # Collapse "### Heading\n(blank lines)\n" with nothing under | |
| # it. Run repeatedly until stable in case of stacked empties. | |
| empty_section = re.compile( | |
| r'^### [^\n]*\n(?:\s*\n)*(?=^### |\Z)', | |
| re.MULTILINE, | |
| ) | |
| while True: | |
| new = empty_section.sub('', filtered) | |
| if new == filtered: | |
| break | |
| filtered = new | |
| filtered = filtered.strip() | |
| parts = [] | |
| if filtered: | |
| parts.append(filtered) | |
| parts.extend(fragments) | |
| new_body = "\n\n".join(parts) | |
| new_text = text[:match.start()] + heading + "\n" + new_body + "\n\n" + text[match.end():] | |
| changelog.write_text(new_text) | |
| PY | |
| git config user.name "workos-sdk-automation[bot]" | |
| git config user.email "255426317+workos-sdk-automation[bot]@users.noreply.github.com" | |
| git rm .changelog-pending/*.md | |
| git add CHANGELOG.md | |
| git commit -m "chore: inline release notes from .changelog-pending" | |
| git push | |
| # Detect when a release-please release PR has merged, then tag and | |
| # create the GitHub Release whose body is extracted from CHANGELOG.md | |
| # (now rich, after the inline step above). Runs on every push to main; | |
| # the detect step gates everything else. | |
| publish-release: | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| steps: | |
| - name: Generate token | |
| id: generate-token | |
| uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 | |
| with: | |
| app-id: ${{ vars.SDK_BOT_APP_ID }} | |
| private-key: ${{ secrets.SDK_BOT_PRIVATE_KEY }} | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| fetch-depth: 0 | |
| token: ${{ steps.generate-token.outputs.token }} | |
| - name: Detect release PR merge | |
| id: detect | |
| run: | | |
| set -euo pipefail | |
| SUBJECT=$(git log -1 --format=%s) | |
| # release-please's default release PR title: | |
| # chore(main): release X.Y.Z | |
| if [[ "$SUBJECT" =~ ^chore\(.*\):[[:space:]]release[[:space:]]([0-9]+\.[0-9]+\.[0-9]+) ]]; then | |
| VERSION="${BASH_REMATCH[1]}" | |
| echo "is-release=true" >> "$GITHUB_OUTPUT" | |
| echo "version=$VERSION" >> "$GITHUB_OUTPUT" | |
| echo "Detected release PR merge for v$VERSION" | |
| else | |
| echo "Not a release PR merge: $SUBJECT" | |
| echo "is-release=false" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Extract release notes from CHANGELOG.md | |
| if: steps.detect.outputs.is-release == 'true' | |
| env: | |
| VERSION: ${{ steps.detect.outputs.version }} | |
| run: | | |
| set -euo pipefail | |
| awk -v v="$VERSION" ' | |
| $0 ~ ("^## \\[" v "\\]") { found=1; next } | |
| found && /^## \[/ { exit } | |
| found | |
| ' CHANGELOG.md > /tmp/release-notes.md | |
| if [ ! -s /tmp/release-notes.md ]; then | |
| echo "::error::CHANGELOG.md has no body for v$VERSION" | |
| exit 1 | |
| fi | |
| - name: Tag and create GitHub Release | |
| if: steps.detect.outputs.is-release == 'true' | |
| env: | |
| GH_TOKEN: ${{ steps.generate-token.outputs.token }} | |
| VERSION: ${{ steps.detect.outputs.version }} | |
| run: | | |
| set -euo pipefail | |
| TAG="v$VERSION" | |
| if gh release view "$TAG" >/dev/null 2>&1; then | |
| echo "Release $TAG already exists; skipping." | |
| exit 0 | |
| fi | |
| git config user.name "workos-sdk-automation[bot]" | |
| git config user.email "255426317+workos-sdk-automation[bot]@users.noreply.github.com" | |
| git tag -a "$TAG" -m "Release $TAG" | |
| git push origin "$TAG" | |
| gh release create "$TAG" \ | |
| --title "$TAG" \ | |
| --notes-file /tmp/release-notes.md |