gowalk-cicd 1.0.198 → 1.0.200

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/README.md CHANGED
@@ -23,6 +23,10 @@ From the root of your app repo:
23
23
  npx --yes gowalk-cicd
24
24
  ```
25
25
 
26
+ To update only backend delivery while preserving mobile tooling and workflows, use
27
+ `npx --yes gowalk-cicd --backend-only`. The lock records the backend release separately;
28
+ see [scoped installation](docs/CONFIGURATION.md).
29
+
26
30
  Writes three core things into your repo:
27
31
 
28
32
  - `.github/actions/swift-app/` — the vendored composite action (action.yml + scripts)
@@ -374,6 +378,9 @@ Place these files under `creds/`:
374
378
  keyPassword=<upload key password>
375
379
  ```
376
380
 
381
+ A relative `storeFile` resolves beside this file; when no keystore is there,
382
+ it resolves from the repository root (`creds/android-upload-key.jks`).
383
+
377
384
  3. One Google service-account JSON key. Its filename is unrestricted
378
385
  (`Gowalk.json` is valid); the action identifies it by the
379
386
  `type: service_account` JSON fields.
@@ -86,6 +86,9 @@ def find_signing_config(workspace: Path) -> SigningConfig:
86
86
  if configured_store:
87
87
  raw_path = Path(configured_store).expanduser()
88
88
  keystore = raw_path if raw_path.is_absolute() else properties.parent / raw_path
89
+ if not raw_path.is_absolute() and not keystore.is_file() and (workspace / raw_path).is_file():
90
+ # Provisioned properties may name the keystore from the checkout root (creds/<file>).
91
+ keystore = workspace / raw_path
89
92
  else:
90
93
  keystore = _single(
91
94
  sorted([*creds.glob("*.jks"), *creds.glob("*.keystore")]),
package/bin/cli.mjs CHANGED
@@ -31,6 +31,7 @@ if (args[0] === 'release') {
31
31
  }
32
32
  }
33
33
  let dryRun = false;
34
+ let backendOnly = false;
34
35
 
