gowalk-cicd 1.0.143 → 1.0.144

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.143
1
+ 1.0.144
@@ -1 +1 @@
1
- 1.0.143
1
+ 1.0.144
@@ -1 +1 @@
1
- 1.0.143
1
+ 1.0.144
@@ -45,9 +45,12 @@ inferred safe from a custom step name. Candidate misses never wait or reserve a
45
45
 
46
46
  Separate custom workflows remain app-owned. Recognized native dependency jobs
47
47
  receive optional lock/toolchain-keyed cache restore/save inside their existing job.
48
- A validation workflow already cancelling superseded work may coalesce duplicate
49
- push/PR runs for the same app branch. Its triggers, job names, commands and custom
50
- checks remain. Separate platforms and repositories keep their existing parallelism.
48
+ Supported validation workflows use their existing PR trigger for `task/**` branches,
49
+ keeping default-branch push checks and all job names, commands and custom checks.
50
+ This requires a single app-owned validation workflow, a PR trigger covering the
51
+ known default branch, and jobs without event-dependent behavior or custom GitHub scripts. Other workflows retain separate push/PR concurrency
52
+ groups, so neither required check cancels the other. An upgrade also repairs the shared
53
+ group emitted by 1.0.142–143. Separate platforms and repositories keep their existing parallelism.
51
54
 
52
55
  `.gowalk-cicd.lock.json` records the template and generated-file digests. Commit it
53
56
  with the configuration and rendered workflows. It is migration state, not a CI job.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gowalk-cicd",
3
- "version": "1.0.143",
3
+ "version": "1.0.144",
4
4
  "description": "Zero-config GitHub Actions delivery for iOS TestFlight and Android Google Play.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -10,7 +10,7 @@ export async function checkCandidate(github, repo, options) {
10
10
  const runs = await github.pages(`repos/${repo.name}/actions/runs?head_sha=${repo.head}`, 'workflow_runs');
11
11
  const latest = new Map();
12
12
  for (const run of runs.filter(run => ['push', 'pull_request'].includes(run.event))) {
13
- const key = run.workflow_id;
13
+ const key = `${run.workflow_id}:${run.event}`;
14
14
  if (!latest.has(key) || latest.get(key).id < run.id) latest.set(key, run);
15
15
  }
16
16
  const current = [...latest.values()];
@@ -0,0 +1,56 @@
1
+ // Keep required push/PR checks independent; avoid duplicate factory pushes only for supported PR validation.
2
+ import { execFileSync } from 'node:child_process';
3
+ const REF = '${{ github.ref }}';
4
+ const COALESCED = '${{ github.event.pull_request.head.repo.full_name == github.repository'
5
+ + ' && github.event.pull_request.head.ref || github.ref_name }}';
6
+
7
+ export function defaultBranch(root) {
8
+ try {
9
+ return execFileSync('git', ['symbolic-ref', '--quiet', 'refs/remotes/origin/HEAD'],
10
+ { cwd: root, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] })
11
+ .trim().replace(/^refs\/remotes\/origin\//, '');
12
+ } catch { return ''; }
13
+ }
14
+
15
+ function coversCandidates(event, branch, jobs) {
16
+ if (!branch || (event !== null && (typeof event !== 'object' || Array.isArray(event)))) return false;
17
+ const filters = event || {};
18
+ if (Object.keys(filters).some(key => !['branches', 'types'].includes(key))) return false;
19
+ if (filters.types && (!Array.isArray(filters.types)
20
+ || !['opened', 'synchronize', 'reopened'].every(type => filters.types.includes(type)))) return false;
21
+ if (filters.branches && (!Array.isArray(filters.branches) || !filters.branches.includes(branch)
22
+ || filters.branches.some(value => typeof value !== 'string' || /[!*?\[\]+]/.test(value)))) return false;
23
+ // Event-dependent app commands and job conditions need their own reviewed migration.
24
+ return !/github\.(event|ref)|GITHUB_(EVENT|REF)|actions\/github-script@/.test(JSON.stringify(jobs));
25
+ }
26
+
27
+ function excludeFactoryPush(doc, push) {
28
+ if (push !== null && (typeof push !== 'object' || Array.isArray(push))) return false;
29
+ const filters = push || {};
30
+ const field = Object.hasOwn(filters, 'branches') ? 'branches' : 'branches-ignore';
31
+ if (!Object.hasOwn(filters, field) && ('tags' in filters || 'tags-ignore' in filters)) return false;
32
+ const values = filters[field] || [];
33
+ if (!Array.isArray(values) || values.some(value => typeof value !== 'string')) return false;
34
+ if ('branches' in filters && 'branches-ignore' in filters) return false;
35
+ const exclusion = field === 'branches' ? '!task/**' : 'task/**';
36
+ if (values.at(-1) === exclusion) return false;
37
+ doc.setIn(['on', 'push'], doc.createNode({ ...filters, [field]: [...values, exclusion] }));
38
+ return true;
39
+ }
40
+
41
+ export function upgradeEvents(doc, value, branch) {
42
+ const events = value.on;
43
+ const concurrency = value.concurrency;
44
+ if (!events || !Object.hasOwn(events, 'push') || !Object.hasOwn(events, 'pull_request')
45
+ || concurrency?.['cancel-in-progress'] !== true || typeof concurrency.group !== 'string') return false;
46
+ const original = concurrency.group;
47
+ const group = original.replace(COALESCED, REF);
48
+ const recognized = group.includes('${{ github.workflow }}') || group === `flutter-ci-${REF}`;
49
+ if (!group.includes(REF) || !recognized) return false;
50
+ let changed = group !== original;
51
+ if (changed) doc.setIn(['concurrency', 'group'], group);
52
+ if (coversCandidates(events.pull_request, branch, value.jobs)) {
53
+ changed = excludeFactoryPush(doc, events.push) || changed;
54
+ }
55
+ return changed;
56
+ }
@@ -1,8 +1,9 @@
1
- // Upgrade supported app-owned validation in place; preserve jobs, triggers, checks and custom commands.
1
+ // Upgrade supported app-owned validation in place; preserve jobs, checks and custom commands.
2
2
  import { existsSync, readdirSync, readFileSync } from 'node:fs';
