@formigio/fazemos-cli 0.10.50 → 0.10.52
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/commands/secrets.d.ts +34 -0
- package/dist/commands/secrets.js +330 -0
- package/dist/commands/secrets.js.map +1 -0
- package/dist/index.js +202 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* F48 — Project Secrets Vault CLI commands.
|
|
3
|
+
*
|
|
4
|
+
* Exports `registerSecretsCommands(program)` which wires the following
|
|
5
|
+
* subcommands under `fazemos secrets`:
|
|
6
|
+
*
|
|
7
|
+
* fazemos secrets set <NAME> [VALUE]
|
|
8
|
+
* → POST /api/projects/:projectId/secrets {name, value, scope}
|
|
9
|
+
* Owner/admin only. Value is written to AWS Secrets Manager and NEVER
|
|
10
|
+
* stored in Aurora or echoed in any response (R1/R4).
|
|
11
|
+
*
|
|
12
|
+
* fazemos secrets list
|
|
13
|
+
* → GET /api/projects/:projectId/secrets
|
|
14
|
+
* All project members. Returns metadata only — never a value field.
|
|
15
|
+
*
|
|
16
|
+
* fazemos secrets get <NAME>
|
|
17
|
+
* → GET /api/projects/:projectId/secrets/:name (owner-only)
|
|
18
|
+
* Returns SM path + SM ARN for operator use. Value is NEVER returned
|
|
19
|
+
* by this endpoint (SF-4 deliberate divergence from feature-spec AC2).
|
|
20
|
+
* Break-glass value inspection is out-of-band via AWS CLI against SM ARN.
|
|
21
|
+
*
|
|
22
|
+
* fazemos secrets delete <NAME>
|
|
23
|
+
* → DELETE /api/projects/:projectId/secrets/:name (owner/admin)
|
|
24
|
+
* Schedules SM deletion with a 7-day recovery window; soft-deletes Aurora
|
|
25
|
+
* row. Prompts for confirmation unless --yes / -y is passed.
|
|
26
|
+
*
|
|
27
|
+
* Auth: all commands require a valid Cognito session + active project context.
|
|
28
|
+
* Project ID is threaded in the URL path (not the X-Fazemos-Project-Id header)
|
|
29
|
+
* so noProjectHeader: true is passed to api() on every call here.
|
|
30
|
+
*
|
|
31
|
+
* Spec: F48-execution-profiles-manifest.yaml §cli / §api (T4)
|
|
32
|
+
*/
|
|
33
|
+
import type { Command } from 'commander';
|
|
34
|
+
export declare function registerSecretsCommands(program: Command): void;
|
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* F48 — Project Secrets Vault CLI commands.
|
|
3
|
+
*
|
|
4
|
+
* Exports `registerSecretsCommands(program)` which wires the following
|
|
5
|
+
* subcommands under `fazemos secrets`:
|
|
6
|
+
*
|
|
7
|
+
* fazemos secrets set <NAME> [VALUE]
|
|
8
|
+
* → POST /api/projects/:projectId/secrets {name, value, scope}
|
|
9
|
+
* Owner/admin only. Value is written to AWS Secrets Manager and NEVER
|
|
10
|
+
* stored in Aurora or echoed in any response (R1/R4).
|
|
11
|
+
*
|
|
12
|
+
* fazemos secrets list
|
|
13
|
+
* → GET /api/projects/:projectId/secrets
|
|
14
|
+
* All project members. Returns metadata only — never a value field.
|
|
15
|
+
*
|
|
16
|
+
* fazemos secrets get <NAME>
|
|
17
|
+
* → GET /api/projects/:projectId/secrets/:name (owner-only)
|
|
18
|
+
* Returns SM path + SM ARN for operator use. Value is NEVER returned
|
|
19
|
+
* by this endpoint (SF-4 deliberate divergence from feature-spec AC2).
|
|
20
|
+
* Break-glass value inspection is out-of-band via AWS CLI against SM ARN.
|
|
21
|
+
*
|
|
22
|
+
* fazemos secrets delete <NAME>
|
|
23
|
+
* → DELETE /api/projects/:projectId/secrets/:name (owner/admin)
|
|
24
|
+
* Schedules SM deletion with a 7-day recovery window; soft-deletes Aurora
|
|
25
|
+
* row. Prompts for confirmation unless --yes / -y is passed.
|
|
26
|
+
*
|
|
27
|
+
* Auth: all commands require a valid Cognito session + active project context.
|
|
28
|
+
* Project ID is threaded in the URL path (not the X-Fazemos-Project-Id header)
|
|
29
|
+
* so noProjectHeader: true is passed to api() on every call here.
|
|
30
|
+
*
|
|
31
|
+
* Spec: F48-execution-profiles-manifest.yaml §cli / §api (T4)
|
|
32
|
+
*/
|
|
33
|
+
import chalk from 'chalk';
|
|
34
|
+
import { readFileSync } from 'fs';
|
|
35
|
+
import { createInterface } from 'readline';
|
|
36
|
+
import { api, ApiError, resolveProjectIdBySlug } from '../api.js';
|
|
37
|
+
// ── Project resolution ────────────────────────────────────────────────────────
|
|
38
|
+
/**
|
|
39
|
+
* Resolve the active project ID for a secrets command.
|
|
40
|
+
*
|
|
41
|
+
* Applies the standard F15 resolution chain:
|
|
42
|
+
* --project <slug> override → resolveProjectIdBySlug (cache + refresh)
|
|
43
|
+
* active project from config → getActiveProjectId()
|
|
44
|
+
*
|
|
45
|
+
* Exits 1 with the uniform "requirement missing: project" block (KD9) if
|
|
46
|
+
* neither a slug override nor an active project can be found.
|
|
47
|
+
*/
|
|
48
|
+
async function requireProjectForSecrets(slugOverride) {
|
|
49
|
+
let projectId;
|
|
50
|
+
try {
|
|
51
|
+
projectId = await resolveProjectIdBySlug(slugOverride);
|
|
52
|
+
}
|
|
53
|
+
catch (err) {
|
|
54
|
+
// resolveProjectIdBySlug throws ApiError(UNKNOWN_PROJECT) on slug miss
|
|
55
|
+
console.error(chalk.red(err?.message ?? String(err)));
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
|
58
|
+
if (!projectId) {
|
|
59
|
+
console.error(chalk.red('Error: requirement missing: project'));
|
|
60
|
+
console.error('');
|
|
61
|
+
console.error(chalk.gray('Set one with: fazemos projects switch <slug>'));
|
|
62
|
+
console.error(chalk.gray('Or pass: --project <slug>'));
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
return projectId;
|
|
66
|
+
}
|
|
67
|
+
// ── Interactive value prompt ──────────────────────────────────────────────────
|
|
68
|
+
/**
|
|
69
|
+
* Prompt for a secret value on a TTY, masking typed characters with *.
|
|
70
|
+
* Falls back to reading from piped stdin when process.stdin is not a TTY.
|
|
71
|
+
*
|
|
72
|
+
* Manifests the "echoed as ***" behaviour specified in the cli.commands.set
|
|
73
|
+
* behaviour block — typed input is not echoed in plaintext so the value does
|
|
74
|
+
* not appear in terminal scrollback.
|
|
75
|
+
*/
|
|
76
|
+
function promptSecretValue(name) {
|
|
77
|
+
return new Promise((resolve, reject) => {
|
|
78
|
+
if (!process.stdin.isTTY) {
|
|
79
|
+
// Piped stdin: accumulate all data, then trim trailing newline.
|
|
80
|
+
let data = '';
|
|
81
|
+
process.stdin.setEncoding('utf8');
|
|
82
|
+
process.stdin.on('data', (chunk) => { data += chunk; });
|
|
83
|
+
process.stdin.on('end', () => resolve(data.trimEnd()));
|
|
84
|
+
process.stdin.on('error', reject);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
// Interactive TTY: mask every typed character with *.
|
|
88
|
+
const rl = createInterface({
|
|
89
|
+
input: process.stdin,
|
|
90
|
+
output: process.stdout,
|
|
91
|
+
terminal: true,
|
|
92
|
+
});
|
|
93
|
+
// Override the readline output writer so typed chars appear as *.
|
|
94
|
+
// Only suppress the echoed input — the prompt text and the final newline
|
|
95
|
+
// are still written normally so the UX stays legible.
|
|
96
|
+
rl._writeToOutput = function (s) {
|
|
97
|
+
if (s === '\r\n' || s === '\n' || s === '\r') {
|
|
98
|
+
process.stdout.write('\n');
|
|
99
|
+
}
|
|
100
|
+
else if (s && s !== '\u0003') {
|
|
101
|
+
// Each typed character → one * (mask everything except Ctrl-C)
|
|
102
|
+
process.stdout.write('*');
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
// Write the prompt ourselves (before the readline question) so the
|
|
106
|
+
// question('') call does not emit a blank prefix.
|
|
107
|
+
process.stdout.write(`Enter value for ${chalk.cyan(name)}: `);
|
|
108
|
+
rl.question('', (answer) => {
|
|
109
|
+
rl.close();
|
|
110
|
+
resolve(answer);
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
// ── Confirmation prompt ───────────────────────────────────────────────────────
|
|
115
|
+
/**
|
|
116
|
+
* Prompt for y/N confirmation. Returns true only if the user types 'y' or 'Y'.
|
|
117
|
+
* Defaults to NO on empty input or any other key (safe default for destructive
|
|
118
|
+
* operations like secret deletion).
|
|
119
|
+
*/
|
|
120
|
+
function confirmPrompt(message) {
|
|
121
|
+
return new Promise((resolve) => {
|
|
122
|
+
const rl = createInterface({
|
|
123
|
+
input: process.stdin,
|
|
124
|
+
output: process.stdout,
|
|
125
|
+
});
|
|
126
|
+
rl.question(`${message} [y/N] `, (answer) => {
|
|
127
|
+
rl.close();
|
|
128
|
+
resolve(answer.trim().toLowerCase() === 'y');
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
// ── Command registration ──────────────────────────────────────────────────────
|
|
133
|
+
export function registerSecretsCommands(program) {
|
|
134
|
+
const secrets = program
|
|
135
|
+
.command('secrets')
|
|
136
|
+
.description('Project secret management (F48 — secrets vault)');
|
|
137
|
+
// ── secrets set ────────────────────────────────────────────────────────────
|
|
138
|
+
secrets
|
|
139
|
+
.command('set')
|
|
140
|
+
.description('Write a project secret to the vault (owner/admin only). ' +
|
|
141
|
+
'Value is stored in AWS Secrets Manager — NEVER in Aurora or echoed back.')
|
|
142
|
+
.argument('<name>', 'Secret name — letters, numbers, and underscores only (max 256 chars)')
|
|
143
|
+
.argument('[value]', 'Secret value (omit to prompt interactively or pipe via stdin)')
|
|
144
|
+
.option('--value-from <file>', 'Read secret value from a file (avoids shell history)')
|
|
145
|
+
.option('--scope <scope>', "Secret scope (default: 'project'; only valid value in Phase A)", 'project')
|
|
146
|
+
.option('--project <slug>', 'Override active project for this call')
|
|
147
|
+
.action(async (name, valueArg, opts) => {
|
|
148
|
+
try {
|
|
149
|
+
const projectId = await requireProjectForSecrets(opts.project);
|
|
150
|
+
// Resolve value in priority order:
|
|
151
|
+
// 1. Positional arg (provided inline — user accepts history risk)
|
|
152
|
+
// 2. --value-from <file> (avoids shell history)
|
|
153
|
+
// 3. stdin (piped) or interactive masked prompt
|
|
154
|
+
let value;
|
|
155
|
+
if (valueArg !== undefined) {
|
|
156
|
+
value = valueArg;
|
|
157
|
+
}
|
|
158
|
+
else if (opts.valueFrom) {
|
|
159
|
+
try {
|
|
160
|
+
value = readFileSync(opts.valueFrom, 'utf-8').trimEnd();
|
|
161
|
+
}
|
|
162
|
+
catch (err) {
|
|
163
|
+
console.error(chalk.red(`Error reading --value-from file: ${err.message}`));
|
|
164
|
+
process.exit(1);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
value = await promptSecretValue(name);
|
|
169
|
+
}
|
|
170
|
+
if (!value) {
|
|
171
|
+
console.error(chalk.red('Error: Secret value cannot be empty'));
|
|
172
|
+
process.exit(1);
|
|
173
|
+
}
|
|
174
|
+
const body = { name, value, scope: opts.scope };
|
|
175
|
+
// Project ID is in the URL path; suppress the X-Fazemos-Project-Id header
|
|
176
|
+
// to avoid a duplicate / mismatched header on path-scoped endpoints.
|
|
177
|
+
const data = await api('POST', `/api/projects/${projectId}/secrets`, body, { noProjectHeader: true });
|
|
178
|
+
console.log(chalk.green(`Secret ${name} written.`));
|
|
179
|
+
console.log(` SM path: ${data.smPath}`);
|
|
180
|
+
}
|
|
181
|
+
catch (err) {
|
|
182
|
+
if (err instanceof ApiError) {
|
|
183
|
+
if (err.code === 'INSUFFICIENT_ROLE') {
|
|
184
|
+
console.error(chalk.red('Error: Only project owners and admins can write secrets'));
|
|
185
|
+
}
|
|
186
|
+
else if (err.code === 'INVALID_NAME') {
|
|
187
|
+
console.error(chalk.red('Error: Secret name must contain only letters, numbers, and underscores (max 256 chars)'));
|
|
188
|
+
}
|
|
189
|
+
else if (err.code === 'INVALID_SCOPE') {
|
|
190
|
+
console.error(chalk.red("Error: --scope must be 'project'"));
|
|
191
|
+
}
|
|
192
|
+
else {
|
|
193
|
+
console.error(chalk.red(err.message));
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
else {
|
|
197
|
+
console.error(chalk.red(err?.message ?? String(err)));
|
|
198
|
+
}
|
|
199
|
+
process.exit(1);
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
// ── secrets list ──────────────────────────────────────────────────────────
|
|
203
|
+
secrets
|
|
204
|
+
.command('list')
|
|
205
|
+
.description('List project secrets — metadata only, values are never returned')
|
|
206
|
+
.option('--project <slug>', 'Override active project for this call')
|
|
207
|
+
.action(async (opts) => {
|
|
208
|
+
try {
|
|
209
|
+
const projectId = await requireProjectForSecrets(opts.project);
|
|
210
|
+
const data = await api('GET', `/api/projects/${projectId}/secrets`, undefined, { noProjectHeader: true });
|
|
211
|
+
const items = data.secrets ?? [];
|
|
212
|
+
if (items.length === 0) {
|
|
213
|
+
console.log(chalk.yellow('No secrets'));
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
// Compute column widths from data (minimum widths from header labels).
|
|
217
|
+
const nameW = Math.max(4, ...items.map((s) => String(s.name ?? '').length));
|
|
218
|
+
const scopeW = Math.max(5, ...items.map((s) => String(s.scope ?? '').length));
|
|
219
|
+
const pathW = Math.max(7, ...items.map((s) => String(s.smPath ?? '').length));
|
|
220
|
+
const header = [
|
|
221
|
+
'NAME'.padEnd(nameW),
|
|
222
|
+
'SCOPE'.padEnd(scopeW),
|
|
223
|
+
'SM PATH'.padEnd(pathW),
|
|
224
|
+
'CREATED AT',
|
|
225
|
+
].join(' ');
|
|
226
|
+
console.log(chalk.gray(header));
|
|
227
|
+
console.log(chalk.gray('─'.repeat(header.length)));
|
|
228
|
+
for (const s of items) {
|
|
229
|
+
// Explicitly exclude any `value` field — the API contract forbids it,
|
|
230
|
+
// but the CLI must never print it even if the response accidentally
|
|
231
|
+
// includes one (R4: no Fazemos path ever returns plaintext values).
|
|
232
|
+
const created = s.createdAt
|
|
233
|
+
? new Date(s.createdAt).toLocaleString()
|
|
234
|
+
: (s.created_at ?? '');
|
|
235
|
+
console.log([
|
|
236
|
+
String(s.name ?? '').padEnd(nameW),
|
|
237
|
+
String(s.scope ?? '').padEnd(scopeW),
|
|
238
|
+
String(s.smPath ?? '').padEnd(pathW),
|
|
239
|
+
created,
|
|
240
|
+
].join(' '));
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
catch (err) {
|
|
244
|
+
console.error(chalk.red(err?.message ?? String(err)));
|
|
245
|
+
process.exit(1);
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
// ── secrets get ───────────────────────────────────────────────────────────
|
|
249
|
+
secrets
|
|
250
|
+
.command('get')
|
|
251
|
+
.description('Get secret metadata (owner-only). ' +
|
|
252
|
+
'Returns SM path + SM ARN — the value is NEVER returned by this command (SF-4). ' +
|
|
253
|
+
'Use the AWS CLI against the SM ARN for break-glass value inspection.')
|
|
254
|
+
.argument('<name>', 'Secret name')
|
|
255
|
+
.option('--project <slug>', 'Override active project for this call')
|
|
256
|
+
.action(async (name, opts) => {
|
|
257
|
+
try {
|
|
258
|
+
const projectId = await requireProjectForSecrets(opts.project);
|
|
259
|
+
const data = await api('GET', `/api/projects/${projectId}/secrets/${encodeURIComponent(name)}`, undefined, { noProjectHeader: true });
|
|
260
|
+
// NEVER print a value field — metadata-only (SF-4 / R4).
|
|
261
|
+
console.log(chalk.cyan(`Secret: ${data.name}`));
|
|
262
|
+
console.log(` Scope: ${data.scope}`);
|
|
263
|
+
console.log(` SM path: ${data.smPath}`);
|
|
264
|
+
console.log(` SM ARN: ${data.smArn ?? '(not yet populated)'}`);
|
|
265
|
+
console.log(` Created by: ${data.createdByMemberId ?? '(unknown)'}`);
|
|
266
|
+
console.log(` Created at: ${data.createdAt ? new Date(data.createdAt).toLocaleString() : ''}`);
|
|
267
|
+
console.log(` Updated at: ${data.updatedAt ? new Date(data.updatedAt).toLocaleString() : ''}`);
|
|
268
|
+
console.log('');
|
|
269
|
+
console.log(chalk.gray('Break-glass value inspection: aws secretsmanager get-secret-value --secret-id <SM ARN above>'));
|
|
270
|
+
}
|
|
271
|
+
catch (err) {
|
|
272
|
+
if (err instanceof ApiError) {
|
|
273
|
+
if (err.code === 'INSUFFICIENT_ROLE') {
|
|
274
|
+
console.error(chalk.red('Error: Only project owners can retrieve secret metadata'));
|
|
275
|
+
}
|
|
276
|
+
else if (err.code === 'SECRET_NOT_FOUND') {
|
|
277
|
+
console.error(chalk.red(`Error: Secret '${name}' not found`));
|
|
278
|
+
}
|
|
279
|
+
else {
|
|
280
|
+
console.error(chalk.red(err.message));
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
else {
|
|
284
|
+
console.error(chalk.red(err?.message ?? String(err)));
|
|
285
|
+
}
|
|
286
|
+
process.exit(1);
|
|
287
|
+
}
|
|
288
|
+
});
|
|
289
|
+
// ── secrets delete ────────────────────────────────────────────────────────
|
|
290
|
+
secrets
|
|
291
|
+
.command('delete')
|
|
292
|
+
.description('Delete a project secret (owner/admin only). ' +
|
|
293
|
+
'Schedules 7-day SM recovery window; soft-deletes the Aurora metadata row. ' +
|
|
294
|
+
'The secret can be re-created at the same name within the window.')
|
|
295
|
+
.argument('<name>', 'Secret name')
|
|
296
|
+
.option('--project <slug>', 'Override active project for this call')
|
|
297
|
+
.option('-y, --yes', 'Skip confirmation prompt')
|
|
298
|
+
.action(async (name, opts) => {
|
|
299
|
+
try {
|
|
300
|
+
const projectId = await requireProjectForSecrets(opts.project);
|
|
301
|
+
if (!opts.yes) {
|
|
302
|
+
const confirmed = await confirmPrompt(`Delete secret ${chalk.cyan(name)}? This schedules SM deletion with a 7-day recovery window.`);
|
|
303
|
+
if (!confirmed) {
|
|
304
|
+
console.log('Aborted.');
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
await api('DELETE', `/api/projects/${projectId}/secrets/${encodeURIComponent(name)}`, undefined, { noProjectHeader: true });
|
|
309
|
+
console.log(chalk.green(`Secret ${name} deleted (7-day SM recovery window).`));
|
|
310
|
+
}
|
|
311
|
+
catch (err) {
|
|
312
|
+
if (err instanceof ApiError) {
|
|
313
|
+
if (err.code === 'INSUFFICIENT_ROLE') {
|
|
314
|
+
console.error(chalk.red('Error: Only project owners and admins can delete secrets'));
|
|
315
|
+
}
|
|
316
|
+
else if (err.code === 'SECRET_NOT_FOUND') {
|
|
317
|
+
console.error(chalk.red(`Error: Secret '${name}' not found`));
|
|
318
|
+
}
|
|
319
|
+
else {
|
|
320
|
+
console.error(chalk.red(err.message));
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
else {
|
|
324
|
+
console.error(chalk.red(err?.message ?? String(err)));
|
|
325
|
+
}
|
|
326
|
+
process.exit(1);
|
|
327
|
+
}
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
//# sourceMappingURL=secrets.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"secrets.js","sourceRoot":"","sources":["../../src/commands/secrets.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -25,6 +25,7 @@ import { registerAutoStartCommands } from './autostart.js';
|
|
|
25
25
|
import { registerFtrCommand } from './ftr.js';
|
|
26
26
|
import { registerScheduleCommands } from './schedule.js';
|
|
27
27
|
import { registerApprovalsCommands } from './approvals.js';
|
|
28
|
+
import { registerSecretsCommands } from './commands/secrets.js';
|
|
28
29
|
import { parseExecutionsJson, resolveWaitOptions, waitForPipelines, buildAwsCommand, validateExecutionEntry, } from './wait-for-pipeline.js';
|
|
29
30
|
import { readFileSync, readdirSync, writeFileSync, mkdirSync, existsSync, statSync } from 'fs';
|
|
30
31
|
import { fileURLToPath } from 'url';
|
|
@@ -1294,6 +1295,72 @@ function readScopeOpts(opts) {
|
|
|
1294
1295
|
return { projectSlug: opts.project };
|
|
1295
1296
|
return {};
|
|
1296
1297
|
}
|
|
1298
|
+
/**
|
|
1299
|
+
* P-TOOL-1 — render a rematerialize reconciliation plan (preview or applied).
|
|
1300
|
+
*
|
|
1301
|
+
* The plan has five step-disposition buckets:
|
|
1302
|
+
* preserved completed/skipped steps kept frozen
|
|
1303
|
+
* rematerialized pending/blocked/failed steps re-derived from the new def
|
|
1304
|
+
* inserted brand-new template steps
|
|
1305
|
+
* removed step_instances absent from the new def
|
|
1306
|
+
* reset completed downstream steps that --reset-downstream-of-new
|
|
1307
|
+
* will RE-RUN (empty unless the flag is set) — the
|
|
1308
|
+
* preview/apply-fidelity bucket, surfaced under "will RE-RUN".
|
|
1309
|
+
*
|
|
1310
|
+
* Each StepDisposition is { step_instance_id, template_step_id, step_name,
|
|
1311
|
+
* action, from_status, to_status }. Warnings render yellow; blockers render red.
|
|
1312
|
+
*/
|
|
1313
|
+
function renderRematerializePlan(plan, warnings, blockers, render) {
|
|
1314
|
+
// process.stderr for the blocked-path so the plan travels with the error;
|
|
1315
|
+
// process.stdout otherwise. Commander's console.log/console.error split.
|
|
1316
|
+
const out = render.blockedHeader ? console.error : console.log;
|
|
1317
|
+
const p = plan ?? {};
|
|
1318
|
+
const buckets = [
|
|
1319
|
+
['preserved', Array.isArray(p.preserved) ? p.preserved : []],
|
|
1320
|
+
['rematerialized', Array.isArray(p.rematerialized) ? p.rematerialized : []],
|
|
1321
|
+
['inserted', Array.isArray(p.inserted) ? p.inserted : []],
|
|
1322
|
+
['removed', Array.isArray(p.removed) ? p.removed : []],
|
|
1323
|
+
['reset', Array.isArray(p.reset) ? p.reset : []],
|
|
1324
|
+
];
|
|
1325
|
+
// Count summary line.
|
|
1326
|
+
const counts = buckets.map(([name, rows]) => `${name}: ${rows.length}`).join(' | ');
|
|
1327
|
+
out(chalk.gray(` ${counts}`));
|
|
1328
|
+
// Per-step table, grouped by disposition. reset[] gets a distinct
|
|
1329
|
+
// "will RE-RUN" heading so an operator sees what apply will actually do.
|
|
1330
|
+
const rowLine = (d) => {
|
|
1331
|
+
const name = d?.step_name ?? d?.template_step_id ?? '(unknown)';
|
|
1332
|
+
const from = d?.from_status ?? '—';
|
|
1333
|
+
const to = d?.to_status ?? '—';
|
|
1334
|
+
return from === to ? ` ${name} [${to}]` : ` ${name} ${from} → ${to}`;
|
|
1335
|
+
};
|
|
1336
|
+
for (const [name, rows] of buckets) {
|
|
1337
|
+
if (rows.length === 0)
|
|
1338
|
+
continue;
|
|
1339
|
+
if (name === 'reset') {
|
|
1340
|
+
out(chalk.magenta(` will RE-RUN (reset ${rows.length}):`));
|
|
1341
|
+
}
|
|
1342
|
+
else {
|
|
1343
|
+
out(chalk.cyan(` ${name} (${rows.length}):`));
|
|
1344
|
+
}
|
|
1345
|
+
for (const d of rows)
|
|
1346
|
+
out(rowLine(d));
|
|
1347
|
+
}
|
|
1348
|
+
const warns = Array.isArray(warnings) ? warnings : [];
|
|
1349
|
+
if (warns.length) {
|
|
1350
|
+
out(chalk.yellow(` warnings (${warns.length}):`));
|
|
1351
|
+
for (const w of warns) {
|
|
1352
|
+
out(chalk.yellow(` - ${w?.code ?? '?'}${w?.template_step_id ? ` [${w.template_step_id}]` : ''}: ${w?.detail ?? ''}`));
|
|
1353
|
+
}
|
|
1354
|
+
}
|
|
1355
|
+
const blocks = Array.isArray(blockers) ? blockers : [];
|
|
1356
|
+
if (blocks.length) {
|
|
1357
|
+
out(chalk.red(` blockers (${blocks.length}):`));
|
|
1358
|
+
for (const b of blocks) {
|
|
1359
|
+
const offending = Array.isArray(b?.offending) && b.offending.length ? ` (${b.offending.join(', ')})` : '';
|
|
1360
|
+
out(chalk.red(` - ${b?.code ?? '?'}: ${b?.detail ?? ''}${offending}`));
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
1363
|
+
}
|
|
1297
1364
|
/**
|
|
1298
1365
|
* Uniform error handler for scoped commands. When the API emits
|
|
1299
1366
|
* MISSING_PROJECT_CONTEXT (§7.3.1), the api helper has already re-shaped
|
|
@@ -5419,6 +5486,132 @@ pipelines
|
|
|
5419
5486
|
process.exit(1);
|
|
5420
5487
|
}
|
|
5421
5488
|
});
|
|
5489
|
+
// ── P-TOOL-1 — `pl rematerialize <instanceId>` ──
|
|
5490
|
+
// Rebase a live pipeline instance onto the CURRENT template definition without
|
|
5491
|
+
// losing completed-step state. Safe-by-default: WITHOUT --apply the verb runs a
|
|
5492
|
+
// preview (dry_run=true) and prints the reconciliation plan, mutating nothing.
|
|
5493
|
+
//
|
|
5494
|
+
// POST /api/pipeline-instances/:id/rematerialize
|
|
5495
|
+
// Body : { dry_run | apply, reason?, force_inflight?, reset_downstream_of_new?, keep_failed? }
|
|
5496
|
+
// Header : If-Match: "<n>" (opt-in via --expect-version; default bypass)
|
|
5497
|
+
//
|
|
5498
|
+
// The API is itself safe-by-default (dry_run defaults to true server-side), so a
|
|
5499
|
+
// bare call previews. We are explicit anyway: send { dry_run: true } on preview
|
|
5500
|
+
// and { apply: true } on --apply. Owner/admin-only for apply and for the
|
|
5501
|
+
// dangerous --force-inflight / --reset-downstream-of-new flags (D5 SPLIT).
|
|
5502
|
+
pipelines
|
|
5503
|
+
.command('rematerialize')
|
|
5504
|
+
.description('Rebase a live pipeline instance onto the current template definition, preserving completed-step state. ' +
|
|
5505
|
+
'Safe default: WITHOUT --apply it previews the reconciliation plan (mutates nothing). ' +
|
|
5506
|
+
'With --apply it reconciles the instance in one transaction. Apply (and the --force-inflight / ' +
|
|
5507
|
+
'--reset-downstream-of-new flags) require org owner or admin.')
|
|
5508
|
+
.argument('<instanceId>', 'Pipeline instance ID')
|
|
5509
|
+
.option('--apply', 'Perform the mutation. WITHOUT it the verb previews (dry_run=true) and prints the plan.')
|
|
5510
|
+
.option('--force-inflight', 'Cancel in-flight step executions and reconcile anyway (D3 override). Owner/admin only.')
|
|
5511
|
+
.option('--reset-downstream-of-new', 'Cascade-reset completed steps downstream of a newly-inserted step so they re-run (D6 override). Owner/admin only.')
|
|
5512
|
+
.option('--keep-failed', 'Leave failed steps in "failed" instead of recovering them to "pending" (D4 opt-out).')
|
|
5513
|
+
.option('-r, --reason <reason>', 'Free-form audit reason (recorded in audit_log on apply).')
|
|
5514
|
+
.option('--expect-version <n>', 'Optimistic-concurrency guard: send If-Match: "<n>" so the API rejects the call (409 VERSION_CONFLICT) ' +
|
|
5515
|
+
'if the current instance version differs. Omit to bypass version checking (default).')
|
|
5516
|
+
.option('--json', 'Print the raw API response as JSON (machine-readable)')
|
|
5517
|
+
.action(async (instanceId, opts) => {
|
|
5518
|
+
try {
|
|
5519
|
+
// Body field names match the API contract verbatim:
|
|
5520
|
+
// dry_run / apply — mode select (preview vs mutate)
|
|
5521
|
+
// reason — optional audit string (apply only, but harmless on preview)
|
|
5522
|
+
// force_inflight — D3 override
|
|
5523
|
+
// reset_downstream_of_new — D6 override
|
|
5524
|
+
// keep_failed — D4 opt-out
|
|
5525
|
+
const apply = opts.apply === true;
|
|
5526
|
+
const body = apply ? { apply: true } : { dry_run: true };
|
|
5527
|
+
if (opts.forceInflight)
|
|
5528
|
+
body.force_inflight = true;
|
|
5529
|
+
if (opts.resetDownstreamOfNew)
|
|
5530
|
+
body.reset_downstream_of_new = true;
|
|
5531
|
+
if (opts.keepFailed)
|
|
5532
|
+
body.keep_failed = true;
|
|
5533
|
+
if (opts.reason)
|
|
5534
|
+
body.reason = opts.reason;
|
|
5535
|
+
// --expect-version <n> opts the caller in to optimistic concurrency.
|
|
5536
|
+
// Default is bypass (no header), mirroring force-transition Decision #4.
|
|
5537
|
+
const apiOpts = {};
|
|
5538
|
+
if (opts.expectVersion !== undefined) {
|
|
5539
|
+
const n = Number(opts.expectVersion);
|
|
5540
|
+
if (!Number.isInteger(n)) {
|
|
5541
|
+
console.error(chalk.red(`--expect-version must be an integer; got "${opts.expectVersion}"`));
|
|
5542
|
+
process.exit(1);
|
|
5543
|
+
}
|
|
5544
|
+
apiOpts.headers = { 'If-Match': String(n) };
|
|
5545
|
+
}
|
|
5546
|
+
const path = `/api/pipeline-instances/${instanceId}/rematerialize`;
|
|
5547
|
+
let data;
|
|
5548
|
+
try {
|
|
5549
|
+
data = (await api('POST', path, body, apiOpts));
|
|
5550
|
+
}
|
|
5551
|
+
catch (err) {
|
|
5552
|
+
// 409 REMATERIALIZE_BLOCKED carries the plan + per-step blockers in its
|
|
5553
|
+
// body; surface them so the operator sees exactly what to fix.
|
|
5554
|
+
if (err instanceof ApiError && err.code === 'REMATERIALIZE_BLOCKED') {
|
|
5555
|
+
if (opts.json) {
|
|
5556
|
+
console.log(JSON.stringify(err.body ?? { code: err.code, error: err.message }, null, 2));
|
|
5557
|
+
process.exit(1);
|
|
5558
|
+
}
|
|
5559
|
+
const b = (err.body ?? {});
|
|
5560
|
+
console.error(chalk.red(`Rematerialize BLOCKED — instance ${instanceId} (template v${b.from_version} → v${b.to_version})`));
|
|
5561
|
+
renderRematerializePlan(b.plan, b.warnings, b.blockers, { blockedHeader: true });
|
|
5562
|
+
console.error(chalk.red('\nResolve the blockers above and retry.'));
|
|
5563
|
+
process.exit(1);
|
|
5564
|
+
}
|
|
5565
|
+
// 409 VERSION_CONFLICT — the If-Match pin did not match the live version.
|
|
5566
|
+
if (err instanceof ApiError && err.code === 'VERSION_CONFLICT') {
|
|
5567
|
+
console.error(chalk.red(`Version conflict: the instance was modified concurrently (--expect-version mismatch). ${err.message}`));
|
|
5568
|
+
process.exit(1);
|
|
5569
|
+
}
|
|
5570
|
+
// 403 owner/admin gate on apply / dangerous flags (or project-only agent).
|
|
5571
|
+
if (err instanceof ApiError && err.status === 403) {
|
|
5572
|
+
console.error(chalk.red(`Permission denied: ${err.message}`));
|
|
5573
|
+
process.exit(1);
|
|
5574
|
+
}
|
|
5575
|
+
throw err;
|
|
5576
|
+
}
|
|
5577
|
+
if (opts.json) {
|
|
5578
|
+
console.log(JSON.stringify(data, null, 2));
|
|
5579
|
+
return;
|
|
5580
|
+
}
|
|
5581
|
+
// 200 ALREADY_CURRENT — idempotent no-op (returned as 200, not an error).
|
|
5582
|
+
if (data?.already_current === true) {
|
|
5583
|
+
console.log(chalk.green(`Already current: instance ${instanceId} is already on template v${data.to_version}. No changes made.`));
|
|
5584
|
+
return;
|
|
5585
|
+
}
|
|
5586
|
+
if (data?.dry_run === true) {
|
|
5587
|
+
// Preview mode — print the plan and the run-again hint.
|
|
5588
|
+
console.log(chalk.bold(`Rematerialize preview — instance ${instanceId} (template v${data.from_version} → v${data.to_version})`));
|
|
5589
|
+
renderRematerializePlan(data.plan, data.warnings, data.blockers, {});
|
|
5590
|
+
if (Array.isArray(data.blockers) && data.blockers.length > 0) {
|
|
5591
|
+
console.log(chalk.yellow('\nThis plan has blockers — apply would be refused (409 REMATERIALIZE_BLOCKED) until they are resolved.'));
|
|
5592
|
+
}
|
|
5593
|
+
console.log(chalk.gray('\nRun again with --apply to execute.'));
|
|
5594
|
+
return;
|
|
5595
|
+
}
|
|
5596
|
+
// Applied — print the applied summary + forensic echo.
|
|
5597
|
+
console.log(chalk.green(`Rematerialized instance ${instanceId} (template v${data.from_version} → v${data.to_version})`));
|
|
5598
|
+
renderRematerializePlan(data.plan, data.warnings, data.blockers, {});
|
|
5599
|
+
if (data?.audit_log_id) {
|
|
5600
|
+
console.log(chalk.gray(` audit_log_id: ${data.audit_log_id}`));
|
|
5601
|
+
}
|
|
5602
|
+
const queued = Array.isArray(data?.queued_step_ids) ? data.queued_step_ids : [];
|
|
5603
|
+
if (queued.length) {
|
|
5604
|
+
console.log(chalk.gray(` queued_step_ids (${queued.length}): ${queued.join(', ')}`));
|
|
5605
|
+
}
|
|
5606
|
+
else {
|
|
5607
|
+
console.log(chalk.gray(' queued_step_ids: (none)'));
|
|
5608
|
+
}
|
|
5609
|
+
}
|
|
5610
|
+
catch (err) {
|
|
5611
|
+
console.error(chalk.red(err.message));
|
|
5612
|
+
process.exit(1);
|
|
5613
|
+
}
|
|
5614
|
+
});
|
|
5422
5615
|
pipelines
|
|
5423
5616
|
.command('set-params')
|
|
5424
5617
|
.description('Set instance parameters (atomic update)')
|
|
@@ -9997,6 +10190,15 @@ registerScheduleCommands(program);
|
|
|
9997
10190
|
// --reason is mandatory for resolve — CLI typed-token equivalent (OBD #1).
|
|
9998
10191
|
// Auth: requireAuth + org membership (server-side, F37 shipped).
|
|
9999
10192
|
registerApprovalsCommands(program);
|
|
10193
|
+
// ── F48 — Project Secrets Vault: secrets set / list / get / delete ────────────
|
|
10194
|
+
// Registers `secrets` top-level command with set, list, get, delete sub-commands.
|
|
10195
|
+
// All calls use project ID in the URL path (/api/projects/:projectId/secrets);
|
|
10196
|
+
// noProjectHeader: true on every call (path-scoped, not header-scoped).
|
|
10197
|
+
// set/delete: owner/admin only. get: owner-only (metadata+smArn, never value).
|
|
10198
|
+
// list: all project members (metadata only — values are never returned, R4).
|
|
10199
|
+
// set supports interactive masked input, --value-from <file>, positional arg,
|
|
10200
|
+
// and piped stdin. delete prompts for confirmation unless --yes / -y is passed.
|
|
10201
|
+
registerSecretsCommands(program);
|
|
10000
10202
|
// Skip auto-parse only when running under Vitest (which sets process.env.VITEST).
|
|
10001
10203
|
// Tests import `program` and drive it via `program.parseAsync(...)` after mocking
|
|
10002
10204
|
// `./api.js`. In every other context — direct invocation, npx tsx, OR the bin
|