@sanity/workflow-cli 0.9.0 → 0.11.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/CHANGELOG.md +516 -0
- package/README.md +71 -19
- package/bin/run.js +0 -4
- package/dist/commands/abort.d.ts +4 -6
- package/dist/commands/abort.js +13 -24
- package/dist/commands/definition/delete.d.ts +2 -2
- package/dist/commands/definition/delete.js +14 -27
- package/dist/commands/definition/diff.d.ts +2 -2
- package/dist/commands/definition/diff.js +3 -12
- package/dist/commands/definition/list.d.ts +3 -4
- package/dist/commands/definition/list.js +41 -47
- package/dist/commands/definition/show.d.ts +35 -3
- package/dist/commands/definition/show.js +44 -24
- package/dist/commands/deploy.d.ts +11 -5
- package/dist/commands/deploy.js +54 -57
- package/dist/commands/diagnose.d.ts +22 -4
- package/dist/commands/diagnose.js +60 -56
- package/dist/commands/fire-action.d.ts +2 -4
- package/dist/commands/fire-action.js +39 -62
- package/dist/commands/list.d.ts +18 -10
- package/dist/commands/list.js +77 -69
- package/dist/commands/reset-activity.js +0 -1
- package/dist/commands/set-stage.d.ts +2 -2
- package/dist/commands/set-stage.js +14 -30
- package/dist/commands/show.d.ts +2 -2
- package/dist/commands/show.js +12 -24
- package/dist/commands/start.d.ts +4 -28
- package/dist/commands/start.js +19 -80
- package/dist/commands/tail.d.ts +2 -2
- package/dist/commands/tail.js +34 -36
- package/dist/hooks/finally/telemetry.d.ts +8 -0
- package/dist/hooks/finally/telemetry.js +13 -0
- package/dist/hooks/prerun/telemetry.d.ts +8 -0
- package/dist/hooks/prerun/telemetry.js +9 -0
- package/dist/index.js +0 -3
- package/dist/lib/base-command.d.ts +14 -0
- package/dist/lib/base-command.js +10 -0
- package/dist/lib/client.js +13 -30
- package/dist/lib/context.d.ts +49 -13
- package/dist/lib/context.js +49 -48
- package/dist/lib/definitions.d.ts +10 -0
- package/dist/lib/definitions.js +11 -24
- package/dist/lib/diff.d.ts +5 -2
- package/dist/lib/diff.js +61 -27
- package/dist/lib/env.d.ts +4 -0
- package/dist/lib/env.js +4 -3
- package/dist/lib/fail.d.ts +19 -6
- package/dist/lib/fail.js +26 -26
- package/dist/lib/flags.d.ts +0 -7
- package/dist/lib/flags.js +0 -17
- package/dist/lib/load-config.d.ts +11 -7
- package/dist/lib/load-config.js +40 -20
- package/dist/lib/operation-args.d.ts +14 -23
- package/dist/lib/operation-args.js +6 -38
- package/dist/lib/ops-report.d.ts +18 -10
- package/dist/lib/ops-report.js +20 -16
- package/dist/lib/params.js +0 -8
- package/dist/lib/read-fanout.d.ts +22 -0
- package/dist/lib/read-fanout.js +28 -0
- package/dist/lib/select-deployment.js +0 -21
- package/dist/lib/share-definitions.d.ts +120 -0
- package/dist/lib/share-definitions.js +187 -0
- package/dist/lib/stub.js +0 -7
- package/dist/lib/telemetry-setup.d.ts +70 -0
- package/dist/lib/telemetry-setup.js +99 -0
- package/dist/lib/telemetry.d.ts +131 -0
- package/dist/lib/telemetry.js +94 -0
- package/dist/lib/ui.d.ts +33 -1
- package/dist/lib/ui.js +32 -22
- package/oclif.manifest.json +21 -52
- package/package.json +14 -8
package/dist/commands/list.js
CHANGED
|
@@ -1,27 +1,34 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { WORKFLOW_INSTANCE_TYPE, tagScopeFilter } from '@sanity/workflow-engine';
|
|
4
|
-
import { Table } from 'console-table-printer';
|
|
1
|
+
import { Flags } from '@oclif/core';
|
|
2
|
+
import { WORKFLOW_INSTANCE_TYPE, documentPrefilter, inFlightFilter, instanceWatchesDocument, tagScopeFilter, terminalState, } from '@sanity/workflow-engine';
|
|
5
3
|
import logSymbols from 'log-symbols';
|
|
6
|
-
import {
|
|
4
|
+
import { WorkflowCommand } from "../lib/base-command.js";
|
|
5
|
+
import { resolveReadTargets } from "../lib/context.js";
|
|
6
|
+
import { failOnThrow } from "../lib/fail.js";
|
|
7
7
|
import { tagFlags } from "../lib/flags.js";
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
* Build the GROQ query + params for `list` from its flags. Pure so the
|
|
11
|
-
* filter combinations can be asserted without a live dataset. `--tag` is an
|
|
12
|
-
* optional filter: omit it and the listing spans every partition.
|
|
13
|
-
*/
|
|
8
|
+
import { runReadAcrossTargets } from "../lib/read-fanout.js";
|
|
9
|
+
import { clipToLimit, formatAge, formatTable } from "../lib/ui.js";
|
|
14
10
|
export function buildListQuery(flags) {
|
|
15
11
|
const filters = [`_type == "${WORKFLOW_INSTANCE_TYPE}"`];
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
12
|
+
const params = {};
|
|
13
|
+
if (!flags['include-completed'])
|
|
14
|
+
filters.push(inFlightFilter());
|
|
15
|
+
if (flags.definition) {
|
|
19
16
|
filters.push('definition == $definition');
|
|
20
|
-
|
|
17
|
+
params['definition'] = flags.definition;
|
|
18
|
+
}
|
|
21
19
|
if (flags.failed)
|
|
22
20
|
filters.push('count(stages[].activities[status == "failed"]) > 0');
|
|
23
|
-
if (flags.tag)
|
|
21
|
+
if (flags.tag) {
|
|
24
22
|
filters.push(tagScopeFilter());
|
|
23
|
+
params['tag'] = flags.tag;
|
|
24
|
+
}
|
|
25
|
+
if (flags.document) {
|
|
26
|
+
const armParams = {};
|
|
27
|
+
filters.push(`(${documentPrefilter([flags.document], armParams)})`);
|
|
28
|
+
Object.assign(params, armParams);
|
|
29
|
+
return { groq: `*[${filters.join(' && ')}] | order(lastChangedAt desc)`, params };
|
|
30
|
+
}
|
|
31
|
+
params['limit'] = flags.limit + 1;
|
|
25
32
|
const groq = `*[${filters.join(' && ')}] | order(lastChangedAt desc) [0...$limit]{
|
|
26
33
|
_id,
|
|
27
34
|
definition,
|
|
@@ -31,50 +38,34 @@ export function buildListQuery(flags) {
|
|
|
31
38
|
abortedAt,
|
|
32
39
|
lastChangedAt
|
|
33
40
|
}`;
|
|
34
|
-
const params = { limit: flags.limit };
|
|
35
|
-
if (flags.definition)
|
|
36
|
-
params['definition'] = flags.definition;
|
|
37
|
-
if (flags.tag)
|
|
38
|
-
params['tag'] = flags.tag;
|
|
39
41
|
return { groq, params };
|
|
40
42
|
}
|
|
41
|
-
/** Aborted instances also carry `completedAt`, so check `abortedAt` first. */
|
|
42
|
-
function deriveStatus(r) {
|
|
43
|
-
if (r.abortedAt)
|
|
44
|
-
return 'aborted';
|
|
45
|
-
if (r.completedAt)
|
|
46
|
-
return 'completed';
|
|
47
|
-
return 'in-flight';
|
|
48
|
-
}
|
|
49
|
-
/** Map a queried instance to its display row (derives the status column).
|
|
50
|
-
* Pure — status is the semantic value; the table colors it at render. */
|
|
51
43
|
export function instanceRow(r) {
|
|
52
44
|
return {
|
|
53
45
|
_id: r._id,
|
|
54
46
|
definition: r.definition,
|
|
55
47
|
tag: r.tag,
|
|
56
48
|
currentStage: r.currentStage,
|
|
57
|
-
status:
|
|
49
|
+
status: terminalState({
|
|
50
|
+
...(r.completedAt != null ? { completedAt: r.completedAt } : {}),
|
|
51
|
+
...(r.abortedAt != null ? { abortedAt: r.abortedAt } : {}),
|
|
52
|
+
}),
|
|
58
53
|
lastChangedAt: r.lastChangedAt,
|
|
59
54
|
};
|
|
60
55
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
aborted: 'dim',
|
|
64
|
-
'in-flight': 'cyan',
|
|
65
|
-
};
|
|
66
|
-
export default class List extends Command {
|
|
67
|
-
static description = 'List workflow instances in the configured dataset.';
|
|
56
|
+
export default class List extends WorkflowCommand {
|
|
57
|
+
static description = 'List workflow instances in the configured dataset (in-flight by default).';
|
|
68
58
|
static examples = [
|
|
69
59
|
'<%= config.bin %> list',
|
|
70
|
-
'<%= config.bin %> list --
|
|
60
|
+
'<%= config.bin %> list --include-completed',
|
|
71
61
|
'<%= config.bin %> list --definition productLaunch',
|
|
62
|
+
'<%= config.bin %> list --document dataset:proj:ds:article-1',
|
|
72
63
|
'<%= config.bin %> list --tag prod',
|
|
73
64
|
];
|
|
74
65
|
static flags = {
|
|
75
66
|
...tagFlags,
|
|
76
|
-
'
|
|
77
|
-
description: '
|
|
67
|
+
'include-completed': Flags.boolean({
|
|
68
|
+
description: 'Include completed/aborted instances (default: in-flight only).',
|
|
78
69
|
default: false,
|
|
79
70
|
}),
|
|
80
71
|
failed: Flags.boolean({
|
|
@@ -84,6 +75,10 @@ export default class List extends Command {
|
|
|
84
75
|
definition: Flags.string({
|
|
85
76
|
description: "Only instances of this workflow definition (its `name`; the instance's `definition` field).",
|
|
86
77
|
}),
|
|
78
|
+
document: Flags.string({
|
|
79
|
+
description: 'Only instances that reference this document (resource-qualified GDR URI, ' +
|
|
80
|
+
'e.g. "dataset:proj:ds:article-1").',
|
|
81
|
+
}),
|
|
87
82
|
limit: Flags.integer({
|
|
88
83
|
description: 'Maximum rows to return.',
|
|
89
84
|
default: 50,
|
|
@@ -91,34 +86,47 @@ export default class List extends Command {
|
|
|
91
86
|
};
|
|
92
87
|
async run() {
|
|
93
88
|
const { flags } = await this.parse(List);
|
|
94
|
-
const
|
|
95
|
-
const { groq, params } = buildListQuery(flags);
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
{
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
89
|
+
const targets = await resolveReadTargets(flags);
|
|
90
|
+
const { groq, params } = failOnThrow('Invalid --document:', () => buildListQuery(flags));
|
|
91
|
+
const document = flags.document;
|
|
92
|
+
const failures = await runReadAcrossTargets({
|
|
93
|
+
targets,
|
|
94
|
+
log: (line) => this.log(line),
|
|
95
|
+
run: async ({ client }) => {
|
|
96
|
+
const fetched = await fetchRows({ client, groq, params, document });
|
|
97
|
+
if (fetched.length === 0) {
|
|
98
|
+
this.log(`${logSymbols.info} no instances match`);
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
const { rows, note } = clipToLimit(fetched, flags.limit);
|
|
102
|
+
const lines = formatTable(['instance', 'workflow', 'tag', 'stage', 'status', 'updated'], rows.map((r) => {
|
|
103
|
+
const row = instanceRow(r);
|
|
104
|
+
return [
|
|
105
|
+
row._id,
|
|
106
|
+
row.definition,
|
|
107
|
+
row.tag,
|
|
108
|
+
row.currentStage,
|
|
109
|
+
row.status,
|
|
110
|
+
formatAge(row.lastChangedAt),
|
|
111
|
+
];
|
|
112
|
+
}));
|
|
113
|
+
for (const line of lines) {
|
|
114
|
+
this.log(line);
|
|
115
|
+
}
|
|
116
|
+
if (note) {
|
|
117
|
+
this.log(note);
|
|
118
|
+
}
|
|
119
|
+
},
|
|
110
120
|
});
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
table.addRow({
|
|
114
|
-
_id: row._id,
|
|
115
|
-
definition: row.definition,
|
|
116
|
-
tag: row.tag,
|
|
117
|
-
currentStage: row.currentStage,
|
|
118
|
-
status: styleText(STATUS_COLOR[row.status], row.status),
|
|
119
|
-
updated: formatAge(row.lastChangedAt),
|
|
120
|
-
});
|
|
121
|
+
if (failures.length > 0) {
|
|
122
|
+
this.exit(1);
|
|
121
123
|
}
|
|
122
|
-
table.printTable();
|
|
123
124
|
}
|
|
124
125
|
}
|
|
126
|
+
async function fetchRows({ client, groq, params, document, }) {
|
|
127
|
+
if (document === undefined) {
|
|
128
|
+
return client.fetch(groq, params, { tag: 'list' });
|
|
129
|
+
}
|
|
130
|
+
const candidates = await client.fetch(groq, params, { tag: 'list' });
|
|
131
|
+
return candidates.filter((instance) => instanceWatchesDocument(instance, document));
|
|
132
|
+
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { Args } from '@oclif/core';
|
|
2
2
|
import { StubCommand } from "../lib/stub.js";
|
|
3
3
|
export default class ResetActivity extends StubCommand {
|
|
4
|
-
// Stubbed, so kept off the help + published surface until it's wired.
|
|
5
4
|
static hidden = true;
|
|
6
5
|
static description = 'Reset a failed activity on an in-flight instance — back to pending (re-run) or to skipped (bypass).';
|
|
7
6
|
static args = {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { Command } from '@oclif/core';
|
|
2
1
|
import { type SetStageArgs, type WorkflowInstance } from '@sanity/workflow-engine';
|
|
2
|
+
import { WorkflowCommand } from '../lib/base-command.ts';
|
|
3
3
|
import { type EngineScope } from '../lib/operation-args.ts';
|
|
4
4
|
import { type WriteOutcome, type WriteReport } from '../lib/ops-report.ts';
|
|
5
|
-
export default class SetStage extends
|
|
5
|
+
export default class SetStage extends WorkflowCommand {
|
|
6
6
|
static description: string;
|
|
7
7
|
static examples: string[];
|
|
8
8
|
static args: {
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import { styleText } from 'node:util';
|
|
2
|
-
import { Args,
|
|
2
|
+
import { Args, Flags } from '@oclif/core';
|
|
3
3
|
import { workflow } from '@sanity/workflow-engine';
|
|
4
|
-
import
|
|
4
|
+
import { WorkflowCommand } from "../lib/base-command.js";
|
|
5
5
|
import { resolveInstanceContext } from "../lib/context.js";
|
|
6
|
-
import { errorMessage, fail } from "../lib/fail.js";
|
|
7
6
|
import { tagFlags } from "../lib/flags.js";
|
|
8
7
|
import { buildOperationArgs } from "../lib/operation-args.js";
|
|
9
|
-
import {
|
|
10
|
-
export default class SetStage extends
|
|
8
|
+
import { cascadeTail, opsAppliedLines, runWriteVerb, } from "../lib/ops-report.js";
|
|
9
|
+
export default class SetStage extends WorkflowCommand {
|
|
11
10
|
static description = "Force an instance into a stage, regardless of its declared transitions and filters — the engine's setStage admin override. The target stage's enter lifecycle still runs (auto-activities start, stage guards reconcile), and the post-move cascade can immediately auto-transition the instance onward.";
|
|
12
11
|
static examples = [
|
|
13
12
|
'<%= config.bin %> set-stage wf-instance.abc123 --to ready',
|
|
@@ -32,9 +31,11 @@ export default class SetStage extends Command {
|
|
|
32
31
|
async run() {
|
|
33
32
|
const { args, flags } = await this.parse(SetStage);
|
|
34
33
|
const { client, scope } = await resolveInstanceContext(flags, args.instanceId);
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
34
|
+
await runWriteVerb({
|
|
35
|
+
startLabel: `Setting ${args.instanceId} → ${flags.to}…`,
|
|
36
|
+
failLabel: 'Stage move rejected',
|
|
37
|
+
failHeadline: 'set-stage error:',
|
|
38
|
+
run: () => workflow.setStage({
|
|
38
39
|
client,
|
|
39
40
|
...buildSetStageArgs({
|
|
40
41
|
scope,
|
|
@@ -42,44 +43,27 @@ export default class SetStage extends Command {
|
|
|
42
43
|
targetStage: flags.to,
|
|
43
44
|
reason: flags.reason,
|
|
44
45
|
}),
|
|
45
|
-
})
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
log: (line) => this.log(line),
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
catch (error) {
|
|
53
|
-
spinner.fail('Stage move rejected');
|
|
54
|
-
fail('set-stage error:', errorMessage(error));
|
|
55
|
-
}
|
|
46
|
+
}),
|
|
47
|
+
report: (result) => setStageReport(result, flags.to),
|
|
48
|
+
log: (line) => this.log(line),
|
|
49
|
+
});
|
|
56
50
|
}
|
|
57
51
|
}
|
|
58
|
-
/** The shared operation args ({@link buildOperationArgs}) plus the
|
|
59
|
-
* setStage-specific target. */
|
|
60
52
|
export function buildSetStageArgs({ scope, instanceId, targetStage, reason, }) {
|
|
61
53
|
return { ...buildOperationArgs({ scope, instanceId, reason }), targetStage };
|
|
62
54
|
}
|
|
63
|
-
/**
|
|
64
|
-
* Turn a `setStage` result into the spinner message + ops block. Pure so
|
|
65
|
-
* the changed / terminal / already-at-target / cascaded / ops permutations
|
|
66
|
-
* can be asserted without driving a real move.
|
|
67
|
-
*/
|
|
68
55
|
export function setStageReport(result, to) {
|
|
69
56
|
const stage = styleText('bold', result.instance.currentStage);
|
|
70
57
|
if (!result.changed) {
|
|
71
|
-
// The engine no-ops in two distinct cases: a terminal instance never
|
|
72
|
-
// moves again, and a same-stage move has nothing to do.
|
|
73
58
|
const { completedAt } = result.instance;
|
|
74
59
|
const message = completedAt !== undefined
|
|
75
60
|
? `setStage changed nothing — instance is terminal (at ${stage} since ${completedAt})`
|
|
76
61
|
: `setStage changed nothing — instance is already at ${stage}`;
|
|
77
62
|
return { changed: false, message, opsLines: [] };
|
|
78
63
|
}
|
|
79
|
-
const tail = result.cascaded > 0 ? `, then cascaded ${result.cascaded} auto-transition(s)` : '';
|
|
80
64
|
return {
|
|
81
65
|
changed: true,
|
|
82
|
-
message: `Now at ${stage} (was → ${to}${
|
|
66
|
+
message: `Now at ${stage} (was → ${to}${cascadeTail(result.cascaded)})`,
|
|
83
67
|
opsLines: opsAppliedLines(result.ranOps),
|
|
84
68
|
};
|
|
85
69
|
}
|
package/dist/commands/show.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { Command } from '@oclif/core';
|
|
2
1
|
import { type WorkflowInstance } from '@sanity/workflow-engine';
|
|
3
|
-
|
|
2
|
+
import { WorkflowCommand } from '../lib/base-command.ts';
|
|
3
|
+
export default class Show extends WorkflowCommand {
|
|
4
4
|
static description: string;
|
|
5
5
|
static examples: string[];
|
|
6
6
|
static args: {
|
package/dist/commands/show.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { styleText } from 'node:util';
|
|
2
|
-
import { Args,
|
|
3
|
-
import { abortReason } from '@sanity/workflow-engine';
|
|
2
|
+
import { Args, Flags } from '@oclif/core';
|
|
3
|
+
import { abortReason, displayTitle, terminalState, } from '@sanity/workflow-engine';
|
|
4
4
|
import logSymbols from 'log-symbols';
|
|
5
|
-
import {
|
|
5
|
+
import { WorkflowCommand } from "../lib/base-command.js";
|
|
6
|
+
import { findInstance, resolveReadTargets } from "../lib/context.js";
|
|
6
7
|
import { tagFlags } from "../lib/flags.js";
|
|
7
8
|
import { formatKeyValue, formatTimestamp, sectionHeader, activityIcon } from "../lib/ui.js";
|
|
8
|
-
export default class Show extends
|
|
9
|
+
export default class Show extends WorkflowCommand {
|
|
9
10
|
static description = 'Show the state, activities, and effects of a workflow instance.';
|
|
10
11
|
static examples = [
|
|
11
12
|
'<%= config.bin %> show wf-instance.abc123',
|
|
@@ -28,11 +29,12 @@ export default class Show extends Command {
|
|
|
28
29
|
};
|
|
29
30
|
async run() {
|
|
30
31
|
const { args, flags } = await this.parse(Show);
|
|
31
|
-
const
|
|
32
|
-
const
|
|
33
|
-
if (!
|
|
32
|
+
const targets = await resolveReadTargets(flags);
|
|
33
|
+
const hit = await findInstance({ targets, instanceId: args.instanceId, requestTag: 'show' });
|
|
34
|
+
if (!hit) {
|
|
34
35
|
this.error(`${logSymbols.error} no instance with id "${args.instanceId}"`, { exit: 1 });
|
|
35
36
|
}
|
|
37
|
+
const { instance } = hit;
|
|
36
38
|
this.log(instanceHeader(instance));
|
|
37
39
|
this.log('');
|
|
38
40
|
for (const line of describeInstance(instance, {
|
|
@@ -42,19 +44,14 @@ export default class Show extends Command {
|
|
|
42
44
|
}
|
|
43
45
|
}
|
|
44
46
|
}
|
|
45
|
-
const HEADER_PAD = 9;
|
|
46
|
-
/** The summary block at the top of `show` (and reused by `diagnose` /
|
|
47
|
-
* `fire-action`): a bold workflow + id title, then aligned detail rows. */
|
|
47
|
+
const HEADER_PAD = 9;
|
|
48
48
|
export function instanceHeader(instance) {
|
|
49
49
|
const title = `${styleText('bold', `${instance.definition} v${instance.pinnedVersion}`)} ${styleText('cyan', instance._id)}`;
|
|
50
50
|
const rows = [
|
|
51
51
|
formatKeyValue({ key: 'Stage', value: instance.currentStage, padTo: HEADER_PAD }),
|
|
52
52
|
formatKeyValue({ key: 'Started', value: formatTimestamp(instance.startedAt), padTo: HEADER_PAD }),
|
|
53
53
|
];
|
|
54
|
-
|
|
55
|
-
// abort line stands in for completion — showing a green "Completed" too would
|
|
56
|
-
// misread as success.
|
|
57
|
-
if (instance.abortedAt) {
|
|
54
|
+
if (terminalState(instance) === 'aborted' && instance.abortedAt !== undefined) {
|
|
58
55
|
const reason = abortReason(instance);
|
|
59
56
|
const tail = reason ? ` — ${reason}` : '';
|
|
60
57
|
const value = styleText('yellow', `${formatTimestamp(instance.abortedAt)}${tail}`);
|
|
@@ -69,11 +66,6 @@ export function instanceHeader(instance) {
|
|
|
69
66
|
rows.push(formatKeyValue({ key: 'Tag', value: instance.tag, padTo: HEADER_PAD }));
|
|
70
67
|
return [title, ...rows].join('\n');
|
|
71
68
|
}
|
|
72
|
-
/**
|
|
73
|
-
* The per-instance body lines: every stage with its activities, any pending
|
|
74
|
-
* effects, and (optionally) the history log. Pure so it can be asserted
|
|
75
|
-
* without driving the oclif command.
|
|
76
|
-
*/
|
|
77
69
|
export function describeInstance(instance, options) {
|
|
78
70
|
const lines = [sectionHeader('Stages')];
|
|
79
71
|
for (const stage of instance.stages) {
|
|
@@ -81,10 +73,6 @@ export function describeInstance(instance, options) {
|
|
|
81
73
|
? styleText('dim', ` (exited ${formatTimestamp(stage.exitedAt)})`)
|
|
82
74
|
: styleText('cyan', ' (current)');
|
|
83
75
|
lines.push(` • ${stage.name}${marker}`);
|
|
84
|
-
// A persisted stage may carry no activities array — an activity-less stage,
|
|
85
|
-
// or an older-shape document — so treat an absent list as none rather than
|
|
86
|
-
// crash. This renders whatever is in the lake, not only freshly-stamped
|
|
87
|
-
// instances; it does not reconstruct activity state from history.
|
|
88
76
|
for (const activity of stage.activities ?? []) {
|
|
89
77
|
lines.push(` ${activityIcon[activity.status]} ${activity.name} ${styleText('dim', `[${activity.status}]`)}`);
|
|
90
78
|
}
|
|
@@ -98,7 +86,7 @@ export function describeInstance(instance, options) {
|
|
|
98
86
|
if (options.includeHistory) {
|
|
99
87
|
lines.push('', sectionHeader('History'));
|
|
100
88
|
for (const entry of instance.history) {
|
|
101
|
-
lines.push(` ${styleText('dim', `[${formatTimestamp(entry.at)}]`)} ${entry._type}`);
|
|
89
|
+
lines.push(` ${styleText('dim', `[${formatTimestamp(entry.at)}]`)} ${displayTitle(entry._type)}`);
|
|
102
90
|
}
|
|
103
91
|
}
|
|
104
92
|
return lines;
|
package/dist/commands/start.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { type InitialFieldValue, type StartInstanceArgs, type WorkflowInstance } from '@sanity/workflow-engine';
|
|
2
|
+
import { WorkflowCommand } from '../lib/base-command.ts';
|
|
3
3
|
import { type EngineScope } from '../lib/operation-args.ts';
|
|
4
|
-
export default class Start extends
|
|
4
|
+
export default class Start extends WorkflowCommand {
|
|
5
5
|
static description: string;
|
|
6
6
|
static examples: string[];
|
|
7
7
|
static args: {
|
|
@@ -9,46 +9,22 @@ export default class Start extends Command {
|
|
|
9
9
|
};
|
|
10
10
|
static flags: {
|
|
11
11
|
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
12
|
-
as: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
|
-
'as-role': import("@oclif/core/interfaces").OptionFlag<string[], import("@oclif/core/interfaces").CustomOptions>;
|
|
14
12
|
version: import("@oclif/core/interfaces").OptionFlag<number | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
15
13
|
field: import("@oclif/core/interfaces").OptionFlag<string[], import("@oclif/core/interfaces").CustomOptions>;
|
|
16
14
|
tag: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
17
15
|
};
|
|
18
16
|
run(): Promise<void>;
|
|
19
17
|
}
|
|
20
|
-
/**
|
|
21
|
-
* Why a definition can't be started standalone, or `undefined` when it can.
|
|
22
|
-
* Advisory, mirroring the Startable column in `definition list` — the engine
|
|
23
|
-
* itself does not refuse ({@link isStartableDefinition}), but the CLI has no
|
|
24
|
-
* way to supply the parent context a spawn-only child expects.
|
|
25
|
-
*/
|
|
26
|
-
export declare function startRefusal(definition: {
|
|
27
|
-
lifecycle?: WorkflowLifecycle | undefined;
|
|
28
|
-
}): string | undefined;
|
|
29
|
-
/**
|
|
30
|
-
* Type each `--field name=value` against the workflow's declared field
|
|
31
|
-
* entries — the engine takes typed {@link InitialFieldValue}s, and a value's
|
|
32
|
-
* type IS its declared entry's kind. Only `input`-sourced entries read
|
|
33
|
-
* caller values (the engine silently ignores the rest), so anything else
|
|
34
|
-
* fails here, naming the fields that ARE settable; value validation stays
|
|
35
|
-
* in the engine.
|
|
36
|
-
*/
|
|
37
|
-
export declare function buildInitialFields({ declared, values, }: {
|
|
38
|
-
declared: Pick<FieldEntry, 'type' | 'name' | 'initialValue'>[];
|
|
39
|
-
values: Record<string, unknown>;
|
|
40
|
-
}): InitialFieldValue[];
|
|
41
18
|
/**
|
|
42
19
|
* The shared engine args ({@link baseEngineArgs}) plus the start-specific
|
|
43
20
|
* fields. Optionals spread conditionally because the engine's optional args
|
|
44
21
|
* reject an explicit `undefined` under `exactOptionalPropertyTypes`.
|
|
45
22
|
*/
|
|
46
|
-
export declare function buildStartArgs({ scope, name, version, initialFields,
|
|
23
|
+
export declare function buildStartArgs({ scope, name, version, initialFields, }: {
|
|
47
24
|
scope: EngineScope;
|
|
48
25
|
name: string;
|
|
49
26
|
version: number | undefined;
|
|
50
27
|
initialFields: InitialFieldValue[];
|
|
51
|
-
actor: Actor;
|
|
52
28
|
}): StartInstanceArgs & EngineScope;
|
|
53
29
|
/** The success spinner message. Pure so the in-flight / immediately-terminal
|
|
54
30
|
* permutations can be asserted without driving a real start. */
|
package/dist/commands/start.js
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
import { styleText } from 'node:util';
|
|
2
|
-
import { Args,
|
|
3
|
-
import {
|
|
4
|
-
import
|
|
2
|
+
import { Args, Flags } from '@oclif/core';
|
|
3
|
+
import { buildInitialFields, startRefusal, workflow, } from '@sanity/workflow-engine';
|
|
4
|
+
import { WorkflowCommand } from "../lib/base-command.js";
|
|
5
5
|
import { resolveContext } from "../lib/context.js";
|
|
6
6
|
import { fetchDeployedDefinition } from "../lib/definitions.js";
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import { baseEngineArgs
|
|
7
|
+
import { fail, failOnThrow, failureDetail } from "../lib/fail.js";
|
|
8
|
+
import { jsonFlags, tagFlags } from "../lib/flags.js";
|
|
9
|
+
import { baseEngineArgs } from "../lib/operation-args.js";
|
|
10
|
+
import { runWriteVerb } from "../lib/ops-report.js";
|
|
10
11
|
import { parseParams } from "../lib/params.js";
|
|
11
|
-
export default class Start extends
|
|
12
|
+
export default class Start extends WorkflowCommand {
|
|
12
13
|
static description = 'Start a workflow instance from a deployed definition. Supply values for the ' +
|
|
13
14
|
"workflow's input-sourced fields with --field (e.g. the subject document ref).";
|
|
14
15
|
static examples = [
|
|
15
16
|
'<%= config.bin %> start productLaunch',
|
|
16
17
|
'<%= config.bin %> start article-review --field subject=\'{"id":"dataset:proj:ds:article-1","type":"article"}\'',
|
|
17
|
-
'<%= config.bin %> start article-review --as user.xyz --as-role editor',
|
|
18
18
|
'<%= config.bin %> start productLaunch --version 2 --tag prod',
|
|
19
19
|
];
|
|
20
20
|
static args = {
|
|
@@ -32,13 +32,11 @@ export default class Start extends Command {
|
|
|
32
32
|
multiple: true,
|
|
33
33
|
default: [],
|
|
34
34
|
}),
|
|
35
|
-
...actorFlags,
|
|
36
35
|
...jsonFlags,
|
|
37
36
|
};
|
|
38
37
|
async run() {
|
|
39
38
|
const { args, flags } = await this.parse(Start);
|
|
40
39
|
const { deployment, client } = await resolveContext(flags);
|
|
41
|
-
const actor = failOnThrow('start usage error:', () => resolveActor(flags.as, flags['as-role']));
|
|
42
40
|
const values = failOnThrow('Invalid --field:', () => parseParams(flags.field));
|
|
43
41
|
const initialFields = await resolveStartInputs({
|
|
44
42
|
client,
|
|
@@ -54,33 +52,25 @@ export default class Start extends Command {
|
|
|
54
52
|
name: args.name,
|
|
55
53
|
version: flags.version,
|
|
56
54
|
initialFields,
|
|
57
|
-
actor,
|
|
58
55
|
}),
|
|
59
56
|
};
|
|
60
57
|
if (flags.json) {
|
|
61
58
|
const result = await workflow
|
|
62
59
|
.startInstance(startArgs)
|
|
63
|
-
.catch((error) => fail('start error:',
|
|
60
|
+
.catch((error) => fail('start error:', failureDetail(error)));
|
|
64
61
|
this.log(JSON.stringify(startResultJson(result.instance), null, 2));
|
|
65
62
|
return;
|
|
66
63
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
}
|
|
64
|
+
await runWriteVerb({
|
|
65
|
+
startLabel: `Starting ${args.name}…`,
|
|
66
|
+
failLabel: 'Start rejected',
|
|
67
|
+
failHeadline: 'start error:',
|
|
68
|
+
run: () => workflow.startInstance(startArgs),
|
|
69
|
+
report: (result) => ({ changed: true, message: startReport(result.instance) }),
|
|
70
|
+
log: (line) => this.log(line),
|
|
71
|
+
});
|
|
76
72
|
}
|
|
77
73
|
}
|
|
78
|
-
/**
|
|
79
|
-
* Everything `start` must resolve before the engine call: fetch the deployed
|
|
80
|
-
* definition (failing when nothing is deployed under the name), refuse
|
|
81
|
-
* non-startable ones, and type the operator's `--field` values against the
|
|
82
|
-
* declared entries.
|
|
83
|
-
*/
|
|
84
74
|
async function resolveStartInputs({ client, name, tag, version, values, }) {
|
|
85
75
|
const deployed = await fetchDeployedDefinition({ client, name, tag, version });
|
|
86
76
|
if (deployed === undefined) {
|
|
@@ -92,62 +82,14 @@ async function resolveStartInputs({ client, name, tag, version, values, }) {
|
|
|
92
82
|
}
|
|
93
83
|
return failOnThrow('start usage error:', () => buildInitialFields({ declared: deployed.fields ?? [], values }));
|
|
94
84
|
}
|
|
95
|
-
|
|
96
|
-
* Why a definition can't be started standalone, or `undefined` when it can.
|
|
97
|
-
* Advisory, mirroring the Startable column in `definition list` — the engine
|
|
98
|
-
* itself does not refuse ({@link isStartableDefinition}), but the CLI has no
|
|
99
|
-
* way to supply the parent context a spawn-only child expects.
|
|
100
|
-
*/
|
|
101
|
-
export function startRefusal(definition) {
|
|
102
|
-
if (isStartableDefinition(definition)) {
|
|
103
|
-
return undefined;
|
|
104
|
-
}
|
|
105
|
-
return "definition is spawn-only (lifecycle: 'child') — instances of it are spawned by a parent workflow's activity, not started standalone";
|
|
106
|
-
}
|
|
107
|
-
/**
|
|
108
|
-
* Type each `--field name=value` against the workflow's declared field
|
|
109
|
-
* entries — the engine takes typed {@link InitialFieldValue}s, and a value's
|
|
110
|
-
* type IS its declared entry's kind. Only `input`-sourced entries read
|
|
111
|
-
* caller values (the engine silently ignores the rest), so anything else
|
|
112
|
-
* fails here, naming the fields that ARE settable; value validation stays
|
|
113
|
-
* in the engine.
|
|
114
|
-
*/
|
|
115
|
-
export function buildInitialFields({ declared, values, }) {
|
|
116
|
-
const settable = declared.filter((f) => f.initialValue?.type === 'input');
|
|
117
|
-
const settableNames = settable.map((f) => f.name).join(', ');
|
|
118
|
-
return Object.entries(values).map(([name, value]) => {
|
|
119
|
-
const entry = declared.find((f) => f.name === name);
|
|
120
|
-
if (entry === undefined) {
|
|
121
|
-
throw new Error(settable.length > 0
|
|
122
|
-
? `workflow declares no field "${name}" — input fields: ${settableNames}`
|
|
123
|
-
: `workflow declares no field "${name}" — it declares no input fields at all`);
|
|
124
|
-
}
|
|
125
|
-
if (entry.initialValue?.type !== 'input') {
|
|
126
|
-
throw new Error(`field "${name}" is not input-sourced — the engine would silently ignore the value. ` +
|
|
127
|
-
(settable.length > 0
|
|
128
|
-
? `Input fields: ${settableNames}`
|
|
129
|
-
: 'This workflow has no input fields.'));
|
|
130
|
-
}
|
|
131
|
-
// Operator-supplied JSON; the engine validates the value against the
|
|
132
|
-
// declared kind at start, so this cast is the CLI/engine boundary.
|
|
133
|
-
return { type: entry.type, name, value };
|
|
134
|
-
});
|
|
135
|
-
}
|
|
136
|
-
/**
|
|
137
|
-
* The shared engine args ({@link baseEngineArgs}) plus the start-specific
|
|
138
|
-
* fields. Optionals spread conditionally because the engine's optional args
|
|
139
|
-
* reject an explicit `undefined` under `exactOptionalPropertyTypes`.
|
|
140
|
-
*/
|
|
141
|
-
export function buildStartArgs({ scope, name, version, initialFields, actor, }) {
|
|
85
|
+
export function buildStartArgs({ scope, name, version, initialFields, }) {
|
|
142
86
|
return {
|
|
143
|
-
...baseEngineArgs(scope
|
|
87
|
+
...baseEngineArgs(scope),
|
|
144
88
|
definition: name,
|
|
145
89
|
...(version !== undefined ? { version } : {}),
|
|
146
90
|
...(initialFields.length > 0 ? { initialFields } : {}),
|
|
147
91
|
};
|
|
148
92
|
}
|
|
149
|
-
/** The success spinner message. Pure so the in-flight / immediately-terminal
|
|
150
|
-
* permutations can be asserted without driving a real start. */
|
|
151
93
|
export function startReport(instance) {
|
|
152
94
|
const id = styleText('bold', instance._id);
|
|
153
95
|
const stage = styleText('bold', instance.currentStage);
|
|
@@ -155,9 +97,6 @@ export function startReport(instance) {
|
|
|
155
97
|
? `Started ${id} — completed immediately at ${stage}`
|
|
156
98
|
: `Started ${id} — now at ${stage}`;
|
|
157
99
|
}
|
|
158
|
-
/** The `--json` payload. Keys follow the CLI's `--json` vocabulary (matching
|
|
159
|
-
* `fire-action`): `instanceId` carries the document `_id`, `version` the
|
|
160
|
-
* pinned definition version; the rest mirror the instance fields. */
|
|
161
100
|
export function startResultJson(instance) {
|
|
162
101
|
return {
|
|
163
102
|
instanceId: instance._id,
|
package/dist/commands/tail.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export default class Tail extends
|
|
1
|
+
import { WorkflowCommand } from '../lib/base-command.ts';
|
|
2
|
+
export default class Tail extends WorkflowCommand {
|
|
3
3
|
static description: string;
|
|
4
4
|
static examples: string[];
|
|
5
5
|
static args: {
|