@sanity/workflow-cli 0.11.0 → 0.13.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 +66 -0
- package/README.md +75 -46
- package/dist/commands/{abort.d.ts → editorial-workflows/abort.d.ts} +3 -2
- package/dist/commands/{abort.js → editorial-workflows/abort.js} +7 -6
- package/dist/commands/{definition → editorial-workflows/definition}/delete.d.ts +3 -2
- package/dist/commands/{definition → editorial-workflows/definition}/delete.js +6 -5
- package/dist/commands/{definition → editorial-workflows/definition}/diff.d.ts +2 -1
- package/dist/commands/{definition → editorial-workflows/definition}/diff.js +7 -6
- package/dist/commands/{definition → editorial-workflows/definition}/list.d.ts +5 -1
- package/dist/commands/{definition → editorial-workflows/definition}/list.js +27 -24
- package/dist/commands/{definition → editorial-workflows/definition}/show.d.ts +3 -2
- package/dist/commands/{definition → editorial-workflows/definition}/show.js +9 -8
- package/dist/commands/{deploy.d.ts → editorial-workflows/deploy.d.ts} +2 -1
- package/dist/commands/{deploy.js → editorial-workflows/deploy.js} +14 -13
- package/dist/commands/{diagnose.d.ts → editorial-workflows/diagnose.d.ts} +2 -1
- package/dist/commands/{diagnose.js → editorial-workflows/diagnose.js} +7 -6
- package/dist/commands/{fire-action.d.ts → editorial-workflows/fire-action.d.ts} +3 -2
- package/dist/commands/{fire-action.js → editorial-workflows/fire-action.js} +8 -7
- package/dist/commands/{list.d.ts → editorial-workflows/list.d.ts} +4 -1
- package/dist/commands/{list.js → editorial-workflows/list.js} +33 -28
- package/dist/commands/editorial-workflows/nuke.d.ts +12 -0
- package/dist/commands/editorial-workflows/nuke.js +77 -0
- package/dist/commands/{reset-activity.d.ts → editorial-workflows/reset-activity.d.ts} +2 -1
- package/dist/commands/{reset-activity.js → editorial-workflows/reset-activity.js} +2 -1
- package/dist/commands/{set-stage.d.ts → editorial-workflows/set-stage.d.ts} +4 -3
- package/dist/commands/{set-stage.js → editorial-workflows/set-stage.js} +6 -5
- package/dist/commands/{show.d.ts → editorial-workflows/show.d.ts} +2 -1
- package/dist/commands/{show.js → editorial-workflows/show.js} +40 -26
- package/dist/commands/{start.d.ts → editorial-workflows/start.d.ts} +20 -4
- package/dist/commands/{start.js → editorial-workflows/start.js} +58 -16
- package/dist/commands/{tail.d.ts → editorial-workflows/tail.d.ts} +2 -1
- package/dist/commands/{tail.js → editorial-workflows/tail.js} +10 -8
- package/dist/hooks/finally/telemetry.js +2 -2
- package/dist/hooks/prerun/telemetry.d.ts +4 -3
- package/dist/lib/base-command.d.ts +4 -6
- package/dist/lib/base-command.js +6 -0
- package/dist/lib/client.d.ts +8 -0
- package/dist/lib/client.js +1 -1
- package/dist/lib/context.d.ts +6 -2
- package/dist/lib/context.js +9 -7
- package/dist/lib/definitions.js +2 -2
- package/dist/lib/flags.d.ts +4 -3
- package/dist/lib/nuke.d.ts +89 -0
- package/dist/lib/nuke.js +111 -0
- package/dist/lib/operation-args.d.ts +3 -1
- package/dist/lib/ops-report.d.ts +2 -1
- package/dist/lib/prompt.d.ts +11 -0
- package/dist/lib/prompt.js +4 -0
- package/dist/lib/select-deployment.d.ts +12 -7
- package/dist/lib/select-deployment.js +26 -1
- package/dist/lib/share-definitions.d.ts +23 -28
- package/dist/lib/share-definitions.js +30 -40
- package/dist/lib/telemetry-setup.d.ts +2 -2
- package/dist/lib/telemetry.d.ts +18 -10
- package/dist/lib/telemetry.js +5 -2
- package/dist/lib/ui.d.ts +12 -0
- package/dist/lib/ui.js +9 -0
- package/oclif.manifest.json +138 -46
- package/package.json +11 -9
|
@@ -2,12 +2,12 @@ import { styleText } from 'node:util';
|
|
|
2
2
|
import { Args } from '@oclif/core';
|
|
3
3
|
import { diagnoseInputFromEvaluation, workflow, unsatisfiedTransitionSummaries, } from '@sanity/workflow-engine';
|
|
4
4
|
import logSymbols from 'log-symbols';
|
|
5
|
-
import { WorkflowCommand } from "
|
|
6
|
-
import { resolveInstanceContext } from "
|
|
7
|
-
import { fail, failureDetail } from "
|
|
8
|
-
import { jsonFlags, tagFlags } from "
|
|
9
|
-
import { baseEngineArgs } from "
|
|
10
|
-
import { formatTimestamp, sectionHeader, activityIcon } from "
|
|
5
|
+
import { WorkflowCommand } from "../../lib/base-command.js";
|
|
6
|
+
import { resolveInstanceContext } from "../../lib/context.js";
|
|
7
|
+
import { fail, failureDetail } from "../../lib/fail.js";
|
|
8
|
+
import { jsonFlags, tagFlags } from "../../lib/flags.js";
|
|
9
|
+
import { baseEngineArgs } from "../../lib/operation-args.js";
|
|
10
|
+
import { formatTimestamp, sectionHeader, activityIcon } from "../../lib/ui.js";
|
|
11
11
|
import { instanceHeader } from "./show.js";
|
|
12
12
|
function formatAssignee(a) {
|
|
13
13
|
return a.type === 'user' ? `user:${a.id}` : `role:${a.role}`;
|
|
@@ -212,6 +212,7 @@ export function renderDiagnosis({ diagnosis, input, remediations, explanations,
|
|
|
212
212
|
return lines;
|
|
213
213
|
}
|
|
214
214
|
export default class Diagnose extends WorkflowCommand {
|
|
215
|
+
static aliases = ['diagnose'];
|
|
215
216
|
static description = "Explain why a workflow instance is or isn't progressing, and what would unstick it.";
|
|
216
217
|
static examples = [
|
|
217
218
|
'<%= config.bin %> diagnose wf-instance.abc123',
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type AvailableAction } from '@sanity/workflow-engine';
|
|
2
|
-
import { WorkflowCommand } from '
|
|
3
|
-
import { type WriteOutcome, type WriteReport } from '
|
|
2
|
+
import { WorkflowCommand } from '../../lib/base-command.ts';
|
|
3
|
+
import { type WriteOutcome, type WriteReport } from '../../lib/ops-report.ts';
|
|
4
4
|
/**
|
|
5
5
|
* The list-mode body: every action on the current stage's activities, marked
|
|
6
6
|
* fireable or not (with the disabling reason), plus a hint showing the
|
|
@@ -30,6 +30,7 @@ export declare function fireActionReport({ result, activity, action, }: {
|
|
|
30
30
|
action: string;
|
|
31
31
|
}): WriteReport;
|
|
32
32
|
export default class FireAction extends WorkflowCommand {
|
|
33
|
+
static aliases: string[];
|
|
33
34
|
static description: string;
|
|
34
35
|
static examples: string[];
|
|
35
36
|
static args: {
|
|
@@ -2,13 +2,13 @@ import { styleText } from 'node:util';
|
|
|
2
2
|
import { Args, Flags } from '@oclif/core';
|
|
3
3
|
import { actionRendering, actionDisabledDetail, deniedGuardLabels, workflow, } from '@sanity/workflow-engine';
|
|
4
4
|
import logSymbols from 'log-symbols';
|
|
5
|
-
import { WorkflowCommand } from "
|
|
6
|
-
import { resolveInstanceContext } from "
|
|
7
|
-
import { fail, failOnThrow, failureDetail } from "
|
|
8
|
-
import { jsonFlags, tagFlags } from "
|
|
9
|
-
import { baseEngineArgs } from "
|
|
10
|
-
import { cascadeTail, opsAppliedLines, runWriteVerb, } from "
|
|
11
|
-
import { parseParams } from "
|
|
5
|
+
import { WorkflowCommand } from "../../lib/base-command.js";
|
|
6
|
+
import { resolveInstanceContext } from "../../lib/context.js";
|
|
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
|
+
import { parseParams } from "../../lib/params.js";
|
|
12
12
|
import { instanceHeader } from "./show.js";
|
|
13
13
|
function reasonText(reason) {
|
|
14
14
|
if (reason.kind === 'mutation-guard-denied') {
|
|
@@ -82,6 +82,7 @@ export function fireActionReport({ result, activity, action, }) {
|
|
|
82
82
|
};
|
|
83
83
|
}
|
|
84
84
|
export default class FireAction extends WorkflowCommand {
|
|
85
|
+
static aliases = ['fire-action'];
|
|
85
86
|
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.';
|
|
86
87
|
static examples = [
|
|
87
88
|
'<%= config.bin %> fire-action wf-instance.abc123',
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type TerminalState } from '@sanity/workflow-engine';
|
|
2
|
-
import { WorkflowCommand } from '
|
|
2
|
+
import { WorkflowCommand } from '../../lib/base-command.ts';
|
|
3
3
|
interface InstanceRow {
|
|
4
4
|
_id: string;
|
|
5
5
|
definition: string;
|
|
@@ -8,6 +8,8 @@ interface InstanceRow {
|
|
|
8
8
|
completedAt?: string | null;
|
|
9
9
|
abortedAt?: string | null;
|
|
10
10
|
lastChangedAt: string;
|
|
11
|
+
modelVersion?: number | null;
|
|
12
|
+
minReaderModel?: number | null;
|
|
11
13
|
}
|
|
12
14
|
interface ListFlags {
|
|
13
15
|
'include-completed': boolean;
|
|
@@ -43,6 +45,7 @@ export declare function instanceRow(r: InstanceRow): {
|
|
|
43
45
|
lastChangedAt: string;
|
|
44
46
|
};
|
|
45
47
|
export default class List extends WorkflowCommand {
|
|
48
|
+
static aliases: string[];
|
|
46
49
|
static description: string;
|
|
47
50
|
static examples: string[];
|
|
48
51
|
static flags: {
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { Flags } from '@oclif/core';
|
|
2
|
-
import { WORKFLOW_INSTANCE_TYPE, documentPrefilter, inFlightFilter, instanceWatchesDocument, tagScopeFilter, terminalState, } from '@sanity/workflow-engine';
|
|
2
|
+
import { WORKFLOW_INSTANCE_TYPE, assertReadableModel, documentPrefilter, inFlightFilter, instanceWatchesDocument, tagScopeFilter, terminalState, } from '@sanity/workflow-engine';
|
|
3
3
|
import logSymbols from 'log-symbols';
|
|
4
|
-
import { WorkflowCommand } from "
|
|
5
|
-
import { resolveReadTargets } from "
|
|
6
|
-
import { failOnThrow } from "
|
|
7
|
-
import { tagFlags } from "
|
|
8
|
-
import { runReadAcrossTargets } from "
|
|
9
|
-
import {
|
|
4
|
+
import { WorkflowCommand } from "../../lib/base-command.js";
|
|
5
|
+
import { resolveReadTargets } from "../../lib/context.js";
|
|
6
|
+
import { failOnThrow } from "../../lib/fail.js";
|
|
7
|
+
import { tagFlags } from "../../lib/flags.js";
|
|
8
|
+
import { runReadAcrossTargets } from "../../lib/read-fanout.js";
|
|
9
|
+
import { formatAge, logClippedTable } from "../../lib/ui.js";
|
|
10
10
|
export function buildListQuery(flags) {
|
|
11
11
|
const filters = [`_type == "${WORKFLOW_INSTANCE_TYPE}"`];
|
|
12
12
|
const params = {};
|
|
@@ -36,7 +36,9 @@ export function buildListQuery(flags) {
|
|
|
36
36
|
currentStage,
|
|
37
37
|
completedAt,
|
|
38
38
|
abortedAt,
|
|
39
|
-
lastChangedAt
|
|
39
|
+
lastChangedAt,
|
|
40
|
+
modelVersion,
|
|
41
|
+
minReaderModel
|
|
40
42
|
}`;
|
|
41
43
|
return { groq, params };
|
|
42
44
|
}
|
|
@@ -54,6 +56,7 @@ export function instanceRow(r) {
|
|
|
54
56
|
};
|
|
55
57
|
}
|
|
56
58
|
export default class List extends WorkflowCommand {
|
|
59
|
+
static aliases = ['list'];
|
|
57
60
|
static description = 'List workflow instances in the configured dataset (in-flight by default).';
|
|
58
61
|
static examples = [
|
|
59
62
|
'<%= config.bin %> list',
|
|
@@ -98,24 +101,23 @@ export default class List extends WorkflowCommand {
|
|
|
98
101
|
this.log(`${logSymbols.info} no instances match`);
|
|
99
102
|
return;
|
|
100
103
|
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
row
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
}
|
|
104
|
+
logClippedTable({
|
|
105
|
+
rows: fetched,
|
|
106
|
+
limit: flags.limit,
|
|
107
|
+
headers: ['instance', 'workflow', 'tag', 'stage', 'status', 'updated'],
|
|
108
|
+
toCells: (r) => {
|
|
109
|
+
const row = instanceRow(r);
|
|
110
|
+
return [
|
|
111
|
+
row._id,
|
|
112
|
+
row.definition,
|
|
113
|
+
row.tag,
|
|
114
|
+
row.currentStage,
|
|
115
|
+
row.status,
|
|
116
|
+
formatAge(row.lastChangedAt),
|
|
117
|
+
];
|
|
118
|
+
},
|
|
119
|
+
log: (line) => this.log(line),
|
|
120
|
+
});
|
|
119
121
|
},
|
|
120
122
|
});
|
|
121
123
|
if (failures.length > 0) {
|
|
@@ -125,8 +127,11 @@ export default class List extends WorkflowCommand {
|
|
|
125
127
|
}
|
|
126
128
|
async function fetchRows({ client, groq, params, document, }) {
|
|
127
129
|
if (document === undefined) {
|
|
128
|
-
|
|
130
|
+
const rows = await client.fetch(groq, params, { tag: 'list' });
|
|
131
|
+
return rows.map(assertReadableModel);
|
|
129
132
|
}
|
|
130
133
|
const candidates = await client.fetch(groq, params, { tag: 'list' });
|
|
131
|
-
return candidates
|
|
134
|
+
return candidates
|
|
135
|
+
.map(assertReadableModel)
|
|
136
|
+
.filter((instance) => instanceWatchesDocument(instance, document));
|
|
132
137
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { WorkflowCommand } from '../../lib/base-command.ts';
|
|
2
|
+
export default class Nuke extends WorkflowCommand {
|
|
3
|
+
static aliases: string[];
|
|
4
|
+
static summary: string;
|
|
5
|
+
static description: string;
|
|
6
|
+
static examples: string[];
|
|
7
|
+
static flags: {
|
|
8
|
+
tag: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
9
|
+
force: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
10
|
+
};
|
|
11
|
+
run(): Promise<void>;
|
|
12
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { Flags } from '@oclif/core';
|
|
2
|
+
import { input } from '@sanity/cli-core/ux';
|
|
3
|
+
import { resourceGdr } from '@sanity/workflow-engine';
|
|
4
|
+
import logSymbols from 'log-symbols';
|
|
5
|
+
import { WorkflowCommand } from "../../lib/base-command.js";
|
|
6
|
+
import { clientFor, resolveApiHost, resolveTokenOrFail } from "../../lib/client.js";
|
|
7
|
+
import { dedupeResources } from "../../lib/context.js";
|
|
8
|
+
import { fail } from "../../lib/fail.js";
|
|
9
|
+
import { loadWorkflowConfig } from "../../lib/load-config.js";
|
|
10
|
+
import { confirmationMatches, executeNuke, formatNukeSummary, involvedTargets, planCounts, renderNukePlan, resolveNukePlan, } from "../../lib/nuke.js";
|
|
11
|
+
import { runWriteVerb } from "../../lib/ops-report.js";
|
|
12
|
+
import { canPromptOnStderr } from "../../lib/prompt.js";
|
|
13
|
+
import { selectDeployment } from "../../lib/select-deployment.js";
|
|
14
|
+
export default class Nuke extends WorkflowCommand {
|
|
15
|
+
static aliases = ['nuke'];
|
|
16
|
+
static summary = 'Delete every engine-owned document for a deployment tag — the dev-period big red button.';
|
|
17
|
+
static description = 'The reset for a dataset holding engine documents the versioned upgrade framework cannot yet ' +
|
|
18
|
+
"migrate: deletes the tag's instances, definitions, and guards (across every alias-bound " +
|
|
19
|
+
'resource). Content documents are never touched. Prints a dry-run plan, then requires you to ' +
|
|
20
|
+
'type back every involved dataset (--force skips the prompt; the plan still prints).';
|
|
21
|
+
static examples = [
|
|
22
|
+
'<%= config.bin %> nuke --tag plugin-dev',
|
|
23
|
+
'<%= config.bin %> nuke --tag plugin-dev --force',
|
|
24
|
+
];
|
|
25
|
+
static flags = {
|
|
26
|
+
tag: Flags.string({
|
|
27
|
+
description: 'The deployment tag to reset. Required — a destructive reset never guesses the environment.',
|
|
28
|
+
required: true,
|
|
29
|
+
}),
|
|
30
|
+
force: Flags.boolean({
|
|
31
|
+
description: 'Skip the confirmation prompt (for scripts/CI). The plan still prints.',
|
|
32
|
+
default: false,
|
|
33
|
+
}),
|
|
34
|
+
};
|
|
35
|
+
async run() {
|
|
36
|
+
const { flags } = await this.parse(Nuke);
|
|
37
|
+
const config = await loadWorkflowConfig();
|
|
38
|
+
const deployment = selectDeployment(config, { tag: flags.tag });
|
|
39
|
+
const targets = buildTargets(deployment, await resolveTokenOrFail());
|
|
40
|
+
const plan = await resolveNukePlan({ tag: deployment.tag, targets });
|
|
41
|
+
const apiHost = resolveApiHost() ?? 'https://api.sanity.io (production default)';
|
|
42
|
+
renderNukePlan(plan, apiHost).forEach((line) => this.log(line));
|
|
43
|
+
const counts = planCounts(plan);
|
|
44
|
+
if (counts.total === 0) {
|
|
45
|
+
this.log(`${logSymbols.info} Nothing to nuke for tag "${deployment.tag}".`);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
if (!flags.force)
|
|
49
|
+
await confirmOrFail(involvedTargets(plan));
|
|
50
|
+
await runWriteVerb({
|
|
51
|
+
startLabel: 'Nuking…',
|
|
52
|
+
failLabel: 'Nuke failed',
|
|
53
|
+
failHeadline: 'workflow nuke error:',
|
|
54
|
+
run: () => executeNuke(plan),
|
|
55
|
+
report: () => ({ changed: true, message: formatNukeSummary(deployment.tag, counts) }),
|
|
56
|
+
log: (line) => this.log(line),
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function buildTargets(deployment, token) {
|
|
61
|
+
const engineResource = deployment.workflowResource;
|
|
62
|
+
const aliasResources = (deployment.resourceAliases ?? []).map((binding) => binding.resource);
|
|
63
|
+
return dedupeResources([engineResource, ...aliasResources]).map((resource) => ({
|
|
64
|
+
resource,
|
|
65
|
+
client: clientFor(resource, token),
|
|
66
|
+
holdsEngineDocs: resourceGdr(resource) === resourceGdr(engineResource),
|
|
67
|
+
}));
|
|
68
|
+
}
|
|
69
|
+
async function confirmOrFail(targets) {
|
|
70
|
+
if (!canPromptOnStderr()) {
|
|
71
|
+
fail('Refusing to nuke without confirmation in a non-interactive shell.', 'Re-run with --force to skip the prompt.');
|
|
72
|
+
}
|
|
73
|
+
const answer = await input({ message: `Type every target to confirm deletion (space-separated):\n ${targets.join(' ')}\n` }, { output: process.stderr });
|
|
74
|
+
if (!confirmationMatches(answer, targets)) {
|
|
75
|
+
fail('Confirmation did not match — nothing was deleted.', `Expected: ${targets.join(' ')}`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Args } from '@oclif/core';
|
|
2
|
-
import { StubCommand } from "
|
|
2
|
+
import { StubCommand } from "../../lib/stub.js";
|
|
3
3
|
export default class ResetActivity extends StubCommand {
|
|
4
|
+
static hiddenAliases = ['reset-activity'];
|
|
4
5
|
static hidden = true;
|
|
5
6
|
static description = 'Reset a failed activity on an in-flight instance — back to pending (re-run) or to skipped (bypass).';
|
|
6
7
|
static args = {
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { type SetStageArgs, type WorkflowInstance } from '@sanity/workflow-engine';
|
|
2
|
-
import { WorkflowCommand } from '
|
|
3
|
-
import { type EngineScope } from '
|
|
4
|
-
import { type WriteOutcome, type WriteReport } from '
|
|
2
|
+
import { WorkflowCommand } from '../../lib/base-command.ts';
|
|
3
|
+
import { type EngineScope } from '../../lib/operation-args.ts';
|
|
4
|
+
import { type WriteOutcome, type WriteReport } from '../../lib/ops-report.ts';
|
|
5
5
|
export default class SetStage extends WorkflowCommand {
|
|
6
|
+
static aliases: string[];
|
|
6
7
|
static description: string;
|
|
7
8
|
static examples: string[];
|
|
8
9
|
static args: {
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { styleText } from 'node:util';
|
|
2
2
|
import { Args, Flags } from '@oclif/core';
|
|
3
3
|
import { workflow } from '@sanity/workflow-engine';
|
|
4
|
-
import { WorkflowCommand } from "
|
|
5
|
-
import { resolveInstanceContext } from "
|
|
6
|
-
import { tagFlags } from "
|
|
7
|
-
import { buildOperationArgs } from "
|
|
8
|
-
import { cascadeTail, opsAppliedLines, runWriteVerb, } from "
|
|
4
|
+
import { WorkflowCommand } from "../../lib/base-command.js";
|
|
5
|
+
import { resolveInstanceContext } from "../../lib/context.js";
|
|
6
|
+
import { tagFlags } from "../../lib/flags.js";
|
|
7
|
+
import { buildOperationArgs } from "../../lib/operation-args.js";
|
|
8
|
+
import { cascadeTail, opsAppliedLines, runWriteVerb, } from "../../lib/ops-report.js";
|
|
9
9
|
export default class SetStage extends WorkflowCommand {
|
|
10
|
+
static aliases = ['set-stage'];
|
|
10
11
|
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.";
|
|
11
12
|
static examples = [
|
|
12
13
|
'<%= config.bin %> set-stage wf-instance.abc123 --to ready',
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type WorkflowInstance } from '@sanity/workflow-engine';
|
|
2
|
-
import { WorkflowCommand } from '
|
|
2
|
+
import { WorkflowCommand } from '../../lib/base-command.ts';
|
|
3
3
|
export default class Show extends WorkflowCommand {
|
|
4
|
+
static aliases: string[];
|
|
4
5
|
static description: string;
|
|
5
6
|
static examples: string[];
|
|
6
7
|
static args: {
|
|
@@ -2,11 +2,12 @@ import { styleText } from 'node:util';
|
|
|
2
2
|
import { Args, Flags } from '@oclif/core';
|
|
3
3
|
import { abortReason, displayTitle, terminalState, } from '@sanity/workflow-engine';
|
|
4
4
|
import logSymbols from 'log-symbols';
|
|
5
|
-
import { WorkflowCommand } from "
|
|
6
|
-
import { findInstance, resolveReadTargets } from "
|
|
7
|
-
import { tagFlags } from "
|
|
8
|
-
import { formatKeyValue, formatTimestamp, sectionHeader, activityIcon } from "
|
|
5
|
+
import { WorkflowCommand } from "../../lib/base-command.js";
|
|
6
|
+
import { findInstance, resolveReadTargets } from "../../lib/context.js";
|
|
7
|
+
import { tagFlags } from "../../lib/flags.js";
|
|
8
|
+
import { formatKeyValue, formatTimestamp, sectionHeader, activityIcon } from "../../lib/ui.js";
|
|
9
9
|
export default class Show extends WorkflowCommand {
|
|
10
|
+
static aliases = ['show'];
|
|
10
11
|
static description = 'Show the state, activities, and effects of a workflow instance.';
|
|
11
12
|
static examples = [
|
|
12
13
|
'<%= config.bin %> show wf-instance.abc123',
|
|
@@ -67,27 +68,40 @@ export function instanceHeader(instance) {
|
|
|
67
68
|
return [title, ...rows].join('\n');
|
|
68
69
|
}
|
|
69
70
|
export function describeInstance(instance, options) {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
71
|
+
return [
|
|
72
|
+
...stageLines(instance),
|
|
73
|
+
...pendingEffectLines(instance),
|
|
74
|
+
...(options.includeHistory ? historyLines(instance) : []),
|
|
75
|
+
];
|
|
76
|
+
}
|
|
77
|
+
function stageLines(instance) {
|
|
78
|
+
return [
|
|
79
|
+
sectionHeader('Stages'),
|
|
80
|
+
...instance.stages.flatMap((stage) => [
|
|
81
|
+
` • ${stage.name}${stageMarker(stage)}`,
|
|
82
|
+
...(stage.activities ?? []).map((activity) => ` ${activityIcon[activity.status]} ${activity.name} ${styleText('dim', `[${activity.status}]`)}`),
|
|
83
|
+
]),
|
|
84
|
+
];
|
|
85
|
+
}
|
|
86
|
+
function stageMarker(stage) {
|
|
87
|
+
return stage.exitedAt
|
|
88
|
+
? styleText('dim', ` (exited ${formatTimestamp(stage.exitedAt)})`)
|
|
89
|
+
: styleText('cyan', ' (current)');
|
|
90
|
+
}
|
|
91
|
+
function pendingEffectLines(instance) {
|
|
92
|
+
if (instance.pendingEffects.length === 0) {
|
|
93
|
+
return [];
|
|
91
94
|
}
|
|
92
|
-
return
|
|
95
|
+
return [
|
|
96
|
+
'',
|
|
97
|
+
sectionHeader('Pending effects'),
|
|
98
|
+
...instance.pendingEffects.map((eff) => ` • ${eff.name} ${styleText('dim', `(key=${eff._key})`)}`),
|
|
99
|
+
];
|
|
100
|
+
}
|
|
101
|
+
function historyLines(instance) {
|
|
102
|
+
return [
|
|
103
|
+
'',
|
|
104
|
+
sectionHeader('History'),
|
|
105
|
+
...instance.history.map((entry) => ` ${styleText('dim', `[${formatTimestamp(entry.at)}]`)} ${displayTitle(entry._type)}`),
|
|
106
|
+
];
|
|
93
107
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { type InitialFieldValue, type StartInstanceArgs, type WorkflowInstance } from '@sanity/workflow-engine';
|
|
2
|
-
import { WorkflowCommand } from '
|
|
3
|
-
import { type EngineScope } from '
|
|
1
|
+
import { StartNotSettledError, type InitialFieldValue, type StartInstanceArgs, type WorkflowInstance } from '@sanity/workflow-engine';
|
|
2
|
+
import { WorkflowCommand } from '../../lib/base-command.ts';
|
|
3
|
+
import { type EngineScope } from '../../lib/operation-args.ts';
|
|
4
4
|
export default class Start extends WorkflowCommand {
|
|
5
|
+
static aliases: string[];
|
|
5
6
|
static description: string;
|
|
6
7
|
static examples: string[];
|
|
7
8
|
static args: {
|
|
@@ -11,20 +12,35 @@ export default class Start extends WorkflowCommand {
|
|
|
11
12
|
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
12
13
|
version: import("@oclif/core/interfaces").OptionFlag<number | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
14
|
field: import("@oclif/core/interfaces").OptionFlag<string[], import("@oclif/core/interfaces").CustomOptions>;
|
|
15
|
+
'instance-id': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
14
16
|
tag: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
15
17
|
};
|
|
16
18
|
run(): Promise<void>;
|
|
17
19
|
}
|
|
20
|
+
/** A created-but-unprimed failure is resumable — the engine message names
|
|
21
|
+
* the id and the generic remediation; operators need the CLI's own retry
|
|
22
|
+
* spelling appended. Every other error passes through untouched. */
|
|
23
|
+
export declare function withRetryHint(error: unknown): unknown;
|
|
24
|
+
/** The spinner message for a start whose first cascade failed after the
|
|
25
|
+
* instance was created and primed: the run exists — say so, name the cause,
|
|
26
|
+
* and point at the retry rail instead of reporting a rejection. */
|
|
27
|
+
export declare function startNotSettledReport(error: StartNotSettledError): string;
|
|
28
|
+
/** The `--json` payload for a not-settled start — same `instanceId` key as
|
|
29
|
+
* the success payload so scripts can pick up the retry id either way. */
|
|
30
|
+
export declare function startNotSettledJson(error: StartNotSettledError): Record<string, unknown>;
|
|
18
31
|
/**
|
|
19
32
|
* The shared engine args ({@link baseEngineArgs}) plus the start-specific
|
|
20
33
|
* fields. Optionals spread conditionally because the engine's optional args
|
|
21
34
|
* reject an explicit `undefined` under `exactOptionalPropertyTypes`.
|
|
22
35
|
*/
|
|
23
|
-
export declare function buildStartArgs({ scope, name, version, initialFields, }: {
|
|
36
|
+
export declare function buildStartArgs({ scope, name, version, initialFields, instanceId, }: {
|
|
24
37
|
scope: EngineScope;
|
|
25
38
|
name: string;
|
|
26
39
|
version: number | undefined;
|
|
27
40
|
initialFields: InitialFieldValue[];
|
|
41
|
+
/** The start's idempotency key (`--instance-id`) — a retry carrying it
|
|
42
|
+
* resumes the earlier attempt instead of creating a duplicate. */
|
|
43
|
+
instanceId?: string | undefined;
|
|
28
44
|
}): StartInstanceArgs & EngineScope;
|
|
29
45
|
/** The success spinner message. Pure so the in-flight / immediately-terminal
|
|
30
46
|
* permutations can be asserted without driving a real start. */
|
|
@@ -1,21 +1,23 @@
|
|
|
1
1
|
import { styleText } from 'node:util';
|
|
2
2
|
import { Args, Flags } from '@oclif/core';
|
|
3
|
-
import { buildInitialFields, startRefusal, workflow, } from '@sanity/workflow-engine';
|
|
4
|
-
import { WorkflowCommand } from "
|
|
5
|
-
import { resolveContext } from "
|
|
6
|
-
import { fetchDeployedDefinition } from "
|
|
7
|
-
import { fail, failOnThrow, failureDetail } from "
|
|
8
|
-
import { jsonFlags, tagFlags } from "
|
|
9
|
-
import { baseEngineArgs } from "
|
|
10
|
-
import { runWriteVerb } from "
|
|
11
|
-
import { parseParams } from "
|
|
3
|
+
import { buildInitialFields, errorMessage, StartNotPrimedError, StartNotSettledError, startRefusal, workflow, } from '@sanity/workflow-engine';
|
|
4
|
+
import { WorkflowCommand } from "../../lib/base-command.js";
|
|
5
|
+
import { resolveContext } from "../../lib/context.js";
|
|
6
|
+
import { fetchDeployedDefinition } from "../../lib/definitions.js";
|
|
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";
|
|
11
|
+
import { parseParams } from "../../lib/params.js";
|
|
12
12
|
export default class Start extends WorkflowCommand {
|
|
13
|
+
static aliases = ['start'];
|
|
13
14
|
static description = 'Start a workflow instance from a deployed definition. Supply values for the ' +
|
|
14
15
|
"workflow's input-sourced fields with --field (e.g. the subject document ref).";
|
|
15
16
|
static examples = [
|
|
16
17
|
'<%= config.bin %> start productLaunch',
|
|
17
18
|
'<%= config.bin %> start article-review --field subject=\'{"id":"dataset:proj:ds:article-1","type":"article"}\'',
|
|
18
19
|
'<%= config.bin %> start productLaunch --version 2 --tag prod',
|
|
20
|
+
'<%= config.bin %> start productLaunch --instance-id prod.wf-instance.a1b2c3d4e5f6',
|
|
19
21
|
];
|
|
20
22
|
static args = {
|
|
21
23
|
name: Args.string({ required: true, description: 'Workflow definition name.' }),
|
|
@@ -32,6 +34,12 @@ export default class Start extends WorkflowCommand {
|
|
|
32
34
|
multiple: true,
|
|
33
35
|
default: [],
|
|
34
36
|
}),
|
|
37
|
+
'instance-id': Flags.string({
|
|
38
|
+
description: "Start under this instance id — for retries. The id is the start's idempotency key: " +
|
|
39
|
+
'pass the id of a start that failed partway and the engine resumes it instead of ' +
|
|
40
|
+
'creating a duplicate (an already-settled start replays as a no-op). Omit to mint ' +
|
|
41
|
+
'a fresh id.',
|
|
42
|
+
}),
|
|
35
43
|
...jsonFlags,
|
|
36
44
|
};
|
|
37
45
|
async run() {
|
|
@@ -52,25 +60,58 @@ export default class Start extends WorkflowCommand {
|
|
|
52
60
|
name: args.name,
|
|
53
61
|
version: flags.version,
|
|
54
62
|
initialFields,
|
|
63
|
+
instanceId: flags['instance-id'],
|
|
55
64
|
}),
|
|
56
65
|
};
|
|
57
66
|
if (flags.json) {
|
|
58
|
-
const result = await workflow
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
67
|
+
const result = await workflow.startInstance(startArgs).catch((error) => {
|
|
68
|
+
if (error instanceof StartNotSettledError) {
|
|
69
|
+
this.log(JSON.stringify(startNotSettledJson(error), null, 2));
|
|
70
|
+
return undefined;
|
|
71
|
+
}
|
|
72
|
+
fail('start error:', failureDetail(withRetryHint(error)));
|
|
73
|
+
});
|
|
74
|
+
if (result !== undefined) {
|
|
75
|
+
this.log(JSON.stringify(startResultJson(result.instance), null, 2));
|
|
76
|
+
}
|
|
62
77
|
return;
|
|
63
78
|
}
|
|
64
79
|
await runWriteVerb({
|
|
65
80
|
startLabel: `Starting ${args.name}…`,
|
|
66
81
|
failLabel: 'Start rejected',
|
|
67
82
|
failHeadline: 'start error:',
|
|
68
|
-
run: () => workflow.startInstance(startArgs)
|
|
69
|
-
|
|
83
|
+
run: () => workflow.startInstance(startArgs).catch((error) => {
|
|
84
|
+
if (error instanceof StartNotSettledError)
|
|
85
|
+
return error;
|
|
86
|
+
throw withRetryHint(error);
|
|
87
|
+
}),
|
|
88
|
+
report: (result) => result instanceof StartNotSettledError
|
|
89
|
+
? { changed: false, message: startNotSettledReport(result) }
|
|
90
|
+
: { changed: true, message: startReport(result.instance) },
|
|
70
91
|
log: (line) => this.log(line),
|
|
71
92
|
});
|
|
72
93
|
}
|
|
73
94
|
}
|
|
95
|
+
export function withRetryHint(error) {
|
|
96
|
+
return error instanceof StartNotPrimedError
|
|
97
|
+
? new Error(`${errorMessage(error)} (--instance-id ${error.instanceId})`)
|
|
98
|
+
: error;
|
|
99
|
+
}
|
|
100
|
+
export function startNotSettledReport(error) {
|
|
101
|
+
const id = styleText('bold', error.instanceId);
|
|
102
|
+
return (`Started ${id} — but its first auto-advance failed: ${errorMessage(error.cause)}. ` +
|
|
103
|
+
`It settles on the next tick, or retry with --instance-id ${error.instanceId}.`);
|
|
104
|
+
}
|
|
105
|
+
export function startNotSettledJson(error) {
|
|
106
|
+
return {
|
|
107
|
+
instanceId: error.instanceId,
|
|
108
|
+
notSettled: true,
|
|
109
|
+
detail: errorMessage(error.cause),
|
|
110
|
+
...(error.instance !== undefined
|
|
111
|
+
? { currentStage: error.instance.currentStage, definition: error.instance.definition }
|
|
112
|
+
: {}),
|
|
113
|
+
};
|
|
114
|
+
}
|
|
74
115
|
async function resolveStartInputs({ client, name, tag, version, values, }) {
|
|
75
116
|
const deployed = await fetchDeployedDefinition({ client, name, tag, version });
|
|
76
117
|
if (deployed === undefined) {
|
|
@@ -82,12 +123,13 @@ async function resolveStartInputs({ client, name, tag, version, values, }) {
|
|
|
82
123
|
}
|
|
83
124
|
return failOnThrow('start usage error:', () => buildInitialFields({ declared: deployed.fields ?? [], values }));
|
|
84
125
|
}
|
|
85
|
-
export function buildStartArgs({ scope, name, version, initialFields, }) {
|
|
126
|
+
export function buildStartArgs({ scope, name, version, initialFields, instanceId, }) {
|
|
86
127
|
return {
|
|
87
128
|
...baseEngineArgs(scope),
|
|
88
129
|
definition: name,
|
|
89
130
|
...(version !== undefined ? { version } : {}),
|
|
90
131
|
...(initialFields.length > 0 ? { initialFields } : {}),
|
|
132
|
+
...(instanceId !== undefined ? { instanceId } : {}),
|
|
91
133
|
};
|
|
92
134
|
}
|
|
93
135
|
export function startReport(instance) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { WorkflowCommand } from '
|
|
1
|
+
import { WorkflowCommand } from '../../lib/base-command.ts';
|
|
2
2
|
export default class Tail extends WorkflowCommand {
|
|
3
|
+
static aliases: string[];
|
|
3
4
|
static description: string;
|
|
4
5
|
static examples: string[];
|
|
5
6
|
static args: {
|