gowalk-cicd 1.0.37 → 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.37
1
+ 1.0.38
@@ -1 +1 @@
1
- 1.0.37
1
+ 1.0.38
@@ -1 +1 @@
1
- 1.0.37
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.37",
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,15 +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' }}
62
- shell: bash
63
- env:
64
- GOOGLE_MAPS_IOS_KEY: ${{ secrets.GOOGLE_MAPS_IOS_KEY }}
65
- run: |
66
- [ -n "${GOOGLE_MAPS_IOS_KEY:-}" ] || exit 0
67
- umask 077
68
- printf 'GOOGLE_MAPS_IOS_KEY=%s\n' "$GOOGLE_MAPS_IOS_KEY" > ios/Flutter/MapsKeys.xcconfig
69
60
  - name: Set up Flutter
70
61
  if: ${{ steps.flutter.outputs.enabled == 'true' }}
71
62
  uses: subosito/flutter-action@1a449444c387b1966244ae4d4f8c696479add0b2 # v2.23.0
@@ -315,21 +306,6 @@ jobs:
315
306
  flutter pub get
316
307
  flutter gen-l10n
317
308
  fi
318
- - name: Materialize restricted Android Maps key
319
- if: >-
320
- ${{ steps.mode.outputs.mode == 'local'
321
- && steps.flutter.outputs.enabled == 'true' }}
322
- shell: bash
323
- env:
324
- GOOGLE_MAPS_ANDROID_KEY: ${{ secrets.GOOGLE_MAPS_ANDROID_KEY }}
325
- run: |
326
- [ -n "${GOOGLE_MAPS_ANDROID_KEY:-}" ] || exit 0
327
- umask 077
328
- mkdir -p android/app/src/main/res/values
329
- printf '%s\n' '<?xml version="1.0" encoding="utf-8"?>' \
330
- '<resources>' \
331
- " <string name=\"google_maps_key\" translatable=\"false\">$GOOGLE_MAPS_ANDROID_KEY</string>" \
332
- '</resources>' > android/app/src/main/res/values/google_maps_api.xml
333
309
  - name: Build and sign Android release
334
310
  id: android
335
311
  if: ${{ steps.mode.outputs.mode == 'local' }}
@@ -387,39 +363,19 @@ jobs:
387
363
  ${{ vars.GOOGLE_PLAY_STATUS == 'inProgress' && (vars.GOOGLE_PLAY_USER_FRACTION || '0.2') || '' }}
388
364
  whatsNewDirectory: ${{ hashFiles('distribution/whatsnew/**') != '' && 'distribution/whatsnew' || '' }}
389
365
 
390
- # A failed action does not expose a machine-readable Play error code. Retry once
391
- # with the ordinary review mode first: that opens a fresh edit and repairs an edit
392
- # that expired during a slow upload without changing submission semantics.
393
- - name: Retry Google Play upload with a fresh edit
394
- id: play-upload-fresh-retry
395
- continue-on-error: true
396
- if: >-
397
- ${{ steps.mode.outputs.mode == 'local'
398
- && steps.play.outputs.ready == 'true'
399
- && steps.play-upload.outcome == 'failure' }}
400
- uses: r0adkll/upload-google-play@e738b9dd8f2476ea806d921b64aacd24f34515a5 # v1.1.5
401
- with:
402
- serviceAccountJson: ${{ steps.android.outputs.play-service-account }}
403
- packageName: ${{ steps.android.outputs.package-name }}
404
- releaseFiles: ${{ steps.android.outputs.bundle-path }}
405
- tracks: ${{ vars.GOOGLE_PLAY_TRACK || 'internal' }}
406
- status: ${{ vars.GOOGLE_PLAY_STATUS || 'completed' }}
407
- userFraction: >-
408
- ${{ vars.GOOGLE_PLAY_STATUS == 'inProgress' && (vars.GOOGLE_PLAY_USER_FRACTION || '0.2') || '' }}
409
- whatsNewDirectory: >-
410
- ${{ hashFiles('distribution/whatsnew/**') != '' && 'distribution/whatsnew' || '' }}
411
-
412
- # If the ordinary fresh-edit retry also fails, Play may be requiring the
413
- # changesNotSentForReview mode used after a policy rejection. A third, fresh edit
414
- # is intentionally reserved for that case. Setting the flag on the edit-expiry
415
- # 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.
416
373
  - name: Retry Google Play upload without auto-submit
417
- id: play-upload-review-retry
374
+ id: play-upload-retry
418
375
  if: >-
419
376
  ${{ steps.mode.outputs.mode == 'local'
420
377
  && steps.play.outputs.ready == 'true'
421
- && steps.play-upload.outcome == 'failure'
422
- && steps.play-upload-fresh-retry.outcome == 'failure' }}
378
+ && steps.play-upload.outcome == 'failure' }}
423
379
  uses: r0adkll/upload-google-play@e738b9dd8f2476ea806d921b64aacd24f34515a5 # v1.1.5
424
380
  with:
425
381
  serviceAccountJson: ${{ steps.android.outputs.play-service-account }}
@@ -434,7 +390,7 @@ jobs:
434
390
  changesNotSentForReview: true
435
391
 
436
392
  - name: Say what still needs a human
437
- if: ${{ always() && steps.play-upload-review-retry.outcome == 'success' }}
393
+ if: ${{ always() && steps.play-upload-retry.outcome == 'success' }}
438
394
  shell: bash
439
395
  run: |
440
396
  echo "::warning::Play would not accept this release for review automatically, so it was \