diff --git a/.github/workflows/pkg-go-release.yaml b/.github/workflows/pkg-go-release.yaml index 2c436783..57dac934 100644 --- a/.github/workflows/pkg-go-release.yaml +++ b/.github/workflows/pkg-go-release.yaml @@ -40,3 +40,28 @@ jobs: sleep 6 done gh release edit "$TAG" --repo "$REPO" --draft=false + + # Once the release is published, open a bump PR against downstream consumers + # of github.com/openfga/language/pkg/go. + downstream-bump: + needs: [finalize-release] + if: startsWith(github.ref, 'refs/tags/') + permissions: + contents: read + strategy: + fail-fast: false + matrix: + target-repo: + - openfga/cli + - openfga/terraform-provider-openfga + uses: ./.github/workflows/reusable-downstream-bump.yaml + with: + target-repo: ${{ matrix.target-repo }} + ecosystem: go + version: ${{ github.ref_name }} + artifact: openfga-language + secrets: + RELEASER_APP_CLIENT_ID: ${{ secrets.RELEASER_APP_CLIENT_ID }} + RELEASER_APP_PRIVATE_KEY: ${{ secrets.RELEASER_APP_PRIVATE_KEY }} + GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} + GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} diff --git a/.github/workflows/pkg-java-release.yaml b/.github/workflows/pkg-java-release.yaml index 9a174f58..ab8a9f24 100644 --- a/.github/workflows/pkg-java-release.yaml +++ b/.github/workflows/pkg-java-release.yaml @@ -121,3 +121,27 @@ jobs: sleep 6 done gh release edit "$TAG" --repo "$REPO" --draft=false + + # Once the release is published, open a bump PR against downstream consumers + # of dev.openfga:openfga-language. + downstream-bump: + needs: [finalize-release] + if: startsWith(github.ref, 'refs/tags/') + permissions: + contents: read + strategy: + fail-fast: false + matrix: + target-repo: + - openfga/intellij-plugin + uses: ./.github/workflows/reusable-downstream-bump.yaml + with: + target-repo: ${{ matrix.target-repo }} + ecosystem: java + version: ${{ github.ref_name }} + artifact: openfga-language + secrets: + RELEASER_APP_CLIENT_ID: ${{ secrets.RELEASER_APP_CLIENT_ID }} + RELEASER_APP_PRIVATE_KEY: ${{ secrets.RELEASER_APP_PRIVATE_KEY }} + GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} + GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} diff --git a/.github/workflows/pkg-js-release.yaml b/.github/workflows/pkg-js-release.yaml index 95ca4870..6d4c4efc 100644 --- a/.github/workflows/pkg-js-release.yaml +++ b/.github/workflows/pkg-js-release.yaml @@ -107,3 +107,27 @@ jobs: sleep 6 done gh release edit "$TAG" --repo "$REPO" --draft=false + + # Once the release is published, open a bump PR against downstream consumers + # of @openfga/syntax-transformer. + downstream-bump: + needs: [finalize-release] + if: startsWith(github.ref, 'refs/tags/') + permissions: + contents: read + strategy: + fail-fast: false + matrix: + target-repo: + - openfga/vscode-ext + uses: ./.github/workflows/reusable-downstream-bump.yaml + with: + target-repo: ${{ matrix.target-repo }} + ecosystem: js + version: ${{ github.ref_name }} + artifact: syntax-transformer + secrets: + RELEASER_APP_CLIENT_ID: ${{ secrets.RELEASER_APP_CLIENT_ID }} + RELEASER_APP_PRIVATE_KEY: ${{ secrets.RELEASER_APP_PRIVATE_KEY }} + GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} + GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} diff --git a/.github/workflows/reusable-downstream-bump.yaml b/.github/workflows/reusable-downstream-bump.yaml new file mode 100644 index 00000000..cf13cbc7 --- /dev/null +++ b/.github/workflows/reusable-downstream-bump.yaml @@ -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// 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}" diff --git a/.github/workflows/scripts/downstream-bump.sh b/.github/workflows/scripts/downstream-bump.sh new file mode 100755 index 00000000..f9f2e94a --- /dev/null +++ b/.github/workflows/scripts/downstream-bump.sh @@ -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 # bumps @openfga/syntax-transformer in +# # client/ and server/ workspaces +# downstream-bump.sh java # bumps dev.openfga:openfga-language in +# # build.gradle.kts +# downstream-bump.sh go # bumps github.com/openfga/language/pkg/go +# # in the module containing go.mod +# +# 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//" 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 ^) 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}" diff --git a/.github/workflows/scripts/downstream-bump.test.sh b/.github/workflows/scripts/downstream-bump.test.sh new file mode 100755 index 00000000..13893a29 --- /dev/null +++ b/.github/workflows/scripts/downstream-bump.test.sh @@ -0,0 +1,223 @@ +#!/usr/bin/env bash +# Tests for downstream-bump.sh +# Run: bash .github/workflows/scripts/downstream-bump.test.sh +# +# The java path is pure text substitution and is exercised end-to-end. The js +# and go paths need a toolchain + network to actually resolve a version, so we +# only assert their hermetic behaviour here: version normalisation and the +# guard/error paths (missing files, missing dependency). + +set -uo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +BUMP="$SCRIPT_DIR/downstream-bump.sh" + +PASS=0 +FAIL=0 + +assert_eq() { + local name="$1" expected="$2" actual="$3" + if [[ "$expected" == "$actual" ]]; then + echo " PASS - $name" + PASS=$((PASS + 1)) + else + echo " FAIL - $name" + echo " expected: $expected" + echo " actual: $actual" + FAIL=$((FAIL + 1)) + fi +} + +assert_contains() { + local name="$1" needle="$2" haystack="$3" + if [[ "$haystack" == *"$needle"* ]]; then + echo " PASS - $name" + PASS=$((PASS + 1)) + else + echo " FAIL - $name" + echo " expected to contain: $needle" + echo " actual: $haystack" + FAIL=$((FAIL + 1)) + fi +} + +assert_not_contains() { + local name="$1" needle="$2" haystack="$3" + if [[ "$haystack" != *"$needle"* ]]; then + echo " PASS - $name" + PASS=$((PASS + 1)) + else + echo " FAIL - $name" + echo " expected NOT to contain: $needle" + echo " actual: $haystack" + FAIL=$((FAIL + 1)) + fi +} + +assert_exit_code() { + local name="$1" expected="$2" actual="$3" + if [[ "$expected" -eq "$actual" ]]; then + echo " PASS - $name (exit=$actual)" + PASS=$((PASS + 1)) + else + echo " FAIL - $name" + echo " expected exit: $expected" + echo " actual exit: $actual" + FAIL=$((FAIL + 1)) + fi +} + +# Run the script inside a throwaway dir, echoing its output. Usage: +# out=$(run_in ); code=$? +run_in() { + local dir="$1"; shift + ( cd "$dir" && bash "$BUMP" "$@" ) +} + +fresh() { mktemp -d; } + +##################################################################### +# java: full end-to-end text substitution +##################################################################### +echo +echo "=== java ===" + +# 1. beta coordinate -> stable, only the language line changes +d=$(fresh) +cat >"$d/build.gradle.kts" <<'EOF' +dependencies { + implementation("org.apache.commons:commons-lang3:3.20.0") + implementation("dev.openfga:openfga-language:0.2.0-beta.2") + implementation("com.diffplug.spotless:spotless-plugin-gradle:8.8.0") +} +EOF +run_in "$d" java 0.2.1 >/dev/null 2>&1 +assert_contains "java bumps language coordinate" \ + 'dev.openfga:openfga-language:0.2.1' "$(cat "$d/build.gradle.kts")" +assert_contains "java leaves other deps untouched (commons)" \ + 'commons-lang3:3.20.0' "$(cat "$d/build.gradle.kts")" +assert_contains "java leaves other deps untouched (spotless)" \ + 'spotless-plugin-gradle:8.8.0' "$(cat "$d/build.gradle.kts")" +assert_not_contains "old java version gone" \ + '0.2.0-beta.2' "$(cat "$d/build.gradle.kts")" +rm -rf "$d" + +# 2. version normalisation: full tag ref is accepted +d=$(fresh) +echo 'implementation("dev.openfga:openfga-language:0.2.0")' >"$d/build.gradle.kts" +run_in "$d" java pkg/java/v0.3.1 >/dev/null 2>&1 +assert_contains "java accepts pkg/java/vX.Y.Z tag ref" \ + 'dev.openfga:openfga-language:0.3.1' "$(cat "$d/build.gradle.kts")" +rm -rf "$d" + +# 3. leading-v normalisation +d=$(fresh) +echo 'implementation("dev.openfga:openfga-language:0.2.0")' >"$d/build.gradle.kts" +run_in "$d" java v0.4.0 >/dev/null 2>&1 +assert_contains "java strips leading v" \ + 'dev.openfga:openfga-language:0.4.0' "$(cat "$d/build.gradle.kts")" +rm -rf "$d" + +# 4. groovy build.gradle (no .kts) is picked up +d=$(fresh) +echo 'implementation "dev.openfga:openfga-language:0.2.0"' >"$d/build.gradle" +run_in "$d" java 0.2.1 >/dev/null 2>&1 +assert_contains "java falls back to build.gradle" \ + 'dev.openfga:openfga-language:0.2.1' "$(cat "$d/build.gradle")" +rm -rf "$d" + +# 4b. groovy build.gradle with single quotes keeps its closing quote intact +d=$(fresh) +echo "implementation 'dev.openfga:openfga-language:0.2.0'" >"$d/build.gradle" +run_in "$d" java 0.2.1 >/dev/null 2>&1 +assert_contains "java handles single-quoted build.gradle" \ + "'dev.openfga:openfga-language:0.2.1'" "$(cat "$d/build.gradle")" +rm -rf "$d" + +# 5. missing build file -> error exit +d=$(fresh) +run_in "$d" java 0.2.1 >/dev/null 2>&1 +assert_exit_code "java missing build file fails" 1 $? +rm -rf "$d" + +# 6. build file without the language dep -> error exit +d=$(fresh) +echo 'implementation("org.other:thing:1.0.0")' >"$d/build.gradle.kts" +run_in "$d" java 0.2.1 >/dev/null 2>&1 +assert_exit_code "java without language dep fails" 1 $? +rm -rf "$d" + +##################################################################### +# js: guard/error paths (npm resolution needs network, not tested here) +##################################################################### +echo +echo "=== js (guards) ===" + +# 7. no workspace declares the dep -> error before touching npm +d=$(fresh) +mkdir -p "$d/client" +echo '{"name":"client","dependencies":{"other":"^1.0.0"}}' >"$d/client/package.json" +err=$(run_in "$d" js 0.2.2 2>&1); code=$? +assert_exit_code "js no matching workspace fails" 1 "$code" +assert_contains "js error mentions syntax-transformer" \ + "syntax-transformer" "$err" +rm -rf "$d" + +# 8. no package.json at all -> error +d=$(fresh) +run_in "$d" js 0.2.2 >/dev/null 2>&1 +assert_exit_code "js no package.json fails" 1 $? +rm -rf "$d" + +##################################################################### +# go: guard/error paths (go get needs network, not tested here) +##################################################################### +echo +echo "=== go (guards) ===" + +# 9. no go.mod -> error +d=$(fresh) +run_in "$d" go 0.3.1 >/dev/null 2>&1 +assert_exit_code "go no go.mod fails" 1 $? +rm -rf "$d" + +# 10. go.mod without the language module -> error before touching go +d=$(fresh) +cat >"$d/go.mod" <<'EOF' +module example.com/foo + +go 1.25.0 + +require github.com/other/thing v1.0.0 +EOF +err=$(run_in "$d" go 0.3.1 2>&1); code=$? +assert_exit_code "go without language module fails" 1 "$code" +assert_contains "go error mentions the module path" \ + "github.com/openfga/language/pkg/go" "$err" +rm -rf "$d" + +##################################################################### +# generic +##################################################################### +echo +echo "=== generic ===" + +# 11. unknown ecosystem -> error +d=$(fresh) +err=$(run_in "$d" rust 1.0.0 2>&1); code=$? +assert_exit_code "unknown ecosystem fails" 1 "$code" +assert_contains "unknown ecosystem message" "Unknown ecosystem" "$err" +rm -rf "$d" + +##################################################################### +# summary +##################################################################### +echo +echo "================================" +echo " Passed: $PASS" +echo " Failed: $FAIL" +echo "================================" + +if [[ "$FAIL" -gt 0 ]]; then + exit 1 +fi diff --git a/.github/workflows/test-release-scripts.yml b/.github/workflows/test-release-scripts.yml index a1b8cd07..f0046b28 100644 --- a/.github/workflows/test-release-scripts.yml +++ b/.github/workflows/test-release-scripts.yml @@ -5,12 +5,16 @@ on: paths: - .github/workflows/scripts/parse-release.sh - .github/workflows/scripts/parse-release.test.sh + - .github/workflows/scripts/downstream-bump.sh + - .github/workflows/scripts/downstream-bump.test.sh - .github/workflows/test-release-scripts.yml push: branches: [main] paths: - .github/workflows/scripts/parse-release.sh - .github/workflows/scripts/parse-release.test.sh + - .github/workflows/scripts/downstream-bump.sh + - .github/workflows/scripts/downstream-bump.test.sh permissions: contents: read @@ -22,4 +26,6 @@ jobs: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: Run parse-release tests run: bash .github/workflows/scripts/parse-release.test.sh + - name: Run downstream-bump tests + run: bash .github/workflows/scripts/downstream-bump.test.sh