@sanity/workflow-cli 0.5.1

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.
Files changed (55) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +123 -0
  3. package/bin/run.js +8 -0
  4. package/dist/commands/abort.d.ts +30 -0
  5. package/dist/commands/abort.js +62 -0
  6. package/dist/commands/definition/delete.d.ts +36 -0
  7. package/dist/commands/definition/delete.js +86 -0
  8. package/dist/commands/definition/diff.d.ts +13 -0
  9. package/dist/commands/definition/diff.js +57 -0
  10. package/dist/commands/definition/list.d.ts +31 -0
  11. package/dist/commands/definition/list.js +83 -0
  12. package/dist/commands/definition/show.d.ts +33 -0
  13. package/dist/commands/definition/show.js +118 -0
  14. package/dist/commands/deploy.d.ts +22 -0
  15. package/dist/commands/deploy.js +97 -0
  16. package/dist/commands/diagnose.d.ts +23 -0
  17. package/dist/commands/diagnose.js +269 -0
  18. package/dist/commands/fire-action.d.ts +63 -0
  19. package/dist/commands/fire-action.js +241 -0
  20. package/dist/commands/list.d.ts +46 -0
  21. package/dist/commands/list.js +106 -0
  22. package/dist/commands/move-stage.d.ts +47 -0
  23. package/dist/commands/move-stage.js +77 -0
  24. package/dist/commands/retry-task.d.ts +9 -0
  25. package/dist/commands/retry-task.js +11 -0
  26. package/dist/commands/set-stage.d.ts +12 -0
  27. package/dist/commands/set-stage.js +18 -0
  28. package/dist/commands/show.d.ts +23 -0
  29. package/dist/commands/show.js +85 -0
  30. package/dist/commands/tail.d.ts +15 -0
  31. package/dist/commands/tail.js +73 -0
  32. package/dist/index.d.ts +1 -0
  33. package/dist/index.js +4 -0
  34. package/dist/lib/client.d.ts +17 -0
  35. package/dist/lib/client.js +40 -0
  36. package/dist/lib/config.d.ts +18 -0
  37. package/dist/lib/config.js +50 -0
  38. package/dist/lib/definitions.d.ts +36 -0
  39. package/dist/lib/definitions.js +122 -0
  40. package/dist/lib/diff.d.ts +7 -0
  41. package/dist/lib/diff.js +49 -0
  42. package/dist/lib/env.d.ts +10 -0
  43. package/dist/lib/env.js +13 -0
  44. package/dist/lib/fail.d.ts +16 -0
  45. package/dist/lib/fail.js +33 -0
  46. package/dist/lib/flags.d.ts +5 -0
  47. package/dist/lib/flags.js +8 -0
  48. package/dist/lib/operation-args.d.ts +25 -0
  49. package/dist/lib/operation-args.js +48 -0
  50. package/dist/lib/ops-report.d.ts +30 -0
  51. package/dist/lib/ops-report.js +28 -0
  52. package/dist/lib/stub.d.ts +10 -0
  53. package/dist/lib/stub.js +24 -0
  54. package/oclif.manifest.json +684 -0
  55. package/package.json +94 -0
