@formigio/fazemos-cli 0.10.7 → 0.10.10
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/api.d.ts +8 -0
- package/dist/api.js +8 -0
- package/dist/api.js.map +1 -1
- package/dist/index.js +136 -12
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/api.d.ts
CHANGED
|
@@ -29,6 +29,14 @@ export interface ApiOptions {
|
|
|
29
29
|
projectSlug?: string;
|
|
30
30
|
allProjects?: boolean;
|
|
31
31
|
noProjectHeader?: boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Extra request headers merged on top of the standard auth/org/project
|
|
34
|
+
* headers. Used by callers that need optimistic-concurrency or other
|
|
35
|
+
* out-of-band headers (e.g., I30 force-transition's `If-Match: <n>` opt-in).
|
|
36
|
+
* Caller-supplied keys win on conflict — this is intentional so a caller
|
|
37
|
+
* can override `Content-Type` if needed.
|
|
38
|
+
*/
|
|
39
|
+
headers?: Record<string, string>;
|
|
32
40
|
}
|
|
33
41
|
/**
|
|
34
42
|
* Resolve the project id to send as X-Fazemos-Project-Id on a scoped call.
|
package/dist/api.js
CHANGED
|
@@ -119,6 +119,14 @@ export async function api(method, path, body, opts = {}) {
|
|
|
119
119
|
headers['X-Fazemos-Project-Id'] = projectId;
|
|
120
120
|
}
|
|
121
121
|
}
|
|
122
|
+
// I30 — caller-supplied extra headers (e.g., If-Match for opt-in optimistic
|
|
123
|
+
// concurrency on force-transition). Applied last so callers can override
|
|
124
|
+
// anything we set above on purpose.
|
|
125
|
+
if (opts.headers) {
|
|
126
|
+
for (const [k, v] of Object.entries(opts.headers)) {
|
|
127
|
+
headers[k] = v;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
122
130
|
// Build URL with optional ?view=all append. We don't try to be clever
|
|
123
131
|
// about merging with existing query strings here — callers that use
|
|
124
132
|
// --all-projects know they're on GET list endpoints with no other query.
|
package/dist/api.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api.js","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,MAAM,EACN,QAAQ,EACR,cAAc,EACd,kBAAkB,EAClB,iBAAiB,EACjB,cAAc,EACd,gBAAgB,GAEjB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAE3C;;;;;;;GAOG;AACH,MAAM,OAAO,QAAS,SAAQ,KAAK;IACjC,MAAM,CAAS;IACf,IAAI,CAAU;IACd,IAAI,CAAW;IACf,YAAY,OAAe,EAAE,MAAc,EAAE,IAAa,EAAE,IAAc;QACxE,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,UAAU,CAAC;QACvB,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IACnB,CAAC;CACF;
|
|
1
|
+
{"version":3,"file":"api.js","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -19,6 +19,20 @@ function parseNumber(val) {
|
|
|
19
19
|
throw new Error(`Invalid number: ${val}`);
|
|
20
20
|
return n;
|
|
21
21
|
}
|
|
22
|
+
function parseStreamSilenceAbortMs(val) {
|
|
23
|
+
const n = Number(val);
|
|
24
|
+
if (!Number.isInteger(n) || n < 30000 || n > 1800000) {
|
|
25
|
+
throw new Error(`stream_silence_abort_ms must be an integer between 30000 and 1800000 (got ${val})`);
|
|
26
|
+
}
|
|
27
|
+
return n;
|
|
28
|
+
}
|
|
29
|
+
function formatMsHuman(ms) {
|
|
30
|
+
if (ms >= 60000) {
|
|
31
|
+
const min = ms / 60000;
|
|
32
|
+
return Number.isInteger(min) ? `${min} min` : `${min.toFixed(1)} min`;
|
|
33
|
+
}
|
|
34
|
+
return `${ms / 1000}s`;
|
|
35
|
+
}
|
|
22
36
|
function parseJson(val, flag) {
|
|
23
37
|
try {
|
|
24
38
|
return JSON.parse(val);
|
|
@@ -2809,6 +2823,9 @@ templates
|
|
|
2809
2823
|
else if (step.outputs?.length) {
|
|
2810
2824
|
console.log(' Inputs: none');
|
|
2811
2825
|
}
|
|
2826
|
+
if (step.stream_silence_abort_ms) {
|
|
2827
|
+
console.log(` Stream Silence Abort: ${step.stream_silence_abort_ms.toLocaleString()}ms (${formatMsHuman(step.stream_silence_abort_ms)})`);
|
|
2828
|
+
}
|
|
2812
2829
|
if (step.execution_config && Object.keys(step.execution_config).length) {
|
|
2813
2830
|
const ec = step.execution_config;
|
|
2814
2831
|
console.log(' Execution config:');
|
|
@@ -3161,6 +3178,7 @@ templates
|
|
|
3161
3178
|
.option('--after <stepId>', 'Insert after this step within the same phase (sets sort_order to target + 1, shifts subsequent steps). Target must live in the phase named by --phase.')
|
|
3162
3179
|
.option('--sort-order <n>', 'Set sort_order directly (lower-level escape hatch)', parseNumber)
|
|
3163
3180
|
.option('--agent-config <json>', 'Per-step agent config overrides as JSON (e.g., \'{"model":"opus","maxBudgetUsd":20}\'). Overrides agent member defaults for: model, maxBudgetUsd, maxTurns, timeoutMs, cwd, repos')
|
|
3181
|
+
.option('--stream-silence-abort-ms <ms>', 'Stream silence abort threshold in milliseconds (30000-1800000)', parseStreamSilenceAbortMs)
|
|
3164
3182
|
.action(async (templateId, opts) => {
|
|
3165
3183
|
try {
|
|
3166
3184
|
if (!VALID_STEP_TYPES.includes(opts.type)) {
|
|
@@ -3220,6 +3238,8 @@ templates
|
|
|
3220
3238
|
};
|
|
3221
3239
|
if (opts.agentConfig)
|
|
3222
3240
|
step.agent_config = parseJson(opts.agentConfig, '--agent-config');
|
|
3241
|
+
if (opts.streamSilenceAbortMs !== undefined)
|
|
3242
|
+
step.stream_silence_abort_ms = opts.streamSilenceAbortMs;
|
|
3223
3243
|
// Positioning: --after or --sort-order
|
|
3224
3244
|
if (opts.after && opts.sortOrder !== undefined) {
|
|
3225
3245
|
console.error(chalk.red('Cannot use both --after and --sort-order'));
|
|
@@ -3245,6 +3265,9 @@ templates
|
|
|
3245
3265
|
await api('PUT', `/api/pipeline-templates/${templateId}`, { definition: t.definition });
|
|
3246
3266
|
console.log(chalk.green(`Added step: ${opts.name} (${opts.type}) to phase ${phase.name}`));
|
|
3247
3267
|
console.log(` ID: ${step.id}`);
|
|
3268
|
+
if (step.stream_silence_abort_ms) {
|
|
3269
|
+
console.log(` Stream Silence Abort: ${step.stream_silence_abort_ms.toLocaleString()}ms (${formatMsHuman(step.stream_silence_abort_ms)})`);
|
|
3270
|
+
}
|
|
3248
3271
|
}
|
|
3249
3272
|
catch (err) {
|
|
3250
3273
|
console.error(chalk.red(err.message));
|
|
@@ -3313,12 +3336,13 @@ templates
|
|
|
3313
3336
|
.option('--env <json>', 'Environment variables for script steps as JSON')
|
|
3314
3337
|
.option('--sections <text>', 'Agent instructions / step content. Use @filepath to load from a file (e.g., --sections @steps/review.md)')
|
|
3315
3338
|
.option('--agent-config <json>', 'Per-step agent config overrides as JSON (merges with existing)')
|
|
3339
|
+
.option('--stream-silence-abort-ms <ms>', 'Stream silence abort threshold in milliseconds (30000-1800000)', parseStreamSilenceAbortMs)
|
|
3316
3340
|
.action(async (templateId, opts) => {
|
|
3317
3341
|
try {
|
|
3318
3342
|
const hasUpdate = opts.name || opts.type || opts.role || opts.description != null
|
|
3319
3343
|
|| opts.reviewer != null || opts.maxReviewCycles != null || opts.parallelGroup != null
|
|
3320
3344
|
|| opts.image || opts.command || opts.timeout !== undefined || opts.workingDir || opts.env
|
|
3321
|
-
|| opts.agentConfig || opts.sections != null;
|
|
3345
|
+
|| opts.agentConfig || opts.sections != null || opts.streamSilenceAbortMs !== undefined;
|
|
3322
3346
|
if (!hasUpdate) {
|
|
3323
3347
|
console.error(chalk.red('Provide at least one field to update'));
|
|
3324
3348
|
process.exit(1);
|
|
@@ -3387,6 +3411,10 @@ templates
|
|
|
3387
3411
|
if (opts.agentConfig) {
|
|
3388
3412
|
step.agent_config = { ...(step.agent_config || {}), ...parseJson(opts.agentConfig, '--agent-config') };
|
|
3389
3413
|
}
|
|
3414
|
+
// stream silence abort threshold
|
|
3415
|
+
if (opts.streamSilenceAbortMs !== undefined) {
|
|
3416
|
+
step.stream_silence_abort_ms = opts.streamSilenceAbortMs;
|
|
3417
|
+
}
|
|
3390
3418
|
await api('PUT', `/api/pipeline-templates/${templateId}`, { definition: t.definition });
|
|
3391
3419
|
console.log(chalk.green(`Updated step: ${step.name}`));
|
|
3392
3420
|
}
|
|
@@ -3994,18 +4022,114 @@ pipelines
|
|
|
3994
4022
|
process.exit(1);
|
|
3995
4023
|
}
|
|
3996
4024
|
});
|
|
4025
|
+
// I30 — `pl force-cancel` removed. Replaced by the generalised
|
|
4026
|
+
// `pl force-transition pipeline <id> --to cancelled` admin endpoint, which
|
|
4027
|
+
// covers the same emergency cancel path plus 12 more entity/state combos
|
|
4028
|
+
// across pipelines, steps, and executions. No shim: callers update or 404.
|
|
3997
4029
|
pipelines
|
|
3998
|
-
.command('force-
|
|
3999
|
-
.description('Force
|
|
4000
|
-
|
|
4001
|
-
|
|
4002
|
-
.
|
|
4003
|
-
|
|
4004
|
-
|
|
4030
|
+
.command('force-transition')
|
|
4031
|
+
.description('Force an entity (pipeline, step, or execution) into a target state. Admin / org owner / project member only. ' +
|
|
4032
|
+
'Bypasses normal state transition rules and fires the appropriate subscriber chain (audit log, ECS stop, ' +
|
|
4033
|
+
'agent reset, token revoke, etc.). Use only when the normal lifecycle is stuck.')
|
|
4034
|
+
.argument('<entity-type>', 'Entity type: pipeline | step | execution')
|
|
4035
|
+
.argument('<id>', 'Entity ID (pipeline_instance / step_instance / execution UUID)')
|
|
4036
|
+
.requiredOption('--to <state>', 'Target state (e.g., cancelled, failed, completed, in_progress, ...)')
|
|
4037
|
+
.option('-r, --reason <reason>', 'Free-form audit reason (recorded in audit_log)')
|
|
4038
|
+
.option('--output-data-file <path>', 'Path to a JSON file with output_data. Required when forcing a step with a structured output schema to "completed".')
|
|
4039
|
+
.option('--expect-version <n>', 'Optimistic-concurrency guard: send If-Match: "<n>" so the API rejects the call (409) ' +
|
|
4040
|
+
'if the current entity version differs. Omit to bypass version checking (default).')
|
|
4041
|
+
.option('--json', 'Print the raw API response as JSON (machine-readable)')
|
|
4042
|
+
.action(async (entityType, id, opts) => {
|
|
4043
|
+
try {
|
|
4044
|
+
const valid = ['pipeline', 'step', 'execution'];
|
|
4045
|
+
if (!valid.includes(entityType)) {
|
|
4046
|
+
console.error(chalk.red(`Invalid entity-type "${entityType}". Must be one of: ${valid.join(', ')}`));
|
|
4047
|
+
process.exit(1);
|
|
4048
|
+
}
|
|
4049
|
+
if (!opts.to || typeof opts.to !== 'string' || !opts.to.trim()) {
|
|
4050
|
+
console.error(chalk.red('--to <state> is required'));
|
|
4051
|
+
process.exit(1);
|
|
4052
|
+
}
|
|
4053
|
+
// Body field names match the API contract verbatim:
|
|
4054
|
+
// target_state — required
|
|
4055
|
+
// reason — optional, free-form audit string
|
|
4056
|
+
// output_data — required for step→completed when the step has an
|
|
4057
|
+
// output schema; carrier rejects with 400 OUTPUT_DATA_*
|
|
4058
|
+
// otherwise.
|
|
4059
|
+
const body = { target_state: opts.to };
|
|
4005
4060
|
if (opts.reason)
|
|
4006
4061
|
body.reason = opts.reason;
|
|
4007
|
-
|
|
4008
|
-
|
|
4062
|
+
if (opts.outputDataFile) {
|
|
4063
|
+
if (entityType !== 'step') {
|
|
4064
|
+
console.error(chalk.red('--output-data-file is only valid when entity-type is "step"'));
|
|
4065
|
+
process.exit(1);
|
|
4066
|
+
}
|
|
4067
|
+
let raw;
|
|
4068
|
+
try {
|
|
4069
|
+
raw = readFileSync(resolve(opts.outputDataFile), 'utf-8');
|
|
4070
|
+
}
|
|
4071
|
+
catch (err) {
|
|
4072
|
+
console.error(chalk.red(`Cannot read --output-data-file "${opts.outputDataFile}": ${err.message}`));
|
|
4073
|
+
process.exit(1);
|
|
4074
|
+
return;
|
|
4075
|
+
}
|
|
4076
|
+
let parsed;
|
|
4077
|
+
try {
|
|
4078
|
+
parsed = JSON.parse(raw);
|
|
4079
|
+
}
|
|
4080
|
+
catch (err) {
|
|
4081
|
+
console.error(chalk.red(`Invalid JSON in --output-data-file "${opts.outputDataFile}": ${err.message}`));
|
|
4082
|
+
process.exit(1);
|
|
4083
|
+
return;
|
|
4084
|
+
}
|
|
4085
|
+
body.output_data = parsed;
|
|
4086
|
+
}
|
|
4087
|
+
// --expect-version <n> opts the caller in to optimistic concurrency.
|
|
4088
|
+
// Default is bypass (no header) per the I30 spec Decision #4.
|
|
4089
|
+
const apiOpts = {};
|
|
4090
|
+
if (opts.expectVersion !== undefined) {
|
|
4091
|
+
const n = Number(opts.expectVersion);
|
|
4092
|
+
if (!Number.isInteger(n)) {
|
|
4093
|
+
console.error(chalk.red(`--expect-version must be an integer; got "${opts.expectVersion}"`));
|
|
4094
|
+
process.exit(1);
|
|
4095
|
+
}
|
|
4096
|
+
apiOpts.headers = { 'If-Match': String(n) };
|
|
4097
|
+
}
|
|
4098
|
+
const path = `/api/admin/force-transition/${entityType}/${id}`;
|
|
4099
|
+
const data = (await api('POST', path, body, apiOpts));
|
|
4100
|
+
if (opts.json) {
|
|
4101
|
+
console.log(JSON.stringify(data, null, 2));
|
|
4102
|
+
return;
|
|
4103
|
+
}
|
|
4104
|
+
// Human-readable summary. Per-entity payloads diverge (instance / step /
|
|
4105
|
+
// execution) so we surface whichever is present and always echo the
|
|
4106
|
+
// forensic fields (audit_log_id, subscribers_fired, subscriber_failures,
|
|
4107
|
+
// orphan_executions_cancelled).
|
|
4108
|
+
const entityPayload = data?.instance || data?.step || data?.execution || null;
|
|
4109
|
+
const label = entityPayload?.name || entityPayload?.step_name || entityPayload?.id || id;
|
|
4110
|
+
console.log(chalk.green(`Force-transitioned ${entityType} ${label} → ${opts.to}`));
|
|
4111
|
+
if (data?.audit_log_id) {
|
|
4112
|
+
console.log(chalk.gray(` audit_log_id: ${data.audit_log_id}`));
|
|
4113
|
+
}
|
|
4114
|
+
const fired = Array.isArray(data?.subscribers_fired) ? data.subscribers_fired : [];
|
|
4115
|
+
if (fired.length) {
|
|
4116
|
+
console.log(chalk.gray(` subscribers_fired: ${fired.join(', ')}`));
|
|
4117
|
+
}
|
|
4118
|
+
const failures = Array.isArray(data?.subscriber_failures)
|
|
4119
|
+
? data.subscriber_failures
|
|
4120
|
+
: [];
|
|
4121
|
+
if (failures.length) {
|
|
4122
|
+
console.log(chalk.yellow(` subscriber_failures: ${failures.length}`));
|
|
4123
|
+
for (const f of failures) {
|
|
4124
|
+
console.log(chalk.yellow(` - ${f.id || f.subscriber || '?'}: ${f.error}`));
|
|
4125
|
+
}
|
|
4126
|
+
}
|
|
4127
|
+
const orphans = Array.isArray(data?.orphan_executions_cancelled)
|
|
4128
|
+
? data.orphan_executions_cancelled
|
|
4129
|
+
: [];
|
|
4130
|
+
if (orphans.length) {
|
|
4131
|
+
console.log(chalk.gray(` orphan_executions_cancelled: ${orphans.join(', ')}`));
|
|
4132
|
+
}
|
|
4009
4133
|
}
|
|
4010
4134
|
catch (err) {
|
|
4011
4135
|
console.error(chalk.red(err.message));
|
|
@@ -4030,7 +4154,7 @@ pipelines
|
|
|
4030
4154
|
});
|
|
4031
4155
|
pipelines
|
|
4032
4156
|
.command('set-status')
|
|
4033
|
-
.description('Update instance status. Allowed: paused, cancelled. Use "pl force-
|
|
4157
|
+
.description('Update instance status. Allowed: paused, cancelled. Use "pl force-transition pipeline <id> --to cancelled" for stuck instances.')
|
|
4034
4158
|
.argument('<id>', 'Instance ID')
|
|
4035
4159
|
.requiredOption('--status <status>', 'New status (paused or cancelled)')
|
|
4036
4160
|
.action(async (id, opts) => {
|
|
@@ -4038,7 +4162,7 @@ pipelines
|
|
|
4038
4162
|
const valid = ['paused', 'cancelled'];
|
|
4039
4163
|
if (!valid.includes(opts.status)) {
|
|
4040
4164
|
console.error(chalk.red(`Invalid status. Must be one of: ${valid.join(', ')}`));
|
|
4041
|
-
console.error(chalk.yellow('Tip: Use "pl force-
|
|
4165
|
+
console.error(chalk.yellow('Tip: Use "pl force-transition pipeline <id> --to cancelled" to cancel stuck pipelines from any state.'));
|
|
4042
4166
|
process.exit(1);
|
|
4043
4167
|
}
|
|
4044
4168
|
const data = await api('PATCH', `/api/pipeline-instances/${id}/status`, { status: opts.status });
|