@apex-actions/cli 0.67.0
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/LICENSE +20 -0
- package/README.md +84 -0
- package/THIRD-PARTY-NOTICES.md +1226 -0
- package/dist/cli/actions/main.js +193 -0
- package/dist/cli/approval/main.js +76 -0
- package/dist/cli/artifact/main.js +115 -0
- package/dist/cli/cache/main.js +104 -0
- package/dist/cli/deployment/main.js +73 -0
- package/dist/cli/dispatch/main.js +47 -0
- package/dist/cli/environment/main.js +139 -0
- package/dist/cli/login/main.js +73 -0
- package/dist/cli/logout/main.js +43 -0
- package/dist/cli/logs/main.js +125 -0
- package/dist/cli/lsp/main.js +46 -0
- package/dist/cli/main.js +88 -0
- package/dist/cli/migrate/main.js +275 -0
- package/dist/cli/notify/main.js +257 -0
- package/dist/cli/oidc/main.js +103 -0
- package/dist/cli/reproduce/main.js +73 -0
- package/dist/cli/rerun/main.js +100 -0
- package/dist/cli/run/main.js +50 -0
- package/dist/cli/runner/main.js +238 -0
- package/dist/cli/schedule/main.js +54 -0
- package/dist/cli/search/main.js +123 -0
- package/dist/cli/secret/main.js +102 -0
- package/dist/cli/secret/shared.js +104 -0
- package/dist/cli/spend/main.js +81 -0
- package/dist/cli/terms/main.js +85 -0
- package/dist/cli/tests/main.js +122 -0
- package/dist/cli/timing/main.js +90 -0
- package/dist/cli/validate/main.js +48 -0
- package/dist/cli/variable/main.js +83 -0
- package/dist/cli/whoami/main.js +63 -0
- package/dist/cli/why/main.js +106 -0
- package/dist/src/application/authenticate.js +56 -0
- package/dist/src/application/dispatch-workflow.js +23 -0
- package/dist/src/application/explain-runs.js +40 -0
- package/dist/src/application/index.js +2 -0
- package/dist/src/application/inspect-runs.js +29 -0
- package/dist/src/application/manage-actions.js +26 -0
- package/dist/src/application/manage-approvals.js +24 -0
- package/dist/src/application/manage-artifacts.js +79 -0
- package/dist/src/application/manage-caches.js +41 -0
- package/dist/src/application/manage-environments.js +136 -0
- package/dist/src/application/manage-notifications.js +20 -0
- package/dist/src/application/manage-runners.js +89 -0
- package/dist/src/application/manage-secrets.js +83 -0
- package/dist/src/application/manage-terms.js +34 -0
- package/dist/src/application/read-logs.js +40 -0
- package/dist/src/application/report-spend.js +15 -0
- package/dist/src/application/rerun-runs.js +34 -0
- package/dist/src/application/resolve-run.js +19 -0
- package/dist/src/application/run-workflow.js +17 -0
- package/dist/src/application/scan-migration.js +76 -0
- package/dist/src/application/search-logs.js +24 -0
- package/dist/src/application/serve-language-server.js +15 -0
- package/dist/src/application/validate-workflows.js +12 -0
- package/dist/src/domain/credentials.js +82 -0
- package/dist/src/domain/fleet-history.js +136 -0
- package/dist/src/domain/index.js +2 -0
- package/dist/src/domain/insight.js +42 -0
- package/dist/src/domain/logsearch.js +40 -0
- package/dist/src/domain/migration.js +122 -0
- package/dist/src/domain/notify.js +64 -0
- package/dist/src/domain/outcomediff.js +40 -0
- package/dist/src/domain/provenance.js +63 -0
- package/dist/src/domain/runs.js +90 -0
- package/dist/src/domain/secrets.js +39 -0
- package/dist/src/domain/spend.js +50 -0
- package/dist/src/domain/supplychain.js +48 -0
- package/dist/src/domain/terms.js +16 -0
- package/dist/src/domain/validation.js +4 -0
- package/dist/src/infrastructure/control-plane-http.js +458 -0
- package/dist/src/infrastructure/credentials.js +134 -0
- package/dist/src/infrastructure/engine-binary.js +164 -0
- package/dist/src/infrastructure/github-api.js +154 -0
- package/dist/src/infrastructure/index.js +2 -0
- package/dist/src/infrastructure/loopback-login.js +136 -0
- package/dist/src/infrastructure/runner-binary.js +74 -0
- package/dist/src/infrastructure/secret-input.js +52 -0
- package/package.json +54 -0
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// domain layer — see ../../../../docs/conventions.md §1
|
|
2
|
+
// What the CLI knows about runs and jobs, and the pure decisions it makes
|
|
3
|
+
// about them. The REST shapes come from control-plane/contracts/openapi.
|
|
4
|
+
/** A run is worth following while anything can still change. */
|
|
5
|
+
export const isLive = (status) => status !== 'completed';
|
|
6
|
+
/** The exit code a completed run should give a shell: 0 only for success. */
|
|
7
|
+
export const exitCodeFor = (conclusion) => (conclusion === 'success' ? 0 : 1);
|
|
8
|
+
/**
|
|
9
|
+
* Whether a selector is a run **id** rather than a relative selector.
|
|
10
|
+
*
|
|
11
|
+
* The distinction is the whole of BUG-0080. `latest` and `#12` are *relative*:
|
|
12
|
+
* they mean nothing except against a list, and `#12` means nothing except
|
|
13
|
+
* within one repository. A run id is **absolute and globally unique**, so
|
|
14
|
+
* resolving it by scanning a list is not a lookup — it is a search with a
|
|
15
|
+
* horizon, and the runs worth asking about are the ones past it.
|
|
16
|
+
*
|
|
17
|
+
* The shape is the control plane's own: `runs.id` is a `uuid` column, so every
|
|
18
|
+
* run id there has ever been matches this and nothing else does. A selector
|
|
19
|
+
* that merely *looks* wrong is still handled by the list path, which is where
|
|
20
|
+
* `#12` and a typo belong.
|
|
21
|
+
*/
|
|
22
|
+
const RUN_ID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
23
|
+
export const isRunId = (selector) => RUN_ID.test(selector.trim());
|
|
24
|
+
/**
|
|
25
|
+
* Resolve which run the user meant. `latest` picks the newest; an id matches
|
|
26
|
+
* exactly; `#12` matches a run number, which is what people read off a Check
|
|
27
|
+
* Run and what they will type.
|
|
28
|
+
*
|
|
29
|
+
* Callers should ask {@link isRunId} first and address an id directly: this
|
|
30
|
+
* function can only ever see the runs it was handed.
|
|
31
|
+
*/
|
|
32
|
+
export const selectRun = (runs, selector) => {
|
|
33
|
+
if (runs.length === 0)
|
|
34
|
+
return undefined;
|
|
35
|
+
if (selector === '' || selector === 'latest')
|
|
36
|
+
return runs[0];
|
|
37
|
+
const byId = runs.find((r) => r.id === selector);
|
|
38
|
+
if (byId)
|
|
39
|
+
return byId;
|
|
40
|
+
const number = Number(selector.startsWith('#') ? selector.slice(1) : selector);
|
|
41
|
+
if (Number.isInteger(number))
|
|
42
|
+
return runs.find((r) => r.runNumber === number);
|
|
43
|
+
return undefined;
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* What to say when a selector found no run.
|
|
47
|
+
*
|
|
48
|
+
* A **run id** that finds nothing is a different failure from a selector that
|
|
49
|
+
* finds nothing, and one message described only the second: *"pass a run id,
|
|
50
|
+
* #number, or latest"* tells somebody who passed a perfectly good run id that
|
|
51
|
+
* their id was the problem. Before BUG-0080 it usually was not — the id was
|
|
52
|
+
* simply older than the hundred runs being searched — and now that ids are
|
|
53
|
+
* addressed directly, one reaching here really is absent or unreadable, which
|
|
54
|
+
* is worth saying rather than hinting at.
|
|
55
|
+
*/
|
|
56
|
+
export const unknownRunMessage = (selector) => isRunId(selector)
|
|
57
|
+
? `no run with id ${selector} — it does not exist, or belongs to a repository you cannot read`
|
|
58
|
+
: `no run matches ${selector} — pass a run id, #number, or "latest"`;
|
|
59
|
+
/** Match a job by id, exact name, or unambiguous prefix — nothing guessy. */
|
|
60
|
+
export const selectJobs = (jobs, selector) => {
|
|
61
|
+
if (selector === undefined || selector === '')
|
|
62
|
+
return [...jobs];
|
|
63
|
+
const exact = jobs.filter((j) => j.id === selector || j.name === selector);
|
|
64
|
+
if (exact.length > 0)
|
|
65
|
+
return exact;
|
|
66
|
+
return jobs.filter((j) => j.name.startsWith(selector));
|
|
67
|
+
};
|
|
68
|
+
/** `owner/repo` from a slug, or undefined when it is not one. */
|
|
69
|
+
export const parseRepository = (slug) => {
|
|
70
|
+
const match = /^([^/\s]+)\/([^/\s]+)$/.exec(slug.trim());
|
|
71
|
+
return match && match[1] !== undefined && match[2] !== undefined ? { owner: match[1], repo: match[2] } : undefined;
|
|
72
|
+
};
|
|
73
|
+
/**
|
|
74
|
+
* `--input name=value`, repeatable. Values stay strings here: the engine types
|
|
75
|
+
* them from the workflow's own declaration, and guessing in the CLI would
|
|
76
|
+
* mean two places deciding what `false` is.
|
|
77
|
+
*/
|
|
78
|
+
export const parseInputs = (pairs) => {
|
|
79
|
+
const inputs = {};
|
|
80
|
+
const invalid = [];
|
|
81
|
+
for (const pair of pairs) {
|
|
82
|
+
const at = pair.indexOf('=');
|
|
83
|
+
if (at <= 0) {
|
|
84
|
+
invalid.push(pair);
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
inputs[pair.slice(0, at)] = pair.slice(at + 1);
|
|
88
|
+
}
|
|
89
|
+
return { inputs, invalid };
|
|
90
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// domain layer — see ../../../../docs/conventions.md §1
|
|
2
|
+
export const targetLabel = (target) => (target.kind === 'org' ? 'the organisation' : `${target.owner}/${target.repo}`);
|
|
3
|
+
/**
|
|
4
|
+
* GitHub's naming rule, which the control plane enforces too: letters, digits
|
|
5
|
+
* and underscores, not starting with a digit, and `GITHUB_` is reserved.
|
|
6
|
+
* Checked here as well so a typo costs no round trip and no half-applied loop
|
|
7
|
+
* over repositories.
|
|
8
|
+
*/
|
|
9
|
+
export const nameProblem = (name) => {
|
|
10
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)) {
|
|
11
|
+
return `invalid name ${JSON.stringify(name)}: use letters, digits and underscores, not starting with a digit`;
|
|
12
|
+
}
|
|
13
|
+
if (name.toUpperCase().startsWith('GITHUB_'))
|
|
14
|
+
return `invalid name ${JSON.stringify(name)}: the GITHUB_ prefix is reserved`;
|
|
15
|
+
return undefined;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Exactly one trailing newline off, and nothing else.
|
|
19
|
+
*
|
|
20
|
+
* `echo value | apex secret set` and a file an editor saved both end in one,
|
|
21
|
+
* and nobody means it to be part of the secret. A second newline, or trailing
|
|
22
|
+
* spaces, might be meant, so they stay.
|
|
23
|
+
*/
|
|
24
|
+
export const trimOneNewline = (value) => {
|
|
25
|
+
if (value.endsWith('\r\n'))
|
|
26
|
+
return value.slice(0, -2);
|
|
27
|
+
if (value.endsWith('\n'))
|
|
28
|
+
return value.slice(0, -1);
|
|
29
|
+
return value;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* The command that sets one name, for `migrate scan`'s checklist. A secret's
|
|
33
|
+
* never carries a value — it is read from stdin; a variable's shows where the
|
|
34
|
+
* value goes.
|
|
35
|
+
*/
|
|
36
|
+
export const setterCommand = (kind, name, repos) => {
|
|
37
|
+
const where = repos.length === 0 ? '--repo owner/repo' : repos.map((r) => `--repo ${r}`).join(' ');
|
|
38
|
+
return kind === 'secret' ? `apex secret set ${name} ${where}` : `apex variable set ${name} --value … ${where}`;
|
|
39
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// domain layer — see ../../../../docs/conventions.md §1
|
|
2
|
+
// The REST shapes for a spend report, mirroring control-plane/contracts/openapi.yaml,
|
|
3
|
+
// plus the pure decisions the CLI makes about presenting money.
|
|
4
|
+
/**
|
|
5
|
+
* Money, at a precision that does not pretend.
|
|
6
|
+
*
|
|
7
|
+
* A fleet's monthly bill and one job's share of an instance differ by six
|
|
8
|
+
* orders of magnitude, so a single format is wrong for one of them. Sub-cent
|
|
9
|
+
* amounts keep enough digits to be distinguishable; everything above a dollar
|
|
10
|
+
* drops to cents, because nobody reconciles a budget in millionths.
|
|
11
|
+
*/
|
|
12
|
+
export const formatMicros = (micros) => {
|
|
13
|
+
if (!Number.isFinite(micros))
|
|
14
|
+
return '—';
|
|
15
|
+
const sign = micros < 0 ? '-' : '';
|
|
16
|
+
const abs = Math.abs(micros);
|
|
17
|
+
if (abs === 0)
|
|
18
|
+
return '$0.00';
|
|
19
|
+
if (abs < 10_000)
|
|
20
|
+
return `${sign}$${(abs / 1_000_000).toFixed(6).replace(/0+$/, '').replace(/\.$/, '.0')}`;
|
|
21
|
+
if (abs < 1_000_000)
|
|
22
|
+
return `${sign}$${(abs / 1_000_000).toFixed(4)}`;
|
|
23
|
+
return `${sign}$${(abs / 1_000_000).toFixed(2)}`;
|
|
24
|
+
};
|
|
25
|
+
/** `6h 12m`, `12m 30s`, `45s` — runner time, which people compare against their patience. */
|
|
26
|
+
export const formatSeconds = (seconds) => {
|
|
27
|
+
if (!Number.isFinite(seconds) || seconds < 0)
|
|
28
|
+
return '—';
|
|
29
|
+
const s = Math.round(seconds);
|
|
30
|
+
if (s < 60)
|
|
31
|
+
return `${s}s`;
|
|
32
|
+
const m = Math.floor(s / 60);
|
|
33
|
+
if (m < 60)
|
|
34
|
+
return `${m}m ${s % 60}s`;
|
|
35
|
+
return `${Math.floor(m / 60)}h ${m % 60}m`;
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* The saving as a phrase, or an honest refusal.
|
|
39
|
+
*
|
|
40
|
+
* Undefined percent means nothing could be priced — which is not "saved
|
|
41
|
+
* nothing", and must not be printed as 0%.
|
|
42
|
+
*/
|
|
43
|
+
export const savingPhrase = (savedMicros, percent) => {
|
|
44
|
+
if (percent === undefined)
|
|
45
|
+
return 'nothing could be priced, so there is nothing to compare';
|
|
46
|
+
const direction = savedMicros >= 0 ? 'less than GitHub' : 'MORE than GitHub';
|
|
47
|
+
return `${formatMicros(Math.abs(savedMicros))} ${direction} (${percent >= 0 ? '' : '-'}${Math.abs(percent).toFixed(0)}%)`;
|
|
48
|
+
};
|
|
49
|
+
/** Rows worth printing: everything, but capped so a big org's report stays readable. */
|
|
50
|
+
export const topWorkflows = (workflows, limit) => workflows.slice(0, limit);
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// domain layer — see ../../../../docs/conventions.md §1
|
|
2
|
+
// The REST shapes for the action supply chain, mirroring
|
|
3
|
+
// control-plane/contracts/openapi.yaml, plus the pure decisions the CLI makes
|
|
4
|
+
// about presenting them. No judgement lives here: whether a reference is
|
|
5
|
+
// allowed is the control plane's answer, and a second implementation would be
|
|
6
|
+
// a second answer.
|
|
7
|
+
/**
|
|
8
|
+
* How a pinning classification reads in a list.
|
|
9
|
+
*
|
|
10
|
+
* `movable` is spelled out rather than abbreviated because it is the one that
|
|
11
|
+
* surprises people: `v4.1.1` looks like a version and is a pointer its owner
|
|
12
|
+
* can move.
|
|
13
|
+
*/
|
|
14
|
+
const PINNING_LABELS = {
|
|
15
|
+
sha: 'pinned',
|
|
16
|
+
digest: 'pinned (digest)',
|
|
17
|
+
movable: 'movable',
|
|
18
|
+
local: 'local',
|
|
19
|
+
unparsable: 'unreadable',
|
|
20
|
+
};
|
|
21
|
+
export const pinningLabel = (p) => PINNING_LABELS[p] ?? p;
|
|
22
|
+
/** Every reference whose pinning could change under the repository's feet. */
|
|
23
|
+
export const movable = (refs) => refs.filter((r) => r.pinning === 'movable');
|
|
24
|
+
/**
|
|
25
|
+
* The one-line summary of an inventory.
|
|
26
|
+
*
|
|
27
|
+
* Counts what is movable rather than what is "unsafe": we cannot tell whether
|
|
28
|
+
* an action is safe, only whether it can change.
|
|
29
|
+
*/
|
|
30
|
+
export const inventorySummary = (inv) => {
|
|
31
|
+
const total = inv.references.length;
|
|
32
|
+
const loose = movable(inv.references).length;
|
|
33
|
+
const plural = (n, one, many) => `${n} ${n === 1 ? one : many}`;
|
|
34
|
+
return `${plural(total, 'reference', 'references')}, ${plural(loose, 'movable', 'movable')}`;
|
|
35
|
+
};
|
|
36
|
+
/** `off` reads as a state, not an absence — a policy nobody set is still an answer. */
|
|
37
|
+
export const policyLine = (p) => {
|
|
38
|
+
if (p.mode === 'off')
|
|
39
|
+
return 'off — no reference is checked';
|
|
40
|
+
const parts = [p.mode];
|
|
41
|
+
parts.push(p.allow.length === 0 ? 'allow: everything not denied' : `allow: ${p.allow.join(', ')}`);
|
|
42
|
+
if (p.deny.length > 0)
|
|
43
|
+
parts.push(`deny: ${p.deny.join(', ')}`);
|
|
44
|
+
parts.push(p.requirePinned ? 'commit SHAs required' : 'tags permitted');
|
|
45
|
+
return parts.join(' · ');
|
|
46
|
+
};
|
|
47
|
+
/** Parse a comma-or-repeat separated pattern list from the command line. */
|
|
48
|
+
export const patternList = (values) => (values ?? []).flatMap((v) => v.split(',')).map((v) => v.trim()).filter((v) => v !== '');
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// domain layer — see ../../../../docs/conventions.md §1
|
|
2
|
+
/**
|
|
3
|
+
* Why one account is outstanding, in the words an operator writing the notice
|
|
4
|
+
* needs — *never accepted anything* and *accepted words that have since
|
|
5
|
+
* changed* are different e-mails.
|
|
6
|
+
*/
|
|
7
|
+
export const pendingReason = (row) => row.acceptedVersion === undefined ? 'has never accepted' : `accepted ${row.acceptedVersion}`;
|
|
8
|
+
export const pendingLine = (row) => `${row.owner} (installation ${row.installationId}) — ${pendingReason(row)}`;
|
|
9
|
+
/**
|
|
10
|
+
* The one sentence worth printing when the list is empty, because an empty
|
|
11
|
+
* list is the state this is trying to reach and "nothing to show" reads like a
|
|
12
|
+
* failed query.
|
|
13
|
+
*/
|
|
14
|
+
export const standingSummary = (standing) => standing.pending.length === 0
|
|
15
|
+
? `every account has accepted the terms in force (${standing.version})`
|
|
16
|
+
: `${standing.pending.length} account${standing.pending.length === 1 ? ' owes' : 's owe'} an acceptance of ${standing.version}`;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
// domain layer — see ../../../../docs/conventions.md §1
|
|
2
|
+
// Workflow validation results as reported by the engine (engine ADR-0004).
|
|
3
|
+
export const hasErrors = (report) => report.files.some((f) => f.diagnostics.some((d) => d.severity === 'error'));
|
|
4
|
+
export const diagnosticCount = (report) => report.files.reduce((n, f) => n + f.diagnostics.length, 0);
|