35
36
  for (const arg of args) {
36
37
  switch (arg) {
@@ -59,6 +60,7 @@ Options:
59
60
  release [ios|android|both] Prepare a mobile release intent on the task branch
60
61
  validate run|check|publish|verify Run local focused checks or verify their authenticated source receipt
61
62
  --dry-run Print what would happen without writing files
63
+ --backend-only Update backend tooling without changing mobile tooling or workflows
62
64
  -v, --version Show version number
63
65
  -h, --help Show this help message
64
66
 
@@ -75,6 +77,9 @@ After install:
75
77
  case '--dry-run':
76
78
  dryRun = true;
77
79
  break;
80
+ case '--backend-only':
81
+ backendOnly = true;
82
+ break;
78
83
  default:
79
84
  console.error(`Unknown option: ${arg}`);
80
85
  console.error(`Run with --help for usage.`);
@@ -84,7 +89,7 @@ After install:
84
89
 
85
90
  const { runInstall } = await import('../src/install.mjs');
86
91
  try {
87
- await runInstall({ dryRun });
92
+ await runInstall({ dryRun, backendOnly });
88
93
  } catch (error) {
89
94
  console.error(JSON.stringify(error.result || {
90
95
  schema: 'gowalk-cicd/migration-repair.v1', code: 'installer_unavailable',
@@ -5,6 +5,23 @@ extracts supported settings from existing workflows into `.gowalk-cicd.yml` and
5
5
  renders the managed workflows in the same change. Later upgrades read that file.
6
6
  The installer does this locally; app CI gains no configuration job or preflight.
7
7
 
8
+ For backend-only work in a repository whose mobile tooling must stay unchanged, run
9
+ `npx --yes gowalk-cicd@<version> --backend-only` (also accepts `--dry-run`). It updates only
10
+ `.github/actions/backend-app/`, `deploy-backend.yml` and their configuration/lock state.
11
+ Mobile actions, workflows, validation workflows and release intents retain their exact bytes.
12
+ Existing backend settings and workflow overrides are preserved by the normal migration rules.
13
+ The separate backend deploy still follows its existing protected merge and push trigger.
14
+
15
+ For this scoped installation, `.gowalk-cicd.lock.json` records `backend_package`; `package`
16
+ continues to name the last full installation. Verify `backend_package` (or `package` after a
17
+ full install), the backend action's `.daemux-version`, the published package integrity and the
18
+ installed backend bytes. A scoped backend upgrade makes no claim that mobile tooling is current.
19
+ The next full installation updates all tooling and removes the scoped version override.
20
+ Repositories with existing managed mobile workflows require their existing `.gowalk-cicd.yml`
21
+ configuration before scoped installation; it never partially migrates an unconfigured mobile
22
+ installation. An interrupted full installation also refuses a backend-only retry, preserving
23
+ its journal instead of applying mobile writes outside the requested scope.
24
+
8
25
  ```yaml
9
26
  schema: 1
10
27
  mobile:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gowalk-cicd",
3
- "version": "1.0.198",
3
+ "version": "1.0.200",
4
4
  "description": "Zero-config GitHub Actions delivery for iOS TestFlight and Android Google Play.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -44,7 +44,7 @@ function apply(root, writes, dryRun) {
44
44
  if (!dryRun) unlinkSync(join(root, JOURNAL));
45
45
  }
46
46
 
47
- export function recoverWorkflows(root, dryRun) {
47
+ export function recoverWorkflows(root, dryRun, { backendOnly = false } = {}) {
48
48
  if (!existsSync(join(root, JOURNAL))) return false;
49
49
  let journal;
50
50
  try { journal = JSON.parse(readFileSync(join(root, JOURNAL), 'utf8')); }
@@ -52,14 +52,18 @@ export function recoverWorkflows(root, dryRun) {
52
52
  if (journal.schema !== 1 || !Array.isArray(journal.writes)) {
53
53
  throw new MigrationRepair('invalid_pending_migration', JOURNAL);
54
54
  }
55
+ if (backendOnly && journal.scope !== 'backend') {
56
+ throw new MigrationRepair('pending_migration_scope_mismatch', JOURNAL);
57
+ }
55
58
  apply(root, journal.writes, dryRun);
56
59
  return true;
57
60
  }
58
61
 
59
- export function writeWorkflows(root, writes, dryRun) {
62
+ export function writeWorkflows(root, writes, dryRun, { backendOnly = false } = {}) {
60
63
  if (!writes.length) return;
61
64
  const planned = writes.map(write => ({ ...write, before: current(join(root, write.file)) }));
62
65
  for (const write of planned) validate(root, write);
63
- if (!dryRun) atomicWrite(join(root, JOURNAL), JSON.stringify({ schema: 1, writes: planned }));
66
+ if (!dryRun) atomicWrite(join(root, JOURNAL), JSON.stringify({ schema: 1,
67
+ ...(backendOnly ? { scope: 'backend' } : {}), writes: planned }));
64
68
  apply(root, planned, dryRun);
65
69
  }
@@ -80,9 +80,14 @@ function upgradeWorkflow(root, file, templateDir, state) {
80
80
  return { file: `.github/workflows/${file}`, content: output };
81
81
  }
82
82
 
83
- export function planWorkflows(root, templateDir, files, version) {
83
+ export function planWorkflows(root, templateDir, files, version, { backendOnly = false } = {}) {
84
84
  const state = configState(root);
85
- const selected = [...new Set([...files, ...Object.keys(state.config.workflows)])];
85
+ if (backendOnly && state.migrating && MANAGED.some(file => file !== 'deploy-backend.yml'
86
+ && load(root, `.github/workflows/${file}`) !== null)) {
87
+ throw new MigrationRepair('backend_scope_requires_existing_config', CONFIG);
88
+ }
89
+ const selected = backendOnly ? ['deploy-backend.yml']
90
+ : [...new Set([...files, ...Object.keys(state.config.workflows)])];
86
91
  for (const file of selected) {
87
92
  if (!MANAGED.includes(file)) throw new MigrationRepair('unknown_managed_workflow', CONFIG);
88
93
  }
@@ -113,8 +118,12 @@ export function planWorkflows(root, templateDir, files, version) {
113
118
  for (const [file, entry] of Object.entries(workflows)) {
114
119
  for (const change of entry.overrides) assertReferences(change.value, file, change.path);
115
120
  }
116
- writes.push(...planValidation(root, [...selected, WARM, RECOVERY]));
117
- state.lock.package = version;
121
+ if (backendOnly) state.lock.backend_package = version;
122
+ else {
123
+ writes.push(...planValidation(root, [...selected, WARM, RECOVERY]));
124
+ state.lock.package = version;
125
+ delete state.lock.backend_package;
126
+ }
118
127
  const config = state.text && equal(readYaml(state.text, CONFIG).value, state.config)
119
128
  ? state.text
120
129
  : '# App-owned settings. Edit this file, then run gowalk-cicd to render workflows.\n' + encode(state.config);
package/src/install.mjs CHANGED
@@ -23,6 +23,7 @@ import { dirname } from 'node:path';
23
23
  import { planWorkflows, writeWorkflows } from './config/workflows.mjs';
24
24
  import { recoverWorkflows } from './config/transaction.mjs';
25
25
  import { backendLayout } from './backend-layout.mjs';
26
+ import { MigrationRepair } from './config/yaml.mjs';
26
27
 
27
28
  const __filename = fileURLToPath(import.meta.url);
28
29
  const __dirname = dirname(__filename);
@@ -201,32 +202,38 @@ function printSummary() {
201
202
  console.log(' npx --yes gowalk-cicd');
202
203
  }
203
204
 
204
- export async function runInstall({ dryRun = false } = {}) {
205
+ export async function runInstall({ dryRun = false, backendOnly = false } = {}) {
205
206
  const repoRoot = findRepoRoot(process.cwd());
206
207
  console.log(`Installing gowalk-cicd into ${repoRoot}${dryRun ? ' (dry-run)' : ''}`);
207
208
  console.log('');
208
- if (recoverWorkflows(repoRoot, dryRun) && dryRun) return;
209
+ if (recoverWorkflows(repoRoot, dryRun, { backendOnly }) && dryRun) return;
209
210
 
210
211
  const layout = backendLayout(repoRoot);
212
+ if (backendOnly && !layout && !existsSync(join(repoRoot, BACKEND_ACTION_DEST_REL))
213
+ && !existsSync(join(repoRoot, '.github/workflows/deploy-backend.yml'))) {
214
+ throw new MigrationRepair('backend_layout_missing', '.github/workflows/deploy-backend.yml');
215
+ }
211
216
  // deploy-web.yml is no longer added; an existing one is upgraded, or removed
212
217
  // when it is still the unmodified generated file (planWorkflows).
213
- const files = ['deploy.yml', 'mobile-candidate.yml'];
214
- if (layout || existsSync(join(repoRoot, BACKEND_ACTION_DEST_REL))) files.push('deploy-backend.yml');
218
+ const files = backendOnly ? ['deploy-backend.yml'] : ['deploy.yml', 'mobile-candidate.yml'];
219
+ if (!backendOnly && (layout || existsSync(join(repoRoot, BACKEND_ACTION_DEST_REL)))) files.push('deploy-backend.yml');
215
220
  for (const file of ['deploy-web.yml', 'deploy-backend.yml']) {
216
- if (existsSync(join(repoRoot, '.github/workflows', file)) && !files.includes(file)) files.push(file);
221
+ if (!backendOnly && existsSync(join(repoRoot, '.github/workflows', file)) && !files.includes(file)) files.push(file);
217
222
  }
218
223
  const version = String(JSON.parse(readFileSync(PACKAGE_JSON, 'utf8')).version);
219
- const writes = planWorkflows(repoRoot, join(PACKAGE_DIR, 'templates'), files, version);
224
+ const writes = planWorkflows(repoRoot, join(PACKAGE_DIR, 'templates'), files, version, { backendOnly });
220
225
  const workflows = workflowText(repoRoot, writes);
221
226
 
222
- copyAction(ACTION_SRC, ACTION_DEST_REL, repoRoot, dryRun, workflows);
223
- copyAction(ANDROID_ACTION_SRC, ANDROID_ACTION_DEST_REL, repoRoot, dryRun, workflows);
224
- writeVersionMarkers(repoRoot, dryRun);
227
+ if (!backendOnly) {
228
+ copyAction(ACTION_SRC, ACTION_DEST_REL, repoRoot, dryRun, workflows);
229
+ copyAction(ANDROID_ACTION_SRC, ANDROID_ACTION_DEST_REL, repoRoot, dryRun, workflows);
230
+ writeVersionMarkers(repoRoot, dryRun);
231
+ }
225
232
  if (files.includes('deploy-backend.yml')) {
226
233
  copyAction(BACKEND_ACTION_SRC, BACKEND_ACTION_DEST_REL, repoRoot, dryRun, workflows);
227
234
  if (!dryRun) writeFileSync(join(repoRoot, BACKEND_VERSION_MARKER_REL), version);
228
235
  }
229
- writeWorkflows(repoRoot, writes, dryRun);
236
+ writeWorkflows(repoRoot, writes, dryRun, { backendOnly });
230
237
 
231
238
  const gi = checkGitignore(repoRoot);
232
239
  printGitignoreWarning(gi);
@@ -237,5 +244,7 @@ export async function runInstall({ dryRun = false } = {}) {
237
244
  return;
238
245
  }
239
246
 
240
- printSummary();
247
+ if (backendOnly) {
248
+ console.log('\nDone. Backend tooling updated; mobile files and release intents are unchanged.');
249
+ } else printSummary();
241
250
  }