gowalk-cicd 1.0.160 → 1.0.162

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.160
1
+ 1.0.162
@@ -1 +1 @@
1
- 1.0.160
1
+ 1.0.162
@@ -1 +1 @@
1
- 1.0.160
1
+ 1.0.162
@@ -6,6 +6,37 @@ certbot_lock_busy() {
6
6
  "$CERTBOT_WORK/stdout" "$CERTBOT_WORK/stderr"
7
7
  }
8
8
 
9
+ # Fixed cause codes for a failed Certbot command. Certbot's own text stays private: only these
10
+ # tokens and a three-digit HTTP status from the CA's validation report reach the log.
11
+ certbot_failure_causes() {
12
+ local causes="" code pattern http=""
13
+ while IFS='|' read -r code pattern; do
14
+ if grep -Eiq -- "$pattern" "$CERTBOT_WORK/stdout" "$CERTBOT_WORK/stderr"; then
15
+ causes="${causes:+$causes,}$code"
16
+ fi
17
+ done <<'CAUSES'
18
+ rate_limited|rateLimited|too many (certificates|new orders|failed authorizations|registrations)
19
+ rejected_identifier|rejectedIdentifier|forbidden by policy
20
+ caa|CAA record
21
+ dns|Type: +dns|acme:error:dns|DNS problem
22
+ connection|Type: +connection|acme:error:connection|Timeout during (connect|read)|Connection refused
23
+ unauthorized|Type: +unauthorized|acme:error:unauthorized
24
+ incorrect_response|Invalid response from|incorrectResponse
25
+ secondary_validation|During secondary validation
26
+ tls|Type: +tls|acme:error:tls
27
+ account|accountDoesNotExist|Account (is not valid|does not exist)
28
+ ca_unavailable|serverInternal|Service busy|down for maintenance
29
+ nginx_restart|nginx restart failed|Error while running nginx
30
+ nginx_vhost|Could not automatically find a matching server block
31
+ nginx_parse|Could not parse file
32
+ nginx_plugin|nginx plugin is not working|Could not find a usable .nginx. binary
33
+ CAUSES
34
+ http="$(grep -Eoh -- 'Invalid response from [^ ]+( \[[^]]+\])?: [1-5][0-9]{2}' "$CERTBOT_WORK/stdout" \
35
+ "$CERTBOT_WORK/stderr" | grep -Eo '[1-5][0-9]{2}$' | head -1)" || http=""
36
+ [ -z "$http" ] || causes="${causes:+$causes,}http_$http"
37
+ printf '%s' "${causes:-unclassified}"
38
+ }
39
+
9
40
  certbot_attempt() {
10
41
  local phase="$1" attempt="$2" status
11
42
  shift 2
@@ -18,7 +49,7 @@ certbot_attempt() {
18
49
  if certbot_lock_busy; then
19
50
  log "certificate phase=$phase attempt=$attempt state=lock_busy exit=$status"
20
51
  else
21
- log "certificate phase=$phase attempt=$attempt state=error exit=$status"
52
+ log "certificate phase=$phase attempt=$attempt state=error exit=$status cause=$(certbot_failure_causes)"
22
53
  fi
23
54
  return "$status"
24
55
  }
@@ -76,12 +76,14 @@ inferred safe from a custom step name. Candidate misses never wait or reserve a
76
76
 
77
77
  Separate custom workflows remain app-owned. Recognized native dependency jobs
78
78
  receive optional lock/toolchain-keyed cache restore/save inside their existing job.
79
- Supported validation workflows use their existing PR trigger for `task/**` branches,
80
- keeping default-branch push checks and all job names, commands and custom checks.
81
- This requires a single app-owned validation workflow, a PR trigger covering the
82
- known default branch, and jobs without event-dependent behavior or custom GitHub scripts. Other workflows retain separate push/PR concurrency
83
- groups, so neither required check cancels the other. An upgrade also repairs the shared
84
- group emitted by 1.0.142–143. Separate platforms and repositories keep their existing parallelism.
79
+ An app-owned workflow whose push trigger is a plain `branches` list admitting `task/**`
80
+ validates each candidate on that push, which runs on the exact head: the installer deletes its
81
+ duplicate `pull_request` trigger, and a trailing `!task/**` written by earlier releases, in one
82
+ write. Job names, commands and custom checks are unchanged. A push with `branches-ignore`,
83
+ `paths` or `paths-ignore`, or one that does not admit `task/**`, is left as it is; such
84
+ workflows retain separate push/PR concurrency groups, so neither check cancels the other.
85
+ An upgrade also repairs the shared group emitted by 1.0.142–143. Separate platforms and
86
+ repositories keep their existing parallelism.
85
87
 
86
88
  `.gowalk-cicd.lock.json` records the template and generated-file digests. Commit it
87
89
  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.160",
3
+ "version": "1.0.162",
4
4
  "description": "Zero-config GitHub Actions delivery for iOS TestFlight and Android Google Play.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,56 +1,38 @@
1
- // Keep required push/PR checks independent; avoid duplicate factory pushes only for supported PR validation.
2
- import { execFileSync } from 'node:child_process';
1
+ // A task/** push already validates the exact candidate head, so a duplicate PR trigger is deleted.
3
2
  const REF = '${{ github.ref }}';
4
3
  const COALESCED = '${{ github.event.pull_request.head.repo.full_name == github.repository'
5
4
  + ' && github.event.pull_request.head.ref || github.ref_name }}';
5
+ const FACTORY_EXCLUSION = '!task/**';
6
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 ''; }
7
+ // Repair the shared push/PR group emitted by 1.0.142–143.
8
+ function normalizeGroup(doc, concurrency) {
9
+ if (concurrency?.['cancel-in-progress'] !== true || typeof concurrency.group !== 'string') return false;
10
+ const group = concurrency.group.replace(COALESCED, REF);
11
+ const recognized = group.includes('${{ github.workflow }}') || group === `flutter-ci-${REF}`;
12
+ if (group === concurrency.group || !group.includes(REF) || !recognized) return false;
13
+ doc.setIn(['concurrency', 'group'], group);
14
+ return true;
13
15
  }
