gowalk-cicd 1.0.77 → 1.0.79
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +35 -2
- package/README.md +55 -41
- package/action/.daemux-version +1 -1
- package/action/action.yml +48 -38
- package/action/scripts/auto_detect.py +8 -8
- package/action/scripts/commit_bot_changes.sh +3 -29
- package/action/scripts/native_dependency_guard.py +59 -0
- package/action/scripts/native_pods.py +97 -0
- package/action/scripts/native_pods_diagnostics.py +53 -0
- package/action/scripts/native_pods_process.py +93 -0
- package/action/scripts/native_project_graph.py +148 -0
- package/action/scripts/source_maintenance.py +93 -0
- package/action/scripts/source_maintenance_fixture.py +58 -0
- package/action/scripts/test_auto_detect.py +11 -0
- package/action/scripts/test_native_dependency_guard.py +116 -0
- package/action/scripts/test_native_dependency_wiring.py +59 -0
- package/action/scripts/test_native_pods.py +106 -0
- package/action/scripts/test_native_pods_diagnostics.py +39 -0
- package/action/scripts/test_native_pods_process.py +41 -0
- package/action/scripts/test_source_maintenance.py +122 -0
- package/android-action/.daemux-version +1 -1
- package/android-action/action.yml +3 -1
- package/android-action/play-upload/action.yml +33 -0
- package/android-action/play-upload/owner.cjs +61 -0
- package/android-action/play-upload/owner_http.cjs +63 -0
- package/android-action/play-upload/owner_principal.cjs +15 -0
- package/android-action/play-upload/owner_record.cjs +63 -0
- package/android-action/play-upload/owner_run.cjs +62 -0
- package/android-action/play-upload/process.cjs +78 -0
- package/android-action/play-upload/run.cjs +66 -0
- package/android-action/play-upload/source.cjs +45 -0
- package/android-action/scripts/resolve_android.py +19 -1
- package/android-action/scripts/test_early_play_workflow.py +8 -3
- package/android-action/scripts/test_resolve_play_readiness.py +26 -0
- package/backend-action/.daemux-version +1 -1
- package/package.json +1 -1
- package/templates/deploy.yml +14 -56
package/CLAUDE.md
CHANGED
|
@@ -197,6 +197,15 @@ node /path/to/gowalk-cicd/bin/cli.mjs
|
|
|
197
197
|
the validated source path. Missing/ambiguous source plists fail; positively generated
|
|
198
198
|
plists retain the existing build-setting version path. Queries disable signing and package
|
|
199
199
|
updates, retain inherited proxies, and never enable provisioning network operations.
|
|
200
|
+
- Native Xcode validation and archives first check the selected project/workspace graph with
|
|
201
|
+
`native_dependency_guard.py ROOT --container PATH`. Xcode-managed Swift packages remain refused:
|
|
202
|
+
proxy environment variables do not protect SwiftPM downloads. The same app session preserves
|
|
203
|
+
source and uses the vendor's official CocoaPods distribution or verified local XCFrameworks.
|
|
204
|
+
`native_pods.py` installs a committed, unchanged `Podfile.lock` with `pod install --deployment`
|
|
205
|
+
through the existing Google relay, then checks the completed workspace. No Podfile means no
|
|
206
|
+
dependency download or Google proxy requirement. Initial local lock resolution may use the
|
|
207
|
+
explicit `--before-pods --initial-pods-lock` guard; CI never uses that development exception.
|
|
208
|
+
These checks cover the declared native dependency graph, not arbitrary custom build-script traffic.
|
|
200
209
|
- Version stamping is per-build-system: Flutter takes `--build-number`, Gradle
|
|
201
210
|
has no equivalent, so `set_gradle_version()` rewrites the literal in the
|
|
202
211
|
checkout the way the iOS action patches the pbxproj. Never commit that edit.
|
|
@@ -256,12 +265,12 @@ node /path/to/gowalk-cicd/bin/cli.mjs
|
|
|
256
265
|
- The iOS action reads every script and prompt from the snapshot it takes of
|
|
257
266
|
itself in its first step (`$SWIFT_APP_ACTION`, under `RUNNER_TEMP`), never
|
|
258
267
|
from `${{ github.action_path }}` after that step. The plugin self-update
|
|
259
|
-
(`autoupdate_check.sh`) runs right after the snapshot and
|
|
268
|
+
(`autoupdate_check.sh`) runs right after the snapshot and preserves its own source candidate,
|
|
260
269
|
before the version/signing/archive steps, so a red deploy still receives the
|
|
261
270
|
release that fixes it; the snapshot is what keeps that run on one version.
|
|
262
271
|
`test_action_step_order.py` pins both. A new script reference in
|
|
263
272
|
`action/action.yml` therefore uses `"$SWIFT_APP_ACTION/scripts/..."`.
|
|
264
|
-
-
|
|
273
|
+
- Delivery dependencies may remain after a green job; each is a typed
|
|
265
274
|
check-run annotation (`::notice title=<name>::<one-line JSON with a versioned
|
|
266
275
|
schema>`), never prose in a `::warning::`: `play_review_pending`
|
|
267
276
|
(`templates/deploy.yml`, Play refused auto-submit and the bundle landed
|
|
@@ -271,9 +280,33 @@ node /path/to/gowalk-cicd/bin/cli.mjs
|
|
|
271
280
|
version and metadata was skipped), and `firebase_symbols_pending` (the dSYM
|
|
272
281
|
archive awaits the session's proxied console upload). The README lists the schemas; add a
|
|
273
282
|
field by bumping the version.
|
|
283
|
+
- `android-action/play-upload/` verifies unchanged SHA/checksum-pinned official uploader
|
|
284
|
+
bytes before running Node24. It classifies immediate private child output, never live
|
|
285
|
+
GitHub annotations: expired/deleted retries normally; only the exact cannot-auto-submit
|
|
286
|
+
refusal permits a review hold. Unknown/transport/post-commit uncertainty does not replay.
|
|
287
|
+
The existing signed AAB, proxy, track/status/rollout and notes survive one bounded retry;
|
|
288
|
+
only a successful hold emits `play_review_pending`. Keep raw child output out of receipts.
|
|
274
289
|
- The JDK is chosen by `scripts/select_jdk.py`, called from the workflow before
|
|
275
290
|
`setup-java`. The `JAVA_VERSION` repo variable always wins; otherwise Flutter
|
|
276
291
|
gets 17 and native Gradle gets 21, except that a positively-detected Kotlin
|
|
277
292
|
Gradle plugin below 1.9.20 drops to 17 (older kapt cannot run under JDK 21).
|
|
278
293
|
Detection logic belongs in that script, where it is unit-tested — not inline
|
|
279
294
|
in the workflow template, where nothing can test it.
|
|
295
|
+
|
|
296
|
+
- CI-generated source is preserved by `source_maintenance.py` on a deterministic
|
|
297
|
+
`task/ci-maintenance-*` branch, with remote parent/tree/SHA readback. It never changes
|
|
298
|
+
the build HEAD, rebases a dirty checkout, force-pushes or pushes to the default branch.
|
|
299
|
+
Only managed action/version source is accepted; credential-shaped staging refuses.
|
|
300
|
+
`source_maintenance_pending` identifies the exact source for the same app session's
|
|
301
|
+
checked PR. Unconfirmed preservation is a job failure; a receipt is not a completed merge.
|
|
302
|
+
- Play preflight, version inventory and the pinned uploader share the panel's package edit owner.
|
|
303
|
+
The binding's nonsecret `principal_sha256` must match the selected service account's
|
|
304
|
+
`client_email` fingerprint before provider work. A mismatch requires supported CI-secret
|
|
305
|
+
provisioning; never accept the account ID or a caller-supplied fingerprint alone.
|
|
306
|
+
`APP_ROBOT_PLAY_EDIT_BINDING` is panel-provisioned repository data; workflows pass it as
|
|
307
|
+
`PLAY_EDIT_BINDING` with the repo-local GitHub token and `contents:write`/`actions:read`.
|
|
308
|
+
`play-upload/owner_run.cjs` protects Python entrypoints; `run.cjs` holds the same owner across
|
|
309
|
+
its permitted upload retry. Missing/retired bindings, unknown write outcomes and active owners
|
|
310
|
+
refuse before provider work; only exact terminal run/attempt/source proof permits recovery.
|
|
311
|
+
Other packages stay parallel. The GitHub coordination request is fixed-origin and does not
|
|
312
|
+
change the Google account proxy. Update workflow wiring as well as action directories.
|
package/README.md
CHANGED
|
@@ -228,8 +228,8 @@ already at or below those numbers are left untouched. Linux only.
|
|
|
228
228
|
|
|
229
229
|
### When Play will not accept a release for review
|
|
230
230
|
|
|
231
|
-
|
|
232
|
-
|
|
231
|
+
Some Play changes require a separate console review submission. The ordinary API
|
|
232
|
+
commit then returns this explicit refusal:
|
|
233
233
|
|
|
234
234
|
```
|
|
235
235
|
Changes cannot be sent for review automatically. Please set the query parameter
|
|
@@ -237,10 +237,10 @@ changesNotSentForReview to true. Once committed, the changes in this edit can be
|
|
|
237
237
|
sent for review from the Google Play Console UI.
|
|
238
238
|
```
|
|
239
239
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
240
|
+
The uncommitted edit has not published the release. Only this exact refusal lets
|
|
241
|
+
the upload wrapper retry the same signed artifact with `changesNotSentForReview`.
|
|
242
|
+
A successful retry records the separate review submission that remains, using a
|
|
243
|
+
typed check-run annotation (see [Check-run annotations](#check-run-annotations-the-panel-reads)):
|
|
244
244
|
|
|
245
245
|
```
|
|
246
246
|
::notice title=play_review_pending::{"schema":"gowalk-cicd/play-review-pending.v1",
|
|
@@ -289,8 +289,10 @@ the situation and whose **message** is one line of JSON carrying a versioned
|
|
|
289
289
|
| title | schema | fields |
|
|
290
290
|
| --- | --- | --- |
|
|
291
291
|
| `play_review_pending` | `gowalk-cicd/play-review-pending.v1` | `package`, `track`, `version_code` |
|
|
292
|
+
| `play_upload_result` | `gowalk-cicd/play-upload.v1` | `ok`, `code`, `attempts`, `review_pending`, upstream identity |
|
|
292
293
|
| `store_version_locked` | `gowalk-cicd/store-version-locked.v1` | `version`, `state`, `build_number` |
|
|
293
294
|
| `firebase_symbols_pending` | `gowalk-cicd/firebase-symbols-pending.v1` | `platform`, `app_id`, `source_sha`, `artifact`, `file`, `sha256`, `status` |
|
|
295
|
+
| `source_maintenance_pending` | `gowalk-cicd/source-maintenance-pending.v1` | `repo`, `base_sha`, `head_sha`, `branch`, `run_id` |
|
|
294
296
|
| `apple_build_upload_failed` (error) | `gowalk-cicd/apple-build-upload-failed.v2` | `category`, `stage`, `receipt_available`, receipt identifiers (`cfBundleVersion`, `upload_id`, `file_id`, `state`, `provider_status`, `provider_code`, `provider_pointer`), `action` |
|
|
295
297
|
|
|
296
298
|
Add a field by bumping the schema version; never change the meaning of an
|
|
@@ -299,10 +301,12 @@ existing one.
|
|
|
299
301
|
### The plugin self-update runs first
|
|
300
302
|
|
|
301
303
|
The vendored iOS action checks npm for a newer `gowalk-cicd` at the **start** of
|
|
302
|
-
every default-branch run and
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
304
|
+
every default-branch run and preserves the refreshed action directories on a verified
|
|
305
|
+
task branch before the version, signing and archive steps. The
|
|
306
|
+
`source_maintenance_pending` receipt lets the same app session adopt that exact commit
|
|
307
|
+
into its current task branch and land it through the normal checked PR, including
|
|
308
|
+
when later deploy steps fail. CI never pushes to the default branch or rebases its
|
|
309
|
+
dirty build checkout; a failed preservation is a job failure. The run that
|
|
306
310
|
performed the update keeps executing the version it started with: the action
|
|
307
311
|
snapshots itself to `$RUNNER_TEMP` before anything mutates and reads every script
|
|
308
312
|
and prompt from that copy.
|
|
@@ -329,23 +333,32 @@ emits the `play_preflight_failed` error annotation with schema
|
|
|
329
333
|
values and raw network errors are omitted. A cleanup failure also records the known
|
|
330
334
|
edit ID and `cleanup_required: true` so recovery targets that edit.
|
|
331
335
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
336
|
+
The upload wrapper at `android-action/play-upload/` runs the unchanged official
|
|
337
|
+
`r0adkll/upload-google-play` revision `e738b9dd8f2476ea806d921b64aacd24f34515a5` in
|
|
338
|
+
Node24. Its fixed GitHub download is checked against the recorded size and SHA256
|
|
339
|
+
before execution; an absent or changed program refuses upload. Both proxy spellings
|
|
340
|
+
and bypass variables are explicitly set for only that provider subprocess.
|
|
341
|
+
|
|
342
|
+
The wrapper permits at most two attempts within one hour, reusing the existing
|
|
343
|
+
signed bundle, track, status, rollout fraction and release notes:
|
|
344
|
+
|
|
345
|
+
| First result | Recovery |
|
|
346
|
+
| --- | --- |
|
|
347
|
+
| Exact expired/deleted-edit refusal | Fresh edit with the original normal review behavior |
|
|
348
|
+
| Exact cannot-auto-submit refusal quoted above | Fresh edit with the explicit review-hold flag |
|
|
349
|
+
| Missing, multiple, unknown, transport or post-commit-uncertain failure | Fail without replay |
|
|
350
|
+
|
|
351
|
+
Raw uploader output and exception text are private to the wrapper. It emits a
|
|
352
|
+
`play_upload_result` annotation with schema `gowalk-cicd/play-upload.v1`, a fixed
|
|
353
|
+
code, attempt count, review-pending boolean and verified upstream identity. Only a
|
|
354
|
+
successful review-hold retry emits the existing `play_review_pending` receipt.
|
|
355
|
+
Ordinary retry success never misreports an unsubmitted release. The wrapper does
|
|
356
|
+
not depend on check-run annotations becoming visible while its job is running.
|
|
357
|
+
|
|
358
|
+
An expired/deleted edit does not identify its cause. Another edit opened with the
|
|
359
|
+
same API user/package can invalidate it; avoid competing read edits while uploading.
|
|
360
|
+
An external actor is not proven by an expired-edit error alone. Ambiguous outcomes
|
|
361
|
+
require a fresh authoritative inventory/track readback before another mutation.
|
|
349
362
|
|
|
350
363
|
### Runner disk space
|
|
351
364
|
|
|
@@ -879,8 +892,9 @@ and configure that one in the Apple Developer portal, then re-run.
|
|
|
879
892
|
|
|
880
893
|
When the ASC combined floor (max of pending review, `preReleaseVersions`,
|
|
881
894
|
or builds-via-`preReleaseVersion`) exceeds your project's
|
|
882
|
-
`MARKETING_VERSION`, the action auto-bumps and
|
|
883
|
-
|
|
895
|
+
`MARKETING_VERSION`, the action auto-bumps and preserves the staged source on a
|
|
896
|
+
verified task branch. The app session resolves its `source_maintenance_pending`
|
|
897
|
+
receipt through its normal checked PR before source delivery is complete.
|
|
884
898
|
|
|
885
899
|
Default policy is `rollover` — patch with carry: at `.9` it rolls into
|
|
886
900
|
the next minor (`1.0.9` → `1.1.0`), and at minor=9 it cascades into the
|
|
@@ -917,8 +931,8 @@ do not override the input.
|
|
|
917
931
|
In `'none'` mode, the floor check fails the build and you must bump
|
|
918
932
|
`MARKETING_VERSION` manually before retrying.
|
|
919
933
|
|
|
920
|
-
**Side effect:** the
|
|
921
|
-
|
|
934
|
+
**Side effect:** the retained source commit identifies the version change.
|
|
935
|
+
Signing credentials are refused from maintenance commits; use encrypted repository secrets.
|
|
922
936
|
|
|
923
937
|
**Source-of-truth resolution.** The auto-bump writes the new value
|
|
924
938
|
into the file your project actually reads from, in this order:
|
|
@@ -946,8 +960,8 @@ The vendored action ships with a per-run autoupdate check. On every
|
|
|
946
960
|
push to your default branch, the action queries npm for the latest
|
|
947
961
|
`gowalk-cicd`, compares against the local marker at
|
|
948
962
|
`.github/actions/swift-app/.daemux-version`, and if newer, re-vendors
|
|
949
|
-
the package via `npx --yes` and
|
|
950
|
-
|
|
963
|
+
the package via `npx --yes` and preserves the refreshed action files
|
|
964
|
+
on a verified task branch (under both `.github/actions/swift-app/` and
|
|
951
965
|
`.github/actions/android-app/`).
|
|
952
966
|
`.github/workflows/deploy.yml` is NEVER auto-committed — see
|
|
953
967
|
"deploy.yml is not auto-updated" below.
|
|
@@ -955,15 +969,15 @@ back (under both `.github/actions/swift-app/` and
|
|
|
955
969
|
| Aspect | Behaviour |
|
|
956
970
|
|--------|-----------|
|
|
957
971
|
| Trigger | Push to default branch only (PR / branch runs do nothing) |
|
|
958
|
-
|
|
|
959
|
-
|
|
|
960
|
-
|
|
|
961
|
-
| Failure mode |
|
|
972
|
+
| Landing | The app session adopts the exact receipt on its current task branch and uses the protected PR |
|
|
973
|
+
| Isolation | CI leaves HEAD and worktree bytes intact; no default-branch push or rebase |
|
|
974
|
+
| Candidates | Action updates and later version changes have separate verified receipts |
|
|
975
|
+
| Failure mode | npm discovery remains optional; unconfirmed source preservation fails the job |
|
|
962
976
|
|
|
963
977
|
The shipped `deploy.yml` template already includes the required
|
|
964
978
|
`paths-ignore` entries for both action directories. If you have an
|
|
965
979
|
older `deploy.yml` checked in, add both entries to prevent the
|
|
966
|
-
|
|
980
|
+
tooling-only source landing from re-triggering the mobile workflow.
|
|
967
981
|
|
|
968
982
|
### deploy.yml is not auto-updated
|
|
969
983
|
|
|
@@ -973,9 +987,9 @@ of `contents: write` — this is a built-in safeguard against CI
|
|
|
973
987
|
self-modification. When a new version of `gowalk-cicd`
|
|
974
988
|
requires `deploy.yml` schema changes (e.g., new permissions, new
|
|
975
989
|
paths-ignore entries), the action's release notes will call this out
|
|
976
|
-
and
|
|
977
|
-
|
|
978
|
-
|
|
990
|
+
and the app session runs the pinned `npx --yes gowalk-cicd` adoption in its task
|
|
991
|
+
workspace, preserving app-specific workflow inputs and landing the full change through
|
|
992
|
+
its checked PR. The CI-side update does not commit the workflow.
|
|
979
993
|
|
|
980
994
|
Changes that need that manual run:
|
|
981
995
|
|
package/action/.daemux-version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
1.0.
|
|
1
|
+
1.0.79
|
package/action/action.yml
CHANGED
|
@@ -182,8 +182,8 @@ inputs:
|
|
|
182
182
|
re-vendors the package via `npx --yes` if the published version is
|
|
183
183
|
newer than the marker at .github/actions/swift-app/.daemux-version.
|
|
184
184
|
The refreshed action files (NOT deploy.yml, which GITHUB_TOKEN
|
|
185
|
-
cannot push) are
|
|
186
|
-
|
|
185
|
+
cannot push) are retained on a verified task branch for the app session's
|
|
186
|
+
checked PR. Set to 'false' to pin the vendored copy.
|
|
187
187
|
required: false
|
|
188
188
|
default: "true"
|
|
189
189
|
|
|
@@ -205,15 +205,23 @@ runs:
|
|
|
205
205
|
rm -rf "$RUNNER_TEMP/swift-app-action"
|
|
206
206
|
mkdir -p "$RUNNER_TEMP/swift-app-action"
|
|
207
207
|
cp -R "${{ github.action_path }}/." "$RUNNER_TEMP/swift-app-action/"
|
|
208
|
+
# Native repositories receive the same canonical Google transport as Android.
|
|
209
|
+
mkdir -p "$RUNNER_TEMP/swift-app-action/google-transport"
|
|
210
|
+
for helper in play_store_proxy.py jvm_proxy_relay.py; do
|
|
211
|
+
if [ -f "$GITHUB_WORKSPACE/.github/actions/android-app/scripts/$helper" ]; then
|
|
212
|
+
cp "$GITHUB_WORKSPACE/.github/actions/android-app/scripts/$helper" \
|
|
213
|
+
"$RUNNER_TEMP/swift-app-action/google-transport/$helper"
|
|
214
|
+
fi
|
|
215
|
+
done
|
|
208
216
|
echo "SWIFT_APP_ACTION=$RUNNER_TEMP/swift-app-action" >> "$GITHUB_ENV"
|
|
209
217
|
|
|
210
218
|
# The plugin self-update runs BEFORE the version, signing and archive
|
|
211
|
-
# steps and
|
|
219
|
+
# steps and preserves its own source candidate. It used to run after them, so a
|
|
212
220
|
# deploy that was red in the version step (App Store Connect refusing a
|
|
213
221
|
# new version while one is in review, GoVolt run 33743284641) never
|
|
214
222
|
# reached the self-update, and the plugin release that fixed exactly
|
|
215
223
|
# that failure could not arrive in the repository that needed it.
|
|
216
|
-
#
|
|
224
|
+
# Preserving here rather than after signing is the same
|
|
217
225
|
# point: a red step later in this run must not take the update with it.
|
|
218
226
|
- name: Stage bot-side scripts to runner temp
|
|
219
227
|
if: ${{ (inputs.archive == 'true' || inputs.auto-update == 'true')
|
|
@@ -227,6 +235,8 @@ runs:
|
|
|
227
235
|
"$RUNNER_TEMP/swift-app-bot-scripts/"
|
|
228
236
|
cp "$SWIFT_APP_ACTION/scripts/commit_bot_changes.sh" \
|
|
229
237
|
"$RUNNER_TEMP/swift-app-bot-scripts/"
|
|
238
|
+
cp "$SWIFT_APP_ACTION/scripts/source_maintenance.py" \
|
|
239
|
+
"$RUNNER_TEMP/swift-app-bot-scripts/"
|
|
230
240
|
chmod +x "$RUNNER_TEMP/swift-app-bot-scripts/"*.sh
|
|
231
241
|
echo "BOT_SCRIPTS_DIR=$RUNNER_TEMP/swift-app-bot-scripts" >> "$GITHUB_ENV"
|
|
232
242
|
|
|
@@ -243,9 +253,9 @@ runs:
|
|
|
243
253
|
run: bash "$BOT_SCRIPTS_DIR/autoupdate_check.sh"
|
|
244
254
|
|
|
245
255
|
- name: Commit + push refreshed app-ci
|
|
246
|
-
#
|
|
247
|
-
#
|
|
248
|
-
#
|
|
256
|
+
# Preserve a source candidate immediately, even when later version/signing
|
|
257
|
+
# fails. The app session lands it through its normal protected PR.
|
|
258
|
+
# deploy.yml is deliberately not staged (GITHUB_TOKEN
|
|
249
259
|
# cannot push workflow files). No-op when the plugin was current.
|
|
250
260
|
if: ${{ inputs.auto-update == 'true'
|
|
251
261
|
&& github.event_name == 'push'
|
|
@@ -256,11 +266,6 @@ runs:
|
|
|
256
266
|
GITHUB_REF_NAME: ${{ github.ref_name }}
|
|
257
267
|
run: |
|
|
258
268
|
set -euo pipefail
|
|
259
|
-
# A shallow checkout (fetch-depth 1 for direct action callers) must
|
|
260
|
-
# be able to rebase on a moved default branch inside the push retry.
|
|
261
|
-
git fetch --unshallow 2>/dev/null \
|
|
262
|
-
|| git fetch --depth=2147483647 2>/dev/null \
|
|
263
|
-
|| true
|
|
264
269
|
bash "$BOT_SCRIPTS_DIR/commit_bot_changes.sh"
|
|
265
270
|
|
|
266
271
|
- name: Resolve credentials + auto-detect Xcode project
|
|
@@ -293,6 +298,21 @@ runs:
|
|
|
293
298
|
echo "Installing xcodegen (project.yml present, binary missing)…"
|
|
294
299
|
brew install xcodegen >/dev/null 2>&1 || HOMEBREW_NO_AUTO_UPDATE=1 brew install xcodegen
|
|
295
300
|
fi
|
|
301
|
+
if ! python3 "$SWIFT_APP_ACTION/scripts/native_pods.py" "$GITHUB_WORKSPACE" \
|
|
302
|
+
--container "${INPUT_WORKSPACE:-$INPUT_PROJECT}" \
|
|
303
|
+
--proxy-helper "$SWIFT_APP_ACTION/google-transport/play_store_proxy.py" \
|
|
304
|
+
> "$RUNNER_TEMP/native-pods.json"; then
|
|
305
|
+
cat "$RUNNER_TEMP/native-pods.json"
|
|
306
|
+
exit 1
|
|
307
|
+
fi
|
|
308
|
+
# CocoaPods adds the dependency workspace even when the app supplied only its project.
|
|
309
|
+
INPUT_WORKSPACE=$(python3 - <<'PY_NATIVE_WORKSPACE'
|
|
310
|
+
import json, os
|
|
311
|
+
with open(os.environ["RUNNER_TEMP"] + "/native-pods.json") as receipt:
|
|
312
|
+
print(json.load(receipt).get("workspace", os.environ.get("INPUT_WORKSPACE", "")))
|
|
313
|
+
PY_NATIVE_WORKSPACE
|
|
314
|
+
)
|
|
315
|
+
export INPUT_WORKSPACE
|
|
296
316
|
python3 "$SWIFT_APP_ACTION/scripts/read_config.py"
|
|
297
317
|
|
|
298
318
|
- name: Select latest Xcode
|
|
@@ -320,6 +340,8 @@ runs:
|
|
|
320
340
|
|| 'platform=iOS Simulator,name=iPhone 15' }}
|
|
321
341
|
run: |
|
|
322
342
|
set -o pipefail
|
|
343
|
+
python3 "$SWIFT_APP_ACTION/scripts/native_dependency_guard.py" "$GITHUB_WORKSPACE" \
|
|
344
|
+
--container "${WORKSPACE:-$PROJECT}"
|
|
323
345
|
if [ -n "$TEST_COMMAND" ]; then
|
|
324
346
|
echo "Running custom test command: $TEST_COMMAND"
|
|
325
347
|
bash -c "$TEST_COMMAND"
|
|
@@ -338,6 +360,7 @@ runs:
|
|
|
338
360
|
-scheme "$SCHEME" \
|
|
339
361
|
-configuration "$CONFIGURATION" \
|
|
340
362
|
-destination "$TEST_DESTINATION" \
|
|
363
|
+
-disableAutomaticPackageResolution -skipPackageUpdates \
|
|
341
364
|
CODE_SIGNING_ALLOWED=NO \
|
|
342
365
|
CODE_SIGNING_REQUIRED=NO \
|
|
343
366
|
CODE_SIGN_IDENTITY="" \
|
|
@@ -376,6 +399,8 @@ runs:
|
|
|
376
399
|
CONFIGURATION: ${{ inputs.configuration || env.CFG_CONFIGURATION }}
|
|
377
400
|
run: |
|
|
378
401
|
set -o pipefail
|
|
402
|
+
python3 "$SWIFT_APP_ACTION/scripts/native_dependency_guard.py" "$GITHUB_WORKSPACE" \
|
|
403
|
+
--container "${WORKSPACE:-$PROJECT}"
|
|
379
404
|
python3 "$SWIFT_APP_ACTION/scripts/resolve_marketing_version.py" \
|
|
380
405
|
| tee -a "$GITHUB_ENV"
|
|
381
406
|
|
|
@@ -487,30 +512,10 @@ runs:
|
|
|
487
512
|
echo "::error::Signing preparation failed; preserving any generated cache before failing."
|
|
488
513
|
fi
|
|
489
514
|
|
|
490
|
-
#
|
|
491
|
-
#
|
|
492
|
-
#
|
|
493
|
-
#
|
|
494
|
-
# repo's default branch. This is a deliberate trade-off chosen over
|
|
495
|
-
# GitHub Secrets-based storage:
|
|
496
|
-
# - Consumer repos already commit the ASC API key (.p8) to creds/, so
|
|
497
|
-
# the threat model already accepts repo-stored Apple credentials.
|
|
498
|
-
# - Each app uses a different Apple Developer account / .p8, so
|
|
499
|
-
# org-level GitHub Secrets do not amortize setup work.
|
|
500
|
-
# - "Drop file in creds/" remains the consistent UX for new projects.
|
|
501
|
-
# Non-negotiable mitigations (see IOS_NATIVE_CI_SETUP.md):
|
|
502
|
-
# - Repo MUST stay private (branch protection on visibility recommended).
|
|
503
|
-
# - permissions: contents: write is required; expands attack surface
|
|
504
|
-
# of any compromised step in the workflow — keep workflow minimal.
|
|
505
|
-
# - .gitignore must NOT block the new creds/ files (see setup doc).
|
|
506
|
-
#
|
|
507
|
-
# The autoupdate half runs FIRST in this action (see the top of the
|
|
508
|
-
# step list) and lands in its own commit, so a fixed plugin reaches a
|
|
509
|
-
# repository whose deploy is failing in the version or signing steps
|
|
510
|
-
# below. What remains here: (1) stage cert files, (2) commit + push
|
|
511
|
-
# whatever is staged, (3) re-raise a deferred prepare failure after
|
|
512
|
-
# preserving its cache.
|
|
513
|
-
|
|
515
|
+
# Encrypted signing material is the supported factory path and disables
|
|
516
|
+
# the legacy signing-cache staging below. Source maintenance refuses staged
|
|
517
|
+
# credential paths; it never pushes them or bypasses branch protection.
|
|
518
|
+
# A version bump is preserved separately from the earlier action update.
|
|
514
519
|
|
|
515
520
|
- name: Stage refreshed signing identity
|
|
516
521
|
# Only stage on push to the default branch — PR runs and feature
|
|
@@ -570,8 +575,8 @@ runs:
|
|
|
570
575
|
fi
|
|
571
576
|
|
|
572
577
|
- name: Commit + push staged bot changes
|
|
573
|
-
#
|
|
574
|
-
#
|
|
578
|
+
# Any marketing-version auto-bump the version step staged is preserved.
|
|
579
|
+
# The autoupdate has its own candidate at the top of
|
|
575
580
|
# this action; this script is a no-op when nothing is staged, so a
|
|
576
581
|
# PR / feature-branch run that somehow reaches this step does no
|
|
577
582
|
# harm — but we still gate on default-branch push for clarity.
|
|
@@ -604,6 +609,8 @@ runs:
|
|
|
604
609
|
BUNDLE_ID: ${{ inputs.bundle-id || env.CFG_BUNDLE_ID }}
|
|
605
610
|
run: |
|
|
606
611
|
set -o pipefail
|
|
612
|
+
python3 "$SWIFT_APP_ACTION/scripts/native_dependency_guard.py" "$GITHUB_WORKSPACE" \
|
|
613
|
+
--container "${WORKSPACE:-$PROJECT}"
|
|
607
614
|
# Read every target's complete settings and preserve Xcode's actual status/error.
|
|
608
615
|
# The selected application's source root can differ from its containing workspace.
|
|
609
616
|
INFOPLIST_PATH=$(python3 "$SWIFT_APP_ACTION/scripts/resolve_info_plist.py")
|
|
@@ -662,6 +669,8 @@ runs:
|
|
|
662
669
|
CONFIGURATION: ${{ inputs.configuration || env.CFG_CONFIGURATION }}
|
|
663
670
|
run: |
|
|
664
671
|
set -o pipefail
|
|
672
|
+
python3 "$SWIFT_APP_ACTION/scripts/native_dependency_guard.py" "$GITHUB_WORKSPACE" \
|
|
673
|
+
--container "${WORKSPACE:-$PROJECT}"
|
|
665
674
|
if [ -n "$WORKSPACE" ]; then
|
|
666
675
|
PROJ_ARGS=(-workspace "$WORKSPACE")
|
|
667
676
|
else
|
|
@@ -680,6 +689,7 @@ runs:
|
|
|
680
689
|
-configuration "$CONFIGURATION" \
|
|
681
690
|
-destination "generic/platform=iOS" \
|
|
682
691
|
-archivePath "$RUNNER_TEMP/app.xcarchive" \
|
|
692
|
+
-disableAutomaticPackageResolution -skipPackageUpdates \
|
|
683
693
|
-skipPackagePluginValidation \
|
|
684
694
|
-skipMacroValidation \
|
|
685
695
|
CURRENT_PROJECT_VERSION="$BUILD_NUMBER" \
|
|
@@ -47,6 +47,7 @@ from pathlib import Path
|
|
|
47
47
|
from typing import Optional
|
|
48
48
|
|
|
49
49
|
from cfg_io import log, notice
|
|
50
|
+
import native_dependency_guard
|
|
50
51
|
|
|
51
52
|
|
|
52
53
|
APP_PRODUCT_TYPE = "com.apple.product-type.application"
|
|
@@ -231,12 +232,8 @@ def _pick_by_basename(matches: list[Path], workspace: Path, label: str) -> Path:
|
|
|
231
232
|
def _resolve_package_dependencies(
|
|
232
233
|
workspace: Path, project: str, workspace_file: str
|
|
233
234
|
) -> bool:
|
|
234
|
-
"""
|
|
235
|
-
|
|
236
|
-
Returns True when resolution finished (regardless of exit status — a
|
|
237
|
-
project with no packages still exits cleanly and a partial resolve may
|
|
238
|
-
be enough for ``-list``), False when it could not be run at all.
|
|
239
|
-
"""
|
|
235
|
+
"""Retain local Xcode recovery only after refusing an unverified package graph."""
|
|
236
|
+
native_dependency_guard.check(workspace, workspace_file or project)
|
|
240
237
|
cmd = ["xcodebuild", "-resolvePackageDependencies"]
|
|
241
238
|
if workspace_file:
|
|
242
239
|
cmd += ["-workspace", workspace_file]
|
|
@@ -269,7 +266,8 @@ def _list_schemes(
|
|
|
269
266
|
if key in _list_cache:
|
|
270
267
|
return _list_cache[key]
|
|
271
268
|
|
|
272
|
-
|
|
269
|
+
native_dependency_guard.check(workspace, workspace_file or project)
|
|
270
|
+
cmd = ["xcodebuild", "-list", "-json", "-disableAutomaticPackageResolution", "-skipPackageUpdates"]
|
|
273
271
|
if workspace_file:
|
|
274
272
|
cmd += ["-workspace", workspace_file]
|
|
275
273
|
elif project:
|
|
@@ -343,7 +341,9 @@ def _show_build_settings(
|
|
|
343
341
|
if key in _settings_cache:
|
|
344
342
|
return _settings_cache[key]
|
|
345
343
|
|
|
346
|
-
|
|
344
|
+
native_dependency_guard.check(workspace, workspace_file or project)
|
|
345
|
+
cmd = ["xcodebuild", "-showBuildSettings", "-json", "-scheme", scheme,
|
|
346
|
+
"-disableAutomaticPackageResolution", "-skipPackageUpdates"]
|
|
347
347
|
if configuration:
|
|
348
348
|
cmd += ["-configuration", configuration]
|
|
349
349
|
if workspace_file:
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
#
|
|
3
|
-
#
|
|
4
|
-
# on exhausted retries so the wrapping job stays green.
|
|
2
|
+
# Preserve staged source on a verified task branch for the app session's normal PR.
|
|
3
|
+
# The build checkout, protected default branch and unrelated dirty files stay intact.
|
|
5
4
|
set -euo pipefail
|
|
6
5
|
|
|
7
6
|
if git diff --cached --quiet; then
|
|
@@ -54,29 +53,4 @@ git config user.name >/dev/null 2>&1 || git config user.name "github-actions[b
|
|
|
54
53
|
git config user.email >/dev/null 2>&1 || \
|
|
55
54
|
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
|
56
55
|
|
|
57
|
-
|
|
58
|
-
-m "Co-Authored-By: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>"
|
|
59
|
-
|
|
60
|
-
branch="${GITHUB_REF_NAME:-$(git rev-parse --abbrev-ref HEAD)}"
|
|
61
|
-
|
|
62
|
-
attempt=1
|
|
63
|
-
max=3
|
|
64
|
-
while (( attempt <= max )); do
|
|
65
|
-
if git push origin "HEAD:$branch"; then
|
|
66
|
-
echo "commit_bot_changes: push succeeded on attempt $attempt"
|
|
67
|
-
exit 0
|
|
68
|
-
fi
|
|
69
|
-
echo "::warning::commit_bot_changes: push attempt $attempt failed"
|
|
70
|
-
if (( attempt < max )); then
|
|
71
|
-
git fetch origin "$branch" || true
|
|
72
|
-
git pull --rebase origin "$branch" || {
|
|
73
|
-
echo "::warning::commit_bot_changes: rebase failed, aborting"
|
|
74
|
-
git rebase --abort >/dev/null 2>&1 || true
|
|
75
|
-
exit 0
|
|
76
|
-
}
|
|
77
|
-
fi
|
|
78
|
-
attempt=$((attempt + 1))
|
|
79
|
-
done
|
|
80
|
-
|
|
81
|
-
echo "::warning::commit_bot_changes: exhausted $max push attempts"
|
|
82
|
-
exit 0
|
|
56
|
+
exec python3 "$(dirname "$0")/source_maintenance.py" "$subject"
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Refuse unverified Xcode package networking in the selected native dependency graph."""
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import json
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
import sys
|
|
9
|
+
|
|
10
|
+
from native_project_graph import Refused, project, selected, workspace
|
|
11
|
+
|
|
12
|
+
REMEDIATION = ("Preserve source; use the vendor's official CocoaPods distribution through the assigned Google proxy "
|
|
13
|
+
"and commit Podfile.lock, or verified local XCFrameworks; rerun the same task's build.")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def check(root: Path, container: str = "", *, before_pods: bool = False, initial_pods_lock: bool = False) -> dict:
|
|
17
|
+
if initial_pods_lock and not before_pods:
|
|
18
|
+
raise Refused("native_initial_lock_phase_required")
|
|
19
|
+
root = root.resolve(strict=True)
|
|
20
|
+
pending, visited, count = [selected(root, container, before_pods)], set(), 0
|
|
21
|
+
while pending:
|
|
22
|
+
path = pending.pop()
|
|
23
|
+
if path in visited:
|
|
24
|
+
continue
|
|
25
|
+
visited.add(path)
|
|
26
|
+
if len(visited) > 200:
|
|
27
|
+
raise Refused("native_project_inventory_unverified")
|
|
28
|
+
if path.suffix == ".xcworkspace":
|
|
29
|
+
pending.extend(workspace(root, path, before_pods, initial_pods_lock))
|
|
30
|
+
elif path.suffix == ".xcodeproj":
|
|
31
|
+
pending.extend(project(root, path))
|
|
32
|
+
count += 1
|
|
33
|
+
else:
|
|
34
|
+
raise Refused("native_workspace_reference_unverified")
|
|
35
|
+
if not count:
|
|
36
|
+
raise Refused("native_project_inventory_missing")
|
|
37
|
+
return {"ok": True, "projects": count, "xcode_package_resolution": "unused",
|
|
38
|
+
"phase": "before_pods" if before_pods else "complete"}
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def main(argv: list[str]) -> int:
|
|
42
|
+
parser = argparse.ArgumentParser(description=__doc__)
|
|
43
|
+
parser.add_argument("root", type=Path)
|
|
44
|
+
parser.add_argument("--container", default="")
|
|
45
|
+
parser.add_argument("--before-pods", action="store_true")
|
|
46
|
+
parser.add_argument("--initial-pods-lock", action="store_true")
|
|
47
|
+
args = parser.parse_args(argv)
|
|
48
|
+
try:
|
|
49
|
+
answer = check(args.root, args.container, before_pods=args.before_pods,
|
|
50
|
+
initial_pods_lock=args.initial_pods_lock)
|
|
51
|
+
except (Refused, OSError) as exc:
|
|
52
|
+
code = str(exc) if isinstance(exc, Refused) else "native_project_inventory_unverified"
|
|
53
|
+
answer = {"ok": False, "code": code, "next": REMEDIATION}
|
|
54
|
+
print(json.dumps(answer, sort_keys=True))
|
|
55
|
+
return 0 if answer["ok"] else 1
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
if __name__ == "__main__":
|
|
59
|
+
raise SystemExit(main(sys.argv[1:]))
|