gowalk-cicd 1.0.0
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 +94 -0
- package/README.md +417 -0
- package/action/.daemux-version +1 -0
- package/action/MIGRATION.md +232 -0
- package/action/action.yml +973 -0
- package/action/prompts/generate_dependent_fields.prompt.yml +78 -0
- package/action/prompts/generate_descriptions.prompt.yml +59 -0
- package/action/scripts/app_context_scanner.py +239 -0
- package/action/scripts/asc_build_history.py +284 -0
- package/action/scripts/asc_common.py +246 -0
- package/action/scripts/asc_metadata_applier.py +368 -0
- package/action/scripts/asc_metadata_detector.py +250 -0
- package/action/scripts/asc_version_create.py +365 -0
- package/action/scripts/asc_version_fetch.py +274 -0
- package/action/scripts/asc_version_reuse.py +177 -0
- package/action/scripts/auto_detect.py +391 -0
- package/action/scripts/autoupdate_check.sh +118 -0
- package/action/scripts/cert_factory.py +190 -0
- package/action/scripts/cfg_io.py +27 -0
- package/action/scripts/cfg_resolve.py +147 -0
- package/action/scripts/commit_bot_changes.sh +82 -0
- package/action/scripts/creds_store.py +328 -0
- package/action/scripts/keychain.py +103 -0
- package/action/scripts/lookup_app_id.py +51 -0
- package/action/scripts/manage_marketing_version.py +337 -0
- package/action/scripts/metadata_constants.py +102 -0
- package/action/scripts/mmv_decide_create.py +181 -0
- package/action/scripts/mmv_floor_check.py +265 -0
- package/action/scripts/next_build_number.py +195 -0
- package/action/scripts/pbxproj_editor.py +220 -0
- package/action/scripts/prepare_signing.py +223 -0
- package/action/scripts/profile_io.py +64 -0
- package/action/scripts/profile_manager.py +307 -0
- package/action/scripts/read_config.py +315 -0
- package/action/scripts/resolve_marketing_version.py +173 -0
- package/action/scripts/set_app_store_whats_new.py +317 -0
- package/action/scripts/team_resolver.py +130 -0
- package/action/scripts/test_app_context_scanner.py +167 -0
- package/action/scripts/test_asc_build_history.py +221 -0
- package/action/scripts/test_asc_builds_prerelease.py +219 -0
- package/action/scripts/test_asc_common_timeouts.py +121 -0
- package/action/scripts/test_asc_metadata_applier.py +850 -0
- package/action/scripts/test_asc_metadata_detector.py +445 -0
- package/action/scripts/test_auto_detect.py +432 -0
- package/action/scripts/test_cfg_resolve_credentials.py +48 -0
- package/action/scripts/test_compute_next_version.py +95 -0
- package/action/scripts/test_compute_next_version_patch_backcompat.py +41 -0
- package/action/scripts/test_compute_next_version_strictness.py +67 -0
- package/action/scripts/test_fetch_versions.py +163 -0
- package/action/scripts/test_ground_truth_floor.py +104 -0
- package/action/scripts/test_manage_marketing_version.py +134 -0
- package/action/scripts/test_manage_marketing_version_autoroll.py +149 -0
- package/action/scripts/test_manage_marketing_version_floor.py +170 -0
- package/action/scripts/test_manage_marketing_version_match.py +187 -0
- package/action/scripts/test_mmv_409_classifier.py +231 -0
- package/action/scripts/test_mmv_autobump_persist_gate.py +131 -0
- package/action/scripts/test_mmv_decide_create.py +119 -0
- package/action/scripts/test_mmv_floor_check.py +158 -0
- package/action/scripts/test_mmv_floor_crosscheck.py +132 -0
- package/action/scripts/test_mmv_helpers.py +57 -0
- package/action/scripts/test_next_build_number.py +174 -0
- package/action/scripts/test_read_config_auto_detect.py +257 -0
- package/action/scripts/test_resolve_marketing_version.py +298 -0
- package/action/scripts/test_reuse_stale_editable.py +182 -0
- package/action/scripts/test_set_app_store_whats_new.py +71 -0
- package/action/scripts/test_team_fallback_wiring.py +157 -0
- package/action/scripts/test_team_resolver.py +249 -0
- package/action/scripts/tests_common.py +167 -0
- package/action/scripts/version_utils.py +373 -0
- package/android-action/.daemux-version +1 -0
- package/android-action/action.yml +92 -0
- package/android-action/scripts/android_config.py +130 -0
- package/android-action/scripts/bitrise_deploy.py +160 -0
- package/android-action/scripts/find_bundle.py +25 -0
- package/android-action/scripts/play_preflight.py +69 -0
- package/android-action/scripts/resolve_android.py +70 -0
- package/android-action/scripts/sign_bundle.py +70 -0
- package/android-action/scripts/test_android_config.py +97 -0
- package/android-action/scripts/test_bitrise_deploy.py +124 -0
- package/android-action/scripts/test_sign_bundle.py +82 -0
- package/bin/cli.mjs +63 -0
- package/package.json +57 -0
- package/src/install.mjs +208 -0
- package/templates/deploy.yml +150 -0
package/CLAUDE.md
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# gowalk-cicd — Development Guide
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
|
|
5
|
+
npm package that distributes iOS TestFlight and Flutter Android Google Play
|
|
6
|
+
actions. Consumer runs `npx gowalk-cicd` and gets:
|
|
7
|
+
|
|
8
|
+
- `.github/actions/swift-app/` — vendored composite action
|
|
9
|
+
- `.github/actions/android-app/` — vendored Flutter Android action
|
|
10
|
+
- `.github/workflows/deploy.yml` — dual-platform workflow
|
|
11
|
+
|
|
12
|
+
Re-running the command is both install and update. No scope flag, no
|
|
13
|
+
uninstall. Keep it simple.
|
|
14
|
+
|
|
15
|
+
## Structure
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
gowalk-cicd/ ← repo root IS the gowalk-cicd package
|
|
19
|
+
├── action/ ← iOS action — canonical source of truth
|
|
20
|
+
│ ├── action.yml
|
|
21
|
+
│ └── scripts/*.py
|
|
22
|
+
├── android-action/ ← Android action — canonical source of truth
|
|
23
|
+
│ ├── action.yml
|
|
24
|
+
│ └── scripts/*.py
|
|
25
|
+
├── bin/cli.mjs ← CLI entrypoint (parses flags, calls runInstall)
|
|
26
|
+
├── src/install.mjs ← copy logic, gitignore-check, summary print
|
|
27
|
+
├── templates/deploy.yml ← dual-platform workflow copied verbatim
|
|
28
|
+
├── scripts/auto-version.mjs ← patch-bump-from-npm used by the publish workflow
|
|
29
|
+
├── .github/workflows/publish.yml ← auto-publish to npm on push to main
|
|
30
|
+
├── README.md ← consumer docs
|
|
31
|
+
├── CLAUDE.md ← this file (for AI agents)
|
|
32
|
+
└── package.json
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Key invariant: action directories are canonical sources
|
|
36
|
+
|
|
37
|
+
The composite actions under `action/` and `android-action/` are the source of
|
|
38
|
+
truth. Edit them directly here; consumer copies are generated by the installer.
|
|
39
|
+
|
|
40
|
+
There is no upstream copy to sync from. This package was split out of the
|
|
41
|
+
daemux-plugins monorepo, where `action/` used to be a vendored mirror of
|
|
42
|
+
`.github/actions/ios-native-testflight/` refreshed by a `sync-action.mjs`
|
|
43
|
+
step. That relationship no longer applies — the sync script and the
|
|
44
|
+
vendoring concept are gone.
|
|
45
|
+
|
|
46
|
+
## Publishing
|
|
47
|
+
|
|
48
|
+
Auto-publish on every push to `main` (the entire repo IS the package). The
|
|
49
|
+
workflow (`.github/workflows/publish.yml`):
|
|
50
|
+
|
|
51
|
+
1. Runs `scripts/auto-version.mjs .` which queries npm for the current
|
|
52
|
+
published version of `gowalk-cicd` and bumps the patch. A first publish
|
|
53
|
+
keeps the seed version from `package.json`.
|
|
54
|
+
2. Stamps the new version into both action directories (consumed by the
|
|
55
|
+
consumer-side autoupdate check) before publishing.
|
|
56
|
+
3. Runs `npm publish --access public` using npm Trusted Publishing. The new
|
|
57
|
+
package name requires one bootstrap publish via `NPM_TOKEN` before npm can
|
|
58
|
+
attach the `publish.yml` trusted-publisher relationship; the workflow
|
|
59
|
+
supplies that token as a fallback and uses OIDC after trust is configured.
|
|
60
|
+
|
|
61
|
+
Manual publish (from repo root):
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
npm publish --access public
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Testing locally
|
|
68
|
+
|
|
69
|
+
From a scratch directory:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
mkdir /tmp/scratch && cd /tmp/scratch && git init
|
|
73
|
+
node /path/to/gowalk-cicd/bin/cli.mjs --dry-run
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Or install into a real app repo (overwrites both action directories and
|
|
77
|
+
`.github/workflows/deploy.yml`):
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
cd ~/path/to/app-project
|
|
81
|
+
node /path/to/gowalk-cicd/bin/cli.mjs
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Conventions
|
|
85
|
+
|
|
86
|
+
- Use Node's built-in `fs`/`path`/`url` modules; no runtime deps except
|
|
87
|
+
`update-notifier` for the nag banner.
|
|
88
|
+
- Never hardcode paths — use `fileURLToPath(import.meta.url)` and derive
|
|
89
|
+
from `__dirname`.
|
|
90
|
+
- Keep the CLI surface minimal: `--dry-run`, `-v`, `-h`. Resist adding flags.
|
|
91
|
+
- `.gitignore` in the consumer repo is git-tracked. Never auto-edit it.
|
|
92
|
+
Detect and warn, tell the user to remove lines manually.
|
|
93
|
+
- Store API calls belong in GitHub Actions. Local tests may build/sign but must
|
|
94
|
+
never connect to App Store Connect or Google Play.
|
package/README.md
ADDED
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
# gowalk-cicd
|
|
2
|
+
|
|
3
|
+
One installer for iOS TestFlight and Android Google Play delivery. It supports
|
|
4
|
+
native Swift/SwiftUI iOS projects and Flutter apps. Put the platform keys under
|
|
5
|
+
`creds/`, push to `main`, and both store builds run only on GitHub Actions.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
From the root of your app repo:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npx --yes gowalk-cicd
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Writes three things into your repo:
|
|
16
|
+
|
|
17
|
+
- `.github/actions/swift-app/` — the vendored composite action (action.yml + scripts)
|
|
18
|
+
- `.github/actions/android-app/` — Flutter Android build/sign action
|
|
19
|
+
- `.github/workflows/deploy.yml` — workflow that builds and deploys both platforms
|
|
20
|
+
|
|
21
|
+
Re-run the same command anytime to pull the latest version.
|
|
22
|
+
|
|
23
|
+
## iOS credentials
|
|
24
|
+
|
|
25
|
+
1. Create an **App Store Connect API key** with the **App Manager** role
|
|
26
|
+
(App Store Connect → Users and Access → Keys → generate).
|
|
27
|
+
2. Download the `.p8` and save it into your repo at:
|
|
28
|
+
```
|
|
29
|
+
creds/AuthKey_<KEY_ID>_Issuer_<ISSUER_UUID>.p8
|
|
30
|
+
```
|
|
31
|
+
Example: `creds/AuthKey_ABC123DEFG_Issuer_69a6de70-xxxx-47e3-e053-5b8c7c11a4d1.p8`
|
|
32
|
+
The filename encodes both the `KEY_ID` and the `ISSUER_ID` — the composite
|
|
33
|
+
action parses them from the filename.
|
|
34
|
+
3. The repo **must be private**. The p8 is a long-lived credential; never
|
|
35
|
+
commit it to a public repo.
|
|
36
|
+
4. (First time only, per app) Create the app record in App Store Connect. See
|
|
37
|
+
[First-time app setup](#first-time-app-setup) below.
|
|
38
|
+
5. The existing metadata-rich form
|
|
39
|
+
`*(key_id_<KEY_ID>_issuer_<UUID>_vendor_id_<ID>).p8` is also accepted.
|
|
40
|
+
|
|
41
|
+
## Android credentials
|
|
42
|
+
|
|
43
|
+
Place these files under `creds/`:
|
|
44
|
+
|
|
45
|
+
1. `android-upload-key.jks` — the long-lived Google Play upload keystore.
|
|
46
|
+
2. `android-signing.properties` — signing metadata in this exact format:
|
|
47
|
+
|
|
48
|
+
```properties
|
|
49
|
+
storeFile=android-upload-key.jks
|
|
50
|
+
storePassword=<keystore password>
|
|
51
|
+
keyAlias=<upload key alias>
|
|
52
|
+
keyPassword=<upload key password>
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
3. One Google service-account JSON key. Its filename is unrestricted
|
|
56
|
+
(`Gowalk.json` is valid); the action identifies it by the
|
|
57
|
+
`type: service_account` JSON fields.
|
|
58
|
+
|
|
59
|
+
The service account must have Google Play Console access to the app and the
|
|
60
|
+
Android Publisher API must be enabled. Keep the repository private. The
|
|
61
|
+
workflow reads these files from the checkout, so they must be available to
|
|
62
|
+
GitHub Actions (this package's zero-config convention is to track them only in
|
|
63
|
+
a private repository).
|
|
64
|
+
|
|
65
|
+
Google Play requires the first AAB to be uploaded through Play Console. The
|
|
66
|
+
first CI run still succeeds and retains the signed AAB as an
|
|
67
|
+
`android-<package>-<versionCode>` workflow artifact. Upload that artifact once
|
|
68
|
+
in Play Console; every later run detects API readiness and uploads to the
|
|
69
|
+
`internal` track automatically. Override the track or status with repository
|
|
70
|
+
variables `GOOGLE_PLAY_TRACK` and `GOOGLE_PLAY_STATUS`.
|
|
71
|
+
|
|
72
|
+
## Android delivery via Bitrise (alternative mode)
|
|
73
|
+
|
|
74
|
+
Use this when the Android **upload keystore password lives in Bitrise** (not in
|
|
75
|
+
the repo) — the GitHub runner then cannot sign the AAB locally. In this mode the
|
|
76
|
+
Android job triggers a Bitrise workflow (which holds the keystore + Play
|
|
77
|
+
service-account secrets) over the Bitrise API and waits for it, mirroring the
|
|
78
|
+
build result back into the GitHub Actions run. iOS still ships via GitHub
|
|
79
|
+
Actions as normal.
|
|
80
|
+
|
|
81
|
+
Enable it by committing `creds/bitrise.json`:
|
|
82
|
+
|
|
83
|
+
```json
|
|
84
|
+
{
|
|
85
|
+
"enabled": true,
|
|
86
|
+
"app_slug": "8b2a7a24-....",
|
|
87
|
+
"workflow": "android-deploy",
|
|
88
|
+
"branch": "main",
|
|
89
|
+
"api_token": "bitpat_...."
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
- `app_slug` — the Bitrise app slug (from the app's Bitrise URL or
|
|
94
|
+
`GET https://api.bitrise.io/v0.1/apps`).
|
|
95
|
+
- `workflow` — the Bitrise workflow that builds, signs, and runs
|
|
96
|
+
`google-play-deploy` (default `android-deploy`).
|
|
97
|
+
- `branch` — optional; defaults to the pushed ref.
|
|
98
|
+
- `api_token` — a Bitrise personal access token. Committing it to a **private**
|
|
99
|
+
repo's `creds/` follows the same trust model as the ASC `.p8`. For non-private
|
|
100
|
+
repos, omit it here and set the `BITRISE_API_TOKEN` Actions secret instead.
|
|
101
|
+
|
|
102
|
+
When `creds/bitrise.json` is present with `enabled: true`, the local
|
|
103
|
+
build/sign/upload steps are skipped and `bitrise_deploy.py` drives the Bitrise
|
|
104
|
+
build; otherwise the local path above runs unchanged.
|
|
105
|
+
|
|
106
|
+
The Bitrise `android-deploy` workflow must produce a versionCode higher than
|
|
107
|
+
what is already on Play. The convention used by this fleet is a `+200` offset
|
|
108
|
+
over `$BITRISE_BUILD_NUMBER` via a `change-android-versioncode-and-versionname`
|
|
109
|
+
step, with the versionName read from `pubspec.yaml`.
|
|
110
|
+
|
|
111
|
+
## Sharing one distribution cert across a team's apps
|
|
112
|
+
|
|
113
|
+
The iOS action caches the Apple Distribution cert under `creds/` and reuses it
|
|
114
|
+
across runs (cert alive + NotAfter > 30 days). But Apple caps a team at **2**
|
|
115
|
+
distribution certs, and the cache is per-repo. If **three or more apps share one
|
|
116
|
+
Apple Developer team**, they cannot each hold their own cert — every run finds
|
|
117
|
+
its cached cert already revoked by a sibling, mints a new one, and revokes
|
|
118
|
+
another. The symptom is a "Your Certificate Has Been Revoked" email on nearly
|
|
119
|
+
every deploy.
|
|
120
|
+
|
|
121
|
+
Fix: apps in the same Apple team should **share one cert**. Commit the *same*
|
|
122
|
+
`creds/cert.p12` + `creds/cert.meta.json` into every repo of that team (an Apple
|
|
123
|
+
Distribution cert is team-scoped, not app-scoped, so one cert signs them all).
|
|
124
|
+
Each app then gets a cache hit and no new cert is ever created. Provisioning
|
|
125
|
+
profiles stay per-app (named `CI-<bundle_id>`) and regenerate against the shared
|
|
126
|
+
cert on the next run. When onboarding a new app into an existing team, copy a
|
|
127
|
+
sibling's `cert.p12` + `cert.meta.json` in rather than letting the first run
|
|
128
|
+
create a fresh cert.
|
|
129
|
+
|
|
130
|
+
## How it works
|
|
131
|
+
|
|
132
|
+
On every push, iOS and Android run in parallel. Flutter is installed only on
|
|
133
|
+
the GitHub-hosted runners. The Android job runs `flutter analyze`,
|
|
134
|
+
`flutter test`, builds an AAB with `GITHUB_RUN_NUMBER` as its monotonically
|
|
135
|
+
increasing version code, replaces any development signature with the upload
|
|
136
|
+
key, and retains the signed bundle before contacting Google Play.
|
|
137
|
+
|
|
138
|
+
### iOS delivery
|
|
139
|
+
|
|
140
|
+
The iOS composite action runs on `macos-15` and:
|
|
141
|
+
|
|
142
|
+
1. **Auto-detects** your `.xcodeproj` / `.xcworkspace`, scheme, bundle ID,
|
|
143
|
+
and `team_id` (from the ASC API key). No `ci.config.yaml` required —
|
|
144
|
+
override via action inputs only if auto-detection fails.
|
|
145
|
+
2. **Reads the ASC key** from `creds/AuthKey_*.p8` and uses it to authenticate
|
|
146
|
+
to App Store Connect via JWT.
|
|
147
|
+
3. **Decides the marketing version**: either reuses the current `PREPARE_FOR_SUBMISSION`
|
|
148
|
+
version on App Store Connect, or creates a new version if the highest
|
|
149
|
+
declared version is already live.
|
|
150
|
+
4. **Computes the next build number** by querying ASC for the latest uploaded
|
|
151
|
+
build and incrementing.
|
|
152
|
+
5. **Provisions signing** at runtime: generates a throwaway Apple Distribution
|
|
153
|
+
cert + a per-target App Store provisioning profile named `CI-<bundle_id>`.
|
|
154
|
+
Patches the `.pbxproj` to use Manual signing against those profiles.
|
|
155
|
+
6. **Archives** with `xcodebuild archive`, exports the IPA, and uploads via
|
|
156
|
+
`xcrun altool`.
|
|
157
|
+
7. **Sets "What's New"** on every declared localization (reads
|
|
158
|
+
`fastlane/metadata/ios/<locale>/release_notes.txt` if present, or from the
|
|
159
|
+
`app-store-whats-new` input).
|
|
160
|
+
8. **Auto-fills empty App Store metadata** (name, subtitle, keywords,
|
|
161
|
+
description, promotional text, what's new) via GitHub Models AI, on every
|
|
162
|
+
locale that has gaps. See [AI metadata auto-fill](#ai-metadata-auto-fill)
|
|
163
|
+
below.
|
|
164
|
+
|
|
165
|
+
The iOS action requires only the p8. Everything else is derived.
|
|
166
|
+
|
|
167
|
+
## AI metadata auto-fill
|
|
168
|
+
|
|
169
|
+
On every run, after the TestFlight upload succeeds, the action:
|
|
170
|
+
|
|
171
|
+
1. Queries App Store Connect for every `appInfoLocalization` and
|
|
172
|
+
`appStoreVersionLocalization` on the editable version.
|
|
173
|
+
2. Computes the set of empty fields per locale (URL fields are always
|
|
174
|
+
skipped — you must set those manually in ASC).
|
|
175
|
+
3. Scans your repo for context (README, Info.plist, dependency files, top
|
|
176
|
+
Swift files) and feeds it to `openai/gpt-4o` via GitHub Models with a
|
|
177
|
+
strict JSON schema.
|
|
178
|
+
4. PATCHes only the fields that were empty — never overwrites existing
|
|
179
|
+
content.
|
|
180
|
+
|
|
181
|
+
Fully idempotent: a second run with no empty fields skips the AI step
|
|
182
|
+
entirely (zero requests, zero PATCHes).
|
|
183
|
+
|
|
184
|
+
### Requirement: `permissions: models: read`
|
|
185
|
+
|
|
186
|
+
`actions/ai-inference` needs the `models: read` permission. The template
|
|
187
|
+
workflow written by `npx gowalk-cicd` already includes it:
|
|
188
|
+
|
|
189
|
+
```yaml
|
|
190
|
+
permissions:
|
|
191
|
+
contents: read
|
|
192
|
+
models: read
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
**Existing consumers must add this block to their `deploy.yml`** at the
|
|
196
|
+
workflow or job level. If it's missing, the AI step fails open with a
|
|
197
|
+
`::warning::` and the rest of the workflow continues unaffected.
|
|
198
|
+
|
|
199
|
+
### Rate limits
|
|
200
|
+
|
|
201
|
+
GitHub Models free tier allows **50 `gpt-4o` requests per day** (10 per
|
|
202
|
+
minute). One workflow run = one request. If you run many apps from the
|
|
203
|
+
same GitHub account, or trigger several builds per day, switch to the
|
|
204
|
+
cheaper mini model:
|
|
205
|
+
|
|
206
|
+
```yaml
|
|
207
|
+
- uses: ./.github/actions/swift-app
|
|
208
|
+
with:
|
|
209
|
+
ai-metadata-model: openai/gpt-4o-mini
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
`gpt-4o-mini` has a much higher free-tier quota.
|
|
213
|
+
|
|
214
|
+
### Disabling AI metadata
|
|
215
|
+
|
|
216
|
+
Pass `ai-metadata: 'false'` to skip the AI steps entirely:
|
|
217
|
+
|
|
218
|
+
```yaml
|
|
219
|
+
- uses: ./.github/actions/swift-app
|
|
220
|
+
with:
|
|
221
|
+
ai-metadata: 'false'
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
## Update
|
|
225
|
+
|
|
226
|
+
```bash
|
|
227
|
+
npx --yes gowalk-cicd
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
Overwrites both vendored actions and `.github/workflows/deploy.yml` with the
|
|
231
|
+
latest versions. Because the actions are vendored locally, builds use the
|
|
232
|
+
reviewed copy in your app repository; updates arrive through this npm package.
|
|
233
|
+
|
|
234
|
+
## Override config (rare)
|
|
235
|
+
|
|
236
|
+
Most projects never need this. If auto-detection fails or you have multiple
|
|
237
|
+
schemes, pass inputs in `.github/workflows/deploy.yml`:
|
|
238
|
+
|
|
239
|
+
```yaml
|
|
240
|
+
- uses: ./.github/actions/swift-app
|
|
241
|
+
with:
|
|
242
|
+
scheme: MyAppRelease
|
|
243
|
+
bundle-id: com.example.myapp
|
|
244
|
+
run-tests: 'false'
|
|
245
|
+
uses-non-exempt-encryption: 'false'
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
All inputs are declared in `.github/actions/swift-app/action.yml`. The
|
|
249
|
+
common ones:
|
|
250
|
+
|
|
251
|
+
| Input | Purpose |
|
|
252
|
+
|-------|---------|
|
|
253
|
+
| `project` / `workspace` | Path to `.xcodeproj` or `.xcworkspace` |
|
|
254
|
+
| `scheme` | Xcode scheme to archive |
|
|
255
|
+
| `configuration` | `Release` (default) or custom |
|
|
256
|
+
| `bundle-id` | Override the auto-detected bundle identifier |
|
|
257
|
+
| `team-id` | Override the auto-detected team ID |
|
|
258
|
+
| `app-store-apple-id` | Numeric ASC app ID (override auto-lookup) |
|
|
259
|
+
| `run-tests` | `false` to skip the simulator test stage |
|
|
260
|
+
| `uses-non-exempt-encryption` | Value for `ITSAppUsesNonExemptEncryption` |
|
|
261
|
+
| `archive` | `false` to build-only (PR runs without secrets) |
|
|
262
|
+
| `upload` | `false` to archive but not upload to TestFlight |
|
|
263
|
+
| `app-store-whats-new` | Inline "What's New" text (overrides files) |
|
|
264
|
+
| `ai-metadata` | `false` to disable AI auto-fill of empty ASC metadata |
|
|
265
|
+
| `ai-metadata-model` | GitHub Models model id (default `openai/gpt-4o`) |
|
|
266
|
+
|
|
267
|
+
## First-time app setup
|
|
268
|
+
|
|
269
|
+
The app record must exist in App Store Connect before the first CI upload.
|
|
270
|
+
Create it once in the App Store Connect web interface with the same bundle ID.
|
|
271
|
+
Do not run store deployment tools locally; builds, signing, and uploads remain
|
|
272
|
+
inside GitHub Actions.
|
|
273
|
+
|
|
274
|
+
Once the app exists, all subsequent builds and uploads are fully automated via
|
|
275
|
+
the ASC API key.
|
|
276
|
+
|
|
277
|
+
## Troubleshooting
|
|
278
|
+
|
|
279
|
+
**"No app found for bundle ID"** — the app record doesn't exist yet. Run the
|
|
280
|
+
first-time setup above.
|
|
281
|
+
|
|
282
|
+
**"MARKETING_VERSION is not set"** — the action requires `MARKETING_VERSION`
|
|
283
|
+
to be declared in your target's build settings. Open the target in Xcode →
|
|
284
|
+
Build Settings → Versioning → set `MARKETING_VERSION` (and
|
|
285
|
+
`CURRENT_PROJECT_VERSION`) to `$(MARKETING_VERSION)` /
|
|
286
|
+
`$(CURRENT_PROJECT_VERSION)` respectively.
|
|
287
|
+
|
|
288
|
+
**"You must accept the latest Program License Agreement"** — go to
|
|
289
|
+
developer.apple.com and App Store Connect as the account holder, accept any
|
|
290
|
+
pending agreements, retry.
|
|
291
|
+
|
|
292
|
+
**Upload fails with provisioning errors** — delete any stale profiles named
|
|
293
|
+
`CI-<bundle_id>` on developer.apple.com and re-run; the action will regenerate.
|
|
294
|
+
|
|
295
|
+
## Auto-bumping MARKETING_VERSION
|
|
296
|
+
|
|
297
|
+
When the ASC combined floor (max of pending review, `preReleaseVersions`,
|
|
298
|
+
or builds-via-`preReleaseVersion`) exceeds your project's
|
|
299
|
+
`MARKETING_VERSION`, the action auto-bumps and commits the new value as
|
|
300
|
+
part of the same bot commit that handles cert refresh / autoupdate.
|
|
301
|
+
|
|
302
|
+
Default policy is `rollover` — patch with carry: at `.9` it rolls into
|
|
303
|
+
the next minor (`1.0.9` → `1.1.0`), and at minor=9 it cascades into the
|
|
304
|
+
next major (`1.9.9` → `2.0.0`). Major has no upper limit (`9.9.9` →
|
|
305
|
+
`10.0.0`). This produces the more natural human progression most
|
|
306
|
+
projects want — patch numbers never silently grow past 9.
|
|
307
|
+
|
|
308
|
+
**Four policies** are supported:
|
|
309
|
+
|
|
310
|
+
| Policy | Example bump | When to use |
|
|
311
|
+
|--------|--------------|-------------|
|
|
312
|
+
| `rollover` (default) | `1.0.5` → `1.0.6`; `1.0.9` → `1.1.0` | Natural progression, carry at .9. |
|
|
313
|
+
| `patch` | `1.0.5` → `1.0.6`; `1.0.9` → `1.0.10` | Legacy unbounded patch — pinned for backward compat. |
|
|
314
|
+
| `minor` | `1.0.5` → `1.1.0`; `1.0.9` → `1.1.0` | Projects that ship every release as a minor. |
|
|
315
|
+
| `none` | (fails the build) | Explicit semver control via human bump. |
|
|
316
|
+
|
|
317
|
+
Full rollover behaviour: `1.0.9` → `1.1.0` (patch overflow), `1.9.9`
|
|
318
|
+
→ `2.0.0` (minor cascade), `9.9.9` → `10.0.0` (major no upper limit).
|
|
319
|
+
|
|
320
|
+
**Backward compat:** existing consumers on `0.0.27` that explicitly pin
|
|
321
|
+
`marketing-version-auto-bump: 'patch'` keep their current unbounded
|
|
322
|
+
behavior — the `'patch'` policy is unchanged. The default change from
|
|
323
|
+
`'patch'` → `'rollover'` only affects new installs and consumers that
|
|
324
|
+
do not override the input.
|
|
325
|
+
|
|
326
|
+
**Opt out** via the action input:
|
|
327
|
+
|
|
328
|
+
```yaml
|
|
329
|
+
- uses: ./.github/actions/swift-app
|
|
330
|
+
with:
|
|
331
|
+
marketing-version-auto-bump: 'none'
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
In `'none'` mode, the floor check fails the build and you must bump
|
|
335
|
+
`MARKETING_VERSION` manually before retrying.
|
|
336
|
+
|
|
337
|
+
**Side effect:** the bot commit subject reflects what was changed, e.g.
|
|
338
|
+
`ci: refresh signing identity + bump MARKETING_VERSION [skip ci]`.
|
|
339
|
+
|
|
340
|
+
**Source-of-truth resolution.** The auto-bump writes the new value
|
|
341
|
+
into the file your project actually reads from, in this order:
|
|
342
|
+
|
|
343
|
+
1. **xcodegen `project.yml`** (preferred when present): regex-rewrite
|
|
344
|
+
of the `MARKETING_VERSION:` key, preserving formatting. The
|
|
345
|
+
generated `*.xcodeproj` is regenerated on every build, so editing
|
|
346
|
+
it directly would lose the bump.
|
|
347
|
+
2. **`*.xcconfig`** sitting alongside the project: handles non-xcodegen
|
|
348
|
+
projects that hoist `MARKETING_VERSION` into xcconfig.
|
|
349
|
+
3. **`*.xcodeproj/project.pbxproj`**: only when no xcodegen spec is
|
|
350
|
+
present.
|
|
351
|
+
4. **`Info.plist` `CFBundleShortVersionString`**: last-ditch fallback.
|
|
352
|
+
|
|
353
|
+
If your project uses xcodegen but `MARKETING_VERSION` lives somewhere
|
|
354
|
+
not in `project.yml` or `.xcconfig`, the action emits a `::warning::`
|
|
355
|
+
and falls back to fail-on-floor (refusing to silently edit the
|
|
356
|
+
generated pbxproj). Either move `MARKETING_VERSION` under
|
|
357
|
+
`settings.base` in `project.yml`, or pin
|
|
358
|
+
`marketing-version-auto-bump: 'none'` and bump manually.
|
|
359
|
+
|
|
360
|
+
## Auto-updates
|
|
361
|
+
|
|
362
|
+
The vendored action ships with a per-run autoupdate check. On every
|
|
363
|
+
push to your default branch, the action queries npm for the latest
|
|
364
|
+
`gowalk-cicd`, compares against the local marker at
|
|
365
|
+
`.github/actions/swift-app/.daemux-version`, and if newer, re-vendors
|
|
366
|
+
the package via `npx --yes` and commits the refreshed action files
|
|
367
|
+
back (under both `.github/actions/swift-app/` and
|
|
368
|
+
`.github/actions/android-app/`).
|
|
369
|
+
`.github/workflows/deploy.yml` is NEVER auto-committed — see
|
|
370
|
+
"deploy.yml is not auto-updated" below.
|
|
371
|
+
|
|
372
|
+
| Aspect | Behaviour |
|
|
373
|
+
|--------|-----------|
|
|
374
|
+
| Trigger | Push to default branch only (PR / branch runs do nothing) |
|
|
375
|
+
| Lag | One run — the *next* push after a new release picks up the update |
|
|
376
|
+
| Suppression | `[skip ci]` in the commit subject + `paths-ignore` for both action directories |
|
|
377
|
+
| Combined commit | Cert refresh + autoupdate share a single commit when both fire in the same run |
|
|
378
|
+
| Failure mode | Non-fatal: a failed `npm view` or `npx` emits `::warning::` and the build continues |
|
|
379
|
+
|
|
380
|
+
The shipped `deploy.yml` template already includes the required
|
|
381
|
+
`paths-ignore` entries for both action directories. If you have an
|
|
382
|
+
older `deploy.yml` checked in, add both entries to prevent the
|
|
383
|
+
autoupdate commit-back from re-triggering the workflow.
|
|
384
|
+
|
|
385
|
+
### deploy.yml is not auto-updated
|
|
386
|
+
|
|
387
|
+
**deploy.yml is NOT auto-updated.** GitHub's `GITHUB_TOKEN` cannot
|
|
388
|
+
push changes to workflow files (`.github/workflows/*.yml`) regardless
|
|
389
|
+
of `contents: write` — this is a built-in safeguard against CI
|
|
390
|
+
self-modification. When a new version of `gowalk-cicd`
|
|
391
|
+
requires `deploy.yml` schema changes (e.g., new permissions, new
|
|
392
|
+
paths-ignore entries), the action's release notes will call this out
|
|
393
|
+
and you must run `npx --yes gowalk-cicd` manually once to
|
|
394
|
+
sync your `deploy.yml`. Existing deploy.yml stays untouched on every
|
|
395
|
+
auto-update cycle until you do.
|
|
396
|
+
|
|
397
|
+
### Opt out
|
|
398
|
+
|
|
399
|
+
Pin the vendored copy by passing `auto-update: 'false'` to the action:
|
|
400
|
+
|
|
401
|
+
```yaml
|
|
402
|
+
- uses: ./.github/actions/swift-app
|
|
403
|
+
with:
|
|
404
|
+
auto-update: 'false'
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
### First-run bootstrap
|
|
408
|
+
|
|
409
|
+
The marker is written by `npx gowalk-cicd` itself. A repo
|
|
410
|
+
without a marker (e.g. an old hand-vendored copy) will be treated as
|
|
411
|
+
out-of-date on its first run, after which updates land incrementally.
|
|
412
|
+
Run `npx --yes gowalk-cicd` once locally if you want to skip
|
|
413
|
+
even that first auto-bootstrap.
|
|
414
|
+
|
|
415
|
+
## License
|
|
416
|
+
|
|
417
|
+
MIT
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
1.0.0
|