gowalk-cicd 1.0.29 → 1.0.30

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
@@ -92,6 +92,10 @@ node /path/to/gowalk-cicd/bin/cli.mjs
92
92
  Detect and warn, tell the user to remove lines manually.
93
93
  - Store API calls belong in GitHub Actions. Local tests may build/sign but must
94
94
  never connect to App Store Connect or Google Play.
95
+ - Signing material should use encrypted Actions secrets. `deploy.yml`
96
+ materializes `ASC_KEY_P8` + its ID/issuer and the Android keystore,
97
+ properties and Play service account into an ephemeral runner checkout.
98
+ Tracked `creds/` files remain a backward-compatible fallback.
95
99
  - The Android action serves two build systems. `android_config.project_kind()`
96
100
  is the only place that decides which; every downstream step branches on the
97
101
  `project_kind` output rather than re-sniffing the repo. Flutter wins the tie
@@ -1 +1 @@
1
- 1.0.29
1
+ 1.0.30
@@ -1 +1 @@
1
- 1.0.29
1
+ 1.0.30
@@ -1 +1 @@
1
- 1.0.29
1
+ 1.0.30
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gowalk-cicd",
3
- "version": "1.0.29",
3
+ "version": "1.0.30",
4
4
  "description": "Zero-config GitHub Actions delivery for iOS TestFlight and Android Google Play.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/install.mjs CHANGED
@@ -16,7 +16,7 @@ import {
16
16
  statSync,
17
17
  writeFileSync,
18
18
  } from 'node:fs';
19
- import { join, relative, resolve } from 'node:path';
19
+ import { join, resolve } from 'node:path';
20
20
  import { fileURLToPath } from 'node:url';
21
21
  import { dirname } from 'node:path';
22
22
 
