gowalk-cicd 1.0.36 → 1.0.38

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 CHANGED
@@ -25,6 +25,8 @@ gowalk-cicd/ ← repo root IS the gowalk-cicd package
25
25
  ├── bin/cli.mjs ← CLI entrypoint (parses flags, calls runInstall)
26
26
  ├── src/install.mjs ← copy logic, gitignore-check, summary print
27
27
  ├── templates/deploy.yml ← dual-platform workflow copied verbatim
28
+ ├── backend-action/ ← backend deploy action + remote host script
29
+ ├── templates/deploy-backend.yml ← backend workflow copied when compose exists
28
30
  ├── scripts/auto-version.mjs ← patch-bump-from-npm used by the publish workflow
29
31
  ├── .github/workflows/publish.yml ← auto-publish to npm on push to main
30
32
  ├── README.md ← consumer docs
@@ -96,6 +98,9 @@ node /path/to/gowalk-cicd/bin/cli.mjs
96
98
  materializes `ASC_KEY_P8` + its ID/issuer and the Android keystore,
97
99
  properties and Play service account into an ephemeral runner checkout.
98
100
  Tracked `creds/` files remain a backward-compatible fallback.
101
+ - Backend runtime credentials use the single encrypted `BACKEND_RUNTIME_ENV`
102
+ secret. The action writes it to host-side `.runtime.env` with mode 0600 and
103
+ supplies it to Compose after the generated `.env`; never print its content.
99
104
  - The Android action serves two build systems. `android_config.project_kind()`
100
105
  is the only place that decides which; every downstream step branches on the
101
106
  `project_kind` output rather than re-sniffing the repo. Flutter wins the tie
package/README.md CHANGED
@@ -21,12 +21,6 @@ Writes three things into your repo:
21
21
 
22
22
  Re-run the same command anytime to pull the latest version.
23
23
 
24
- Flutter map apps may also provide `GOOGLE_MAPS_IOS_KEY` and
25
- `GOOGLE_MAPS_ANDROID_KEY` as Actions secrets. When present, the workflow writes
26
- the iOS key to `ios/Flutter/MapsKeys.xcconfig` and the Android key to
27
- `android/app/src/main/res/values/google_maps_api.xml` as mode-restricted runner
28
- files. Apps without either secret are unchanged.
29
-
30
24
  ## iOS credentials
31
25
 
32
26
  1. Create an **App Store Connect API key** with the **App Manager** role
@@ -67,6 +61,9 @@ Requirements on the consumer repo:
67
61
  never bake secrets in. A generated `.env` (with `POSTGRES_PASSWORD`) is created
68
62
  on the host on first deploy and preserved across deploys (rsync excludes it).
69
63
  - Repo **secret** `BACKEND_DEPLOY_SSH_KEY` (a private key with access to the host).
64
+ - Repo **secret** `BACKEND_RUNTIME_ENV` (optional): newline-delimited `KEY=value`
65
+ runtime secrets. The action writes it as mode 0600 on the host and passes it
66
+ to Compose without copying values into the repository or action log.
70
67
  - Repo **variables** (optional): `BACKEND_API_DOMAIN` (its DNS A record must point
71
68
  at the host for the cert; empty = deploy the container only, skip nginx/cert),
72
69
  `BACKEND_APP_NAME` (defaults to the repo name), `BACKEND_HEALTH_PATH`
