@formigio/fazemos-cli 0.10.53 → 0.10.55
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/index.js +120 -4
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -3814,6 +3814,30 @@ function findPhaseById(definition, phaseId) {
|
|
|
3814
3814
|
return (definition?.phases || []).find((p) => p.id === phaseId);
|
|
3815
3815
|
}
|
|
3816
3816
|
const VALID_STEP_TYPES = ['human', 'agent', 'script', 'gate', 'human_approval'];
|
|
3817
|
+
/**
|
|
3818
|
+
* G15 — resolve a template identifier that may be a UUID *or* a template name
|
|
3819
|
+
* to a UUID. The template-by-id endpoints (`GET/PUT/PATCH /api/pipeline-templates/:id`)
|
|
3820
|
+
* require a UUID param (`requireUuidParam` middleware — a non-UUID arg returns
|
|
3821
|
+
* "id must be a valid UUID" with empty stdout), so a name like "aiv-full-auto"
|
|
3822
|
+
* must first be resolved via the project-scoped list. UUIDs pass through
|
|
3823
|
+
* untouched (no extra API call). Names are matched within the active project
|
|
3824
|
+
* (or `--project <slug>`); slug is also matched for forward-compatibility if the
|
|
3825
|
+
* API ever adds one.
|
|
3826
|
+
*/
|
|
3827
|
+
async function resolveTemplateId(idOrName, opts = {}) {
|
|
3828
|
+
if (UUID_RE.test(idOrName))
|
|
3829
|
+
return idOrName;
|
|
3830
|
+
const data = await api('GET', '/api/pipeline-templates', undefined, projectOpts(opts));
|
|
3831
|
+
const matches = (data.templates ?? []).filter((t) => t.name === idOrName || t.slug === idOrName);
|
|
3832
|
+
if (matches.length === 1)
|
|
3833
|
+
return matches[0].id;
|
|
3834
|
+
if (matches.length === 0) {
|
|
3835
|
+
throw new Error(`No template named "${idOrName}" in the active project. ` +
|
|
3836
|
+
`Run "tpl list" to see names/IDs, pass --project <slug> to look in another project, or use the template UUID.`);
|
|
3837
|
+
}
|
|
3838
|
+
throw new Error(`Multiple templates named "${idOrName}" (${matches.map((t) => t.id).join(', ')}). ` +
|
|
3839
|
+
`Use the template UUID to disambiguate.`);
|
|
3840
|
+
}
|
|
3817
3841
|
// ── Templates ──────────────────────────────────────────────
|
|
3818
3842
|
const templates = program.command('templates').alias('tpl').description('Pipeline template commands.\n\n' +
|
|
3819
3843
|
' Templates define multi-step workflows: template → phases → steps → I/O.\n' +
|
|
@@ -3856,12 +3880,14 @@ templates
|
|
|
3856
3880
|
templates
|
|
3857
3881
|
.command('show')
|
|
3858
3882
|
.description('Show template detail including phases, steps, I/O declarations, pipeline inputs, and revision number. Use this to inspect the full structure of a template and discover phase/step IDs needed by other commands.')
|
|
3859
|
-
.argument('<id>', 'Template ID (use "tpl list" to find
|
|
3883
|
+
.argument('<id>', 'Template ID or name (use "tpl list" to find them)')
|
|
3860
3884
|
.option('--json', 'Output the template definition as JSON (same as tpl export). Useful for piping/inspecting without a temp file.')
|
|
3861
3885
|
.option('--show-sections', 'Print full sections content for all steps (default: truncated at 200 chars)')
|
|
3886
|
+
.option('--project <slug>', 'Override active project when resolving a template name')
|
|
3862
3887
|
.action(async (id, opts) => {
|
|
3863
3888
|
try {
|
|
3864
|
-
const
|
|
3889
|
+
const resolvedId = await resolveTemplateId(id, opts);
|
|
3890
|
+
const data = await api('GET', `/api/pipeline-templates/${resolvedId}`);
|
|
3865
3891
|
const t = data.template;
|
|
3866
3892
|
// --json mode: emit definition JSON (same as tpl export)
|
|
3867
3893
|
if (opts.json) {
|
|
@@ -3982,11 +4008,13 @@ templates
|
|
|
3982
4008
|
templates
|
|
3983
4009
|
.command('export')
|
|
3984
4010
|
.description('Export a template\'s full definition JSON to stdout or a file. Includes all phases, steps, sections, verification, uat, agent_config, I/O wires, and pipeline inputs. The exported JSON is the canonical input to "tpl update-definition". Round-trip: tpl export <id> > def.json && tpl update-definition <id> def.json is a no-op.')
|
|
3985
|
-
.argument('<id>', 'Template ID (use "tpl list" to find
|
|
4011
|
+
.argument('<id>', 'Template ID or name (use "tpl list" to find them)')
|
|
3986
4012
|
.option('--out <file>', 'Write output to file instead of stdout')
|
|
4013
|
+
.option('--project <slug>', 'Override active project when resolving a template name')
|
|
3987
4014
|
.action(async (id, opts) => {
|
|
3988
4015
|
try {
|
|
3989
|
-
const
|
|
4016
|
+
const resolvedId = await resolveTemplateId(id, opts);
|
|
4017
|
+
const data = await api('GET', `/api/pipeline-templates/${resolvedId}`);
|
|
3990
4018
|
const t = data.template;
|
|
3991
4019
|
const json = JSON.stringify(t.definition, null, 2);
|
|
3992
4020
|
if (opts.out) {
|
|
@@ -5612,6 +5640,94 @@ pipelines
|
|
|
5612
5640
|
process.exit(1);
|
|
5613
5641
|
}
|
|
5614
5642
|
});
|
|
5643
|
+
// ── OPERATOR-RECOVERY-BUNDLE — `pl reconcile <instanceId>` ──
|
|
5644
|
+
// Idempotent operator-safe healing command (spec §2).
|
|
5645
|
+
// Re-runs the DAG resolver, detects stranded steps (queued/in_progress with
|
|
5646
|
+
// no live execution), gates through canLaunch, re-launches eligible ones,
|
|
5647
|
+
// and returns a structured JSON report (launched, skipped, stalled).
|
|
5648
|
+
//
|
|
5649
|
+
// POST /api/pipeline-instances/:id/reconcile
|
|
5650
|
+
// Auth: owner or admin only
|
|
5651
|
+
//
|
|
5652
|
+
// Distinct from pl rematerialize (P-TOOL-1): rematerialize rebases onto the
|
|
5653
|
+
// TEMPLATE (schema/content-side); reconcile re-launches missing EXECUTIONS on
|
|
5654
|
+
// the instance as-is (runtime-side only). Different jobs kept separate.
|
|
5655
|
+
pipelines
|
|
5656
|
+
.command('reconcile')
|
|
5657
|
+
.description('Idempotent operator recovery: re-runs the DAG resolver, detects stranded steps ' +
|
|
5658
|
+
'(queued/in_progress with no live execution), and re-launches eligible ones. ' +
|
|
5659
|
+
'Safe to run multiple times — the second call fires nothing if the first succeeded. ' +
|
|
5660
|
+
'Owner/admin only. Distinct from rematerialize: this heals EXECUTIONS, not the template schema.')
|
|
5661
|
+
.argument('<instanceId>', 'Pipeline instance ID')
|
|
5662
|
+
.option('--json', 'Print the raw API response as JSON (machine-readable)')
|
|
5663
|
+
.action(async (instanceId, opts) => {
|
|
5664
|
+
try {
|
|
5665
|
+
const path = `/api/pipeline-instances/${instanceId}/reconcile`;
|
|
5666
|
+
const data = (await api('POST', path, {}));
|
|
5667
|
+
if (opts.json) {
|
|
5668
|
+
console.log(JSON.stringify(data, null, 2));
|
|
5669
|
+
return;
|
|
5670
|
+
}
|
|
5671
|
+
// Human-readable reconcile report
|
|
5672
|
+
const reconciledAt = data?.reconciled_at ? new Date(data.reconciled_at).toLocaleString() : 'unknown';
|
|
5673
|
+
console.log(chalk.bold(`Reconcile — instance ${instanceId}`));
|
|
5674
|
+
console.log(chalk.gray(` Reconciled at : ${reconciledAt}`));
|
|
5675
|
+
console.log(chalk.gray(` DAG resolver : ${data?.dag_resolver_ran ? 'ran' : 'skipped'}`));
|
|
5676
|
+
console.log(chalk.gray(` Steps verified: ${data?.steps_verified ?? 0}`));
|
|
5677
|
+
console.log();
|
|
5678
|
+
// Launched
|
|
5679
|
+
const launched = Array.isArray(data?.launched) ? data.launched : [];
|
|
5680
|
+
if (launched.length > 0) {
|
|
5681
|
+
console.log(chalk.green(` Launched (${launched.length}):`));
|
|
5682
|
+
for (const s of launched) {
|
|
5683
|
+
console.log(chalk.green(` ✓ ${s.step_name} (${s.step_id}) → execution ${s.execution_id}` +
|
|
5684
|
+
(s.assigned_to ? ` [agent: ${s.assigned_to}]` : '')));
|
|
5685
|
+
}
|
|
5686
|
+
}
|
|
5687
|
+
else {
|
|
5688
|
+
console.log(chalk.gray(' Launched: (none)'));
|
|
5689
|
+
}
|
|
5690
|
+
// Skipped (canLaunch=false due to pause/budget/fan-out)
|
|
5691
|
+
const skipped = Array.isArray(data?.skipped) ? data.skipped : [];
|
|
5692
|
+
if (skipped.length > 0) {
|
|
5693
|
+
console.log(chalk.yellow(`\n Skipped — canLaunch blocked (${skipped.length}):`));
|
|
5694
|
+
for (const s of skipped) {
|
|
5695
|
+
console.log(chalk.yellow(` ⚠ ${s.step_name} (${s.step_id}) [${s.reason}]: ${s.detail}`));
|
|
5696
|
+
}
|
|
5697
|
+
}
|
|
5698
|
+
// Stalled (governance signal — queued, no execution, canLaunch=false for other reason)
|
|
5699
|
+
const stalled = Array.isArray(data?.stalled) ? data.stalled : [];
|
|
5700
|
+
if (stalled.length > 0) {
|
|
5701
|
+
console.log(chalk.red(`\n Stalled — governance signal (${stalled.length}):`));
|
|
5702
|
+
for (const s of stalled) {
|
|
5703
|
+
console.log(chalk.red(` ✗ ${s.step_name} (${s.step_id}) [status: ${s.status}]: ${s.detail}`));
|
|
5704
|
+
}
|
|
5705
|
+
}
|
|
5706
|
+
console.log();
|
|
5707
|
+
if (launched.length === 0 && skipped.length === 0 && stalled.length === 0) {
|
|
5708
|
+
console.log(chalk.green('No stranded steps found — instance is healthy.'));
|
|
5709
|
+
}
|
|
5710
|
+
else {
|
|
5711
|
+
const summary = [];
|
|
5712
|
+
if (launched.length > 0)
|
|
5713
|
+
summary.push(chalk.green(`${launched.length} launched`));
|
|
5714
|
+
if (skipped.length > 0)
|
|
5715
|
+
summary.push(chalk.yellow(`${skipped.length} skipped`));
|
|
5716
|
+
if (stalled.length > 0)
|
|
5717
|
+
summary.push(chalk.red(`${stalled.length} stalled`));
|
|
5718
|
+
console.log(`Summary: ${summary.join(', ')}`);
|
|
5719
|
+
}
|
|
5720
|
+
}
|
|
5721
|
+
catch (err) {
|
|
5722
|
+
// 403 owner/admin gate
|
|
5723
|
+
if (err instanceof ApiError && err.status === 403) {
|
|
5724
|
+
console.error(chalk.red(`Permission denied: ${err.message}`));
|
|
5725
|
+
process.exit(1);
|
|
5726
|
+
}
|
|
5727
|
+
console.error(chalk.red(err.message));
|
|
5728
|
+
process.exit(1);
|
|
5729
|
+
}
|
|
5730
|
+
});
|
|
5615
5731
|
pipelines
|
|
5616
5732
|
.command('set-params')
|
|
5617
5733
|
.description('Set instance parameters (atomic update)')
|