@@ -0,0 +1,241 @@
1
+ import { Args, Command, Flags } from '@oclif/core';
2
+ import { workflow, } from '@sanity/workflow-engine';
3
+ import boxen from 'boxen';
4
+ import logSymbols from 'log-symbols';
5
+ import ora from 'ora';
6
+ import pc from 'picocolors';
7
+ import { loadClient } from "../lib/client.js";
8
+ import { loadWorkflowConfig } from "../lib/config.js";
9
+ import { fail, failOnThrow } from "../lib/fail.js";
10
+ import { tagFlags } from "../lib/flags.js";
11
+ import { resolveActor } from "../lib/operation-args.js";
12
+ import { emitWriteReport, opsAppliedLines } from "../lib/ops-report.js";
13
+ import { instanceHeader } from "./show.js";
14
+ function errorMessage(error) {
15
+ return error instanceof Error ? error.message : String(error);
16
+ }
17
+ function coerceParamValue(raw) {
18
+ try {
19
+ return JSON.parse(raw);
20
+ }
21
+ catch {
22
+ // Not valid JSON — treat it as a plain string so `--param note=hi`
23
+ // needs no quoting. The engine validates against the action's
24
+ // declared param type either way.
25
+ return raw;
26
+ }
27
+ }
28
+ function parseParam(pair) {
29
+ const eq = pair.indexOf('=');
30
+ if (eq < 1) {
31
+ throw new Error(`expected key=value, got "${pair}"`);
32
+ }
33
+ return [pair.slice(0, eq), coerceParamValue(pair.slice(eq + 1))];
34
+ }
35
+ /**
36
+ * Parse repeated `--param key=value` flags into a params object. Each
37
+ * value is JSON-parsed so numbers/booleans/objects type correctly,
38
+ * falling back to the raw string. The engine validates the result against
39
+ * the action's declared param types.
40
+ */
41
+ export function parseParams(pairs) {
42
+ return Object.fromEntries(pairs.map(parseParam));
43
+ }
44
+ function reasonText(reason) {
45
+ switch (reason.kind) {
46
+ case 'filter-failed':
47
+ return `action filter not satisfied${reason.detail ? ` (${reason.detail})` : ''}`;
48
+ case 'task-not-active':
49
+ return `task is ${reason.status}`;
50
+ case 'stage-terminal':
51
+ return `stage '${reason.stage}' is terminal`;
52
+ case 'mutation-guard-denied':
53
+ return `blocked by mutation guard(s) ${reason.guardIds.join(', ')}${reason.detail ? ` (${reason.detail})` : ''}`;
54
+ case 'instance-completed':
55
+ return `instance completed at ${reason.completedAt}`;
56
+ case 'requirements-unmet':
57
+ return `unmet requirement(s): ${reason.unmetRequirements.join(', ')}`;
58
+ }
59
+ }
60
+ function paramLabel(p) {
61
+ return `${p.name}${p.required === true ? '' : '?'}:${p.type}`;
62
+ }
63
+ function actionLines(a) {
64
+ const mark = a.allowed ? pc.green(logSymbols.success) : pc.red(logSymbols.error);
65
+ const title = a.title !== undefined ? pc.dim(` (${a.title})`) : '';
66
+ const lines = [` ${mark} ${a.task} → ${a.action}${title} ${pc.dim(`[task ${a.taskStatus}]`)}`];
67
+ if (a.params.length > 0) {
68
+ lines.push(pc.dim(` params: ${a.params.map(paramLabel).join(', ')}`));
69
+ }
70
+ if (!a.allowed && a.disabledReason !== undefined) {
71
+ lines.push(pc.dim(` disabled: ${reasonText(a.disabledReason)}`));
72
+ }
73
+ return lines;
74
+ }
75
+ /**
76
+ * The list-mode body: every action on the current stage's tasks, marked
77
+ * fireable or not (with the disabling reason), plus a hint showing the
78
+ * flags to fire one. Pure so the populated/empty permutations assert
79
+ * without a live evaluation.
80
+ */
81
+ export function renderAvailableActions(actions, stage, instanceId) {
82
+ if (actions.length === 0) {
83
+ return [`No actions on the tasks of the current stage ('${stage}').`];
84
+ }
85
+ const lines = [pc.bold(`actions on stage '${stage}':`), ''];
86
+ for (const a of actions) {
87
+ lines.push(...actionLines(a));
88
+ }
89
+ lines.push('', pc.dim('fire one with:'), pc.dim(` fire-action ${instanceId} --task <task> --action <action>`));
90
+ return lines;
91
+ }
92
+ /** The `--json` payload for a fired action — the structured counterpart of
93
+ * {@link fireActionReport}. Pure so its shape is asserted directly. */
94
+ export function fireResultJson(result, ids) {
95
+ return {
96
+ instanceId: ids.instanceId,
97
+ task: ids.task,
98
+ action: ids.action,
99
+ fired: result.fired,
100
+ cascaded: result.cascaded,
101
+ currentStage: result.instance.currentStage,
102
+ ...(result.ranOps !== undefined ? { ranOps: result.ranOps } : {}),
103
+ };
104
+ }
105
+ /**
106
+ * Turn a `fireAction` result into the spinner message + ops block. Pure so
107
+ * the fired / no-op / cascaded / ops permutations assert without a live
108
+ * fire.
109
+ */
110
+ export function fireActionReport(result, task, action) {
111
+ const stage = pc.bold(result.instance.currentStage);
112
+ if (!result.fired) {
113
+ return {
114
+ fired: false,
115
+ message: `fireAction returned without firing — task '${task}' is no longer active`,
116
+ opsLines: [],
117
+ };
118
+ }
119
+ const tail = result.cascaded > 0 ? `, then cascaded ${result.cascaded} auto-transition(s)` : '';
120
+ return {
121
+ fired: true,
122
+ message: `Fired ${pc.bold(action)} on ${pc.bold(task)} — now at ${stage}${tail}`,
123
+ opsLines: opsAppliedLines(result.ranOps),
124
+ };
125
+ }
126
+ export default class FireAction extends Command {
127
+ static description = "Fire an action on an instance — act on a user's behalf to unstick a waiting task. Omit --action to list what can be fired.";
128
+ static examples = [
129
+ '<%= config.bin %> fire-action wf-instance.abc123',
130
+ '<%= config.bin %> fire-action wf-instance.abc123 --task approve --action approve',
131
+ '<%= config.bin %> fire-action wf-instance.abc123 --task approve --action approve --as user.xyz --as-role editor',
132
+ '<%= config.bin %> fire-action wf-instance.abc123 --task publish --action publish --param note=shipping',
133
+ ];
134
+ static args = {
135
+ instanceId: Args.string({ required: true, description: 'Workflow instance id.' }),
136
+ };
137
+ static flags = {
138
+ ...tagFlags,
139
+ task: Flags.string({
140
+ description: 'Task the action belongs to. Required to fire; omit --action to list.',
141
+ }),
142
+ action: Flags.string({
143
+ description: 'Action to fire. Omit to list the actions available on the instance.',
144
+ }),
145
+ param: Flags.string({
146
+ description: 'Action param as key=value (repeatable). Values are JSON-parsed, falling back to a string.',
147
+ multiple: true,
148
+ default: [],
149
+ }),
150
+ as: Flags.string({
151
+ description: 'Attribute the fire to this Sanity user id (default: a workflow-cli system actor).',
152
+ }),
153
+ 'as-role': Flags.string({
154
+ description: 'Role to attribute to the --as user (repeatable), so role-gated filters pass.',
155
+ multiple: true,
156
+ default: [],
157
+ }),
158
+ json: Flags.boolean({
159
+ description: 'Emit structured JSON instead of rendered output.',
160
+ default: false,
161
+ }),
162
+ };
163
+ async run() {
164
+ const { args, flags } = await this.parse(FireAction);
165
+ const config = loadWorkflowConfig(flags.tag);
166
+ const client = loadClient();
167
+ const actor = failOnThrow('fire-action usage error:', () => resolveActor(flags.as, flags['as-role']));
168
+ if (flags.action === undefined) {
169
+ await this.listActions(client, config, args.instanceId, actor, flags.json);
170
+ return;
171
+ }
172
+ if (flags.task === undefined) {
173
+ fail('fire-action usage error:', 'specify --task for the action (or omit --action to list what can be fired)');
174
+ }
175
+ await this.fireOne({
176
+ client,
177
+ config,
178
+ instanceId: args.instanceId,
179
+ actor,
180
+ task: flags.task,
181
+ action: flags.action,
182
+ params: flags.param,
183
+ json: flags.json,
184
+ });
185
+ }
186
+ async listActions(client, config, instanceId, actor, json) {
187
+ const { evaluation, actions } = await workflow
188
+ .availableActions({
189
+ client,
190
+ tag: config.tag,
191
+ workflowResource: config.workflowResource,
192
+ instanceId,
193
+ access: { actor },
194
+ })
195
+ .catch((error) => fail('fire-action failed:', errorMessage(error)));
196
+ const stage = evaluation.instance.currentStage;
197
+ if (json) {
198
+ this.log(JSON.stringify({ instanceId, stage, actions }, null, 2));
199
+ return;
200
+ }
201
+ this.log(boxen(instanceHeader(evaluation.instance), {
202
+ padding: 1,
203
+ borderStyle: 'round',
204
+ title: 'fire-action',
205
+ }));
206
+ this.log('');
207
+ for (const line of renderAvailableActions(actions, stage, instanceId)) {
208
+ this.log(line);
209
+ }
210
+ }
211
+ async fireOne(opts) {
212
+ const { client, config, instanceId, actor, task, action, json } = opts;
213
+ const params = failOnThrow('Invalid --param:', () => parseParams(opts.params));
214
+ const fireArgs = {
215
+ client,
216
+ tag: config.tag,
217
+ workflowResource: config.workflowResource,
218
+ instanceId,
219
+ task,
220
+ action,
221
+ access: { actor },
222
+ params,
223
+ };
224
+ if (json) {
225
+ const result = await workflow
226
+ .fireAction(fireArgs)
227
+ .catch((error) => fail('fireAction error:', errorMessage(error)));
228
+ this.log(JSON.stringify(fireResultJson(result, { instanceId, task, action }), null, 2));
229
+ return;
230
+ }
231
+ const spinner = ora(`Firing ${action} on ${task}…`).start();
232
+ try {
233
+ const result = await workflow.fireAction(fireArgs);
234
+ emitWriteReport(spinner, fireActionReport(result, task, action), (line) => this.log(line));
235
+ }
236
+ catch (error) {
237
+ spinner.fail('Action rejected');
238
+ fail('fireAction error:', errorMessage(error));
239
+ }
240
+ }
241
+ }
@@ -0,0 +1,46 @@
1
+ import { Command } from '@oclif/core';
2
+ interface InstanceRow {
3
+ _id: string;
4
+ definition: string;
5
+ currentStage: string;
6
+ startedAt: string;
7
+ completedAt?: string;
8
+ abortedAt?: string;
9
+ failedTaskCount: number;
10
+ }
11
+ interface ListFlags {
12
+ 'in-flight': boolean;
13
+ failed: boolean;
14
+ 'workflow-id'?: string | undefined;
15
+ limit: number;
16
+ }
17
+ /**
18
+ * Build the GROQ query + params for `list` from its flags. Pure so the
19
+ * filter combinations can be asserted without a live dataset.
20
+ */
21
+ export declare function buildListQuery(flags: ListFlags): {
22
+ groq: string;
23
+ params: Record<string, unknown>;
24
+ };
25
+ /** Map a queried instance to its display row (derives the status column). */
26
+ export declare function instanceRow(r: InstanceRow): {
27
+ _id: string;
28
+ definition: string;
29
+ currentStage: string;
30
+ startedAt: string;
31
+ status: 'aborted' | 'completed' | 'in-flight';
32
+ failedTaskCount: number;
33
+ };
34
+ export default class List extends Command {
35
+ static description: string;
36
+ static examples: string[];
37
+ static flags: {
38
+ 'in-flight': import("@oclif/core/interfaces").BooleanFlag<boolean>;
39
+ failed: import("@oclif/core/interfaces").BooleanFlag<boolean>;
40
+ 'workflow-id': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
41
+ subject: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
42
+ limit: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
43
+ };
44
+ run(): Promise<void>;
45
+ }
46
+ export {};
@@ -0,0 +1,106 @@
1
+ import { Command, Flags } from '@oclif/core';
2
+ import { WORKFLOW_INSTANCE_TYPE } from '@sanity/workflow-engine';
3
+ import { Table } from 'console-table-printer';
4
+ import logSymbols from 'log-symbols';
5
+ import { loadClient } from "../lib/client.js";
6
+ /**
7
+ * Build the GROQ query + params for `list` from its flags. Pure so the
8
+ * filter combinations can be asserted without a live dataset.
9
+ */
10
+ export function buildListQuery(flags) {
11
+ const filters = [`_type == "${WORKFLOW_INSTANCE_TYPE}"`];
12
+ if (flags['in-flight'])
13
+ filters.push('!defined(completedAt)');
14
+ if (flags['workflow-id'])
15
+ filters.push('definition == $definition');
16
+ // Cheap approximation of --failed: any task in any stage with status "failed".
17
+ if (flags.failed)
18
+ filters.push('count(stages[].tasks[status == "failed"]) > 0');
19
+ const groq = `*[${filters.join(' && ')}] | order(startedAt desc) [0...$limit]{
20
+ _id,
21
+ definition,
22
+ currentStage,
23
+ startedAt,
24
+ completedAt,
25
+ abortedAt,
26
+ "failedTaskCount": count(stages[].tasks[status == "failed"])
27
+ }`;
28
+ const params = { limit: flags.limit };
29
+ if (flags['workflow-id'])
30
+ params['definition'] = flags['workflow-id'];
31
+ return { groq, params };
32
+ }
33
+ /** Aborted instances also carry `completedAt`, so check `abortedAt` first. */
34
+ function deriveStatus(r) {
35
+ if (r.abortedAt)
36
+ return 'aborted';
37
+ if (r.completedAt)
38
+ return 'completed';
39
+ return 'in-flight';
40
+ }
41
+ /** Map a queried instance to its display row (derives the status column). */
42
+ export function instanceRow(r) {
43
+ return {
44
+ _id: r._id,
45
+ definition: r.definition,
46
+ currentStage: r.currentStage,
47
+ startedAt: r.startedAt,
48
+ status: deriveStatus(r),
49
+ failedTaskCount: r.failedTaskCount,
50
+ };
51
+ }
52
+ export default class List extends Command {
53
+ static description = 'List workflow instances in the configured dataset.';
54
+ static examples = [
55
+ '<%= config.bin %> list',
56
+ '<%= config.bin %> list --in-flight',
57
+ '<%= config.bin %> list --workflow-id productLaunch',
58
+ ];
59
+ static flags = {
60
+ 'in-flight': Flags.boolean({
61
+ description: 'Only instances that have not completed.',
62
+ default: false,
63
+ }),
64
+ failed: Flags.boolean({
65
+ description: 'Only instances with at least one failed task.',
66
+ default: false,
67
+ }),
68
+ 'workflow-id': Flags.string({
69
+ description: 'Filter by workflow definition name.',
70
+ }),
71
+ subject: Flags.string({
72
+ description: 'Filter by subject document id. (not yet implemented)',
73
+ }),
74
+ limit: Flags.integer({
75
+ description: 'Maximum rows to return.',
76
+ default: 50,
77
+ }),
78
+ };
79
+ async run() {
80
+ const { flags } = await this.parse(List);
81
+ if (flags.subject) {
82
+ this.warn('--subject is parsed but not yet implemented; ignoring.');
83
+ }
84
+ const { groq, params } = buildListQuery(flags);
85
+ const client = loadClient();
86
+ const rows = await client.fetch(groq, params);
87
+ if (rows.length === 0) {
88
+ this.log(`${logSymbols.info} no instances match`);
89
+ return;
90
+ }
91
+ const table = new Table({
92
+ columns: [
93
+ { name: '_id', title: 'instance', alignment: 'left' },
94
+ { name: 'definition', title: 'workflow', alignment: 'left' },
95
+ { name: 'currentStage', title: 'stage', alignment: 'left' },
96
+ { name: 'startedAt', title: 'started', alignment: 'left' },
97
+ { name: 'status', title: 'status', alignment: 'left' },
98
+ { name: 'failedTaskCount', title: 'failed', alignment: 'right' },
99
+ ],
100
+ });
101
+ for (const r of rows) {
102
+ table.addRow(instanceRow(r));
103
+ }
104
+ table.printTable();
105
+ }
106
+ }
@@ -0,0 +1,47 @@
1
+ import { Command } from '@oclif/core';
2
+ import { type SetStageArgs } from '@sanity/workflow-engine';
3
+ import { type WorkflowConfig } from '../lib/config.ts';
4
+ import { type RanOp } from '../lib/ops-report.ts';
5
+ export default class MoveStage extends Command {
6
+ static description: string;
7
+ static examples: string[];
8
+ static args: {
9
+ instanceId: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
10
+ };
11
+ static flags: {
12
+ to: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
13
+ reason: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
14
+ tag: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
15
+ };
16
+ run(): Promise<void>;
17
+ }
18
+ /** The shared operation args ({@link buildOperationArgs}) plus the
19
+ * setStage-specific target. */
20
+ export declare function buildSetStageArgs(config: WorkflowConfig, instanceId: string, targetStage: string, reason: string | undefined): Omit<SetStageArgs, 'client'>;
21
+ interface SetStageOutcome {
22
+ fired: boolean;
23
+ cascaded: number;
24
+ instance: {
25
+ currentStage: string;
26
+ };
27
+ ranOps?: RanOp[];
28
+ }
29
+ export interface MoveStageReport {
30
+ fired: boolean;
31
+ /** Spinner message — warn copy when not fired, success copy otherwise. */
32
+ message: string;
33
+ /**
34
+ * The full "ops applied" block to print after the spinner — a blank
35
+ * line, the header, and one line per op. Empty when nothing ran (or
36
+ * when the transition didn't fire), so the caller can print it
37
+ * unconditionally.
38
+ */
39
+ opsLines: string[];
40
+ }
41
+ /**
42
+ * Turn a `setStage` result into the spinner message + ops block. Pure so
43
+ * the fired / not-fired / cascaded / ops permutations can be asserted
44
+ * without driving a real transition.
45
+ */
46
+ export declare function moveStageReport(result: SetStageOutcome, to: string): MoveStageReport;
47
+ export {};
@@ -0,0 +1,77 @@
1
+ import { Args, Command, Flags } from '@oclif/core';
2
+ import { workflow } from '@sanity/workflow-engine';
3
+ import ora from 'ora';
4
+ import pc from 'picocolors';
5
+ import { loadClient } from "../lib/client.js";
6
+ import { loadWorkflowConfig } from "../lib/config.js";
7
+ import { fail } from "../lib/fail.js";
8
+ import { tagFlags } from "../lib/flags.js";
9
+ import { buildOperationArgs } from "../lib/operation-args.js";
10
+ import { emitWriteReport, opsAppliedLines } from "../lib/ops-report.js";
11
+ export default class MoveStage extends Command {
12
+ static description = 'Move an instance to a new stage. Guards + transition effects run as if the transition fired normally.';
13
+ static examples = [
14
+ '<%= config.bin %> move-stage wf-instance.abc123 --to ready',
15
+ "<%= config.bin %> move-stage wf-instance.abc123 --to ready --reason 'unblock for demo'",
16
+ ];
17
+ static args = {
18
+ instanceId: Args.string({
19
+ required: true,
20
+ description: 'Workflow instance id to move.',
21
+ }),
22
+ };
23
+ static flags = {
24
+ ...tagFlags,
25
+ to: Flags.string({
26
+ required: true,
27
+ description: 'Target stage name.',
28
+ }),
29
+ reason: Flags.string({
30
+ description: 'Free-text reason — recorded on the history entry for audit.',
31
+ }),
32
+ };
33
+ async run() {
34
+ const { args, flags } = await this.parse(MoveStage);
35
+ const config = loadWorkflowConfig(flags.tag);
36
+ const client = loadClient();
37
+ const spinner = ora(`Moving ${args.instanceId} → ${flags.to}…`).start();
38
+ try {
39
+ const result = await workflow.setStage({
40
+ client,
41
+ ...buildSetStageArgs(config, args.instanceId, flags.to, flags.reason),
42
+ });
43
+ emitWriteReport(spinner, moveStageReport(result, flags.to), (line) => this.log(line));
44
+ }
45
+ catch (error) {
46
+ spinner.fail('Move rejected');
47
+ const message = error instanceof Error ? error.message : String(error);
48
+ fail('setStage error:', message);
49
+ }
50
+ }
51
+ }
52
+ /** The shared operation args ({@link buildOperationArgs}) plus the
53
+ * setStage-specific target. */
54
+ export function buildSetStageArgs(config, instanceId, targetStage, reason) {
55
+ return { ...buildOperationArgs(config, instanceId, reason), targetStage };
56
+ }
57
+ /**
58
+ * Turn a `setStage` result into the spinner message + ops block. Pure so
59
+ * the fired / not-fired / cascaded / ops permutations can be asserted
60
+ * without driving a real transition.
61
+ */
62
+ export function moveStageReport(result, to) {
63
+ const stage = pc.bold(result.instance.currentStage);
64
+ if (!result.fired) {
65
+ return {
66
+ fired: false,
67
+ message: `setStage returned without firing — instance is already at ${stage}`,
68
+ opsLines: [],
69
+ };
70
+ }
71
+ const tail = result.cascaded > 0 ? `, then cascaded ${result.cascaded} auto-transition(s)` : '';
72
+ return {
73
+ fired: true,
74
+ message: `Now at ${stage} (was → ${to}${tail})`,
75
+ opsLines: opsAppliedLines(result.ranOps),
76
+ };
77
+ }
@@ -0,0 +1,9 @@
1
+ import { StubCommand } from '../lib/stub.ts';
2
+ export default class RetryTask extends StubCommand {
3
+ static hidden: boolean;
4
+ static description: string;
5
+ static args: {
6
+ instanceId: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
7
+ task: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
8
+ };
9
+ }
@@ -0,0 +1,11 @@
1
+ import { Args } from '@oclif/core';
2
+ import { StubCommand } from "../lib/stub.js";
3
+ export default class RetryTask extends StubCommand {
4
+ // Stubbed, so kept off the help + published surface until it's wired.
5
+ static hidden = true;
6
+ static description = 'Re-invoke a failed task on an in-flight instance.';
7
+ static args = {
8
+ instanceId: Args.string({ required: true, description: 'Workflow instance id.' }),
9
+ task: Args.string({ required: true, description: 'Task name within the current stage.' }),
10
+ };
11
+ }
@@ -0,0 +1,12 @@
1
+ import { StubCommand } from '../lib/stub.ts';
2
+ export default class SetStage extends StubCommand {
3
+ static hidden: boolean;
4
+ static description: string;
5
+ static args: {
6
+ instanceId: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
7
+ };
8
+ static flags: {
9
+ to: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
10
+ reason: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
11
+ };
12
+ }
@@ -0,0 +1,18 @@
1
+ import { Args, Flags } from '@oclif/core';
2
+ import { StubCommand } from "../lib/stub.js";
3
+ export default class SetStage extends StubCommand {
4
+ // Stubbed, so kept off the help + published surface until it's wired.
5
+ static hidden = true;
6
+ // Engine note: workflow.setStage currently always runs guards + stage
7
+ // entry (task activation, effects). A "manual override" that bypasses
8
+ // them is engine work that hasn't landed; this command stub captures
9
+ // the intended surface only.
10
+ static description = "Force-set an instance's current stage, bypassing guards + effects (manual override).";
11
+ static args = {
12
+ instanceId: Args.string({ required: true, description: 'Workflow instance id.' }),
13
+ };
14
+ static flags = {
15
+ to: Flags.string({ required: true, description: 'Target stage name.' }),
16
+ reason: Flags.string({ description: 'Reason for the manual override.' }),
17
+ };
18
+ }
@@ -0,0 +1,23 @@
1
+ import { Command } from '@oclif/core';
2
+ import { type WorkflowInstance } from '@sanity/workflow-engine';
3
+ export default class Show extends Command {
4
+ static description: string;
5
+ static examples: string[];
6
+ static args: {
7
+ instanceId: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
8
+ };
9
+ static flags: {
10
+ include: import("@oclif/core/interfaces").OptionFlag<string[], import("@oclif/core/interfaces").CustomOptions>;
11
+ };
12
+ run(): Promise<void>;
13
+ }
14
+ /** The boxed summary block at the top of `show` output. */
15
+ export declare function instanceHeader(instance: WorkflowInstance): string;
16
+ /**
17
+ * The per-instance body lines: every stage with its tasks, any pending
18
+ * effects, and (optionally) the history log. Pure so it can be asserted
19
+ * without driving the oclif command.
20
+ */
21
+ export declare function describeInstance(instance: WorkflowInstance, options: {
22
+ includeHistory: boolean;
23
+ }): string[];