@@ -182,29 +182,28 @@ function checkGitignore(repoRoot) {
182
182
  function printGitignoreWarning(result) {
183
183
  if (!result.exists || result.offenders.length === 0) return;
184
184
  console.log('');
185
- console.log('WARNING: .gitignore excludes required CI credentials.');
185
+ console.log('NOTICE: .gitignore excludes checkout credentials.');
186
186
  console.log(` File: ${result.path}`);
187
187
  for (const { lineNumber, text } of result.offenders) {
188
188
  console.log(` Line ${lineNumber}: ${text}`);
189
189
  }
190
190
  console.log('');
191
- console.log(' The actions read Apple and Android keys from creds/ in the checkout.');
192
- console.log(' Remove those lines from .gitignore manually, then commit the .p8 file.');
193
- console.log(' (Your repo MUST be private — never do this in a public repo.)');
191
+ console.log(' Keep the ignore when using encrypted Actions secrets (recommended).');
192
+ console.log(' Otherwise the actions still support credentials already tracked in creds/.');
194
193
  }
195
194
 
196
- function printSummary(repoRoot) {
195
+ function printSummary() {
197
196
  console.log('');
198
197
  console.log('Done.');
199
198
  console.log('');
200
199
  console.log('Next steps:');
201
- console.log(` 1. Place your ASC API key:`);
202
- console.log(` ${join(relative(process.cwd(), repoRoot) || '.', 'creds', 'AuthKey_<KEY_ID>_Issuer_<UUID>.p8')}`);
203
- console.log(` (filename must include the 10-char KEY_ID and the issuer UUID)`);
204
- console.log(` 2. Add creds/android-upload-key.jks and creds/android-signing.properties.`);
205
- console.log(` 3. Add a Google Play service-account JSON anywhere under creds/.`);
206
- console.log(` 4. Ensure the repo is PRIVATE; these long-lived credentials must remain private.`);
207
- console.log(` 5. Commit and push to main. GitHub Actions builds both platforms.`);
200
+ console.log(' Recommended: configure encrypted Actions secrets:');
201
+ console.log(' ASC_KEY_P8, ASC_KEY_ID, ASC_ISSUER_ID');
202
+ console.log(' ANDROID_UPLOAD_KEY_BASE64, ANDROID_SIGNING_PROPERTIES');
203
+ console.log(' GOOGLE_PLAY_SERVICE_ACCOUNT_JSON');
204
+ console.log(' The workflow materializes them with mode 0600 only on its ephemeral runner.');
205
+ console.log(' Existing checkout credentials remain a backward-compatible fallback.');
206
+ console.log(' Push to main after configuring the secrets; GitHub Actions builds both platforms.');
208
207
  console.log('');
209
208
  console.log('Re-run this installer anytime to update the vendored action:');
210
209
  console.log(' npx --yes gowalk-cicd');
@@ -242,5 +241,5 @@ export async function runInstall({ dryRun = false } = {}) {
242
241
  return;
243
242
  }
244
243
 
245
- printSummary(repoRoot);
244
+ printSummary();
246
245
  }
@@ -35,6 +35,19 @@ jobs:
35
35
  with:
36
36
  fetch-depth: 0
37
37
  persist-credentials: true
38
+ - name: Materialize encrypted iOS credentials
39
+ shell: bash
40
+ env:
41
+ CI_ASC_KEY_P8: ${{ secrets.ASC_KEY_P8 }}
42
+ CI_ASC_KEY_ID: ${{ secrets.ASC_KEY_ID }}
43
+ CI_ASC_ISSUER_ID: ${{ secrets.ASC_ISSUER_ID }}
44
+ run: |
45
+ [ -n "${CI_ASC_KEY_P8:-}" ] || exit 0
46
+ : "${CI_ASC_KEY_ID:?ASC_KEY_ID secret is required with ASC_KEY_P8}"
47
+ : "${CI_ASC_ISSUER_ID:?ASC_ISSUER_ID secret is required with ASC_KEY_P8}"
48
+ umask 077
49
+ mkdir -p creds
50
+ printf '%s' "$CI_ASC_KEY_P8" > "creds/AuthKey_${CI_ASC_KEY_ID}_Issuer_${CI_ASC_ISSUER_ID}.p8"
38
51
  - name: Detect Flutter
39
52
  id: flutter
40
53
  shell: bash
@@ -74,7 +87,8 @@ jobs:
74
87
  exit 0
75
88
  fi
76
89
  echo "::add-mask::$GIT_PRIVATE_TOKEN"
77
- git config --global url."https://x-access-token:${GIT_PRIVATE_TOKEN}@github.com/".insteadOf "https://github.com/"
90
+ git config --global \
91
+ url."https://x-access-token:${GIT_PRIVATE_TOKEN}@github.com/".insteadOf "https://github.com/"
78
92
  echo "Configured authenticated github.com remotes for pub."
79
93
  - name: Prepare Flutter iOS project
80
94
  if: ${{ steps.flutter.outputs.enabled == 'true' }}
@@ -107,6 +121,22 @@ jobs:
107
121
  timeout-minutes: 45
108
122
  steps:
109
123
  - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
124
+ - name: Materialize encrypted Android credentials
125
+ shell: bash
126
+ env:
127
+ CI_ANDROID_KEYSTORE: ${{ secrets.ANDROID_UPLOAD_KEY_BASE64 }}
128
+ CI_ANDROID_PROPERTIES: ${{ secrets.ANDROID_SIGNING_PROPERTIES }}
129
+ CI_PLAY_ACCOUNT: ${{ secrets.GOOGLE_PLAY_SERVICE_ACCOUNT_JSON }}
130
+ run: |
131
+ [ -n "${CI_ANDROID_KEYSTORE:-}${CI_ANDROID_PROPERTIES:-}${CI_PLAY_ACCOUNT:-}" ] || exit 0
132
+ : "${CI_ANDROID_KEYSTORE:?ANDROID_UPLOAD_KEY_BASE64 secret is required}"
133
+ : "${CI_ANDROID_PROPERTIES:?ANDROID_SIGNING_PROPERTIES secret is required}"
134
+ : "${CI_PLAY_ACCOUNT:?GOOGLE_PLAY_SERVICE_ACCOUNT_JSON secret is required}"
135
+ umask 077
136
+ mkdir -p creds
137
+ printf '%s' "$CI_ANDROID_KEYSTORE" | base64 --decode > creds/android-upload-key.jks
138
+ printf '%s' "$CI_ANDROID_PROPERTIES" > creds/android-signing.properties
139
+ printf '%s' "$CI_PLAY_ACCOUNT" > creds/play-service-account.json
110
140
  # Two Android delivery modes:
111
141
  # * bitrise — the upload key + Play service account live in Bitrise,
112
142
  # not the repo, so the GitHub runner cannot sign. Drop a
@@ -119,8 +149,9 @@ jobs:
119
149
  id: mode
120
150
  shell: bash
121
151
  run: |
122
- if [ -f creds/bitrise.json ] && \
123
- [ "$(python3 -c "import json,sys;print(json.load(open('creds/bitrise.json')).get('enabled',False))" 2>/dev/null)" = "True" ]; then
152
+ bitrise_enabled=$(python3 -c \
153
+ "import json;print(json.load(open('creds/bitrise.json')).get('enabled',False))" 2>/dev/null || true)
154
+ if [ -f creds/bitrise.json ] && [ "$bitrise_enabled" = "True" ]; then
124
155
  echo "mode=bitrise" >> "$GITHUB_OUTPUT"
125
156
  else
126
157
  echo "mode=local" >> "$GITHUB_OUTPUT"
@@ -225,7 +256,8 @@ jobs:
225
256
  exit 0
226
257
  fi
227
258
  echo "::add-mask::$GIT_PRIVATE_TOKEN"
228
- git config --global url."https://x-access-token:${GIT_PRIVATE_TOKEN}@github.com/".insteadOf "https://github.com/"
259
+ git config --global \
260
+ url."https://x-access-token:${GIT_PRIVATE_TOKEN}@github.com/".insteadOf "https://github.com/"
229
261
  echo "Configured authenticated github.com remotes for pub."
230
262
  # A Gradle daemon sized for a workstation does not fit a CI runner. Asking
231
263
  # for -Xmx8G with a 4G metaspace reserves 12 GB before the Kotlin compile
@@ -322,13 +354,8 @@ jobs:
322
354
  releaseFiles: ${{ steps.android.outputs.bundle-path }}
323
355
  tracks: ${{ vars.GOOGLE_PLAY_TRACK || 'internal' }}
324
356
  status: ${{ vars.GOOGLE_PLAY_STATUS || 'completed' }}
325
- # Play rejects an 'inProgress' release that does not declare how much of
326
- # the audience it reaches. Only send the fraction in that mode: passing it
327
- # alongside status 'completed' is an error rather than a no-op.
328
- userFraction: ${{ vars.GOOGLE_PLAY_STATUS == 'inProgress' && (vars.GOOGLE_PLAY_USER_FRACTION || '0.2') || '' }}
329
- # Localized release notes: commit distribution/whatsnew/whatsnew-<bcp47>
330
- # files (e.g. whatsnew-en-US, whatsnew-de-DE) and they ship with every
331
- # release. Empty when the directory is absent — the action skips it.
357
+ userFraction: >-
358
+ ${{ vars.GOOGLE_PLAY_STATUS == 'inProgress' && (vars.GOOGLE_PLAY_USER_FRACTION || '0.2') || '' }}
332
359
  whatsNewDirectory: ${{ hashFiles('distribution/whatsnew/**') != '' && 'distribution/whatsnew' || '' }}
333
360
 
334
361
  # The retry serves two different failures, and both need a fresh edit:
@@ -338,14 +365,12 @@ jobs:
338
365
  # this once an app has changes a human must submit — after a policy
339
366
  # rejection, for instance — and it fails the whole upload, so the built
340
367
  # bundle never lands at all.
341
- # Retrying with changesNotSentForReview gets the bundle onto Play either way.
342
- # In the ordinary case the first attempt already submitted it and this is
343
- # skipped; in the refused case the bundle is uploaded and waits for someone to
344
- # press "Send changes for review" in Play Console, which is the only thing
345
- # Google will not let a tool do.
346
368
  - name: Retry Google Play upload without auto-submit
347
369
  id: play-upload-retry
348
- if: ${{ steps.mode.outputs.mode == 'local' && steps.play.outputs.ready == 'true' && steps.play-upload.outcome == 'failure' }}
370
+ if: >-
371
+ ${{ steps.mode.outputs.mode == 'local'
372
+ && steps.play.outputs.ready == 'true'
373
+ && steps.play-upload.outcome == 'failure' }}
349
374
  uses: r0adkll/upload-google-play@e738b9dd8f2476ea806d921b64aacd24f34515a5 # v1.1.5
350
375
  with:
351
376
  serviceAccountJson: ${{ steps.android.outputs.play-service-account }}
@@ -353,8 +378,10 @@ jobs:
353
378
  releaseFiles: ${{ steps.android.outputs.bundle-path }}
354
379
  tracks: ${{ vars.GOOGLE_PLAY_TRACK || 'internal' }}
355
380
  status: ${{ vars.GOOGLE_PLAY_STATUS || 'completed' }}
356
- userFraction: ${{ vars.GOOGLE_PLAY_STATUS == 'inProgress' && (vars.GOOGLE_PLAY_USER_FRACTION || '0.2') || '' }}
357
- whatsNewDirectory: ${{ hashFiles('distribution/whatsnew/**') != '' && 'distribution/whatsnew' || '' }}
381
+ userFraction: >-
382
+ ${{ vars.GOOGLE_PLAY_STATUS == 'inProgress' && (vars.GOOGLE_PLAY_USER_FRACTION || '0.2') || '' }}
383
+ whatsNewDirectory: >-
384
+ ${{ hashFiles('distribution/whatsnew/**') != '' && 'distribution/whatsnew' || '' }}
358
385
  changesNotSentForReview: true
359
386
 
360
387
  - name: Say what still needs a human