@sanity/workflow-cli 0.7.0 → 0.8.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/README.md CHANGED
@@ -4,7 +4,7 @@ Command-line tool for deploying, inspecting, and administering Sanity workflow
4
4
  definitions and instances.
5
5
 
6
6
  > [!WARNING]
7
- > Early access, restricted. `retry-task` and `set-stage` are still stubs —
7
+ > Early access, restricted. `retry-activity` and `set-stage` are still stubs —
8
8
  > they print their parsed input and exit non-zero. The "wired" commands below
9
9
  > talk to a real Sanity dataset using `SANITY_*` env vars.
10
10
 
@@ -75,25 +75,25 @@ built or standalone CLI instead resolves the package's compiled `dist`.
75
75
 
76
76
  ## Command status
77
77
 
78
- | Command | Status |
79
- | ------------------------------------------ | ----------------------------------------------------------------------------------------- |
80
- | `deploy` | wired — calls `workflow.deployDefinitions` over the `WORKFLOW_DEFS` module |
81
- | `deploy --check` | wired — runs `validateDefinition` over the local batch + a duplicate-id check |
82
- | `deploy --dry-run` | wired — fetches existing docs and renders a coloured JSON diff per change |
83
- | `deploy --only <id>` | wired — filters deploy/check/dry-run to one `definition` |
84
- | `list` | wired — `client.fetch` over `workflow.instance` documents |
85
- | `show <instance-id>` | wired — `client.getDocument` |
86
- | `diagnose <instance-id>` | wired — one `workflow.evaluate` call, classifies why the instance is/isn't progressing |
87
- | `tail <instance-id>` | wired — `client.listen()` over the instance, prints new history entries |
88
- | `abort <instance-id>` | wired — calls `workflow.abortInstance` (hard stop: cancels pending effects, lifts guards) |
89
- | `retry-task <instance-id> <task-id>` | stub |
90
- | `move-stage <instance-id> --to <stage-id>` | wired — calls `workflow.setStage` (runs guards + onEnter effects) |
91
- | `fire-action <instance-id>` | wired — `workflow.evaluate` lists actions; `workflow.fireAction` fires one |
92
- | `set-stage <instance-id> --to <stage-id>` | stub (engine has no guard-bypass path yet) |
93
- | `definition list` | wired — `client.fetch` over `workflow.definition` documents |
94
- | `definition show <id>` | wired — `client.fetch`, latest version unless `--version` |
95
- | `definition diff <id>` | wired — diffs the in-code definition against the deployed latest (`--version` to pin) |
96
- | `definition delete <name>` | wired — calls `workflow.deleteDefinition` (refuses on live instances unless `--cascade`) |
78
+ | Command | Status |
79
+ | -------------------------------------------- | ----------------------------------------------------------------------------------------- |
80
+ | `deploy` | wired — calls `workflow.deployDefinitions` over the `WORKFLOW_DEFS` module |
81
+ | `deploy --check` | wired — runs `validateDefinition` over the local batch + a duplicate-id check |
82
+ | `deploy --dry-run` | wired — fetches existing docs and renders a coloured JSON diff per change |
83
+ | `deploy --only <id>` | wired — filters deploy/check/dry-run to one `definition` |
84
+ | `list` | wired — `client.fetch` over `workflow.instance` documents |
85
+ | `show <instance-id>` | wired — `client.getDocument` |
86
+ | `diagnose <instance-id>` | wired — one `workflow.evaluate` call, classifies why the instance is/isn't progressing |
87
+ | `tail <instance-id>` | wired — `client.listen()` over the instance, prints new history entries |
88
+ | `abort <instance-id>` | wired — calls `workflow.abortInstance` (hard stop: cancels pending effects, lifts guards) |
89
+ | `retry-activity <instance-id> <activity-id>` | stub |
90
+ | `move-stage <instance-id> --to <stage-id>` | wired — calls `workflow.setStage` (runs guards + onEnter effects) |
91
+ | `fire-action <instance-id>` | wired — `workflow.evaluate` lists actions; `workflow.fireAction` fires one |
92
+ | `set-stage <instance-id> --to <stage-id>` | stub (engine has no guard-bypass path yet) |
93
+ | `definition list` | wired — `client.fetch` over `workflow.definition` documents |
94
+ | `definition show <id>` | wired — `client.fetch`, latest version unless `--version` |
95
+ | `definition diff <id>` | wired — diffs the in-code definition against the deployed latest (`--version` to pin) |
96
+ | `definition delete <name>` | wired — calls `workflow.deleteDefinition` (refuses on live instances unless `--cascade`) |
97
97
 
98
98
  ## Known gaps
99
99
 
