@apex-actions/cli 0.88.1 → 0.89.1
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/dist/cli/migrate/main.js +8 -6
- package/dist/cli/runner/main.js +2 -0
- package/dist/cli/secret/main.js +4 -1
- package/dist/cli/secret/shared.js +15 -1
- package/dist/src/domain/secrets.js +13 -1
- package/dist/src/infrastructure/control-plane-http.js +5 -3
- package/dist/src/infrastructure/runner-binary.js +2 -0
- package/package.json +1 -1
package/dist/cli/migrate/main.js
CHANGED
|
@@ -119,19 +119,21 @@ const printScan = (scan, limit) => {
|
|
|
119
119
|
* first thing somebody evaluating a migration wants: an answer about a
|
|
120
120
|
* repository they already have, without handing anybody credentials.
|
|
121
121
|
*/
|
|
122
|
-
const deps = (options, engine) => {
|
|
123
|
-
const checker = new EngineBinaryCompat(engine);
|
|
122
|
+
const deps = async (options, engine) => {
|
|
124
123
|
if (options.path !== undefined)
|
|
125
|
-
return { workflows: new LocalWorkflowSource(options.path), checker };
|
|
124
|
+
return { workflows: new LocalWorkflowSource(options.path), checker: new EngineBinaryCompat(await engine()) };
|
|
125
|
+
// The token first: resolving the engine can download it (ADR-0040), and a scan that cannot run must say so before
|
|
126
|
+
// spending seven megabytes on it.
|
|
127
|
+
let api;
|
|
126
128
|
try {
|
|
127
|
-
|
|
128
|
-
return { workflows: api, checker, names: api };
|
|
129
|
+
api = new GitHubApi();
|
|
129
130
|
}
|
|
130
131
|
catch (err) {
|
|
131
132
|
console.error(chalk.red(err instanceof NoTokenError ? err.message : String(err)));
|
|
132
133
|
process.exitCode = 2;
|
|
133
134
|
return undefined;
|
|
134
135
|
}
|
|
136
|
+
return { workflows: api, checker: new EngineBinaryCompat(await engine()), names: api };
|
|
135
137
|
};
|
|
136
138
|
const handler = async (org, options) => {
|
|
137
139
|
if (org === undefined && options.path === undefined) {
|
|
@@ -139,7 +141,7 @@ const handler = async (org, options) => {
|
|
|
139
141
|
process.exitCode = 2;
|
|
140
142
|
return;
|
|
141
143
|
}
|
|
142
|
-
const built = deps(options,
|
|
144
|
+
const built = await deps(options, () => resolveBinary('apex-engine', 'APEX_ENGINE_BIN'));
|
|
143
145
|
if (built === undefined)
|
|
144
146
|
return;
|
|
145
147
|
// A local scan has no organization. The directory's name is what the person
|
package/dist/cli/runner/main.js
CHANGED
|
@@ -127,6 +127,7 @@ const registerCommand = new Command('register')
|
|
|
127
127
|
.description('Attach this machine as a self-hosted runner and start leasing work')
|
|
128
128
|
.requiredOption('--labels <labels>', 'comma-separated labels, e.g. self-hosted,linux,gpu')
|
|
129
129
|
.option('--server <host:port>', 'runner endpoint (APEX_RUNNER_SERVER)', process.env['APEX_RUNNER_SERVER'])
|
|
130
|
+
.option('--server-ca <file>', "PEM file with the endpoint's certificate; set, the runner speaks TLS and trusts only it (APEX_RUNNER_SERVER_CA)", process.env['APEX_RUNNER_SERVER_CA'])
|
|
130
131
|
.option('--name <name>', 'display name (default: this hostname)')
|
|
131
132
|
.option('--work-dir <path>', 'scratch root for job workspaces')
|
|
132
133
|
.option('--idle-timeout <duration>', 'stop after this long with no job, e.g. 30m (default: never)')
|
|
@@ -145,6 +146,7 @@ const registerCommand = new Command('register')
|
|
|
145
146
|
console.error(chalk.dim(`registering ${opts.name ?? 'this machine'} with ${opts.server} as [${opts.labels}]`));
|
|
146
147
|
const outcome = await registerRunner(client, new RunnerBinaryExecutor(await resolveBinary('apex-runner', 'APEX_RUNNER_BIN')), {
|
|
147
148
|
server: opts.server,
|
|
149
|
+
serverCa: opts.serverCa,
|
|
148
150
|
labels: opts.labels.split(','),
|
|
149
151
|
name: opts.name,
|
|
150
152
|
workDir: opts.workDir,
|
package/dist/cli/secret/main.js
CHANGED
|
@@ -2,7 +2,7 @@ import { Command, Option } from 'commander';
|
|
|
2
2
|
import chalk from 'chalk';
|
|
3
3
|
import { batchExitCode, listSecrets, setEverywhere } from '../../src/application/manage-secrets.js';
|
|
4
4
|
import { ValueInputError, promptHidden, readToEnd, readValueFile } from '../../src/infrastructure/secret-input.js';
|
|
5
|
-
import { api, collect, deleteAction, ORG_OPTION, printResults, reachLabel, reachOf, targetsFrom, usage, validName } from './shared.js';
|
|
5
|
+
import { api, APP_OPTION, collect, deleteAction, ORG_OPTION, printResults, reachLabel, reachOf, targetsFrom, usage, validName } from './shared.js';
|
|
6
6
|
/**
|
|
7
7
|
* The refusal, in full, because the person who typed `--value` deserves the
|
|
8
8
|
* reason and the alternative in one breath (BUG-0157).
|
|
@@ -31,6 +31,7 @@ const setCommand = new Command('set')
|
|
|
31
31
|
.argument('<name>', 'secret name, as a workflow reads it: secrets.NAME')
|
|
32
32
|
.option('--repo <owner/repo>', 'repository to set it on (repeatable: one value, many repositories)', collect, [])
|
|
33
33
|
.option(...ORG_OPTION)
|
|
34
|
+
.option(...APP_OPTION)
|
|
34
35
|
.option('--visibility <visibility>', 'with --org <organization>: which repositories reach it — all (default), private or selected')
|
|
35
36
|
.option('--repos <names>', 'with --org <organization>: the repositories that reach it, comma-separated (implies --visibility selected)')
|
|
36
37
|
.option('--from-file <path>', 'read the value from this file (one trailing newline is dropped)')
|
|
@@ -67,6 +68,7 @@ const listCommand = new Command('list')
|
|
|
67
68
|
.description("A repository's secret names — never values: the API has none to give")
|
|
68
69
|
.option('--repo <owner/repo>', 'repository', collect, [])
|
|
69
70
|
.option(...ORG_OPTION)
|
|
71
|
+
.option(...APP_OPTION)
|
|
70
72
|
.option('--json', 'machine-readable output')
|
|
71
73
|
.action(async (opts) => {
|
|
72
74
|
const targets = targetsFrom(opts, true);
|
|
@@ -100,6 +102,7 @@ const deleteCommand = new Command('delete')
|
|
|
100
102
|
.argument('<name>', 'secret name')
|
|
101
103
|
.option('--repo <owner/repo>', 'repository (repeatable)', collect, [])
|
|
102
104
|
.option(...ORG_OPTION)
|
|
105
|
+
.option(...APP_OPTION)
|
|
103
106
|
.option('-y, --yes', 'do not ask; required when not on a terminal')
|
|
104
107
|
.action(deleteAction('secret'));
|
|
105
108
|
export default new Command('secret')
|
|
@@ -2,10 +2,12 @@ import chalk from 'chalk';
|
|
|
2
2
|
import { splitRepository } from '../../src/application/manage-caches.js';
|
|
3
3
|
import { batchExitCode, deleteEverywhere } from '../../src/application/manage-secrets.js';
|
|
4
4
|
import { confirm } from '../../src/infrastructure/secret-input.js';
|
|
5
|
-
import { nameProblem, reachFrom } from '../../src/domain/secrets.js';
|
|
5
|
+
import { nameProblem, reachFrom, secretAppFrom } from '../../src/domain/secrets.js';
|
|
6
6
|
import { HttpControlPlane, NotConfiguredError } from '../../src/infrastructure/control-plane-http.js';
|
|
7
7
|
/** What `--org` says in every subcommand's help. */
|
|
8
8
|
export const ORG_OPTION = ['--org [organization]', 'an organization’s scope, by its GitHub login (bare: the operator organization)'];
|
|
9
|
+
/** `gh secret --app`'s spelling (context TASK-0394): Dependabot's store is handed only to a run Dependabot started. */
|
|
10
|
+
export const APP_OPTION = ['--app <app>', 'which store: actions (default) or dependabot, handed only to runs Dependabot started'];
|
|
9
11
|
export const collect = (value, previous) => [...previous, value];
|
|
10
12
|
export const usage = (message) => {
|
|
11
13
|
console.error(chalk.red(message));
|
|
@@ -33,6 +35,18 @@ export const validName = (name) => {
|
|
|
33
35
|
* is for `list`, which answers about one place at a time.
|
|
34
36
|
*/
|
|
35
37
|
export const targetsFrom = (opts, single = false) => {
|
|
38
|
+
const found = placesFrom(opts, single);
|
|
39
|
+
if (found === undefined)
|
|
40
|
+
return undefined;
|
|
41
|
+
const app = secretAppFrom(opts.app);
|
|
42
|
+
if (typeof app !== 'string') {
|
|
43
|
+
usage(app.problem);
|
|
44
|
+
return undefined;
|
|
45
|
+
}
|
|
46
|
+
// The Actions store is the default and stays unnamed; only `apex secret` offers --app.
|
|
47
|
+
return app === 'actions' ? found : found.map((t) => ({ ...t, app }));
|
|
48
|
+
};
|
|
49
|
+
const placesFrom = (opts, single) => {
|
|
36
50
|
if (opts.org !== undefined && opts.org !== false && opts.repo.length > 0) {
|
|
37
51
|
usage('pass --repo or --org, not both');
|
|
38
52
|
return undefined;
|
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
// domain layer — see ../../../../docs/conventions.md §1
|
|
2
|
-
|
|
2
|
+
/** GitHub's two secret stores, as `gh secret --app` names them: every workflow's, and Dependabot's runs' only. */
|
|
3
|
+
export const SECRET_APPS = ['actions', 'dependabot'];
|
|
4
|
+
/** `--app` into a store, or the reason it is not one. Absent is `actions`. */
|
|
5
|
+
export const secretAppFrom = (app) => {
|
|
6
|
+
if (app === undefined)
|
|
7
|
+
return 'actions';
|
|
8
|
+
const lower = app.toLowerCase();
|
|
9
|
+
return SECRET_APPS.includes(lower) ? lower : { problem: `--app is one of ${SECRET_APPS.join(', ')}, not ${JSON.stringify(app)}` };
|
|
10
|
+
};
|
|
11
|
+
export const targetLabel = (target) => {
|
|
12
|
+
const place = target.kind === 'repo' ? `${target.owner}/${target.repo}` : target.org === undefined ? 'the organisation' : `the ${target.org} organization`;
|
|
13
|
+
return target.app === 'dependabot' ? `${place} (Dependabot)` : place;
|
|
14
|
+
};
|
|
3
15
|
/** Which of an Organization's repositories an organisation entry reaches — GitHub's three. */
|
|
4
16
|
export const VISIBILITIES = ['all', 'private', 'selected'];
|
|
5
17
|
/**
|
|
@@ -15,6 +15,8 @@ export class HttpError extends Error {
|
|
|
15
15
|
}
|
|
16
16
|
}
|
|
17
17
|
/** A named Organization's reach, as the API takes it (control-plane ADR-0084); nothing for any other scope. */
|
|
18
|
+
/** Dependabot's store is named; the Actions store is the routes' default, so its paths are unchanged (TASK-0394). */
|
|
19
|
+
const appQuery = (target) => (target.app === 'dependabot' ? '?app=dependabot' : '');
|
|
18
20
|
const reachBody = (target, reach) => target.kind === 'org' && target.org !== undefined && reach !== undefined
|
|
19
21
|
? { visibility: reach.visibility, ...(reach.visibility === 'selected' ? { selectedRepositories: [...reach.repos] } : {}) }
|
|
20
22
|
: {};
|
|
@@ -95,11 +97,11 @@ export class HttpControlPlane {
|
|
|
95
97
|
}
|
|
96
98
|
// ── secrets and variables (values are write-only) ──
|
|
97
99
|
async listSecrets(target) {
|
|
98
|
-
const body = await this.#json('GET', `${this.#scopePath(target)}/secrets`);
|
|
100
|
+
const body = await this.#json('GET', `${this.#scopePath(target)}/secrets${appQuery(target)}`);
|
|
99
101
|
return body.secrets;
|
|
100
102
|
}
|
|
101
103
|
async setSecret(target, name, value, reach) {
|
|
102
|
-
await this.#noContent('PUT', `${this.#scopePath(target)}/secrets/${encodeURIComponent(name)}`, { value, ...reachBody(target, reach) });
|
|
104
|
+
await this.#noContent('PUT', `${this.#scopePath(target)}/secrets/${encodeURIComponent(name)}${appQuery(target)}`, { value, ...reachBody(target, reach) });
|
|
103
105
|
}
|
|
104
106
|
/**
|
|
105
107
|
* `apex logout`: revoke the token this client authenticates with. A 401 means
|
|
@@ -113,7 +115,7 @@ export class HttpControlPlane {
|
|
|
113
115
|
throw new HttpError(await this.#errorMessage(response), response.status);
|
|
114
116
|
}
|
|
115
117
|
async deleteSecret(target, name) {
|
|
116
|
-
return await this.#deleted(`${this.#scopePath(target)}/secrets/${encodeURIComponent(name)}`);
|
|
118
|
+
return await this.#deleted(`${this.#scopePath(target)}/secrets/${encodeURIComponent(name)}${appQuery(target)}`);
|
|
117
119
|
}
|
|
118
120
|
async listVariables(target) {
|
|
119
121
|
const body = await this.#json('GET', `${this.#scopePath(target)}/variables`);
|
|
@@ -29,6 +29,8 @@ export const runnerArgs = (request) => {
|
|
|
29
29
|
/** Builds the remote-mode argv for `apex runner register` (pure, tested). */
|
|
30
30
|
export const registerArgs = (request) => {
|
|
31
31
|
const args = ['--server', request.server, '--registration-token', request.token, '--labels', request.labels.join(',')];
|
|
32
|
+
if (request.serverCa !== undefined)
|
|
33
|
+
args.push('--server-ca', request.serverCa);
|
|
32
34
|
if (request.name !== undefined)
|
|
33
35
|
args.push('--name', request.name);
|
|
34
36
|
if (request.workDir !== undefined)
|