@sanity/workflow-cli 0.20.0 → 0.22.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 +48 -0
- package/README.md +4 -3
- package/dist/commands/editorial-workflows/definition/list.d.ts +16 -0
- package/dist/commands/editorial-workflows/definition/list.js +46 -16
- package/dist/commands/editorial-workflows/definition/show.d.ts +1 -0
- package/dist/commands/editorial-workflows/definition/show.js +6 -1
- package/dist/commands/editorial-workflows/deploy.d.ts +18 -0
- package/dist/commands/editorial-workflows/deploy.js +23 -9
- package/dist/commands/editorial-workflows/list.d.ts +21 -7
- package/dist/commands/editorial-workflows/list.js +55 -12
- package/dist/commands/editorial-workflows/reset-activity.d.ts +26 -4
- package/dist/commands/editorial-workflows/reset-activity.js +56 -6
- package/dist/commands/editorial-workflows/show.d.ts +1 -0
- package/dist/commands/editorial-workflows/show.js +16 -8
- package/dist/help.d.ts +8 -0
- package/dist/help.js +23 -0
- package/dist/lib/context.d.ts +11 -37
- package/dist/lib/context.js +44 -40
- package/dist/lib/fail.js +1 -1
- package/dist/lib/flags.d.ts +12 -7
- package/dist/lib/flags.js +2 -2
- package/dist/lib/read-fanout.d.ts +23 -0
- package/dist/lib/read-fanout.js +17 -1
- package/dist/lib/select-deployment.d.ts +0 -4
- package/dist/lib/select-deployment.js +1 -1
- package/dist/lib/ui.d.ts +9 -2
- package/dist/lib/ui.js +10 -5
- package/oclif.manifest.json +82 -25
- package/package.json +6 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,53 @@
|
|
|
1
1
|
# @sanity/workflow-cli
|
|
2
2
|
|
|
3
|
+
## 0.22.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 1c6d4c0: Name the target deployment and resource on `deploy` progress and result lines. The spinner and completion line now read `Deploying N definition(s) to <name> (<tag>) → <resource>…` / `Processed N definition(s) · <name> (<tag>) → <resource>` (and the dry-run equivalents), so a single-deployment run — the common case, which prints no per-batch banner — always shows where the deploy landed.
|
|
8
|
+
- fb42001: The instance-keyed commands — `diagnose`, `fire-action`, `set-stage`, `abort`, `reset-activity` — now locate an instance by fanning out across every configured resource, the same way `show` and the other read commands already do. Passing an instance id is enough: on a config that spans several resources they no longer prompt for (or require) a `--deployment` / `--tag` selector — those stay as optional narrowers. As an optimization, an id carrying the engine's default `<tag>.` mint prefix probes that tag's resource first and skips the fan-out on a hit. `reset-activity` also gains the `--deployment` narrower its siblings already had.
|
|
9
|
+
- 84ff807: The read commands — `list`, `show`, `definition list`, `definition show` — now take `--json`, emitting structured output on stdout for parity with the verb commands. `show` and `definition show` print the fetched document as stored; `list` and `definition list` print their table's row view (each row annotated with the `resource` it came from) under a `truncated` flag. A JSON read spanning several resources is all-or-nothing: an unreadable resource fails the whole command rather than emitting a partial listing a script would mistake for the complete result.
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- a8c460c: Colour the whole terminal-status row in the instance header — aborted rows render bold yellow, completed rows bold green — so a finished instance stands out from the neutral detail rows instead of only its timestamp carrying colour.
|
|
14
|
+
- Updated dependencies [88ba4ba]
|
|
15
|
+
- @sanity/workflow-engine@0.22.0
|
|
16
|
+
|
|
17
|
+
## 0.21.0
|
|
18
|
+
|
|
19
|
+
### Minor Changes
|
|
20
|
+
|
|
21
|
+
- 9273d55: List the nested `definition` commands (`list`, `show`, `diff`, `delete`) in the
|
|
22
|
+
standalone binary's flat `--help` output. A custom oclif help class surfaces the
|
|
23
|
+
bare `definition <verb>` aliases at the root instead of burying them under the
|
|
24
|
+
`editorial-workflows` topic, so `sanity-workflows --help` shows every command
|
|
25
|
+
without topic-digging. `help --all` still prints the full canonical tree.
|
|
26
|
+
- e7392af: Add the `resetActivity` admin verb — reset a failed (or otherwise terminal) activity in an instance's current stage back to `active` (re-run) or `skipped` (bypass), then cascade so a `$allActivitiesDone`-gated exit transition can fire. `workflow.diagnose` now marks its `reset-activity` remediation `available`, and the CLI ships the real `reset-activity <instanceId> <activity> [--skip]` command in place of the stub. Emits the `Editorial Workflows Activity Reset` adoption event on every attempt (`changed: false` on a no-op), matching the `set-stage` and `abort` overrides.
|
|
27
|
+
|
|
28
|
+
Writes strictly within the existing model-3 grammar: it reuses the `activityStatusChanged` history variant and the existing activity statuses, so it adds no persisted shape — there is no data-model version bump and no reader-floor change.
|
|
29
|
+
|
|
30
|
+
### Patch Changes
|
|
31
|
+
|
|
32
|
+
- b248789: CLI error detail lines now render in the terminal's default colour instead of red. The red headline with its `✖` still signals the failure; the detail beneath it (validation output, auth hints, wrapped errors) is the information the user has to read, and long runs of red text were hard to read.
|
|
33
|
+
- f4bc057: **BREAKING:** `list_workflow_instances` now returns one page with `has_more` and an optional `next_cursor`; callers that need every match must continue with the cursor. The optional `limit` accepts 1–100 rows and defaults to 25.
|
|
34
|
+
|
|
35
|
+
Bound instance and definition list reads with lake-side filtering and consumer-specific projections.
|
|
36
|
+
|
|
37
|
+
Document filtering now excludes exited-stage references and includes live unresolved child-workflow references. Definition discovery selects and model-gates only the latest deployed version of each workflow name; historical versions remain stored and available to history-oriented APIs.
|
|
38
|
+
|
|
39
|
+
The MCP eval suite now verifies that agents follow list cursors to find a target beyond the first default page, including in persisted Braintrust runs.
|
|
40
|
+
|
|
41
|
+
MCP tool telemetry reports a `cursorUsed` boolean so continued list-page adoption is measurable without sending cursor values, arguments, or results.
|
|
42
|
+
|
|
43
|
+
- Updated dependencies [d9394e5]
|
|
44
|
+
- Updated dependencies [92e28bd]
|
|
45
|
+
- Updated dependencies [f4bc057]
|
|
46
|
+
- Updated dependencies [bcc30fe]
|
|
47
|
+
- Updated dependencies [e7392af]
|
|
48
|
+
- Updated dependencies [fa9c796]
|
|
49
|
+
- @sanity/workflow-engine@0.21.0
|
|
50
|
+
|
|
3
51
|
## 0.20.0
|
|
4
52
|
|
|
5
53
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -60,7 +60,7 @@ import {articleReview, urlDraft} from './src/workflows.ts'
|
|
|
60
60
|
export default defineWorkflowConfig({
|
|
61
61
|
deployments: [
|
|
62
62
|
{
|
|
63
|
-
expectedMinReaderModel:
|
|
63
|
+
expectedMinReaderModel: 4,
|
|
64
64
|
name: 'production', // the deployment's unique identity (lowercase letters, digits, dashes)
|
|
65
65
|
tag: 'prod', // the environment partition the engine's docs are scoped to
|
|
66
66
|
workflowResource: {type: 'dataset', id: 'acme.workflows'}, // where those docs live
|
|
@@ -89,8 +89,8 @@ tag names exactly one deployment — on `deploy` it targets every deployment
|
|
|
89
89
|
carrying the tag (a tag is an environment group). With several configured, a
|
|
90
90
|
bare interactive run presents a keyboard-driven deployment selector (by name,
|
|
91
91
|
tag alongside) — on `deploy`, `start`, and `definition diff`/`delete`, and on
|
|
92
|
-
the instance-targeted commands (`abort`, `set-stage`, `
|
|
93
|
-
`diagnose`) when the config spans several resources. In CI or another
|
|
92
|
+
the instance-targeted commands (`abort`, `set-stage`, `reset-activity`,
|
|
93
|
+
`fire-action`, `diagnose`) when the config spans several resources. In CI or another
|
|
94
94
|
non-interactive shell, the command fails asking for `--deployment` or `--tag`
|
|
95
95
|
(`deploy` also suggests `--all-tags`) instead of blocking for input.
|
|
96
96
|
`--all-tags` deploys every deployment in the config in one run: a failure in one doesn't
|
|
@@ -170,6 +170,7 @@ stale compiled output.
|
|
|
170
170
|
| `tail <instance-id>` | wired — `client.listen()` over the instance, prints new history entries |
|
|
171
171
|
| `abort <instance-id>` | wired — calls `workflow.abortInstance` (hard stop: cancels pending effects, removes guards) |
|
|
172
172
|
| `set-stage <instance-id> --to <stage>` | wired — calls `workflow.setStage` (admin override: skips declared transitions/filters; enter lifecycle + cascade still run) |
|
|
173
|
+
| `reset-activity <instance-id> <activity>` | wired — calls `workflow.resetActivity` (recovery override: reset a failed activity to `active`, or `--skip` to bypass it; then cascade) |
|
|
173
174
|
| `fire-action <instance-id>` | wired — `workflow.availableActions` lists actions; `workflow.fireAction` fires one |
|
|
174
175
|
| `definition list` | wired — `client.fetch` over `sanity.workflow.definition` documents |
|
|
175
176
|
| `definition show <name>` | wired — `client.fetch`, latest version unless `--version` |
|
|
@@ -26,10 +26,26 @@ export default class DefinitionList extends WorkflowCommand {
|
|
|
26
26
|
static description: string;
|
|
27
27
|
static examples: string[];
|
|
28
28
|
static flags: {
|
|
29
|
+
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
29
30
|
limit: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
|
|
30
31
|
name: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
31
32
|
tag: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
32
33
|
};
|
|
33
34
|
run(): Promise<void>;
|
|
34
35
|
}
|
|
36
|
+
/** Map a queried definition to its display row. Drops the stamp pair (gate
|
|
37
|
+
* plumbing, not view data) and `_id` — `tag.name.vN`, which just repeats
|
|
38
|
+
* name + tag + version, and no command takes an `_id` anyway (they take
|
|
39
|
+
* name + --tag/--version); `name vN` is this CLI's identifier format (the
|
|
40
|
+
* `definition show` title). */
|
|
41
|
+
export declare function definitionRow(r: DefinitionListRow): {
|
|
42
|
+
name: string;
|
|
43
|
+
version: number;
|
|
44
|
+
title: string;
|
|
45
|
+
tag: string;
|
|
46
|
+
stageCount: number;
|
|
47
|
+
inFlightCount: number;
|
|
48
|
+
totalInstances: number;
|
|
49
|
+
_createdAt: string;
|
|
50
|
+
};
|
|
35
51
|
export {};
|
|
@@ -3,8 +3,8 @@ import { WORKFLOW_DEFINITION_TYPE, WORKFLOW_INSTANCE_TYPE, assertReadableModel,
|
|
|
3
3
|
import logSymbols from 'log-symbols';
|
|
4
4
|
import { WorkflowCommand } from "../../../lib/base-command.js";
|
|
5
5
|
import { resolveReadTargets } from "../../../lib/context.js";
|
|
6
|
-
import { tagFlags } from "../../../lib/flags.js";
|
|
7
|
-
import { runReadAcrossTargets } from "../../../lib/read-fanout.js";
|
|
6
|
+
import { jsonFlags, tagFlags } from "../../../lib/flags.js";
|
|
7
|
+
import { logJsonListing, runReadAcrossTargets } from "../../../lib/read-fanout.js";
|
|
8
8
|
import { logClippedTable } from "../../../lib/ui.js";
|
|
9
9
|
export function buildDefinitionListQuery(flags) {
|
|
10
10
|
const params = { limit: flags.limit + 1 };
|
|
@@ -45,6 +45,7 @@ export default class DefinitionList extends WorkflowCommand {
|
|
|
45
45
|
static examples = [
|
|
46
46
|
'<%= config.bin %> definition list',
|
|
47
47
|
'<%= config.bin %> definition list --tag prod',
|
|
48
|
+
'<%= config.bin %> definition list --json',
|
|
48
49
|
];
|
|
49
50
|
static flags = {
|
|
50
51
|
...tagFlags,
|
|
@@ -55,35 +56,48 @@ export default class DefinitionList extends WorkflowCommand {
|
|
|
55
56
|
name: Flags.string({
|
|
56
57
|
description: 'Filter to a single workflow definition name (e.g. product-launch).',
|
|
57
58
|
}),
|
|
59
|
+
...jsonFlags,
|
|
58
60
|
};
|
|
59
61
|
async run() {
|
|
60
62
|
const { flags } = await this.parse(DefinitionList);
|
|
61
63
|
const targets = await resolveReadTargets(flags);
|
|
62
64
|
const { groq, params } = buildDefinitionListQuery(flags);
|
|
65
|
+
if (flags.json) {
|
|
66
|
+
await logJsonListing({
|
|
67
|
+
targets,
|
|
68
|
+
key: 'definitions',
|
|
69
|
+
limit: flags.limit,
|
|
70
|
+
log: (line) => this.log(line),
|
|
71
|
+
fetch: ({ client }) => fetchDefinitionRows({ client, groq, params, limit: flags.limit }),
|
|
72
|
+
toRow: definitionRow,
|
|
73
|
+
});
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
63
76
|
const failures = await runReadAcrossTargets({
|
|
64
77
|
targets,
|
|
65
78
|
log: (line) => this.log(line),
|
|
66
79
|
run: async ({ client }) => {
|
|
67
|
-
const fetched =
|
|
68
|
-
|
|
69
|
-
})).map(assertReadableModel);
|
|
70
|
-
if (!fetched.length) {
|
|
80
|
+
const fetched = await fetchDefinitionRows({ client, groq, params, limit: flags.limit });
|
|
81
|
+
if (!fetched.rows.length) {
|
|
71
82
|
this.log(`${logSymbols.info} no definitions found`);
|
|
72
83
|
return;
|
|
73
84
|
}
|
|
74
85
|
logClippedTable({
|
|
75
|
-
rows: fetched,
|
|
86
|
+
rows: fetched.rows,
|
|
76
87
|
limit: flags.limit,
|
|
77
88
|
headers: ['workflow', 'title', 'tag', 'stages', 'in flight', 'instances', 'created'],
|
|
78
|
-
toCells: (r) =>
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
89
|
+
toCells: (r) => {
|
|
90
|
+
const row = definitionRow(r);
|
|
91
|
+
return [
|
|
92
|
+
`${row.name} v${row.version}`,
|
|
93
|
+
row.title,
|
|
94
|
+
row.tag,
|
|
95
|
+
String(row.stageCount),
|
|
96
|
+
String(row.inFlightCount),
|
|
97
|
+
String(row.totalInstances),
|
|
98
|
+
row._createdAt,
|
|
99
|
+
];
|
|
100
|
+
},
|
|
87
101
|
log: (line) => this.log(line),
|
|
88
102
|
});
|
|
89
103
|
},
|
|
@@ -93,3 +107,19 @@ export default class DefinitionList extends WorkflowCommand {
|
|
|
93
107
|
}
|
|
94
108
|
}
|
|
95
109
|
}
|
|
110
|
+
export function definitionRow(r) {
|
|
111
|
+
return {
|
|
112
|
+
name: r.name,
|
|
113
|
+
version: r.version,
|
|
114
|
+
title: r.title,
|
|
115
|
+
tag: r.tag,
|
|
116
|
+
stageCount: r.stageCount,
|
|
117
|
+
inFlightCount: r.inFlightCount,
|
|
118
|
+
totalInstances: r.totalInstances,
|
|
119
|
+
_createdAt: r._createdAt,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
async function fetchDefinitionRows({ client, groq, params, limit, }) {
|
|
123
|
+
const rows = await client.fetch(groq, params, { tag: 'definition.list' });
|
|
124
|
+
return { rows: rows.map(assertReadableModel), hasMore: rows.length > limit };
|
|
125
|
+
}
|
|
@@ -8,6 +8,7 @@ export default class DefinitionShow extends WorkflowCommand {
|
|
|
8
8
|
name: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
9
9
|
};
|
|
10
10
|
static flags: {
|
|
11
|
+
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
11
12
|
version: import("@oclif/core/interfaces").OptionFlag<number | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
13
|
tag: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
14
|
};
|
|
@@ -6,7 +6,7 @@ import { WorkflowCommand } from "../../../lib/base-command.js";
|
|
|
6
6
|
import { resolveReadTargets, soleHitOrFail } from "../../../lib/context.js";
|
|
7
7
|
import { buildDefinitionShowQuery, buildDefinitionTagsQuery } from "../../../lib/definitions.js";
|
|
8
8
|
import { fail } from "../../../lib/fail.js";
|
|
9
|
-
import { tagFlags } from "../../../lib/flags.js";
|
|
9
|
+
import { jsonFlags, tagFlags } from "../../../lib/flags.js";
|
|
10
10
|
import { formatKeyValue, resourceLabel, sectionHeader } from "../../../lib/ui.js";
|
|
11
11
|
export default class DefinitionShow extends WorkflowCommand {
|
|
12
12
|
static aliases = ['definition:show'];
|
|
@@ -17,6 +17,7 @@ export default class DefinitionShow extends WorkflowCommand {
|
|
|
17
17
|
static flags = {
|
|
18
18
|
...tagFlags,
|
|
19
19
|
version: Flags.integer({ description: 'Specific version (default: latest).' }),
|
|
20
|
+
...jsonFlags,
|
|
20
21
|
};
|
|
21
22
|
async run() {
|
|
22
23
|
const { args, flags } = await this.parse(DefinitionShow);
|
|
@@ -32,6 +33,10 @@ export default class DefinitionShow extends WorkflowCommand {
|
|
|
32
33
|
exit: 1,
|
|
33
34
|
});
|
|
34
35
|
}
|
|
36
|
+
if (flags.json) {
|
|
37
|
+
this.log(JSON.stringify(def, null, 2));
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
35
40
|
this.log(definitionHeader(def, def.tag));
|
|
36
41
|
this.log('');
|
|
37
42
|
for (const line of describeDefinition(def)) {
|
|
@@ -77,6 +77,24 @@ export declare function deploymentBanner({ deployment, index, total, }: {
|
|
|
77
77
|
index: number;
|
|
78
78
|
total: number;
|
|
79
79
|
}): string | undefined;
|
|
80
|
+
/** The deploy target as one label — the deployment's friendly name/tag
|
|
81
|
+
* ({@link deploymentLabel}) and the resource it writes to
|
|
82
|
+
* ({@link resourceLabel}) — so every progress and result line names where a
|
|
83
|
+
* deploy landed, including the common single-deployment run that prints no
|
|
84
|
+
* banner. */
|
|
85
|
+
export declare function targetLabel(deployment: WorkflowDeployment): string;
|
|
86
|
+
/** The deploy spinner's progress line — names the full target it writes to. */
|
|
87
|
+
export declare function deployStartLine(count: number, where: string): string;
|
|
88
|
+
/** The dry-run spinner's progress line — a diff reads the resource's deployed
|
|
89
|
+
* state, so it names the resource it compares against, not the deployment. */
|
|
90
|
+
export declare function dryRunStartLine(count: number, resource: string): string;
|
|
91
|
+
/** A completed deploy/dry-run result line: the past-tense verb, the count, and
|
|
92
|
+
* the full target, so a finished run always shows where it landed. */
|
|
93
|
+
export declare function resultLine({ verb, count, where, }: {
|
|
94
|
+
verb: 'Processed' | 'Diffed';
|
|
95
|
+
count: number;
|
|
96
|
+
where: string;
|
|
97
|
+
}): string;
|
|
80
98
|
/** One line per deployed definition, symbol-tagged by its result status,
|
|
81
99
|
* followed by any advisory effect-output lint warnings indented beneath it. */
|
|
82
100
|
export declare function deployResultLines(results: DeployDefinitionResult[]): string[];
|
|
@@ -13,7 +13,7 @@ import { loadWorkflowConfig } from "../../lib/load-config.js";
|
|
|
13
13
|
import { deploymentLabel, deploymentToTarget, selectDeployments, } from "../../lib/select-deployment.js";
|
|
14
14
|
import { shareDefinitionsAfterDeploy } from "../../lib/share-definitions.js";
|
|
15
15
|
import { cliTelemetry } from "../../lib/telemetry.js";
|
|
16
|
-
import { groupBanner } from "../../lib/ui.js";
|
|
16
|
+
import { groupBanner, resourceLabel } from "../../lib/ui.js";
|
|
17
17
|
export default class Deploy extends WorkflowCommand {
|
|
18
18
|
static aliases = ['deploy'];
|
|
19
19
|
static description = 'Validate, diff, and deploy workflow definitions to the resource bound by the selected deployment.';
|
|
@@ -110,14 +110,16 @@ export default class Deploy extends WorkflowCommand {
|
|
|
110
110
|
}
|
|
111
111
|
async processDeployment({ deployment, defs, dryRun, client, }) {
|
|
112
112
|
const target = deploymentToTarget(deployment);
|
|
113
|
+
const where = targetLabel(deployment);
|
|
114
|
+
const resource = resourceLabel(deployment.workflowResource);
|
|
113
115
|
if (dryRun) {
|
|
114
|
-
await this.runDryRun({ client, defs, target });
|
|
116
|
+
await this.runDryRun({ client, defs, target, where, resource });
|
|
115
117
|
return undefined;
|
|
116
118
|
}
|
|
117
|
-
return this.executeDeploy({ client, defs, target });
|
|
119
|
+
return this.executeDeploy({ client, defs, target, where });
|
|
118
120
|
}
|
|
119
|
-
async executeDeploy({ client, defs, target, }) {
|
|
120
|
-
const spinner = ora(
|
|
121
|
+
async executeDeploy({ client, defs, target, where, }) {
|
|
122
|
+
const spinner = ora(deployStartLine(defs.length, where)).start();
|
|
121
123
|
try {
|
|
122
124
|
const deployed = await workflow.deployDefinitions({
|
|
123
125
|
...target,
|
|
@@ -125,7 +127,7 @@ export default class Deploy extends WorkflowCommand {
|
|
|
125
127
|
definitions: defs,
|
|
126
128
|
telemetry: cliTelemetry().logger,
|
|
127
129
|
});
|
|
128
|
-
spinner.succeed(
|
|
130
|
+
spinner.succeed(resultLine({ verb: 'Processed', count: deployed.results.length, where }));
|
|
129
131
|
for (const line of deployResultLines(deployed.results))
|
|
130
132
|
this.log(line);
|
|
131
133
|
return deployed;
|
|
@@ -135,11 +137,11 @@ export default class Deploy extends WorkflowCommand {
|
|
|
135
137
|
throw error;
|
|
136
138
|
}
|
|
137
139
|
}
|
|
138
|
-
async runDryRun({ client, defs, target, }) {
|
|
139
|
-
const spinner = ora(
|
|
140
|
+
async runDryRun({ client, defs, target, where, resource, }) {
|
|
141
|
+
const spinner = ora(dryRunStartLine(defs.length, resource)).start();
|
|
140
142
|
try {
|
|
141
143
|
const entries = await computeDiffEntries({ client, defs, target });
|
|
142
|
-
spinner.succeed(
|
|
144
|
+
spinner.succeed(resultLine({ verb: 'Diffed', count: defs.length, where }));
|
|
143
145
|
for (const line of diffReport(entries))
|
|
144
146
|
this.log(line);
|
|
145
147
|
}
|
|
@@ -194,6 +196,18 @@ export function deploySummary({ total, failures, }) {
|
|
|
194
196
|
export function deploymentBanner({ deployment, index, total, }) {
|
|
195
197
|
return groupBanner({ label: deploymentLabel(deployment), index, total });
|
|
196
198
|
}
|
|
199
|
+
export function targetLabel(deployment) {
|
|
200
|
+
return `${deploymentLabel(deployment)} → ${resourceLabel(deployment.workflowResource)}`;
|
|
201
|
+
}
|
|
202
|
+
export function deployStartLine(count, where) {
|
|
203
|
+
return `Deploying ${count} definition(s) to ${where}…`;
|
|
204
|
+
}
|
|
205
|
+
export function dryRunStartLine(count, resource) {
|
|
206
|
+
return `Diffing ${count} definition(s) against ${resource}…`;
|
|
207
|
+
}
|
|
208
|
+
export function resultLine({ verb, count, where, }) {
|
|
209
|
+
return `${verb} ${count} definition(s) · ${where}`;
|
|
210
|
+
}
|
|
197
211
|
export function deployResultLines(results) {
|
|
198
212
|
const symbols = {
|
|
199
213
|
created: logSymbols.success,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type TerminalState } from '@sanity/workflow-engine';
|
|
1
|
+
import { type GdrUri, type TerminalState } from '@sanity/workflow-engine';
|
|
2
2
|
import { WorkflowCommand } from '../../lib/base-command.ts';
|
|
3
3
|
interface InstanceRow {
|
|
4
4
|
_id: string;
|
|
@@ -19,17 +19,19 @@ interface ListFlags {
|
|
|
19
19
|
tag?: string | undefined;
|
|
20
20
|
limit: number;
|
|
21
21
|
}
|
|
22
|
+
interface ListQueryClient {
|
|
23
|
+
fetch<T>(groq: string, params: Record<string, unknown>, options: {
|
|
24
|
+
tag: string;
|
|
25
|
+
}): Promise<T>;
|
|
26
|
+
}
|
|
22
27
|
/**
|
|
23
28
|
* Build the GROQ query + params for `list` from its flags. Pure so the
|
|
24
29
|
* filter combinations can be asserted without a live dataset. `--tag` is an
|
|
25
30
|
* optional filter: omit it and the listing spans every partition.
|
|
26
31
|
*
|
|
27
|
-
* With `--document` the lake filter is the engine's
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
* documents (the narrowing reads fields/stages/ancestors) and UNCAPPED: a
|
|
31
|
-
* capped superset could silently hide matches. The display limit applies
|
|
32
|
-
* after narrowing.
|
|
32
|
+
* With `--document` the lake filter is the engine's exact watch-set arm. The
|
|
33
|
+
* projection keeps its ref-bearing fields for a defensive JS recheck while
|
|
34
|
+
* omitting the instance's unbounded ledgers and definition snapshot.
|
|
33
35
|
*/
|
|
34
36
|
export declare function buildListQuery(flags: ListFlags): {
|
|
35
37
|
groq: string;
|
|
@@ -49,6 +51,7 @@ export default class List extends WorkflowCommand {
|
|
|
49
51
|
static description: string;
|
|
50
52
|
static examples: string[];
|
|
51
53
|
static flags: {
|
|
54
|
+
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
52
55
|
'include-completed': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
53
56
|
failed: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
54
57
|
definition: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
@@ -58,4 +61,15 @@ export default class List extends WorkflowCommand {
|
|
|
58
61
|
};
|
|
59
62
|
run(): Promise<void>;
|
|
60
63
|
}
|
|
64
|
+
/** Fetch one target's projected rows, defensively rechecking document refs. */
|
|
65
|
+
export declare function fetchRows({ client, groq, params, document, limit, }: {
|
|
66
|
+
client: ListQueryClient;
|
|
67
|
+
groq: string;
|
|
68
|
+
params: Record<string, unknown>;
|
|
69
|
+
document: GdrUri | undefined;
|
|
70
|
+
limit: number;
|
|
71
|
+
}): Promise<{
|
|
72
|
+
rows: InstanceRow[];
|
|
73
|
+
hasMore: boolean;
|
|
74
|
+
}>;
|
|
61
75
|
export {};
|
|
@@ -4,9 +4,26 @@ import logSymbols from 'log-symbols';
|
|
|
4
4
|
import { WorkflowCommand } from "../../lib/base-command.js";
|
|
5
5
|
import { resolveReadTargets } from "../../lib/context.js";
|
|
6
6
|
import { failOnThrow } from "../../lib/fail.js";
|
|
7
|
-
import { tagFlags } from "../../lib/flags.js";
|
|
8
|
-
import { runReadAcrossTargets } from "../../lib/read-fanout.js";
|
|
7
|
+
import { jsonFlags, tagFlags } from "../../lib/flags.js";
|
|
8
|
+
import { logJsonListing, runReadAcrossTargets } from "../../lib/read-fanout.js";
|
|
9
9
|
import { formatAge, logClippedTable } from "../../lib/ui.js";
|
|
10
|
+
const DOCUMENT_LIST_PROJECTION = `{
|
|
11
|
+
_type,
|
|
12
|
+
_id,
|
|
13
|
+
workflowResource,
|
|
14
|
+
modelVersion,
|
|
15
|
+
minReaderModel,
|
|
16
|
+
definition,
|
|
17
|
+
tag,
|
|
18
|
+
currentStage,
|
|
19
|
+
fields,
|
|
20
|
+
stages,
|
|
21
|
+
ancestors,
|
|
22
|
+
subworkflows,
|
|
23
|
+
completedAt,
|
|
24
|
+
abortedAt,
|
|
25
|
+
lastChangedAt
|
|
26
|
+
}`;
|
|
10
27
|
export function buildListQuery(flags) {
|
|
11
28
|
const filters = [`_type == "${WORKFLOW_INSTANCE_TYPE}"`];
|
|
12
29
|
const params = {};
|
|
@@ -26,7 +43,11 @@ export function buildListQuery(flags) {
|
|
|
26
43
|
const armParams = {};
|
|
27
44
|
filters.push(`(${documentPrefilter([flags.document], armParams)})`);
|
|
28
45
|
Object.assign(params, armParams);
|
|
29
|
-
|
|
46
|
+
params['limit'] = flags.limit + 1;
|
|
47
|
+
return {
|
|
48
|
+
groq: `*[${filters.join(' && ')}] | order(lastChangedAt desc)[0...$limit]${DOCUMENT_LIST_PROJECTION}`,
|
|
49
|
+
params,
|
|
50
|
+
};
|
|
30
51
|
}
|
|
31
52
|
params['limit'] = flags.limit + 1;
|
|
32
53
|
const groq = `*[${filters.join(' && ')}] | order(lastChangedAt desc) [0...$limit]{
|
|
@@ -64,6 +85,7 @@ export default class List extends WorkflowCommand {
|
|
|
64
85
|
'<%= config.bin %> list --definition productLaunch',
|
|
65
86
|
'<%= config.bin %> list --document dataset:proj:ds:article-1',
|
|
66
87
|
'<%= config.bin %> list --tag prod',
|
|
88
|
+
'<%= config.bin %> list --json',
|
|
67
89
|
];
|
|
68
90
|
static flags = {
|
|
69
91
|
...tagFlags,
|
|
@@ -86,24 +108,42 @@ export default class List extends WorkflowCommand {
|
|
|
86
108
|
description: 'Maximum rows to return.',
|
|
87
109
|
default: 50,
|
|
88
110
|
}),
|
|
111
|
+
...jsonFlags,
|
|
89
112
|
};
|
|
90
113
|
async run() {
|
|
91
114
|
const { flags } = await this.parse(List);
|
|
92
115
|
const targets = await resolveReadTargets(flags);
|
|
93
116
|
const { groq, params } = failOnThrow('Invalid --document:', () => buildListQuery(flags));
|
|
94
117
|
const document = flags.document;
|
|
118
|
+
if (flags.json) {
|
|
119
|
+
await logJsonListing({
|
|
120
|
+
targets,
|
|
121
|
+
key: 'instances',
|
|
122
|
+
limit: flags.limit,
|
|
123
|
+
log: (line) => this.log(line),
|
|
124
|
+
fetch: ({ client }) => fetchRows({ client, groq, params, document, limit: flags.limit }),
|
|
125
|
+
toRow: instanceRow,
|
|
126
|
+
});
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
95
129
|
const failures = await runReadAcrossTargets({
|
|
96
130
|
targets,
|
|
97
131
|
log: (line) => this.log(line),
|
|
98
132
|
run: async ({ client }) => {
|
|
99
|
-
const fetched = await fetchRows({ client, groq, params, document });
|
|
100
|
-
if (fetched.length === 0) {
|
|
101
|
-
|
|
133
|
+
const fetched = await fetchRows({ client, groq, params, document, limit: flags.limit });
|
|
134
|
+
if (fetched.rows.length === 0) {
|
|
135
|
+
if (fetched.hasMore) {
|
|
136
|
+
this.log(`${logSymbols.info} no matches in the first ${flags.limit} candidates — raise --limit to search further`);
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
this.log(`${logSymbols.info} no instances match`);
|
|
140
|
+
}
|
|
102
141
|
return;
|
|
103
142
|
}
|
|
104
143
|
logClippedTable({
|
|
105
|
-
rows: fetched,
|
|
144
|
+
rows: fetched.rows,
|
|
106
145
|
limit: flags.limit,
|
|
146
|
+
moreAvailable: fetched.hasMore,
|
|
107
147
|
headers: ['instance', 'workflow', 'tag', 'stage', 'status', 'updated'],
|
|
108
148
|
toCells: (r) => {
|
|
109
149
|
const row = instanceRow(r);
|
|
@@ -125,13 +165,16 @@ export default class List extends WorkflowCommand {
|
|
|
125
165
|
}
|
|
126
166
|
}
|
|
127
167
|
}
|
|
128
|
-
async function fetchRows({ client, groq, params, document, }) {
|
|
168
|
+
export async function fetchRows({ client, groq, params, document, limit, }) {
|
|
129
169
|
if (document === undefined) {
|
|
130
170
|
const rows = await client.fetch(groq, params, { tag: 'list' });
|
|
131
|
-
return rows.map(assertReadableModel);
|
|
171
|
+
return { rows: rows.map(assertReadableModel), hasMore: rows.length > limit };
|
|
132
172
|
}
|
|
133
173
|
const candidates = await client.fetch(groq, params, { tag: 'list' });
|
|
134
|
-
return
|
|
135
|
-
|
|
136
|
-
|
|
174
|
+
return {
|
|
175
|
+
rows: candidates
|
|
176
|
+
.map(assertReadableModel)
|
|
177
|
+
.filter((instance) => instanceWatchesDocument(instance, document)),
|
|
178
|
+
hasMore: candidates.length > limit,
|
|
179
|
+
};
|
|
137
180
|
}
|
|
@@ -1,10 +1,32 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import { type ResetActivityTarget, type WorkflowInstance } from '@sanity/workflow-engine';
|
|
2
|
+
import { WorkflowCommand } from '../../lib/base-command.ts';
|
|
3
|
+
import { type WriteOutcome, type WriteReport } from '../../lib/ops-report.ts';
|
|
4
|
+
export default class ResetActivity extends WorkflowCommand {
|
|
5
|
+
static aliases: string[];
|
|
5
6
|
static description: string;
|
|
7
|
+
static examples: string[];
|
|
6
8
|
static args: {
|
|
7
9
|
instanceId: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
8
10
|
activity: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
9
11
|
};
|
|
12
|
+
static flags: {
|
|
13
|
+
skip: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
14
|
+
deployment: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
15
|
+
tag: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
16
|
+
};
|
|
17
|
+
run(): Promise<void>;
|
|
18
|
+
}
|
|
19
|
+
/** {@link WriteOutcome} with the terminal stamp the no-op copy reads. */
|
|
20
|
+
interface ResetActivityOutcome extends WriteOutcome {
|
|
21
|
+
instance: Pick<WorkflowInstance, 'currentStage' | 'completedAt'>;
|
|
10
22
|
}
|
|
23
|
+
/**
|
|
24
|
+
* Turn a `resetActivity` result into the spinner message. Pure so the
|
|
25
|
+
* skipped / re-run / already-at-target / terminal / cascaded permutations can
|
|
26
|
+
* be asserted without driving a real reset.
|
|
27
|
+
*/
|
|
28
|
+
export declare function resetActivityReport(result: ResetActivityOutcome, { activity, to }: {
|
|
29
|
+
activity: string;
|
|
30
|
+
to: ResetActivityTarget;
|
|
31
|
+
}): WriteReport;
|
|
32
|
+
export {};
|
|
@@ -1,11 +1,61 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
import { styleText } from 'node:util';
|
|
2
|
+
import { Args, Flags } from '@oclif/core';
|
|
3
|
+
import { workflow } from '@sanity/workflow-engine';
|
|
4
|
+
import { WorkflowCommand } from "../../lib/base-command.js";
|
|
5
|
+
import { resolveInstanceContext } from "../../lib/context.js";
|
|
6
|
+
import { instanceFlags } from "../../lib/flags.js";
|
|
7
|
+
import { buildOperationArgs } from "../../lib/operation-args.js";
|
|
8
|
+
import { cascadeTail, runWriteVerb, } from "../../lib/ops-report.js";
|
|
9
|
+
export default class ResetActivity extends WorkflowCommand {
|
|
10
|
+
static aliases = ['reset-activity'];
|
|
11
|
+
static description = 'Reset a failed activity on an in-flight instance — back to active to re-run it, or --skip to bypass it (mark it skipped) so a gated exit transition can fire.';
|
|
12
|
+
static examples = [
|
|
13
|
+
'<%= config.bin %> reset-activity wf-instance.abc123 legal-review',
|
|
14
|
+
'<%= config.bin %> reset-activity wf-instance.abc123 legal-review --skip',
|
|
15
|
+
];
|
|
7
16
|
static args = {
|
|
8
17
|
instanceId: Args.string({ required: true, description: 'Workflow instance id.' }),
|
|
9
18
|
activity: Args.string({ required: true, description: 'Activity name within the current stage.' }),
|
|
10
19
|
};
|
|
20
|
+
static flags = {
|
|
21
|
+
...instanceFlags,
|
|
22
|
+
skip: Flags.boolean({
|
|
23
|
+
default: false,
|
|
24
|
+
description: 'Bypass the activity (mark it skipped) instead of re-running it (back to active).',
|
|
25
|
+
}),
|
|
26
|
+
};
|
|
27
|
+
async run() {
|
|
28
|
+
const { args, flags } = await this.parse(ResetActivity);
|
|
29
|
+
const { client, scope } = await resolveInstanceContext(flags, args.instanceId);
|
|
30
|
+
const to = flags.skip ? 'skipped' : 'active';
|
|
31
|
+
await runWriteVerb({
|
|
32
|
+
startLabel: `Resetting ${args.activity} on ${args.instanceId}…`,
|
|
33
|
+
failLabel: 'Reset rejected',
|
|
34
|
+
failHeadline: 'reset-activity error:',
|
|
35
|
+
run: () => workflow.resetActivity({
|
|
36
|
+
client,
|
|
37
|
+
...buildOperationArgs({ scope, instanceId: args.instanceId, reason: undefined }),
|
|
38
|
+
activity: args.activity,
|
|
39
|
+
to,
|
|
40
|
+
}),
|
|
41
|
+
report: (result) => resetActivityReport(result, { activity: args.activity, to }),
|
|
42
|
+
log: (line) => this.log(line),
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
export function resetActivityReport(result, { activity, to }) {
|
|
47
|
+
const name = styleText('bold', activity);
|
|
48
|
+
if (!result.changed) {
|
|
49
|
+
const { completedAt } = result.instance;
|
|
50
|
+
const message = completedAt !== undefined
|
|
51
|
+
? `reset-activity changed nothing — instance is terminal (since ${completedAt})`
|
|
52
|
+
: `reset-activity changed nothing — ${name} is already ${to}`;
|
|
53
|
+
return { changed: false, message };
|
|
54
|
+
}
|
|
55
|
+
const stage = styleText('bold', result.instance.currentStage);
|
|
56
|
+
const verb = to === 'skipped' ? 'Skipped' : 'Reset';
|
|
57
|
+
return {
|
|
58
|
+
changed: true,
|
|
59
|
+
message: `${verb} ${name} — now at ${stage}${cascadeTail(result.cascaded)}`,
|
|
60
|
+
};
|
|
11
61
|
}
|
|
@@ -8,6 +8,7 @@ export default class Show extends WorkflowCommand {
|
|
|
8
8
|
instanceId: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
9
9
|
};
|
|
10
10
|
static flags: {
|
|
11
|
+
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
11
12
|
include: import("@oclif/core/interfaces").OptionFlag<string[], import("@oclif/core/interfaces").CustomOptions>;
|
|
12
13
|
tag: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
14
|
};
|