-
Notifications
You must be signed in to change notification settings - Fork 17
feat(ci): auto-open downstream bump PRs on package release #638
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
SoulPancake
wants to merge
5
commits into
main
Choose a base branch
from
feat/downstream-bump-prs
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
2f9c0a5
feat(ci): auto-open downstream bump PRs on package release
SoulPancake 32e38ee
Merge branch 'main' into feat/downstream-bump-prs
SoulPancake 7edfd39
fix(ci): address review feedback on downstream bump flow
SoulPancake 5a80ec1
fix(ci): keep app token and gpg key away from untrusted toolchain steps
SoulPancake e1162fd
Merge branch 'main' into feat/downstream-bump-prs
SoulPancake File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
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
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
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,208 @@ | ||
| name: reusable-downstream-bump | ||
|
|
||
| # Opens a "bump openfga-language" PR against a downstream repo, authored and | ||
| # GPG-signed by the same openfga-releaser-bot identity used by release-please. | ||
| # | ||
| # Invoked (via a matrix) from the pkg-*-release workflows once a release has | ||
| # been published, so downstream consumers pick up the new version automatically. | ||
| # | ||
| # NOTE: the releaser GitHub App must be installed on each target repo with | ||
| # `contents: write` + `pull-requests: write`, otherwise token minting fails. | ||
|
|
||
| on: | ||
| workflow_call: | ||
| inputs: | ||
| target-repo: | ||
| description: "Downstream repo to bump, e.g. openfga/cli" | ||
| type: string | ||
| required: true | ||
| ecosystem: | ||
| description: "js | java | go" | ||
| type: string | ||
| required: true | ||
| version: | ||
| description: "Released version (plain semver or a tag ref; normalised)" | ||
| type: string | ||
| required: true | ||
| artifact: | ||
| description: "Human name of the bumped artifact, for PR text" | ||
| type: string | ||
| required: true | ||
| secrets: | ||
| RELEASER_APP_CLIENT_ID: | ||
| required: true | ||
| RELEASER_APP_PRIVATE_KEY: | ||
| required: true | ||
| GPG_PRIVATE_KEY: | ||
| required: true | ||
| GPG_PASSPHRASE: | ||
| required: false | ||
|
|
||
| permissions: | ||
| contents: read | ||
|
|
||
| jobs: | ||
| bump: | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - name: Normalise version | ||
| id: ver | ||
| env: | ||
| RAW: ${{ inputs.version }} | ||
| run: | | ||
| set -euo pipefail | ||
| v="${RAW##*/}" # strip any pkg/<lang>/ prefix | ||
| v="${v#v}" # strip leading v | ||
| echo "version=${v}" >> "$GITHUB_OUTPUT" | ||
|
|
||
| - name: Split target owner/repo | ||
| id: repo | ||
| env: | ||
| TARGET: ${{ inputs.target-repo }} | ||
| run: | | ||
| set -euo pipefail | ||
| echo "owner=${TARGET%%/*}" >> "$GITHUB_OUTPUT" | ||
| echo "name=${TARGET##*/}" >> "$GITHUB_OUTPUT" | ||
|
|
||
| # Token scoped to the *target* repo so the bot can push a branch + open a PR. | ||
| - name: Generate GitHub App token (target repo) | ||
| id: app-token | ||
| uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 | ||
| with: | ||
| client-id: ${{ secrets.RELEASER_APP_CLIENT_ID }} | ||
| private-key: ${{ secrets.RELEASER_APP_PRIVATE_KEY }} | ||
| owner: ${{ steps.repo.outputs.owner }} | ||
| repositories: ${{ steps.repo.outputs.name }} | ||
|
|
||
| # The bump script lives in *this* repo; check it out separately so we can | ||
| # run it against the downstream checkout regardless of downstream layout. | ||
| - name: Checkout scripts (this repo) | ||
| uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | ||
| with: | ||
| path: _scripts | ||
| sparse-checkout: .github/workflows/scripts | ||
| sparse-checkout-cone-mode: false | ||
| persist-credentials: false | ||
|
|
||
| # persist-credentials: false so the App token never lands in .git/config, | ||
| # where npm lifecycle scripts could read it. Pushes authenticate | ||
| # explicitly in the final step instead. | ||
| - name: Checkout target repo | ||
| uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | ||
| with: | ||
| repository: ${{ inputs.target-repo }} | ||
| token: ${{ steps.app-token.outputs.token }} | ||
| path: target | ||
| fetch-depth: 0 | ||
| persist-credentials: false | ||
|
|
||
| - name: Set up Node (js) | ||
| if: inputs.ecosystem == 'js' | ||
| uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 | ||
| with: | ||
| node-version: '24.x' | ||
| registry-url: "https://registry.npmjs.org" | ||
|
|
||
| - name: Set up Go (go) | ||
| if: inputs.ecosystem == 'go' | ||
| uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6.5.0 | ||
| with: | ||
| # Use the toolchain the downstream repo declares, so go.mod/go.sum | ||
| # churn from a newer Go doesn't leak into the bump PR. | ||
| go-version-file: target/go.mod | ||
| cache-dependency-path: target/go.sum | ||
|
|
||
| - name: Check for existing bump PR | ||
| id: precheck | ||
| env: | ||
| GH_TOKEN: ${{ steps.app-token.outputs.token }} | ||
| TARGET_REPO: ${{ inputs.target-repo }} | ||
| ARTIFACT: ${{ inputs.artifact }} | ||
| VERSION: ${{ steps.ver.outputs.version }} | ||
| run: | | ||
| set -euo pipefail | ||
| BRANCH="chore/bump-${ARTIFACT}-${VERSION}" | ||
| echo "branch=${BRANCH}" >> "$GITHUB_OUTPUT" | ||
| # Idempotency: if a bump PR for this exact version is already open, stop. | ||
| if gh pr list --repo "${TARGET_REPO}" --state open --head "${BRANCH}" \ | ||
| --json number --jq '.[0].number' | grep -q '[0-9]'; then | ||
| echo "PR for ${BRANCH} already open; nothing to do." | ||
| echo "skip=true" >> "$GITHUB_OUTPUT" | ||
| else | ||
| echo "skip=false" >> "$GITHUB_OUTPUT" | ||
| fi | ||
|
|
||
| # Deliberately no token in the environment: the js path runs npm install, | ||
| # which executes third-party lifecycle scripts. | ||
| - name: Apply bump | ||
| id: bump | ||
| if: steps.precheck.outputs.skip != 'true' | ||
| env: | ||
| ECOSYSTEM: ${{ inputs.ecosystem }} | ||
| VERSION: ${{ steps.ver.outputs.version }} | ||
| BRANCH: ${{ steps.precheck.outputs.branch }} | ||
| working-directory: target | ||
| run: | | ||
| set -euo pipefail | ||
|
|
||
| git checkout -b "${BRANCH}" | ||
|
|
||
| # Apply the edit (script checked out in the sibling _scripts dir). | ||
| bash "${GITHUB_WORKSPACE}/_scripts/.github/workflows/scripts/downstream-bump.sh" \ | ||
| "${ECOSYSTEM}" "${VERSION}" | ||
|
|
||
| # Stage everything (gitignore still applies) so newly created files | ||
| # (e.g. a first package-lock.json) make it into the PR too. | ||
| git add -A | ||
| if git diff --cached --quiet; then | ||
| echo "No changes after bump (already up to date); nothing to do." | ||
| echo "changed=false" >> "$GITHUB_OUTPUT" | ||
| else | ||
| echo "changed=true" >> "$GITHUB_OUTPUT" | ||
| fi | ||
|
|
||
| # Imported only after the untrusted toolchain work above has finished, | ||
| # so npm lifecycle scripts never run alongside the loaded key. | ||
| - name: Import GPG key and enable signed commits | ||
| id: import-gpg | ||
| if: steps.bump.outputs.changed == 'true' | ||
| uses: crazy-max/ghaction-import-gpg@2dc316deee8e90f13e1a351ab510b4d5bc0c82cd # v7.0.0 | ||
| with: | ||
| gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }} | ||
| passphrase: ${{ secrets.GPG_PASSPHRASE }} | ||
| git_user_signingkey: true | ||
| git_commit_gpgsign: true | ||
| workdir: target | ||
|
|
||
| - name: Commit, push and open PR | ||
| if: steps.bump.outputs.changed == 'true' | ||
| env: | ||
| GH_TOKEN: ${{ steps.app-token.outputs.token }} | ||
| VERSION: ${{ steps.ver.outputs.version }} | ||
| ARTIFACT: ${{ inputs.artifact }} | ||
| TARGET_REPO: ${{ inputs.target-repo }} | ||
| BRANCH: ${{ steps.precheck.outputs.branch }} | ||
| BOT_EMAIL: ${{ steps.import-gpg.outputs.email }} | ||
| working-directory: target | ||
| run: | | ||
| set -euo pipefail | ||
|
|
||
| git config user.name "openfga-releaser-bot" | ||
| git config user.email "${BOT_EMAIL}" | ||
| git config commit.gpgSign true | ||
|
|
||
| TITLE="chore(deps): bump ${ARTIFACT} to ${VERSION}" | ||
| git commit -m "${TITLE}" | ||
|
|
||
| # Checkout ran with persist-credentials: false; authenticate the | ||
| # push with the short-lived App token here instead. | ||
| git remote set-url origin "https://x-access-token:${GH_TOKEN}@github.com/${TARGET_REPO}.git" | ||
| git push --force-with-lease origin "${BRANCH}" | ||
|
|
||
| BODY="$(printf 'Automated bump of \x60%s\x60 to \x60%s\x60, published from openfga/language.\n\nOpened by openfga-releaser-bot as part of the language release flow.' "${ARTIFACT}" "${VERSION}")" | ||
|
|
||
| gh pr create --repo "${TARGET_REPO}" \ | ||
| --head "${BRANCH}" \ | ||
| --base "$(gh repo view "${TARGET_REPO}" --json defaultBranchRef --jq '.defaultBranchRef.name')" \ | ||
| --title "${TITLE}" \ | ||
| --body "${BODY}" | ||
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,91 @@ | ||
| #!/usr/bin/env bash | ||
| # Applies a version bump of an openfga-language artifact inside a checked-out | ||
| # downstream repository. Kept out of the workflow YAML so the edit logic can be | ||
| # unit-tested independently (mirrors parse-release.sh). | ||
| # | ||
| # Run from the root of the *downstream* repo checkout. | ||
| # | ||
| # Usage: | ||
| # downstream-bump.sh js <version> # bumps @openfga/syntax-transformer in | ||
| # # client/ and server/ workspaces | ||
| # downstream-bump.sh java <version> # bumps dev.openfga:openfga-language in | ||
| # # build.gradle.kts | ||
| # downstream-bump.sh go <version> # bumps github.com/openfga/language/pkg/go | ||
| # # in the module containing go.mod | ||
| # | ||
| # <version> is the plain semver (e.g. 0.2.2), without a leading "v". | ||
| # | ||
| # Requires the relevant toolchain on PATH: npm for js, go for go. java uses | ||
| # perl for the in-place build.gradle(.kts) edit. | ||
| set -euo pipefail | ||
|
|
||
| ecosystem="${1:?ecosystem required: js|java|go}" | ||
| version="${2:?version required}" | ||
|
|
||
| # Normalise: callers may pass a tag ref like "pkg/js/v0.2.2" or "v0.2.2". | ||
| version="${version##*/}" # strip any "pkg/<lang>/" prefix | ||
| version="${version#v}" # strip a leading "v" | ||
|
|
||
| case "${ecosystem}" in | ||
| js) | ||
| # Update each workspace that declares the dependency. npm rewrites both | ||
| # package.json (to ^<version>) and package-lock.json (with the correct | ||
| # integrity hash + refreshed transitive deps), matching the manual PRs. | ||
| found=0 | ||
| for dir in client server; do | ||
| if [[ -f "${dir}/package.json" ]] \ | ||
| && grep -q '@openfga/syntax-transformer' "${dir}/package.json"; then | ||
| echo "Bumping @openfga/syntax-transformer in ${dir}/" | ||
| ( cd "${dir}" && npm install "@openfga/syntax-transformer@^${version}" ) | ||
| found=1 | ||
| fi | ||
| done | ||
| if [[ "${found}" -eq 0 ]]; then | ||
| echo "No workspace declares @openfga/syntax-transformer" >&2 | ||
| exit 1 | ||
| fi | ||
| ;; | ||
|
|
||
| java) | ||
| file="build.gradle.kts" | ||
| [[ -f "${file}" ]] || file="build.gradle" | ||
| if [[ ! -f "${file}" ]]; then | ||
| echo "No build.gradle(.kts) found" >&2 | ||
| exit 1 | ||
| fi | ||
| if ! grep -q 'dev\.openfga:openfga-language:' "${file}"; then | ||
| echo "${file} does not depend on dev.openfga:openfga-language" >&2 | ||
| exit 1 | ||
| fi | ||
| echo "Bumping dev.openfga:openfga-language to ${version} in ${file}" | ||
| # Replace the version segment of the coordinate, whatever it currently is | ||
| # (e.g. 0.2.0-beta.2 -> 0.2.1). Only touch chars up to the closing quote, | ||
| # which may be single (groovy) or double (groovy/kts). | ||
| # perl for portability (GNU vs BSD sed differ on -i / backrefs). | ||
| perl -i -pe "s/(dev\.openfga:openfga-language:)[^\"']+([\"'])/\${1}${version}\${2}/g" "${file}" | ||
| ;; | ||
|
|
||
| go) | ||
| module="github.com/openfga/language/pkg/go" | ||
| if [[ ! -f "go.mod" ]]; then | ||
| echo "No go.mod in current directory" >&2 | ||
| exit 1 | ||
| fi | ||
| if ! grep -q "${module}" go.mod; then | ||
| echo "go.mod does not require ${module}" >&2 | ||
| exit 1 | ||
| fi | ||
| echo "Bumping ${module} to v${version}" | ||
| # proxy.golang.org first; fall back to direct if the tag isn't indexed yet. | ||
| export GOPROXY="proxy.golang.org,direct" | ||
| go get "${module}@v${version}" | ||
| go mod tidy | ||
| ;; | ||
|
|
||
| *) | ||
| echo "Unknown ecosystem: ${ecosystem} (expected js|java|go)" >&2 | ||
| exit 1 | ||
| ;; | ||
| esac | ||
|
|
||
| echo "Bump applied for ${ecosystem} -> ${version}" |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.