-
Notifications
You must be signed in to change notification settings - Fork 24
ci: DashUIKit ref override, internal-only channel, explicit release version #1072
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -17,21 +17,26 @@ on: | |
| required: true | ||
| default: v4.2-dev | ||
| type: string | ||
| dashuikit_ref: | ||
| description: "dashpay/DashUIKit branch, tag, or commit SHA — blank uses the pin committed in Package.resolved" | ||
| required: false | ||
| default: "" | ||
| type: string | ||
| release_channel: | ||
| description: "TestFlight distribution channel" | ||
| description: "TestFlight distribution channel — internal-only marks the upload TestFlight Internal Only" | ||
| required: true | ||
| default: internal | ||
| default: internal-only | ||
| type: choice | ||
| options: | ||
| - internal-only | ||
| - internal | ||
| - external | ||
| app_version: | ||
| description: "App version, or 'auto' to follow the latest TestFlight/App Store version" | ||
| description: "App version to ship, e.g. 9.1.0 — the build number is assigned automatically" | ||
| required: true | ||
| default: auto | ||
| type: string | ||
| testflight_group: | ||
| description: "Exact external TestFlight group name (ignored for internal releases)" | ||
| description: "Exact external TestFlight group name (ignored unless release_channel is external)" | ||
| required: true | ||
| default: Public Beta v9.0 | ||
| type: string | ||
|
|
@@ -78,6 +83,90 @@ jobs: | |
| fetch-depth: 1 | ||
| persist-credentials: false | ||
|
|
||
| # DashUIKit is a remote Swift package, so a branch of it cannot be tested | ||
| # by checking it out beside the wallet the way platform is. The archive | ||
| # runs with `-onlyUsePackageVersionsFromResolvedFile`, which makes | ||
| # `Package.resolved` the authority — so pointing that (and the project's | ||
| # branch requirement, which must agree) at another ref is what selects it. | ||
| # | ||
| # Left blank, nothing is touched and the committed pin is used. | ||
| - name: Override the DashUIKit pin | ||
| if: inputs.dashuikit_ref != '' | ||
| working-directory: ${{ github.workspace }}/dashwallet-ios | ||
| env: | ||
| DASHUIKIT_REF: ${{ inputs.dashuikit_ref }} | ||
| run: | | ||
| set -euo pipefail | ||
|
|
||
| # A branch or tag resolves to its tip; anything else is taken as a | ||
| # commit SHA already, and `Package.resolved` wants both a name and a | ||
| # revision either way. | ||
| sha="$(git ls-remote https://github.com/dashpay/DashUIKit "$DASHUIKIT_REF" | cut -f1 | head -1)" | ||
| if [ -z "$sha" ]; then | ||
| sha="$DASHUIKIT_REF" | ||
| fi | ||
| echo "DashUIKit → $DASHUIKIT_REF ($sha)" | ||
|
|
||
| python3 - "$DASHUIKIT_REF" "$sha" <<'PY' | ||
| import json, re, sys | ||
|
|
||
| ref, sha = sys.argv[1], sys.argv[2] | ||
|
|
||
| resolved = "DashWallet.xcworkspace/xcshareddata/swiftpm/Package.resolved" | ||
| with open(resolved) as handle: | ||
| document = json.load(handle) | ||
| pins = [p for p in document["pins"] if p["identity"].lower() == "dashuikit"] | ||
| if not pins: | ||
| sys.exit("no DashUIKit pin in Package.resolved") | ||
| pins[0]["state"] = {"branch": ref, "revision": sha} | ||
|
Comment on lines
+104
to
+121
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win 🔎 Supported by static analysis🏁 Script executed: #!/usr/bin/env bash
set -euo pipefail
ref="${DASHUIKIT_REF:?Set DASHUIKIT_REF to an annotated tag or full commit SHA}"
git ls-remote https://github.com/dashpay/DashUIKit \
"refs/heads/$ref" \
"refs/tags/$ref" \
"refs/tags/$ref^{}"
rg -n -C 3 'DashUIKit|branch =|revision =' \
DashWallet.xcworkspace/xcshareddata/swiftpm/Package.resolved \
DashWallet.xcodeproj/project.pbxprojRepository: dashpay/dashwallet-ios Length of output: 246 🏁 Script executed: #!/usr/bin/env bash
set -euo pipefail
printf '%s\n' '--- repository conventions ---'
find /tmp/coderabbit-repo-knowledge/dashpay-dashwallet-ios-725f7b4f -maxdepth 2 -type f -name '*.md' -print
printf '%s\n' '--- workflow ---'
cat -n .github/workflows/release-dashpay-testflight.yml | sed -n '70,155p'
printf '%s\n' '--- directly bound package declarations ---'
rg -n -C 8 'DashUIKit|branch|revision|package' \
DashWallet.xcworkspace/xcshareddata/swiftpm/Package.resolved \
DashWallet.xcodeproj/project.pbxprojRepository: dashpay/dashwallet-ios Length of output: 21103 🏁 Script executed: #!/usr/bin/env bash
set -euo pipefail
printf '%s\n' '--- applicable repository conventions ---'
cat /tmp/coderabbit-repo-knowledge/dashpay-dashwallet-ios-725f7b4f/conventions/repo-wide.md
cat /tmp/coderabbit-repo-knowledge/dashpay-dashwallet-ios-725f7b4f/conventions/dashwallet.md
printf '%s\n' '--- learnings for this scope ---'
cat /tmp/coderabbit-repo-knowledge/dashpay-dashwallet-ios-725f7b4f/learnings/dashwallet.md
cat /tmp/coderabbit-repo-knowledge/dashpay-dashwallet-ios-725f7b4f/learnings/repo-wide.mdRepository: dashpay/dashwallet-ios Length of output: 5900 🌐 Web query:
💡 Result: The Swift Package Manager (SPM) Package.resolved version 3 file format was introduced in Swift 5.10 (associated with Xcode 15.3 and later) [1][2]. It is a JSON-formatted file that records the exact state of resolved dependencies to ensure consistent builds [3][4][5]. The structure of a version 3 file differs from version 2 primarily by the addition of an originHash field at the root level [1][3]. This hash is used by SwiftPM to determine if dependency resolution is required by checking if the package manifest has changed [1][6]. The file structure is as follows: { "originHash": "...", "pins": [... ], "version": 3 } Within the pins array, each entry contains a state object. The state object defines the requirement for the dependency and includes the following fields [2]: - revision: The specific git commit SHA (mandatory for pinning to a specific state) [4][2]. - version: The semantic version string (e.g., "1.9.2"), if applicable [2]. - branch: Used when the dependency is pinned to a specific branch name [4][5]. While the file is automatically managed by SwiftPM, the state object within the pins array captures the precise requirement—such as a git revision (commit SHA), tag (version), or branch—that was resolved during the last build [7][8][2]. When you specify a dependency in your Package.swift, SwiftPM resolves it to a specific revision; the Package.resolved file then locks that revision to ensure subsequent builds use the identical source code [4][5][7]. Citations:
🏁 Script executed: #!/usr/bin/env bash
set -euo pipefail
printf '%s\n' '--- workflow input and package-resolution command ---'
cat -n .github/workflows/release-dashpay-testflight.yml | sed -n '1,75p;185,255p'
printf '%s\n' '--- DashUIKit tag refs ---'
git ls-remote --tags https://github.com/dashpay/DashUIKit \
| head -30
printf '%s\n' '--- exact workflow references ---'
rg -n -C 2 'DASHUIKIT_REF|onlyUsePackageVersionsFromResolvedFile|xcodebuild|Package.resolved' \
.github/workflows/release-dashpay-testflight.ymlRepository: dashpay/dashwallet-ios Length of output: 10140 Classify the DashUIKit reference before writing package metadata. For 🤖 Prompt for AI Agents |
||
| with open(resolved, "w") as handle: | ||
| json.dump(document, handle, indent=2) | ||
| handle.write("\n") | ||
|
|
||
| # The project pins a branch too, and `-onlyUsePackageVersionsFromResolvedFile` | ||
| # still checks the resolved revision against it. Rewrites only the | ||
| # requirement inside DashUIKit's own reference block. | ||
| project = "DashWallet.xcodeproj/project.pbxproj" | ||
| with open(project) as handle: | ||
| text = handle.read() | ||
| pattern = re.compile( | ||
| r'(XCRemoteSwiftPackageReference "DashUIKit" \*/ = \{.*?branch = )[^;]+(;)', | ||
| re.DOTALL) | ||
| # Quoted: a branch name carries slashes, and pbxproj only leaves bare | ||
| # tokens unquoted. Quoting a plain name too is harmless. | ||
| text, count = pattern.subn(rf'\g<1>"{ref}"\g<2>', text, count=1) | ||
| if count != 1: | ||
| sys.exit("could not rewrite the DashUIKit branch requirement") | ||
| with open(project, "w") as handle: | ||
| handle.write(text) | ||
| PY | ||
|
|
||
| # The release scripts come from the ref this workflow was dispatched | ||
| # from, not from the branch being built. Every step runs inside the | ||
| # `dashwallet-ios` checkout, so `.github/scripts` there is whatever | ||
| # `wallet_ref` happens to carry — and a branch cut before those scripts | ||
| # existed cannot be released at all, which is the opposite of what a | ||
| # release pipeline is for. Sparse: only the scripts are needed. | ||
| - name: Checkout release tooling | ||
| uses: actions/checkout@v6 | ||
| with: | ||
| ref: ${{ github.ref }} | ||
| path: release-tooling | ||
| sparse-checkout: .github/scripts | ||
| sparse-checkout-cone-mode: false | ||
| fetch-depth: 1 | ||
| persist-credentials: false | ||
|
|
||
| - name: Locate release tooling | ||
| run: | | ||
| set -euo pipefail | ||
| script="$GITHUB_WORKSPACE/release-tooling/.github/scripts/app_store_connect_release.rb" | ||
| if [[ ! -f "$script" ]]; then | ||
| echo "::error::release tooling missing at $script" | ||
| exit 1 | ||
| fi | ||
| echo "RELEASE_SCRIPT=$script" >> "$GITHUB_ENV" | ||
|
|
||
| - name: Checkout platform | ||
| uses: actions/checkout@v6 | ||
| with: | ||
|
|
@@ -211,7 +300,7 @@ jobs: | |
| TESTFLIGHT_GROUP: ${{ inputs.testflight_group }} | ||
| run: | | ||
| set -euo pipefail | ||
| ruby .github/scripts/app_store_connect_release.rb resolve-version | ||
| ruby "$RELEASE_SCRIPT" resolve-version | ||
|
|
||
| - name: Select Xcode 26.6 | ||
| uses: maxim-lobanov/setup-xcode@v1 | ||
|
|
@@ -356,7 +445,7 @@ jobs: | |
| EFFECTIVE_VERSION: ${{ steps.release-version.outputs.effective_version }} | ||
| run: | | ||
| set -euo pipefail | ||
| ruby .github/scripts/app_store_connect_release.rb resolve-build | ||
| ruby "$RELEASE_SCRIPT" resolve-build | ||
|
|
||
| - name: Install Apple signing certificates | ||
| env: | ||
|
|
@@ -530,12 +619,13 @@ jobs: | |
| BUILD_NUMBER: ${{ steps.app-version.outputs.build }} | ||
| run: | | ||
| set -euo pipefail | ||
| ruby .github/scripts/app_store_connect_release.rb assert-build-free | ||
| ruby "$RELEASE_SCRIPT" assert-build-free | ||
|
|
||
| - name: Upload archive to TestFlight | ||
| env: | ||
| API_KEY_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_ID }} | ||
| API_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_API_ISSUER_ID }} | ||
| RELEASE_CHANNEL: ${{ inputs.release_channel }} | ||
| run: | | ||
| set -euo pipefail | ||
|
|
||
|
|
@@ -547,6 +637,14 @@ jobs: | |
| /usr/libexec/PlistBuddy -c 'Add :manageAppVersionAndBuildNumber bool false' "$export_options" | ||
| /usr/libexec/PlistBuddy -c 'Add :uploadSymbols bool true' "$export_options" | ||
|
|
||
| # Xcode Organizer's "TestFlight Internal Only": App Store Connect | ||
| # marks the uploaded build so it can only reach internal testers — | ||
| # it can never be distributed externally or released to the App | ||
| # Store. The export method stays app-store-connect either way. | ||
| if [[ "$RELEASE_CHANNEL" == 'internal-only' ]]; then | ||
| /usr/libexec/PlistBuddy -c 'Add :testFlightInternalTestingOnly bool true' "$export_options" | ||
| fi | ||
|
|
||
| xcodebuild -exportArchive \ | ||
| -archivePath "$ARCHIVE_PATH" \ | ||
| -exportPath "$EXPORT_PATH" \ | ||
|
|
@@ -566,10 +664,10 @@ jobs: | |
| BUILD_NUMBER: ${{ steps.app-version.outputs.build }} | ||
| run: | | ||
| set -euo pipefail | ||
| ruby .github/scripts/app_store_connect_release.rb wait-build | ||
| ruby "$RELEASE_SCRIPT" wait-build | ||
|
|
||
| - name: Finalize internal TestFlight build | ||
| if: inputs.release_channel == 'internal' | ||
| if: inputs.release_channel != 'external' | ||
| env: | ||
| WHAT_TO_TEST: ${{ inputs.what_to_test }} | ||
| VERSION: ${{ steps.app-version.outputs.version }} | ||
|
|
@@ -644,13 +742,16 @@ jobs: | |
| echo "- dashwallet-ios: \`${WALLET_SHA}\` (requested: \`${REQUESTED_WALLET_REF}\`)" | ||
| echo "- platform: \`${PLATFORM_SHA}\` (requested: \`${REQUESTED_PLATFORM_REF}\`)" | ||
| echo "- Channel: \`${RELEASE_CHANNEL}\`" | ||
| if [[ "$RELEASE_CHANNEL" == 'internal' ]]; then | ||
| if [[ "$RELEASE_CHANNEL" == 'external' ]]; then | ||
| echo "- External group: \`${TESTFLIGHT_GROUP}\`" | ||
| echo '- External tester notifications: enabled' | ||
| else | ||
| echo '- Internal group: `App Store Connect Users`' | ||
| echo '- External distribution: disabled' | ||
| echo '- Beta App Review: not submitted' | ||
| else | ||
| echo "- External group: \`${TESTFLIGHT_GROUP}\`" | ||
| echo '- External tester notifications: enabled' | ||
| if [[ "$RELEASE_CHANNEL" == 'internal-only' ]]; then | ||
| echo '- Marked TestFlight Internal Only: this build can never go external or to the App Store' | ||
| fi | ||
| fi | ||
| echo | ||
| echo 'The uploaded build is processed and ready for TestFlight.' | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Prefer an exact TestFlight train match.
If both
9.1and9.1.0exist, this selects whichever numeric match the API returns first. A request for an existing9.1.0train can then archive into9.1instead. Selectvalue == requestedfirst, then fall back to a numerically equivalent train. Add a regression test with both spellings.Proposed fix
🤖 Prompt for AI Agents