@sanity/workflow-cli 0.32.0 → 0.34.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -3,23 +3,31 @@
3
3
  Command-line tool for deploying, inspecting, and administering Sanity workflow
4
4
  definitions and instances.
5
5
 
6
- > [!WARNING]
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`.
6
+ The package is in early access and is publicly available on npm. Commands that
7
+ access Sanity use the resources in `sanity.workflow.ts` and authenticate with a
8
+ login session or an API token. `deploy --check` validates definitions offline.
10
9
 
11
10
  ## Run
12
11
 
13
- ```bash
14
- pnpm --filter @sanity/workflow-cli dev <command> [...args]
12
+ Use Node.js 20.12 or later. Install the CLI and its engine peer from the same
13
+ Workflows release in your project:
15
14
 
16
- # Examples:
17
- pnpm --filter @sanity/workflow-cli dev --help
18
- pnpm --filter @sanity/workflow-cli dev deploy --dry-run
19
- pnpm --filter @sanity/workflow-cli dev list --include-completed
20
- pnpm --filter @sanity/workflow-cli dev show wf-instance.abc123
15
+ ```sh
16
+ npm install --save-dev @sanity/workflow-cli @sanity/workflow-engine
17
+ npx @sanity/workflow-cli --help
21
18
  ```
22
19
 
