From 08e8139676b1cd8c4011ebebacea888df3f7c42f Mon Sep 17 00:00:00 2001 From: Simon Sestak Date: Wed, 7 Oct 2026 17:44:08 +0200 Subject: [PATCH 1/3] fix(notify): keep bump PRs open on update and retry failed repos with backoff The update path force-reset the PR branch to the default branch HEAD before committing the bumped files. That left the PR with zero commits for a moment and GitHub auto-closed it, while the script still reported UPDATED. Existing PRs are now brought up to date by merging the default branch into the PR branch; on a merge conflict the branch is recreated and a fresh PR opened. The script also verifies the PR is still open before reporting success. Repos that fail (typically during a GitHub outage, as writes via the Contents API are the first to go) are now retried in passes with exponential backoff: up to 10 passes, starting at 2 s and doubling up to 5 min, configurable via NOTIFY_RETRY_* env vars. Only failed repos are retried and files already bumped on the branch are not rewritten, so partial progress is kept. Other fixes along the way: - gh errors are no longer discarded; the last stderr line is printed next to each FAIL so logs show the actual API response - a failed `gh pr create` was reported as CREATED because stderr was captured into the URL variable - NOTIFY_REPOS env var / `repos` workflow_dispatch input to process an explicit list of repos, e.g. to re-run only the ones that failed Adds a gh CLI mock and BATS tests covering the create/update/retry paths. Claude-Session: https://claude.ai/code/session_015HbhiukLQv71hkruK31fyT --- .github/actions/ios-kmp-build/action.yml | 4 +- .github/scripts/notify-consumer-repos.sh | 530 ++++++++++++------ .github/scripts/test/mocks/gh | 132 +++++ .../test/test_notify-consumer-repos.bats | 166 ++++++ .github/workflows/android-cloud-check.yml | 4 +- ...droid-cloud-generate-baseline-profiles.yml | 4 +- .../workflows/android-cloud-nightly-build.yml | 8 +- ...-cloud-release-firebaseAppDistribution.yml | 4 +- .../android-cloud-release-googlePlay.yml | 4 +- .../workflows/ios-kmp-selfhosted-build.yml | 4 +- .../workflows/ios-kmp-selfhosted-release.yml | 4 +- .github/workflows/ios-kmp-selfhosted-test.yml | 2 +- .github/workflows/ios-selfhosted-build.yml | 2 +- .../ios-selfhosted-nightly-build.yml | 8 +- .../ios-selfhosted-on-demand-build.yml | 6 +- .github/workflows/ios-selfhosted-release.yml | 4 +- .github/workflows/ios-selfhosted-test.yml | 2 +- .../workflows/kmp-cloud-detect-changes.yml | 4 +- .../workflows/kmp-combined-nightly-build.yml | 12 +- .github/workflows/notify-consumer-repos.yml | 6 + 20 files changed, 692 insertions(+), 218 deletions(-) create mode 100755 .github/scripts/test/mocks/gh create mode 100644 .github/scripts/test/test_notify-consumer-repos.bats diff --git a/.github/actions/ios-kmp-build/action.yml b/.github/actions/ios-kmp-build/action.yml index 8a2d5e48..fd169498 100644 --- a/.github/actions/ios-kmp-build/action.yml +++ b/.github/actions/ios-kmp-build/action.yml @@ -48,7 +48,7 @@ runs: steps: - name: Export secrets to .xcconfig file if: ${{ inputs.secret_xcconfig_path != '' }} - uses: futuredapp/.github/.github/actions/ios-export-secrets@2.6.0 + uses: futuredapp/.github/.github/actions/ios-export-secrets@main with: XCCONFIG_PATH: ${{ inputs.secret_xcconfig_path }} SECRET_PROPERTIES: ${{ inputs.secret_properties }} @@ -64,7 +64,7 @@ runs: cd ${{ inputs.kmp_swift_package_path }} make build - name: Fastlane Beta - uses: futuredapp/.github/.github/actions/ios-fastlane-beta@2.6.0 + uses: futuredapp/.github/.github/actions/ios-fastlane-beta@main with: match_password: ${{ inputs.match_password }} testflight_changelog: ${{ inputs.testflight_changelog }} diff --git a/.github/scripts/notify-consumer-repos.sh b/.github/scripts/notify-consumer-repos.sh index 92e154c8..55166483 100755 --- a/.github/scripts/notify-consumer-repos.sh +++ b/.github/scripts/notify-consumer-repos.sh @@ -7,119 +7,271 @@ # Requires: gh CLI authenticated with a token that has repo scope across the org. # # Environment variables: -# NOTIFY_REPOS_WHITELIST — space-separated glob patterns matching repo names -# (without org prefix). If set, only matching repos -# receive PRs. Example: "ios-* android-* kmp-project" +# NOTIFY_REPOS — space-separated full repo names (org/name). If set, +# the GitHub code search is skipped and only these repos +# are processed. Useful for re-running a few failed repos. +# NOTIFY_REPOS_WHITELIST — space-separated glob patterns matching repo names +# (without org prefix). If set, only matching repos +# receive PRs. Example: "ios-* android-* kmp-project" +# NOTIFY_RETRY_MAX_ATTEMPTS — how many retry passes to run over repos that failed +# (default 10). Each pass waits with exponential backoff. +# NOTIFY_RETRY_BASE_DELAY — delay in seconds before the first retry pass (default 2). +# Doubles with every pass. +# NOTIFY_RETRY_MAX_DELAY — upper bound for the backoff delay in seconds (default 300). set -euo pipefail -if [ $# -lt 1 ]; then - echo "Usage: $0 [--dry-run]" - exit 1 -fi - -NEW_VERSION="$1" -DRY_RUN="${2:-}" BRANCH_NAME="housekeep/bump-shared-workflows" SELF_REPO="futuredapp/.github" -# Detect major version bump → breaking change -PREVIOUS_TAG=$(git tag --sort=-v:refname | grep -E '^[0-9]+\.[0-9]+\.[0-9]+$' | grep -v "^${NEW_VERSION}$" | head -1) -IS_BREAKING=false -if [ -n "$PREVIOUS_TAG" ]; then - old_major="${PREVIOUS_TAG%%.*}" - new_major="${NEW_VERSION%%.*}" - if [ "$old_major" != "$new_major" ]; then - IS_BREAKING=true - echo "Major version bump detected: ${PREVIOUS_TAG} → ${NEW_VERSION}" +RETRY_MAX_ATTEMPTS="${NOTIFY_RETRY_MAX_ATTEMPTS:-10}" +RETRY_BASE_DELAY="${NOTIFY_RETRY_BASE_DELAY:-2}" +RETRY_MAX_DELAY="${NOTIFY_RETRY_MAX_DELAY:-300}" + +# Last stderr line of the most recent failed gh call, shown next to FAIL messages. +LAST_ERROR="" +STDERR_FILE="" + +# --------------------------------------------------------------------------- +# Helpers +# --------------------------------------------------------------------------- + +# Backoff delay (seconds) before retry pass number $1 (1-based): +# base * 2^(attempt-1), capped at RETRY_MAX_DELAY. +retry_delay() { + local attempt="$1" + local delay=$(( RETRY_BASE_DELAY * (1 << (attempt - 1)) )) + if [ "$delay" -gt "$RETRY_MAX_DELAY" ]; then + delay="$RETRY_MAX_DELAY" fi -fi + echo "$delay" +} + +# Remember the last non-empty stderr line of a failed gh call. +capture_last_error() { + LAST_ERROR=$(grep -v '^$' "$STDERR_FILE" | tail -1 || true) +} + +# Run a gh command whose output we do not need. Returns gh's exit code and +# records the error message on failure instead of silently discarding it. +gh_run() { + LAST_ERROR="" + if gh "$@" >/dev/null 2>"$STDERR_FILE"; then + return 0 + fi + capture_last_error + return 1 +} + +# Print a FAIL line for the current repo, including the API error if known. +report_failure() { + local reason="$1" repo="$2" + if [ -n "$LAST_ERROR" ]; then + echo "FAIL ($reason): $repo — $LAST_ERROR" + else + echo "FAIL ($reason): $repo" + fi +} -echo "Searching for consumer repos..." -REPOS="" -page=1 -while true; do - result=$(gh api -X GET "/search/code" \ - -f q="org:futuredapp \"uses: futuredapp/.github\" path:.github/workflows" \ - -f per_page=100 \ - -f page="$page" \ - --jq '.items[].repository.full_name' 2>/dev/null || true) - [ -z "$result" ] && break - REPOS="$REPOS +# Decode a base64 "content" field from the Contents API; empty on failure. +decode_content() { + local raw="$1" + if [ -n "$raw" ]; then + echo "$raw" | base64 -d 2>/dev/null || true + fi +} + +# Replace all futuredapp/.github refs (@main or @x.y.z) with @NEW_VERSION. +bump_refs() { + sed -E "s#(futuredapp/\.github/.+)@(main|[0-9]+\.[0-9]+\.[0-9]+)#\1@${NEW_VERSION}#g" +} + +# --------------------------------------------------------------------------- +# Repo discovery +# --------------------------------------------------------------------------- + +discover_repos() { + if [ -n "${NOTIFY_REPOS:-}" ]; then + echo "Using explicit repo list from NOTIFY_REPOS" >&2 + echo "$NOTIFY_REPOS" | tr ' ' '\n' | sort -u | sed '/^$/d' + return + fi + + echo "Searching for consumer repos..." >&2 + local repos="" page=1 result + while true; do + result=$(gh api -X GET "/search/code" \ + -f q="org:futuredapp \"uses: futuredapp/.github\" path:.github/workflows" \ + -f per_page=100 \ + -f page="$page" \ + --jq '.items[].repository.full_name' 2>/dev/null || true) + [ -z "$result" ] && break + repos="$repos $result" - page=$((page + 1)) -done -REPOS=$(echo "$REPOS" | sort -u | sed '/^$/d') - -# Apply whitelist filter if set -if [ -n "${NOTIFY_REPOS_WHITELIST:-}" ]; then - NOTIFY_REPOS_WHITELIST=$(echo "$NOTIFY_REPOS_WHITELIST" | tr -d '\r') - FILTERED="" + page=$((page + 1)) + done + echo "$repos" | sort -u | sed '/^$/d' +} + +apply_whitelist() { + local repos="$1" filtered="" repo repo_name pattern count + local whitelist + whitelist=$(echo "$NOTIFY_REPOS_WHITELIST" | tr -d '\r') set -f # disable file glob expansion so patterns like "ios-*" stay literal - for repo in $REPOS; do + for repo in $repos; do repo_name="${repo#*/}" - for pattern in $NOTIFY_REPOS_WHITELIST; do + for pattern in $whitelist; do if [[ "$repo_name" == $pattern ]]; then - FILTERED="$FILTERED + filtered="$filtered $repo" break fi done done set +f - REPOS=$(echo "$FILTERED" | sed '/^$/d') - count=$(echo "$REPOS" | grep -c . 2>/dev/null || true) - echo "Whitelist applied: ${count:-0} repos match" -fi + repos=$(echo "$filtered" | sed '/^$/d') + count=$(echo "$repos" | grep -c . 2>/dev/null || true) + echo "Whitelist applied: ${count:-0} repos match" >&2 + echo "$repos" +} + +# --------------------------------------------------------------------------- +# Per-repo processing +# --------------------------------------------------------------------------- + +# Prepare the PR branch for an existing open PR: merge the default branch into it. +# Force-resetting the branch to base HEAD would leave the PR with zero commits and +# GitHub auto-closes such PRs, so we only ever add commits on top. +# On merge conflict the branch is recreated from scratch; GitHub closes the stale PR +# once its head ref is deleted and a fresh PR is created by the caller. +# Sets existing_pr="" when the caller must create a new PR. +prepare_existing_branch() { + local repo="$1" default_branch="$2" base_sha="$3" + + if gh_run api "repos/$repo/merges" -f "base=$BRANCH_NAME" -f "head=$default_branch"; then + return 0 + fi + + echo " merge of $default_branch into $BRANCH_NAME failed ($LAST_ERROR), recreating branch" + if ! gh_run api -X DELETE "repos/$repo/git/refs/heads/$BRANCH_NAME"; then + report_failure "cannot delete branch" "$repo" + return 1 + fi + if ! gh_run api "repos/$repo/git/refs" -f "ref=refs/heads/$BRANCH_NAME" -f "sha=$base_sha"; then + report_failure "cannot recreate branch" "$repo" + return 1 + fi + existing_pr="" + return 0 +} + +# Create the PR branch at base HEAD, resetting a leftover branch from a previous run. +prepare_new_branch() { + local repo="$1" base_sha="$2" + + if gh_run api "repos/$repo/git/refs" -f "ref=refs/heads/$BRANCH_NAME" -f "sha=$base_sha"; then + return 0 + fi + if gh_run api "repos/$repo/git/refs/heads/$BRANCH_NAME" -X PATCH -f sha="$base_sha" -F force=true; then + return 0 + fi + report_failure "cannot create branch" "$repo" + return 1 +} + +# Commit bumped workflow files to the PR branch. Files already at NEW_VERSION +# on the branch (e.g. from a previous attempt) are left untouched. +update_workflow_files() { + local repo="$1"; shift + local wf file_data file_sha old_content new_content encoded + + for wf in "$@"; do + file_data=$(gh api "repos/$repo/contents/.github/workflows/$wf?ref=$BRANCH_NAME" \ + --jq '{sha: .sha, content: .content}' 2>"$STDERR_FILE" || echo "{}") + file_sha=$(echo "$file_data" | jq -r '.sha // empty') + if [ -z "$file_sha" ]; then + capture_last_error + report_failure "cannot read $wf" "$repo" + return 1 + fi + + old_content=$(decode_content "$(echo "$file_data" | jq -r '.content // empty')") + new_content=$(echo "$old_content" | bump_refs) + if [ "$new_content" = "$old_content" ]; then + continue + fi + encoded=$(echo "$new_content" | base64 | tr -d '\n') -created=0 -updated=0 -skipped=0 -failed=0 + if ! gh_run api "repos/$repo/contents/.github/workflows/$wf" \ + -X PUT \ + -f "message=Bump shared workflow refs to ${NEW_VERSION}" \ + -f "content=$encoded" \ + -f "sha=$file_sha" \ + -f "branch=$BRANCH_NAME"; then + report_failure "cannot update $wf" "$repo" + return 1 + fi + done +} -# Disable set -e for the repo loop — errors are handled explicitly with if/continue -set +eo pipefail +build_pr_body() { + local files="$1" + local body="## Summary -for repo in $REPOS; do - # Skip self - if [ "$repo" = "$SELF_REPO" ]; then - continue +Updates \`futuredapp/.github\` workflow refs from current version to \`@${NEW_VERSION}\`. + +**Updated files:** ${files}" + + if [ "$IS_BREAKING" = true ]; then + body="$body + +> [!CAUTION] +> **This is a major version bump (\`${PREVIOUS_TAG}\` → \`${NEW_VERSION}\`).** This release may contain breaking changes. Review carefully before merging." fi - # Check if repo is archived + echo "$body + +See [changelog](https://futuredapp.github.io/.github/${NEW_VERSION}/) for what changed. + +--- +*Automated PR created by [futuredapp/.github](https://github.com/futuredapp/.github)*" +} + +# Process one repo. Sets RESULT to created | updated | skipped | failed. +process_repo() { + local repo="$1" + RESULT="failed" + + local is_archived default_branch existing_pr workflow_files wf raw_content content + local base_sha pr_title pr_body pr_url pr_state + is_archived=$(gh api "repos/$repo" --jq '.archived' 2>/dev/null || echo "true") if [ "$is_archived" = "true" ]; then echo "SKIP (archived): $repo" - skipped=$((skipped + 1)) - continue + RESULT="skipped" + return fi - # Get default branch default_branch=$(gh api "repos/$repo" --jq '.default_branch' 2>/dev/null || echo "") if [ -z "$default_branch" ]; then echo "SKIP (no access): $repo" - skipped=$((skipped + 1)) - continue + RESULT="skipped" + return fi - # Check if an open PR already exists for this branch existing_pr=$(gh pr list --repo "$repo" --head "$BRANCH_NAME" --state open --json number --jq '.[0].number' 2>/dev/null || echo "") - # Find workflow files referencing futuredapp/.github workflow_files=$(gh api "repos/$repo/contents/.github/workflows" --jq '.[].name' 2>/dev/null || echo "") if [ -z "$workflow_files" ]; then echo "SKIP (no workflows): $repo" - skipped=$((skipped + 1)) - continue + RESULT="skipped" + return fi - files_to_update=() + local files_to_update=() for wf in $workflow_files; do raw_content=$(gh api "repos/$repo/contents/.github/workflows/$wf" --jq '.content' 2>/dev/null || echo "") - content="" - if [ -n "$raw_content" ]; then - content=$(echo "$raw_content" | base64 -d 2>/dev/null || echo "") - fi + content=$(decode_content "$raw_content") if echo "$content" | grep -q 'futuredapp/\.github/'; then if ! echo "$content" | grep -q "@${NEW_VERSION}"; then files_to_update+=("$wf") @@ -129,8 +281,8 @@ for repo in $REPOS; do if [ ${#files_to_update[@]} -eq 0 ]; then echo "SKIP (already up to date): $repo" - skipped=$((skipped + 1)) - continue + RESULT="skipped" + return fi if [ "$DRY_RUN" = "--dry-run" ]; then @@ -139,141 +291,159 @@ for repo in $REPOS; do else echo "DRY RUN (create): $repo (${#files_to_update[@]} files: ${files_to_update[*]})" fi - created=$((created + 1)) - continue + RESULT="created" + return fi - # Get the SHA of the default branch HEAD - base_sha=$(gh api "repos/$repo/git/refs/heads/$default_branch" --jq '.object.sha' 2>/dev/null || echo "") + base_sha=$(gh api "repos/$repo/git/refs/heads/$default_branch" --jq '.object.sha' 2>"$STDERR_FILE" || echo "") if [ -z "$base_sha" ]; then - echo "FAIL (cannot get HEAD): $repo" - failed=$((failed + 1)) - continue + capture_last_error + report_failure "cannot get HEAD" "$repo" + return fi if [ -n "$existing_pr" ]; then - # --- Update existing PR --- - - # Reset branch to latest default branch HEAD - if ! gh api "repos/$repo/git/refs/heads/$BRANCH_NAME" \ - -X PATCH -f sha="$base_sha" -F force=true >/dev/null 2>&1; then - echo "FAIL (cannot reset branch): $repo" - failed=$((failed + 1)) - continue - fi + prepare_existing_branch "$repo" "$default_branch" "$base_sha" || return else - # --- Create new branch --- - - if ! gh api "repos/$repo/git/refs" \ - -f "ref=refs/heads/$BRANCH_NAME" \ - -f "sha=$base_sha" >/dev/null 2>&1; then - # Branch may already exist from a previous run — try to reset it - if ! gh api "repos/$repo/git/refs/heads/$BRANCH_NAME" \ - -X PATCH -f sha="$base_sha" -F force=true >/dev/null 2>&1; then - echo "FAIL (cannot create branch): $repo" - failed=$((failed + 1)) - continue - fi - fi + prepare_new_branch "$repo" "$base_sha" || return fi - # Update each workflow file on the branch via Contents API - update_ok=true - for wf in "${files_to_update[@]}"; do - file_data=$(gh api "repos/$repo/contents/.github/workflows/$wf?ref=$BRANCH_NAME" --jq '{sha: .sha, content: .content}' 2>/dev/null || echo "{}") - file_sha=$(echo "$file_data" | jq -r '.sha // empty') - raw_content=$(echo "$file_data" | jq -r '.content // empty') - old_content="" - if [ -n "$raw_content" ]; then - old_content=$(echo "$raw_content" | base64 -d 2>/dev/null || echo "") - fi - - new_content=$(echo "$old_content" | sed -E "s#(futuredapp/\.github/.+)@(main|[0-9]+\.[0-9]+\.[0-9]+)#\1@${NEW_VERSION}#g") - encoded=$(echo "$new_content" | base64 | tr -d '\n') + update_workflow_files "$repo" "${files_to_update[@]}" || return - if ! gh api "repos/$repo/contents/.github/workflows/$wf" \ - -X PUT \ - -f "message=Bump shared workflow refs to ${NEW_VERSION}" \ - -f "content=$encoded" \ - -f "sha=$file_sha" \ - -f "branch=$BRANCH_NAME" >/dev/null 2>&1; then - echo "FAIL (cannot update $wf): $repo" - update_ok=false - break - fi - done - - if [ "$update_ok" = false ]; then - failed=$((failed + 1)) - continue - fi - - # Build PR title and body pr_title="Bump shared workflows to ${NEW_VERSION}" if [ "$IS_BREAKING" = true ]; then pr_title="⚠️ $pr_title (breaking changes)" fi + pr_body=$(build_pr_body "${files_to_update[*]}") - pr_body="## Summary + if [ -n "$existing_pr" ]; then + if ! gh_run pr edit "$existing_pr" --repo "$repo" --title "$pr_title" --body "$pr_body"; then + report_failure "cannot edit PR #$existing_pr" "$repo" + return + fi + if [ "$IS_BREAKING" = true ]; then + gh pr ready --undo --repo "$repo" "$existing_pr" 2>/dev/null || true + fi -Updates \`futuredapp/.github\` workflow refs from current version to \`@${NEW_VERSION}\`. + # Guard against reporting success on a PR GitHub closed underneath us. + pr_state=$(gh pr view "$existing_pr" --repo "$repo" --json state --jq '.state' 2>/dev/null || echo "") + if [ "$pr_state" != "OPEN" ]; then + LAST_ERROR="PR state is ${pr_state:-unknown}" + report_failure "PR #$existing_pr not open" "$repo" + return + fi -**Updated files:** ${files_to_update[*]}" + echo "UPDATED: $repo → PR #$existing_pr" + RESULT="updated" + return + fi + local create_args=( + --repo "$repo" + --head "$BRANCH_NAME" + --base "$default_branch" + --title "$pr_title" + --body "$pr_body" + ) if [ "$IS_BREAKING" = true ]; then - pr_body="$pr_body + create_args+=(--draft) + fi -> [!CAUTION] -> **This is a major version bump (\`${PREVIOUS_TAG}\` → \`${NEW_VERSION}\`).** This release may contain breaking changes. Review carefully before merging." + if ! pr_url=$(gh pr create "${create_args[@]}" 2>"$STDERR_FILE"); then + capture_last_error + report_failure "cannot create PR" "$repo" + return fi - pr_body="$pr_body + echo "CREATED: $repo → $pr_url" + RESULT="created" +} + +# Process a repo and update the counters. Returns 1 when the repo failed. +run_repo() { + process_repo "$1" + case "$RESULT" in + created) created=$((created + 1)) ;; + updated) updated=$((updated + 1)) ;; + skipped) skipped=$((skipped + 1)) ;; + *) return 1 ;; + esac + return 0 +} + +# --------------------------------------------------------------------------- +# Main +# --------------------------------------------------------------------------- + +main() { + if [ $# -lt 1 ]; then + echo "Usage: $0 [--dry-run]" + exit 1 + fi -See [changelog](https://futuredapp.github.io/.github/${NEW_VERSION}/) for what changed. + NEW_VERSION="$1" + DRY_RUN="${2:-}" + + STDERR_FILE=$(mktemp) + trap 'rm -f "$STDERR_FILE"' EXIT + + # Detect major version bump → breaking change + PREVIOUS_TAG=$(git tag --sort=-v:refname | grep -E '^[0-9]+\.[0-9]+\.[0-9]+$' | grep -v "^${NEW_VERSION}$" | head -1 || true) + IS_BREAKING=false + if [ -n "$PREVIOUS_TAG" ]; then + local old_major="${PREVIOUS_TAG%%.*}" + local new_major="${NEW_VERSION%%.*}" + if [ "$old_major" != "$new_major" ]; then + IS_BREAKING=true + echo "Major version bump detected: ${PREVIOUS_TAG} → ${NEW_VERSION}" + fi + fi ---- -*Automated PR created by [futuredapp/.github](https://github.com/futuredapp/.github)*" + local repos + repos=$(discover_repos) + if [ -n "${NOTIFY_REPOS_WHITELIST:-}" ]; then + repos=$(apply_whitelist "$repos") + fi + repos=$(echo "$repos" | grep -vx "$SELF_REPO" || true) - if [ -n "$existing_pr" ]; then - # --- Update existing PR --- + created=0 + updated=0 + skipped=0 - gh pr edit "$existing_pr" \ - --repo "$repo" \ - --title "$pr_title" \ - --body "$pr_body" >/dev/null 2>&1 + # Disable set -e for the repo loop — errors are handled explicitly per repo + set +eo pipefail - # Convert to draft if breaking - if [ "$IS_BREAKING" = true ]; then - gh pr ready --undo --repo "$repo" "$existing_pr" 2>/dev/null || true - fi - - echo "UPDATED: $repo → PR #$existing_pr" - updated=$((updated + 1)) - else - # --- Create new PR --- - - create_args=( - --repo "$repo" - --head "$BRANCH_NAME" - --base "$default_branch" - --title "$pr_title" - --body "$pr_body" - ) - if [ "$IS_BREAKING" = true ]; then - create_args+=(--draft) - fi + local repo + local failed_repos=() + for repo in $repos; do + run_repo "$repo" || failed_repos+=("$repo") + done - pr_url=$(gh pr create "${create_args[@]}" 2>&1 || echo "") + # Retry failed repos with exponential backoff (covers transient API/GitHub outages). + local attempt=0 delay + local still_failed=() + while [ ${#failed_repos[@]} -gt 0 ] && [ "$attempt" -lt "$RETRY_MAX_ATTEMPTS" ]; do + attempt=$((attempt + 1)) + delay=$(retry_delay "$attempt") + echo "" + echo "Retry ${attempt}/${RETRY_MAX_ATTEMPTS}: ${#failed_repos[@]} repo(s) failed, waiting ${delay}s..." + sleep "$delay" + + still_failed=() + for repo in "${failed_repos[@]}"; do + run_repo "$repo" || still_failed+=("$repo") + done + failed_repos=(${still_failed[@]+"${still_failed[@]}"}) + done - if [ -n "$pr_url" ]; then - echo "CREATED: $repo → $pr_url" - created=$((created + 1)) - else - echo "FAIL (cannot create PR): $repo" - failed=$((failed + 1)) - fi + local failed=${#failed_repos[@]} + echo "" + echo "Done. Created: $created, Updated: $updated, Skipped: $skipped, Failed: $failed" + if [ "$failed" -gt 0 ]; then + echo "Still failing after ${attempt} retries: ${failed_repos[*]}" fi -done +} -echo "" -echo "Done. Created: $created, Updated: $updated, Skipped: $skipped, Failed: $failed" +if [ "${BASH_SOURCE[0]}" = "$0" ]; then + main "$@" +fi diff --git a/.github/scripts/test/mocks/gh b/.github/scripts/test/mocks/gh new file mode 100755 index 00000000..02566f4d --- /dev/null +++ b/.github/scripts/test/mocks/gh @@ -0,0 +1,132 @@ +#!/bin/bash +# Mock of the gh CLI used by test_notify-consumer-repos.bats. +# +# Every invocation is appended to $GH_MOCK_LOG (one line per call) so tests can +# assert which API calls were made. Behaviour is driven by environment variables: +# +# GH_MOCK_EXISTING_PR number of an open PR on the bump branch (empty = none) +# GH_MOCK_PR_STATE state returned by `gh pr view` (default OPEN) +# GH_MOCK_BRANCH_EXISTS 1 → creating the branch fails (already exists) +# GH_MOCK_MERGE_FAIL 1 → merging the default branch into the bump branch fails +# GH_MOCK_PUT_FAIL_TIMES number of file PUTs that fail before succeeding (-1 = always) +# GH_MOCK_PUT_FAIL_REPO limit PUT failures to this org/repo (default: every repo) +# GH_MOCK_PR_CREATE_FAIL 1 → `gh pr create` fails +# GH_MOCK_BRANCH_FILE_VERSION shared-workflow version referenced by the file on the bump branch +# GH_MOCK_STATE_DIR directory for counters that persist across invocations + +set -uo pipefail + +echo "$*" >> "$GH_MOCK_LOG" + +DEFAULT_BRANCH_VERSION="2.5.0" +BRANCH_FILE_VERSION="${GH_MOCK_BRANCH_FILE_VERSION:-$DEFAULT_BRANCH_VERSION}" + +fail() { + echo "gh: $1" >&2 + exit 1 +} + +respond() { + if [ -n "$jq_filter" ]; then + echo "$1" | jq -r "$jq_filter" + else + echo "$1" + fi +} + +workflow_with_ref() { + printf 'jobs:\n test:\n uses: futuredapp/.github/.github/workflows/ios-selfhosted-test.yml@%s\n' "$1" +} + +contents_json() { + local sha="$1" body="$2" + printf '{"sha":"%s","content":"%s"}' "$sha" "$(echo "$body" | base64 | tr -d '\n')" +} + +cmd="$1"; shift + +method="GET"; path=""; jq_filter=""; has_body=0 +args=("$@") +i=0 +while [ $i -lt ${#args[@]} ]; do + a="${args[$i]}" + case "$a" in + -X) i=$((i + 1)); method="${args[$i]}" ;; + -f|-F|-H) i=$((i + 1)); has_body=1 ;; + --jq|-q) i=$((i + 1)); jq_filter="${args[$i]}" ;; + --repo|--head|--base|--state|--json|--title|--body) i=$((i + 1)) ;; + -*) ;; + *) [ -z "$path" ] && path="$a" ;; + esac + i=$((i + 1)) +done +if [ "$method" = "GET" ] && [ "$has_body" = 1 ]; then + method="POST" +fi + +case "$cmd" in + api) + case "$method $path" in + "GET /search/code") + respond '{"items":[]}' ;; + "GET repos/"*"/contents/.github/workflows") + respond '[{"name":"ci.yml"},{"name":"lint.yml"}]' ;; + "GET repos/"*"/contents/.github/workflows/lint.yml"*) + respond "$(contents_json lintsha "$(printf 'jobs:\n lint:\n runs-on: ubuntu-latest\n')")" ;; + "GET repos/"*"/contents/.github/workflows/ci.yml?ref="*) + respond "$(contents_json branchsha "$(workflow_with_ref "$BRANCH_FILE_VERSION")")" ;; + "GET repos/"*"/contents/.github/workflows/ci.yml") + respond "$(contents_json basesha "$(workflow_with_ref "$DEFAULT_BRANCH_VERSION")")" ;; + "PUT repos/"*"/contents/.github/workflows/"*) + counter="$GH_MOCK_STATE_DIR/put_count" + count=$(cat "$counter" 2>/dev/null || echo 0) + echo $((count + 1)) > "$counter" + limit="${GH_MOCK_PUT_FAIL_TIMES:-0}" + if [ -n "${GH_MOCK_PUT_FAIL_REPO:-}" ] && [[ "$path" != "repos/${GH_MOCK_PUT_FAIL_REPO}/"* ]]; then + limit=0 + fi + if [ "$limit" = "-1" ] || [ "$count" -lt "$limit" ]; then + fail "HTTP 502: Server Error (https://api.github.com/$path)" + fi + respond '{"commit":{"sha":"newsha"}}' ;; + "GET repos/"*"/git/refs/heads/"*) + respond '{"object":{"sha":"basesha123"}}' ;; + "PATCH repos/"*"/git/refs/heads/"*|"DELETE repos/"*"/git/refs/heads/"*) + respond '{}' ;; + "POST repos/"*"/git/refs") + if [ "${GH_MOCK_BRANCH_EXISTS:-0}" = 1 ]; then + fail "HTTP 422: Reference already exists" + fi + respond '{"ref":"refs/heads/housekeep/bump-shared-workflows"}' ;; + "POST repos/"*"/merges") + if [ "${GH_MOCK_MERGE_FAIL:-0}" = 1 ]; then + fail "HTTP 409: Merge conflict" + fi + respond '{"sha":"mergesha"}' ;; + "GET repos/"*) + respond '{"archived":false,"default_branch":"main"}' ;; + *) + fail "mock: unhandled api call: $method $path" ;; + esac ;; + pr) + sub="$1" + case "$sub" in + list) + if [ -n "${GH_MOCK_EXISTING_PR:-}" ]; then + echo "$GH_MOCK_EXISTING_PR" + fi ;; + create) + if [ "${GH_MOCK_PR_CREATE_FAIL:-0}" = 1 ]; then + fail "pull request create failed: GraphQL: Something went wrong" + fi + echo "https://github.com/futuredapp/demo-repo/pull/42" ;; + edit|ready) + : ;; + view) + respond "{\"state\":\"${GH_MOCK_PR_STATE:-OPEN}\"}" ;; + *) + fail "mock: unhandled pr subcommand: $sub" ;; + esac ;; + *) + fail "mock: unhandled command: $cmd" ;; +esac diff --git a/.github/scripts/test/test_notify-consumer-repos.bats b/.github/scripts/test/test_notify-consumer-repos.bats new file mode 100644 index 00000000..57d9188a --- /dev/null +++ b/.github/scripts/test/test_notify-consumer-repos.bats @@ -0,0 +1,166 @@ +#!/usr/bin/env bats + +SCRIPT="$BATS_TEST_DIRNAME/../notify-consumer-repos.sh" +REPO="futuredapp/demo-repo" +BRANCH="housekeep/bump-shared-workflows" + +setup() { + export PATH="$BATS_TEST_DIRNAME/mocks:$PATH" + export GH_MOCK_LOG="$BATS_TEST_TMPDIR/gh.log" + export GH_MOCK_STATE_DIR="$BATS_TEST_TMPDIR/state" + mkdir -p "$GH_MOCK_STATE_DIR" + : > "$GH_MOCK_LOG" + + export NOTIFY_REPOS="$REPO" + export NOTIFY_RETRY_BASE_DELAY=0 + export NOTIFY_RETRY_MAX_ATTEMPTS=3 + unset NOTIFY_REPOS_WHITELIST + unset GH_MOCK_EXISTING_PR GH_MOCK_PR_STATE GH_MOCK_BRANCH_EXISTS GH_MOCK_MERGE_FAIL \ + GH_MOCK_PUT_FAIL_TIMES GH_MOCK_PUT_FAIL_REPO GH_MOCK_PR_CREATE_FAIL GH_MOCK_BRANCH_FILE_VERSION +} + +run_script() { + run bash "$SCRIPT" 2.9.9 "$@" +} + +gh_called() { + grep -q -- "$1" "$GH_MOCK_LOG" +} + +# --- backoff ----------------------------------------------------------------- + +@test "retry delay doubles from the base delay and is capped" { + NOTIFY_RETRY_BASE_DELAY=2 NOTIFY_RETRY_MAX_DELAY=300 source "$SCRIPT" + delays="" + for attempt in 1 2 3 4 5 6 7 8 9 10; do + delays="$delays $(retry_delay "$attempt")" + done + [ "$delays" = " 2 4 8 16 32 64 128 256 300 300" ] +} + +# --- create path ------------------------------------------------------------- + +@test "creates a PR when no bump PR is open" { + run_script + [ "$status" -eq 0 ] + [[ "$output" == *"CREATED: $REPO → https://github.com/futuredapp/demo-repo/pull/42"* ]] + [[ "$output" == *"Done. Created: 1, Updated: 0, Skipped: 0, Failed: 0"* ]] + gh_called "api repos/$REPO/git/refs -f ref=refs/heads/$BRANCH" + gh_called "contents/.github/workflows/ci.yml -X PUT" + ! gh_called "contents/.github/workflows/lint.yml -X PUT" + ! gh_called "repos/$REPO/merges" +} + +@test "reuses a leftover branch by resetting it to base HEAD" { + export GH_MOCK_BRANCH_EXISTS=1 + run_script + [[ "$output" == *"CREATED: $REPO"* ]] + gh_called "api repos/$REPO/git/refs/heads/$BRANCH -X PATCH -f sha=basesha123 -F force=true" +} + +@test "failed PR creation is reported as FAIL with the gh error message" { + export GH_MOCK_PR_CREATE_FAIL=1 + run_script + [[ "$output" != *"CREATED"* ]] + [[ "$output" == *"FAIL (cannot create PR): $REPO — gh: pull request create failed"* ]] + [[ "$output" == *"Failed: 1"* ]] +} + +# --- update path ------------------------------------------------------------- + +@test "updates an open PR by merging the default branch instead of force-resetting" { + export GH_MOCK_EXISTING_PR=7 + run_script + [ "$status" -eq 0 ] + [[ "$output" == *"UPDATED: $REPO → PR #7"* ]] + [[ "$output" == *"Created: 0, Updated: 1, Skipped: 0, Failed: 0"* ]] + gh_called "api repos/$REPO/merges -f base=$BRANCH -f head=main" + gh_called "contents/.github/workflows/ci.yml -X PUT" + gh_called "pr edit 7 --repo $REPO" + ! gh_called "-X PATCH" + ! gh_called "pr create" +} + +@test "merge conflict recreates the branch and opens a fresh PR" { + export GH_MOCK_EXISTING_PR=7 + export GH_MOCK_MERGE_FAIL=1 + run_script + [[ "$output" == *"recreating branch"* ]] + [[ "$output" == *"CREATED: $REPO"* ]] + [[ "$output" == *"Created: 1, Updated: 0, Skipped: 0, Failed: 0"* ]] + gh_called "api -X DELETE repos/$REPO/git/refs/heads/$BRANCH" + gh_called "api repos/$REPO/git/refs -f ref=refs/heads/$BRANCH -f sha=basesha123" + gh_called "pr create" + ! gh_called "pr edit" +} + +@test "update is a failure when the PR is no longer open" { + export GH_MOCK_EXISTING_PR=7 + export GH_MOCK_PR_STATE=CLOSED + run_script + [[ "$output" != *"UPDATED"* ]] + [[ "$output" == *"FAIL (PR #7 not open): $REPO — PR state is CLOSED"* ]] + [[ "$output" == *"Failed: 1"* ]] +} + +@test "files already bumped on the branch are not rewritten" { + export GH_MOCK_EXISTING_PR=7 + export GH_MOCK_BRANCH_FILE_VERSION=2.9.9 + run_script + [[ "$output" == *"UPDATED: $REPO → PR #7"* ]] + ! gh_called "-X PUT" +} + +# --- retries ----------------------------------------------------------------- + +@test "transient write failures are retried with backoff until they succeed" { + export GH_MOCK_PUT_FAIL_TIMES=2 + export NOTIFY_RETRY_MAX_ATTEMPTS=5 + run_script + [ "$status" -eq 0 ] + [[ "$output" == *"FAIL (cannot update ci.yml): $REPO — gh: HTTP 502"* ]] + [[ "$output" == *"Retry 1/5: 1 repo(s) failed, waiting 0s..."* ]] + [[ "$output" == *"Retry 2/5: 1 repo(s) failed"* ]] + [[ "$output" != *"Retry 3/5"* ]] + [[ "$output" == *"CREATED: $REPO"* ]] + [[ "$output" == *"Done. Created: 1, Updated: 0, Skipped: 0, Failed: 0"* ]] + [ "$(grep -c -- '-X PUT' "$GH_MOCK_LOG")" -eq 3 ] +} + +@test "gives up after the maximum number of retries" { + export GH_MOCK_PUT_FAIL_TIMES=-1 + run_script + [[ "$output" == *"Retry 3/3"* ]] + [[ "$output" != *"Retry 4/3"* ]] + [[ "$output" == *"Done. Created: 0, Updated: 0, Skipped: 0, Failed: 1"* ]] + [[ "$output" == *"Still failing after 3 retries: $REPO"* ]] + [ "$(echo "$output" | grep -c 'FAIL (cannot update ci.yml)')" -eq 4 ] +} + +@test "only failed repos are retried" { + export NOTIFY_REPOS="futuredapp/healthy-repo $REPO" + export GH_MOCK_PUT_FAIL_TIMES=-1 + export GH_MOCK_PUT_FAIL_REPO="$REPO" + export NOTIFY_RETRY_MAX_ATTEMPTS=1 + run_script + [[ "$output" == *"Done. Created: 1, Updated: 0, Skipped: 0, Failed: 1"* ]] + [ "$(grep -c 'repos/futuredapp/healthy-repo/git/refs -f ref=' "$GH_MOCK_LOG")" -eq 1 ] + [ "$(grep -c "repos/$REPO/git/refs -f ref=" "$GH_MOCK_LOG")" -eq 2 ] +} + +# --- misc -------------------------------------------------------------------- + +@test "the shared workflows repo itself is never processed" { + export NOTIFY_REPOS="futuredapp/.github $REPO" + run_script + [[ "$output" == *"Done. Created: 1, Updated: 0, Skipped: 0, Failed: 0"* ]] + ! gh_called "repos/futuredapp/.github" +} + +@test "dry run reports planned changes without writing anything" { + run_script --dry-run + [[ "$output" == *"DRY RUN (create): $REPO (1 files: ci.yml)"* ]] + ! gh_called "-X PUT" + ! gh_called "pr create" + ! gh_called "git/refs -f ref=" +} diff --git a/.github/workflows/android-cloud-check.yml b/.github/workflows/android-cloud-check.yml index e72c9efc..0b25d189 100644 --- a/.github/workflows/android-cloud-check.yml +++ b/.github/workflows/android-cloud-check.yml @@ -81,7 +81,7 @@ jobs: with: lfs: ${{ inputs.USE_GIT_LFS }} - name: Set up environment - uses: futuredapp/.github/.github/actions/android-setup-environment@2.6.0 + uses: futuredapp/.github/.github/actions/android-setup-environment@main with: java_version: ${{ inputs.JAVA_VERSION }} java_distribution: ${{ inputs.JAVA_DISTRIBUTION }} @@ -90,7 +90,7 @@ jobs: android_sdk_packages: ${{ inputs.ANDROID_SDK_PACKAGES }} kotlin_native_cache: ${{ inputs.KOTLIN_NATIVE_CACHE }} - name: Run checks - uses: futuredapp/.github/.github/actions/android-check@2.6.0 + uses: futuredapp/.github/.github/actions/android-check@main with: lint_gradle_task: ${{ inputs.LINT_GRADLE_TASKS }} test_gradle_task: ${{ inputs.TEST_GRADLE_TASKS }} diff --git a/.github/workflows/android-cloud-generate-baseline-profiles.yml b/.github/workflows/android-cloud-generate-baseline-profiles.yml index 001ff94d..39ae2cac 100644 --- a/.github/workflows/android-cloud-generate-baseline-profiles.yml +++ b/.github/workflows/android-cloud-generate-baseline-profiles.yml @@ -75,13 +75,13 @@ jobs: with: lfs: ${{ inputs.USE_GIT_LFS }} - name: Set up environment - uses: futuredapp/.github/.github/actions/android-setup-environment@2.6.0 + uses: futuredapp/.github/.github/actions/android-setup-environment@main with: java_version: ${{ inputs.JAVA_VERSION }} java_distribution: ${{ inputs.JAVA_DISTRIBUTION }} gradle_cache_encryption_key: ${{ secrets.GRADLE_CACHE_ENCRYPTION_KEY }} - name: Generate baseline profiles - uses: futuredapp/.github/.github/actions/android-generate-baseline-profiles@2.6.0 + uses: futuredapp/.github/.github/actions/android-generate-baseline-profiles@main with: generate_gradle_task: ${{ inputs.TASK_NAME }} signing_keystore_password: ${{ secrets.SIGNING_KEYSTORE_PASSWORD }} diff --git a/.github/workflows/android-cloud-nightly-build.yml b/.github/workflows/android-cloud-nightly-build.yml index 16b59b02..7b0acb1e 100644 --- a/.github/workflows/android-cloud-nightly-build.yml +++ b/.github/workflows/android-cloud-nightly-build.yml @@ -138,7 +138,7 @@ jobs: steps: - name: Detect changes and generate changelog id: detect_changes - uses: futuredapp/.github/.github/actions/universal-detect-changes-and-generate-changelog@2.6.0 + uses: futuredapp/.github/.github/actions/universal-detect-changes-and-generate-changelog@main with: checkout_depth: ${{ inputs.CHANGELOG_CHECKOUT_DEPTH }} debug: ${{ inputs.CHANGELOG_DEBUG }} @@ -158,7 +158,7 @@ jobs: with: lfs: ${{ inputs.USE_GIT_LFS }} - name: Set up environment - uses: futuredapp/.github/.github/actions/android-setup-environment@2.6.0 + uses: futuredapp/.github/.github/actions/android-setup-environment@main with: java_version: ${{ inputs.JAVA_VERSION }} java_distribution: ${{ inputs.JAVA_DISTRIBUTION }} @@ -167,7 +167,7 @@ jobs: android_sdk_packages: ${{ inputs.ANDROID_SDK_PACKAGES }} kotlin_native_cache: ${{ inputs.KOTLIN_NATIVE_CACHE }} - name: Build and upload to App Distribution - uses: futuredapp/.github/.github/actions/android-build-firebase@2.6.0 + uses: futuredapp/.github/.github/actions/android-build-firebase@main with: test_gradle_task: ${{ inputs.TEST_GRADLE_TASKS }} package_gradle_task: ${{ inputs.PACKAGE_GRADLE_TASK }} @@ -214,7 +214,7 @@ jobs: fi - name: Transition JIRA tickets if: steps.check.outputs.enabled == 'true' - uses: futuredapp/.github/.github/actions/jira-transition-tickets@2.6.0 + uses: futuredapp/.github/.github/actions/jira-transition-tickets@main with: jira_context: ${{ secrets.JIRA_CONTEXT }} transition: ${{ inputs.JIRA_TRANSITION }} diff --git a/.github/workflows/android-cloud-release-firebaseAppDistribution.yml b/.github/workflows/android-cloud-release-firebaseAppDistribution.yml index e7704919..5844bb8a 100644 --- a/.github/workflows/android-cloud-release-firebaseAppDistribution.yml +++ b/.github/workflows/android-cloud-release-firebaseAppDistribution.yml @@ -117,7 +117,7 @@ jobs: with: lfs: ${{ inputs.USE_GIT_LFS }} - name: Set up environment - uses: futuredapp/.github/.github/actions/android-setup-environment@2.6.0 + uses: futuredapp/.github/.github/actions/android-setup-environment@main with: java_version: ${{ inputs.JAVA_VERSION }} java_distribution: ${{ inputs.JAVA_DISTRIBUTION }} @@ -126,7 +126,7 @@ jobs: android_sdk_packages: ${{ inputs.ANDROID_SDK_PACKAGES }} kotlin_native_cache: ${{ inputs.KOTLIN_NATIVE_CACHE }} - name: Build and upload to App Distribution - uses: futuredapp/.github/.github/actions/android-build-firebase@2.6.0 + uses: futuredapp/.github/.github/actions/android-build-firebase@main with: test_gradle_task: ${{ inputs.TEST_GRADLE_TASKS }} package_gradle_task: ${{ inputs.PACKAGE_GRADLE_TASK }} diff --git a/.github/workflows/android-cloud-release-googlePlay.yml b/.github/workflows/android-cloud-release-googlePlay.yml index 93782925..e3c23f3c 100644 --- a/.github/workflows/android-cloud-release-googlePlay.yml +++ b/.github/workflows/android-cloud-release-googlePlay.yml @@ -129,7 +129,7 @@ jobs: with: lfs: ${{ inputs.USE_GIT_LFS }} - name: Set up environment - uses: futuredapp/.github/.github/actions/android-setup-environment@2.6.0 + uses: futuredapp/.github/.github/actions/android-setup-environment@main with: java_version: ${{ inputs.JAVA_VERSION }} java_distribution: ${{ inputs.JAVA_DISTRIBUTION }} @@ -137,7 +137,7 @@ jobs: android_sdk_packages: ${{ inputs.ANDROID_SDK_PACKAGES }} kotlin_native_cache: ${{ inputs.KOTLIN_NATIVE_CACHE }} - name: Build and release - uses: futuredapp/.github/.github/actions/android-build-googlePlay@2.6.0 + uses: futuredapp/.github/.github/actions/android-build-googlePlay@main with: bundle_gradle_task: ${{ inputs.BUNDLE_GRADLE_TASK }} version_name: ${{ inputs.VERSION_NAME }} diff --git a/.github/workflows/ios-kmp-selfhosted-build.yml b/.github/workflows/ios-kmp-selfhosted-build.yml index 94f59419..a99fac79 100644 --- a/.github/workflows/ios-kmp-selfhosted-build.yml +++ b/.github/workflows/ios-kmp-selfhosted-build.yml @@ -98,14 +98,14 @@ jobs: with: lfs: ${{ inputs.use_git_lfs }} - name: Set up environment - uses: futuredapp/.github/.github/actions/android-setup-environment@2.6.0 + uses: futuredapp/.github/.github/actions/android-setup-environment@main with: java_version: ${{ inputs.java_version }} java_distribution: ${{ inputs.java_distribution }} gradle_cache_encryption_key: ${{ secrets.GRADLE_CACHE_ENCRYPTION_KEY }} ruby: 'false' - name: Build and upload to TestFlight - uses: futuredapp/.github/.github/actions/ios-kmp-build@2.6.0 + uses: futuredapp/.github/.github/actions/ios-kmp-build@main with: match_password: ${{ secrets.MATCH_PASSWORD }} app_store_connect_api_key_key: ${{ secrets.APP_STORE_CONNECT_API_KEY_KEY }} diff --git a/.github/workflows/ios-kmp-selfhosted-release.yml b/.github/workflows/ios-kmp-selfhosted-release.yml index aa47a91a..c20a4a88 100644 --- a/.github/workflows/ios-kmp-selfhosted-release.yml +++ b/.github/workflows/ios-kmp-selfhosted-release.yml @@ -92,7 +92,7 @@ jobs: lfs: ${{ inputs.use_git_lfs }} - name: Export secrets to .xcconfig file if: ${{ inputs.xcconfig_path != '' }} - uses: futuredapp/.github/.github/actions/ios-export-secrets@2.6.0 + uses: futuredapp/.github/.github/actions/ios-export-secrets@main with: XCCONFIG_PATH: ${{ inputs.xcconfig_path }} SECRET_PROPERTIES: ${{ secrets.SECRET_PROPERTIES }} @@ -116,7 +116,7 @@ jobs: cd ${{ inputs.kmp_swift_package_path }} make build - name: Fastlane Release - uses: futuredapp/.github/.github/actions/ios-fastlane-release@2.6.0 + uses: futuredapp/.github/.github/actions/ios-fastlane-release@main with: match_password: ${{ secrets.MATCH_PASSWORD }} version_number: ${{ github.event.release.tag_name || github.ref_name }} diff --git a/.github/workflows/ios-kmp-selfhosted-test.yml b/.github/workflows/ios-kmp-selfhosted-test.yml index 1fef7813..500048b5 100644 --- a/.github/workflows/ios-kmp-selfhosted-test.yml +++ b/.github/workflows/ios-kmp-selfhosted-test.yml @@ -99,7 +99,7 @@ jobs: cd ${{ inputs.kmp_swift_package_path }} make build - name: Fastlane Test - uses: futuredapp/.github/.github/actions/ios-fastlane-test@2.6.0 + uses: futuredapp/.github/.github/actions/ios-fastlane-test@main with: github_token: ${{ secrets.GITHUB_TOKEN_DANGER }} custom_values: ${{ inputs.custom_values }} diff --git a/.github/workflows/ios-selfhosted-build.yml b/.github/workflows/ios-selfhosted-build.yml index 6411581d..c52f4743 100644 --- a/.github/workflows/ios-selfhosted-build.yml +++ b/.github/workflows/ios-selfhosted-build.yml @@ -63,7 +63,7 @@ jobs: echo "::warning::This workflow ('ios-selfhosted-build.yml') is deprecated and will be removed in the future." echo "::warning::Please use 'ios-selfhosted-nightly-build.yml' instead." build: - uses: futuredapp/.github/.github/workflows/ios-selfhosted-nightly-build.yml@2.6.0 + uses: futuredapp/.github/.github/workflows/ios-selfhosted-nightly-build.yml@main with: use_git_lfs: ${{ inputs.use_git_lfs }} custom_values: ${{ inputs.custom_values }} diff --git a/.github/workflows/ios-selfhosted-nightly-build.yml b/.github/workflows/ios-selfhosted-nightly-build.yml index 56cc05c1..4ddd1d7d 100644 --- a/.github/workflows/ios-selfhosted-nightly-build.yml +++ b/.github/workflows/ios-selfhosted-nightly-build.yml @@ -80,7 +80,7 @@ jobs: steps: - name: Detect changes and generate changelog id: detect_changes - uses: futuredapp/.github/.github/actions/universal-detect-changes-and-generate-changelog@2.6.0 + uses: futuredapp/.github/.github/actions/universal-detect-changes-and-generate-changelog@main with: checkout_depth: ${{ inputs.checkout_depth }} fallback_lookback: ${{ inputs.changelog_fallback_lookback }} @@ -94,7 +94,7 @@ jobs: - name: Export secrets if: ${{ steps.detect_changes.outputs.skip_build == 'false' && inputs.xcconfig_path != '' }} - uses: futuredapp/.github/.github/actions/ios-export-secrets@2.6.0 + uses: futuredapp/.github/.github/actions/ios-export-secrets@main with: XCCONFIG_PATH: ${{ inputs.xcconfig_path }} SECRET_PROPERTIES: ${{ secrets.SECRET_PROPERTIES }} @@ -102,7 +102,7 @@ jobs: - name: Fastlane Beta if: ${{ steps.detect_changes.outputs.skip_build == 'false' }} - uses: futuredapp/.github/.github/actions/ios-fastlane-beta@2.6.0 + uses: futuredapp/.github/.github/actions/ios-fastlane-beta@main with: match_password: ${{ secrets.MATCH_PASSWORD }} testflight_changelog: ${{ steps.set_changelog.outputs.changelog }} @@ -158,7 +158,7 @@ jobs: fi - name: Transition JIRA tickets if: steps.check.outputs.enabled == 'true' - uses: futuredapp/.github/.github/actions/jira-transition-tickets@2.6.0 + uses: futuredapp/.github/.github/actions/jira-transition-tickets@main with: jira_context: ${{ secrets.JIRA_CONTEXT }} transition: ${{ inputs.jira_transition }} diff --git a/.github/workflows/ios-selfhosted-on-demand-build.yml b/.github/workflows/ios-selfhosted-on-demand-build.yml index ec77554c..bedc2211 100644 --- a/.github/workflows/ios-selfhosted-on-demand-build.yml +++ b/.github/workflows/ios-selfhosted-on-demand-build.yml @@ -81,7 +81,7 @@ jobs: - name: Generate changelog if not provided if: ${{ inputs.changelog == '' }} id: detect_changes - uses: futuredapp/.github/.github/actions/universal-detect-changes-and-generate-changelog@2.6.0 + uses: futuredapp/.github/.github/actions/universal-detect-changes-and-generate-changelog@main with: checkout_depth: ${{ inputs.checkout_depth }} fallback_lookback: ${{ inputs.changelog_fallback_lookback }} @@ -104,14 +104,14 @@ jobs: - name: Export secrets if: ${{ inputs.xcconfig_path != '' }} - uses: futuredapp/.github/.github/actions/ios-export-secrets@2.6.0 + uses: futuredapp/.github/.github/actions/ios-export-secrets@main with: XCCONFIG_PATH: ${{ inputs.xcconfig_path }} SECRET_PROPERTIES: ${{ secrets.SECRET_PROPERTIES }} REQUIRED_KEYS: ${{ inputs.required_keys }} - name: Fastlane Beta - uses: futuredapp/.github/.github/actions/ios-fastlane-beta@2.6.0 + uses: futuredapp/.github/.github/actions/ios-fastlane-beta@main with: MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }} testflight_changelog: ${{ steps.set_changelog.outputs.changelog }} diff --git a/.github/workflows/ios-selfhosted-release.yml b/.github/workflows/ios-selfhosted-release.yml index 18782b21..63148f60 100644 --- a/.github/workflows/ios-selfhosted-release.yml +++ b/.github/workflows/ios-selfhosted-release.yml @@ -62,14 +62,14 @@ jobs: - name: Export secrets if: ${{ inputs.xcconfig_path != '' }} - uses: futuredapp/.github/.github/actions/ios-export-secrets@2.6.0 + uses: futuredapp/.github/.github/actions/ios-export-secrets@main with: XCCONFIG_PATH: ${{ inputs.xcconfig_path }} SECRET_PROPERTIES: ${{ secrets.SECRET_PROPERTIES }} REQUIRED_KEYS: ${{ inputs.required_keys }} - name: Fastlane Release - uses: futuredapp/.github/.github/actions/ios-fastlane-release@2.6.0 + uses: futuredapp/.github/.github/actions/ios-fastlane-release@main with: match_password: ${{ secrets.MATCH_PASSWORD }} version_number: ${{ github.ref_name }} diff --git a/.github/workflows/ios-selfhosted-test.yml b/.github/workflows/ios-selfhosted-test.yml index 9db512e9..4f06ba20 100644 --- a/.github/workflows/ios-selfhosted-test.yml +++ b/.github/workflows/ios-selfhosted-test.yml @@ -41,7 +41,7 @@ jobs: lfs: ${{ inputs.use_git_lfs }} - name: Fastlane Test - uses: futuredapp/.github/.github/actions/ios-fastlane-test@2.6.0 + uses: futuredapp/.github/.github/actions/ios-fastlane-test@main with: github_token: ${{ secrets.GITHUB_TOKEN_DANGER }} custom_values: ${{ inputs.custom_values }} \ No newline at end of file diff --git a/.github/workflows/kmp-cloud-detect-changes.yml b/.github/workflows/kmp-cloud-detect-changes.yml index e6a8240a..50caa63d 100644 --- a/.github/workflows/kmp-cloud-detect-changes.yml +++ b/.github/workflows/kmp-cloud-detect-changes.yml @@ -28,7 +28,7 @@ name: Detect Changes # # Note: For direct action usage in other workflows, you can also use: # - name: Detect Changes -# uses: futuredapp/.github/.github/actions/kmp-detect-changes@2.6.0 +# uses: futuredapp/.github/.github/actions/kmp-detect-changes@main on: workflow_call: @@ -58,6 +58,6 @@ jobs: steps: - name: Detect Changes id: detect - uses: futuredapp/.github/.github/actions/kmp-detect-changes@2.6.0 + uses: futuredapp/.github/.github/actions/kmp-detect-changes@main with: USE_GIT_LFS: ${{ inputs.USE_GIT_LFS }} diff --git a/.github/workflows/kmp-combined-nightly-build.yml b/.github/workflows/kmp-combined-nightly-build.yml index f6d56d3e..43355d1d 100644 --- a/.github/workflows/kmp-combined-nightly-build.yml +++ b/.github/workflows/kmp-combined-nightly-build.yml @@ -173,7 +173,7 @@ jobs: steps: - name: Detect changes and generate changelog id: detect_changes - uses: futuredapp/.github/.github/actions/universal-detect-changes-and-generate-changelog@2.6.0 + uses: futuredapp/.github/.github/actions/universal-detect-changes-and-generate-changelog@main with: checkout_depth: ${{ inputs.CHANGELOG_CHECKOUT_DEPTH }} debug: ${{ inputs.CHANGELOG_DEBUG }} @@ -192,14 +192,14 @@ jobs: with: lfs: ${{ inputs.USE_GIT_LFS }} - name: Set up environment - uses: futuredapp/.github/.github/actions/android-setup-environment@2.6.0 + uses: futuredapp/.github/.github/actions/android-setup-environment@main with: java_version: ${{ inputs.JAVA_VERSION }} java_distribution: ${{ inputs.JAVA_DISTRIBUTION }} gradle_cache_encryption_key: ${{ secrets.GRADLE_CACHE_ENCRYPTION_KEY }} ruby: 'false' - name: Build and upload to TestFlight - uses: futuredapp/.github/.github/actions/ios-kmp-build@2.6.0 + uses: futuredapp/.github/.github/actions/ios-kmp-build@main with: match_password: ${{ secrets.IOS_MATCH_PASSWORD }} app_store_connect_api_key_key: ${{ secrets.IOS_APP_STORE_CONNECT_API_KEY_KEY }} @@ -229,7 +229,7 @@ jobs: with: lfs: ${{ inputs.USE_GIT_LFS }} - name: Set up environment - uses: futuredapp/.github/.github/actions/android-setup-environment@2.6.0 + uses: futuredapp/.github/.github/actions/android-setup-environment@main with: java_version: ${{ inputs.JAVA_VERSION }} java_distribution: ${{ inputs.JAVA_DISTRIBUTION }} @@ -238,7 +238,7 @@ jobs: android_sdk_packages: ${{ inputs.ANDROID_SDK_PACKAGES }} kotlin_native_cache: ${{ inputs.ANDROID_KOTLIN_NATIVE_CACHE }} - name: Build and upload to App Distribution - uses: futuredapp/.github/.github/actions/android-build-firebase@2.6.0 + uses: futuredapp/.github/.github/actions/android-build-firebase@main with: test_gradle_task: ${{ inputs.ANDROID_TEST_GRADLE_TASK }} package_gradle_task: ${{ inputs.ANDROID_PACKAGE_GRADLE_TASK }} @@ -294,7 +294,7 @@ jobs: fi - name: Transition JIRA tickets if: steps.check.outputs.enabled == 'true' - uses: futuredapp/.github/.github/actions/jira-transition-tickets@2.6.0 + uses: futuredapp/.github/.github/actions/jira-transition-tickets@main with: jira_context: ${{ secrets.JIRA_CONTEXT }} transition: ${{ inputs.JIRA_TRANSITION }} diff --git a/.github/workflows/notify-consumer-repos.yml b/.github/workflows/notify-consumer-repos.yml index 37927e72..fd3789f2 100644 --- a/.github/workflows/notify-consumer-repos.yml +++ b/.github/workflows/notify-consumer-repos.yml @@ -6,6 +6,11 @@ on: - '[0-9]+.[0-9]+.[0-9]+' workflow_dispatch: + inputs: + repos: + description: 'Only process these repos (space-separated org/name). Leave empty to discover all consumer repos.' + required: false + type: string jobs: notify: @@ -21,6 +26,7 @@ jobs: env: GH_TOKEN: ${{ secrets.BOT_TOKEN }} NOTIFY_REPOS_WHITELIST: ${{ vars.NOTIFY_REPOS_WHITELIST }} + NOTIFY_REPOS: ${{ inputs.repos }} run: | if [ "${{ github.event_name }}" = "push" ]; then VERSION="${{ github.ref_name }}" From b23e06b6388c5ba3a82dee4c6a634ce684b620d6 Mon Sep 17 00:00:00 2001 From: Simon Sestak Date: Wed, 7 Oct 2026 17:45:18 +0200 Subject: [PATCH 2/3] chore: restore action refs to 2.6.0 The action-refs BATS suite rewrites every ref to @main in its teardown; those changes slipped into the previous commit. No functional change. Claude-Session: https://claude.ai/code/session_015HbhiukLQv71hkruK31fyT --- .github/actions/ios-kmp-build/action.yml | 4 ++-- .github/workflows/android-cloud-check.yml | 4 ++-- .../android-cloud-generate-baseline-profiles.yml | 4 ++-- .github/workflows/android-cloud-nightly-build.yml | 8 ++++---- ...android-cloud-release-firebaseAppDistribution.yml | 4 ++-- .../workflows/android-cloud-release-googlePlay.yml | 4 ++-- .github/workflows/ios-kmp-selfhosted-build.yml | 4 ++-- .github/workflows/ios-kmp-selfhosted-release.yml | 4 ++-- .github/workflows/ios-kmp-selfhosted-test.yml | 2 +- .github/workflows/ios-selfhosted-build.yml | 2 +- .github/workflows/ios-selfhosted-nightly-build.yml | 8 ++++---- .github/workflows/ios-selfhosted-on-demand-build.yml | 6 +++--- .github/workflows/ios-selfhosted-release.yml | 4 ++-- .github/workflows/ios-selfhosted-test.yml | 2 +- .github/workflows/kmp-cloud-detect-changes.yml | 4 ++-- .github/workflows/kmp-combined-nightly-build.yml | 12 ++++++------ 16 files changed, 38 insertions(+), 38 deletions(-) diff --git a/.github/actions/ios-kmp-build/action.yml b/.github/actions/ios-kmp-build/action.yml index fd169498..8a2d5e48 100644 --- a/.github/actions/ios-kmp-build/action.yml +++ b/.github/actions/ios-kmp-build/action.yml @@ -48,7 +48,7 @@ runs: steps: - name: Export secrets to .xcconfig file if: ${{ inputs.secret_xcconfig_path != '' }} - uses: futuredapp/.github/.github/actions/ios-export-secrets@main + uses: futuredapp/.github/.github/actions/ios-export-secrets@2.6.0 with: XCCONFIG_PATH: ${{ inputs.secret_xcconfig_path }} SECRET_PROPERTIES: ${{ inputs.secret_properties }} @@ -64,7 +64,7 @@ runs: cd ${{ inputs.kmp_swift_package_path }} make build - name: Fastlane Beta - uses: futuredapp/.github/.github/actions/ios-fastlane-beta@main + uses: futuredapp/.github/.github/actions/ios-fastlane-beta@2.6.0 with: match_password: ${{ inputs.match_password }} testflight_changelog: ${{ inputs.testflight_changelog }} diff --git a/.github/workflows/android-cloud-check.yml b/.github/workflows/android-cloud-check.yml index 0b25d189..e72c9efc 100644 --- a/.github/workflows/android-cloud-check.yml +++ b/.github/workflows/android-cloud-check.yml @@ -81,7 +81,7 @@ jobs: with: lfs: ${{ inputs.USE_GIT_LFS }} - name: Set up environment - uses: futuredapp/.github/.github/actions/android-setup-environment@main + uses: futuredapp/.github/.github/actions/android-setup-environment@2.6.0 with: java_version: ${{ inputs.JAVA_VERSION }} java_distribution: ${{ inputs.JAVA_DISTRIBUTION }} @@ -90,7 +90,7 @@ jobs: android_sdk_packages: ${{ inputs.ANDROID_SDK_PACKAGES }} kotlin_native_cache: ${{ inputs.KOTLIN_NATIVE_CACHE }} - name: Run checks - uses: futuredapp/.github/.github/actions/android-check@main + uses: futuredapp/.github/.github/actions/android-check@2.6.0 with: lint_gradle_task: ${{ inputs.LINT_GRADLE_TASKS }} test_gradle_task: ${{ inputs.TEST_GRADLE_TASKS }} diff --git a/.github/workflows/android-cloud-generate-baseline-profiles.yml b/.github/workflows/android-cloud-generate-baseline-profiles.yml index 39ae2cac..001ff94d 100644 --- a/.github/workflows/android-cloud-generate-baseline-profiles.yml +++ b/.github/workflows/android-cloud-generate-baseline-profiles.yml @@ -75,13 +75,13 @@ jobs: with: lfs: ${{ inputs.USE_GIT_LFS }} - name: Set up environment - uses: futuredapp/.github/.github/actions/android-setup-environment@main + uses: futuredapp/.github/.github/actions/android-setup-environment@2.6.0 with: java_version: ${{ inputs.JAVA_VERSION }} java_distribution: ${{ inputs.JAVA_DISTRIBUTION }} gradle_cache_encryption_key: ${{ secrets.GRADLE_CACHE_ENCRYPTION_KEY }} - name: Generate baseline profiles - uses: futuredapp/.github/.github/actions/android-generate-baseline-profiles@main + uses: futuredapp/.github/.github/actions/android-generate-baseline-profiles@2.6.0 with: generate_gradle_task: ${{ inputs.TASK_NAME }} signing_keystore_password: ${{ secrets.SIGNING_KEYSTORE_PASSWORD }} diff --git a/.github/workflows/android-cloud-nightly-build.yml b/.github/workflows/android-cloud-nightly-build.yml index 7b0acb1e..16b59b02 100644 --- a/.github/workflows/android-cloud-nightly-build.yml +++ b/.github/workflows/android-cloud-nightly-build.yml @@ -138,7 +138,7 @@ jobs: steps: - name: Detect changes and generate changelog id: detect_changes - uses: futuredapp/.github/.github/actions/universal-detect-changes-and-generate-changelog@main + uses: futuredapp/.github/.github/actions/universal-detect-changes-and-generate-changelog@2.6.0 with: checkout_depth: ${{ inputs.CHANGELOG_CHECKOUT_DEPTH }} debug: ${{ inputs.CHANGELOG_DEBUG }} @@ -158,7 +158,7 @@ jobs: with: lfs: ${{ inputs.USE_GIT_LFS }} - name: Set up environment - uses: futuredapp/.github/.github/actions/android-setup-environment@main + uses: futuredapp/.github/.github/actions/android-setup-environment@2.6.0 with: java_version: ${{ inputs.JAVA_VERSION }} java_distribution: ${{ inputs.JAVA_DISTRIBUTION }} @@ -167,7 +167,7 @@ jobs: android_sdk_packages: ${{ inputs.ANDROID_SDK_PACKAGES }} kotlin_native_cache: ${{ inputs.KOTLIN_NATIVE_CACHE }} - name: Build and upload to App Distribution - uses: futuredapp/.github/.github/actions/android-build-firebase@main + uses: futuredapp/.github/.github/actions/android-build-firebase@2.6.0 with: test_gradle_task: ${{ inputs.TEST_GRADLE_TASKS }} package_gradle_task: ${{ inputs.PACKAGE_GRADLE_TASK }} @@ -214,7 +214,7 @@ jobs: fi - name: Transition JIRA tickets if: steps.check.outputs.enabled == 'true' - uses: futuredapp/.github/.github/actions/jira-transition-tickets@main + uses: futuredapp/.github/.github/actions/jira-transition-tickets@2.6.0 with: jira_context: ${{ secrets.JIRA_CONTEXT }} transition: ${{ inputs.JIRA_TRANSITION }} diff --git a/.github/workflows/android-cloud-release-firebaseAppDistribution.yml b/.github/workflows/android-cloud-release-firebaseAppDistribution.yml index 5844bb8a..e7704919 100644 --- a/.github/workflows/android-cloud-release-firebaseAppDistribution.yml +++ b/.github/workflows/android-cloud-release-firebaseAppDistribution.yml @@ -117,7 +117,7 @@ jobs: with: lfs: ${{ inputs.USE_GIT_LFS }} - name: Set up environment - uses: futuredapp/.github/.github/actions/android-setup-environment@main + uses: futuredapp/.github/.github/actions/android-setup-environment@2.6.0 with: java_version: ${{ inputs.JAVA_VERSION }} java_distribution: ${{ inputs.JAVA_DISTRIBUTION }} @@ -126,7 +126,7 @@ jobs: android_sdk_packages: ${{ inputs.ANDROID_SDK_PACKAGES }} kotlin_native_cache: ${{ inputs.KOTLIN_NATIVE_CACHE }} - name: Build and upload to App Distribution - uses: futuredapp/.github/.github/actions/android-build-firebase@main + uses: futuredapp/.github/.github/actions/android-build-firebase@2.6.0 with: test_gradle_task: ${{ inputs.TEST_GRADLE_TASKS }} package_gradle_task: ${{ inputs.PACKAGE_GRADLE_TASK }} diff --git a/.github/workflows/android-cloud-release-googlePlay.yml b/.github/workflows/android-cloud-release-googlePlay.yml index e3c23f3c..93782925 100644 --- a/.github/workflows/android-cloud-release-googlePlay.yml +++ b/.github/workflows/android-cloud-release-googlePlay.yml @@ -129,7 +129,7 @@ jobs: with: lfs: ${{ inputs.USE_GIT_LFS }} - name: Set up environment - uses: futuredapp/.github/.github/actions/android-setup-environment@main + uses: futuredapp/.github/.github/actions/android-setup-environment@2.6.0 with: java_version: ${{ inputs.JAVA_VERSION }} java_distribution: ${{ inputs.JAVA_DISTRIBUTION }} @@ -137,7 +137,7 @@ jobs: android_sdk_packages: ${{ inputs.ANDROID_SDK_PACKAGES }} kotlin_native_cache: ${{ inputs.KOTLIN_NATIVE_CACHE }} - name: Build and release - uses: futuredapp/.github/.github/actions/android-build-googlePlay@main + uses: futuredapp/.github/.github/actions/android-build-googlePlay@2.6.0 with: bundle_gradle_task: ${{ inputs.BUNDLE_GRADLE_TASK }} version_name: ${{ inputs.VERSION_NAME }} diff --git a/.github/workflows/ios-kmp-selfhosted-build.yml b/.github/workflows/ios-kmp-selfhosted-build.yml index a99fac79..94f59419 100644 --- a/.github/workflows/ios-kmp-selfhosted-build.yml +++ b/.github/workflows/ios-kmp-selfhosted-build.yml @@ -98,14 +98,14 @@ jobs: with: lfs: ${{ inputs.use_git_lfs }} - name: Set up environment - uses: futuredapp/.github/.github/actions/android-setup-environment@main + uses: futuredapp/.github/.github/actions/android-setup-environment@2.6.0 with: java_version: ${{ inputs.java_version }} java_distribution: ${{ inputs.java_distribution }} gradle_cache_encryption_key: ${{ secrets.GRADLE_CACHE_ENCRYPTION_KEY }} ruby: 'false' - name: Build and upload to TestFlight - uses: futuredapp/.github/.github/actions/ios-kmp-build@main + uses: futuredapp/.github/.github/actions/ios-kmp-build@2.6.0 with: match_password: ${{ secrets.MATCH_PASSWORD }} app_store_connect_api_key_key: ${{ secrets.APP_STORE_CONNECT_API_KEY_KEY }} diff --git a/.github/workflows/ios-kmp-selfhosted-release.yml b/.github/workflows/ios-kmp-selfhosted-release.yml index c20a4a88..aa47a91a 100644 --- a/.github/workflows/ios-kmp-selfhosted-release.yml +++ b/.github/workflows/ios-kmp-selfhosted-release.yml @@ -92,7 +92,7 @@ jobs: lfs: ${{ inputs.use_git_lfs }} - name: Export secrets to .xcconfig file if: ${{ inputs.xcconfig_path != '' }} - uses: futuredapp/.github/.github/actions/ios-export-secrets@main + uses: futuredapp/.github/.github/actions/ios-export-secrets@2.6.0 with: XCCONFIG_PATH: ${{ inputs.xcconfig_path }} SECRET_PROPERTIES: ${{ secrets.SECRET_PROPERTIES }} @@ -116,7 +116,7 @@ jobs: cd ${{ inputs.kmp_swift_package_path }} make build - name: Fastlane Release - uses: futuredapp/.github/.github/actions/ios-fastlane-release@main + uses: futuredapp/.github/.github/actions/ios-fastlane-release@2.6.0 with: match_password: ${{ secrets.MATCH_PASSWORD }} version_number: ${{ github.event.release.tag_name || github.ref_name }} diff --git a/.github/workflows/ios-kmp-selfhosted-test.yml b/.github/workflows/ios-kmp-selfhosted-test.yml index 500048b5..1fef7813 100644 --- a/.github/workflows/ios-kmp-selfhosted-test.yml +++ b/.github/workflows/ios-kmp-selfhosted-test.yml @@ -99,7 +99,7 @@ jobs: cd ${{ inputs.kmp_swift_package_path }} make build - name: Fastlane Test - uses: futuredapp/.github/.github/actions/ios-fastlane-test@main + uses: futuredapp/.github/.github/actions/ios-fastlane-test@2.6.0 with: github_token: ${{ secrets.GITHUB_TOKEN_DANGER }} custom_values: ${{ inputs.custom_values }} diff --git a/.github/workflows/ios-selfhosted-build.yml b/.github/workflows/ios-selfhosted-build.yml index c52f4743..6411581d 100644 --- a/.github/workflows/ios-selfhosted-build.yml +++ b/.github/workflows/ios-selfhosted-build.yml @@ -63,7 +63,7 @@ jobs: echo "::warning::This workflow ('ios-selfhosted-build.yml') is deprecated and will be removed in the future." echo "::warning::Please use 'ios-selfhosted-nightly-build.yml' instead." build: - uses: futuredapp/.github/.github/workflows/ios-selfhosted-nightly-build.yml@main + uses: futuredapp/.github/.github/workflows/ios-selfhosted-nightly-build.yml@2.6.0 with: use_git_lfs: ${{ inputs.use_git_lfs }} custom_values: ${{ inputs.custom_values }} diff --git a/.github/workflows/ios-selfhosted-nightly-build.yml b/.github/workflows/ios-selfhosted-nightly-build.yml index 4ddd1d7d..56cc05c1 100644 --- a/.github/workflows/ios-selfhosted-nightly-build.yml +++ b/.github/workflows/ios-selfhosted-nightly-build.yml @@ -80,7 +80,7 @@ jobs: steps: - name: Detect changes and generate changelog id: detect_changes - uses: futuredapp/.github/.github/actions/universal-detect-changes-and-generate-changelog@main + uses: futuredapp/.github/.github/actions/universal-detect-changes-and-generate-changelog@2.6.0 with: checkout_depth: ${{ inputs.checkout_depth }} fallback_lookback: ${{ inputs.changelog_fallback_lookback }} @@ -94,7 +94,7 @@ jobs: - name: Export secrets if: ${{ steps.detect_changes.outputs.skip_build == 'false' && inputs.xcconfig_path != '' }} - uses: futuredapp/.github/.github/actions/ios-export-secrets@main + uses: futuredapp/.github/.github/actions/ios-export-secrets@2.6.0 with: XCCONFIG_PATH: ${{ inputs.xcconfig_path }} SECRET_PROPERTIES: ${{ secrets.SECRET_PROPERTIES }} @@ -102,7 +102,7 @@ jobs: - name: Fastlane Beta if: ${{ steps.detect_changes.outputs.skip_build == 'false' }} - uses: futuredapp/.github/.github/actions/ios-fastlane-beta@main + uses: futuredapp/.github/.github/actions/ios-fastlane-beta@2.6.0 with: match_password: ${{ secrets.MATCH_PASSWORD }} testflight_changelog: ${{ steps.set_changelog.outputs.changelog }} @@ -158,7 +158,7 @@ jobs: fi - name: Transition JIRA tickets if: steps.check.outputs.enabled == 'true' - uses: futuredapp/.github/.github/actions/jira-transition-tickets@main + uses: futuredapp/.github/.github/actions/jira-transition-tickets@2.6.0 with: jira_context: ${{ secrets.JIRA_CONTEXT }} transition: ${{ inputs.jira_transition }} diff --git a/.github/workflows/ios-selfhosted-on-demand-build.yml b/.github/workflows/ios-selfhosted-on-demand-build.yml index bedc2211..ec77554c 100644 --- a/.github/workflows/ios-selfhosted-on-demand-build.yml +++ b/.github/workflows/ios-selfhosted-on-demand-build.yml @@ -81,7 +81,7 @@ jobs: - name: Generate changelog if not provided if: ${{ inputs.changelog == '' }} id: detect_changes - uses: futuredapp/.github/.github/actions/universal-detect-changes-and-generate-changelog@main + uses: futuredapp/.github/.github/actions/universal-detect-changes-and-generate-changelog@2.6.0 with: checkout_depth: ${{ inputs.checkout_depth }} fallback_lookback: ${{ inputs.changelog_fallback_lookback }} @@ -104,14 +104,14 @@ jobs: - name: Export secrets if: ${{ inputs.xcconfig_path != '' }} - uses: futuredapp/.github/.github/actions/ios-export-secrets@main + uses: futuredapp/.github/.github/actions/ios-export-secrets@2.6.0 with: XCCONFIG_PATH: ${{ inputs.xcconfig_path }} SECRET_PROPERTIES: ${{ secrets.SECRET_PROPERTIES }} REQUIRED_KEYS: ${{ inputs.required_keys }} - name: Fastlane Beta - uses: futuredapp/.github/.github/actions/ios-fastlane-beta@main + uses: futuredapp/.github/.github/actions/ios-fastlane-beta@2.6.0 with: MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }} testflight_changelog: ${{ steps.set_changelog.outputs.changelog }} diff --git a/.github/workflows/ios-selfhosted-release.yml b/.github/workflows/ios-selfhosted-release.yml index 63148f60..18782b21 100644 --- a/.github/workflows/ios-selfhosted-release.yml +++ b/.github/workflows/ios-selfhosted-release.yml @@ -62,14 +62,14 @@ jobs: - name: Export secrets if: ${{ inputs.xcconfig_path != '' }} - uses: futuredapp/.github/.github/actions/ios-export-secrets@main + uses: futuredapp/.github/.github/actions/ios-export-secrets@2.6.0 with: XCCONFIG_PATH: ${{ inputs.xcconfig_path }} SECRET_PROPERTIES: ${{ secrets.SECRET_PROPERTIES }} REQUIRED_KEYS: ${{ inputs.required_keys }} - name: Fastlane Release - uses: futuredapp/.github/.github/actions/ios-fastlane-release@main + uses: futuredapp/.github/.github/actions/ios-fastlane-release@2.6.0 with: match_password: ${{ secrets.MATCH_PASSWORD }} version_number: ${{ github.ref_name }} diff --git a/.github/workflows/ios-selfhosted-test.yml b/.github/workflows/ios-selfhosted-test.yml index 4f06ba20..9db512e9 100644 --- a/.github/workflows/ios-selfhosted-test.yml +++ b/.github/workflows/ios-selfhosted-test.yml @@ -41,7 +41,7 @@ jobs: lfs: ${{ inputs.use_git_lfs }} - name: Fastlane Test - uses: futuredapp/.github/.github/actions/ios-fastlane-test@main + uses: futuredapp/.github/.github/actions/ios-fastlane-test@2.6.0 with: github_token: ${{ secrets.GITHUB_TOKEN_DANGER }} custom_values: ${{ inputs.custom_values }} \ No newline at end of file diff --git a/.github/workflows/kmp-cloud-detect-changes.yml b/.github/workflows/kmp-cloud-detect-changes.yml index 50caa63d..e6a8240a 100644 --- a/.github/workflows/kmp-cloud-detect-changes.yml +++ b/.github/workflows/kmp-cloud-detect-changes.yml @@ -28,7 +28,7 @@ name: Detect Changes # # Note: For direct action usage in other workflows, you can also use: # - name: Detect Changes -# uses: futuredapp/.github/.github/actions/kmp-detect-changes@main +# uses: futuredapp/.github/.github/actions/kmp-detect-changes@2.6.0 on: workflow_call: @@ -58,6 +58,6 @@ jobs: steps: - name: Detect Changes id: detect - uses: futuredapp/.github/.github/actions/kmp-detect-changes@main + uses: futuredapp/.github/.github/actions/kmp-detect-changes@2.6.0 with: USE_GIT_LFS: ${{ inputs.USE_GIT_LFS }} diff --git a/.github/workflows/kmp-combined-nightly-build.yml b/.github/workflows/kmp-combined-nightly-build.yml index 43355d1d..f6d56d3e 100644 --- a/.github/workflows/kmp-combined-nightly-build.yml +++ b/.github/workflows/kmp-combined-nightly-build.yml @@ -173,7 +173,7 @@ jobs: steps: - name: Detect changes and generate changelog id: detect_changes - uses: futuredapp/.github/.github/actions/universal-detect-changes-and-generate-changelog@main + uses: futuredapp/.github/.github/actions/universal-detect-changes-and-generate-changelog@2.6.0 with: checkout_depth: ${{ inputs.CHANGELOG_CHECKOUT_DEPTH }} debug: ${{ inputs.CHANGELOG_DEBUG }} @@ -192,14 +192,14 @@ jobs: with: lfs: ${{ inputs.USE_GIT_LFS }} - name: Set up environment - uses: futuredapp/.github/.github/actions/android-setup-environment@main + uses: futuredapp/.github/.github/actions/android-setup-environment@2.6.0 with: java_version: ${{ inputs.JAVA_VERSION }} java_distribution: ${{ inputs.JAVA_DISTRIBUTION }} gradle_cache_encryption_key: ${{ secrets.GRADLE_CACHE_ENCRYPTION_KEY }} ruby: 'false' - name: Build and upload to TestFlight - uses: futuredapp/.github/.github/actions/ios-kmp-build@main + uses: futuredapp/.github/.github/actions/ios-kmp-build@2.6.0 with: match_password: ${{ secrets.IOS_MATCH_PASSWORD }} app_store_connect_api_key_key: ${{ secrets.IOS_APP_STORE_CONNECT_API_KEY_KEY }} @@ -229,7 +229,7 @@ jobs: with: lfs: ${{ inputs.USE_GIT_LFS }} - name: Set up environment - uses: futuredapp/.github/.github/actions/android-setup-environment@main + uses: futuredapp/.github/.github/actions/android-setup-environment@2.6.0 with: java_version: ${{ inputs.JAVA_VERSION }} java_distribution: ${{ inputs.JAVA_DISTRIBUTION }} @@ -238,7 +238,7 @@ jobs: android_sdk_packages: ${{ inputs.ANDROID_SDK_PACKAGES }} kotlin_native_cache: ${{ inputs.ANDROID_KOTLIN_NATIVE_CACHE }} - name: Build and upload to App Distribution - uses: futuredapp/.github/.github/actions/android-build-firebase@main + uses: futuredapp/.github/.github/actions/android-build-firebase@2.6.0 with: test_gradle_task: ${{ inputs.ANDROID_TEST_GRADLE_TASK }} package_gradle_task: ${{ inputs.ANDROID_PACKAGE_GRADLE_TASK }} @@ -294,7 +294,7 @@ jobs: fi - name: Transition JIRA tickets if: steps.check.outputs.enabled == 'true' - uses: futuredapp/.github/.github/actions/jira-transition-tickets@main + uses: futuredapp/.github/.github/actions/jira-transition-tickets@2.6.0 with: jira_context: ${{ secrets.JIRA_CONTEXT }} transition: ${{ inputs.JIRA_TRANSITION }} From 607ca8d2dd0d40a61684e1dc00e70f07ff655322 Mon Sep 17 00:00:00 2001 From: Simon Sestak Date: Wed, 7 Oct 2026 17:49:26 +0200 Subject: [PATCH 3/3] ci(notify): make retry tuning configurable via repository variables NOTIFY_RETRY_MAX_ATTEMPTS, NOTIFY_RETRY_BASE_DELAY and NOTIFY_RETRY_MAX_DELAY are read from repository variables so they can be changed without a merge. Script defaults apply when the variables are not set. Claude-Session: https://claude.ai/code/session_015HbhiukLQv71hkruK31fyT --- .github/workflows/notify-consumer-repos.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/notify-consumer-repos.yml b/.github/workflows/notify-consumer-repos.yml index fd3789f2..71ec10e4 100644 --- a/.github/workflows/notify-consumer-repos.yml +++ b/.github/workflows/notify-consumer-repos.yml @@ -27,6 +27,10 @@ jobs: GH_TOKEN: ${{ secrets.BOT_TOKEN }} NOTIFY_REPOS_WHITELIST: ${{ vars.NOTIFY_REPOS_WHITELIST }} NOTIFY_REPOS: ${{ inputs.repos }} + # Retry tuning — optional repository variables, script defaults apply when unset + NOTIFY_RETRY_MAX_ATTEMPTS: ${{ vars.NOTIFY_RETRY_MAX_ATTEMPTS }} + NOTIFY_RETRY_BASE_DELAY: ${{ vars.NOTIFY_RETRY_BASE_DELAY }} + NOTIFY_RETRY_MAX_DELAY: ${{ vars.NOTIFY_RETRY_MAX_DELAY }} run: | if [ "${{ github.event_name }}" = "push" ]; then VERSION="${{ github.ref_name }}"