@sanity/workflow-cli 0.8.1 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +485 -0
- package/README.md +145 -63
- package/bin/run.js +0 -4
- package/dist/commands/abort.d.ts +8 -12
- package/dist/commands/abort.js +24 -34
- package/dist/commands/definition/delete.d.ts +6 -6
- package/dist/commands/definition/delete.js +17 -33
- package/dist/commands/definition/diff.d.ts +10 -3
- package/dist/commands/definition/diff.js +21 -29
- package/dist/commands/definition/list.d.ts +5 -6
- package/dist/commands/definition/list.js +44 -50
- package/dist/commands/definition/show.d.ts +22 -13
- package/dist/commands/definition/show.js +25 -36
- package/dist/commands/deploy.d.ts +67 -8
- package/dist/commands/deploy.js +136 -32
- package/dist/commands/diagnose.d.ts +22 -4
- package/dist/commands/diagnose.js +45 -61
- package/dist/commands/fire-action.d.ts +7 -30
- package/dist/commands/fire-action.js +42 -115
- package/dist/commands/list.d.ts +9 -10
- package/dist/commands/list.js +49 -62
- package/dist/commands/{retry-activity.d.ts → reset-activity.d.ts} +1 -1
- package/dist/commands/{retry-activity.js → reset-activity.js} +2 -3
- package/dist/commands/set-stage.d.ts +27 -3
- package/dist/commands/set-stage.js +63 -12
- package/dist/commands/show.d.ts +3 -2
- package/dist/commands/show.js +15 -21
- package/dist/commands/start.d.ts +56 -0
- package/dist/commands/start.js +133 -0
- package/dist/commands/tail.d.ts +5 -2
- package/dist/commands/tail.js +25 -26
- package/dist/hooks/finally/telemetry.d.ts +8 -0
- package/dist/hooks/finally/telemetry.js +13 -0
- package/dist/hooks/prerun/telemetry.d.ts +5 -0
- package/dist/hooks/prerun/telemetry.js +5 -0
- package/dist/index.js +0 -3
- package/dist/lib/base-command.d.ts +14 -0
- package/dist/lib/base-command.js +10 -0
- package/dist/lib/client.d.ts +11 -14
- package/dist/lib/client.js +29 -34
- package/dist/lib/context.d.ts +98 -0
- package/dist/lib/context.js +83 -0
- package/dist/lib/definitions.d.ts +38 -29
- package/dist/lib/definitions.js +34 -93
- package/dist/lib/diff.d.ts +5 -2
- package/dist/lib/diff.js +62 -20
- package/dist/lib/env.d.ts +4 -6
- package/dist/lib/env.js +4 -9
- package/dist/lib/fail.d.ts +12 -0
- package/dist/lib/fail.js +25 -16
- package/dist/lib/flags.d.ts +7 -2
- package/dist/lib/flags.js +7 -3
- package/dist/lib/load-config.d.ts +15 -0
- package/dist/lib/load-config.js +89 -0
- package/dist/lib/operation-args.d.ts +20 -15
- package/dist/lib/operation-args.js +9 -40
- package/dist/lib/ops-report.d.ts +29 -13
- package/dist/lib/ops-report.js +20 -17
- package/dist/lib/params.d.ts +7 -0
- package/dist/lib/params.js +18 -0
- package/dist/lib/read-fanout.d.ts +22 -0
- package/dist/lib/read-fanout.js +28 -0
- package/dist/lib/select-deployment.d.ts +31 -0
- package/dist/lib/select-deployment.js +37 -0
- package/dist/lib/share-definitions.d.ts +86 -0
- package/dist/lib/share-definitions.js +106 -0
- package/dist/lib/stub.js +0 -7
- package/dist/lib/telemetry-setup.d.ts +66 -0
- package/dist/lib/telemetry-setup.js +92 -0
- package/dist/lib/telemetry.d.ts +100 -0
- package/dist/lib/telemetry.js +89 -0
- package/dist/lib/ui.d.ts +33 -1
- package/dist/lib/ui.js +32 -21
- package/oclif.manifest.json +133 -113
- package/package.json +16 -8
- package/dist/commands/move-stage.d.ts +0 -52
- package/dist/commands/move-stage.js +0 -86
- package/dist/lib/config.d.ts +0 -18
- package/dist/lib/config.js +0 -50
package/README.md
CHANGED
|
@@ -4,9 +4,9 @@ Command-line tool for deploying, inspecting, and administering Sanity workflow
|
|
|
4
4
|
definitions and instances.
|
|
5
5
|
|
|
6
6
|
> [!WARNING]
|
|
7
|
-
> Early access, restricted.
|
|
8
|
-
>
|
|
9
|
-
>
|
|
7
|
+
> Early access, restricted. The commands below talk to a real Sanity dataset,
|
|
8
|
+
> configured by a `sanity.workflow.ts` file and authenticated with
|
|
9
|
+
> `sanity login`.
|
|
10
10
|
|
|
11
11
|
## Run
|
|
12
12
|
|
|
@@ -20,39 +20,82 @@ pnpm --filter @sanity/workflow-cli dev list --in-flight
|
|
|
20
20
|
pnpm --filter @sanity/workflow-cli dev show wf-instance.abc123
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
23
|
+
Authenticate once with `sanity login` (the CLI reads that session token); for
|
|
24
|
+
CI, set `SANITY_AUTH_TOKEN` instead. Then create a `sanity.workflow.ts` in the
|
|
25
|
+
directory you run from — see [Configuration](#configuration).
|
|
26
|
+
|
|
27
|
+
The token must span **every resource the workflow references**, not just the
|
|
28
|
+
workflow resource. Runtime commands build one client from the deployment
|
|
29
|
+
descriptor; when a workflow's subject or `doc.ref` fields point into other
|
|
30
|
+
datasets (or a canvas / media library), the engine reaches them through
|
|
31
|
+
sibling clients derived from that same token. A `sanity login` session or an
|
|
32
|
+
org-capable token covers this out of the box; a token scoped to a single
|
|
33
|
+
project will fail on cross-resource reads.
|
|
34
|
+
|
|
35
|
+
## Configuration
|
|
36
|
+
|
|
37
|
+
The CLI is configured by a `sanity.workflow.ts` (or `.js`/`.mjs`) discovered in
|
|
38
|
+
the directory you run from. It exports a config built with
|
|
39
|
+
`defineWorkflowConfig`, declaring one **deployment** per environment:
|
|
40
|
+
|
|
41
|
+
```ts
|
|
42
|
+
import {defineWorkflowConfig} from '@sanity/workflow-engine/define'
|
|
43
|
+
|
|
44
|
+
import {articleReview, urlDraft} from './src/workflows.ts'
|
|
45
|
+
|
|
46
|
+
export default defineWorkflowConfig({
|
|
47
|
+
deployments: [
|
|
48
|
+
{
|
|
49
|
+
name: 'production',
|
|
50
|
+
tag: 'prod', // the environment partition the engine's docs are scoped to
|
|
51
|
+
workflowResource: {type: 'dataset', id: 'acme.workflows'}, // where those docs live
|
|
52
|
+
resourceAliases: [
|
|
53
|
+
// binds each `@<handle>:` a definition references to a physical resource
|
|
54
|
+
// in a DIFFERENT dataset from `workflowResource` (same-resource content
|
|
55
|
+
// needs no alias — see below)
|
|
56
|
+
{name: 'content', resource: {type: 'dataset', id: 'acme.content'}},
|
|
57
|
+
{name: 'assets', resource: {type: 'dataset', id: 'acme.assets'}},
|
|
58
|
+
],
|
|
59
|
+
definitions: [articleReview, urlDraft], // the batch this deployment ships
|
|
60
|
+
},
|
|
61
|
+
],
|
|
62
|
+
})
|
|
63
|
+
```
|
|
49
64
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
65
|
+
Pick a deployment with `--tag <tag>`; with a single deployment configured you
|
|
66
|
+
can omit it, and with several configured a bare `deploy` fails asking for
|
|
67
|
+
`--tag` or `--all-tags`. `--all-tags` deploys every deployment in the config in
|
|
68
|
+
one run: a failure in one doesn't stop the rest — the run continues, prints a
|
|
69
|
+
summary of what failed, and exits non-zero. The client's project + dataset are derived
|
|
70
|
+
from the deployment's `workflowResource`, and `deploy` expands each
|
|
71
|
+
definition's `@<handle>:` reference to the bound physical resource. An invalid
|
|
72
|
+
config fails with a clean, path-prefixed error before the command runs.
|
|
73
|
+
|
|
74
|
+
`resourceAliases` is only for content that lives in a _different_ resource from
|
|
75
|
+
`workflowResource`. A definition can reference a document in the **same**
|
|
76
|
+
resource by bare id, no alias needed — bare ids root at `workflowResource` at
|
|
77
|
+
runtime. Rule of thumb: **different resource → bind an alias and reference it as
|
|
78
|
+
`@<handle>:<id>`; same resource → use a bare id and omit `resourceAliases`
|
|
79
|
+
entirely.** So the simplest single-dataset setup is just a `workflowResource`
|
|
80
|
+
and `definitions`, with no `resourceAliases` at all.
|
|
81
|
+
|
|
82
|
+
| Var | Purpose |
|
|
83
|
+
| ------------------- | ---------------------------------------------------------------------------------------------------- |
|
|
84
|
+
| `SANITY_AUTH_TOKEN` | Explicit token for CI / scripted use — wins over the `sanity login` session (editor role for writes) |
|
|
85
|
+
| `SANITY_API_HOST` | Optional API host override (defaults to the prod API) |
|
|
86
|
+
|
|
87
|
+
Run through the `dev` / `cli` scripts and the CLI itself and your
|
|
88
|
+
`sanity.workflow.ts` load straight from TypeScript source: the scripts set
|
|
89
|
+
`NODE_OPTIONS='--conditions=development'` under `tsx`, and the config file is
|
|
90
|
+
transpiled on the fly by `jiti`. Definitions you author **inline** in the
|
|
91
|
+
config, or import by **relative path**, transpile the same way — no build step.
|
|
92
|
+
|
|
93
|
+
One caveat: a definition imported from a _separate workspace package_ (e.g.
|
|
94
|
+
`@sanity/workflow-examples`) resolves to that package's built `dist/`. `jiti`
|
|
95
|
+
applies its own module resolution and does **not** honour
|
|
96
|
+
`--conditions=development`, so it never picks up a package's `src/` export
|
|
97
|
+
condition — rebuild that package after editing it, or the config loads its
|
|
98
|
+
stale compiled output.
|
|
56
99
|
|
|
57
100
|
> [!NOTE]
|
|
58
101
|
> pnpm intercepts a handful of its own flag names (`--check`,
|
|
@@ -75,25 +118,58 @@ built or standalone CLI instead resolves the package's compiled `dist`.
|
|
|
75
118
|
|
|
76
119
|
## Command status
|
|
77
120
|
|
|
78
|
-
| Command
|
|
79
|
-
|
|
|
80
|
-
| `deploy`
|
|
81
|
-
| `deploy --
|
|
82
|
-
| `deploy --
|
|
83
|
-
| `deploy --
|
|
84
|
-
| `
|
|
85
|
-
| `
|
|
86
|
-
| `
|
|
87
|
-
| `
|
|
88
|
-
| `
|
|
89
|
-
| `
|
|
90
|
-
| `
|
|
91
|
-
| `
|
|
92
|
-
| `
|
|
93
|
-
| `definition list`
|
|
94
|
-
| `definition show <
|
|
95
|
-
| `definition diff <
|
|
96
|
-
| `definition delete <name>`
|
|
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`) |
|
|
140
|
+
|
|
141
|
+
## Telemetry
|
|
142
|
+
|
|
143
|
+
The CLI collects usage telemetry through Sanity's standard pipeline: a
|
|
144
|
+
per-command trace (`Editorial Workflows CLI Command Executed` — the command id, the
|
|
145
|
+
names of declared flags used, never their values, and a success flag) plus
|
|
146
|
+
the engine's adoption events from the operations it drives. Consent is the
|
|
147
|
+
account-wide status managed by `npx sanity telemetry enable|disable|status`;
|
|
148
|
+
CI and trueish `DO_NOT_TRACK` suppress everything, and a session that isn't
|
|
149
|
+
logged in sends nothing. A one-time notice on stderr discloses collection on
|
|
150
|
+
first use.
|
|
151
|
+
|
|
152
|
+
One extension point: supply your own logger as `telemetry` in
|
|
153
|
+
`sanity.workflow.ts` and the built-in pipeline is not constructed at all —
|
|
154
|
+
every event flows to your implementation unconditionally (CI included), and
|
|
155
|
+
consent, suppression, and destination become its business.
|
|
156
|
+
|
|
157
|
+
## Definition sharing
|
|
158
|
+
|
|
159
|
+
Separate from telemetry, `deploy --share-definitions-with-sanity` donates the
|
|
160
|
+
definition documents the deploy newly created to Sanity — verbatim (structure,
|
|
161
|
+
names, titles, GROQ filters, effect configuration, seeded values), plus their
|
|
162
|
+
deployment coordinates (project and dataset, or resource id) — never content
|
|
163
|
+
documents, instances, or your auth token. The documents go to Sanity's
|
|
164
|
+
first-party definition-feedback endpoint, not the telemetry pipeline;
|
|
165
|
+
telemetry carries only a content-free marker (content hash plus structural
|
|
166
|
+
counts) per shared definition.
|
|
167
|
+
|
|
168
|
+
Sharing is opt-in by the flag alone, per invocation: the CLI never prompts
|
|
169
|
+
and nothing persists. An interactive deploy that created new definition
|
|
170
|
+
versions without the flag logs a one-line stderr hint pointing at it — CI /
|
|
171
|
+
non-TTY runs stay silent. Telemetry consent and `DO_NOT_TRACK` play no part:
|
|
172
|
+
sharing is its own explicit consent, given per deploy.
|
|
97
173
|
|
|
98
174
|
## Known gaps
|
|
99
175
|
|
|
@@ -101,15 +177,21 @@ built or standalone CLI instead resolves the package's compiled `dist`.
|
|
|
101
177
|
Sanity CLI as a plugin host. The package publishes as a standalone
|
|
102
178
|
`sanity-workflows` binary today; plugging into `sanity` as an oclif
|
|
103
179
|
plugin is a separate piece of work.
|
|
104
|
-
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
180
|
+
- **No true bypass.** `set-stage` is the engine's `setStage` admin
|
|
181
|
+
override: it skips the definition's declared transitions and filters, but
|
|
182
|
+
the target stage's enter lifecycle and the post-move cascade still run. A
|
|
183
|
+
true force-set that also bypasses guards + enter effects does not exist
|
|
184
|
+
yet — it needs engine work that hasn't landed.
|
|
185
|
+
|
|
186
|
+
## Identity
|
|
187
|
+
|
|
188
|
+
Identity is the token behind the configured client: the engine resolves the
|
|
189
|
+
acting user (and their roles) from `/users/me`, so every write is attributed
|
|
190
|
+
to whoever the token authenticates — there is no identity injection and no
|
|
191
|
+
`--as` impersonation. Alongside that "who", the CLI declares an advisory
|
|
192
|
+
"via what": every verb carries
|
|
193
|
+
`executionContext: { kind: "cli", id: "workflow-cli" }`, stamped on history
|
|
194
|
+
entries as provenance.
|
|
113
195
|
|
|
114
196
|
## Tech stack
|
|
115
197
|
|
package/bin/run.js
CHANGED
|
@@ -1,8 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// Production entry for the published package: loads the compiled commands
|
|
3
|
-
// from ./dist/commands (per the `oclif.commands` field). The dev counterpart,
|
|
4
|
-
// bin/dev.js, passes `development: true` to load TS sources instead.
|
|
5
|
-
|
|
6
2
|
import {execute} from '@oclif/core'
|
|
7
3
|
|
|
8
4
|
await execute({dir: import.meta.url})
|
package/dist/commands/abort.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import { type WorkflowInstance } from '@sanity/workflow-engine';
|
|
2
|
+
import { WorkflowCommand } from '../lib/base-command.ts';
|
|
3
|
+
import { type WriteReport } from '../lib/ops-report.ts';
|
|
4
|
+
export default class Abort extends WorkflowCommand {
|
|
3
5
|
static description: string;
|
|
4
6
|
static examples: string[];
|
|
5
7
|
static args: {
|
|
@@ -12,19 +14,13 @@ export default class Abort extends Command {
|
|
|
12
14
|
run(): Promise<void>;
|
|
13
15
|
}
|
|
14
16
|
interface AbortOutcome {
|
|
15
|
-
|
|
16
|
-
instance:
|
|
17
|
-
currentStage: string;
|
|
18
|
-
completedAt?: string | undefined;
|
|
19
|
-
};
|
|
17
|
+
changed: boolean;
|
|
18
|
+
instance: Pick<WorkflowInstance, 'currentStage' | 'completedAt'>;
|
|
20
19
|
}
|
|
21
20
|
/**
|
|
22
21
|
* Turn an `abortInstance` result into the spinner message. Pure so the
|
|
23
|
-
*
|
|
22
|
+
* aborted / already-terminal permutations can be asserted without driving
|
|
24
23
|
* a real abort.
|
|
25
24
|
*/
|
|
26
|
-
export declare function abortReport(result: AbortOutcome):
|
|
27
|
-
fired: boolean;
|
|
28
|
-
message: string;
|
|
29
|
-
};
|
|
25
|
+
export declare function abortReport(result: AbortOutcome): WriteReport;
|
|
30
26
|
export {};
|
package/dist/commands/abort.js
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import { styleText } from 'node:util';
|
|
2
|
-
import { Args,
|
|
2
|
+
import { Args, Flags } from '@oclif/core';
|
|
3
3
|
import { workflow } from '@sanity/workflow-engine';
|
|
4
|
-
import
|
|
5
|
-
import {
|
|
6
|
-
import { loadWorkflowConfig } from "../lib/config.js";
|
|
7
|
-
import { fail } from "../lib/fail.js";
|
|
4
|
+
import { WorkflowCommand } from "../lib/base-command.js";
|
|
5
|
+
import { resolveInstanceContext } from "../lib/context.js";
|
|
8
6
|
import { tagFlags } from "../lib/flags.js";
|
|
9
7
|
import { buildOperationArgs } from "../lib/operation-args.js";
|
|
10
|
-
|
|
8
|
+
import { runWriteVerb } from "../lib/ops-report.js";
|
|
9
|
+
export default class Abort extends WorkflowCommand {
|
|
11
10
|
static description = 'Abort an in-flight workflow instance — a hard stop: pending effects are cancelled, stage guards lifted, and the instance is marked terminal where it stands.';
|
|
12
11
|
static examples = [
|
|
13
12
|
'<%= config.bin %> abort wf-instance.abc123',
|
|
@@ -22,41 +21,32 @@ export default class Abort extends Command {
|
|
|
22
21
|
};
|
|
23
22
|
async run() {
|
|
24
23
|
const { args, flags } = await this.parse(Abort);
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
24
|
+
const { client, scope } = await resolveInstanceContext(flags, args.instanceId);
|
|
25
|
+
await runWriteVerb({
|
|
26
|
+
startLabel: `Aborting ${args.instanceId}…`,
|
|
27
|
+
failLabel: 'Abort rejected',
|
|
28
|
+
failHeadline: 'abort error:',
|
|
29
|
+
run: () => workflow.abortInstance({
|
|
30
30
|
client,
|
|
31
|
-
...buildOperationArgs({
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
}
|
|
40
|
-
catch (error) {
|
|
41
|
-
spinner.fail('Abort rejected');
|
|
42
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
43
|
-
fail('abortInstance error:', message);
|
|
44
|
-
}
|
|
31
|
+
...buildOperationArgs({
|
|
32
|
+
scope,
|
|
33
|
+
instanceId: args.instanceId,
|
|
34
|
+
reason: flags.reason,
|
|
35
|
+
}),
|
|
36
|
+
}),
|
|
37
|
+
report: abortReport,
|
|
38
|
+
log: (line) => this.log(line),
|
|
39
|
+
});
|
|
45
40
|
}
|
|
46
41
|
}
|
|
47
|
-
/**
|
|
48
|
-
* Turn an `abortInstance` result into the spinner message. Pure so the
|
|
49
|
-
* fired / already-terminal permutations can be asserted without driving
|
|
50
|
-
* a real abort.
|
|
51
|
-
*/
|
|
52
42
|
export function abortReport(result) {
|
|
53
43
|
const stage = styleText('bold', result.instance.currentStage);
|
|
54
|
-
if (!result.
|
|
44
|
+
if (!result.changed) {
|
|
55
45
|
const at = result.instance.completedAt ?? 'unknown';
|
|
56
46
|
return {
|
|
57
|
-
|
|
58
|
-
message: `abortInstance
|
|
47
|
+
changed: false,
|
|
48
|
+
message: `abortInstance changed nothing — instance is already terminal (at ${stage} since ${at})`,
|
|
59
49
|
};
|
|
60
50
|
}
|
|
61
|
-
return {
|
|
51
|
+
return { changed: true, message: `Aborted — instance hard-stopped at ${stage}` };
|
|
62
52
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { Command } from '@oclif/core';
|
|
2
1
|
import { type DeleteDefinitionArgs, type DeleteDefinitionResult } from '@sanity/workflow-engine';
|
|
3
|
-
import {
|
|
4
|
-
|
|
2
|
+
import { WorkflowCommand } from '../../lib/base-command.ts';
|
|
3
|
+
import { type EngineScope } from '../../lib/operation-args.ts';
|
|
4
|
+
export default class DefinitionDelete extends WorkflowCommand {
|
|
5
5
|
static description: string;
|
|
6
6
|
static examples: string[];
|
|
7
7
|
static args: {
|
|
@@ -21,15 +21,15 @@ export default class DefinitionDelete extends Command {
|
|
|
21
21
|
* engine's optionals reject an explicit `undefined` under
|
|
22
22
|
* `exactOptionalPropertyTypes`.
|
|
23
23
|
*/
|
|
24
|
-
export declare function buildDeleteArgs({
|
|
25
|
-
|
|
24
|
+
export declare function buildDeleteArgs({ scope, name, flags, }: {
|
|
25
|
+
scope: EngineScope;
|
|
26
26
|
name: string;
|
|
27
27
|
flags: {
|
|
28
28
|
version?: number | undefined;
|
|
29
29
|
cascade: boolean;
|
|
30
30
|
reason?: string | undefined;
|
|
31
31
|
};
|
|
32
|
-
}):
|
|
32
|
+
}): DeleteDefinitionArgs & EngineScope;
|
|
33
33
|
/** What the spinner names: the workflow, or one pinned version of it. */
|
|
34
34
|
export declare function deleteTargetLabel(args: Pick<DeleteDefinitionArgs, 'definition' | 'version'>): string;
|
|
35
35
|
/**
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import { styleText } from 'node:util';
|
|
2
|
-
import { Args,
|
|
2
|
+
import { Args, Flags } from '@oclif/core';
|
|
3
3
|
import { workflow, } from '@sanity/workflow-engine';
|
|
4
|
-
import
|
|
5
|
-
import {
|
|
6
|
-
import { loadWorkflowConfig } from "../../lib/config.js";
|
|
7
|
-
import { fail } from "../../lib/fail.js";
|
|
4
|
+
import { WorkflowCommand } from "../../lib/base-command.js";
|
|
5
|
+
import { resolveContext } from "../../lib/context.js";
|
|
8
6
|
import { tagFlags } from "../../lib/flags.js";
|
|
9
7
|
import { baseEngineArgs } from "../../lib/operation-args.js";
|
|
10
|
-
|
|
8
|
+
import { runWriteVerb } from "../../lib/ops-report.js";
|
|
9
|
+
export default class DefinitionDelete extends WorkflowCommand {
|
|
11
10
|
static description = 'Delete a deployed workflow definition (every version, or one via --version). ' +
|
|
12
11
|
'Refuses while non-terminal instances exist unless --cascade aborts them first — ' +
|
|
13
12
|
'instances are aborted in place, never deleted.';
|
|
@@ -34,45 +33,30 @@ export default class DefinitionDelete extends Command {
|
|
|
34
33
|
};
|
|
35
34
|
async run() {
|
|
36
35
|
const { args, flags } = await this.parse(DefinitionDelete);
|
|
37
|
-
const
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
48
|
-
fail('deleteDefinition error:', message);
|
|
49
|
-
}
|
|
36
|
+
const { deployment, client } = await resolveContext(flags);
|
|
37
|
+
const deleteArgs = buildDeleteArgs({ scope: deployment, name: args.name, flags });
|
|
38
|
+
await runWriteVerb({
|
|
39
|
+
startLabel: `Deleting definition ${deleteTargetLabel(deleteArgs)}…`,
|
|
40
|
+
failLabel: 'Delete rejected',
|
|
41
|
+
failHeadline: 'definition delete error:',
|
|
42
|
+
run: () => workflow.deleteDefinition({ client, ...deleteArgs }),
|
|
43
|
+
report: (result) => ({ changed: true, message: deleteReport(result) }),
|
|
44
|
+
log: (line) => this.log(line),
|
|
45
|
+
});
|
|
50
46
|
}
|
|
51
47
|
}
|
|
52
|
-
|
|
53
|
-
* The shared engine args ({@link baseEngineArgs}) plus the delete-specific
|
|
54
|
-
* targeting. `version` and `reason` spread conditionally because the
|
|
55
|
-
* engine's optionals reject an explicit `undefined` under
|
|
56
|
-
* `exactOptionalPropertyTypes`.
|
|
57
|
-
*/
|
|
58
|
-
export function buildDeleteArgs({ config, name, flags, }) {
|
|
48
|
+
export function buildDeleteArgs({ scope, name, flags, }) {
|
|
59
49
|
return {
|
|
60
|
-
...baseEngineArgs(
|
|
50
|
+
...baseEngineArgs(scope),
|
|
61
51
|
definition: name,
|
|
62
52
|
cascade: flags.cascade,
|
|
63
53
|
...(flags.version !== undefined ? { version: flags.version } : {}),
|
|
64
54
|
...(flags.reason !== undefined ? { reason: flags.reason } : {}),
|
|
65
55
|
};
|
|
66
56
|
}
|
|
67
|
-
/** What the spinner names: the workflow, or one pinned version of it. */
|
|
68
57
|
export function deleteTargetLabel(args) {
|
|
69
58
|
return args.version === undefined ? args.definition : `${args.definition} v${args.version}`;
|
|
70
59
|
}
|
|
71
|
-
/**
|
|
72
|
-
* Turn a `deleteDefinition` result into the spinner message. Pure so the
|
|
73
|
-
* version / cascade / guard permutations can be asserted without driving
|
|
74
|
-
* a real delete.
|
|
75
|
-
*/
|
|
76
60
|
export function deleteReport(result) {
|
|
77
61
|
const versions = result.deletedVersions.map((v) => `v${v}`).join(', ');
|
|
78
62
|
const parts = [`Deleted ${styleText('bold', result.name)} ${versions}`];
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import { type WorkflowDefinition } from '@sanity/workflow-engine';
|
|
2
|
+
import { WorkflowCommand } from '../../lib/base-command.ts';
|
|
3
|
+
export default class DefinitionDiff extends WorkflowCommand {
|
|
3
4
|
static description: string;
|
|
4
5
|
static examples: string[];
|
|
5
6
|
static args: {
|
|
6
|
-
|
|
7
|
+
name: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
7
8
|
};
|
|
8
9
|
static flags: {
|
|
9
10
|
version: import("@oclif/core/interfaces").OptionFlag<number | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
@@ -11,3 +12,9 @@ export default class DefinitionDiff extends Command {
|
|
|
11
12
|
};
|
|
12
13
|
run(): Promise<void>;
|
|
13
14
|
}
|
|
15
|
+
/**
|
|
16
|
+
* Pick the single definition to diff by name and validate it, failing cleanly
|
|
17
|
+
* when it's missing from the deployment or doesn't pass validation. Extracted
|
|
18
|
+
* from `run` so the command's orchestration stays thin and this stays testable.
|
|
19
|
+
*/
|
|
20
|
+
export declare function selectDefinitionToDiff(definitions: WorkflowDefinition[], name: string): WorkflowDefinition;
|
|
@@ -1,21 +1,20 @@
|
|
|
1
|
-
import { Args,
|
|
1
|
+
import { Args, Flags } from '@oclif/core';
|
|
2
2
|
import { diffEntry } from '@sanity/workflow-engine';
|
|
3
3
|
import ora from 'ora';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
4
|
+
import { WorkflowCommand } from "../../lib/base-command.js";
|
|
5
|
+
import { resolveContext } from "../../lib/context.js";
|
|
6
|
+
import { fetchDeployedDefinition, selectDefinitions, validateOrFail } from "../../lib/definitions.js";
|
|
7
7
|
import { diffReport } from "../../lib/diff.js";
|
|
8
|
-
import { fail } from "../../lib/fail.js";
|
|
9
8
|
import { tagFlags } from "../../lib/flags.js";
|
|
10
|
-
import {
|
|
11
|
-
export default class DefinitionDiff extends
|
|
9
|
+
import { deploymentToTarget } from "../../lib/select-deployment.js";
|
|
10
|
+
export default class DefinitionDiff extends WorkflowCommand {
|
|
12
11
|
static description = 'Diff an in-code definition against the deployed version (latest by default).';
|
|
13
12
|
static examples = [
|
|
14
13
|
'<%= config.bin %> definition diff productLaunch',
|
|
15
14
|
'<%= config.bin %> definition diff productLaunch --version 2',
|
|
16
15
|
];
|
|
17
16
|
static args = {
|
|
18
|
-
|
|
17
|
+
name: Args.string({ required: true, description: 'Workflow definition name.' }),
|
|
19
18
|
};
|
|
20
19
|
static flags = {
|
|
21
20
|
...tagFlags,
|
|
@@ -23,40 +22,33 @@ export default class DefinitionDiff extends Command {
|
|
|
23
22
|
};
|
|
24
23
|
async run() {
|
|
25
24
|
const { args, flags } = await this.parse(DefinitionDiff);
|
|
26
|
-
const
|
|
27
|
-
const def =
|
|
28
|
-
const
|
|
29
|
-
const spinner = ora(`Diffing ${args.
|
|
25
|
+
const { deployment, client } = await resolveContext(flags);
|
|
26
|
+
const def = selectDefinitionToDiff(deployment.definitions, args.name);
|
|
27
|
+
const target = deploymentToTarget(deployment);
|
|
28
|
+
const spinner = ora(`Diffing ${args.name} against deployed…`).start();
|
|
30
29
|
let deployed;
|
|
31
30
|
try {
|
|
32
31
|
deployed = await fetchDeployedDefinition({
|
|
33
32
|
client,
|
|
34
|
-
name: args.
|
|
35
|
-
|
|
33
|
+
name: args.name,
|
|
34
|
+
tag: target.tag,
|
|
36
35
|
version: flags.version,
|
|
37
36
|
});
|
|
38
|
-
spinner.succeed(`Diffed ${args.
|
|
37
|
+
spinner.succeed(`Diffed ${args.name}`);
|
|
39
38
|
}
|
|
40
39
|
catch (error) {
|
|
41
40
|
spinner.fail('Diff failed');
|
|
42
41
|
throw error;
|
|
43
42
|
}
|
|
44
|
-
for (const line of diffReport([diffEntry({ def, latestRaw: deployed, target
|
|
43
|
+
for (const line of diffReport([diffEntry({ def, latestRaw: deployed, target })]))
|
|
45
44
|
this.log(line);
|
|
46
45
|
}
|
|
47
46
|
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
* superseded — latest unless pinned via `version`. An absent latest is a
|
|
53
|
-
* legitimate "create" diff, but an absent *pinned* version is an error.
|
|
54
|
-
*/
|
|
55
|
-
async function fetchDeployedDefinition({ client, name, config, version, }) {
|
|
56
|
-
const { groq, params } = buildDefinitionShowQuery({ name, tag: config.tag, version });
|
|
57
|
-
const deployed = await client.fetch(groq, params);
|
|
58
|
-
if (deployed === null && version !== undefined) {
|
|
59
|
-
fail(`No deployed definition "${name}" v${version}.`);
|
|
47
|
+
export function selectDefinitionToDiff(definitions, name) {
|
|
48
|
+
const [def] = selectDefinitions(definitions, { only: name });
|
|
49
|
+
if (def === undefined) {
|
|
50
|
+
throw new Error(`selectDefinitions returned no match for "${name}"`);
|
|
60
51
|
}
|
|
61
|
-
|
|
52
|
+
validateOrFail([def]);
|
|
53
|
+
return def;
|
|
62
54
|
}
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { type WorkflowRole } from '@sanity/workflow-engine';
|
|
1
|
+
import { WorkflowCommand } from '../../lib/base-command.ts';
|
|
3
2
|
interface DefinitionListFlags {
|
|
4
3
|
tag?: string | undefined;
|
|
5
|
-
|
|
4
|
+
name?: string | undefined;
|
|
6
5
|
limit: number;
|
|
7
6
|
}
|
|
8
7
|
export interface DefinitionListRow {
|
|
@@ -10,21 +9,21 @@ export interface DefinitionListRow {
|
|
|
10
9
|
version: number;
|
|
11
10
|
title: string;
|
|
12
11
|
tag: string;
|
|
13
|
-
role?: WorkflowRole;
|
|
14
12
|
stageCount: number;
|
|
15
13
|
inFlightCount: number;
|
|
16
14
|
totalInstances: number;
|
|
15
|
+
_createdAt: string;
|
|
17
16
|
}
|
|
18
17
|
export declare function buildDefinitionListQuery(flags: DefinitionListFlags): {
|
|
19
18
|
groq: string;
|
|
20
19
|
params: Record<string, unknown>;
|
|
21
20
|
};
|
|
22
|
-
export default class DefinitionList extends
|
|
21
|
+
export default class DefinitionList extends WorkflowCommand {
|
|
23
22
|
static description: string;
|
|
24
23
|
static examples: string[];
|
|
25
24
|
static flags: {
|
|
26
25
|
limit: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
|
|
27
|
-
|
|
26
|
+
name: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
28
27
|
tag: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
29
28
|
};
|
|
30
29
|
run(): Promise<void>;
|