@@ -190,11 +187,11 @@ sent for review from the Google Play Console UI.
190
187
  ```
191
188
 
192
189
  That fails the whole upload, so the bundle you just built never reaches Play at
193
- all. After an ordinary fresh-edit retry, the final retry re-uploads with
194
- `changesNotSentForReview`, which lands the bundle and leaves it waiting, then
195
- emits a `::warning::` naming the package. The delivery system can then complete
196
- the console-only **Send changes for review** action through the app's leased
197
- AdsPower profile.
190
+ all. The retry re-uploads with `changesNotSentForReview`, which lands the bundle
191
+ and leaves it waiting, then emits a `::warning::` naming the package. Someone
192
+ then presses **Send changes for review** on that app's Publishing overview page.
193
+ There is no API for that press it is the one step Google deliberately reserves
194
+ for a human, and no amount of tooling removes it.
198
195
 
199
196
  In the ordinary case the first upload submits normally and the retry is skipped.
200
197
 
@@ -210,10 +207,8 @@ long enough outlives one:
210
207
 
211
208
  Neither says anything about the build, which is signed and retained by that
212
209
  point. The upload step therefore runs with `continue-on-error` and a second
213
- ordinary attempt follows when the first fails. This preserves automatic review
214
- submission while opening a fresh edit. Only when that retry also fails does a
215
- third attempt use `changesNotSentForReview`; setting that flag on an ordinary
216
- app is itself a Play validation error.
210
+ attempt follows when the first fails a fresh edit succeeds. Both attempts
211
+ failing still fails the job.
217
212
 
218
213
  One caveat this makes explicit: **nothing else should hold an edit open on the
219
214
  same package while a deploy runs.** A script that polls `edits.insert` /
@@ -1 +1 @@
1
- 1.0.36
1
+ 1.0.38
@@ -1 +1 @@
1
- 1.0.36
1
+ 1.0.38
@@ -1 +1 @@
1
- 1.0.36
1
+ 1.0.38
@@ -39,6 +39,13 @@ inputs:
39
39
  description: Email for the Let's Encrypt registration.
40
40
  required: false
41
41
  default: "admin@gowalk.com"
42
+ runtime-env:
43
+ description: >
44
+ Optional newline-delimited KEY=value secrets written to a mode-0600
45
+ host-side env file and supplied to Docker Compose. Pass from one
46
+ encrypted secret such as BACKEND_RUNTIME_ENV; never commit the values.
47
+ required: false
48
+ default: ""
42
49
 
43
50
  runs:
44
51
  using: composite
@@ -83,10 +90,17 @@ runs:
83
90
 
84
91
  - name: Deploy on the host
85
92
  shell: bash
93
+ env:
94
+ RUNTIME_ENV: ${{ inputs.runtime-env }}
86
95
  run: |
87
96
  set -euo pipefail
88
97
  SSH="ssh -i ~/.ssh/backend_deploy -o IdentitiesOnly=yes"
89
98
  DEST="/opt/gowalk-backends/${{ inputs.app-name }}"
99
+ if [ -n "${RUNTIME_ENV:-}" ]; then
100
+ printf '%s\n' "$RUNTIME_ENV" | $SSH \
101
+ "${{ inputs.ssh-user }}@${{ inputs.host }}" \
102
+ "umask 077; cat > '$DEST/.runtime.env'"
103
+ fi
90
104
  $SSH "${{ inputs.ssh-user }}@${{ inputs.host }}" \
91
105
  "bash '$DEST/.remote_deploy.sh' \
92
106
  '${{ inputs.app-name }}' '${{ inputs.api-domain }}' \
@@ -23,15 +23,22 @@ if [ ! -f .env ]; then
23
23
  chmod 600 .env
24
24
  fi
25
25
 
26
+ COMPOSE_ENV=(--env-file .env)
27
+ if [ -s .runtime.env ]; then
28
+ chmod 600 .runtime.env
29
+ COMPOSE_ENV+=(--env-file .runtime.env)
30
+ fi
31
+
26
32
  # ── bring the stack up
27
33
  log "docker compose up -d --build"
28
- docker compose --project-name "$APP" up -d --build --remove-orphans
34
+ docker compose "${COMPOSE_ENV[@]}" --project-name "$APP" \
35
+ up -d --build --remove-orphans
29
36
 
30
37
  # ── discover the host port the service published on 127.0.0.1.
31
38
  # The text `ps` PORTS column reliably shows `127.0.0.1:<port>-><target>/tcp`.
32
39
  detect_port() {
33
40
  # Capture ONLY the published port after 127.0.0.1: (not the IP's own digits).
34
- docker compose --project-name "$APP" ps 2>/dev/null \
41
+ docker compose "${COMPOSE_ENV[@]}" --project-name "$APP" ps 2>/dev/null \
35
42
  | sed -nE 's/.*127\.0\.0\.1:([0-9]+)->.*/\1/p' | head -1
36
43
  }
37
44
  PORT="$(detect_port || true)"
@@ -51,8 +58,8 @@ for i in $(seq 1 90); do
51
58
  done
52
59
  if [ -z "$HEALTHY" ]; then
53
60
  log "ERROR: container never became healthy on :$PORT$HEALTH"
54
- docker compose --project-name "$APP" ps
55
- docker compose --project-name "$APP" logs --tail 60
61
+ docker compose "${COMPOSE_ENV[@]}" --project-name "$APP" ps
62
+ docker compose "${COMPOSE_ENV[@]}" --project-name "$APP" logs --tail 60
56
63
  exit 1
57
64
  fi
58
65
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gowalk-cicd",
3
- "version": "1.0.36",
3
+ "version": "1.0.38",
4
4
  "description": "Zero-config GitHub Actions delivery for iOS TestFlight and Android Google Play.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -39,3 +39,4 @@ jobs:
39
39
  api-domain: ${{ vars.BACKEND_API_DOMAIN }}
40
40
  backend-dir: ${{ steps.dir.outputs.path }}
41
41
  health-path: ${{ vars.BACKEND_HEALTH_PATH || '/health' }}
42
+ runtime-env: ${{ secrets.BACKEND_RUNTIME_ENV }}
@@ -57,14 +57,6 @@ jobs:
57
57
  else
58
58
  echo "enabled=false" >> "$GITHUB_OUTPUT"
59
59
  fi
60
- - name: Materialize restricted iOS Maps key
61
- if: ${{ steps.flutter.outputs.enabled == 'true' && secrets.GOOGLE_MAPS_IOS_KEY != '' }}
62
- shell: bash
63
- env:
64
- GOOGLE_MAPS_IOS_KEY: ${{ secrets.GOOGLE_MAPS_IOS_KEY }}
65
- run: |
66
- umask 077
67
- printf 'GOOGLE_MAPS_IOS_KEY=%s\n' "$GOOGLE_MAPS_IOS_KEY" > ios/Flutter/MapsKeys.xcconfig
68
60
  - name: Set up Flutter
69
61
  if: ${{ steps.flutter.outputs.enabled == 'true' }}
70
62
  uses: subosito/flutter-action@1a449444c387b1966244ae4d4f8c696479add0b2 # v2.23.0
@@ -314,21 +306,6 @@ jobs:
314
306
  flutter pub get
315
307
  flutter gen-l10n
316
308
  fi
317
- - name: Materialize restricted Android Maps key
318
- if: >-
319
- ${{ steps.mode.outputs.mode == 'local'
320
- && steps.flutter.outputs.enabled == 'true'
321
- && secrets.GOOGLE_MAPS_ANDROID_KEY != '' }}
322
- shell: bash
323
- env:
324
- GOOGLE_MAPS_ANDROID_KEY: ${{ secrets.GOOGLE_MAPS_ANDROID_KEY }}
325
- run: |
326
- umask 077
327
- mkdir -p android/app/src/main/res/values
328
- printf '%s\n' '<?xml version="1.0" encoding="utf-8"?>' \
329
- '<resources>' \
330
- " <string name=\"google_maps_key\" translatable=\"false\">$GOOGLE_MAPS_ANDROID_KEY</string>" \
331
- '</resources>' > android/app/src/main/res/values/google_maps_api.xml
332
309
  - name: Build and sign Android release
333
310
  id: android
334
311
  if: ${{ steps.mode.outputs.mode == 'local' }}
@@ -386,39 +363,19 @@ jobs:
386
363
  ${{ vars.GOOGLE_PLAY_STATUS == 'inProgress' && (vars.GOOGLE_PLAY_USER_FRACTION || '0.2') || '' }}
387
364
  whatsNewDirectory: ${{ hashFiles('distribution/whatsnew/**') != '' && 'distribution/whatsnew' || '' }}
388
365
 
389
- # A failed action does not expose a machine-readable Play error code. Retry once
390
- # with the ordinary review mode first: that opens a fresh edit and repairs an edit
391
- # that expired during a slow upload without changing submission semantics.
392
- - name: Retry Google Play upload with a fresh edit
393
- id: play-upload-fresh-retry
394
- continue-on-error: true
395
- if: >-
396
- ${{ steps.mode.outputs.mode == 'local'
397
- && steps.play.outputs.ready == 'true'
398
- && steps.play-upload.outcome == 'failure' }}
399
- uses: r0adkll/upload-google-play@e738b9dd8f2476ea806d921b64aacd24f34515a5 # v1.1.5
400
- with:
401
- serviceAccountJson: ${{ steps.android.outputs.play-service-account }}
402
- packageName: ${{ steps.android.outputs.package-name }}
403
- releaseFiles: ${{ steps.android.outputs.bundle-path }}
404
- tracks: ${{ vars.GOOGLE_PLAY_TRACK || 'internal' }}
405
- status: ${{ vars.GOOGLE_PLAY_STATUS || 'completed' }}
406
- userFraction: >-
407
- ${{ vars.GOOGLE_PLAY_STATUS == 'inProgress' && (vars.GOOGLE_PLAY_USER_FRACTION || '0.2') || '' }}
408
- whatsNewDirectory: >-
409
- ${{ hashFiles('distribution/whatsnew/**') != '' && 'distribution/whatsnew' || '' }}
410
-
411
- # If the ordinary fresh-edit retry also fails, Play may be requiring the
412
- # changesNotSentForReview mode used after a policy rejection. A third, fresh edit
413
- # is intentionally reserved for that case. Setting the flag on the edit-expiry
414
- # retry is invalid for apps whose changes are submitted automatically.
366
+ # The retry serves two different failures, and both need a fresh edit:
367
+ # * the edit expired mid-upload (a large bundle on a slow runner), and
368
+ # * Play refused to auto-submit: "Changes cannot be sent for review
369
+ # automatically. Please set changesNotSentForReview to true." Play returns
370
+ # this once an app has changes a human must submit — after a policy
371
+ # rejection, for instance — and it fails the whole upload, so the built
372
+ # bundle never lands at all.
415
373
  - name: Retry Google Play upload without auto-submit
416
- id: play-upload-review-retry
374
+ id: play-upload-retry
417
375
  if: >-
418
376
  ${{ steps.mode.outputs.mode == 'local'
419
377
  && steps.play.outputs.ready == 'true'
420
- && steps.play-upload.outcome == 'failure'
421
- && steps.play-upload-fresh-retry.outcome == 'failure' }}
378
+ && steps.play-upload.outcome == 'failure' }}
422
379
  uses: r0adkll/upload-google-play@e738b9dd8f2476ea806d921b64aacd24f34515a5 # v1.1.5
423
380
  with:
424
381
  serviceAccountJson: ${{ steps.android.outputs.play-service-account }}
@@ -433,7 +390,7 @@ jobs:
433
390
  changesNotSentForReview: true
434
391
 
435
392
  - name: Say what still needs a human
436
- if: ${{ always() && steps.play-upload-review-retry.outcome == 'success' }}
393
+ if: ${{ always() && steps.play-upload-retry.outcome == 'success' }}
437
394
  shell: bash
438
395
  run: |
439
396
  echo "::warning::Play would not accept this release for review automatically, so it was \