@sanity/workflow-cli 0.8.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +99 -61
- package/dist/commands/abort.d.ts +5 -7
- package/dist/commands/abort.js +15 -14
- package/dist/commands/definition/delete.d.ts +10 -6
- package/dist/commands/definition/delete.js +8 -11
- package/dist/commands/definition/diff.d.ts +8 -1
- package/dist/commands/definition/diff.js +30 -24
- package/dist/commands/definition/list.d.ts +4 -4
- package/dist/commands/definition/list.js +8 -8
- package/dist/commands/definition/show.d.ts +0 -10
- package/dist/commands/definition/show.js +11 -20
- package/dist/commands/deploy.d.ts +58 -5
- package/dist/commands/deploy.js +134 -27
- package/dist/commands/diagnose.d.ts +5 -1
- package/dist/commands/diagnose.js +18 -28
- package/dist/commands/fire-action.d.ts +15 -28
- package/dist/commands/fire-action.js +37 -72
- package/dist/commands/list.d.ts +2 -2
- package/dist/commands/list.js +8 -8
- package/dist/commands/{retry-activity.d.ts → reset-activity.d.ts} +1 -1
- package/dist/commands/{retry-activity.js → reset-activity.js} +2 -2
- package/dist/commands/set-stage.d.ts +27 -3
- package/dist/commands/set-stage.js +80 -13
- package/dist/commands/show.d.ts +1 -0
- package/dist/commands/show.js +14 -8
- package/dist/commands/start.d.ts +59 -0
- package/dist/commands/start.js +169 -0
- package/dist/commands/tail.d.ts +3 -0
- package/dist/commands/tail.js +9 -5
- package/dist/lib/client.d.ts +11 -14
- package/dist/lib/client.js +45 -33
- package/dist/lib/context.d.ts +62 -0
- package/dist/lib/context.js +82 -0
- package/dist/lib/definitions.d.ts +38 -29
- package/dist/lib/definitions.js +45 -85
- package/dist/lib/diff.js +8 -0
- package/dist/lib/env.d.ts +0 -6
- package/dist/lib/env.js +3 -9
- package/dist/lib/fail.d.ts +6 -0
- package/dist/lib/fail.js +11 -1
- package/dist/lib/flags.d.ts +14 -2
- package/dist/lib/flags.js +24 -3
- package/dist/lib/load-config.d.ts +11 -0
- package/dist/lib/load-config.js +69 -0
- package/dist/lib/operation-args.d.ts +25 -7
- package/dist/lib/operation-args.js +12 -11
- package/dist/lib/ops-report.d.ts +19 -7
- package/dist/lib/ops-report.js +6 -7
- package/dist/lib/params.d.ts +7 -0
- package/dist/lib/params.js +26 -0
- package/dist/lib/select-deployment.d.ts +31 -0
- package/dist/lib/select-deployment.js +58 -0
- package/dist/lib/ui.d.ts +3 -1
- package/dist/lib/ui.js +1 -3
- package/oclif.manifest.json +145 -86
- package/package.json +6 -4
- package/dist/commands/move-stage.d.ts +0 -47
- package/dist/commands/move-stage.js +0 -77
- 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,74 @@ 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
|
+
## Configuration
|
|
28
|
+
|
|
29
|
+
The CLI is configured by a `sanity.workflow.ts` (or `.js`/`.mjs`) discovered in
|
|
30
|
+
the directory you run from. It exports a config built with
|
|
31
|
+
`defineWorkflowConfig`, declaring one **deployment** per environment:
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
import {defineWorkflowConfig} from '@sanity/workflow-engine/define'
|
|
35
|
+
|
|
36
|
+
import {articleReview, urlDraft} from './src/workflows.ts'
|
|
37
|
+
|
|
38
|
+
export default defineWorkflowConfig({
|
|
39
|
+
deployments: [
|
|
40
|
+
{
|
|
41
|
+
name: 'production',
|
|
42
|
+
tag: 'prod', // the environment partition the engine's docs are scoped to
|
|
43
|
+
workflowResource: {type: 'dataset', id: 'acme.workflows'}, // where those docs live
|
|
44
|
+
resourceAliases: [
|
|
45
|
+
// binds each `@<handle>:` a definition references to a physical resource
|
|
46
|
+
// in a DIFFERENT dataset from `workflowResource` (same-resource content
|
|
47
|
+
// needs no alias — see below)
|
|
48
|
+
{name: 'content', resource: {type: 'dataset', id: 'acme.content'}},
|
|
49
|
+
{name: 'assets', resource: {type: 'dataset', id: 'acme.assets'}},
|
|
50
|
+
],
|
|
51
|
+
definitions: [articleReview, urlDraft], // the batch this deployment ships
|
|
52
|
+
},
|
|
53
|
+
],
|
|
54
|
+
})
|
|
55
|
+
```
|
|
49
56
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
57
|
+
Pick a deployment with `--tag <tag>`; with a single deployment configured you
|
|
58
|
+
can omit it, and with several configured a bare `deploy` fails asking for
|
|
59
|
+
`--tag` or `--all-tags`. `--all-tags` deploys every deployment in the config in
|
|
60
|
+
one run: a failure in one doesn't stop the rest — the run continues, prints a
|
|
61
|
+
summary of what failed, and exits non-zero. The client's project + dataset are derived
|
|
62
|
+
from the deployment's `workflowResource`, and `deploy` expands each
|
|
63
|
+
definition's `@<handle>:` reference to the bound physical resource. An invalid
|
|
64
|
+
config fails with a clean, path-prefixed error before the command runs.
|
|
65
|
+
|
|
66
|
+
`resourceAliases` is only for content that lives in a _different_ resource from
|
|
67
|
+
`workflowResource`. A definition can reference a document in the **same**
|
|
68
|
+
resource by bare id, no alias needed — bare ids root at `workflowResource` at
|
|
69
|
+
runtime. Rule of thumb: **different resource → bind an alias and reference it as
|
|
70
|
+
`@<handle>:<id>`; same resource → use a bare id and omit `resourceAliases`
|
|
71
|
+
entirely.** So the simplest single-dataset setup is just a `workflowResource`
|
|
72
|
+
and `definitions`, with no `resourceAliases` at all.
|
|
73
|
+
|
|
74
|
+
| Var | Purpose |
|
|
75
|
+
| ------------------- | --------------------------------------------------------------------------- |
|
|
76
|
+
| `SANITY_AUTH_TOKEN` | CI fallback when there's no `sanity login` session (editor role for writes) |
|
|
77
|
+
| `SANITY_API_HOST` | Optional API host override (defaults to the prod API) |
|
|
78
|
+
|
|
79
|
+
Run through the `dev` / `cli` scripts and the CLI itself and your
|
|
80
|
+
`sanity.workflow.ts` load straight from TypeScript source: the scripts set
|
|
81
|
+
`NODE_OPTIONS='--conditions=development'` under `tsx`, and the config file is
|
|
82
|
+
transpiled on the fly by `jiti`. Definitions you author **inline** in the
|
|
83
|
+
config, or import by **relative path**, transpile the same way — no build step.
|
|
84
|
+
|
|
85
|
+
One caveat: a definition imported from a _separate workspace package_ (e.g.
|
|
86
|
+
`@sanity/workflow-examples`) resolves to that package's built `dist/`. `jiti`
|
|
87
|
+
applies its own module resolution and does **not** honour
|
|
88
|
+
`--conditions=development`, so it never picks up a package's `src/` export
|
|
89
|
+
condition — rebuild that package after editing it, or the config loads its
|
|
90
|
+
stale compiled output.
|
|
56
91
|
|
|
57
92
|
> [!NOTE]
|
|
58
93
|
> pnpm intercepts a handful of its own flag names (`--check`,
|
|
@@ -75,25 +110,25 @@ built or standalone CLI instead resolves the package's compiled `dist`.
|
|
|
75
110
|
|
|
76
111
|
## Command status
|
|
77
112
|
|
|
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>`
|
|
113
|
+
| Command | Status |
|
|
114
|
+
| -------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
|
|
115
|
+
| `deploy` | wired — calls `workflow.deployDefinitions` over the selected deployment's definitions |
|
|
116
|
+
| `deploy --all-tags` | wired — deploys every deployment in the config, continuing past per-deployment failures |
|
|
117
|
+
| `deploy --check` | wired — runs `validateDefinition` over the local batch + a duplicate-name check |
|
|
118
|
+
| `deploy --dry-run` | wired — fetches existing docs and renders a coloured JSON diff per change |
|
|
119
|
+
| `deploy --only <name>` | wired — filters deploy/check/dry-run to one definition by `name` |
|
|
120
|
+
| `start <name>` | wired — calls `workflow.startInstance` (`--field` for input fields, `--as` for attribution) |
|
|
121
|
+
| `list` | wired — `client.fetch` over `workflow.instance` documents (`--definition <name>` to filter) |
|
|
122
|
+
| `show <instance-id>` | wired — `client.getDocument` |
|
|
123
|
+
| `diagnose <instance-id>` | wired — one `workflow.evaluate` call, classifies why the instance is/isn't progressing |
|
|
124
|
+
| `tail <instance-id>` | wired — `client.listen()` over the instance, prints new history entries |
|
|
125
|
+
| `abort <instance-id>` | wired — calls `workflow.abortInstance` (hard stop: cancels pending effects, lifts guards) |
|
|
126
|
+
| `set-stage <instance-id> --to <stage>` | wired — calls `workflow.setStage` (admin override: skips declared transitions/filters; enter lifecycle + cascade still run) |
|
|
127
|
+
| `fire-action <instance-id>` | wired — `workflow.evaluate` lists actions; `workflow.fireAction` fires one |
|
|
128
|
+
| `definition list` | wired — `client.fetch` over `workflow.definition` documents |
|
|
129
|
+
| `definition show <name>` | wired — `client.fetch`, latest version unless `--version` |
|
|
130
|
+
| `definition diff <name>` | wired — diffs the in-code definition against the deployed latest (`--version` to pin) |
|
|
131
|
+
| `definition delete <name>` | wired — calls `workflow.deleteDefinition` (refuses on live instances unless `--cascade`) |
|
|
97
132
|
|
|
98
133
|
## Known gaps
|
|
99
134
|
|
|
@@ -101,15 +136,18 @@ built or standalone CLI instead resolves the package's compiled `dist`.
|
|
|
101
136
|
Sanity CLI as a plugin host. The package publishes as a standalone
|
|
102
137
|
`sanity-workflows` binary today; plugging into `sanity` as an oclif
|
|
103
138
|
plugin is a separate piece of work.
|
|
104
|
-
-
|
|
105
|
-
|
|
106
|
-
|
|
139
|
+
- **No true bypass.** `set-stage` is the engine's `setStage` admin
|
|
140
|
+
override: it skips the definition's declared transitions and filters, but
|
|
141
|
+
the target stage's enter lifecycle and the post-move cascade still run. A
|
|
142
|
+
command that also bypasses guards + enter effects (`force-stage`) needs
|
|
143
|
+
engine work that hasn't landed.
|
|
107
144
|
- **Actor identity.** Write paths default the actor to
|
|
108
145
|
`{ kind: "system", id: "workflow-cli" }` rather than resolving the real
|
|
109
|
-
user via `/users/me` (which a token-only CLI can't reach). `
|
|
110
|
-
|
|
111
|
-
user, so assignee- and role-gated
|
|
112
|
-
the Sanity CLI's auth flow, the real user can flow through
|
|
146
|
+
user via `/users/me` (which a token-only CLI can't reach). `start` and
|
|
147
|
+
`fire-action` take `--as <userId>` / `--as-role <role>` to attribute the
|
|
148
|
+
write to a real user, so assignee- and role-gated filters pass. Once we
|
|
149
|
+
host inside the Sanity CLI's auth flow, the real user can flow through
|
|
150
|
+
everywhere.
|
|
113
151
|
|
|
114
152
|
## Tech stack
|
|
115
153
|
|
package/dist/commands/abort.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Command } from '@oclif/core';
|
|
2
|
+
import { type WorkflowInstance } from '@sanity/workflow-engine';
|
|
2
3
|
export default class Abort extends Command {
|
|
3
4
|
static description: string;
|
|
4
5
|
static examples: string[];
|
|
@@ -12,19 +13,16 @@ export default class Abort extends Command {
|
|
|
12
13
|
run(): Promise<void>;
|
|
13
14
|
}
|
|
14
15
|
interface AbortOutcome {
|
|
15
|
-
|
|
16
|
-
instance:
|
|
17
|
-
currentStage: string;
|
|
18
|
-
completedAt?: string | undefined;
|
|
19
|
-
};
|
|
16
|
+
changed: boolean;
|
|
17
|
+
instance: Pick<WorkflowInstance, 'currentStage' | 'completedAt'>;
|
|
20
18
|
}
|
|
21
19
|
/**
|
|
22
20
|
* Turn an `abortInstance` result into the spinner message. Pure so the
|
|
23
|
-
*
|
|
21
|
+
* aborted / already-terminal permutations can be asserted without driving
|
|
24
22
|
* a real abort.
|
|
25
23
|
*/
|
|
26
24
|
export declare function abortReport(result: AbortOutcome): {
|
|
27
|
-
|
|
25
|
+
changed: boolean;
|
|
28
26
|
message: string;
|
|
29
27
|
};
|
|
30
28
|
export {};
|
package/dist/commands/abort.js
CHANGED
|
@@ -2,9 +2,8 @@ import { styleText } from 'node:util';
|
|
|
2
2
|
import { Args, Command, Flags } from '@oclif/core';
|
|
3
3
|
import { workflow } from '@sanity/workflow-engine';
|
|
4
4
|
import ora from 'ora';
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import { fail } from "../lib/fail.js";
|
|
5
|
+
import { resolveInstanceContext } from "../lib/context.js";
|
|
6
|
+
import { errorMessage, fail } from "../lib/fail.js";
|
|
8
7
|
import { tagFlags } from "../lib/flags.js";
|
|
9
8
|
import { buildOperationArgs } from "../lib/operation-args.js";
|
|
10
9
|
export default class Abort extends Command {
|
|
@@ -22,16 +21,19 @@ export default class Abort extends Command {
|
|
|
22
21
|
};
|
|
23
22
|
async run() {
|
|
24
23
|
const { args, flags } = await this.parse(Abort);
|
|
25
|
-
const
|
|
26
|
-
const client = loadClient();
|
|
24
|
+
const { client, scope } = await resolveInstanceContext(flags, args.instanceId);
|
|
27
25
|
const spinner = ora(`Aborting ${args.instanceId}…`).start();
|
|
28
26
|
try {
|
|
29
27
|
const result = await workflow.abortInstance({
|
|
30
28
|
client,
|
|
31
|
-
...buildOperationArgs(
|
|
29
|
+
...buildOperationArgs({
|
|
30
|
+
scope,
|
|
31
|
+
instanceId: args.instanceId,
|
|
32
|
+
reason: flags.reason,
|
|
33
|
+
}),
|
|
32
34
|
});
|
|
33
35
|
const report = abortReport(result);
|
|
34
|
-
if (!report.
|
|
36
|
+
if (!report.changed) {
|
|
35
37
|
spinner.warn(report.message);
|
|
36
38
|
return;
|
|
37
39
|
}
|
|
@@ -39,24 +41,23 @@ export default class Abort extends Command {
|
|
|
39
41
|
}
|
|
40
42
|
catch (error) {
|
|
41
43
|
spinner.fail('Abort rejected');
|
|
42
|
-
|
|
43
|
-
fail('abortInstance error:', message);
|
|
44
|
+
fail('abort error:', errorMessage(error));
|
|
44
45
|
}
|
|
45
46
|
}
|
|
46
47
|
}
|
|
47
48
|
/**
|
|
48
49
|
* Turn an `abortInstance` result into the spinner message. Pure so the
|
|
49
|
-
*
|
|
50
|
+
* aborted / already-terminal permutations can be asserted without driving
|
|
50
51
|
* a real abort.
|
|
51
52
|
*/
|
|
52
53
|
export function abortReport(result) {
|
|
53
54
|
const stage = styleText('bold', result.instance.currentStage);
|
|
54
|
-
if (!result.
|
|
55
|
+
if (!result.changed) {
|
|
55
56
|
const at = result.instance.completedAt ?? 'unknown';
|
|
56
57
|
return {
|
|
57
|
-
|
|
58
|
-
message: `abortInstance
|
|
58
|
+
changed: false,
|
|
59
|
+
message: `abortInstance changed nothing — instance is already terminal (at ${stage} since ${at})`,
|
|
59
60
|
};
|
|
60
61
|
}
|
|
61
|
-
return {
|
|
62
|
+
return { changed: true, message: `Aborted — instance hard-stopped at ${stage}` };
|
|
62
63
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Command } from '@oclif/core';
|
|
2
2
|
import { type DeleteDefinitionArgs, type DeleteDefinitionResult } from '@sanity/workflow-engine';
|
|
3
|
-
import { type
|
|
3
|
+
import { type EngineScope } from '../../lib/operation-args.ts';
|
|
4
4
|
export default class DefinitionDelete extends Command {
|
|
5
5
|
static description: string;
|
|
6
6
|
static examples: string[];
|
|
@@ -21,11 +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
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
24
|
+
export declare function buildDeleteArgs({ scope, name, flags, }: {
|
|
25
|
+
scope: EngineScope;
|
|
26
|
+
name: string;
|
|
27
|
+
flags: {
|
|
28
|
+
version?: number | undefined;
|
|
29
|
+
cascade: boolean;
|
|
30
|
+
reason?: string | undefined;
|
|
31
|
+
};
|
|
32
|
+
}): DeleteDefinitionArgs & EngineScope;
|
|
29
33
|
/** What the spinner names: the workflow, or one pinned version of it. */
|
|
30
34
|
export declare function deleteTargetLabel(args: Pick<DeleteDefinitionArgs, 'definition' | 'version'>): string;
|
|
31
35
|
/**
|
|
@@ -2,11 +2,10 @@ import { styleText } from 'node:util';
|
|
|
2
2
|
import { Args, Command, Flags } from '@oclif/core';
|
|
3
3
|
import { workflow, } from '@sanity/workflow-engine';
|
|
4
4
|
import ora from 'ora';
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import { fail } from "../../lib/fail.js";
|
|
5
|
+
import { resolveContext } from "../../lib/context.js";
|
|
6
|
+
import { errorMessage, fail } from "../../lib/fail.js";
|
|
8
7
|
import { tagFlags } from "../../lib/flags.js";
|
|
9
|
-
import { baseEngineArgs } from "../../lib/operation-args.js";
|
|
8
|
+
import { SYSTEM_ACTOR, baseEngineArgs } from "../../lib/operation-args.js";
|
|
10
9
|
export default class DefinitionDelete extends Command {
|
|
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 — ' +
|
|
@@ -34,9 +33,8 @@ 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
|
-
const deleteArgs = buildDeleteArgs(config, args.name, flags);
|
|
36
|
+
const { deployment, client } = await resolveContext(flags);
|
|
37
|
+
const deleteArgs = buildDeleteArgs({ scope: deployment, name: args.name, flags });
|
|
40
38
|
const spinner = ora(`Deleting definition ${deleteTargetLabel(deleteArgs)}…`).start();
|
|
41
39
|
try {
|
|
42
40
|
const result = await workflow.deleteDefinition({ client, ...deleteArgs });
|
|
@@ -44,8 +42,7 @@ export default class DefinitionDelete extends Command {
|
|
|
44
42
|
}
|
|
45
43
|
catch (error) {
|
|
46
44
|
spinner.fail('Delete rejected');
|
|
47
|
-
|
|
48
|
-
fail('deleteDefinition error:', message);
|
|
45
|
+
fail('definition delete error:', errorMessage(error));
|
|
49
46
|
}
|
|
50
47
|
}
|
|
51
48
|
}
|
|
@@ -55,9 +52,9 @@ export default class DefinitionDelete extends Command {
|
|
|
55
52
|
* engine's optionals reject an explicit `undefined` under
|
|
56
53
|
* `exactOptionalPropertyTypes`.
|
|
57
54
|
*/
|
|
58
|
-
export function buildDeleteArgs(
|
|
55
|
+
export function buildDeleteArgs({ scope, name, flags, }) {
|
|
59
56
|
return {
|
|
60
|
-
...baseEngineArgs(
|
|
57
|
+
...baseEngineArgs(scope, SYSTEM_ACTOR),
|
|
61
58
|
definition: name,
|
|
62
59
|
cascade: flags.cascade,
|
|
63
60
|
...(flags.version !== undefined ? { version: flags.version } : {}),
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { Command } from '@oclif/core';
|
|
2
|
+
import { type WorkflowDefinition } from '@sanity/workflow-engine';
|
|
2
3
|
export default class DefinitionDiff extends Command {
|
|
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,13 +1,11 @@
|
|
|
1
1
|
import { Args, Command, Flags } from '@oclif/core';
|
|
2
2
|
import { diffEntry } from '@sanity/workflow-engine';
|
|
3
3
|
import ora from 'ora';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import { loadValidatedDefinition } from "../../lib/definitions.js";
|
|
4
|
+
import { resolveContext } from "../../lib/context.js";
|
|
5
|
+
import { fetchDeployedDefinition, selectDefinitions, validateOrFail } from "../../lib/definitions.js";
|
|
7
6
|
import { diffReport } from "../../lib/diff.js";
|
|
8
|
-
import { fail } from "../../lib/fail.js";
|
|
9
7
|
import { tagFlags } from "../../lib/flags.js";
|
|
10
|
-
import {
|
|
8
|
+
import { deploymentToTarget } from "../../lib/select-deployment.js";
|
|
11
9
|
export default class DefinitionDiff extends Command {
|
|
12
10
|
static description = 'Diff an in-code definition against the deployed version (latest by default).';
|
|
13
11
|
static examples = [
|
|
@@ -15,7 +13,7 @@ export default class DefinitionDiff extends Command {
|
|
|
15
13
|
'<%= config.bin %> definition diff productLaunch --version 2',
|
|
16
14
|
];
|
|
17
15
|
static args = {
|
|
18
|
-
|
|
16
|
+
name: Args.string({ required: true, description: 'Workflow definition name.' }),
|
|
19
17
|
};
|
|
20
18
|
static flags = {
|
|
21
19
|
...tagFlags,
|
|
@@ -23,35 +21,43 @@ export default class DefinitionDiff extends Command {
|
|
|
23
21
|
};
|
|
24
22
|
async run() {
|
|
25
23
|
const { args, flags } = await this.parse(DefinitionDiff);
|
|
26
|
-
const
|
|
27
|
-
const def =
|
|
28
|
-
const
|
|
29
|
-
const spinner = ora(`Diffing ${args.
|
|
24
|
+
const { deployment, client } = await resolveContext(flags);
|
|
25
|
+
const def = selectDefinitionToDiff(deployment.definitions, args.name);
|
|
26
|
+
const target = deploymentToTarget(deployment);
|
|
27
|
+
const spinner = ora(`Diffing ${args.name} against deployed…`).start();
|
|
30
28
|
let deployed;
|
|
31
29
|
try {
|
|
32
|
-
|
|
33
|
-
|
|
30
|
+
// An absent latest is a legitimate "create" diff, so `undefined`
|
|
31
|
+
// flows straight into the diff entry.
|
|
32
|
+
deployed = await fetchDeployedDefinition({
|
|
33
|
+
client,
|
|
34
|
+
name: args.name,
|
|
35
|
+
tag: target.tag,
|
|
36
|
+
version: flags.version,
|
|
37
|
+
});
|
|
38
|
+
spinner.succeed(`Diffed ${args.name}`);
|
|
34
39
|
}
|
|
35
40
|
catch (error) {
|
|
36
41
|
spinner.fail('Diff failed');
|
|
37
42
|
throw error;
|
|
38
43
|
}
|
|
39
|
-
for (const line of diffReport([diffEntry(def, deployed,
|
|
44
|
+
for (const line of diffReport([diffEntry({ def, latestRaw: deployed, target })]))
|
|
40
45
|
this.log(line);
|
|
41
46
|
}
|
|
42
47
|
}
|
|
43
48
|
/**
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
* superseded — latest unless pinned via `version`. An absent latest is a
|
|
48
|
-
* legitimate "create" diff, but an absent *pinned* version is an error.
|
|
49
|
+
* Pick the single definition to diff by name and validate it, failing cleanly
|
|
50
|
+
* when it's missing from the deployment or doesn't pass validation. Extracted
|
|
51
|
+
* from `run` so the command's orchestration stays thin and this stays testable.
|
|
49
52
|
*/
|
|
50
|
-
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
53
|
+
export function selectDefinitionToDiff(definitions, name) {
|
|
54
|
+
const [def] = selectDefinitions(definitions, { only: name });
|
|
55
|
+
// selectDefinitions already fails on no-match (naming the available defs), so
|
|
56
|
+
// a named lookup always yields at least one — this narrows the type, it is
|
|
57
|
+
// not a reachable failure.
|
|
58
|
+
if (def === undefined) {
|
|
59
|
+
throw new Error(`selectDefinitions returned no match for "${name}"`);
|
|
55
60
|
}
|
|
56
|
-
|
|
61
|
+
validateOrFail([def]);
|
|
62
|
+
return def;
|
|
57
63
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { Command } from '@oclif/core';
|
|
2
|
-
import { type
|
|
2
|
+
import { type WorkflowLifecycle } from '@sanity/workflow-engine';
|
|
3
3
|
interface DefinitionListFlags {
|
|
4
4
|
tag?: string | undefined;
|
|
5
|
-
|
|
5
|
+
name?: string | undefined;
|
|
6
6
|
limit: number;
|
|
7
7
|
}
|
|
8
8
|
export interface DefinitionListRow {
|
|
@@ -10,7 +10,7 @@ export interface DefinitionListRow {
|
|
|
10
10
|
version: number;
|
|
11
11
|
title: string;
|
|
12
12
|
tag: string;
|
|
13
|
-
|
|
13
|
+
lifecycle?: WorkflowLifecycle;
|
|
14
14
|
stageCount: number;
|
|
15
15
|
inFlightCount: number;
|
|
16
16
|
totalInstances: number;
|
|
@@ -24,7 +24,7 @@ export default class DefinitionList extends Command {
|
|
|
24
24
|
static examples: string[];
|
|
25
25
|
static flags: {
|
|
26
26
|
limit: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
|
|
27
|
-
|
|
27
|
+
name: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
28
28
|
tag: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
29
29
|
};
|
|
30
30
|
run(): Promise<void>;
|
|
@@ -2,7 +2,7 @@ import { Command, Flags } from '@oclif/core';
|
|
|
2
2
|
import { WORKFLOW_DEFINITION_TYPE, WORKFLOW_INSTANCE_TYPE, isStartableDefinition, tagScopeFilter, } from '@sanity/workflow-engine';
|
|
3
3
|
import { Table } from 'console-table-printer';
|
|
4
4
|
import logSymbols from 'log-symbols';
|
|
5
|
-
import {
|
|
5
|
+
import { resolveReadContext } from "../../lib/context.js";
|
|
6
6
|
import { tagFlags } from "../../lib/flags.js";
|
|
7
7
|
export function buildDefinitionListQuery(flags) {
|
|
8
8
|
const params = { limit: flags.limit };
|
|
@@ -21,8 +21,8 @@ export function buildDefinitionListQuery(flags) {
|
|
|
21
21
|
params.tag = flags.tag;
|
|
22
22
|
filters.push(tagScopeFilter());
|
|
23
23
|
}
|
|
24
|
-
if (flags
|
|
25
|
-
params.name = flags
|
|
24
|
+
if (flags.name) {
|
|
25
|
+
params.name = flags.name;
|
|
26
26
|
filters.push('name == $name');
|
|
27
27
|
}
|
|
28
28
|
const instanceMatch = instanceFilters.join(' && ');
|
|
@@ -31,7 +31,7 @@ export function buildDefinitionListQuery(flags) {
|
|
|
31
31
|
version,
|
|
32
32
|
title,
|
|
33
33
|
tag,
|
|
34
|
-
|
|
34
|
+
lifecycle,
|
|
35
35
|
"stageCount": count(stages),
|
|
36
36
|
"inFlightCount": count(*[${instanceMatch} && !defined(completedAt)]),
|
|
37
37
|
"totalInstances": count(*[${instanceMatch}])
|
|
@@ -50,14 +50,14 @@ export default class DefinitionList extends Command {
|
|
|
50
50
|
description: 'The maximum number of definitions to return.',
|
|
51
51
|
default: 100,
|
|
52
52
|
}),
|
|
53
|
-
|
|
54
|
-
description: 'Filter to a single workflow definition name (e.g. product-launch)',
|
|
53
|
+
name: Flags.string({
|
|
54
|
+
description: 'Filter to a single workflow definition name (e.g. product-launch).',
|
|
55
55
|
}),
|
|
56
56
|
};
|
|
57
57
|
async run() {
|
|
58
58
|
const { flags } = await this.parse(DefinitionList);
|
|
59
|
+
const { client } = await resolveReadContext(flags);
|
|
59
60
|
const { groq, params } = buildDefinitionListQuery(flags);
|
|
60
|
-
const client = loadClient();
|
|
61
61
|
const rows = await client.fetch(groq, params);
|
|
62
62
|
if (!rows.length) {
|
|
63
63
|
this.log(`${logSymbols.info} no definitions found`);
|
|
@@ -81,7 +81,7 @@ export default class DefinitionList extends Command {
|
|
|
81
81
|
// `definition show` title). Only declared keys are passed — console-table-
|
|
82
82
|
// printer renders any extra key as a stray column.
|
|
83
83
|
//
|
|
84
|
-
// `child
|
|
84
|
+
// `lifecycle: 'child'` defs are spawn-only; the Startable column tells an admin
|
|
85
85
|
// which definitions a human can actually start standalone.
|
|
86
86
|
table.addRow({
|
|
87
87
|
workflow: `${r.name} v${r.version}`,
|
|
@@ -1,14 +1,5 @@
|
|
|
1
1
|
import { Command } from '@oclif/core';
|
|
2
2
|
import { type WorkflowDefinition } from '@sanity/workflow-engine';
|
|
3
|
-
interface DefinitionShowFlags {
|
|
4
|
-
name: string;
|
|
5
|
-
tag?: string | undefined;
|
|
6
|
-
version?: number | undefined;
|
|
7
|
-
}
|
|
8
|
-
export declare function buildDefinitionShowQuery(flags: DefinitionShowFlags): {
|
|
9
|
-
groq: string;
|
|
10
|
-
params: Record<string, unknown>;
|
|
11
|
-
};
|
|
12
3
|
export default class DefinitionShow extends Command {
|
|
13
4
|
static description: string;
|
|
14
5
|
static args: {
|
|
@@ -32,4 +23,3 @@ export declare function definitionHeader(def: WorkflowDefinition & {
|
|
|
32
23
|
* activity's actions. Pure so it can be asserted without driving the oclif command.
|
|
33
24
|
*/
|
|
34
25
|
export declare function describeDefinition(def: WorkflowDefinition): string[];
|
|
35
|
-
export {};
|