3
3
  import { join } from 'node:path';
4
4
  import { readYaml, MigrationRepair } from './yaml.mjs';
5
5
  import { upgradeReuse } from './validation-reuse.mjs';
6
+ import { defaultBranch, upgradeEvents } from './validation-events.mjs';
6
7
  const CACHE = 'caa296126883cff596d87d8935842f9db880ef25';
7
8
  const PREFIX = '.github/actions/swift-app/scripts/';
8
9
 
@@ -48,32 +49,20 @@ function upgradeCaches(doc, value, file) {
48
49
  return changed;
49
50
  }
50
51
 
51
- function coalesceValidation(doc, value) {
52
- const events = value.on;
53
- const concurrency = value.concurrency;
54
- if (!events || !Object.hasOwn(events, 'push') || !Object.hasOwn(events, 'pull_request')
55
- || concurrency?.['cancel-in-progress'] !== true || typeof concurrency.group !== 'string') return false;
56
- const group = concurrency.group;
57
- if (!group.includes('${{ github.ref }}')) return false;
58
- if (!group.includes('${{ github.workflow }}') && group !== 'flutter-ci-${{ github.ref }}') return false;
59
- const identity = '${{ github.event.pull_request.head.repo.full_name == github.repository'
60
- + ' && github.event.pull_request.head.ref || github.ref_name }}';
61
- doc.setIn(['concurrency', 'group'], group.replace('${{ github.ref }}', identity));
62
- return true;
63
- }
64
-
65
52
  export function planValidation(root, managed) {
66
53
  const directory = join(root, '.github/workflows');
67
54
  if (!existsSync(directory)) return [];
68
55
  const writes = [];
69
- for (const file of readdirSync(directory).filter(file => /\.ya?ml$/.test(file) && !managed.includes(file))) {
56
+ const files = readdirSync(directory).filter(file => /\.ya?ml$/.test(file) && !managed.includes(file));
57
+ // Another app-owned workflow may depend on this workflow's push evidence.
58
+ const branch = files.length === 1 ? defaultBranch(root) : '';
59
+ for (const file of files) {
70
60
  const source = readFileSync(join(directory, file), 'utf8');
71
61
  const { doc, value } = readYaml(source, file);
72
- const coalesced = coalesceValidation(doc, value);
73
- const compatibleGroup = coalesced || value.concurrency?.group?.includes('github.event.pull_request.head.ref');
74
- const reused = compatibleGroup && upgradeReuse(doc, value, file);
62
+ const events = upgradeEvents(doc, value, branch);
63
+ const reused = upgradeReuse(doc, value, file);
75
64
  const cached = upgradeCaches(doc, doc.toJS({ maxAliasCount: 100 }), file);
76
- if (cached || coalesced || reused) {
65
+ if (cached || events || reused) {
77
66
  writes.push({ file: `.github/workflows/${file}`, content: doc.toString({ lineWidth: 120 }) });
78
67
  }
79
68
  }