From b228387eeb735e139176a72d825c42b52bfe6e03 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 2 Oct 2026 13:04:35 +0000 Subject: [PATCH 1/2] Open a PR when a native SDK releases Adds native-sdk-bump.yml, triggered by the native repos' native-sdk-release repository_dispatch (or by hand). It bumps the pinned native version and this SDK's version, opens a PR with the native release notes for the whole skipped range, then runs the Pi coding agent on that PR to implement any public API changes the release needs here. Needs the ANTHROPIC_API_KEY secret; SDK_BOT_TOKEN is recommended so the PR triggers CI. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015S6mZpaJyu9J5v7NPv2WCS --- .github/scripts/changelog-entry.py | 59 ++++++ .github/scripts/native-changelog.py | 40 ++++ .github/workflows/native-sdk-bump.yml | 291 ++++++++++++++++++++++++++ 3 files changed, 390 insertions(+) create mode 100644 .github/scripts/changelog-entry.py create mode 100644 .github/scripts/native-changelog.py create mode 100644 .github/workflows/native-sdk-bump.yml diff --git a/.github/scripts/changelog-entry.py b/.github/scripts/changelog-entry.py new file mode 100644 index 0000000..5f9f2eb --- /dev/null +++ b/.github/scripts/changelog-entry.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python3 +"""Add a bullet to the "" list of a CHANGELOG.md version section. + +Usage: changelog-entry.py [replace-prefix] + +If the top "## " section already exists (a release is staged), the +bullet joins it, replacing a bullet that starts with replace-prefix if given +(so a second native bump in one release updates "Updates Android SDK to ..." +instead of listing both). Otherwise a new section is inserted above the newest. +""" +import re +import sys + +VERSION_HEADING = re.compile(r"^##\s+\d+\.\d+\.\d+") + + +def main(): + path, version, subheading, bullet = sys.argv[1:5] + replace_prefix = sys.argv[5] if len(sys.argv) > 5 else None + with open(path, encoding="utf-8") as f: + lines = f.read().split("\n") + bullet_line = bullet if bullet.startswith("- ") else f"- {bullet}" + + top = next((i for i, l in enumerate(lines) if VERSION_HEADING.match(l)), len(lines)) + if top < len(lines) and lines[top].split()[1] == version: + end = next( + (i for i in range(top + 1, len(lines)) if VERSION_HEADING.match(lines[i])), + len(lines), + ) + if replace_prefix: + stale = next( + (i for i in range(top + 1, end) if lines[i].startswith(f"- {replace_prefix}")), + None, + ) + if stale is not None: + lines[stale] = bullet_line + return write(path, lines) + sub = next((i for i in range(top + 1, end) if lines[i].strip() == subheading), None) + if sub is None: + lines[top + 1:top + 1] = ["", subheading, bullet_line] + else: + # Append after the last bullet of that sub-list. + j = sub + 1 + while j < end and (lines[j].startswith("- ") or lines[j].startswith(" ")): + j += 1 + lines.insert(j, bullet_line) + else: + lines[top:top] = [f"## {version}", "", subheading, bullet_line, ""] + + write(path, lines) + + +def write(path, lines): + with open(path, "w", encoding="utf-8") as f: + f.write("\n".join(lines)) + + +if __name__ == "__main__": + main() diff --git a/.github/scripts/native-changelog.py b/.github/scripts/native-changelog.py new file mode 100644 index 0000000..e893bf9 --- /dev/null +++ b/.github/scripts/native-changelog.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python3 +"""Print the CHANGELOG.md sections of a native Superwall SDK between two versions. + +Usage: native-changelog.py + +Prints every "## " section with old < version <= new, newest first, +headings included. A wrapper can lag several native releases behind, so the +whole range matters, not just the newest entry. + +Only "## " lines count as section boundaries: Superwall-Android also +uses "## Fixes" / "## Enhancements" as sub-headings inside a version. +""" +import re +import sys + +HEADING = re.compile(r"^##\s+v?(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)\s*$") + + +def key(version): + core, _, pre = version.partition("-") + # A release sorts after its own prereleases (2.9.0-beta.1 < 2.9.0). + return tuple(int(p) for p in core.split(".")), pre == "", pre + + +def main(): + path, old, new = sys.argv[1:4] + lo, hi = key(old), key(new) + out, keep = [], False + with open(path, encoding="utf-8") as f: + for line in f: + m = HEADING.match(line.rstrip("\n")) + if m: + keep = lo < key(m.group(1)) <= hi + if keep: + out.append(line) + sys.stdout.write("".join(out).strip() + "\n" if out else "") + + +if __name__ == "__main__": + main() diff --git a/.github/workflows/native-sdk-bump.yml b/.github/workflows/native-sdk-bump.yml new file mode 100644 index 0000000..5f0b7bd --- /dev/null +++ b/.github/workflows/native-sdk-bump.yml @@ -0,0 +1,291 @@ +# Bumps the wrapped native SDK when Superwall-Android or Superwall-iOS releases. +# +# Fired by the native repos' notify-wrappers.yml (repository_dispatch +# `native-sdk-release`, payload {platform, version}), or by hand. +# +# Two stages, so a PR always exists even if the agent fails or finds nothing: +# 1. Deterministic bump: native pin, pubspec version, CHANGELOG — committed +# and opened as a PR against develop. +# 2. Pi agent (shaftoe/pi-coding-agent-action) reads the native release notes +# for the whole skipped range and, if the public API changed, implements +# the plugin side and pushes it onto the same PR. +# +# Secrets: +# ANTHROPIC_API_KEY — for the agent. +# SDK_BOT_TOKEN — optional but recommended: PAT/App token with contents + +# pull-requests write here. Pushes and PRs made with +# GITHUB_TOKEN do not trigger other workflows. If unset, +# GITHUB_TOKEN is used and "Allow GitHub Actions to create +# and approve pull requests" must be enabled in the repo +# settings. +# Variables: +# PI_MODEL — optional model override. +name: Native SDK bump + +on: + repository_dispatch: + types: [native-sdk-release] + workflow_dispatch: + inputs: + platform: + description: Native SDK that released + required: true + type: choice + options: [android, ios] + version: + description: Native version (blank = latest release) + required: false + type: string + +# One bump per platform at a time; an Android and an iOS bump may run together. +concurrency: + group: native-sdk-bump-${{ github.event.client_payload.platform || inputs.platform }} + cancel-in-progress: false + +permissions: + contents: write + pull-requests: write + issues: write + +env: + BASE_BRANCH: develop + +jobs: + bump: + runs-on: ubuntu-latest + timeout-minutes: 90 + env: + GH_TOKEN: ${{ secrets.SDK_BOT_TOKEN || secrets.GITHUB_TOKEN }} + steps: + - name: Resolve target + id: target + env: + PLATFORM: ${{ github.event.client_payload.platform || inputs.platform }} + VERSION: ${{ github.event.client_payload.version || inputs.version }} + run: | + case "$PLATFORM" in + android) REPO=superwall/Superwall-Android; LABEL=Android ;; + ios) REPO=superwall/Superwall-iOS; LABEL=iOS ;; + *) echo "::error::Unknown platform '$PLATFORM'"; exit 1 ;; + esac + if [ -z "$VERSION" ]; then + VERSION="$(gh release view --repo "$REPO" --json tagName -q .tagName)" + fi + VERSION="${VERSION#v}" + # The payload is interpolated into commands and the agent prompt, so + # accept nothing but a stable x.y.z. + if ! [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "::error::'$VERSION' is not a stable x.y.z version"; exit 1 + fi + { + echo "platform=$PLATFORM" + echo "repo=$REPO" + echo "label=$LABEL" + echo "version=$VERSION" + echo "branch=native-sdk/$PLATFORM-$VERSION" + } >> "$GITHUB_OUTPUT" + + - uses: actions/checkout@v4 + with: + ref: ${{ env.BASE_BRANCH }} + token: ${{ secrets.SDK_BOT_TOKEN || secrets.GITHUB_TOKEN }} + + - name: Read current pin + id: current + env: + PLATFORM: ${{ steps.target.outputs.platform }} + VERSION: ${{ steps.target.outputs.version }} + BRANCH: ${{ steps.target.outputs.branch }} + run: | + if [ "$PLATFORM" = android ]; then + CURRENT="$(sed -nE 's/.*com\.superwall\.sdk:superwall-android:([^"]+)".*/\1/p' android/build.gradle)" + else + CURRENT="$(sed -nE "s/.*s\.dependency 'SuperwallKit', '([^']+)'.*/\1/p" ios/superwallkit_flutter.podspec)" + fi + [ -n "$CURRENT" ] || { echo "::error::Could not read the current $PLATFORM pin"; exit 1; } + echo "current=$CURRENT" >> "$GITHUB_OUTPUT" + + SKIP=false + if [ "$(printf '%s\n%s\n' "$CURRENT" "$VERSION" | sort -V | tail -1)" = "$CURRENT" ]; then + echo "::notice::$PLATFORM is already at $CURRENT (>= $VERSION), nothing to do." + SKIP=true + elif git ls-remote --exit-code --heads origin "$BRANCH" >/dev/null; then + echo "::notice::Branch $BRANCH already exists, a bump PR was already opened." + SKIP=true + fi + echo "skip=$SKIP" >> "$GITHUB_OUTPUT" + + - name: Fetch native source and release notes + if: steps.current.outputs.skip == 'false' + env: + REPO: ${{ steps.target.outputs.repo }} + VERSION: ${{ steps.target.outputs.version }} + CURRENT: ${{ steps.current.outputs.current }} + run: | + # Inside the workspace so the agent can read it, but excluded from git + # so the agent's commits never pick it up. + git clone --quiet --filter=blob:none "https://github.com/$REPO" .native-sdk + git -C .native-sdk checkout --quiet "$VERSION" 2>/dev/null \ + || git -C .native-sdk checkout --quiet "v$VERSION" + echo ".native-sdk/" >> .git/info/exclude + python3 .github/scripts/native-changelog.py .native-sdk/CHANGELOG.md "$CURRENT" "$VERSION" \ + > "$RUNNER_TEMP/native-changelog.md" + [ -s "$RUNNER_TEMP/native-changelog.md" ] \ + || echo "_No CHANGELOG.md entries found between $CURRENT and $VERSION._" > "$RUNNER_TEMP/native-changelog.md" + cat "$RUNNER_TEMP/native-changelog.md" + + - name: Apply version bump + if: steps.current.outputs.skip == 'false' + id: bump + env: + PLATFORM: ${{ steps.target.outputs.platform }} + REPO: ${{ steps.target.outputs.repo }} + LABEL: ${{ steps.target.outputs.label }} + VERSION: ${{ steps.target.outputs.version }} + run: | + if [ "$PLATFORM" = android ]; then + sed -i -E "s/(com\.superwall\.sdk:superwall-android:)[^\"]+\"/\1$VERSION\"/" android/build.gradle + else + # The podspec and the SPM manifest must pin the same version. + sed -i -E \ + -e "s/('SuperwallKit', ')[^']+'/\1$VERSION'/" \ + -e "s#(Superwall-iOS\.git\", exact: \")[^\"]+\"#\1$VERSION\"#" \ + ios/superwallkit_flutter.podspec ios/superwallkit_flutter/Package.swift + fi + + # Bump the patch version unless the pubspec version is not on pub.dev + # yet (a release is already staged on develop) — then this joins it. + # pub.dev rather than git tags: not every release gets tagged. + PLUGIN_VERSION="$(sed -nE 's/^version: *(.*)$/\1/p' pubspec.yaml)" + if curl -fsS https://pub.dev/api/packages/superwallkit_flutter \ + | python3 -c 'import json,sys; v=sys.argv[1]; sys.exit(0 if any(x["version"]==v for x in json.load(sys.stdin)["versions"]) else 1)' "$PLUGIN_VERSION"; then + IFS=. read -r MA MI PA <<< "$PLUGIN_VERSION" + PLUGIN_VERSION="$MA.$MI.$((PA + 1))" + sed -i -E "s/^version: .*/version: $PLUGIN_VERSION/" pubspec.yaml + fi + echo "plugin_version=$PLUGIN_VERSION" >> "$GITHUB_OUTPUT" + + python3 .github/scripts/changelog-entry.py CHANGELOG.md "$PLUGIN_VERSION" "### Enhancements" \ + "Updates $LABEL SDK to $VERSION [View $LABEL SDK release notes](https://github.com/$REPO/releases/tag/$VERSION)." \ + "Updates $LABEL SDK to" + git --no-pager diff + + - name: Open PR + if: steps.current.outputs.skip == 'false' + id: pr + env: + LABEL: ${{ steps.target.outputs.label }} + REPO: ${{ steps.target.outputs.repo }} + VERSION: ${{ steps.target.outputs.version }} + CURRENT: ${{ steps.current.outputs.current }} + BRANCH: ${{ steps.target.outputs.branch }} + PLUGIN_VERSION: ${{ steps.bump.outputs.plugin_version }} + run: | + git config user.name 'github-actions[bot]' + git config user.email 'github-actions[bot]@users.noreply.github.com' + git checkout -b "$BRANCH" + git commit -am "Bump $LABEL SDK to $VERSION" + git push -u origin "$BRANCH" + + { + echo "Bumps the $LABEL SDK from \`$CURRENT\` to \`$VERSION\` and superwallkit_flutter to \`$PLUGIN_VERSION\`." + echo + echo "Opened automatically by \`native-sdk-bump.yml\` after [$REPO $VERSION](https://github.com/$REPO/releases/tag/$VERSION) was released. An agent then checks the release notes below for public API changes and pushes any integration work onto this PR." + echo + echo "
$LABEL release notes ($CURRENT → $VERSION)" + echo + cat "$RUNNER_TEMP/native-changelog.md" + echo + echo "
" + } > "$RUNNER_TEMP/pr-body.md" + gh pr create --base "$BASE_BRANCH" --head "$BRANCH" \ + --title "Bump $LABEL SDK to $VERSION" --body-file "$RUNNER_TEMP/pr-body.md" + echo "number=$(gh pr view "$BRANCH" --json number -q .number)" >> "$GITHUB_OUTPUT" + + # Toolchain for the agent to validate its changes with. + - uses: actions/setup-node@v4 + if: steps.current.outputs.skip == 'false' + with: + node-version: 24 + - uses: actions/setup-java@v4 + if: steps.current.outputs.skip == 'false' + with: + distribution: temurin + java-version: '17' + - uses: subosito/flutter-action@v2 + if: steps.current.outputs.skip == 'false' + with: + channel: stable + cache: true + - name: flutter pub get + if: steps.current.outputs.skip == 'false' + run: flutter pub get + + - name: Build agent prompt + if: steps.current.outputs.skip == 'false' + id: prompt + env: + PLATFORM: ${{ steps.target.outputs.platform }} + LABEL: ${{ steps.target.outputs.label }} + REPO: ${{ steps.target.outputs.repo }} + VERSION: ${{ steps.target.outputs.version }} + CURRENT: ${{ steps.current.outputs.current }} + BRANCH: ${{ steps.target.outputs.branch }} + PR: ${{ steps.pr.outputs.number }} + PLUGIN_VERSION: ${{ steps.bump.outputs.plugin_version }} + run: | + if [ "$PLATFORM" = android ]; then + PLATFORM_NOTES="The Android host lives in android/src/main/kotlin. Besides flutter analyze and flutter test, check the Android side compiles with: (cd example && flutter build apk --debug)." + else + PLATFORM_NOTES="The iOS host lives in ios/superwallkit_flutter/Sources/superwallkit_flutter. This runner is Linux: Swift cannot be compiled here, so be careful and precise with Swift and say in the PR description that iOS was not compiled locally." + fi + DELIM="PROMPT_$(openssl rand -hex 8)" + { + echo "text<<$DELIM" + cat <\` to see exactly how its public API changed. Do not modify anything under .native-sdk/ or .context/. + + Native release notes for every version in the range: + + $(cat "$RUNNER_TEMP/native-changelog.md") + + Your job: + 1. Read CLAUDE.md first and follow its "Adding New SDK Methods" workflow: models and APIs are defined in pigeons/configure.dart (P-prefixed), regenerated with \`dart run pigeon --input pigeons/configure.dart\` (never hand-edit generated files), implemented in the native hosts, then exposed in lib/src/public and exported from lib/superwallkit_flutter.dart. + 2. Decide whether the plugin needs changes beyond the version bump. Work is needed for: new public methods, properties or options; changed signatures; new or renamed delegate callbacks, events or enum cases that the plugin maps; deprecated or removed APIs the plugin calls; behaviour changes the plugin or its docs describe. Internal fixes and performance work need nothing. + 3. If work is needed, implement it end to end in Dart and the $LABEL host, with sensible behaviour on the other platform when the feature is $LABEL-only (document it as such). Add or update tests next to the existing ones. Validate with flutter analyze and flutter test. $PLATFORM_NOTES + 4. Update docs (README.md, CLAUDE.md) that state the pinned $LABEL version $CURRENT so they say $VERSION — only where they describe the current pin, not historical changelog entries. + 5. Add customer-facing bullets for anything you add to the "## $PLUGIN_VERSION" section of CHANGELOG.md. If you add public API, bump the minor version instead (pubspec.yaml and that CHANGELOG heading) unless the section is already a minor/major release. + 6. Never commit code that does not compile. If part of the work cannot be done safely, leave it out and list it in the PR description as follow-up. + 7. Push with the update_pull_request tool for PR #$PR — never create_pull_request, never push to $BASE_BRANCH, never edit .github/. Update the PR description: keep the existing text and release notes, and add an "Integration changes" section listing what you changed and why. If nothing beyond the bump is needed, change no files and only add that section explaining why no integration work is needed. + EOF + echo "$DELIM" + } >> "$GITHUB_OUTPUT" + + - name: Integrate with Pi + if: steps.current.outputs.skip == 'false' + id: pi + continue-on-error: true + uses: shaftoe/pi-coding-agent-action@v2.29.1 + with: + github_token: ${{ secrets.SDK_BOT_TOKEN || secrets.GITHUB_TOKEN }} + provider: anthropic + model: ${{ vars.PI_MODEL || 'claude-opus-5-5' }} + token: ${{ secrets.ANTHROPIC_API_KEY }} + thinking_level: high + auto_compaction: true + pr_number: ${{ steps.pr.outputs.number }} + prompt: ${{ steps.prompt.outputs.text }} + + # The bump PR stands on its own; make it obvious that nobody reviewed the + # release notes for API changes. + - name: Flag failed integration + if: steps.current.outputs.skip == 'false' && steps.pi.outcome == 'failure' + env: + PR: ${{ steps.pr.outputs.number }} + RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + run: | + gh pr comment "$PR" --body "The integration agent failed ([run]($RUN_URL)). This PR only contains the version bump — check the release notes above for public API changes by hand before merging." From 91c541599fad1fa0b927a28dc49d8d044f410551 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 2 Oct 2026 14:04:30 +0000 Subject: [PATCH 2/2] Add a dry-run mode and make the bump workflow runnable under local-ci - dry_run input (workflow_dispatch and the new workflow_call trigger): applies the bump and prints the diff and agent prompt, without pushing, opening a PR or running the agent. - Pass values between steps through $GITHUB_ENV instead of step outputs. Same behaviour on GitHub, and it drops the per-step env mapping blocks; local-ci resolves steps.*.outputs.* to empty strings. - Check for an existing bump branch (and, in KMP, an existing release tag) through the GitHub API instead of git ls-remote, which local-ci stubs. - Ignore file-mode changes when printing diffs. Verified with local-ci in dry-run mode: KMP Android 2.8.0 -> 2.8.4, Flutter iOS 4.16.3 -> 4.17.0 and Expo Android 2.8.3 -> 2.8.4 (pins lowered locally to exercise the bump), plus the already-up-to-date skip path. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015S6mZpaJyu9J5v7NPv2WCS --- .github/workflows/native-sdk-bump.yml | 126 +++++++++++++------------- 1 file changed, 65 insertions(+), 61 deletions(-) diff --git a/.github/workflows/native-sdk-bump.yml b/.github/workflows/native-sdk-bump.yml index 5f0b7bd..b7fa78b 100644 --- a/.github/workflows/native-sdk-bump.yml +++ b/.github/workflows/native-sdk-bump.yml @@ -20,6 +20,11 @@ # settings. # Variables: # PI_MODEL — optional model override. +# +# Testing: run it by hand with dry_run: true — it applies the bump and prints +# the diff and the agent prompt, but pushes nothing, opens no PR and skips the +# agent. Locally, local-ci (https://agent-ci.dev) can run it through a caller +# workflow that uses this one via workflow_call with dry_run: true. name: Native SDK bump on: @@ -36,6 +41,26 @@ on: description: Native version (blank = latest release) required: false type: string + dry_run: + description: Apply the bump and print the diff and agent prompt only — no push, PR or agent + required: false + type: boolean + default: false + # Same inputs for callers. Mainly so the workflow can be run locally with + # local-ci (https://agent-ci.dev), which cannot simulate dispatch events but + # does pass workflow_call inputs. + workflow_call: + inputs: + platform: + required: true + type: string + version: + required: false + type: string + dry_run: + required: false + type: boolean + default: false # One bump per platform at a time; an Android and an iOS bump may run together. concurrency: @@ -56,9 +81,12 @@ jobs: timeout-minutes: 90 env: GH_TOKEN: ${{ secrets.SDK_BOT_TOKEN || secrets.GITHUB_TOKEN }} + DRY_RUN: ${{ inputs.dry_run && 'true' || 'false' }} steps: + # Values flow between steps through $GITHUB_ENV rather than step + # outputs: same behaviour on GitHub, and it also works under local-ci, + # which resolves steps.*.outputs.* to empty strings. - name: Resolve target - id: target env: PLATFORM: ${{ github.event.client_payload.platform || inputs.platform }} VERSION: ${{ github.event.client_payload.version || inputs.version }} @@ -78,12 +106,12 @@ jobs: echo "::error::'$VERSION' is not a stable x.y.z version"; exit 1 fi { - echo "platform=$PLATFORM" - echo "repo=$REPO" - echo "label=$LABEL" - echo "version=$VERSION" - echo "branch=native-sdk/$PLATFORM-$VERSION" - } >> "$GITHUB_OUTPUT" + echo "PLATFORM=$PLATFORM" + echo "REPO=$REPO" + echo "LABEL=$LABEL" + echo "VERSION=$VERSION" + echo "BRANCH=native-sdk/$PLATFORM-$VERSION" + } >> "$GITHUB_ENV" - uses: actions/checkout@v4 with: @@ -91,11 +119,6 @@ jobs: token: ${{ secrets.SDK_BOT_TOKEN || secrets.GITHUB_TOKEN }} - name: Read current pin - id: current - env: - PLATFORM: ${{ steps.target.outputs.platform }} - VERSION: ${{ steps.target.outputs.version }} - BRANCH: ${{ steps.target.outputs.branch }} run: | if [ "$PLATFORM" = android ]; then CURRENT="$(sed -nE 's/.*com\.superwall\.sdk:superwall-android:([^"]+)".*/\1/p' android/build.gradle)" @@ -103,24 +126,22 @@ jobs: CURRENT="$(sed -nE "s/.*s\.dependency 'SuperwallKit', '([^']+)'.*/\1/p" ios/superwallkit_flutter.podspec)" fi [ -n "$CURRENT" ] || { echo "::error::Could not read the current $PLATFORM pin"; exit 1; } - echo "current=$CURRENT" >> "$GITHUB_OUTPUT" + echo "CURRENT=$CURRENT" >> "$GITHUB_ENV" SKIP=false if [ "$(printf '%s\n%s\n' "$CURRENT" "$VERSION" | sort -V | tail -1)" = "$CURRENT" ]; then echo "::notice::$PLATFORM is already at $CURRENT (>= $VERSION), nothing to do." SKIP=true - elif git ls-remote --exit-code --heads origin "$BRANCH" >/dev/null; then + # Through the API rather than `git ls-remote`, which local-ci stubs to + # always succeed. + elif gh api --silent "repos/$GITHUB_REPOSITORY/git/ref/heads/$BRANCH" 2>/dev/null; then echo "::notice::Branch $BRANCH already exists, a bump PR was already opened." SKIP=true fi - echo "skip=$SKIP" >> "$GITHUB_OUTPUT" + echo "SKIP=$SKIP" >> "$GITHUB_ENV" - name: Fetch native source and release notes - if: steps.current.outputs.skip == 'false' - env: - REPO: ${{ steps.target.outputs.repo }} - VERSION: ${{ steps.target.outputs.version }} - CURRENT: ${{ steps.current.outputs.current }} + if: env.SKIP == 'false' run: | # Inside the workspace so the agent can read it, but excluded from git # so the agent's commits never pick it up. @@ -135,13 +156,7 @@ jobs: cat "$RUNNER_TEMP/native-changelog.md" - name: Apply version bump - if: steps.current.outputs.skip == 'false' - id: bump - env: - PLATFORM: ${{ steps.target.outputs.platform }} - REPO: ${{ steps.target.outputs.repo }} - LABEL: ${{ steps.target.outputs.label }} - VERSION: ${{ steps.target.outputs.version }} + if: env.SKIP == 'false' run: | if [ "$PLATFORM" = android ]; then sed -i -E "s/(com\.superwall\.sdk:superwall-android:)[^\"]+\"/\1$VERSION\"/" android/build.gradle @@ -163,23 +178,15 @@ jobs: PLUGIN_VERSION="$MA.$MI.$((PA + 1))" sed -i -E "s/^version: .*/version: $PLUGIN_VERSION/" pubspec.yaml fi - echo "plugin_version=$PLUGIN_VERSION" >> "$GITHUB_OUTPUT" + echo "PLUGIN_VERSION=$PLUGIN_VERSION" >> "$GITHUB_ENV" python3 .github/scripts/changelog-entry.py CHANGELOG.md "$PLUGIN_VERSION" "### Enhancements" \ "Updates $LABEL SDK to $VERSION [View $LABEL SDK release notes](https://github.com/$REPO/releases/tag/$VERSION)." \ "Updates $LABEL SDK to" - git --no-pager diff + git -c core.fileMode=false --no-pager diff - name: Open PR - if: steps.current.outputs.skip == 'false' - id: pr - env: - LABEL: ${{ steps.target.outputs.label }} - REPO: ${{ steps.target.outputs.repo }} - VERSION: ${{ steps.target.outputs.version }} - CURRENT: ${{ steps.current.outputs.current }} - BRANCH: ${{ steps.target.outputs.branch }} - PLUGIN_VERSION: ${{ steps.bump.outputs.plugin_version }} + if: env.SKIP == 'false' && env.DRY_RUN == 'false' run: | git config user.name 'github-actions[bot]' git config user.email 'github-actions[bot]@users.noreply.github.com' @@ -200,39 +207,29 @@ jobs: } > "$RUNNER_TEMP/pr-body.md" gh pr create --base "$BASE_BRANCH" --head "$BRANCH" \ --title "Bump $LABEL SDK to $VERSION" --body-file "$RUNNER_TEMP/pr-body.md" - echo "number=$(gh pr view "$BRANCH" --json number -q .number)" >> "$GITHUB_OUTPUT" + echo "PR=$(gh pr view "$BRANCH" --json number -q .number)" >> "$GITHUB_ENV" # Toolchain for the agent to validate its changes with. - uses: actions/setup-node@v4 - if: steps.current.outputs.skip == 'false' + if: env.SKIP == 'false' && env.DRY_RUN == 'false' with: node-version: 24 - uses: actions/setup-java@v4 - if: steps.current.outputs.skip == 'false' + if: env.SKIP == 'false' && env.DRY_RUN == 'false' with: distribution: temurin java-version: '17' - uses: subosito/flutter-action@v2 - if: steps.current.outputs.skip == 'false' + if: env.SKIP == 'false' && env.DRY_RUN == 'false' with: channel: stable cache: true - name: flutter pub get - if: steps.current.outputs.skip == 'false' + if: env.SKIP == 'false' && env.DRY_RUN == 'false' run: flutter pub get - name: Build agent prompt - if: steps.current.outputs.skip == 'false' - id: prompt - env: - PLATFORM: ${{ steps.target.outputs.platform }} - LABEL: ${{ steps.target.outputs.label }} - REPO: ${{ steps.target.outputs.repo }} - VERSION: ${{ steps.target.outputs.version }} - CURRENT: ${{ steps.current.outputs.current }} - BRANCH: ${{ steps.target.outputs.branch }} - PR: ${{ steps.pr.outputs.number }} - PLUGIN_VERSION: ${{ steps.bump.outputs.plugin_version }} + if: env.SKIP == 'false' run: | if [ "$PLATFORM" = android ]; then PLATFORM_NOTES="The Android host lives in android/src/main/kotlin. Besides flutter analyze and flutter test, check the Android side compiles with: (cd example && flutter build apk --debug)." @@ -241,7 +238,7 @@ jobs: fi DELIM="PROMPT_$(openssl rand -hex 8)" { - echo "text<<$DELIM" + echo "AGENT_PROMPT<<$DELIM" cat <> "$GITHUB_OUTPUT" + } >> "$GITHUB_ENV" + + - name: Dry-run summary + if: env.SKIP == 'false' && env.DRY_RUN == 'true' + run: | + echo "::notice::Dry run: nothing was pushed, no PR was opened, the agent did not run." + git -c core.fileMode=false --no-pager diff HEAD + echo "----- agent prompt -----" + printf '%s\n' "$AGENT_PROMPT" - name: Integrate with Pi - if: steps.current.outputs.skip == 'false' + if: env.SKIP == 'false' && env.DRY_RUN == 'false' id: pi continue-on-error: true uses: shaftoe/pi-coding-agent-action@v2.29.1 @@ -277,15 +282,14 @@ jobs: token: ${{ secrets.ANTHROPIC_API_KEY }} thinking_level: high auto_compaction: true - pr_number: ${{ steps.pr.outputs.number }} - prompt: ${{ steps.prompt.outputs.text }} + pr_number: ${{ env.PR }} + prompt: ${{ env.AGENT_PROMPT }} # The bump PR stands on its own; make it obvious that nobody reviewed the # release notes for API changes. - name: Flag failed integration - if: steps.current.outputs.skip == 'false' && steps.pi.outcome == 'failure' + if: env.SKIP == 'false' && env.DRY_RUN == 'false' && steps.pi.outcome == 'failure' env: - PR: ${{ steps.pr.outputs.number }} RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} run: | gh pr comment "$PR" --body "The integration agent failed ([run]($RUN_URL)). This PR only contains the version bump — check the release notes above for public API changes by hand before merging."