@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.
Files changed (79) hide show
  1. package/CHANGELOG.md +485 -0
  2. package/README.md +145 -63
  3. package/bin/run.js +0 -4
  4. package/dist/commands/abort.d.ts +8 -12
  5. package/dist/commands/abort.js +24 -34
  6. package/dist/commands/definition/delete.d.ts +6 -6
  7. package/dist/commands/definition/delete.js +17 -33
  8. package/dist/commands/definition/diff.d.ts +10 -3
  9. package/dist/commands/definition/diff.js +21 -29
  10. package/dist/commands/definition/list.d.ts +5 -6
  11. package/dist/commands/definition/list.js +44 -50
  12. package/dist/commands/definition/show.d.ts +22 -13
  13. package/dist/commands/definition/show.js +25 -36
  14. package/dist/commands/deploy.d.ts +67 -8
  15. package/dist/commands/deploy.js +136 -32
  16. package/dist/commands/diagnose.d.ts +22 -4
  17. package/dist/commands/diagnose.js +45 -61
  18. package/dist/commands/fire-action.d.ts +7 -30
  19. package/dist/commands/fire-action.js +42 -115
  20. package/dist/commands/list.d.ts +9 -10
  21. package/dist/commands/list.js +49 -62
  22. package/dist/commands/{retry-activity.d.ts → reset-activity.d.ts} +1 -1
  23. package/dist/commands/{retry-activity.js → reset-activity.js} +2 -3
  24. package/dist/commands/set-stage.d.ts +27 -3
  25. package/dist/commands/set-stage.js +63 -12
  26. package/dist/commands/show.d.ts +3 -2
  27. package/dist/commands/show.js +15 -21
  28. package/dist/commands/start.d.ts +56 -0
  29. package/dist/commands/start.js +133 -0
  30. package/dist/commands/tail.d.ts +5 -2
  31. package/dist/commands/tail.js +25 -26
  32. package/dist/hooks/finally/telemetry.d.ts +8 -0
  33. package/dist/hooks/finally/telemetry.js +13 -0
  34. package/dist/hooks/prerun/telemetry.d.ts +5 -0
  35. package/dist/hooks/prerun/telemetry.js +5 -0
  36. package/dist/index.js +0 -3
  37. package/dist/lib/base-command.d.ts +14 -0
  38. package/dist/lib/base-command.js +10 -0
  39. package/dist/lib/client.d.ts +11 -14
  40. package/dist/lib/client.js +29 -34
  41. package/dist/lib/context.d.ts +98 -0
  42. package/dist/lib/context.js +83 -0
  43. package/dist/lib/definitions.d.ts +38 -29
  44. package/dist/lib/definitions.js +34 -93
  45. package/dist/lib/diff.d.ts +5 -2
  46. package/dist/lib/diff.js +62 -20
  47. package/dist/lib/env.d.ts +4 -6
  48. package/dist/lib/env.js +4 -9
  49. package/dist/lib/fail.d.ts +12 -0
  50. package/dist/lib/fail.js +25 -16
  51. package/dist/lib/flags.d.ts +7 -2
  52. package/dist/lib/flags.js +7 -3
  53. package/dist/lib/load-config.d.ts +15 -0
  54. package/dist/lib/load-config.js +89 -0
  55. package/dist/lib/operation-args.d.ts +20 -15
  56. package/dist/lib/operation-args.js +9 -40
  57. package/dist/lib/ops-report.d.ts +29 -13
  58. package/dist/lib/ops-report.js +20 -17
  59. package/dist/lib/params.d.ts +7 -0
  60. package/dist/lib/params.js +18 -0
  61. package/dist/lib/read-fanout.d.ts +22 -0
  62. package/dist/lib/read-fanout.js +28 -0
  63. package/dist/lib/select-deployment.d.ts +31 -0
  64. package/dist/lib/select-deployment.js +37 -0
  65. package/dist/lib/share-definitions.d.ts +86 -0
  66. package/dist/lib/share-definitions.js +106 -0
  67. package/dist/lib/stub.js +0 -7
  68. package/dist/lib/telemetry-setup.d.ts +66 -0
  69. package/dist/lib/telemetry-setup.js +92 -0
  70. package/dist/lib/telemetry.d.ts +100 -0
  71. package/dist/lib/telemetry.js +89 -0
  72. package/dist/lib/ui.d.ts +33 -1
  73. package/dist/lib/ui.js +32 -21
  74. package/oclif.manifest.json +133 -113
  75. package/package.json +16 -8
  76. package/dist/commands/move-stage.d.ts +0 -52
  77. package/dist/commands/move-stage.js +0 -86
  78. package/dist/lib/config.d.ts +0 -18
  79. package/dist/lib/config.js +0 -50