@@ -30,7 +30,7 @@ export default class DefinitionDiff extends Command {
30
30
  let deployed;
31
31
  try {
32
32
  deployed = await fetchDeployedDefinition(client, args.definition, config, flags.version);
33
- spinner.succeed(`Diffed ${args.definition} v${def.version}`);
33
+ spinner.succeed(`Diffed ${args.definition}`);
34
34
  }
35
35
  catch (error) {
36
36
  spinner.fail('Diff failed');
@@ -2,7 +2,7 @@ import { Command } from '@oclif/core';
2
2
  import { type WorkflowRole } from '@sanity/workflow-engine';
3
3
  interface DefinitionListFlags {
4
4
  tag?: string | undefined;
5
- 'workflow-id'?: string | undefined;
5
+ 'workflow-name'?: string | undefined;
6
6
  limit: number;
7
7
  }
8
8
  export interface DefinitionListRow {
@@ -24,7 +24,7 @@ export default class DefinitionList extends Command {
24
24
  static examples: string[];
25
25
  static flags: {
26
26
  limit: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
27
- 'workflow-id': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
27
+ 'workflow-name': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
28
28
  tag: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
29
29
  };
30
30
  run(): Promise<void>;
@@ -21,8 +21,8 @@ export function buildDefinitionListQuery(flags) {
21
21
  params.tag = flags.tag;
22
22
  filters.push(tagScopeFilter());
23
23
  }
24
- if (flags['workflow-id']) {
25
- params.name = flags['workflow-id'];
24
+ if (flags['workflow-name']) {
25
+ params.name = flags['workflow-name'];
26
26
  filters.push('name == $name');
27
27
  }
28
28
  const instanceMatch = instanceFilters.join(' && ');
@@ -50,7 +50,7 @@ export default class DefinitionList extends Command {
50
50
  description: 'The maximum number of definitions to return.',
51
51
  default: 100,
52
52
  }),
53
- 'workflow-id': Flags.string({
53
+ 'workflow-name': Flags.string({
54
54
  description: 'Filter to a single workflow definition name (e.g. product-launch)',
55
55
  }),
56
56
  };
@@ -24,10 +24,12 @@ export default class DefinitionShow extends Command {
24
24
  * title, then the human title, description, and deploy `tag` as aligned
25
25
  * detail rows. `tag` is deployment metadata (the partition), not part of the
26
26
  * authored definition, so it's passed separately and omitted when unknown. */
27
- export declare function definitionHeader(def: WorkflowDefinition, tag?: string): string;
27
+ export declare function definitionHeader(def: WorkflowDefinition & {
28
+ version: number;
29
+ }, tag?: string): string;
28
30
  /**
29
- * The body lines for `definition show`: every stage with its tasks and each
30
- * task's actions. Pure so it can be asserted without driving the oclif command.
31
+ * The body lines for `definition show`: every stage with its activities and each
32
+ * activity's actions. Pure so it can be asserted without driving the oclif command.
31
33
  */
32
34
  export declare function describeDefinition(def: WorkflowDefinition): string[];
33
35
  export {};
@@ -36,8 +36,8 @@ export default class DefinitionShow extends Command {
36
36
  version: flags.version,
37
37
  });
38
38
  const client = loadClient();
39
- // The authored {@link WorkflowDefinition} carries no `tag` — it's the
40
- // partition the document was deployed into, stamped on the stored doc.
39
+ // A deployed document: the authored {@link WorkflowDefinition} plus the
40
+ // deploy-stamped envelope (`version`, `contentHash`, `tag`).
41
41
  const def = await client.fetch(groq, params);
42
42
  if (!def) {
43
43
  const at = flags.version ? `v${flags.version}` : 'latest';
@@ -71,24 +71,24 @@ function actionLine(action) {
71
71
  const status = statusOp ? styleText('dim', ` → ${statusOp.status}`) : '';
72
72
  return ` · ${action.name}${status}`;
73
73
  }
74
- /** A `- task` line followed by one line per action it exposes. */
75
- function taskBlock(task) {
76
- const title = task.title ? styleText('dim', ` — ${task.title}`) : '';
77
- return [` - ${task.name}${title}`, ...(task.actions ?? []).map(actionLine)];
74
+ /** A `- activity` line followed by one line per action it exposes. */
75
+ function activityBlock(activity) {
76
+ const title = activity.title ? styleText('dim', ` — ${activity.title}`) : '';
77
+ return [` - ${activity.name}${title}`, ...(activity.actions ?? []).map(actionLine)];
78
78
  }
79
- /** A `• stage` line (with initial/terminal markers) followed by its task blocks. */
79
+ /** A `• stage` line (with initial/terminal markers) followed by its activity blocks. */
80
80
  function stageBlock(stage, initialStage) {
81
81
  const title = stage.title ? styleText('dim', ` — ${stage.title}`) : '';
82
82
  const terminal = isTerminalStage(stage) ? styleText('dim', ' (terminal)') : '';
83
83
  const initial = stage.name === initialStage ? styleText('cyan', ' [initial]') : '';
84
84
  return [
85
85
  ` • ${stage.name}${title}${terminal}${initial}`,
86
- ...(stage.tasks ?? []).flatMap(taskBlock),
86
+ ...(stage.activities ?? []).flatMap(activityBlock),
87
87
  ];
88
88
  }
89
89
  /**
90
- * The body lines for `definition show`: every stage with its tasks and each
91
- * task's actions. Pure so it can be asserted without driving the oclif command.
90
+ * The body lines for `definition show`: every stage with its activities and each
91
+ * activity's actions. Pure so it can be asserted without driving the oclif command.
92
92
  */
93
93
  export function describeDefinition(def) {
94
94
  return [
@@ -88,7 +88,6 @@ export function validateModeFlags(check, dryRun) {
88
88
  export function deployResultLines(results) {
89
89
  const symbols = {
90
90
  created: logSymbols.success,
91
- updated: logSymbols.info,
92
91
  };
93
92
  return results.map((r) => {
94
93
  const symbol = symbols[r.status] ?? logSymbols.warning;
@@ -1,7 +1,7 @@
1
1
  import { Command } from '@oclif/core';
2
2
  import { type Diagnosis, type DiagnoseInput, type SuggestedRemediation } from '@sanity/workflow-engine';
3
3
  /**
4
- * The diagnosis body: the verdict line, the current stage's tasks +
4
+ * The diagnosis body: the verdict line, the current stage's activities +
5
5
  * transitions as evidence (for any in-flight state), and the cause block with
6
6
  * the engine's suggested fix when stuck. Pure so every verdict permutation is
7
7
  * assertable without driving a live evaluation. Takes the engine's
@@ -6,25 +6,28 @@ import { loadClient } from "../lib/client.js";
6
6
  import { loadWorkflowConfig } from "../lib/config.js";
7
7
  import { fail } from "../lib/fail.js";
8
8
  import { tagFlags } from "../lib/flags.js";
9
- import { formatTimestamp, sectionHeader, taskIcon } from "../lib/ui.js";
9
+ import { formatTimestamp, sectionHeader, activityIcon } from "../lib/ui.js";
10
10
  import { instanceHeader } from "./show.js";
11
11
  function formatAssignee(a) {
12
12
  return a.type === 'user' ? `user:${a.id}` : `role:${a.role}`;
13
13
  }
14
- function taskLine(t, assignees) {
14
+ function activityLine(t, assignees) {
15
15
  const who = assignees.length > 0
16
16
  ? styleText('dim', ` assigned → ${assignees.map(formatAssignee).join(', ')}`)
17
17
  : '';
18
- return ` ${taskIcon[t.status]} ${t.task.name} ${styleText('dim', `[${t.status}]`)}${who}`;
18
+ return ` ${activityIcon[t.status]} ${t.activity.name} ${styleText('dim', `[${t.status}]`)}${who}`;
19
19
  }
20
20
  function transitionLine(tr) {
21
21
  const mark = tr.filterSatisfied ? styleText('green', 'ready') : styleText('red', 'false');
22
22
  return ` → ${tr.transition.to} ${styleText('dim', tr.transition.filter ?? 'true')} ${mark}`;
23
23
  }
24
24
  function currentStageLines(input) {
25
- const lines = [`${sectionHeader('Stage')} ${input.instance.currentStage}`, sectionHeader('Tasks')];
26
- for (const t of input.tasks) {
27
- lines.push(taskLine(t, input.assignees[t.task.name] ?? []));
25
+ const lines = [
26
+ `${sectionHeader('Stage')} ${input.instance.currentStage}`,
27
+ sectionHeader('Activities'),
28
+ ];
29
+ for (const t of input.activities) {
30
+ lines.push(activityLine(t, input.assignees[t.activity.name] ?? []));
28
31
  }
29
32
  if (input.transitions.length > 0) {
30
33
  lines.push('', sectionHeader('Exit transitions'));
@@ -37,14 +40,14 @@ function currentStageLines(input) {
37
40
  function failedEffectDetail(effect) {
38
41
  const ran = effect.durationMs !== undefined ? ` (after ${effect.durationMs}ms)` : '';
39
42
  return {
40
- headline: `a failed effect is blocking task '${effect.origin.name}'`,
43
+ headline: `a failed effect is blocking activity '${effect.origin.name}'`,
41
44
  why: [
42
45
  styleText('red', `${logSymbols.error} failed effect: ${effect.name}`),
43
- ` queued by task '${effect.origin.name}', failed ${formatTimestamp(effect.ranAt)}${ran}`,
46
+ ` queued by activity '${effect.origin.name}', failed ${formatTimestamp(effect.ranAt)}${ran}`,
44
47
  ...(effect.error !== undefined ? [` error: ${effect.error.message}`] : []),
45
48
  '',
46
- `Task '${effect.origin.name}' is waiting on this effect. It failed against an`,
47
- `external system, so the task never completes and the stage can't advance.`,
49
+ `Activity '${effect.origin.name}' is waiting on this effect. It failed against an`,
50
+ `external system, so the activity never completes and the stage can't advance.`,
48
51
  ],
49
52
  };
50
53
  }
@@ -58,12 +61,12 @@ function hungEffectDetail(effect) {
58
61
  ],
59
62
  };
60
63
  }
61
- function failedTaskDetail(task) {
64
+ function failedActivityDetail(activity) {
62
65
  return {
63
- headline: `task '${task}' failed`,
66
+ headline: `activity '${activity}' failed`,
64
67
  why: [
65
- styleText('red', `${logSymbols.error} task '${task}' is in a terminal failed state.`),
66
- `Any exit transition gated on '${task}' being done can never fire.`,
68
+ styleText('red', `${logSymbols.error} activity '${activity}' is in a terminal failed state.`),
69
+ `Any exit transition gated on '${activity}' being done can never fire.`,
67
70
  ],
68
71
  };
69
72
  }
@@ -76,31 +79,31 @@ function waitingLines(w) {
76
79
  ? `assigned to ${w.assignees.map(formatAssignee).join(', ')}`
77
80
  : 'unassigned — anyone with permission can act';
78
81
  return [
79
- `${logSymbols.info} task '${w.task}' is active (${who}),`,
82
+ `${logSymbols.info} activity '${w.activity}' is active (${who}),`,
80
83
  `waiting for action: ${w.actions.join(' or ')}. This is the normal in-flight`,
81
84
  `state — it advances when someone acts.`,
82
85
  ];
83
86
  }
84
87
  function blockedHeadline(b) {
85
88
  const who = b.assignees.length > 0 ? ` for ${b.assignees.map(formatAssignee).join(', ')}` : '';
86
- return `task '${b.task}'${who} is not yet ready — unmet requirement(s): ${b.requirements.join(', ')}`;
89
+ return `activity '${b.activity}'${who} is not yet ready — unmet requirement(s): ${b.requirements.join(', ')}`;
87
90
  }
88
91
  function blockedLines(b) {
89
92
  const who = b.assignees.length > 0
90
93
  ? `assigned to ${b.assignees.map(formatAssignee).join(', ')}`
91
94
  : 'unassigned';
92
95
  return [
93
- `${logSymbols.info} task '${b.task}' is visible but not yet executable (${who}).`,
96
+ `${logSymbols.info} activity '${b.activity}' is visible but not yet executable (${who}).`,
94
97
  `Unmet requirement(s): ${b.requirements.join(', ')}. It can't be acted on until`,
95
98
  `those preconditions are met — check what would satisfy them (a prerequisite`,
96
- `task completing, required content landing). It will not advance on its own.`,
99
+ `activity completing, required content landing). It will not advance on its own.`,
97
100
  ];
98
101
  }
99
102
  function noTransitionDetail() {
100
103
  return {
101
104
  headline: `no exit transition's filter is satisfied`,
102
105
  why: [
103
- `${logSymbols.info} every task is resolved, but no exit transition's filter is true.`,
106
+ `${logSymbols.info} every activity is resolved, but no exit transition's filter is true.`,
104
107
  `Likely a routing state value a filter reads never got written.`,
105
108
  ],
106
109
  };
@@ -114,7 +117,7 @@ function transitionUnevaluableDetail(transitions) {
114
117
  return {
115
118
  headline: `an exit transition's filter could not be evaluated`,
116
119
  why: [
117
- `${logSymbols.info} every task is resolved, but ${transitions.join(', ')} reads an operand`,
120
+ `${logSymbols.info} every activity is resolved, but ${transitions.join(', ')} reads an operand`,
118
121
  `that is missing or unreadable (GROQ null), so routing is held rather than`,
119
122
  `falling through. Make the data the filter reads readable — publish the`,
120
123
  `subject (or fill the field) — and the instance advances on its own; no`,
@@ -125,7 +128,7 @@ function transitionUnevaluableDetail(transitions) {
125
128
  const REMEDIATION_LABEL = {
126
129
  'retry-effect': 'retry-effect',
127
130
  'drain-effects': 're-run the effect drainer',
128
- 'reset-task': 'reset-task',
131
+ 'reset-activity': 'reset-activity',
129
132
  'set-stage': 'move-stage',
130
133
  abort: 'abort',
131
134
  };
@@ -142,8 +145,8 @@ function causeDetail(cause) {
142
145
  return failedEffectDetail(cause.effect);
143
146
  case 'hung-effect':
144
147
  return hungEffectDetail(cause.effect);
145
- case 'failed-task':
146
- return failedTaskDetail(cause.task);
148
+ case 'failed-activity':
149
+ return failedActivityDetail(cause.activity);
147
150
  case 'no-transition-fires':
148
151
  return noTransitionDetail();
149
152
  case 'transition-unevaluable':
@@ -169,7 +172,7 @@ function statusLine(diagnosis) {
169
172
  }
170
173
  }
171
174
  /**
172
- * The diagnosis body: the verdict line, the current stage's tasks +
175
+ * The diagnosis body: the verdict line, the current stage's activities +
173
176
  * transitions as evidence (for any in-flight state), and the cause block with
174
177
  * the engine's suggested fix when stuck. Pure so every verdict permutation is
175
178
  * assertable without driving a live evaluation. Takes the engine's
@@ -232,7 +235,7 @@ export default class Diagnose extends Command {
232
235
  workflowResource: config.workflowResource,
233
236
  instanceId: args.instanceId,
234
237
  // Token-auth CLI can't hit /users/me; pin the same system actor the
235
- // write verbs use. Transition filters and task status are actor-
238
+ // write verbs use. Transition filters and activity status are actor-
236
239
  // independent. A requirement that reads $actor/$assigned/$can is the
237
240
  // exception — it's evaluated against this system actor, so a `blocked`
238
241
  // verdict on such a requirement reflects the engine's view, not a
@@ -9,7 +9,7 @@ import { type RanOp } from '../lib/ops-report.ts';
9
9
  */
10
10
  export declare function parseParams(pairs: string[]): Record<string, unknown>;
11
11
  /**
12
- * The list-mode body: every action on the current stage's tasks, marked
12
+ * The list-mode body: every action on the current stage's activities, marked
13
13
  * fireable or not (with the disabling reason), plus a hint showing the
14
14
  * flags to fire one. Pure so the populated/empty permutations assert
15
15
  * without a live evaluation.
@@ -27,7 +27,7 @@ interface FireOutcome {
27
27
  * {@link fireActionReport}. Pure so its shape is asserted directly. */
28
28
  export declare function fireResultJson(result: FireOutcome, ids: {
29
29
  instanceId: string;
30
- task: string;
30
+ activity: string;
31
31
  action: string;
32
32
  }): Record<string, unknown>;
33
33
  export interface FireActionReport {
@@ -40,7 +40,7 @@ export interface FireActionReport {
40
40
  * the fired / no-op / cascaded / ops permutations assert without a live
41
41
  * fire.
42
42
  */
43
- export declare function fireActionReport(result: FireOutcome, task: string, action: string): FireActionReport;
43
+ export declare function fireActionReport(result: FireOutcome, activity: string, action: string): FireActionReport;
44
44
  export default class FireAction extends Command {
45
45
  static description: string;
46
46
  static examples: string[];
@@ -48,7 +48,7 @@ export default class FireAction extends Command {
48
48
  instanceId: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
49
49
  };
50
50
  static flags: {
51
- task: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
51
+ activity: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
52
52
  action: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
53
53
  param: import("@oclif/core/interfaces").OptionFlag<string[], import("@oclif/core/interfaces").CustomOptions>;
54
54
  as: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
@@ -44,8 +44,8 @@ function reasonText(reason) {
44
44
  switch (reason.kind) {
45
45
  case 'filter-failed':
46
46
  return `action filter not satisfied${reason.detail ? ` (${reason.detail})` : ''}`;
47
- case 'task-not-active':
48
- return `task is ${reason.status}`;
47
+ case 'activity-not-active':
48
+ return `activity is ${reason.status}`;
49
49
  case 'stage-terminal':
50
50
  return `stage '${reason.stage}' is terminal`;
51
51
  case 'mutation-guard-denied':
@@ -63,7 +63,7 @@ function actionLines(a) {
63
63
  const mark = a.allowed ? logSymbols.success : logSymbols.error;
64
64
  const title = a.title !== undefined ? styleText('dim', ` (${a.title})`) : '';
65
65
  const lines = [
66
- ` ${mark} ${a.task} → ${a.action}${title} ${styleText('dim', `[task ${a.taskStatus}]`)}`,
66
+ ` ${mark} ${a.activity} → ${a.action}${title} ${styleText('dim', `[activity ${a.activityStatus}]`)}`,
67
67
  ];
68
68
  if (a.params.length > 0) {
69
69
  lines.push(styleText('dim', ` params: ${a.params.map(paramLabel).join(', ')}`));
@@ -74,20 +74,20 @@ function actionLines(a) {
74
74
  return lines;
75
75
  }
76
76
  /**
77
- * The list-mode body: every action on the current stage's tasks, marked
77
+ * The list-mode body: every action on the current stage's activities, marked
78
78
  * fireable or not (with the disabling reason), plus a hint showing the
79
79
  * flags to fire one. Pure so the populated/empty permutations assert
80
80
  * without a live evaluation.
81
81
  */
82
82
  export function renderAvailableActions(actions, stage, instanceId) {
83
83
  if (actions.length === 0) {
84
- return [`No actions on the tasks of the current stage ('${stage}').`];
84
+ return [`No actions on the activities of the current stage ('${stage}').`];
85
85
  }
86
86
  const lines = [styleText('bold', `Actions on stage '${stage}':`), ''];
87
87
  for (const a of actions) {
88
88
  lines.push(...actionLines(a));
89
89
  }
90
- lines.push('', styleText('dim', 'Fire one with:'), styleText('dim', ` fire-action ${instanceId} --task <task> --action <action>`));
90
+ lines.push('', styleText('dim', 'Fire one with:'), styleText('dim', ` fire-action ${instanceId} --activity <activity> --action <action>`));
91
91
  return lines;
92
92
  }
93
93
  /** The `--json` payload for a fired action — the structured counterpart of
@@ -95,7 +95,7 @@ export function renderAvailableActions(actions, stage, instanceId) {
95
95
  export function fireResultJson(result, ids) {
96
96
  return {
97
97
  instanceId: ids.instanceId,
98
- task: ids.task,
98
+ activity: ids.activity,
99
99
  action: ids.action,
100
100
  fired: result.fired,
101
101
  cascaded: result.cascaded,
@@ -108,37 +108,37 @@ export function fireResultJson(result, ids) {
108
108
  * the fired / no-op / cascaded / ops permutations assert without a live
109
109
  * fire.
110
110
  */
111
- export function fireActionReport(result, task, action) {
111
+ export function fireActionReport(result, activity, action) {
112
112
  const stage = styleText('bold', result.instance.currentStage);
113
113
  if (!result.fired) {
114
114
  return {
115
115
  fired: false,
116
- message: `fireAction returned without firing — task '${task}' is no longer active`,
116
+ message: `fireAction returned without firing — activity '${activity}' is no longer active`,
117
117
  opsLines: [],
118
118
  };
119
119
  }
120
120
  const tail = result.cascaded > 0 ? `, then cascaded ${result.cascaded} auto-transition(s)` : '';
121
121
  return {
122
122
  fired: true,
123
- message: `Fired ${styleText('bold', action)} on ${styleText('bold', task)} — now at ${stage}${tail}`,
123
+ message: `Fired ${styleText('bold', action)} on ${styleText('bold', activity)} — now at ${stage}${tail}`,
124
124
  opsLines: opsAppliedLines(result.ranOps),
125
125
  };
126
126
  }
127
127
  export default class FireAction extends Command {
128
- 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 description = "Fire an action on an instance — act on a user's behalf to unstick a waiting activity. Omit --action to list what can be fired.";
129
129
  static examples = [
130
130
  '<%= config.bin %> fire-action wf-instance.abc123',
131
- '<%= config.bin %> fire-action wf-instance.abc123 --task approve --action approve',
132
- '<%= config.bin %> fire-action wf-instance.abc123 --task approve --action approve --as user.xyz --as-role editor',
133
- '<%= config.bin %> fire-action wf-instance.abc123 --task publish --action publish --param note=shipping',
131
+ '<%= config.bin %> fire-action wf-instance.abc123 --activity approve --action approve',
132
+ '<%= config.bin %> fire-action wf-instance.abc123 --activity approve --action approve --as user.xyz --as-role editor',
133
+ '<%= config.bin %> fire-action wf-instance.abc123 --activity publish --action publish --param note=shipping',
134
134
  ];
135
135
  static args = {
136
136
  instanceId: Args.string({ required: true, description: 'Workflow instance id.' }),
137
137
  };
138
138
  static flags = {
139
139
  ...tagFlags,
140
- task: Flags.string({
141
- description: 'Task the action belongs to. Required to fire; omit --action to list.',
140
+ activity: Flags.string({
141
+ description: 'Activity the action belongs to. Required to fire; omit --action to list.',
142
142
  }),
143
143
  action: Flags.string({
144
144
  description: 'Action to fire. Omit to list the actions available on the instance.',
@@ -170,15 +170,15 @@ export default class FireAction extends Command {
170
170
  await this.listActions(client, config, args.instanceId, actor, flags.json);
171
171
  return;
172
172
  }
173
- if (flags.task === undefined) {
174
- fail('fire-action usage error:', 'specify --task for the action (or omit --action to list what can be fired)');
173
+ if (flags.activity === undefined) {
174
+ fail('fire-action usage error:', 'specify --activity for the action (or omit --action to list what can be fired)');
175
175
  }
176
176
  await this.fireOne({
177
177
  client,
178
178
  config,
179
179
  instanceId: args.instanceId,
180
180
  actor,
181
- task: flags.task,
181
+ activity: flags.activity,
182
182
  action: flags.action,
183
183
  params: flags.param,
184
184
  json: flags.json,
@@ -206,14 +206,14 @@ export default class FireAction extends Command {
206
206
  }
207
207
  }
208
208
  async fireOne(opts) {
209
- const { client, config, instanceId, actor, task, action, json } = opts;
209
+ const { client, config, instanceId, actor, activity, action, json } = opts;
210
210
  const params = failOnThrow('Invalid --param:', () => parseParams(opts.params));
211
211
  const fireArgs = {
212
212
  client,
213
213
  tag: config.tag,
214
214
  workflowResource: config.workflowResource,
215
215
  instanceId,
216
- task,
216
+ activity,
217
217
  action,
218
218
  access: { actor },
219
219
  params,
@@ -222,13 +222,13 @@ export default class FireAction extends Command {
222
222
  const result = await workflow
223
223
  .fireAction(fireArgs)
224
224
  .catch((error) => fail('fireAction error:', errorMessage(error)));
225
- this.log(JSON.stringify(fireResultJson(result, { instanceId, task, action }), null, 2));
225
+ this.log(JSON.stringify(fireResultJson(result, { instanceId, activity, action }), null, 2));
226
226
  return;
227
227
  }
228
- const spinner = ora(`Firing ${action} on ${task}…`).start();
228
+ const spinner = ora(`Firing ${action} on ${activity}…`).start();
229
229
  try {
230
230
  const result = await workflow.fireAction(fireArgs);
231
- emitWriteReport(spinner, fireActionReport(result, task, action), (line) => this.log(line));
231
+ emitWriteReport(spinner, fireActionReport(result, activity, action), (line) => this.log(line));
232
232
  }
233
233
  catch (error) {
234
234
  spinner.fail('Action rejected');
@@ -12,7 +12,7 @@ type InstanceStatus = 'aborted' | 'completed' | 'in-flight';
12
12
  interface ListFlags {
13
13
  'in-flight': boolean;
14
14
  failed: boolean;
15
- 'workflow-id'?: string | undefined;
15
+ 'workflow-name'?: string | undefined;
16
16
  tag?: string | undefined;
17
17
  limit: number;
18
18
  }
@@ -41,7 +41,7 @@ export default class List extends Command {
41
41
  static flags: {
42
42
  'in-flight': import("@oclif/core/interfaces").BooleanFlag<boolean>;
43
43
  failed: import("@oclif/core/interfaces").BooleanFlag<boolean>;
44
- 'workflow-id': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
44
+ 'workflow-name': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
45
45
  limit: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
46
46
  tag: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
47
47
  };
@@ -15,11 +15,11 @@ export function buildListQuery(flags) {
15
15
  const filters = [`_type == "${WORKFLOW_INSTANCE_TYPE}"`];
16
16
  if (flags['in-flight'])
17
17
  filters.push('!defined(completedAt)');
18
- if (flags['workflow-id'])
18
+ if (flags['workflow-name'])
19
19
  filters.push('definition == $definition');
20
- // Cheap approximation of --failed: any task in any stage with status "failed".
20
+ // Cheap approximation of --failed: any activity in any stage with status "failed".
21
21
  if (flags.failed)
22
- filters.push('count(stages[].tasks[status == "failed"]) > 0');
22
+ filters.push('count(stages[].activities[status == "failed"]) > 0');
23
23
  if (flags.tag)
24
24
  filters.push(tagScopeFilter());
25
25
  const groq = `*[${filters.join(' && ')}] | order(lastChangedAt desc) [0...$limit]{
@@ -32,8 +32,8 @@ export function buildListQuery(flags) {
32
32
  lastChangedAt
33
33
  }`;
34
34
  const params = { limit: flags.limit };
35
- if (flags['workflow-id'])
36
- params['definition'] = flags['workflow-id'];
35
+ if (flags['workflow-name'])
36
+ params['definition'] = flags['workflow-name'];
37
37
  if (flags.tag)
38
38
  params['tag'] = flags.tag;
39
39
  return { groq, params };
@@ -68,7 +68,7 @@ export default class List extends Command {
68
68
  static examples = [
69
69
  '<%= config.bin %> list',
70
70
  '<%= config.bin %> list --in-flight',
71
- '<%= config.bin %> list --workflow-id productLaunch',
71
+ '<%= config.bin %> list --workflow-name productLaunch',
72
72
  '<%= config.bin %> list --tag prod',
73
73
  ];
74
74
  static flags = {
@@ -78,10 +78,10 @@ export default class List extends Command {
78
78
  default: false,
79
79
  }),
80
80
  failed: Flags.boolean({
81
- description: 'Only instances with at least one failed task.',
81
+ description: 'Only instances with at least one failed activity.',
82
82
  default: false,
83
83
  }),
84
- 'workflow-id': Flags.string({
84
+ 'workflow-name': Flags.string({
85
85
  description: 'Filter by workflow definition name.',
86
86
  }),
87
87
  limit: Flags.integer({
@@ -1,9 +1,9 @@
1
1
  import { StubCommand } from '../lib/stub.ts';
2
- export default class RetryTask extends StubCommand {
2
+ export default class RetryActivity extends StubCommand {
3
3
  static hidden: boolean;
4
4
  static description: string;
5
5
  static args: {
6
6
  instanceId: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
7
- task: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
7
+ activity: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
8
8
  };
9
9
  }
@@ -1,11 +1,11 @@
1
1
  import { Args } from '@oclif/core';
2
2
  import { StubCommand } from "../lib/stub.js";
3
- export default class RetryTask extends StubCommand {
3
+ export default class RetryActivity extends StubCommand {
4
4
  // Stubbed, so kept off the help + published surface until it's wired.
5
5
  static hidden = true;
6
- static description = 'Re-invoke a failed task on an in-flight instance.';
6
+ static description = 'Re-invoke a failed activity on an in-flight instance.';
7
7
  static args = {
8
8
  instanceId: Args.string({ required: true, description: 'Workflow instance id.' }),
9
- task: Args.string({ required: true, description: 'Task name within the current stage.' }),
9
+ activity: Args.string({ required: true, description: 'Activity name within the current stage.' }),
10
10
  };
11
11
  }
@@ -4,7 +4,7 @@ export default class SetStage extends StubCommand {
4
4
  // Stubbed, so kept off the help + published surface until it's wired.
5
5
  static hidden = true;
6
6
  // Engine note: workflow.setStage currently always runs guards + stage
7
- // entry (task activation, effects). A "manual override" that bypasses
7
+ // entry (activity activation, effects). A "manual override" that bypasses
8
8
  // them is engine work that hasn't landed; this command stub captures
9
9
  // the intended surface only.
10
10
  static description = "Force-set an instance's current stage, bypassing guards + effects (manual override).";
@@ -15,7 +15,7 @@ export default class Show extends Command {
15
15
  * `fire-action`): a bold workflow + id title, then aligned detail rows. */
16
16
  export declare function instanceHeader(instance: WorkflowInstance): string;
17
17
  /**
18
- * The per-instance body lines: every stage with its tasks, any pending
18
+ * The per-instance body lines: every stage with its activities, any pending
19
19
  * effects, and (optionally) the history log. Pure so it can be asserted
20
20
  * without driving the oclif command.
21
21
  */
@@ -3,9 +3,9 @@ import { Args, Command, Flags } from '@oclif/core';
3
3
  import { abortReason } from '@sanity/workflow-engine';
4
4
  import logSymbols from 'log-symbols';
5
5
  import { loadClient } from "../lib/client.js";
6
- import { formatKeyValue, formatTimestamp, sectionHeader, taskIcon } from "../lib/ui.js";
6
+ import { formatKeyValue, formatTimestamp, sectionHeader, activityIcon } from "../lib/ui.js";
7
7
  export default class Show extends Command {
8
- static description = 'Show the state, tasks, and effects of a workflow instance.';
8
+ static description = 'Show the state, activities, and effects of a workflow instance.';
9
9
  static examples = [
10
10
  '<%= config.bin %> show wf-instance.abc123',
11
11
  '<%= config.bin %> show wf-instance.abc123 --include history',
@@ -68,7 +68,7 @@ export function instanceHeader(instance) {
68
68
  return [title, ...rows].join('\n');
69
69
  }
70
70
  /**
71
- * The per-instance body lines: every stage with its tasks, any pending
71
+ * The per-instance body lines: every stage with its activities, any pending
72
72
  * effects, and (optionally) the history log. Pure so it can be asserted
73
73
  * without driving the oclif command.
74
74
  */
@@ -79,8 +79,8 @@ export function describeInstance(instance, options) {
79
79
  ? styleText('dim', ` (exited ${formatTimestamp(stage.exitedAt)})`)
80
80
  : styleText('cyan', ' (current)');
81
81
  lines.push(` • ${stage.name}${marker}`);
82
- for (const task of stage.tasks) {
83
- lines.push(` ${taskIcon[task.status]} ${task.name} ${styleText('dim', `[${task.status}]`)}`);
82
+ for (const activity of stage.activities) {
83
+ lines.push(` ${activityIcon[activity.status]} ${activity.name} ${styleText('dim', `[${activity.status}]`)}`);
84
84
  }
85
85
  }
86
86
  if (instance.pendingEffects.length > 0) {
@@ -115,7 +115,7 @@ export function collectValidationErrors(defs) {
115
115
  }
116
116
  catch (err) {
117
117
  const message = err instanceof Error ? err.message : String(err);
118
- errors.push(` · ${d.name} v${d.version}: ${message}`);
118
+ errors.push(` · ${d.name}: ${message}`);
119
119
  }
120
120
  }
121
121
  return errors;
@@ -1,5 +1,6 @@
1
1
  import type { DiffEntry } from '@sanity/workflow-engine';
2
- /** One summary line per diff entry, tagged create / update / unchanged. */
2
+ /** One summary line per diff entry, tagged create / unchanged. Definitions are
3
+ * immutable, so a content change is a new version (`create`), never an update. */
3
4
  export declare function summaryLines(entries: DiffEntry[]): string[];
4
5
  /** Render a JSON diff of `oldObj` → `newObj` as `+`/`-`/context lines. */
5
6
  export declare function diffLines(oldObj: object, newObj: object): string[];
package/dist/lib/diff.js CHANGED
@@ -2,11 +2,11 @@ import { styleText } from 'node:util';
2
2
  import { diffJson } from 'diff';
3
3
  import logSymbols from 'log-symbols';
4
4
  import { sectionHeader } from "./ui.js";
5
- /** One summary line per diff entry, tagged create / update / unchanged. */
5
+ /** One summary line per diff entry, tagged create / unchanged. Definitions are
6
+ * immutable, so a content change is a new version (`create`), never an update. */
6
7
  export function summaryLines(entries) {
7
8
  const tags = {
8
9
  create: styleText('green', '+ create '),
9
- update: styleText('yellow', '~ update '),
10
10
  unchanged: styleText('dim', ' unchanged'),
11
11
  };
12
12
  return entries.map((e) => {
package/dist/lib/ui.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { TaskStatus } from '@sanity/workflow-engine';
1
+ import type { ActivityStatus } from '@sanity/workflow-engine';
2
2
  /**
3
3
  * A bold `Title:` section heading. Mirrors the Sanity CLI's `sectionHeader`
4
4
  * (name + shape) so it folds into the shared helper when this CLI merges in.
@@ -13,9 +13,9 @@ export declare function formatKeyValue(key: string, value: string, options?: {
13
13
  indent?: number;
14
14
  padTo?: number;
15
15
  }): string;
16
- /** Status glyph per task state. log-symbols ship pre-colored (green ✔, red ✖),
16
+ /** Status glyph per activity state. log-symbols ship pre-colored (green ✔, red ✖),
17
17
  * so those need no extra wrap; the rest carry their own state color. */
18
- export declare const taskIcon: Record<TaskStatus, string>;
18
+ export declare const activityIcon: Record<ActivityStatus, string>;
19
19
  /** An engine ISO-8601 timestamp as an absolute `yyyy-MM-dd HH:mm:ss` — the
20
20
  * Sanity CLI's audit-log format (see `backups/list`). For detail views. */
21
21
  export declare function formatTimestamp(iso: string): string;
package/dist/lib/ui.js CHANGED
@@ -21,9 +21,9 @@ export function formatKeyValue(key, value, options) {
21
21
  const paddedKey = `${key}:`.padEnd(padTo > 0 ? padTo + 1 : key.length + 1);
22
22
  return `${' '.repeat(indent)}${styleText('dim', paddedKey)} ${value}`;
23
23
  }
24
- /** Status glyph per task state. log-symbols ship pre-colored (green ✔, red ✖),
24
+ /** Status glyph per activity state. log-symbols ship pre-colored (green ✔, red ✖),
25
25
  * so those need no extra wrap; the rest carry their own state color. */
26
- export const taskIcon = {
26
+ export const activityIcon = {
27
27
  done: logSymbols.success,
28
28
  active: styleText('cyan', '●'),
29
29
  pending: styleText('dim', '○'),
@@ -152,12 +152,12 @@
152
152
  "required": true
153
153
  }
154
154
  },
155
- "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.",
155
+ "description": "Fire an action on an instance — act on a user's behalf to unstick a waiting activity. Omit --action to list what can be fired.",
156
156
  "examples": [
157
157
  "<%= config.bin %> fire-action wf-instance.abc123",
158
- "<%= config.bin %> fire-action wf-instance.abc123 --task approve --action approve",
159
- "<%= config.bin %> fire-action wf-instance.abc123 --task approve --action approve --as user.xyz --as-role editor",
160
- "<%= config.bin %> fire-action wf-instance.abc123 --task publish --action publish --param note=shipping"
158
+ "<%= config.bin %> fire-action wf-instance.abc123 --activity approve --action approve",
159
+ "<%= config.bin %> fire-action wf-instance.abc123 --activity approve --action approve --as user.xyz --as-role editor",
160
+ "<%= config.bin %> fire-action wf-instance.abc123 --activity publish --action publish --param note=shipping"
161
161
  ],
162
162
  "flags": {
163
163
  "tag": {
@@ -167,9 +167,9 @@
167
167
  "multiple": false,
168
168
  "type": "option"
169
169
  },
170
- "task": {
171
- "description": "Task the action belongs to. Required to fire; omit --action to list.",
172
- "name": "task",
170
+ "activity": {
171
+ "description": "Activity the action belongs to. Required to fire; omit --action to list.",
172
+ "name": "activity",
173
173
  "hasDynamicHelp": false,
174
174
  "multiple": false,
175
175
  "type": "option"
@@ -233,7 +233,7 @@
233
233
  "examples": [
234
234
  "<%= config.bin %> list",
235
235
  "<%= config.bin %> list --in-flight",
236
- "<%= config.bin %> list --workflow-id productLaunch",
236
+ "<%= config.bin %> list --workflow-name productLaunch",
237
237
  "<%= config.bin %> list --tag prod"
238
238
  ],
239
239
  "flags": {
@@ -251,14 +251,14 @@
251
251
  "type": "boolean"
252
252
  },
253
253
  "failed": {
254
- "description": "Only instances with at least one failed task.",
254
+ "description": "Only instances with at least one failed activity.",
255
255
  "name": "failed",
256
256
  "allowNo": false,
257
257
  "type": "boolean"
258
258
  },
259
- "workflow-id": {
259
+ "workflow-name": {
260
260
  "description": "Filter by workflow definition name.",
261
- "name": "workflow-id",
261
+ "name": "workflow-name",
262
262
  "hasDynamicHelp": false,
263
263
  "multiple": false,
264
264
  "type": "option"
@@ -340,7 +340,7 @@
340
340
  "move-stage.js"
341
341
  ]
342
342
  },
343
- "retry-task": {
343
+ "retry-activity": {
344
344
  "aliases": [],
345
345
  "args": {
346
346
  "instanceId": {
@@ -348,18 +348,18 @@
348
348
  "name": "instanceId",
349
349
  "required": true
350
350
  },
351
- "task": {
352
- "description": "Task name within the current stage.",
353
- "name": "task",
351
+ "activity": {
352
+ "description": "Activity name within the current stage.",
353
+ "name": "activity",
354
354
  "required": true
355
355
  }
356
356
  },
357
- "description": "Re-invoke a failed task on an in-flight instance.",
357
+ "description": "Re-invoke a failed activity on an in-flight instance.",
358
358
  "flags": {},
359
359
  "hasDynamicHelp": false,
360
360
  "hidden": true,
361
361
  "hiddenAliases": [],
362
- "id": "retry-task",
362
+ "id": "retry-activity",
363
363
  "pluginAlias": "@sanity/workflow-cli",
364
364
  "pluginName": "@sanity/workflow-cli",
365
365
  "pluginType": "core",
@@ -368,7 +368,7 @@
368
368
  "relativePath": [
369
369
  "dist",
370
370
  "commands",
371
- "retry-task.js"
371
+ "retry-activity.js"
372
372
  ]
373
373
  },
374
374
  "set-stage": {
@@ -422,7 +422,7 @@
422
422
  "required": true
423
423
  }
424
424
  },
425
- "description": "Show the state, tasks, and effects of a workflow instance.",
425
+ "description": "Show the state, activities, and effects of a workflow instance.",
426
426
  "examples": [
427
427
  "<%= config.bin %> show wf-instance.abc123",
428
428
  "<%= config.bin %> show wf-instance.abc123 --include history"
@@ -614,9 +614,9 @@
614
614
  "multiple": false,
615
615
  "type": "option"
616
616
  },
617
- "workflow-id": {
617
+ "workflow-name": {
618
618
  "description": "Filter to a single workflow definition name (e.g. product-launch)",
619
- "name": "workflow-id",
619
+ "name": "workflow-name",
620
620
  "hasDynamicHelp": false,
621
621
  "multiple": false,
622
622
  "type": "option"
@@ -681,5 +681,5 @@
681
681
  ]
682
682
  }
683
683
  },
684
- "version": "0.7.0"
684
+ "version": "0.8.0"
685
685
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sanity/workflow-cli",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "description": "Command-line tool for deploying, inspecting, and administering Sanity workflow definitions and instances.",
5
5
  "keywords": [
6
6
  "cli",
@@ -53,15 +53,15 @@
53
53
  "diff": "^9.0.0",
54
54
  "log-symbols": "^7.0.1",
55
55
  "ora": "^9.4.0",
56
- "@sanity/workflow-engine": "0.10.0"
56
+ "@sanity/workflow-engine": "0.12.0"
57
57
  },
58
58
  "devDependencies": {
59
59
  "@types/diff": "^8.0.0",
60
60
  "@types/node": "^24.12.4",
61
61
  "oclif": "^4.23.16",
62
62
  "vitest": "^4.1.8",
63
- "@sanity/workflow-engine-test": "0.6.0",
64
- "@sanity/workflow-examples": "0.1.6"
63
+ "@sanity/workflow-engine-test": "0.7.0",
64
+ "@sanity/workflow-examples": "0.2.0"
65
65
  },
66
66
  "oclif": {
67
67
  "bin": "sanity-workflows",