gowalk-cicd 1.0.142 → 1.0.143

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.
@@ -1 +1 @@
1
- 1.0.142
1
+ 1.0.143
@@ -1 +1 @@
1
- 1.0.142
1
+ 1.0.143
@@ -1 +1 @@
1
- 1.0.142
1
+ 1.0.143
@@ -62,7 +62,7 @@ in the same repository; unrelated repositories can continue upgrading.
62
62
 
63
63
  # Organization rollout
64
64
 
65
- Use the script from the exact released package being installed:
65
+ Use a released organization script and the reviewed installer package:
66
66
 
67
67
  ```sh
68
68
  node /path/to/package/scripts/upgrade-org.mjs \
@@ -75,8 +75,10 @@ It paginates the organization, discovers actual installed actions, and uses one
75
75
  independent repositories concurrently. Existing CI remains authoritative; each
76
76
  successful candidate merges independently, and any resulting push workflows are
77
77
  followed before branch/checkout cleanup. No factory task or Mac reservation is
78
- created. API throttling and queued CI limit new migration PR publication while
79
- other local preparation and completed candidates continue.
78
+ created. API throttling and `--max-active` bound publication. One repository’s queued CI
79
+ never stops unrelated PRs; GitHub schedules each platform against its available runners.
80
+ Existing checks must finish successfully, and GitHub enforces its required checks and reviews
81
+ for the exact candidate. A repository without candidate checks gains no new CI requirement.
80
82
 
81
83
  The state file records repository heads, PR/run URLs, failures and cleanup.
82
84
  Restart with the same file and package to resume. `--retry-unresolved` retries
@@ -84,3 +86,8 @@ recorded exceptions after their cause is repaired; `--dry-run` prepares local
84
86
  changes without pushing, opening PRs or merging. Use a separate state file for a
85
87
  new target version. The final status counts distinguish updated, already-current,
86
88
  not-installed and unresolved repositories; an unresolved result exits nonzero.
89
+
90
+ `--installer-dir /path/to/reviewed/package` selects a retained installer independently of
91
+ the organization script release. Its package version owns the existing state, branches and
92
+ installed files. This lets an orchestration-only repair resume a rollout without reinstalling
93
+ another package version or replacing its PRs. Omitting it installs the script’s own package.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gowalk-cicd",
3
- "version": "1.0.142",
3
+ "version": "1.0.143",
4
4
  "description": "Zero-config GitHub Actions delivery for iOS TestFlight and Android Google Play.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -22,13 +22,13 @@ export async function checkCandidate(github, repo, options) {
22
22
  repo.nextAt = Date.now() + 30000;
23
23
  return;
24
24
  }
25
- if (!current.length) throw new RolloutError('candidate_ci_missing');
26
25
  if (current.some(run => !['success', 'skipped'].includes(run.conclusion))) {
27
26
  throw new RolloutError('candidate_ci_failed');
28
27
  }
29
28
  // GitHub enforces existing required checks and reviews; no admin bypass or new gate is installed.
30
29
  await (github.command || command)('gh', ['pr', 'merge', String(repo.pr), '--repo', repo.name, '--squash',
31
30
  '--match-head-commit', repo.head]);
31
+ repo.validation = current.length ? 'existing_ci' : 'github_merge_policy';
32
32
  repo.status = 'deploy';
33
33
  }
34
34
 
@@ -0,0 +1,39 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { resolve, dirname, join } from 'node:path';
3
+ import { RolloutError } from './github.mjs';
4
+
5
+ export function options(args, packageDir) {
6
+ const result = { packageDir, org: 'gowalk-public', workers: 8, maxActive: 8, ciTimeout: 3600000 };
7
+ const names = { '--org': 'org', '--state': 'statePath', '--workers': 'workers',
8
+ '--max-active': 'maxActive', '--installer-dir': 'packageDir' };
9
+ for (let index = 0; index < args.length; index++) {
10
+ if (args[index] === '--dry-run') { result.dryRun = true; continue; }
11
+ if (args[index] === '--retry-unresolved') { result.retryUnresolved = true; continue; }
12
+ const name = names[args[index]];
13
+ if (!name || !args[index + 1]) throw new RolloutError('invalid_rollout_arguments');
14
+ result[name] = ['workers', 'maxActive'].includes(name) ? Number(args[++index]) : args[++index];
15
+ }
16
+ if (!result.statePath || !/^[\w-]+$/.test(result.org)
17
+ || ![result.workers, result.maxActive].every(value => Number.isInteger(value) && value > 0 && value <= 32)) {
18
+ throw new RolloutError('invalid_rollout_arguments');
19
+ }
20
+ result.packageDir = resolve(result.packageDir);
21
+ const target = JSON.parse(readFileSync(join(result.packageDir, 'package.json'), 'utf8'));
22
+ if (target.name !== 'gowalk-cicd' || !/^\d+\.\d+\.\d+$/.test(target.version)) {
23
+ throw new RolloutError('invalid_installer_package');
24
+ }
25
+ result.version = target.version;
26
+ result.statePath = resolve(result.statePath);
27
+ result.workDir = join(dirname(result.statePath), 'checkouts');
28
+ return result;
29
+ }
30
+
31
+ export function eligible(repo, repositories, opts) {
32
+ if ((repo.nextAt || 0) > Date.now()) return false;
33
+ if (repo.status === 'publish') {
34
+ const active = repositories.filter(row => ['ci', 'deploy', 'cleanup'].includes(row.status));
35
+ return active.length + (opts.publishing || 0) < opts.maxActive;
36
+ }
37
+ return ['inspect', 'prepare', 'ci', 'deploy', 'cleanup', 'update-base'].includes(repo.status);
38
+ }
39
+
@@ -1,44 +1,14 @@
1
1
  #!/usr/bin/env node
2
- // Run from one exact published package. Local workers never claim or reserve factory Macs.
3
- import { readFileSync } from 'node:fs';
4
- import { resolve, dirname, join } from 'node:path';
2
+ // Use a released coordinator and reviewed installer. Workers never claim or reserve factory Macs.
3
+ import { resolve, dirname } from 'node:path';
5
4
  import { fileURLToPath } from 'node:url';
6
5
  import { GitHub, RolloutError, delay } from './lib/github.mjs';
7
6
  import { openState, summary } from './lib/state.mjs';
7
+ import { options, eligible } from './lib/coordination.mjs';
8
8
  import { discover, inspect } from './lib/discovery.mjs';
9
9
  import { prepare, publish, incorporateBase } from './lib/repository.mjs';
10
10
  import { checkCandidate, checkDeploy, cleanup } from './lib/checks.mjs';
11
11
 
12
- function options(args) {
13
- const packageDir = resolve(dirname(fileURLToPath(import.meta.url)), '..');
14
- const version = JSON.parse(readFileSync(join(packageDir, 'package.json'), 'utf8')).version;
15
- const result = { packageDir, version, org: 'gowalk-public', workers: 8, maxActive: 8, ciTimeout: 3600000 };
16
- const names = { '--org': 'org', '--state': 'statePath', '--workers': 'workers', '--max-active': 'maxActive' };
17
- for (let index = 0; index < args.length; index++) {
18
- if (args[index] === '--dry-run') { result.dryRun = true; continue; }
19
- if (args[index] === '--retry-unresolved') { result.retryUnresolved = true; continue; }
20
- const name = names[args[index]];
21
- if (!name || !args[index + 1]) throw new RolloutError('invalid_rollout_arguments');
22
- result[name] = ['workers', 'maxActive'].includes(name) ? Number(args[++index]) : args[++index];
23
- }
24
- if (!result.statePath || !/^[\w-]+$/.test(result.org)
25
- || ![result.workers, result.maxActive].every(value => Number.isInteger(value) && value > 0 && value <= 32)) {
26
- throw new RolloutError('invalid_rollout_arguments');
27
- }
28
- result.statePath = resolve(result.statePath);
29
- result.workDir = join(dirname(result.statePath), 'checkouts');
30
- return result;
31
- }
32
-
33
- function eligible(repo, repositories, opts) {
34
- if ((repo.nextAt || 0) > Date.now()) return false;
35
- if (repo.status === 'publish') {
36
- const active = repositories.filter(row => ['ci', 'deploy', 'cleanup'].includes(row.status));
37
- return active.length + (opts.publishing || 0) < opts.maxActive && !active.some(row => row.queued > 0);
38
- }
39
- return ['inspect', 'prepare', 'ci', 'deploy', 'cleanup', 'update-base'].includes(repo.status);
40
- }
41
-
42
12
  async function advance(github, repo, opts) {
43
13
  const handlers = { inspect, prepare, publish, ci: checkCandidate, deploy: checkDeploy, cleanup };
44
14
  if (repo.status === 'update-base') return incorporateBase(repo);
@@ -99,7 +69,7 @@ async function run(opts, state) {
99
69
 
100
70
  let state;
101
71
  try {
102
- const opts = options(process.argv.slice(2));
72
+ const opts = options(process.argv.slice(2), resolve(dirname(fileURLToPath(import.meta.url)), '..'));
103
73
  state = openState(opts.statePath, opts.org, opts.version);
104
74
  await run(opts, state);
105
75
  state.save();