@apex-actions/cli 0.72.0 → 0.73.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/dist/cli/tenant/main.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
2
|
import chalk from 'chalk';
|
|
3
3
|
import { listDepartures, purgeDeparture } from '../../src/application/manage-departures.js';
|
|
4
|
+
import { backfillAccounts } from '../../src/application/manage-accounts.js';
|
|
5
|
+
import { backfillLines } from '../../src/domain/accounts.js';
|
|
4
6
|
import { HttpControlPlane, NotConfiguredError } from '../../src/infrastructure/control-plane-http.js';
|
|
5
7
|
import { countsLine, departureLine } from '../../src/domain/departures.js';
|
|
6
8
|
/** Build the API client, or explain what is missing and stop. */
|
|
@@ -75,7 +77,34 @@ const purgeCommand = new Command('purge')
|
|
|
75
77
|
}
|
|
76
78
|
console.log(chalk.yellow(`${report.repositories.length} repositor${report.repositories.length === 1 ? 'y' : 'ies'} and everything under ${report.repositories.length === 1 ? 'it' : 'them'} would be deleted, with the installation's deliveries and organisation secrets. Re-run with --apply.`));
|
|
77
79
|
});
|
|
80
|
+
const backfillCommand = new Command('backfill')
|
|
81
|
+
.description('Move every installation into one Account owned by --owner, its only member — a dry run unless --apply (TASK-0195)')
|
|
82
|
+
.requiredOption('--owner <login>', 'the GitHub login the Owner signs in with; they must have signed in since 0.72.0')
|
|
83
|
+
.option('--name <name>', 'the Account’s name, when it does not exist yet')
|
|
84
|
+
.option('--keep-paused-trials', 'leave paused trials in the provider (default: cancel them — decisions-log O19)')
|
|
85
|
+
.option('--apply', 'do it (default: report what would be done)')
|
|
86
|
+
.option('--json', 'machine-readable output')
|
|
87
|
+
.action(async (opts) => {
|
|
88
|
+
const client = api();
|
|
89
|
+
if (!client)
|
|
90
|
+
return;
|
|
91
|
+
const outcome = await backfillAccounts(client, { ownerLogin: opts.owner, name: opts.name, cancelPausedTrials: opts.keepPausedTrials !== true }, opts.apply === true);
|
|
92
|
+
if (outcome.kind === 'refused') {
|
|
93
|
+
console.error(chalk.red(outcome.message));
|
|
94
|
+
process.exitCode = 1;
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
if (opts.json === true) {
|
|
98
|
+
console.log(JSON.stringify(outcome.value, null, 2));
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
for (const line of backfillLines(outcome.value))
|
|
102
|
+
console.log(line);
|
|
103
|
+
if (!outcome.value.applied)
|
|
104
|
+
console.log(chalk.yellow('\nNothing was changed. Review the above, then re-run with --apply.'));
|
|
105
|
+
});
|
|
78
106
|
export default new Command('tenant')
|
|
79
|
-
.description(
|
|
107
|
+
.description('Tenants: who left and deleting their data on request, and the one-time move into Accounts')
|
|
80
108
|
.addCommand(departuresCommand)
|
|
81
|
-
.addCommand(purgeCommand)
|
|
109
|
+
.addCommand(purgeCommand)
|
|
110
|
+
.addCommand(backfillCommand);
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one-time move into Accounts — a dry run unless `apply`. The control plane
|
|
3
|
+
* decides everything; the machine token is the only caller it accepts.
|
|
4
|
+
*/
|
|
5
|
+
export const backfillAccounts = async (api, input, apply) => {
|
|
6
|
+
try {
|
|
7
|
+
return { kind: 'ok', value: await api.accountsBackfill(input, apply) };
|
|
8
|
+
}
|
|
9
|
+
catch (err) {
|
|
10
|
+
return { kind: 'refused', message: err instanceof Error ? err.message : String(err) };
|
|
11
|
+
}
|
|
12
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Accounts backfill as the control plane reports it (control-plane
|
|
3
|
+
* `POST /api/v1/admin/accounts/backfill`, TASK-0195), and how a person reads it.
|
|
4
|
+
*/
|
|
5
|
+
const ORG_VERB = {
|
|
6
|
+
connect: ['would join', 'joined'],
|
|
7
|
+
connected: ['already in', 'already in'],
|
|
8
|
+
elsewhere: ['stays in another Account', 'stays in another Account'],
|
|
9
|
+
unreachable: ['left out: GitHub cannot name it', 'left out: GitHub cannot name it'],
|
|
10
|
+
};
|
|
11
|
+
const SUB_VERB = {
|
|
12
|
+
cancel: ['would be cancelled now', 'cancelled'],
|
|
13
|
+
keep: ['kept — live, yours to decide', 'kept — live, yours to decide'],
|
|
14
|
+
leave: ['left as it is', 'left as it is'],
|
|
15
|
+
};
|
|
16
|
+
/** The report as lines, in the tense of what happened: "would" for a dry run. */
|
|
17
|
+
export const backfillLines = (r) => {
|
|
18
|
+
const t = r.applied ? 1 : 0;
|
|
19
|
+
const lines = [
|
|
20
|
+
`Account: ${r.account.name}${r.account.exists ? ` (exists, ${r.account.id ?? '?'})` : r.applied ? ` (created, ${r.account.id ?? '?'})` : ' (would be created)'}`,
|
|
21
|
+
`Owner and only member: ${r.owner.login}`,
|
|
22
|
+
'Organizations:',
|
|
23
|
+
...r.organizations.map((o) => ` ${o.login} — installation ${o.installationId}: ${ORG_VERB[o.action][t]}`),
|
|
24
|
+
];
|
|
25
|
+
if (r.subscriptions.length > 0)
|
|
26
|
+
lines.push('Subscriptions:', ...r.subscriptions.map((s) => ` ${s.owner} (${s.status}): ${SUB_VERB[s.action][t]}`));
|
|
27
|
+
if (r.grants.length > 0) {
|
|
28
|
+
lines.push('Open grants — unchanged here; choose the one to keep when the claim pools per Account:', ...r.grants.map((g) => ` ${g.owner}: ${g.minutesPerMonth} minutes a month, ${g.concurrency} concurrent${g.expiresAt === undefined ? '' : `, until ${g.expiresAt.slice(0, 10)}`}`));
|
|
29
|
+
}
|
|
30
|
+
if (r.notes.length > 0)
|
|
31
|
+
lines.push('Notes:', ...r.notes.map((n) => ` ${n}`));
|
|
32
|
+
return lines;
|
|
33
|
+
};
|
|
@@ -220,6 +220,9 @@ export class HttpControlPlane {
|
|
|
220
220
|
async purgeDeparture(installationId, apply) {
|
|
221
221
|
return await this.#json('POST', `/api/v1/departures/${installationId}/purge${apply ? '?apply=true' : ''}`);
|
|
222
222
|
}
|
|
223
|
+
async accountsBackfill(input, apply) {
|
|
224
|
+
return await this.#json('POST', `/api/v1/admin/accounts/backfill${apply ? '?apply=true' : ''}`, input);
|
|
225
|
+
}
|
|
223
226
|
async actionPolicy(owner) {
|
|
224
227
|
return await this.#json('GET', `/api/v1/orgs/${encodeURIComponent(owner)}/action-policy`);
|
|
225
228
|
}
|