@sanity/workflow-cli 0.9.0 → 0.10.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 +485 -0
- package/README.md +62 -18
- 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 +40 -46
- package/dist/commands/definition/show.d.ts +22 -3
- package/dist/commands/definition/show.js +23 -21
- 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 +39 -45
- package/dist/commands/fire-action.d.ts +2 -4
- package/dist/commands/fire-action.js +27 -61
- package/dist/commands/list.d.ts +7 -8
- package/dist/commands/list.js +43 -56
- 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 -7
- package/dist/commands/start.js +18 -54
- package/dist/commands/tail.d.ts +2 -2
- package/dist/commands/tail.js +20 -25
- package/dist/hooks/finally/telemetry.d.ts +8 -0
- package/dist/hooks/finally/telemetry.js +13 -0
- package/dist/hooks/prerun/telemetry.d.ts +5 -0
- package/dist/hooks/prerun/telemetry.js +5 -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.js +5 -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 +12 -6
- package/dist/lib/fail.js +24 -25
- 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 +86 -0
- package/dist/lib/share-definitions.js +106 -0
- package/dist/lib/stub.js +0 -7
- package/dist/lib/telemetry-setup.d.ts +66 -0
- package/dist/lib/telemetry-setup.js +92 -0
- package/dist/lib/telemetry.d.ts +100 -0
- package/dist/lib/telemetry.js +89 -0
- package/dist/lib/ui.d.ts +33 -1
- package/dist/lib/ui.js +32 -21
- package/oclif.manifest.json +8 -47
- package/package.json +14 -8
|
@@ -1,30 +1,20 @@
|
|
|
1
1
|
import { styleText } from 'node:util';
|
|
2
|
-
import { Args,
|
|
3
|
-
import { deniedGuardLabels, workflow, } from '@sanity/workflow-engine';
|
|
2
|
+
import { Args, Flags } from '@oclif/core';
|
|
3
|
+
import { actionDisabledDetail, deniedGuardLabels, workflow, } from '@sanity/workflow-engine';
|
|
4
4
|
import logSymbols from 'log-symbols';
|
|
5
|
-
import
|
|
5
|
+
import { WorkflowCommand } from "../lib/base-command.js";
|
|
6
6
|
import { resolveInstanceContext } from "../lib/context.js";
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import { baseEngineArgs
|
|
10
|
-
import {
|
|
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 { cascadeTail, opsAppliedLines, runWriteVerb, } from "../lib/ops-report.js";
|
|
11
11
|
import { parseParams } from "../lib/params.js";
|
|
12
12
|
import { instanceHeader } from "./show.js";
|
|
13
13
|
function reasonText(reason) {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
return `action filter not satisfied${reason.detail ? ` (${reason.detail})` : ''}`;
|
|
17
|
-
case 'activity-not-active':
|
|
18
|
-
return `activity is ${reason.status}`;
|
|
19
|
-
case 'stage-terminal':
|
|
20
|
-
return `stage '${reason.stage}' is terminal`;
|
|
21
|
-
case 'mutation-guard-denied':
|
|
22
|
-
return `blocked by mutation guard(s) ${deniedGuardLabels(reason.denied).join(', ')}`;
|
|
23
|
-
case 'instance-completed':
|
|
24
|
-
return `instance completed at ${reason.completedAt}`;
|
|
25
|
-
case 'requirements-unmet':
|
|
26
|
-
return `unmet requirement(s): ${reason.unmetRequirements.join(', ')}`;
|
|
14
|
+
if (reason.kind === 'mutation-guard-denied') {
|
|
15
|
+
return `blocked by mutation guard(s) ${deniedGuardLabels(reason.denied).join(', ')}`;
|
|
27
16
|
}
|
|
17
|
+
return actionDisabledDetail(reason);
|
|
28
18
|
}
|
|
29
19
|
function paramLabel(p) {
|
|
30
20
|
return `${p.name}${p.required === true ? '' : '?'}:${p.type}`;
|
|
@@ -43,12 +33,6 @@ function actionLines(a) {
|
|
|
43
33
|
}
|
|
44
34
|
return lines;
|
|
45
35
|
}
|
|
46
|
-
/**
|
|
47
|
-
* The list-mode body: every action on the current stage's activities, marked
|
|
48
|
-
* fireable or not (with the disabling reason), plus a hint showing the
|
|
49
|
-
* flags to fire one. Pure so the populated/empty permutations assert
|
|
50
|
-
* without a live evaluation.
|
|
51
|
-
*/
|
|
52
36
|
export function renderAvailableActions({ actions, stage, instanceId, }) {
|
|
53
37
|
if (actions.length === 0) {
|
|
54
38
|
return [`No actions on the activities of the current stage ('${stage}').`];
|
|
@@ -60,8 +44,6 @@ export function renderAvailableActions({ actions, stage, instanceId, }) {
|
|
|
60
44
|
lines.push('', styleText('dim', 'Fire one with:'), styleText('dim', ` fire-action ${instanceId} --activity <activity> --action <action>`));
|
|
61
45
|
return lines;
|
|
62
46
|
}
|
|
63
|
-
/** The `--json` payload for a fired action — the structured counterpart of
|
|
64
|
-
* {@link fireActionReport}. Pure so its shape is asserted directly. */
|
|
65
47
|
export function fireResultJson(result, ids) {
|
|
66
48
|
return {
|
|
67
49
|
instanceId: ids.instanceId,
|
|
@@ -73,11 +55,6 @@ export function fireResultJson(result, ids) {
|
|
|
73
55
|
...(result.ranOps !== undefined ? { ranOps: result.ranOps } : {}),
|
|
74
56
|
};
|
|
75
57
|
}
|
|
76
|
-
/**
|
|
77
|
-
* Turn a `fireAction` result into the spinner message + ops block. Pure so
|
|
78
|
-
* the fired / no-op / cascaded / ops permutations assert without a live
|
|
79
|
-
* fire.
|
|
80
|
-
*/
|
|
81
58
|
export function fireActionReport({ result, activity, action, }) {
|
|
82
59
|
const stage = styleText('bold', result.instance.currentStage);
|
|
83
60
|
if (!result.changed) {
|
|
@@ -87,19 +64,17 @@ export function fireActionReport({ result, activity, action, }) {
|
|
|
87
64
|
opsLines: [],
|
|
88
65
|
};
|
|
89
66
|
}
|
|
90
|
-
const tail = result.cascaded > 0 ? `, then cascaded ${result.cascaded} auto-transition(s)` : '';
|
|
91
67
|
return {
|
|
92
68
|
changed: true,
|
|
93
|
-
message: `Fired ${styleText('bold', action)} on ${styleText('bold', activity)} — now at ${stage}${
|
|
69
|
+
message: `Fired ${styleText('bold', action)} on ${styleText('bold', activity)} — now at ${stage}${cascadeTail(result.cascaded)}`,
|
|
94
70
|
opsLines: opsAppliedLines(result.ranOps),
|
|
95
71
|
};
|
|
96
72
|
}
|
|
97
|
-
export default class FireAction extends
|
|
98
|
-
static description =
|
|
73
|
+
export default class FireAction extends WorkflowCommand {
|
|
74
|
+
static description = 'Fire an action on an instance to unstick a waiting activity — the write acts as the configured token. Omit --action to list what can be fired.';
|
|
99
75
|
static examples = [
|
|
100
76
|
'<%= config.bin %> fire-action wf-instance.abc123',
|
|
101
77
|
'<%= config.bin %> fire-action wf-instance.abc123 --activity approve --action approve',
|
|
102
|
-
'<%= config.bin %> fire-action wf-instance.abc123 --activity approve --action approve --as user.xyz --as-role editor',
|
|
103
78
|
'<%= config.bin %> fire-action wf-instance.abc123 --activity publish --action publish --param note=shipping',
|
|
104
79
|
];
|
|
105
80
|
static args = {
|
|
@@ -118,19 +93,16 @@ export default class FireAction extends Command {
|
|
|
118
93
|
multiple: true,
|
|
119
94
|
default: [],
|
|
120
95
|
}),
|
|
121
|
-
...actorFlags,
|
|
122
96
|
...jsonFlags,
|
|
123
97
|
};
|
|
124
98
|
async run() {
|
|
125
99
|
const { args, flags } = await this.parse(FireAction);
|
|
126
100
|
const { client, scope } = await resolveInstanceContext(flags, args.instanceId);
|
|
127
|
-
const actor = failOnThrow('fire-action usage error:', () => resolveActor(flags.as, flags['as-role']));
|
|
128
101
|
if (flags.action === undefined) {
|
|
129
102
|
await this.listActions({
|
|
130
103
|
client,
|
|
131
104
|
scope,
|
|
132
105
|
instanceId: args.instanceId,
|
|
133
|
-
actor,
|
|
134
106
|
json: flags.json,
|
|
135
107
|
});
|
|
136
108
|
return;
|
|
@@ -142,21 +114,20 @@ export default class FireAction extends Command {
|
|
|
142
114
|
client,
|
|
143
115
|
scope,
|
|
144
116
|
instanceId: args.instanceId,
|
|
145
|
-
actor,
|
|
146
117
|
activity: flags.activity,
|
|
147
118
|
action: flags.action,
|
|
148
119
|
params: flags.param,
|
|
149
120
|
json: flags.json,
|
|
150
121
|
});
|
|
151
122
|
}
|
|
152
|
-
async listActions({ client, scope, instanceId,
|
|
123
|
+
async listActions({ client, scope, instanceId, json, }) {
|
|
153
124
|
const { evaluation, actions } = await workflow
|
|
154
125
|
.availableActions({
|
|
155
126
|
client,
|
|
156
|
-
...baseEngineArgs(scope
|
|
127
|
+
...baseEngineArgs(scope),
|
|
157
128
|
instanceId,
|
|
158
129
|
})
|
|
159
|
-
.catch((error) => fail('fire-action failed:',
|
|
130
|
+
.catch((error) => fail('fire-action failed:', failureDetail(error)));
|
|
160
131
|
const stage = evaluation.instance.currentStage;
|
|
161
132
|
if (json) {
|
|
162
133
|
this.log(JSON.stringify({ instanceId, stage, actions }, null, 2));
|
|
@@ -169,11 +140,11 @@ export default class FireAction extends Command {
|
|
|
169
140
|
}
|
|
170
141
|
}
|
|
171
142
|
async fireOne(opts) {
|
|
172
|
-
const { client, scope, instanceId,
|
|
143
|
+
const { client, scope, instanceId, activity, action, json } = opts;
|
|
173
144
|
const params = failOnThrow('Invalid --param:', () => parseParams(opts.params));
|
|
174
145
|
const fireArgs = {
|
|
175
146
|
client,
|
|
176
|
-
...baseEngineArgs(scope
|
|
147
|
+
...baseEngineArgs(scope),
|
|
177
148
|
instanceId,
|
|
178
149
|
activity,
|
|
179
150
|
action,
|
|
@@ -182,22 +153,17 @@ export default class FireAction extends Command {
|
|
|
182
153
|
if (json) {
|
|
183
154
|
const result = await workflow
|
|
184
155
|
.fireAction(fireArgs)
|
|
185
|
-
.catch((error) => fail('fire-action error:',
|
|
156
|
+
.catch((error) => fail('fire-action error:', failureDetail(error)));
|
|
186
157
|
this.log(JSON.stringify(fireResultJson(result, { instanceId, activity, action }), null, 2));
|
|
187
158
|
return;
|
|
188
159
|
}
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
}
|
|
198
|
-
catch (error) {
|
|
199
|
-
spinner.fail('Action rejected');
|
|
200
|
-
fail('fire-action error:', errorMessage(error));
|
|
201
|
-
}
|
|
160
|
+
await runWriteVerb({
|
|
161
|
+
startLabel: `Firing ${action} on ${activity}…`,
|
|
162
|
+
failLabel: 'Action rejected',
|
|
163
|
+
failHeadline: 'fire-action error:',
|
|
164
|
+
run: () => workflow.fireAction(fireArgs),
|
|
165
|
+
report: (result) => fireActionReport({ result, activity, action }),
|
|
166
|
+
log: (line) => this.log(line),
|
|
167
|
+
});
|
|
202
168
|
}
|
|
203
169
|
}
|
package/dist/commands/list.d.ts
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { type TerminalState } from '@sanity/workflow-engine';
|
|
2
|
+
import { WorkflowCommand } from '../lib/base-command.ts';
|
|
2
3
|
interface InstanceRow {
|
|
3
4
|
_id: string;
|
|
4
5
|
definition: string;
|
|
5
6
|
tag: string;
|
|
6
7
|
currentStage: string;
|
|
7
|
-
completedAt?: string;
|
|
8
|
-
abortedAt?: string;
|
|
8
|
+
completedAt?: string | null;
|
|
9
|
+
abortedAt?: string | null;
|
|
9
10
|
lastChangedAt: string;
|
|
10
11
|
}
|
|
11
|
-
type InstanceStatus = 'aborted' | 'completed' | 'in-flight';
|
|
12
12
|
interface ListFlags {
|
|
13
13
|
'in-flight': boolean;
|
|
14
14
|
failed: boolean;
|
|
@@ -25,17 +25,16 @@ export declare function buildListQuery(flags: ListFlags): {
|
|
|
25
25
|
groq: string;
|
|
26
26
|
params: Record<string, unknown>;
|
|
27
27
|
};
|
|
28
|
-
/** Map a queried instance to its display row (derives the status column).
|
|
29
|
-
* Pure — status is the semantic value; the table colors it at render. */
|
|
28
|
+
/** Map a queried instance to its display row (derives the status column). */
|
|
30
29
|
export declare function instanceRow(r: InstanceRow): {
|
|
31
30
|
_id: string;
|
|
32
31
|
definition: string;
|
|
33
32
|
tag: string;
|
|
34
33
|
currentStage: string;
|
|
35
|
-
status:
|
|
34
|
+
status: TerminalState;
|
|
36
35
|
lastChangedAt: string;
|
|
37
36
|
};
|
|
38
|
-
export default class List extends
|
|
37
|
+
export default class List extends WorkflowCommand {
|
|
39
38
|
static description: string;
|
|
40
39
|
static examples: string[];
|
|
41
40
|
static flags: {
|
package/dist/commands/list.js
CHANGED
|
@@ -1,23 +1,17 @@
|
|
|
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, 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";
|
|
7
6
|
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
|
-
*/
|
|
7
|
+
import { runReadAcrossTargets } from "../lib/read-fanout.js";
|
|
8
|
+
import { clipToLimit, formatAge, formatTable } from "../lib/ui.js";
|
|
14
9
|
export function buildListQuery(flags) {
|
|
15
10
|
const filters = [`_type == "${WORKFLOW_INSTANCE_TYPE}"`];
|
|
16
11
|
if (flags['in-flight'])
|
|
17
12
|
filters.push('!defined(completedAt)');
|
|
18
13
|
if (flags.definition)
|
|
19
14
|
filters.push('definition == $definition');
|
|
20
|
-
// Cheap approximation of --failed: any activity in any stage with status "failed".
|
|
21
15
|
if (flags.failed)
|
|
22
16
|
filters.push('count(stages[].activities[status == "failed"]) > 0');
|
|
23
17
|
if (flags.tag)
|
|
@@ -31,39 +25,27 @@ export function buildListQuery(flags) {
|
|
|
31
25
|
abortedAt,
|
|
32
26
|
lastChangedAt
|
|
33
27
|
}`;
|
|
34
|
-
const params = { limit: flags.limit };
|
|
28
|
+
const params = { limit: flags.limit + 1 };
|
|
35
29
|
if (flags.definition)
|
|
36
30
|
params['definition'] = flags.definition;
|
|
37
31
|
if (flags.tag)
|
|
38
32
|
params['tag'] = flags.tag;
|
|
39
33
|
return { groq, params };
|
|
40
34
|
}
|
|
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
35
|
export function instanceRow(r) {
|
|
52
36
|
return {
|
|
53
37
|
_id: r._id,
|
|
54
38
|
definition: r.definition,
|
|
55
39
|
tag: r.tag,
|
|
56
40
|
currentStage: r.currentStage,
|
|
57
|
-
status:
|
|
41
|
+
status: terminalState({
|
|
42
|
+
...(r.completedAt != null ? { completedAt: r.completedAt } : {}),
|
|
43
|
+
...(r.abortedAt != null ? { abortedAt: r.abortedAt } : {}),
|
|
44
|
+
}),
|
|
58
45
|
lastChangedAt: r.lastChangedAt,
|
|
59
46
|
};
|
|
60
47
|
}
|
|
61
|
-
|
|
62
|
-
completed: 'green',
|
|
63
|
-
aborted: 'dim',
|
|
64
|
-
'in-flight': 'cyan',
|
|
65
|
-
};
|
|
66
|
-
export default class List extends Command {
|
|
48
|
+
export default class List extends WorkflowCommand {
|
|
67
49
|
static description = 'List workflow instances in the configured dataset.';
|
|
68
50
|
static examples = [
|
|
69
51
|
'<%= config.bin %> list',
|
|
@@ -91,34 +73,39 @@ export default class List extends Command {
|
|
|
91
73
|
};
|
|
92
74
|
async run() {
|
|
93
75
|
const { flags } = await this.parse(List);
|
|
94
|
-
const
|
|
76
|
+
const targets = await resolveReadTargets(flags);
|
|
95
77
|
const { groq, params } = buildListQuery(flags);
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
this.log(
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
{
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
78
|
+
const failures = await runReadAcrossTargets({
|
|
79
|
+
targets,
|
|
80
|
+
log: (line) => this.log(line),
|
|
81
|
+
run: async ({ client }) => {
|
|
82
|
+
const fetched = await client.fetch(groq, params, { tag: 'list' });
|
|
83
|
+
if (fetched.length === 0) {
|
|
84
|
+
this.log(`${logSymbols.info} no instances match`);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
const { rows, note } = clipToLimit(fetched, flags.limit);
|
|
88
|
+
const lines = formatTable(['instance', 'workflow', 'tag', 'stage', 'status', 'updated'], rows.map((r) => {
|
|
89
|
+
const row = instanceRow(r);
|
|
90
|
+
return [
|
|
91
|
+
row._id,
|
|
92
|
+
row.definition,
|
|
93
|
+
row.tag,
|
|
94
|
+
row.currentStage,
|
|
95
|
+
row.status,
|
|
96
|
+
formatAge(row.lastChangedAt),
|
|
97
|
+
];
|
|
98
|
+
}));
|
|
99
|
+
for (const line of lines) {
|
|
100
|
+
this.log(line);
|
|
101
|
+
}
|
|
102
|
+
if (note) {
|
|
103
|
+
this.log(note);
|
|
104
|
+
}
|
|
105
|
+
},
|
|
110
106
|
});
|
|
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
|
-
});
|
|
107
|
+
if (failures.length > 0) {
|
|
108
|
+
this.exit(1);
|
|
121
109
|
}
|
|
122
|
-
table.printTable();
|
|
123
110
|
}
|
|
124
111
|
}
|
|
@@ -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 FieldEntry, type InitialFieldValue, type StartInstanceArgs, type WorkflowInstance, type WorkflowLifecycle } 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,8 +9,6 @@ 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>;
|
|
@@ -43,12 +41,11 @@ export declare function buildInitialFields({ declared, values, }: {
|
|
|
43
41
|
* fields. Optionals spread conditionally because the engine's optional args
|
|
44
42
|
* reject an explicit `undefined` under `exactOptionalPropertyTypes`.
|
|
45
43
|
*/
|
|
46
|
-
export declare function buildStartArgs({ scope, name, version, initialFields,
|
|
44
|
+
export declare function buildStartArgs({ scope, name, version, initialFields, }: {
|
|
47
45
|
scope: EngineScope;
|
|
48
46
|
name: string;
|
|
49
47
|
version: number | undefined;
|
|
50
48
|
initialFields: InitialFieldValue[];
|
|
51
|
-
actor: Actor;
|
|
52
49
|
}): StartInstanceArgs & EngineScope;
|
|
53
50
|
/** The success spinner message. Pure so the in-flight / immediately-terminal
|
|
54
51
|
* permutations can be asserted without driving a real start. */
|