@@ -1,10 +1,14 @@
1
1
  import { styleText } from 'node:util';
2
- import { Args, Command } from '@oclif/core';
2
+ import { Args } from '@oclif/core';
3
+ import { displayTitle, errorMessage } from '@sanity/workflow-engine';
3
4
  import logSymbols from 'log-symbols';
4
- import { buildClient, loadClient } from "../lib/client.js";
5
- import { fail } from "../lib/fail.js";
5
+ import { WorkflowCommand } from "../lib/base-command.js";
6
+ import { findInstance, resolveReadTargets } from "../lib/context.js";
7
+ import { fail, failureDetail } from "../lib/fail.js";
8
+ import { tagFlags } from "../lib/flags.js";
6
9
  import { formatTimestamp } from "../lib/ui.js";
7
- export default class Tail extends Command {
10
+ const TAIL_TAG = 'tail';
11
+ export default class Tail extends WorkflowCommand {
8
12
  static description = 'Stream new history entries on a workflow instance as they land in the dataset.';
9
13
  static examples = ['<%= config.bin %> tail wf-instance.abc123'];
10
14
  static args = {
@@ -13,36 +17,38 @@ export default class Tail extends Command {
13
17
  description: 'Workflow instance id to tail.',
14
18
  }),
15
19
  };
20
+ static flags = {
21
+ ...tagFlags,
22
+ };
16
23
  async run() {
17
- const { args } = await this.parse(Tail);
18
- const client = loadClient();
19
- const initial = await client.getDocument(args.instanceId);
20
- if (!initial) {
24
+ const { args, flags } = await this.parse(Tail);
25
+ const targets = await resolveReadTargets(flags);
26
+ const hit = await findInstance({ targets, instanceId: args.instanceId, requestTag: TAIL_TAG });
27
+ if (!hit) {
21
28
  fail(`No instance with id "${args.instanceId}"`);
22
29
  }
30
+ const { client, instance: initial } = hit;
23
31
  this.log(styleText('dim', `tailing ${styleText('bold', args.instanceId)} (${initial.definition} v${initial.pinnedVersion}, ${initial.history.length} prior entries, currently in ${initial.currentStage})`));
24
32
  this.log(styleText('dim', 'Ctrl+C to stop'));
25
33
  this.log('');
26
- // Track the high-water mark so we only print history entries that
27
- // landed after we started tailing. Listening with `includeResult:
28
- // false` (matching what the studio does) keeps the wire payload
29
- // small — we just refetch on each notification.
30
34
  let seen = initial.history.length;
31
35
  const subscription = client
32
- .listen(`*[_id == $id]`, { id: args.instanceId }, { includeResult: false, visibility: 'query' })
36
+ .listen(`*[_id == $id]`, { id: args.instanceId }, {
37
+ includeResult: false,
38
+ visibility: 'query',
39
+ tag: TAIL_TAG,
40
+ })
33
41
  .subscribe({
34
42
  next: async () => {
35
43
  try {
36
44
  seen = await this.printNewEntries({ client, instanceId: args.instanceId, seen });
37
45
  }
38
46
  catch (err) {
39
- const message = err instanceof Error ? err.message : String(err);
40
- process.stderr.write(`${styleText('red', `${logSymbols.error} fetch failed:`)} ${message}\n`);
47
+ process.stderr.write(`${styleText('red', `${logSymbols.error} fetch failed:`)} ${errorMessage(err)}\n`);
41
48
  }
42
49
  },
43
50
  error: (err) => {
44
- const message = err instanceof Error ? err.message : String(err);
45
- fail('Listen subscription error:', message);
51
+ fail('Listen subscription error:', failureDetail(err));
46
52
  },
47
53
  });
48
54
  const shutdown = () => {
@@ -52,22 +58,15 @@ export default class Tail extends Command {
52
58
  };
53
59
  process.on('SIGINT', shutdown);
54
60
  process.on('SIGTERM', shutdown);
55
- // Keep the event loop alive — `subscribe` doesn't itself anchor the
56
- // process. The shutdown handlers above are the only exit path.
57
61
  await new Promise(() => { });
58
62
  }
59
- /**
60
- * Fetch the current instance state, print history entries past the
61
- * given high-water mark, return the new mark. Pulled out so the
62
- * async work in the subscription's `next` handler stays tidy.
63
- */
64
63
  async printNewEntries({ client, instanceId, seen, }) {
65
- const next = await client.getDocument(instanceId);
64
+ const next = await client.getDocument(instanceId, { tag: TAIL_TAG });
66
65
  if (!next)
67
66
  return seen;
68
67
  const newEntries = next.history.slice(seen);
69
68
  for (const entry of newEntries) {
70
- this.log(`${styleText('dim', `[${formatTimestamp(entry.at)}]`)} ${styleText('cyan', entry._type)}`);
69
+ this.log(`${styleText('dim', `[${formatTimestamp(entry.at)}]`)} ${styleText('cyan', displayTitle(entry._type))}`);
71
70
  }
72
71
  return next.history.length;
73
72
  }
@@ -0,0 +1,8 @@
1
+ import type { Hook } from '@oclif/core';
2
+ /**
3
+ * Completes the command trace — command id, declared-flag names, success —
4
+ * and flushes the final batch. Runs on success and on error alike (oclif's
5
+ * `finally`); an invocation the prerun hook left with telemetry off no-ops.
6
+ */
7
+ declare const hook: Hook<'finally'>;
8
+ export default hook;
@@ -0,0 +1,13 @@
1
+ import { finishCliTelemetry, usedFlagNames } from "../../lib/telemetry.js";
2
+ const hook = async function (options) {
3
+ try {
4
+ await finishCliTelemetry({
5
+ command: options.id,
6
+ flags: usedFlagNames(options.argv, options.Command?.flags),
7
+ success: options.error === undefined,
8
+ });
9
+ }
10
+ catch {
11
+ }
12
+ };
13
+ export default hook;
@@ -0,0 +1,5 @@
1
+ import type { Hook } from '@oclif/core';
2
+ /** Builds and installs the invocation's telemetry shell before the command
3
+ * runs; the `finally` hook completes the trace and flushes. Never throws. */
4
+ declare const hook: Hook<'prerun'>;
5
+ export default hook;
@@ -0,0 +1,5 @@
1
+ import { setupCliTelemetry } from "../../lib/telemetry-setup.js";
2
+ const hook = async function ({ config }) {
3
+ await setupCliTelemetry({ cliVersion: config.version });
4
+ };
5
+ export default hook;
package/dist/index.js CHANGED
@@ -1,4 +1 @@
1
- // Library entry for the package's `.` export. The CLI itself runs through the
2
- // `bin/run.js` (published) and `bin/dev.js` (dev) entries; this module exists
3
- // only to satisfy the repo's source-first exports map.
4
1
  export { execute } from '@oclif/core';
@@ -0,0 +1,14 @@
1
+ import { Command } from '@oclif/core';
2
+ /**
3
+ * Base for the API-backed workflow commands: the backstop that renders an
4
+ * auth rejection (HTTP 401) escaping a command's `run` through the clean
5
+ * {@link fail} path — with {@link failureDetail}'s recovery hint — instead
6
+ * of an oclif error dump. Errors caught inside commands render the same
7
+ * detail at their own `fail` sites; every other error propagates to oclif
8
+ * unchanged.
9
+ */
10
+ export declare abstract class WorkflowCommand extends Command {
11
+ protected catch(err: Error & {
12
+ exitCode?: number;
13
+ }): Promise<unknown>;
14
+ }
@@ -0,0 +1,10 @@
1
+ import { Command } from '@oclif/core';
2
+ import { fail, failureDetail, isAuthRejection } from "./fail.js";
3
+ export class WorkflowCommand extends Command {
4
+ async catch(err) {
5
+ if (isAuthRejection(err)) {
6
+ fail('Authentication failed:', failureDetail(err));
7
+ }
8
+ return super.catch(err);
9
+ }
10
+ }
@@ -1,17 +1,14 @@
1
1
  import { type SanityClient } from '@sanity/client';
2
- export interface ClientEnv {
3
- projectId: string;
4
- dataset: string;
5
- token: string;
6
- apiHost: string;
7
- apiVersion: string;
8
- }
9
- export declare function buildClient(env?: ClientEnv): SanityClient;
2
+ import { type WorkflowResource } from '@sanity/workflow-engine';
10
3
  /**
11
- * {@link buildClient} but funnel a missing/empty `SANITY_*` env var
12
- * through the standard {@link failOnThrow} path, so commands fail with a
13
- * clean message rather than a raw stack trace. This is the boundary
14
- * commands call; {@link buildClient} stays throw-based for tests and for
15
- * callers that supply their own {@link ClientEnv}.
4
+ * Resolve the Sanity auth token. An explicit `SANITY_AUTH_TOKEN` wins (CI /
5
+ * scripted use); otherwise fall back to the `sanity login` session token.
6
+ * Throws when neither exists rather than building an unauthenticated client
7
+ * that only 401s once it hits the API.
16
8
  */
17
- export declare function loadClient(): SanityClient;
9
+ export declare function resolveToken(): Promise<string>;
10
+ /** {@link resolveToken}, funnelling the no-token case through the clean
11
+ * {@link fail} path rather than letting it surface as an oclif stack. */
12
+ export declare function resolveTokenOrFail(): Promise<string>;
13
+ /** Build a client for a workflow resource, authenticated with `token`. */
14
+ export declare function clientFor(resource: WorkflowResource, token: string): SanityClient;
@@ -1,40 +1,35 @@
1
+ import { getCliToken, isStaging } from '@sanity/cli-core';
1
2
  import { createClient } from '@sanity/client';
2
- import { ENV } from "./env.js";
3
- import { failOnThrow } from "./fail.js";
4
- function readClientEnv() {
5
- const projectId = process.env[ENV.SANITY_PROJECT_ID];
6
- const token = process.env[ENV.SANITY_AUTH_TOKEN];
7
- if (!projectId) {
8
- throw new Error('SANITY_PROJECT_ID is required (set in root .env)');
3
+ import { clientConfigFromResource, ENGINE_API_VERSION, errorMessage, } from '@sanity/workflow-engine';
4
+ import { ENV, envAuthToken } from "./env.js";
5
+ import { fail } from "./fail.js";
6
+ const API_VERSION = ENGINE_API_VERSION;
7
+ const STAGING_API_HOST = 'https://api.sanity.work';
8
+ function resolveApiHost() {
9
+ return process.env[ENV.SANITY_API_HOST] ?? (isStaging() ? STAGING_API_HOST : undefined);
10
+ }
11
+ export async function resolveToken() {
12
+ const fromEnv = envAuthToken();
13
+ if (fromEnv !== undefined) {
14
+ return fromEnv;
9
15
  }
10
- if (!token) {
11
- throw new Error('SANITY_AUTH_TOKEN is required with editor role (set in root .env)');
16
+ const fromSession = await getCliToken();
17
+ if (fromSession) {
18
+ return fromSession;
12
19
  }
13
- return {
14
- projectId,
15
- dataset: process.env[ENV.SANITY_DATASET] ?? 'production',
16
- token,
17
- apiHost: process.env[ENV.SANITY_API_HOST] ?? 'https://api.sanity.io',
18
- apiVersion: '2026-04-29',
19
- };
20
+ throw new Error('No Sanity token found — run `sanity login`, or set SANITY_AUTH_TOKEN.');
20
21
  }
21
- export function buildClient(env = readClientEnv()) {
22
- return createClient({
23
- projectId: env.projectId,
24
- dataset: env.dataset,
25
- apiVersion: env.apiVersion,
26
- token: env.token,
27
- apiHost: env.apiHost,
28
- useCdn: false,
29
- });
22
+ export async function resolveTokenOrFail() {
23
+ return resolveToken().catch((err) => fail('Authentication required:', errorMessage(err)));
30
24
  }
31
- /**
32
- * {@link buildClient} but funnel a missing/empty `SANITY_*` env var
33
- * through the standard {@link failOnThrow} path, so commands fail with a
34
- * clean message rather than a raw stack trace. This is the boundary
35
- * commands call; {@link buildClient} stays throw-based for tests and for
36
- * callers that supply their own {@link ClientEnv}.
37
- */
38
- export function loadClient() {
39
- return failOnThrow('Invalid Sanity client config:', () => buildClient());
25
+ export function clientFor(resource, token) {
26
+ const apiHost = resolveApiHost();
27
+ const baseParams = {
28
+ token,
29
+ apiVersion: API_VERSION,
30
+ useCdn: false,
31
+ requestTagPrefix: 'sanity.workflows.cli',
32
+ ...(apiHost ? { apiHost } : {}),
33
+ };
34
+ return createClient({ ...baseParams, ...clientConfigFromResource(resource) });
40
35
  }
@@ -0,0 +1,98 @@
1
+ import type { SanityClient } from '@sanity/client';
2
+ import type { WorkflowConfig, WorkflowDeployment, WorkflowInstance, WorkflowResource } from '@sanity/workflow-engine';
3
+ import type { EngineScope } from './operation-args.ts';
4
+ export interface DeploymentContext {
5
+ deployment: WorkflowDeployment;
6
+ client: SanityClient;
7
+ }
8
+ export interface InstanceContext {
9
+ client: SanityClient;
10
+ /** The engine scope derived from the instance itself — `{tag, workflowResource}`
11
+ * ready to pass straight to an engine verb, so callers never re-assemble it. */
12
+ scope: EngineScope;
13
+ }
14
+ /**
15
+ * The resolution a WRITE shares: discover the config, pick the deployment for
16
+ * `--tag` (or the sole one), and build an authenticated client for that
17
+ * deployment's resource. A write acts on one specific deployment, so a
18
+ * single, definite target is exactly what it needs.
19
+ */
20
+ export declare function resolveContext(flags: {
21
+ tag?: string | undefined;
22
+ }): Promise<DeploymentContext>;
23
+ /** One resource a read inspects, with its authenticated client. */
24
+ export interface ReadTarget {
25
+ resource: WorkflowResource;
26
+ client: SanityClient;
27
+ }
28
+ /**
29
+ * The resolution every READ shares: an authenticated client per resource to
30
+ * inspect — and nothing more. A read lists/inspects whatever is in the
31
+ * Content Lake, so it must NOT inherit a deployment's tag or definition set
32
+ * as a filter. The config only locates the resources here; the command
33
+ * applies the user's explicit `--tag` (if any) as a query filter itself.
34
+ *
35
+ * Untagged, this is every distinct resource the config mentions — a read is
36
+ * harmless to fan out, so "show me what's deployed" spans the whole config.
37
+ * `--tag` narrows to that deployment's resource. The token resolves once for
38
+ * the run, however many targets it spans.
39
+ */
40
+ export declare function resolveReadTargets(flags: {
41
+ tag?: string | undefined;
42
+ }): Promise<ReadTarget[]>;
43
+ /** The distinct resources a read targets: the tagged deployment's sole
44
+ * resource, or — untagged — every distinct one the config's deployments
45
+ * mention (deduped by `type:id`). */
46
+ export declare function resolveReadResources(config: WorkflowConfig, tag: string | undefined): WorkflowResource[];
47
+ /** The single resource a read should target, or fail asking which when the
48
+ * config spans more than one. For the paths that need one definite dataset
49
+ * (the instance-targeted verbs); listings fan out via
50
+ * {@link resolveReadResources} instead. */
51
+ export declare function resolveReadResource(config: WorkflowConfig, tag: string | undefined): WorkflowResource;
52
+ /**
53
+ * The resolution an instance-id-targeted command shares, read or write: an
54
+ * authenticated client for the resource, plus the instance itself — so the
55
+ * command derives its partition (`tag`) from the instance, never from the
56
+ * config's declared deployments. An instance id is globally unique and carries
57
+ * its own `tag`, so a command that names one acts on that instance regardless
58
+ * of which tags the config happens to deploy; the config only locates the
59
+ * resource (via {@link resolveReadResource}, no tag filter).
60
+ *
61
+ * Contrast {@link resolveContext}: the deploy/diff/delete path IS scoped to a
62
+ * declared deployment because it acts on the authored definition set (or a
63
+ * definition name, which — unlike an instance id — isn't unique across tags).
64
+ */
65
+ export declare function resolveInstanceContext(flags: {
66
+ tag?: string | undefined;
67
+ }, instanceId: string): Promise<InstanceContext>;
68
+ /** Fetch an instance by id, exiting cleanly when the resource has no such
69
+ * document — the diagnostic an operator sees on a mistyped id. Split out from
70
+ * {@link resolveInstanceContext} so the not-found path is unit-testable
71
+ * without a live config/token/client. */
72
+ export declare function loadInstanceOrFail(client: Pick<SanityClient, 'getDocument'>, instanceId: string): Promise<WorkflowInstance>;
73
+ /** A read target that turned out to hold the instance being looked up. */
74
+ export interface InstanceHit extends ReadTarget {
75
+ instance: WorkflowInstance;
76
+ }
77
+ /**
78
+ * The sole cross-resource hit, or fail listing every holding resource —
79
+ * ambiguity across datasets must error, never pick. `subject` names the
80
+ * thing being looked up in the diagnostic (`Instance <id>`, `Definition "x"`).
81
+ */
82
+ export declare function soleHitOrFail<T extends {
83
+ resource: WorkflowResource;
84
+ }>(hits: T[], subject: string): T | undefined;
85
+ /**
86
+ * Look an instance id up across every read target. Exactly one hit wins —
87
+ * instance ids are random and collision-free, so a single match IS the
88
+ * instance. Several hits (id reuse across datasets) fail listing the
89
+ * resources rather than silently picking one. `undefined` means confidently
90
+ * not found: when a probe throws and no other target has the id, that error
91
+ * propagates instead — an unreadable dataset must not masquerade as "no such
92
+ * instance".
93
+ */
94
+ export declare function findInstance({ targets, instanceId, requestTag, }: {
95
+ targets: ReadTarget[];
96
+ instanceId: string;
97
+ requestTag: string;
98
+ }): Promise<InstanceHit | undefined>;
@@ -0,0 +1,83 @@
1
+ import { clientFor, resolveTokenOrFail } from "./client.js";
2
+ import { fail } from "./fail.js";
3
+ import { loadWorkflowConfig } from "./load-config.js";
4
+ import { selectDeployment } from "./select-deployment.js";
5
+ import { resourceLabel } from "./ui.js";
6
+ export async function resolveContext(flags) {
7
+ const config = await loadWorkflowConfig();
8
+ const deployment = selectDeployment(config, { tag: flags.tag });
9
+ return { deployment, client: clientFor(deployment.workflowResource, await resolveTokenOrFail()) };
10
+ }
11
+ export async function resolveReadTargets(flags) {
12
+ const config = await loadWorkflowConfig();
13
+ const resources = resolveReadResources(config, flags.tag);
14
+ const token = await resolveTokenOrFail();
15
+ return resources.map((resource) => ({ resource, client: clientFor(resource, token) }));
16
+ }
17
+ export function resolveReadResources(config, tag) {
18
+ if (tag !== undefined) {
19
+ return [selectDeployment(config, { tag }).workflowResource];
20
+ }
21
+ const byKey = new Map(config.deployments.map((d) => [
22
+ `${d.workflowResource.type}:${d.workflowResource.id}`,
23
+ d.workflowResource,
24
+ ]));
25
+ const resources = [...byKey.values()];
26
+ if (resources.length === 0) {
27
+ fail('No deployments configured.');
28
+ }
29
+ return resources;
30
+ }
31
+ export function resolveReadResource(config, tag) {
32
+ const resources = resolveReadResources(config, tag);
33
+ const [sole] = resources;
34
+ if (resources.length === 1 && sole !== undefined) {
35
+ return sole;
36
+ }
37
+ const tags = config.deployments.map((d) => d.tag).join(', ');
38
+ fail('Config spans multiple resources — pass --tag to choose one.', `Available tags: ${tags}`);
39
+ }
40
+ export async function resolveInstanceContext(flags, instanceId) {
41
+ const config = await loadWorkflowConfig();
42
+ const workflowResource = resolveReadResource(config, flags.tag);
43
+ const client = clientFor(workflowResource, await resolveTokenOrFail());
44
+ const instance = await loadInstanceOrFail(client, instanceId);
45
+ return { client, scope: { tag: instance.tag, workflowResource } };
46
+ }
47
+ export async function loadInstanceOrFail(client, instanceId) {
48
+ const instance = await client.getDocument(instanceId, {
49
+ tag: 'instance.load',
50
+ });
51
+ if (!instance) {
52
+ fail(`Workflow instance ${instanceId} not found`);
53
+ }
54
+ return instance;
55
+ }
56
+ export function soleHitOrFail(hits, subject) {
57
+ const [sole, ...rest] = hits;
58
+ if (rest.length > 0) {
59
+ fail(`${subject} exists in several resources — pass --tag to choose one.`, hits.map((hit) => resourceLabel(hit.resource)).join('\n'));
60
+ }
61
+ return sole;
62
+ }
63
+ export async function findInstance({ targets, instanceId, requestTag, }) {
64
+ const probes = await Promise.allSettled(targets.map(async (target) => {
65
+ const instance = await target.client.getDocument(instanceId, {
66
+ tag: requestTag,
67
+ });
68
+ return instance === undefined ? undefined : { ...target, instance };
69
+ }));
70
+ const hits = probes
71
+ .filter((probe) => probe.status === 'fulfilled')
72
+ .map((probe) => probe.value)
73
+ .filter((hit) => hit !== undefined);
74
+ const sole = soleHitOrFail(hits, `Instance ${instanceId}`);
75
+ if (sole !== undefined) {
76
+ return sole;
77
+ }
78
+ const failedProbe = probes.find((probe) => probe.status === 'rejected');
79
+ if (failedProbe !== undefined) {
80
+ throw failedProbe.reason;
81
+ }
82
+ return undefined;
83
+ }
@@ -1,36 +1,45 @@
1
- import { type WorkflowDefinition } from '@sanity/workflow-engine';
1
+ import type { SanityClient } from '@sanity/client';
2
+ import { type DeployedDefinition, type WorkflowDefinition } from '@sanity/workflow-engine';
3
+ interface DefinitionShowQueryArgs {
4
+ name: string;
5
+ tag?: string | undefined;
6
+ version?: number | undefined;
7
+ }
8
+ export declare function buildDefinitionShowQuery(flags: DefinitionShowQueryArgs): {
9
+ groq: string;
10
+ params: Record<string, unknown>;
11
+ };
2
12
  /**
3
- * Turn a configured definitions source into a specifier `import()` can
4
- * load. Path-like values (relative or absolute) resolve against `cwd` and
5
- * become file URLs so they're imported as files, not bare package names;
6
- * anything else passes through as a module specifier (e.g. a workspace
7
- * package such as `@sanity/workflow-examples`).
13
+ * Fetch the deployed definition a command targets latest unless pinned via
14
+ * `version`. An absent pinned version always fails (the caller asked for
15
+ * something specific); an absent latest returns `undefined` so the caller
16
+ * decides whether that's fatal.
8
17
  */
9
- export declare function resolveDefinitionsSpecifier(source: string, cwd: string): string;
18
+ export declare function fetchDeployedDefinition({ client, name, tag, version, }: {
19
+ client: SanityClient;
20
+ name: string;
21
+ tag: string;
22
+ version: number | undefined;
23
+ }): Promise<DeployedDefinition | undefined>;
10
24
  /**
11
- * Pull the `allDefinitions` array out of an imported definitions module,
12
- * failing hard with a source-specific message when the module doesn't
13
- * expose it. Per-definition structure is validated downstream by
14
- * {@link collectValidationErrors}, so this only asserts the array shape.
25
+ * Narrow a batch to one definition name (`only`), failing with the available
26
+ * names when there's no match. With `only` unset the whole batch passes through
27
+ * (a fresh array, so callers can mutate without aliasing). `context` prefixes
28
+ * the failure so callers whose run spans several deployments can attribute it.
15
29
  */
16
- export declare function extractDefinitions(mod: unknown, source: string): WorkflowDefinition[];
30
+ export declare function selectDefinitions(allDefinitions: WorkflowDefinition[], { only, context }: {
31
+ only: string | undefined;
32
+ context?: string;
33
+ }): WorkflowDefinition[];
17
34
  /**
18
- * Narrow the local batch to one definition `name`, failing with the
19
- * available names when there's no match. With `only` unset the whole batch passes
20
- * through (a fresh array, so callers can mutate without aliasing).
21
- */
22
- export declare function selectDefinitions(allDefinitions: WorkflowDefinition[], only: string | undefined): WorkflowDefinition[];
23
- /**
24
- * The full local-definitions pipeline shared by `deploy` and
25
- * `definition diff`: resolve the `WORKFLOW_DEFS` source, import it,
26
- * narrow to `only` (when given), and fail on any validation error.
27
- */
28
- export declare function loadValidatedDefinitions(only: string | undefined): Promise<WorkflowDefinition[]>;
29
- /** {@link loadValidatedDefinitions} narrowed to exactly one definition name. */
30
- export declare function loadValidatedDefinition(name: string): Promise<WorkflowDefinition>;
31
- /**
32
- * Per-definition structural/GROQ checks plus one batch-level check the
33
- * engine doesn't do for us: duplicate `name` within the local set.
34
- * Returns the human-readable error lines; empty means the batch is valid.
35
+ * Per-definition structural/GROQ checks plus one batch-level check the engine
36
+ * doesn't do for us: duplicate `name` within the set. Returns the
37
+ * human-readable error lines; empty means the batch is valid.
35
38
  */
36
39
  export declare function collectValidationErrors(defs: WorkflowDefinition[]): string[];
40
+ /** Validate a set of definitions, exiting with the collected errors on any
41
+ * failure. The single validate-then-fail gate `deploy` and `definition diff`
42
+ * share, so the two can't drift into divergent failure messages. `context`
43
+ * prefixes the failure with the deployment it belongs to in multi-target runs. */
44
+ export declare function validateOrFail(defs: WorkflowDefinition[], context?: string): void;
45
+ export {};