@sanity/workflow-cli 0.10.0 → 0.12.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 +61 -0
- package/README.md +57 -39
- package/dist/commands/definition/list.d.ts +3 -0
- package/dist/commands/definition/list.js +7 -4
- package/dist/commands/definition/show.d.ts +13 -0
- package/dist/commands/definition/show.js +25 -7
- package/dist/commands/deploy.d.ts +1 -1
- package/dist/commands/deploy.js +4 -4
- package/dist/commands/diagnose.d.ts +1 -1
- package/dist/commands/diagnose.js +23 -13
- package/dist/commands/fire-action.js +13 -2
- package/dist/commands/list.d.ts +13 -2
- package/dist/commands/list.js +43 -17
- package/dist/commands/nuke.d.ts +11 -0
- package/dist/commands/nuke.js +76 -0
- package/dist/commands/start.d.ts +17 -23
- package/dist/commands/start.js +49 -33
- package/dist/commands/tail.js +19 -15
- package/dist/hooks/prerun/telemetry.d.ts +4 -1
- package/dist/hooks/prerun/telemetry.js +6 -2
- 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.d.ts +10 -0
- package/dist/lib/definitions.js +8 -2
- package/dist/lib/fail.d.ts +7 -0
- package/dist/lib/fail.js +2 -1
- 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 +10 -0
- package/dist/lib/prompt.js +4 -0
- package/dist/lib/share-definitions.d.ts +59 -25
- package/dist/lib/share-definitions.js +104 -21
- package/dist/lib/telemetry-setup.d.ts +4 -0
- package/dist/lib/telemetry-setup.js +18 -11
- package/dist/lib/telemetry.d.ts +33 -2
- package/dist/lib/telemetry.js +9 -4
- package/dist/lib/ui.js +0 -1
- package/oclif.manifest.json +66 -11
- package/package.json +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,66 @@
|
|
|
1
1
|
# @sanity/workflow-cli
|
|
2
2
|
|
|
3
|
+
## 0.12.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- e3a7ba2: `start` gains `--instance-id` — for retries: the id is the start's idempotency key, so passing the id of a start that failed partway resumes it instead of creating a duplicate (an already-settled start replays as a no-op). A start whose first auto-advance fails after the instance was created and primed (`StartNotSettledError`) now reports as STARTED with the cause and the retry id — a warn, not the "Start rejected" failure path — and the `--json` payload carries `{instanceId, notSettled: true, detail}`. A start that fails before priming (`StartNotPrimedError`) still fails, with the `--instance-id` retry hint appended so the operator can resume instead of duplicating.
|
|
8
|
+
- a8ace4d: New `nuke --tag <tag>` command — the dev-period big red button that deletes every engine-owned document for a deployment tag: instances, definitions, and guard docs (matched by instance-id prefix, so guards orphaned by earlier partial deletions are swept too) across the deployment's workflow resource and every alias-bound resource. Content documents are never touched. The command always prints a per-dataset dry-run plan, then requires typing back every involved `project.dataset` target; `--force` skips the prompt for scripts/CI (the plan still prints). Unlike `definition delete` (the governed lifecycle path), `nuke` deletes raw docs without reading them, so it works on pre-amendment shapes a newer engine can no longer read — the sanctioned reset while the versioned upgrade framework doesn't exist.
|
|
9
|
+
- 5a1a9fe: Direct reads of engine-owned documents adopt the data-model gate (`assertReadableModel`): the CLI's instance and definition reads (`show`, `tail`, `list`, `definition show`, `definition list`, deploy lookups, the share read-back) and the MCP tools that interpret rows from the ungated `engine.query` escape hatch (`list_workflow_definitions`, `list_workflow_instances`, the definition lookup behind `start_workflow` / `get_workflow_definition`). A document written by a newer engine data model now fails with the governed "upgrade `@sanity/workflow-engine`" error instead of rendering unchecked; list projections carry the stamp pair so projected rows gate too.
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- 092a0d4: **BREAKING:** runtime-supplied refs written into field state are gated on the deployment's declared resource surface.
|
|
14
|
+
- A `doc.ref` / `doc.refs` / `release.ref` value a caller or effect handler supplies — start `initialFields` input, an action's param-sourced op values, `editField` values, effect-completion ops — must target a declared resource: the `workflowResource` itself or a resource the `resourceClients` resolver serves. An off-surface ref throws the new `RefResourceUndeclaredError` and the commit aborts, instead of writing cleanly and failing later when something dereferences it. The check is advisory like every engine check; stored documents are unchanged.
|
|
15
|
+
- Definition-authored refs are exempt: literal `initialValue` seeds, `type: 'query'` texts, spawn `with` projections, and op values that bind no action param were validated (and alias-expanded) at deploy, so they materialize into field state without re-checking.
|
|
16
|
+
- Resource aliases stay a deploy-time abstraction: `deployDefinitions({definitions, resourceAliases?})` expands every `@<alias>:` reference to its physical GDR before fingerprinting, and nothing alias-shaped survives past deploy — the runtime holds no alias map, and an alias-shaped ref supplied to a runtime verb is rejected as malformed like any other non-GDR string.
|
|
17
|
+
- `createBench` takes `serveResources` (dataset siblings served — and thereby declared — straight from the bench's own store) plus a raw `resourceClients` resolver for anything beyond same-store siblings. The engine also exports `sameResource` alongside the existing GDR helpers.
|
|
18
|
+
- The CLI wires no `resourceClients`, so refs supplied at runtime through it (`start --field`, `fire-action` ref params) admit the workflow resource only.
|
|
19
|
+
|
|
20
|
+
- Updated dependencies [f9389e5]
|
|
21
|
+
- Updated dependencies [d0c62ea]
|
|
22
|
+
- Updated dependencies [c3eed2e]
|
|
23
|
+
- Updated dependencies [df4bd80]
|
|
24
|
+
- Updated dependencies [092a0d4]
|
|
25
|
+
- Updated dependencies [5a1a9fe]
|
|
26
|
+
- Updated dependencies [e3a7ba2]
|
|
27
|
+
- Updated dependencies [30fed9e]
|
|
28
|
+
- Updated dependencies [1321ba5]
|
|
29
|
+
- Updated dependencies [e683875]
|
|
30
|
+
- Updated dependencies [a8ace4d]
|
|
31
|
+
- @sanity/workflow-engine@0.17.0
|
|
32
|
+
|
|
33
|
+
## 0.11.0
|
|
34
|
+
|
|
35
|
+
### Minor Changes
|
|
36
|
+
|
|
37
|
+
- e5f5b77: **BREAKING:** rendering and the `--json` diagnose payload follow the engine's actions-only payload model.
|
|
38
|
+
- `fire-action`'s list mode renders a cascade-fired action as automation (`⚡ … [automation]` with the engine's canonical cascade-fired detail), never as a fireable command; other disabled reasons keep the shared `actionDisabledDetail` rendering.
|
|
39
|
+
- `diagnose` renders transitions by their `when` trigger (`whenSatisfied` replaces `filterSatisfied` in the `--json` transitions payload), narrates a waiting activity by its caller-fireable actions only (an all-automation activity reads "waiting on automation"), and words failed-effect causes by their action origin.
|
|
40
|
+
- The activity status glyphs drop `pending` — activities are active from stage entry.
|
|
41
|
+
|
|
42
|
+
- 7510fd5: **BREAKING:** `list` speaks the engine's filter vocabulary: the `--in-flight` flag (default: list everything) is replaced by `--include-completed` (default: in-flight only) — the same name, polarity, and default the engine and MCP list surfaces use. `list` also gains `--document <gdr-uri>` to list the instances that reference a document.
|
|
43
|
+
|
|
44
|
+
Fixes: expected failures (rejected write verbs, validation errors, the 401 arm) now exit by throwing through the oclif lifecycle instead of a raw `process.exit`, so the telemetry `finally` hook still completes the command trace and flushes batched events; `tail` serializes its notification handling (a burst of commits could double-print history entries and regress the high-water mark); and an untagged `definition show` whose name exists in several tag partitions of one dataset now errors listing the partitions — it previously picked the highest version across partitions, which could show a prod operator the dev definition.
|
|
45
|
+
|
|
46
|
+
- 6e7f42d: **BREAKING:** Definition sharing on `deploy` flips from opt-in to opt-out, and the flag is renamed. `--share-definitions-with-sanity` becomes the negatable `--share-defs` / `--no-share-defs`. A deploy that creates new definition versions now donates them by default; `--no-share-defs` opts out.
|
|
47
|
+
|
|
48
|
+
With neither flag, an interactive terminal is asked **once** — a blocking acknowledgement whose answer persists in the Sanity user config, so it never repeats — before anything is sent, since notice must precede the send. Every later flagless deploy prints a one-line reminder of the current state and the flag to change it, so an ongoing donation is never silent. Unattended runs (CI, `DO_NOT_TRACK`, non-TTY) can't prompt and donate nothing by default; opt in per run with `--share-defs`.
|
|
49
|
+
|
|
50
|
+
An explicit `--share-defs` also sends that deploy's telemetry regardless of CI / `DO_NOT_TRACK` (a command-line arg beats env vars; account-level `sanity telemetry disable` still applies). Telemetry gains a content-free `Editorial Workflows Definition Sharing Decided` event per invocation — how the decision was reached and what it was (`opt-in` / `opt-out` / `prompt-accepted` / `prompt-declined` / `remembered-share` / `remembered-decline` / `unattended`), whether anything reached the endpoint, and how many versions were eligible — so adoption dashboards can separate explicit opt-outs, prompt declines, and unattended runs and measure the true opt-out rate. The per-definition `Editorial Workflows Definition Shared` marker and the one-POST feedback transport are unchanged.
|
|
51
|
+
|
|
52
|
+
### Patch Changes
|
|
53
|
+
|
|
54
|
+
- Updated dependencies [11e2d6f]
|
|
55
|
+
- Updated dependencies [7510fd5]
|
|
56
|
+
- Updated dependencies [01e8042]
|
|
57
|
+
- Updated dependencies [79c54db]
|
|
58
|
+
- Updated dependencies [6a46db1]
|
|
59
|
+
- Updated dependencies [35b9b85]
|
|
60
|
+
- Updated dependencies [e5f5b77]
|
|
61
|
+
- Updated dependencies [01e8042]
|
|
62
|
+
- @sanity/workflow-engine@0.16.0
|
|
63
|
+
|
|
3
64
|
## 0.10.0
|
|
4
65
|
|
|
5
66
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@ pnpm --filter @sanity/workflow-cli dev <command> [...args]
|
|
|
16
16
|
# Examples:
|
|
17
17
|
pnpm --filter @sanity/workflow-cli dev --help
|
|
18
18
|
pnpm --filter @sanity/workflow-cli dev deploy --dry-run
|
|
19
|
-
pnpm --filter @sanity/workflow-cli dev list --
|
|
19
|
+
pnpm --filter @sanity/workflow-cli dev list --include-completed
|
|
20
20
|
pnpm --filter @sanity/workflow-cli dev show wf-instance.abc123
|
|
21
21
|
```
|
|
22
22
|
|
|
@@ -74,11 +74,20 @@ config fails with a clean, path-prefixed error before the command runs.
|
|
|
74
74
|
`resourceAliases` is only for content that lives in a _different_ resource from
|
|
75
75
|
`workflowResource`. A definition can reference a document in the **same**
|
|
76
76
|
resource by bare id, no alias needed — bare ids root at `workflowResource` at
|
|
77
|
-
runtime.
|
|
78
|
-
|
|
77
|
+
runtime. Bindings exist at deploy time only: `deploy` expands each `@<handle>:`
|
|
78
|
+
reference to its bound physical GDR, and nothing alias-shaped survives into the
|
|
79
|
+
deployed definition or the runtime. Rule of thumb: **different resource →
|
|
80
|
+
bind an alias; same resource → use a bare id and omit `resourceAliases`
|
|
79
81
|
entirely.** So the simplest single-dataset setup is just a `workflowResource`
|
|
80
82
|
and `definitions`, with no `resourceAliases` at all.
|
|
81
83
|
|
|
84
|
+
Refs supplied at runtime (`start --field`, `fire-action` ref params) are gated
|
|
85
|
+
separately: they may target the `workflowResource` itself, and anything else is
|
|
86
|
+
rejected at the write with `RefResourceUndeclaredError`. The CLI wires no
|
|
87
|
+
`resourceClients` resolver, so it has no way to widen that surface — a runtime
|
|
88
|
+
foreign-resource ref goes through an engine consumer that serves the target
|
|
89
|
+
resource, not through the CLI.
|
|
90
|
+
|
|
82
91
|
| Var | Purpose |
|
|
83
92
|
| ------------------- | ---------------------------------------------------------------------------------------------------- |
|
|
84
93
|
| `SANITY_AUTH_TOKEN` | Explicit token for CI / scripted use — wins over the `sanity login` session (editor role for writes) |
|
|
@@ -118,25 +127,26 @@ stale compiled output.
|
|
|
118
127
|
|
|
119
128
|
## Command status
|
|
120
129
|
|
|
121
|
-
| Command | Status
|
|
122
|
-
| -------------------------------------- |
|
|
123
|
-
| `deploy` | wired — calls `workflow.deployDefinitions` over the selected deployment's definitions
|
|
124
|
-
| `deploy --all-tags` | wired — deploys every deployment in the config, continuing past per-deployment failures
|
|
125
|
-
| `deploy --check` | wired — runs `validateDefinition` over the local batch + a duplicate-name check
|
|
126
|
-
| `deploy --dry-run` | wired — fetches existing docs and renders a coloured JSON diff per change
|
|
127
|
-
| `deploy --only <name>` | wired — filters deploy/check/dry-run to one definition by `name`
|
|
128
|
-
| `start <name>` | wired — calls `workflow.startInstance` (`--field` for input fields)
|
|
129
|
-
| `list` | wired — `client.fetch` over `sanity.workflow.instance` documents (`--definition <name>` to filter)
|
|
130
|
-
| `show <instance-id>` | wired — `client.getDocument`
|
|
131
|
-
| `diagnose <instance-id>` | wired — calls `workflow.diagnose`, classifies why the instance is/isn't progressing
|
|
132
|
-
| `tail <instance-id>` | wired — `client.listen()` over the instance, prints new history entries
|
|
133
|
-
| `abort <instance-id>` | wired — calls `workflow.abortInstance` (hard stop: cancels pending effects, lifts guards)
|
|
134
|
-
| `set-stage <instance-id> --to <stage>` | wired — calls `workflow.setStage` (admin override: skips declared transitions/filters; enter lifecycle + cascade still run)
|
|
135
|
-
| `fire-action <instance-id>` | wired — `workflow.availableActions` lists actions; `workflow.fireAction` fires one
|
|
136
|
-
| `definition list` | wired — `client.fetch` over `sanity.workflow.definition` documents
|
|
137
|
-
| `definition show <name>` | wired — `client.fetch`, latest version unless `--version`
|
|
138
|
-
| `definition diff <name>` | wired — diffs the in-code definition against the deployed latest (`--version` to pin)
|
|
139
|
-
| `definition delete <name>` | wired — calls `workflow.deleteDefinition` (refuses on live instances unless `--cascade`)
|
|
130
|
+
| Command | Status |
|
|
131
|
+
| -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
132
|
+
| `deploy` | wired — calls `workflow.deployDefinitions` over the selected deployment's definitions |
|
|
133
|
+
| `deploy --all-tags` | wired — deploys every deployment in the config, continuing past per-deployment failures |
|
|
134
|
+
| `deploy --check` | wired — runs `validateDefinition` over the local batch + a duplicate-name check |
|
|
135
|
+
| `deploy --dry-run` | wired — fetches existing docs and renders a coloured JSON diff per change |
|
|
136
|
+
| `deploy --only <name>` | wired — filters deploy/check/dry-run to one definition by `name` |
|
|
137
|
+
| `start <name>` | wired — calls `workflow.startInstance` (`--field` for input fields) |
|
|
138
|
+
| `list` | wired — `client.fetch` over `sanity.workflow.instance` documents (`--definition <name>` to filter) |
|
|
139
|
+
| `show <instance-id>` | wired — `client.getDocument` |
|
|
140
|
+
| `diagnose <instance-id>` | wired — calls `workflow.diagnose`, classifies why the instance is/isn't progressing |
|
|
141
|
+
| `tail <instance-id>` | wired — `client.listen()` over the instance, prints new history entries |
|
|
142
|
+
| `abort <instance-id>` | wired — calls `workflow.abortInstance` (hard stop: cancels pending effects, lifts guards) |
|
|
143
|
+
| `set-stage <instance-id> --to <stage>` | wired — calls `workflow.setStage` (admin override: skips declared transitions/filters; enter lifecycle + cascade still run) |
|
|
144
|
+
| `fire-action <instance-id>` | wired — `workflow.availableActions` lists actions; `workflow.fireAction` fires one |
|
|
145
|
+
| `definition list` | wired — `client.fetch` over `sanity.workflow.definition` documents |
|
|
146
|
+
| `definition show <name>` | wired — `client.fetch`, latest version unless `--version` |
|
|
147
|
+
| `definition diff <name>` | wired — diffs the in-code definition against the deployed latest (`--version` to pin) |
|
|
148
|
+
| `definition delete <name>` | wired — calls `workflow.deleteDefinition` (refuses on live instances unless `--cascade`) |
|
|
149
|
+
| `nuke --tag <tag>` | wired — dev-period reset (exists only until the versioned upgrade framework): deletes every engine-owned doc for the tag (instances, definitions, guards across resources); plan + typed confirm |
|
|
140
150
|
|
|
141
151
|
## Telemetry
|
|
142
152
|
|
|
@@ -145,9 +155,10 @@ per-command trace (`Editorial Workflows CLI Command Executed` — the command id
|
|
|
145
155
|
names of declared flags used, never their values, and a success flag) plus
|
|
146
156
|
the engine's adoption events from the operations it drives. Consent is the
|
|
147
157
|
account-wide status managed by `npx sanity telemetry enable|disable|status`;
|
|
148
|
-
CI and trueish `DO_NOT_TRACK` suppress everything
|
|
149
|
-
|
|
150
|
-
|
|
158
|
+
CI and trueish `DO_NOT_TRACK` suppress everything (except a `deploy --share-defs`,
|
|
159
|
+
which forces that deploy's telemetry — see [Definition sharing](#definition-sharing)),
|
|
160
|
+
and a session that isn't logged in sends nothing. A one-time notice on stderr
|
|
161
|
+
discloses collection on first use.
|
|
151
162
|
|
|
152
163
|
One extension point: supply your own logger as `telemetry` in
|
|
153
164
|
`sanity.workflow.ts` and the built-in pipeline is not constructed at all —
|
|
@@ -156,20 +167,27 @@ consent, suppression, and destination become its business.
|
|
|
156
167
|
|
|
157
168
|
## Definition sharing
|
|
158
169
|
|
|
159
|
-
Separate from telemetry, `deploy
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
Sharing is opt-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
170
|
+
Separate from telemetry, a `deploy` that creates new definition versions
|
|
171
|
+
donates those documents to Sanity — verbatim (structure, names, titles, GROQ
|
|
172
|
+
filters, effect configuration, seeded values), plus their deployment
|
|
173
|
+
coordinates (project and dataset, or resource id) — never content documents,
|
|
174
|
+
instances, or your auth token. The documents go to Sanity's first-party
|
|
175
|
+
definition-feedback endpoint, not the telemetry pipeline; telemetry carries
|
|
176
|
+
only a content-free marker (content hash plus structural counts) per shared
|
|
177
|
+
definition and a per-invocation decision event.
|
|
178
|
+
|
|
179
|
+
Sharing is **opt-out**. Pass `--share-defs` to donate without being asked, or
|
|
180
|
+
`--no-share-defs` to decline. With neither flag, an interactive terminal is
|
|
181
|
+
asked **once** — the answer is remembered in your Sanity user config, so the
|
|
182
|
+
prompt never repeats — before anything is sent; notice always precedes the
|
|
183
|
+
send. Every later flagless deploy then prints a one-line reminder of the
|
|
184
|
+
current state (sharing, or not) and the flag to change it, so an ongoing
|
|
185
|
+
donation is never silent. Unattended runs (CI, `DO_NOT_TRACK`, a non-TTY pipe)
|
|
186
|
+
can't prompt: by default they donate nothing (opt in per run with
|
|
187
|
+
`--share-defs`). An explicit `--share-defs` also sends that deploy's telemetry
|
|
188
|
+
regardless of CI / `DO_NOT_TRACK` — a command-line arg beats env vars;
|
|
189
|
+
account-level `sanity telemetry disable` still applies. A failed share only
|
|
190
|
+
warns, never failing a completed deploy.
|
|
173
191
|
|
|
174
192
|
## Known gaps
|
|
175
193
|
|
|
@@ -5,6 +5,7 @@ interface DefinitionListFlags {
|
|
|
5
5
|
limit: number;
|
|
6
6
|
}
|
|
7
7
|
export interface DefinitionListRow {
|
|
8
|
+
_id: string;
|
|
8
9
|
name: string;
|
|
9
10
|
version: number;
|
|
10
11
|
title: string;
|
|
@@ -13,6 +14,8 @@ export interface DefinitionListRow {
|
|
|
13
14
|
inFlightCount: number;
|
|
14
15
|
totalInstances: number;
|
|
15
16
|
_createdAt: string;
|
|
17
|
+
modelVersion?: number | null;
|
|
18
|
+
minReaderModel?: number | null;
|
|
16
19
|
}
|
|
17
20
|
export declare function buildDefinitionListQuery(flags: DefinitionListFlags): {
|
|
18
21
|
groq: string;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Flags } from '@oclif/core';
|
|
2
|
-
import { WORKFLOW_DEFINITION_TYPE, WORKFLOW_INSTANCE_TYPE, tagScopeFilter, } from '@sanity/workflow-engine';
|
|
2
|
+
import { WORKFLOW_DEFINITION_TYPE, WORKFLOW_INSTANCE_TYPE, assertReadableModel, inFlightFilter, tagScopeFilter, } from '@sanity/workflow-engine';
|
|
3
3
|
import logSymbols from 'log-symbols';
|
|
4
4
|
import { WorkflowCommand } from "../../lib/base-command.js";
|
|
5
5
|
import { resolveReadTargets } from "../../lib/context.js";
|
|
@@ -25,13 +25,16 @@ export function buildDefinitionListQuery(flags) {
|
|
|
25
25
|
}
|
|
26
26
|
const instanceMatch = instanceFilters.join(' && ');
|
|
27
27
|
const groq = `*[${filters.join(' && ')}] | order(name asc, version desc) [0...$limit]{
|
|
28
|
+
_id,
|
|
28
29
|
name,
|
|
29
30
|
version,
|
|
30
31
|
title,
|
|
31
32
|
tag,
|
|
32
33
|
_createdAt,
|
|
34
|
+
modelVersion,
|
|
35
|
+
minReaderModel,
|
|
33
36
|
"stageCount": count(stages),
|
|
34
|
-
"inFlightCount": count(*[${instanceMatch} &&
|
|
37
|
+
"inFlightCount": count(*[${instanceMatch} && ${inFlightFilter()}]),
|
|
35
38
|
"totalInstances": count(*[${instanceMatch}])
|
|
36
39
|
}`;
|
|
37
40
|
return { groq, params };
|
|
@@ -60,9 +63,9 @@ export default class DefinitionList extends WorkflowCommand {
|
|
|
60
63
|
targets,
|
|
61
64
|
log: (line) => this.log(line),
|
|
62
65
|
run: async ({ client }) => {
|
|
63
|
-
const fetched = await client.fetch(groq, params, {
|
|
66
|
+
const fetched = (await client.fetch(groq, params, {
|
|
64
67
|
tag: 'definition.list',
|
|
65
|
-
});
|
|
68
|
+
})).map(assertReadableModel);
|
|
66
69
|
if (!fetched.length) {
|
|
67
70
|
this.log(`${logSymbols.info} no definitions found`);
|
|
68
71
|
return;
|
|
@@ -12,6 +12,19 @@ export default class DefinitionShow extends WorkflowCommand {
|
|
|
12
12
|
};
|
|
13
13
|
run(): Promise<void>;
|
|
14
14
|
}
|
|
15
|
+
/**
|
|
16
|
+
* Resolve the one definition `show` names, refusing every ambiguity: an
|
|
17
|
+
* untagged name spanning several tag partitions within a dataset
|
|
18
|
+
* ({@link assertSingleTagPartition}) and a name held by several datasets
|
|
19
|
+
* ({@link soleHitOrFail}) both error, never pick. `undefined` means nothing
|
|
20
|
+
* is deployed under the name.
|
|
21
|
+
*/
|
|
22
|
+
export declare function resolveShownDefinition({ targets, name, tag, version, }: {
|
|
23
|
+
targets: ReadTarget[];
|
|
24
|
+
name: string;
|
|
25
|
+
tag: string | undefined;
|
|
26
|
+
version: number | undefined;
|
|
27
|
+
}): Promise<DeployedDefinition | undefined>;
|
|
15
28
|
/** How a lookup names the version it asked for: `vN` for a pinned
|
|
16
29
|
* `--version`, `latest` when unpinned. */
|
|
17
30
|
export declare function versionLabel(version: number | undefined): string;
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { styleText } from 'node:util';
|
|
2
2
|
import { Args, Flags } from '@oclif/core';
|
|
3
|
-
import { isTerminalStage, } from '@sanity/workflow-engine';
|
|
3
|
+
import { assertReadableModel, isTerminalStage, } from '@sanity/workflow-engine';
|
|
4
4
|
import logSymbols from 'log-symbols';
|
|
5
5
|
import { WorkflowCommand } from "../../lib/base-command.js";
|
|
6
6
|
import { resolveReadTargets, soleHitOrFail } from "../../lib/context.js";
|
|
7
|
-
import { buildDefinitionShowQuery } from "../../lib/definitions.js";
|
|
7
|
+
import { buildDefinitionShowQuery, buildDefinitionTagsQuery } from "../../lib/definitions.js";
|
|
8
|
+
import { fail } from "../../lib/fail.js";
|
|
8
9
|
import { tagFlags } from "../../lib/flags.js";
|
|
9
|
-
import { formatKeyValue, sectionHeader } from "../../lib/ui.js";
|
|
10
|
+
import { formatKeyValue, resourceLabel, sectionHeader } from "../../lib/ui.js";
|
|
10
11
|
export default class DefinitionShow extends WorkflowCommand {
|
|
11
12
|
static description = 'Show a deployed workflow definition.';
|
|
12
13
|
static args = {
|
|
@@ -19,13 +20,12 @@ export default class DefinitionShow extends WorkflowCommand {
|
|
|
19
20
|
async run() {
|
|
20
21
|
const { args, flags } = await this.parse(DefinitionShow);
|
|
21
22
|
const targets = await resolveReadTargets(flags);
|
|
22
|
-
const
|
|
23
|
+
const def = await resolveShownDefinition({
|
|
24
|
+
targets,
|
|
23
25
|
name: args.name,
|
|
24
26
|
tag: flags.tag,
|
|
25
27
|
version: flags.version,
|
|
26
28
|
});
|
|
27
|
-
const hits = await fetchDefinitionHits({ targets, groq, params });
|
|
28
|
-
const def = soleHitOrFail(hits, `Definition "${args.name}"`)?.def;
|
|
29
29
|
if (!def) {
|
|
30
30
|
this.error(`${logSymbols.error} no definition "${args.name}" (${versionLabel(flags.version)})`, {
|
|
31
31
|
exit: 1,
|
|
@@ -38,15 +38,33 @@ export default class DefinitionShow extends WorkflowCommand {
|
|
|
38
38
|
}
|
|
39
39
|
}
|
|
40
40
|
}
|
|
41
|
+
export async function resolveShownDefinition({ targets, name, tag, version, }) {
|
|
42
|
+
if (tag === undefined) {
|
|
43
|
+
await assertSingleTagPartition({ targets, name });
|
|
44
|
+
}
|
|
45
|
+
const { groq, params } = buildDefinitionShowQuery({ name, tag, version });
|
|
46
|
+
const hits = await fetchDefinitionHits({ targets, groq, params });
|
|
47
|
+
return soleHitOrFail(hits, `Definition "${name}"`)?.def;
|
|
48
|
+
}
|
|
41
49
|
export function versionLabel(version) {
|
|
42
50
|
return version === undefined ? 'latest' : `v${version}`;
|
|
43
51
|
}
|
|
52
|
+
async function assertSingleTagPartition({ targets, name, }) {
|
|
53
|
+
const { groq, params } = buildDefinitionTagsQuery(name);
|
|
54
|
+
await Promise.all(targets.map(async (target) => {
|
|
55
|
+
const tags = await target.client.fetch(groq, params, { tag: 'definition.show' });
|
|
56
|
+
if (tags.length > 1) {
|
|
57
|
+
fail(`Definition "${name}" exists in several tag partitions in ` +
|
|
58
|
+
`${resourceLabel(target.resource)} — pass --tag to choose one.`, tags.toSorted().join('\n'));
|
|
59
|
+
}
|
|
60
|
+
}));
|
|
61
|
+
}
|
|
44
62
|
export async function fetchDefinitionHits({ targets, groq, params, }) {
|
|
45
63
|
const probes = await Promise.all(targets.map(async (target) => {
|
|
46
64
|
const def = await target.client.fetch(groq, params, {
|
|
47
65
|
tag: 'definition.show',
|
|
48
66
|
});
|
|
49
|
-
return def === null ? undefined : { resource: target.resource, def };
|
|
67
|
+
return def === null ? undefined : { resource: target.resource, def: assertReadableModel(def) };
|
|
50
68
|
}));
|
|
51
69
|
return probes.filter((hit) => hit !== undefined);
|
|
52
70
|
}
|
|
@@ -18,7 +18,7 @@ export default class Deploy extends WorkflowCommand {
|
|
|
18
18
|
'dry-run': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
19
19
|
check: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
20
20
|
only: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
21
|
-
'share-
|
|
21
|
+
'share-defs': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
22
22
|
tag: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
23
23
|
};
|
|
24
24
|
run(): Promise<void>;
|
package/dist/commands/deploy.js
CHANGED
|
@@ -41,9 +41,9 @@ export default class Deploy extends WorkflowCommand {
|
|
|
41
41
|
only: Flags.string({
|
|
42
42
|
description: 'Limit deploy/check/diff to a single workflow definition by name. Every targeted deployment must contain it.',
|
|
43
43
|
}),
|
|
44
|
-
'share-
|
|
45
|
-
|
|
46
|
-
|
|
44
|
+
'share-defs': Flags.boolean({
|
|
45
|
+
allowNo: true,
|
|
46
|
+
description: 'Share the definition documents newly created by this deploy with Sanity — the full document, verbatim (structure, names, filters, effect configuration, seeded values), plus its deployment coordinates (project and dataset, or resource id); never content documents, instances, or your Sanity auth token. Opt-out: an interactive terminal is asked once (the answer is remembered), while unattended runs (CI / non-TTY / DO_NOT_TRACK) share nothing unless --share-defs is passed. Use --no-share-defs to opt out. An explicit --share-defs also sends telemetry for this deploy regardless of CI / DO_NOT_TRACK.',
|
|
47
47
|
}),
|
|
48
48
|
};
|
|
49
49
|
async run() {
|
|
@@ -90,7 +90,7 @@ export default class Deploy extends WorkflowCommand {
|
|
|
90
90
|
log(line);
|
|
91
91
|
}
|
|
92
92
|
await shareDefinitionsAfterDeploy({
|
|
93
|
-
flag: flags['share-
|
|
93
|
+
flag: flags['share-defs'],
|
|
94
94
|
candidates,
|
|
95
95
|
warn: (message) => this.warn(message),
|
|
96
96
|
});
|
|
@@ -6,7 +6,7 @@ import { WorkflowCommand } from '../lib/base-command.ts';
|
|
|
6
6
|
export declare function rawTransitionProjection(evaluation: WorkflowEvaluation): {
|
|
7
7
|
name: string;
|
|
8
8
|
to: string;
|
|
9
|
-
|
|
9
|
+
whenSatisfied: boolean;
|
|
10
10
|
unevaluable: boolean;
|
|
11
11
|
blockedBy: {
|
|
12
12
|
requirement?: import("@sanity/groq-condition-describe").AtomRequirement;
|
|
@@ -19,8 +19,8 @@ function activityLine(t, assignees) {
|
|
|
19
19
|
return ` ${activityIcon[t.status]} ${t.activity.name} ${styleText('dim', `[${t.status}]`)}${who}`;
|
|
20
20
|
}
|
|
21
21
|
function transitionLine(tr, explanation) {
|
|
22
|
-
const mark = tr.
|
|
23
|
-
const line = ` → ${tr.transition.to} ${styleText('dim', tr.transition.
|
|
22
|
+
const mark = tr.whenSatisfied ? styleText('green', 'ready') : styleText('red', 'false');
|
|
23
|
+
const line = ` → ${tr.transition.to} ${styleText('dim', tr.transition.when)} ${mark}`;
|
|
24
24
|
return explanation !== undefined ? `${line}\n ${styleText('dim', `↳ ${explanation}`)}` : line;
|
|
25
25
|
}
|
|
26
26
|
function currentStageLines(input, explanations) {
|
|
@@ -28,7 +28,7 @@ function currentStageLines(input, explanations) {
|
|
|
28
28
|
`${sectionHeader('Stage')} ${input.instance.currentStage}`,
|
|
29
29
|
sectionHeader('Activities'),
|
|
30
30
|
];
|
|
31
|
-
for (const t of input.activities) {
|
|
31
|
+
for (const t of input.activities.filter((a) => !a.scopedOut)) {
|
|
32
32
|
lines.push(activityLine(t, input.assignees[t.activity.name] ?? []));
|
|
33
33
|
}
|
|
34
34
|
if (input.transitions.length > 0) {
|
|
@@ -42,14 +42,14 @@ function currentStageLines(input, explanations) {
|
|
|
42
42
|
function failedEffectDetail(effect) {
|
|
43
43
|
const ran = effect.durationMs !== undefined ? ` (after ${effect.durationMs}ms)` : '';
|
|
44
44
|
return {
|
|
45
|
-
headline: `a failed effect
|
|
45
|
+
headline: `a failed effect from action '${effect.origin.name}' is blocking its activity`,
|
|
46
46
|
why: [
|
|
47
47
|
styleText('red', `${logSymbols.error} failed effect: ${effect.name}`),
|
|
48
|
-
` queued by
|
|
48
|
+
` queued by action '${effect.origin.name}', failed ${formatTimestamp(effect.ranAt)}${ran}`,
|
|
49
49
|
...(effect.error !== undefined ? [` error: ${effect.error.message}`] : []),
|
|
50
50
|
'',
|
|
51
|
-
`
|
|
52
|
-
`external system, so the activity never
|
|
51
|
+
`The activity that fired '${effect.origin.name}' is waiting on this effect. It failed`,
|
|
52
|
+
`against an external system, so the activity never resolves and the stage can't advance.`,
|
|
53
53
|
],
|
|
54
54
|
};
|
|
55
55
|
}
|
|
@@ -74,12 +74,22 @@ function failedActivityDetail(activity) {
|
|
|
74
74
|
}
|
|
75
75
|
function waitingHeadline(w) {
|
|
76
76
|
const who = w.assignees.length > 0 ? ` on ${w.assignees.map(formatAssignee).join(', ')}` : '';
|
|
77
|
+
if (w.actions.length === 0) {
|
|
78
|
+
return `waiting${who} on automation — a trigger fires on its own when its condition holds`;
|
|
79
|
+
}
|
|
77
80
|
return `waiting${who} for action: ${w.actions.join(' or ')}`;
|
|
78
81
|
}
|
|
79
82
|
function waitingLines(w) {
|
|
80
83
|
const who = w.assignees.length > 0
|
|
81
84
|
? `assigned to ${w.assignees.map(formatAssignee).join(', ')}`
|
|
82
85
|
: 'unassigned — anyone with permission can act';
|
|
86
|
+
if (w.actions.length === 0) {
|
|
87
|
+
return [
|
|
88
|
+
`${logSymbols.info} activity '${w.activity}' is active (${who}),`,
|
|
89
|
+
`waiting on automation: its cascade-fired action(s) fire on their own when their`,
|
|
90
|
+
`trigger conditions hold — nothing for a caller to fire.`,
|
|
91
|
+
];
|
|
92
|
+
}
|
|
83
93
|
return [
|
|
84
94
|
`${logSymbols.info} activity '${w.activity}' is active (${who}),`,
|
|
85
95
|
`waiting for action: ${w.actions.join(' or ')}. This is the normal in-flight`,
|
|
@@ -103,20 +113,20 @@ function blockedLines(b) {
|
|
|
103
113
|
}
|
|
104
114
|
function noTransitionDetail() {
|
|
105
115
|
return {
|
|
106
|
-
headline: `no exit transition's
|
|
116
|
+
headline: `no exit transition's trigger is satisfied`,
|
|
107
117
|
why: [
|
|
108
|
-
`${logSymbols.info} every activity is resolved, but no exit transition's
|
|
109
|
-
`Likely a routing state value a
|
|
118
|
+
`${logSymbols.info} every activity is resolved, but no exit transition's \`when\` is true.`,
|
|
119
|
+
`Likely a routing state value a trigger reads never got written.`,
|
|
110
120
|
],
|
|
111
121
|
};
|
|
112
122
|
}
|
|
113
123
|
function transitionUnevaluableDetail(transitions) {
|
|
114
124
|
return {
|
|
115
|
-
headline: `an exit transition's
|
|
125
|
+
headline: `an exit transition's trigger could not be evaluated`,
|
|
116
126
|
why: [
|
|
117
127
|
`${logSymbols.info} every activity is resolved, but ${transitions.join(', ')} reads an operand`,
|
|
118
128
|
`that is missing or unreadable (GROQ null), so routing is held rather than`,
|
|
119
|
-
`falling through. Make the data the
|
|
129
|
+
`falling through. Make the data the trigger reads readable — publish the`,
|
|
120
130
|
`subject (or fill the field) — and the instance advances on its own; no`,
|
|
121
131
|
`set-stage needed.`,
|
|
122
132
|
],
|
|
@@ -166,7 +176,7 @@ export function rawTransitionProjection(evaluation) {
|
|
|
166
176
|
return evaluation.currentStage.transitions.map((t) => ({
|
|
167
177
|
name: t.transition.name,
|
|
168
178
|
to: t.transition.to,
|
|
169
|
-
|
|
179
|
+
whenSatisfied: t.whenSatisfied,
|
|
170
180
|
unevaluable: t.unevaluable,
|
|
171
181
|
blockedBy: t.insight.blockedBy.map((atom) => ({
|
|
172
182
|
groq: atom.atom.groq,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { styleText } from 'node:util';
|
|
2
2
|
import { Args, Flags } from '@oclif/core';
|
|
3
|
-
import { actionDisabledDetail, deniedGuardLabels, workflow, } from '@sanity/workflow-engine';
|
|
3
|
+
import { actionRendering, actionDisabledDetail, deniedGuardLabels, workflow, } from '@sanity/workflow-engine';
|
|
4
4
|
import logSymbols from 'log-symbols';
|
|
5
5
|
import { WorkflowCommand } from "../lib/base-command.js";
|
|
6
6
|
import { resolveInstanceContext } from "../lib/context.js";
|
|
@@ -20,8 +20,19 @@ function paramLabel(p) {
|
|
|
20
20
|
return `${p.name}${p.required === true ? '' : '?'}:${p.type}`;
|
|
21
21
|
}
|
|
22
22
|
function actionLines(a) {
|
|
23
|
-
const mark = a.allowed ? logSymbols.success : logSymbols.error;
|
|
24
23
|
const title = a.title !== undefined ? styleText('dim', ` (${a.title})`) : '';
|
|
24
|
+
const rendering = actionRendering(a);
|
|
25
|
+
if (rendering === 'absent')
|
|
26
|
+
return [];
|
|
27
|
+
if (rendering === 'automation') {
|
|
28
|
+
return [
|
|
29
|
+
` ${styleText('cyan', '⚡')} ${a.activity} → ${a.action}${title} ${styleText('dim', '[automation]')}`,
|
|
30
|
+
...(a.disabledReason?.kind === 'cascade-fired'
|
|
31
|
+
? [styleText('dim', ` ${actionDisabledDetail(a.disabledReason)}`)]
|
|
32
|
+
: []),
|
|
33
|
+
];
|
|
34
|
+
}
|
|
35
|
+
const mark = a.allowed ? logSymbols.success : logSymbols.error;
|
|
25
36
|
const lines = [
|
|
26
37
|
` ${mark} ${a.activity} → ${a.action}${title} ${styleText('dim', `[activity ${a.activityStatus}]`)}`,
|
|
27
38
|
];
|
package/dist/commands/list.d.ts
CHANGED
|
@@ -8,11 +8,14 @@ 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;
|
|
14
16
|
failed: boolean;
|
|
15
17
|
definition?: string | undefined;
|
|
18
|
+
document?: string | undefined;
|
|
16
19
|
tag?: string | undefined;
|
|
17
20
|
limit: number;
|
|
18
21
|
}
|
|
@@ -20,6 +23,13 @@ interface ListFlags {
|
|
|
20
23
|
* Build the GROQ query + params for `list` from its flags. Pure so the
|
|
21
24
|
* filter combinations can be asserted without a live dataset. `--tag` is an
|
|
22
25
|
* optional filter: omit it and the listing spans every partition.
|
|
26
|
+
*
|
|
27
|
+
* With `--document` the lake filter is the engine's prefilter arm — a
|
|
28
|
+
* deliberate superset narrowed to the exact watch-set in JS
|
|
29
|
+
* ({@link instanceWatchesDocument}) — so the rows come back as full
|
|
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.
|
|
23
33
|
*/
|
|
24
34
|
export declare function buildListQuery(flags: ListFlags): {
|
|
25
35
|
groq: string;
|
|
@@ -38,9 +48,10 @@ export default class List extends WorkflowCommand {
|
|
|
38
48
|
static description: string;
|
|
39
49
|
static examples: string[];
|
|
40
50
|
static flags: {
|
|
41
|
-
'
|
|
51
|
+
'include-completed': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
42
52
|
failed: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
43
53
|
definition: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
54
|
+
document: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
44
55
|
limit: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
|
|
45
56
|
tag: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
46
57
|
};
|