14
16
 
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));
17
+ // Only a plain branch list whose last pattern for task refs admits them all; anything else is left alone.
18
+ function candidateBranches(push) {
19
+ if (!push || typeof push !== 'object' || Array.isArray(push)) return null;
20
+ if (['branches-ignore', 'paths', 'paths-ignore'].some(key => Object.hasOwn(push, key))) return null;
21
+ const values = push.branches;
22
+ if (!Array.isArray(values) || values.some(value => typeof value !== 'string')) return null;
23
+ const branches = values.at(-1) === FACTORY_EXCLUSION ? values.slice(0, -1) : values;
24
+ const admitted = branches.findLastIndex(value => value === 'task/**' || value === '**');
25
+ if (admitted < 0 || branches.slice(admitted + 1).some(value => value.startsWith('!'))) return null;
26
+ return branches;
25
27
  }
26
28
 
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) {
29
+ export function upgradeEvents(doc, value) {
42
30
  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;
31
+ if (!events || !Object.hasOwn(events, 'push') || !Object.hasOwn(events, 'pull_request')) return false;
32
+ const grouped = normalizeGroup(doc, value.concurrency);
33
+ const branches = candidateBranches(events.push);
34
+ if (!branches) return grouped;
35
+ if (branches.length < events.push.branches.length) doc.deleteIn(['on', 'push', 'branches', branches.length]);
36
+ doc.deleteIn(['on', 'pull_request']);
37
+ return true;
56
38
  }
@@ -3,7 +3,7 @@ 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
+ import { upgradeEvents } from './validation-events.mjs';
7
7
  const CACHE = 'caa296126883cff596d87d8935842f9db880ef25';
8
8
  const PREFIX = '.github/actions/swift-app/scripts/';
9
9
 
@@ -54,12 +54,10 @@ export function planValidation(root, managed) {
54
54
  if (!existsSync(directory)) return [];
55
55
  const writes = [];
56
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
57
  for (const file of files) {
60
58
  const source = readFileSync(join(directory, file), 'utf8');
61
59
  const { doc, value } = readYaml(source, file);
62
- const events = upgradeEvents(doc, value, branch);
60
+ const events = upgradeEvents(doc, value);
63
61
  const reused = upgradeReuse(doc, value, file);
64
62
  const cached = upgradeCaches(doc, doc.toJS({ maxAliasCount: 100 }), file);
65
63
  if (cached || events || reused) {