20
+ After authentication and [configuration](#configuration), run commands through
21
+ the project-local package:
22
+
23
+ ```sh
24
+ npx @sanity/workflow-cli deploy --dry-run
25
+ npx @sanity/workflow-cli list --include-completed
26
+ npx @sanity/workflow-cli show INSTANCE_ID
27
+ ```
28
+
29
+ Replace `INSTANCE_ID` with the workflow instance ID returned by `list`.
30
+
23
31
  The package is an [oclif](https://oclif.io) plugin: every command's canonical
24
32
  id nests under the `workflows` topic
25
33
  (`sanity-workflows workflows deploy`), so mounting the package into
@@ -32,9 +40,9 @@ binary's stable surface — its production and development entrypoints
32
40
  resolves the command. Those short forms are **not** registered as oclif aliases,
33
41
  so a host mount does not pollute the host root.
34
42
 
35
- Authenticate once with `sanity login` (the CLI reads that session token); for
36
- CI, set `SANITY_AUTH_TOKEN` instead. Then create a `sanity.workflow.ts` in the
37
- directory you run from see [Configuration](#configuration).
43
+ Authenticate with `npx sanity@latest login`; the CLI reads that session token.
44
+ For CI, set `SANITY_AUTH_TOKEN` instead. Create a `sanity.workflow.ts` in the
45
+ directory you run from, as described in [Configuration](#configuration).
38
46
 
39
47
  The token must span **every resource the workflow references**, not just the
40
48
  workflow resource. Runtime commands build one client from the deployment
@@ -52,28 +60,29 @@ the directory you run from. It exports a config built with
52
60
  environment):
53
61
 
54
62
  ```ts
63
+ import type {WorkflowDeploymentInput} from '@sanity/workflow-engine'
55
64
  import {defineWorkflowConfig} from '@sanity/workflow-engine/define'
56
65
 
57
66
  import {articleReview, urlDraft} from './src/workflows.ts'
58
67
 
59
- export default defineWorkflowConfig({
60
- deployments: [
61
- {
62
- expectedMinReaderModel: 4,
63
- name: 'production', // the deployment's unique identity (lowercase letters, digits, dashes)
64
- tag: 'prod', // the environment partition the engine's docs are scoped to
65
- workflowResource: {type: 'dataset', id: 'acme.workflows'}, // where those docs live
66
- resourceAliases: [
67
- // binds each `@<handle>:` a definition references to a physical resource
68
- // in a DIFFERENT dataset from `workflowResource` (same-resource content
69
- // needs no alias — see below)
70
- {name: 'content', resource: {type: 'dataset', id: 'acme.content'}},
71
- {name: 'assets', resource: {type: 'dataset', id: 'acme.assets'}},
72
- ],
73
- definitions: [articleReview, urlDraft], // the batch this deployment ships
74
- },
68
+ // Export each deployment under its own `name`, then list the exports in the
69
+ // config. `blueprint generate` requires these named exports; nothing else does.
70
+ export const production = {
71
+ expectedMinReaderModel: 4,
72
+ name: 'production', // the deployment's unique identity (lowercase letters, digits, dashes)
73
+ tag: 'prod', // the environment partition the engine's docs are scoped to
74
+ workflowResource: {type: 'dataset', id: 'acme.workflows'}, // where those docs live
75
+ resourceAliases: [
76
+ // binds each `@<handle>:` a definition references to a physical resource
77
+ // in a DIFFERENT dataset from `workflowResource` (same-resource content
78
+ // needs no alias — see below)
79
+ {name: 'content', resource: {type: 'dataset', id: 'acme.content'}},
80
+ {name: 'assets', resource: {type: 'dataset', id: 'acme.assets'}},
75
81
  ],
76
- })
82
+ definitions: [articleReview, urlDraft], // the batch this deployment ships
83
+ } satisfies WorkflowDeploymentInput
84
+
85
+ export default defineWorkflowConfig({deployments: [production]})
77
86
  ```
78
87
 
79
88
  `name` is the deployment's identity: unique across the config and constrained
@@ -82,16 +91,20 @@ repeat, as long as no two deployments share both a `workflowResource` and a
82
91
  `tag` — that pair is the storage partition, and the config rejects the
83
92
  collision naming both entries.
84
93
 
85
- Pick a deployment with `--deployment <name>`; with a single deployment configured you
86
- can omit it. `--tag <tag>` also works on single-deployment commands while the
87
- tag names exactly one deployment on `deploy` it targets every deployment
88
- carrying the tag (a tag is an environment group). With several configured, a
89
- bare interactive run presents a keyboard-driven deployment selector (by name,
90
- tag alongside) on `deploy`, `start`, and `definition diff`/`delete`, and on
91
- the instance-targeted commands (`abort`, `set-stage`, `reset-activity`,
92
- `fire-action`, `diagnose`) when the config spans several resources. In CI or another
93
- non-interactive shell, the command fails asking for `--deployment` or `--tag`
94
- (`deploy` also suggests `--all-tags`) instead of blocking for input.
94
+ For `deploy`, `start`, `definition diff`, and `definition delete`, select a
95
+ deployment with `--deployment <name>`. With one deployment configured, you can
96
+ omit the selector. `--tag <tag>` selects one deployment when the tag is unique;
97
+ on `deploy`, it targets every deployment carrying the tag. With several
98
+ deployments and no selector, these commands prompt for a deployment in an
99
+ interactive terminal. In a non-interactive shell, they fail asking for
100
+ `--deployment` or `--tag`; `deploy` also suggests `--all-tags`.
101
+
102
+ The instance-targeted commands `abort`, `set-stage`, `reset-activity`,
103
+ `fire-action`, and `diagnose` locate the instance by ID across configured
104
+ resources. They do not prompt for a deployment. Their optional `--deployment`
105
+ and `--tag` flags narrow the resources searched; the operation's tag comes from
106
+ the loaded instance.
107
+
95
108
  `--all-tags` deploys every deployment in the config in one run: a failure in one doesn't
96
109
  stop the rest — the run continues, prints a summary of what failed, and exits
97
110
  non-zero. The client's project + dataset are derived from the deployment's
@@ -173,6 +186,8 @@ Invoke as `sanity-workflows <command>` (or `sanity workflows <command>` once the
173
186
  | `set-stage <instance-id> --to <stage>` | Move an instance to a stage, skipping declared transitions. The target stage's enter lifecycle still runs. |
174
187
  | `reset-activity <instance-id> <activity>` | Re-run a failed activity, or `--skip` it so a gated transition can fire. |
175
188
  | `fire-action <instance-id>` | Fire an action on a waiting activity. Omit `--action` to list what can be fired. |
189
+ | `blueprint generate` | Write the Sanity Blueprints runtime the definitions require next to `sanity.workflow.ts`. |
190
+ | `blueprint generate --check` | Verify that runtime still matches the definitions. Writes nothing; exits non-zero on any difference. |
176
191
  | `definition list` | List deployed definitions. |
177
192
  | `definition show <name>` | Show a deployed definition (latest version, or `--version`). |
178
193
  | `definition diff <name>` | Diff the in-code definition against what is deployed. |
@@ -180,24 +195,320 @@ Invoke as `sanity-workflows <command>` (or `sanity workflows <command>` once the
180
195
  | `nuke --deployment <name>` / `nuke --tag <tag>` | Dev reset: delete every engine-owned document for that tag (instances, definitions, guards). Prints a plan, then asks for confirmation. Content documents are never touched. |
181
196
  | `nuke --instance <id>` | Dev reset: delete one finished instance and its guards. Refuses in-flight instances (abort first). |
182
197
 
198
+ ## Generating the Blueprints runtime
199
+
200
+ **Experimental, and not ready for production use in this release.** Both
201
+ commands are available so you can try the generated runtime and give feedback;
202
+ their flags and their output may still change. What you would hit today:
203
+
204
+ - The start watcher runs on every create and update of a subject type, not only
205
+ on writes that can actually start a workflow, so it wakes for documents that
206
+ already have an instance or match no definition. The heartbeat is scheduled
207
+ every minute and evaluates every in-flight instance in the tag when it runs;
208
+ how often the platform runs it is the plan limit described below.
209
+ - `sanity-workflows deploy` does not check the generated tree, so drift between
210
+ your definitions and the tree on disk is caught only by
211
+ `blueprint generate --check` in CI.
212
+ - Definitions and functions deploy in two steps, definitions first, until the
213
+ Blueprints service registers the `sanity.workflow` resource.
214
+ - A generated file cannot be taken over: editing one is drift, and the next
215
+ generation rewrites it.
216
+ - A retry policy is not checked against its function's timeout at generation, so
217
+ an effect can be configured to retry for longer than its function may run.
218
+
219
+ All of these are tracked, and the next release is the bar for using this in
220
+ production.
221
+
222
+ A definition that declares an effect, reads `$now`, or starts autonomously needs
223
+ server-side code: something has to run the effect handlers, sample deadlines, and
224
+ start instances from document writes. `blueprint generate` derives that runtime
225
+ from the definitions and writes it next to `sanity.workflow.ts`, so
226
+ `npx sanity blueprints deploy` has something to deploy. A purely interactive
227
+ workflow needs none of it, and the command says so.
228
+
229
+ Five prerequisites:
230
+
231
+ - **Each deployment is exported under its own `name`, and the export is the
232
+ deployment object itself**, as the [Configuration](#configuration) example
233
+ does: every generated module imports its deployment from `./sanity.workflow`
234
+ by that name. The command refuses to write anything until each name is
235
+ exported, and names the export to add. It checks the name only, so make sure
236
+ each export is the deployment your config lists. A typecheck of the generated
237
+ modules catches a wrong export only where a generated function uses it, and a
238
+ deployment that needs no functions emits none.
239
+ - **`sanity.workflow.ts` is importable inside a deployed function.** Every
240
+ generated module imports it, and each function bundles it, so the file runs
241
+ where your shell and your `.env` do not exist. Reading an absent variable
242
+ there yields `undefined`; what fails is a config that **throws** when a
243
+ variable is missing, or requires one to build the deployment, because that
244
+ runs on init before your handler is reached. Keep the deployment's resource
245
+ coordinates literal in the file and read secrets inside your handlers.
246
+ - **The Blueprints packages the generated modules import are installed**,
247
+ alongside the CLI and its peers.
248
+ - **Your definitions are deployed before the functions.** The generated
249
+ `workflows.blueprint.ts` carries the definitions entry commented out, because
250
+ the Blueprints service does not accept the `sanity.workflow` resource type
251
+ yet, so `npx sanity blueprints deploy` does not deploy them. Run
252
+ `npx @sanity/workflow-cli deploy` first: a function that starts or advances an
253
+ instance resolves its definition from the Content Lake, and finds nothing
254
+ until that deploy has run.
255
+ - **A generated heartbeat needs an organization-scoped stack.** The heartbeat is
256
+ a scheduled function, and the platform only accepts one on a stack in
257
+ organization scope. A project-scoped stack refuses it, and with it the whole
258
+ deploy. See
259
+ [Promote a stack to organization scope](https://www.sanity.io/docs/blueprints/promote-stack-to-organization-scope).
260
+ How often it may run is a plan limit rather than something the generator
261
+ chooses — see [the schedule](#the-generated-heartbeats-schedule) below.
262
+
263
+ ```sh
264
+ npm install --save-dev @sanity/blueprints @sanity/functions @sanity/workflow-blueprint
265
+ npx @sanity/workflow-cli blueprint generate
266
+ npx @sanity/workflow-cli deploy
267
+ npx sanity blueprints deploy
268
+ ```
269
+
270
+ Each generated function's `package.json` copies the specifications your project
271
+ declares for `@sanity/functions`, `@sanity/workflow-blueprint`, and
272
+ `@sanity/workflow-engine`. A package you declare nowhere fails the command
273
+ rather than being stamped with a guessed range.
274
+
275
+ The command prints what the definitions need and why, then every file it wrote:
276
+
277
+ ```
278
+ $ npx @sanity/workflow-cli blueprint generate
279
+ Runtime needs:
280
+
281
+ ▸ prod (prod)
282
+ hosting: this deployment declares function, and each level below inherits it
283
+ → function workflow(s): article-review
284
+ → function effect(s): notify-reviewer
285
+ effects: 1 declared — notify-reviewer
286
+ → drain function wf-prod-drain-effects runs notify-reviewer
287
+ triggered by instance writes matching _type == "sanity.workflow.instance" && tag == "prod" && count(pendingEffects[!defined(claim)]) > 0
288
+ clock: samples $now deadlines; sweeps stale effect claims left by a dead drain
289
+ article-review · working · action-when · $fields.subject.embargoAt <= $now
290
+ → scheduled function wf-prod-heartbeat on * * * * *
291
+ a scheduled function runs at most as often as your organization's plan allows — every minute on Enterprise, hourly on Growth, daily on Free (https://www.sanity.io/docs/functions/functions-introduction) — and a schedule below that threshold is deployed and never invoked
292
+ autonomous starts: 1 definition(s) — article-review
293
+ → start watcher wf-prod-start-workflows on acme.workflows matching _type in ["article"]
294
+
295
+ Generated tree:
296
+ created workflows.blueprint.ts
297
+ created functions/wf-prod-drain-effects/index.ts
298
+ created functions/wf-prod-drain-effects/package.json
299
+ created functions/wf-prod-heartbeat/index.ts
300
+ created functions/wf-prod-heartbeat/package.json
301
+ created functions/wf-prod-start-workflows/index.ts
302
+ created functions/wf-prod-start-workflows/package.json
303
+ created effect-handlers/all.ts
304
+ created effect-handlers/notify-reviewer.ts yours now — implement the effect
305
+ created sanity.blueprint.ts the workflow resources spread
306
+ ```
307
+
308
+ Fill in each `effect-handlers/<effect name>.ts`: they are yours, scaffolded once
309
+ and never overwritten. Everything marked `created`, `updated`, or `unchanged`
310
+ under `workflows.blueprint.ts`, `functions/`, and `effect-handlers/all.ts` is
311
+ regenerated on every run, so edits there do not survive. Your own
312
+ `sanity.blueprint.ts` receives the `workflowResources` import and spread once;
313
+ after that the command leaves it alone. The command never deletes a file. A
314
+ handler for an effect you removed, a scaffold blocked by a path that differs
315
+ only in case, and a blueprint it could not wire are reported for you to resolve.
316
+
317
+ The command covers every deployment in the config, because the emitted
318
+ `workflows.blueprint.ts` declares them all.
319
+
320
+ ### Where each workflow runs
321
+
322
+ The report opens with the hosting each `runtime` block resolved to, after every
323
+ level has inherited from the one above it. It matters because a name listed
324
+ under `durableFunction` or `selfHosted` has no emitted function to point at:
325
+
326
+ - `function` — the drain, heartbeat, and start watcher above.
327
+ - `durableFunction` — nothing is emitted yet. The rest of the deployment emits
328
+ normally around it.
329
+ - `selfHosted` — nothing is emitted, and the report ends with what your own
330
+ process must do instead: which deadlines to tick, which effects to drain,
331
+ which parents to re-evaluate when a child settles, and which autonomous
332
+ definitions to start. A self-hosted deadline is never a reason to emit a
333
+ heartbeat, so it is listed there and not under `clock`; an emitted heartbeat
334
+ ticks every in-flight instance in the tag, whatever hosts each workflow.
335
+
336
+ ### The generated heartbeat's schedule
337
+
338
+ The heartbeat is emitted on `* * * * *`, one run a minute, and how often the
339
+ platform will actually run it is a plan limit rather than something the
340
+ generator can choose: Sanity documents the threshold as
341
+ [Free daily, Growth hourly, Enterprise minutely](https://www.sanity.io/docs/functions/functions-introduction).
342
+ Below Enterprise the platform accepts the scheduled function and does not run
343
+ it, so a workflow whose deadlines depend on the heartbeat does not advance and
344
+ nothing reports an error. Check your plan's cadence before relying on a `$now`
345
+ deadline in a deployed workflow.
346
+
347
+ ### Checking for drift in CI
348
+
349
+ `blueprint generate --check` renders the same runtime in memory and compares it
350
+ with the files on disk. It writes nothing and exits zero when they match:
351
+
352
+ ```
353
+ $ npx @sanity/workflow-cli blueprint generate --check
354
+ ✔ the generated tree matches the definitions
355
+ ```
356
+
357
+ Otherwise it exits `1` and names every file, so the log alone says what to fix:
358
+
359
+ ```
360
+ $ npx @sanity/workflow-cli blueprint generate --check
361
+ ✖ The generated tree does not match the definitions (1):
362
+ differs effect-handlers/all.ts
363
+ Run `sanity-workflows blueprint generate` to bring it back in line.
364
+ $ echo $?
365
+ 1
366
+ ```
367
+
368
+ A file reports `missing` when nothing is at its path, `differs` when the file
369
+ there has other contents, and a handler for a declared effect reports `missing`
370
+ when you have not written it yet. Add the command to the job that builds your
371
+ blueprint and a definition change can no longer ship without the runtime it
372
+ needs.
373
+
183
374
  ## Telemetry
184
375
 
185
- The CLI collects usage telemetry through Sanity's standard pipeline: a
186
- per-command trace (`Workflows CLI Command Executed` the command id, the
187
- names of declared flags used, never their values, and a success flag) plus
188
- the engine's adoption events from the operations it drives. Consent is the
189
- account-wide status managed by `npx sanity telemetry enable|disable|status`;
190
- CI and trueish `DO_NOT_TRACK` suppress everything (except a deploy that may share
376
+ The CLI collects usage telemetry through Sanity's standard pipeline. Consent
377
+ is the account-wide status managed by `npx sanity telemetry enable|disable|status`.
378
+ CI and trueish `DO_NOT_TRACK` suppress everything except a deploy that may share
191
379
  new definitions, which forces that deploy's telemetry unless `--no-share-defs`
192
- is passed — see [Definition sharing](#definition-sharing)),
193
- and a session that isn't logged in sends nothing. A one-time notice on stderr
194
- discloses collection on first use.
380
+ is passed — see [Definition sharing](#definition-sharing). A session that isn't
381
+ logged in sends nothing. A one-time notice on stderr discloses collection on
382
+ first use.
383
+
384
+ Payloads never include customer-authored strings: no flag values, argv
385
+ tokens, error text, definition names, stage names, GROQ, or document content.
386
+ The one instance-scoped exception the engine already ships is `instanceId`
387
+ (the instance document `_id`). Effect events also carry the author-chosen
388
+ effect name. Definition sharing sends the definition document to a first-party
389
+ feedback endpoint, not through this pipeline; telemetry only records the
390
+ content-free markers below.
391
+
392
+ Each event includes `context.surface: 'cli'` and its process execution mode
393
+ in `context.environment`. `NODE_ENV=production` reports `production`;
394
+ `development` and `test` report `development`. Unset, empty, and unrecognized
395
+ values default to `production` for installed CLI and MCP runs. The SDK reports
396
+ build mode and defaults to `development` instead.
397
+
398
+ When comparing activity across surfaces, group or filter by `context.surface`
399
+ alongside `context.environment`. Environment does not identify a production
400
+ dataset or deployment; the API host, dataset name, and deployment tag do not
401
+ set it. Existing telemetry consent and opt-out settings still apply.
195
402
 
196
403
  One extension point: supply your own logger as `telemetry` in
197
404
  `sanity.workflow.ts` and the built-in pipeline is not constructed at all —
198
405
  every event flows to your implementation unconditionally (CI included), and
199
406
  consent, suppression, and destination become its business.
200
407
 
408
+ ### Session properties
409
+
410
+ The built-in store attaches these user properties once per invocation (joined
411
+ to events by session id). User identity is not in the payload; the intake
412
+ service resolves the sender from the authenticated session.
413
+
414
+ | Property | What it is |
415
+ | ----------------- | ------------------------------------------------------------------------------------------------ |
416
+ | `surface` | Always `'cli'`. |
417
+ | `machinePlatform` | Node `process.platform` (for example `darwin`, `linux`). |
418
+ | `cpuArchitecture` | Node `process.arch`. |
419
+ | `runtime` | Always `'node'` for this CLI. |
420
+ | `runtimeVersion` | Node `process.version`. |
421
+ | `cliVersion` | The CLI package version, when oclif supplied it. |
422
+ | `projectId` | Project id of the first dataset-backed deployment in `sanity.workflow.ts`. |
423
+ | `dataset` | Dataset of that same deployment. |
424
+ | `orgId` | Organization id for that project, when the lookup succeeds before the command flushes. Optional. |
425
+
426
+ ### CLI events
427
+
428
+ These events are defined by this package.
429
+
430
+ #### `Workflows CLI Command Executed` (version 1)
431
+
432
+ One trace per workflows command. The built-in store starts the trace in the
433
+ prerun hook with `groupOrCommand` set to the oclif command id (for example
434
+ `workflows:deploy`, `workflows:definition:list`, `workflows:fire-action`) and
435
+ completes it in the finally hook. A config-supplied logger receives the same
436
+ payload as a log, with no trace. Host commands outside the `workflows` topic
437
+ do not emit.
438
+
439
+ | Field | Type | What is collected |
440
+ | --------- | ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
441
+ | `command` | `string` | The oclif command id (`workflows:…`). Never argv free-text. |
442
+ | `flags` | `string[]` | Names of **declared** flags present on the invocation, sorted. Long form, `--no-name`, and short chars. Never values, never undeclared tokens, never tokens after `--`. |
443
+ | `success` | `boolean` | `true` when the command finished without an oclif error. |
444
+
445
+ #### `Workflows Definition Shared` (version 1)
446
+
447
+ One content-free marker per newly created definition version that actually
448
+ reached Sanity's definition-feedback endpoint. Unchanged redeploys, dry runs,
449
+ and `--check` never emit it. The document itself is not in this event.
450
+
451
+ | Field | Type | What is collected |
452
+ | ------------------ | ---------- | ---------------------------------------------------------------------------------------------------- |
453
+ | `contentHash` | `string` | Content fingerprint of the donated definition. |
454
+ | `deployId` | `string` | Random run id shared with that invocation's `Workflows Definition Deployed` events. |
455
+ | `stageCount` | `number` | Declared stages. |
456
+ | `activityCount` | `number` | Activities across those stages. |
457
+ | `actionCount` | `number` | Actions across those activities. |
458
+ | `transitionCount` | `number` | Declared transitions. |
459
+ | `fieldCount` | `number` | Declared field entries at workflow, stage, and activity scope. |
460
+ | `activityKinds` | `string[]` | Distinct effective activity kinds, sorted: `user`, `service`, `script`, `manual`, `receive`. |
461
+ | `fieldKinds` | `string[]` | Distinct declared field kinds, sorted (engine field kinds such as `string`, `subject`, `assignees`). |
462
+ | `guardCount` | `number` | Guards declared on stages. |
463
+ | `effectCount` | `number` | Effects declared on actions. |
464
+ | `subworkflowCount` | `number` | Actions that carry a `spawn` block. |
465
+ | `lifecycle` | `string` | `'standalone'` or `'child'`. |
466
+
467
+ #### `Workflows Definition Sharing Decided` (version 2)
468
+
469
+ One event per `deploy` that created at least one new definition version,
470
+ whether or not anything was donated. Measures opt-out rate without carrying
471
+ the document.
472
+
473
+ | Field | Type | What is collected |
474
+ | ----------------- | --------- | ----------------------------------------------------------------------------------------------------------------------------- |
475
+ | `decision` | `string` | `'opt-in'` (`--share-defs`), `'opt-out'` (`--no-share-defs`), or `'default'` (neither flag). |
476
+ | `shared` | `boolean` | Whether a donation POST reached the endpoint. `false` for any non-sharing decision, and for a chosen share whose POST failed. |
477
+ | `definitionCount` | `number` | Newly created definition versions this invocation could have donated. |
478
+
479
+ ### Engine events the CLI records
480
+
481
+ Write commands pass the CLI logger into the engine, so a successful operation
482
+ also records the engine's adoption events on the same session. Read commands
483
+ (`list`, `show`, `diagnose`, `tail`, `definition list` / `show` / `diff`) do
484
+ not emit engine events. `nuke` deletes engine-owned documents outside those
485
+ verbs and does not emit them either.
486
+
487
+ Every instance-scoped engine event includes:
488
+
489
+ | Field | Type | What is collected |
490
+ | ----------------------- | -------- | -------------------------------------------------------------------------------------------------- |
491
+ | `definitionContentHash` | `string` | Content fingerprint of the pinned definition. Omitted when the definition predates fingerprinting. |
492
+ | `instanceId` | `string` | Instance document `_id`. |
493
+
494
+ | Event | Version | When the CLI emits it | Additional payload |
495
+ | ------------------------------- | ------- | ------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
496
+ | `Workflows Definition Deployed` | 1 | `deploy` for each definition (created or unchanged) | Same structural counts as `Workflows Definition Shared`, plus `status`: `'created'` or `'unchanged'`. |
497
+ | `Workflows Instance Started` | 1 | `start`, and a `fire-action` that spawns a child | `initialFieldCount` (number of caller-supplied initial fields), `viaSpawn` (`true` only for a spawned child), `lifecycle`. |
498
+ | `Workflows Stage Transitioned` | 1 | A committed hop from `fire-action`, `start` cascade, or `set-stage` | `fromStageIndex`, `toStageIndex` (indexes into `stages[]`, never names), `toIsTerminal`, `isRevisit`, `dwellMs` (milliseconds in the exited stage, omitted if that entry is missing), `via`: `'transition'` or `'setStage'`. Unsampled. |
499
+ | `Workflows Action Fired` | 1 | `fire-action` | `activityKind` (optional; derived kind of the fired activity), `hasParams` (whether params were supplied — not the params), `cascaded` (auto-transitions in the post-action cascade). |
500
+ | `Workflows Instance Aborted` | 1 | `abort` | `changed`: `false` if the instance was already terminal. |
501
+ | `Workflows Stage Set` | 1 | `set-stage` | `changed`: `false` if already at the target stage. |
502
+ | `Workflows Activity Reset` | 1 | `reset-activity` | `changed`: `false` if already at the target status, or the instance was terminal. |
503
+ | `Workflows Definition Deleted` | 1 | `definition delete` | `deletedVersionCount`, `cascadeAbortedCount`, `deletedGuardCount` (guards only when the last version goes). |
504
+ | `Workflows Effect Completed` | 1 | `abort` cancelling a pending effect | `effect` (author-chosen name), `status` (`'done'`, `'failed'`, or `'cancelled'`), `origin` (`'action'`), `cascaded`. |
505
+
506
+ The engine also defines `Workflows Field Edited`, `Workflows Instance Ticked`,
507
+ `Workflows Effects Drained`, and `Workflows Effect State Reported`. This CLI
508
+ does not call those verbs, so it does not record those events. Field-edit and
509
+ tick events are sampled at most once per 60 seconds when some other shell
510
+ does emit them.
511
+
201
512
  ## Definition sharing
202
513
 
203
514
  Separate from telemetry, a `deploy` that creates new definition versions
@@ -0,0 +1,9 @@
1
+ import { WorkflowCommand } from '../../../lib/base-command.ts';
2
+ export default class BlueprintGenerate extends WorkflowCommand {
3
+ static description: string;
4
+ static examples: string[];
5
+ static flags: {
6
+ check: import("@oclif/core/interfaces").BooleanFlag<boolean>;
7
+ };
8
+ run(): Promise<void>;
9
+ }
@@ -0,0 +1,28 @@
1
+ import { Flags } from '@oclif/core';
2
+ import { WorkflowCommand } from "../../../lib/base-command.js";
3
+ import { checkBlueprint, generateBlueprint } from "../../../lib/blueprint-emit.js";
4
+ import { loadWorkflowConfigModule } from "../../../lib/load-config.js";
5
+ export default class BlueprintGenerate extends WorkflowCommand {
6
+ static description = 'Experimental: generate the Sanity Blueprints runtime the definitions require, next to sanity.workflow.ts. Writes the workflow resources, one function per derived need, the effect-handler registry, and a handler stub per declared effect. Covers every deployment in the config, because the emitted resources module declares them all. These flags and this output may change before the Blueprints backend accepts the sanity.workflow resource.';
7
+ static examples = [
8
+ '<%= config.bin %> workflows blueprint generate',
9
+ '<%= config.bin %> workflows blueprint generate --check',
10
+ ];
11
+ static flags = {
12
+ check: Flags.boolean({
13
+ description: 'Experimental: verify the tree on disk still matches the definitions; write nothing and exit non-zero on any difference. The CI drift gate.',
14
+ default: false,
15
+ }),
16
+ };
17
+ async run() {
18
+ const { flags } = await this.parse(BlueprintGenerate);
19
+ const root = process.cwd();
20
+ const { config, configFile, exportedNames } = await loadWorkflowConfigModule(root);
21
+ const log = (line) => this.log(line);
22
+ if (flags.check) {
23
+ checkBlueprint({ root, config, configFile, exportedNames, log });
24
+ return;
25
+ }
26
+ generateBlueprint({ root, config, configFile, exportedNames, log });
27
+ }
28
+ }
@@ -1,4 +1,4 @@
1
- import { type Diagnosis, type DiagnoseInput, type SuggestedRemediation, type WorkflowEvaluation } from '@sanity/workflow-engine';
1
+ import { type Diagnosis, type DiagnoseInput, type MissingDocument, type SuggestedRemediation, type WorkflowEvaluation } from '@sanity/workflow-engine';
2
2
  import { WorkflowCommand } from '../../lib/base-command.ts';
3
3
  /** The `--json` transitions payload: raw derived state (atom GROQ, negation,
4
4
  * pivotality, solvable requirement) — structure for scripts, never baked
@@ -23,10 +23,12 @@ export declare function rawTransitionProjection(evaluation: WorkflowEvaluation):
23
23
  * {@link SuggestedRemediation}s rather than re-deriving them, so the rendered
24
24
  * fix block and the `--json` output stay one computation.
25
25
  */
26
- export declare function renderDiagnosis({ diagnosis, input, remediations, explanations, }: {
26
+ export declare function renderDiagnosis({ diagnosis, input, remediations, explanations, allMissingDocuments, }: {
27
27
  diagnosis: Diagnosis;
28
28
  input: DiagnoseInput;
29
29
  remediations: SuggestedRemediation[];
30
+ /** Full evaluation evidence, including references outside the blocking subset. */
31
+ allMissingDocuments?: MissingDocument[] | undefined;
30
32
  /** Insight summaries per unsatisfied transition name — rendered under the
31
33
  * transition line so "stuck" reads as "stuck until X". */
32
34
  explanations?: ReadonlyMap<string, string> | undefined;