@sanity/workflow-cli 0.11.0 → 0.13.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 (59) hide show
  1. package/CHANGELOG.md +66 -0
  2. package/README.md +75 -46
  3. package/dist/commands/{abort.d.ts → editorial-workflows/abort.d.ts} +3 -2
  4. package/dist/commands/{abort.js → editorial-workflows/abort.js} +7 -6
  5. package/dist/commands/{definition → editorial-workflows/definition}/delete.d.ts +3 -2
  6. package/dist/commands/{definition → editorial-workflows/definition}/delete.js +6 -5
  7. package/dist/commands/{definition → editorial-workflows/definition}/diff.d.ts +2 -1
  8. package/dist/commands/{definition → editorial-workflows/definition}/diff.js +7 -6
  9. package/dist/commands/{definition → editorial-workflows/definition}/list.d.ts +5 -1
  10. package/dist/commands/{definition → editorial-workflows/definition}/list.js +27 -24
  11. package/dist/commands/{definition → editorial-workflows/definition}/show.d.ts +3 -2
  12. package/dist/commands/{definition → editorial-workflows/definition}/show.js +9 -8
  13. package/dist/commands/{deploy.d.ts → editorial-workflows/deploy.d.ts} +2 -1
  14. package/dist/commands/{deploy.js → editorial-workflows/deploy.js} +14 -13
  15. package/dist/commands/{diagnose.d.ts → editorial-workflows/diagnose.d.ts} +2 -1
  16. package/dist/commands/{diagnose.js → editorial-workflows/diagnose.js} +7 -6
  17. package/dist/commands/{fire-action.d.ts → editorial-workflows/fire-action.d.ts} +3 -2
  18. package/dist/commands/{fire-action.js → editorial-workflows/fire-action.js} +8 -7
  19. package/dist/commands/{list.d.ts → editorial-workflows/list.d.ts} +4 -1
  20. package/dist/commands/{list.js → editorial-workflows/list.js} +33 -28
  21. package/dist/commands/editorial-workflows/nuke.d.ts +12 -0
  22. package/dist/commands/editorial-workflows/nuke.js +77 -0
  23. package/dist/commands/{reset-activity.d.ts → editorial-workflows/reset-activity.d.ts} +2 -1
  24. package/dist/commands/{reset-activity.js → editorial-workflows/reset-activity.js} +2 -1
  25. package/dist/commands/{set-stage.d.ts → editorial-workflows/set-stage.d.ts} +4 -3
  26. package/dist/commands/{set-stage.js → editorial-workflows/set-stage.js} +6 -5
  27. package/dist/commands/{show.d.ts → editorial-workflows/show.d.ts} +2 -1
  28. package/dist/commands/{show.js → editorial-workflows/show.js} +40 -26
  29. package/dist/commands/{start.d.ts → editorial-workflows/start.d.ts} +20 -4
  30. package/dist/commands/{start.js → editorial-workflows/start.js} +58 -16
  31. package/dist/commands/{tail.d.ts → editorial-workflows/tail.d.ts} +2 -1
  32. package/dist/commands/{tail.js → editorial-workflows/tail.js} +10 -8
  33. package/dist/hooks/finally/telemetry.js +2 -2
  34. package/dist/hooks/prerun/telemetry.d.ts +4 -3
  35. package/dist/lib/base-command.d.ts +4 -6
  36. package/dist/lib/base-command.js +6 -0
  37. package/dist/lib/client.d.ts +8 -0
  38. package/dist/lib/client.js +1 -1
  39. package/dist/lib/context.d.ts +6 -2
  40. package/dist/lib/context.js +9 -7
  41. package/dist/lib/definitions.js +2 -2
  42. package/dist/lib/flags.d.ts +4 -3
  43. package/dist/lib/nuke.d.ts +89 -0
  44. package/dist/lib/nuke.js +111 -0
  45. package/dist/lib/operation-args.d.ts +3 -1
  46. package/dist/lib/ops-report.d.ts +2 -1
  47. package/dist/lib/prompt.d.ts +11 -0
  48. package/dist/lib/prompt.js +4 -0
  49. package/dist/lib/select-deployment.d.ts +12 -7
  50. package/dist/lib/select-deployment.js +26 -1
  51. package/dist/lib/share-definitions.d.ts +23 -28
  52. package/dist/lib/share-definitions.js +30 -40
  53. package/dist/lib/telemetry-setup.d.ts +2 -2
  54. package/dist/lib/telemetry.d.ts +18 -10
  55. package/dist/lib/telemetry.js +5 -2
  56. package/dist/lib/ui.d.ts +12 -0
  57. package/dist/lib/ui.js +9 -0
  58. package/oclif.manifest.json +138 -46
  59. package/package.json +11 -9
@@ -1,21 +1,23 @@
1
1
  import { styleText } from 'node:util';
2
- import { getUserConfig, isInteractive } from '@sanity/cli-core';
3
- import { confirm } from '@sanity/cli-core/ux';
4
- import { definitionDeployedData, errorMessage, isTelemetryEnvDenied, } from '@sanity/workflow-engine';
2
+ import { getUserConfig } from '@sanity/cli-core';
3
+ import { assertReadableModel, definitionDeployedData, errorMessage, } from '@sanity/workflow-engine';
5
4
  import { buildDefinitionShowQuery } from "./definitions.js";
5
+ import { canPromptOnStderr } from "./prompt.js";
6
6
  import { cliTelemetry, WorkflowDefinitionShared, WorkflowDefinitionSharingDecided, } from "./telemetry.js";
7
7
  export const SHARE_ENDPOINT_URI = '/workflow/definition-feedback';
8
8
  const SHARE_TAG = 'definition.share';
9
9
  const SHARE_DECISION_KEY = 'workflowCliDefinitionSharing';
10
- export const SHARE_CONSENT_PROMPT = `${styleText('bold', 'Share the workflow definitions this deploy creates with Sanity?')}\n` +
10
+ export const SHARE_FIRST_RUN_NOTICE = `${styleText('bold', 'Sharing new workflow definitions with Sanity')} to improve Editorial Workflows.\n` +
11
11
  'They are sent verbatim (structure, names, filters, effect configuration,\n' +
12
- 'seeded values) with their deployment coordinates, and kept to improve\n' +
13
- 'Editorial Workflows — never your content documents, workflow instances, or\n' +
14
- 'your Sanity auth token. Your answer is remembered for future deploys on\n' +
15
- `this machine; override any run with ${styleText('cyan', '--share-defs')} / ${styleText('cyan', '--no-share-defs')}.`;
16
- export const SHARE_REMINDER_ON = `${styleText('bold', 'Sharing new workflow definitions with Sanity')} to improve Editorial Workflows` +
17
- ` — opt out with ${styleText('cyan', '--no-share-defs')}.\n`;
18
- export const SHARE_REMINDER_OFF = `Not sharing workflow definitions with Sanity — share with ${styleText('cyan', '--share-defs')}.\n`;
12
+ 'seeded values) with their deployment coordinates, and kept never your\n' +
13
+ 'content documents, workflow instances, or Sanity auth token. This notice is\n' +
14
+ `shown once on this machine; opt out of any run with ${styleText('cyan', '--no-share-defs')}.\n`;
15
+ export const SHARE_REMINDER = `${styleText('bold', 'Definition sharing: ON (default)')}\n` +
16
+ 'New workflow definition versions are being shared with Sanity to improve Editorial Workflows.\n' +
17
+ 'Examples:\n' +
18
+ ` ${styleText('dim', '$')} sanity-workflows deploy ${styleText('dim', '# share by default')}\n` +
19
+ ` ${styleText('dim', '$')} sanity-workflows deploy ${styleText('cyan', '--share-defs')} ${styleText('dim', '# share explicitly; hide this warning')}\n` +
20
+ ` ${styleText('dim', '$')} sanity-workflows deploy ${styleText('cyan', '--no-share-defs')} ${styleText('dim', '# opt out')}\n`;
19
21
  export function definitionShareMarker(args) {
20
22
  const { status: _status, ...structural } = definitionDeployedData(args.definition, {
21
23
  contentHash: args.contentHash,
@@ -26,7 +28,7 @@ export function definitionShareMarker(args) {
26
28
  }
27
29
  export function shouldForceShareTelemetry(args) {
28
30
  const { commandId, argv } = args;
29
- if (commandId !== 'deploy') {
31
+ if (commandId !== 'editorial-workflows:deploy') {
30
32
  return false;
31
33
  }
32
34
  const terminator = argv.indexOf('--');
@@ -34,7 +36,8 @@ export function shouldForceShareTelemetry(args) {
34
36
  if (tokens.includes('--dry-run') || tokens.includes('--check')) {
35
37
  return false;
36
38
  }
37
- return tokens.lastIndexOf('--share-defs') > tokens.lastIndexOf('--no-share-defs');
39
+ return (tokens.lastIndexOf('--share-defs') > tokens.lastIndexOf('--no-share-defs') ||
40
+ !tokens.includes('--no-share-defs'));
38
41
  }
39
42
  async function loadShareEntry(args) {
40
43
  const { candidate, result, warn } = args;
@@ -70,6 +73,7 @@ async function loadDeployedForShare(args) {
70
73
  warn(`Could not read back ${result.name} v${result.version} — not shared.`);
71
74
  return undefined;
72
75
  }
76
+ assertReadableModel(deployed);
73
77
  return deployed;
74
78
  }
75
79
  catch (error) {
@@ -82,43 +86,32 @@ function collectCreated(candidates) {
82
86
  .filter((result) => result.status === 'created')
83
87
  .map((result) => ({ candidate, result })));
84
88
  }
85
- function readShareDecision(userConfig) {
89
+ function wasShareNoticeShown(userConfig) {
86
90
  const value = userConfig.get(SHARE_DECISION_KEY);
87
- return typeof value === 'boolean' ? value : undefined;
91
+ return typeof value === 'boolean';
88
92
  }
89
- async function promptFirstRun(args) {
90
- let share;
93
+ function rememberShareNotice(userConfig) {
91
94
  try {
92
- share = await args.confirmShare();
93
- }
94
- catch {
95
- return false;
96
- }
97
- try {
98
- args.userConfig.set(SHARE_DECISION_KEY, share);
95
+ userConfig.set(SHARE_DECISION_KEY, true);
99
96
  }
100
97
  catch {
101
98
  }
102
- return share;
103
99
  }
104
- async function resolveShareIntent(args) {
100
+ function resolveShareIntent(args) {
105
101
  if (args.flag === true) {
106
102
  return { share: true, decision: 'opt-in' };
107
103
  }
108
104
  if (args.flag === false) {
109
105
  return { share: false, decision: 'opt-out' };
110
106
  }
111
- const unattended = args.envDenied || !args.interactive;
112
- if (unattended) {
113
- return { share: false, decision: 'unattended' };
107
+ if (args.interactive && !wasShareNoticeShown(args.userConfig)) {
108
+ args.writeStderr(SHARE_FIRST_RUN_NOTICE);
109
+ rememberShareNotice(args.userConfig);
114
110
  }
115
- const remembered = readShareDecision(args.userConfig);
116
- if (remembered !== undefined) {
117
- args.writeStderr(remembered ? SHARE_REMINDER_ON : SHARE_REMINDER_OFF);
118
- return { share: remembered, decision: remembered ? 'remembered-share' : 'remembered-decline' };
111
+ else {
112
+ args.writeStderr(SHARE_REMINDER);
119
113
  }
120
- const accepted = await promptFirstRun(args);
121
- return { share: accepted, decision: accepted ? 'prompt-accepted' : 'prompt-declined' };
114
+ return { share: true, decision: 'default' };
122
115
  }
123
116
  async function donate(args) {
124
117
  const shared = [];
@@ -169,14 +162,11 @@ export async function shareDefinitionsAfterDeploy(args) {
169
162
  return;
170
163
  }
171
164
  try {
172
- const { share, decision } = await resolveShareIntent({
165
+ const { share, decision } = resolveShareIntent({
173
166
  flag: args.flag,
174
- interactive: args.interactive ?? (isInteractive() && process.stderr.isTTY === true),
175
- envDenied: args.envDenied ?? isTelemetryEnvDenied(process.env),
167
+ interactive: args.interactive ?? canPromptOnStderr(),
176
168
  userConfig: args.userConfig ?? getUserConfig(),
177
169
  writeStderr: args.writeStderr ?? ((message) => void process.stderr.write(message)),
178
- confirmShare: args.confirmShare ??
179
- (() => confirm({ message: SHARE_CONSENT_PROMPT, default: true }, { output: process.stderr })),
180
170
  });
181
171
  const shared = share ? await donate({ created, warn: args.warn }) : false;
182
172
  emitSharingDecided({ decision, shared, definitionCount: created.length });
@@ -40,8 +40,8 @@ export declare function setupCliTelemetry(args?: {
40
40
  /** The oclif config's version — the prerun hook supplies it. */
41
41
  cliVersion?: string;
42
42
  /** Override the environment denial (CI / `DO_NOT_TRACK`) — the prerun hook
43
- * sets this for a deploy that explicitly opts into definition sharing with
44
- * `--share-defs`. */
43
+ * sets this for a deploy that may share definitions, either by default or
44
+ * through `--share-defs`. */
45
45
  forceSend?: boolean;
46
46
  /** Intake client override — tests inject a fake here, like the other
47
47
  * hook dependencies above; omitted, the real project client applies. */
@@ -24,7 +24,7 @@
24
24
  * Definition Shared` is a content-free marker (hash + structural counts); the shared
25
25
  * document itself rides the first-party feedback endpoint, never
26
26
  * telemetry (see `share-definitions.ts`). `Editorial Workflows Definition
27
- * Sharing Decided` records the per-invocation consent outcome (default /
27
+ * Sharing Decided` records the per-invocation sharing decision (default /
28
28
  * opt-in / opt-out and whether anything was donated) — also content-free.
29
29
  */
30
30
  import { type DefinedTelemetryTrace, type TelemetryStore } from '@sanity/telemetry';
@@ -47,14 +47,11 @@ export declare const WorkflowDefinitionShared: import("@sanity/telemetry").Defin
47
47
  /**
48
48
  * How the donation decision was reached, and what it was. Paired with `shared`
49
49
  * (whether it actually reached the endpoint) this keeps every case distinct —
50
- * an explicit decline vs a run that couldn't ask, and a chosen share vs a
51
- * failed POST:
50
+ * an explicit opt-out vs default sharing, and a chosen share vs a failed POST:
52
51
  * - `opt-in` / `opt-out` — an explicit `--share-defs` / `--no-share-defs`.
53
- * - `prompt-accepted` / `prompt-declined` the first-run consent prompt.
54
- * - `remembered-share` / `remembered-decline` — a persisted earlier answer.
55
- * - `unattended` — CI / `DO_NOT_TRACK` / non-TTY: never asked, never shares.
52
+ * - `default` no sharing flag; definitions are donated in every environment.
56
53
  */
57
- export type DefinitionSharingDecision = 'opt-in' | 'opt-out' | 'prompt-accepted' | 'prompt-declined' | 'remembered-share' | 'remembered-decline' | 'unattended';
54
+ export type DefinitionSharingDecision = 'opt-in' | 'opt-out' | 'default';
58
55
  export interface WorkflowDefinitionSharingDecidedData {
59
56
  decision: DefinitionSharingDecision;
60
57
  /** Whether the donation actually reached the feedback endpoint — `false` for
@@ -93,9 +90,8 @@ export declare function setCliTelemetry(telemetry: CliTelemetry): void;
93
90
  export declare function clearCliTelemetry(): void;
94
91
  /** Build the built-in Sanity-intake shell over an authenticated,
95
92
  * project-bound client. `forceSend` overrides the environment denial
96
- * (CI / `DO_NOT_TRACK`) an explicit `--share-defs` opts the deploy
97
- * invocation's telemetry in regardless (a command-line arg beats env
98
- * vars). Account-level consent is unaffected: the intake still fetches
93
+ * (CI / `DO_NOT_TRACK`) when a deploy may share definitions—flagless by
94
+ * default or explicitly enabled. Account-level consent is unaffected: the intake still fetches
99
95
  * `/intake/telemetry-status`, so `sanity telemetry disable` still mutes. */
100
96
  export declare function createBuiltinTelemetry(args: {
101
97
  client: TelemetryIntakeClient;
@@ -119,6 +115,18 @@ export declare function raceWithDeadline<T>(work: Promise<T>, deadlineMs: number
119
115
  * the same payload through its `log` seam and owns its own delivery.
120
116
  */
121
117
  export declare function finishCliTelemetry(data: WorkflowCliCommandData): Promise<void>;
118
+ /**
119
+ * The command id a trace reports: the resolved class's canonical id when
120
+ * oclif supplies it, else the id as typed. An alias invocation must report
121
+ * under the same name as the canonical form — one command, one telemetry
122
+ * identity.
123
+ */
124
+ export declare function traceCommandId(options: {
125
+ id: string;
126
+ Command?: {
127
+ id: string;
128
+ } | undefined;
129
+ }): string;
122
130
  /**
123
131
  * The names of declared flags present in `argv` — long form (`--name`,
124
132
  * `--name=value`), negated booleans (`--no-name`), and short chars
@@ -12,8 +12,8 @@ export const WorkflowDefinitionShared = defineEvent({
12
12
  });
13
13
  export const WorkflowDefinitionSharingDecided = defineEvent({
14
14
  name: 'Editorial Workflows Definition Sharing Decided',
15
- version: 1,
16
- description: 'One per deploy invocation that created new definition versions: how the donation decision was reached and what it was (explicit flag / prompt accept-or-decline / remembered / unattended), whether anything reached the endpoint, and how many versions were eligible. Content-free — lets adoption dashboards separate explicit opt-outs, prompt declines, and unattended runs, and measure the true opt-out rate, without ever carrying the document.',
15
+ version: 2,
16
+ description: 'One per deploy invocation that created new definition versions: whether donation followed the default or an explicit flag, whether anything reached the endpoint, and how many versions were eligible. Content-free — measures the true opt-out rate without ever carrying the document.',
17
17
  });
18
18
  const OFF = { kind: 'off', logger: noopTelemetry };
19
19
  let current = OFF;
@@ -77,6 +77,9 @@ export async function finishCliTelemetry(data) {
77
77
  store.end();
78
78
  }
79
79
  }
80
+ export function traceCommandId(options) {
81
+ return options.Command?.id ?? options.id;
82
+ }
80
83
  export function usedFlagNames(argv, flagDefs) {
81
84
  const terminator = argv.indexOf('--');
82
85
  const tokens = argv.slice(0, terminator === -1 ? argv.length : terminator);
package/dist/lib/ui.d.ts CHANGED
@@ -32,6 +32,18 @@ export declare function clipToLimit<T>(rows: T[], limit: number): {
32
32
  rows: T[];
33
33
  note?: string;
34
34
  };
35
+ /**
36
+ * The shared tail of a list command: {@link clipToLimit} the probe-fetched
37
+ * rows, render them through {@link formatTable}, print every line, and print
38
+ * the clip note (when present) beneath the table.
39
+ */
40
+ export declare function logClippedTable<T>({ rows, limit, headers, toCells, log, }: {
41
+ rows: T[];
42
+ limit: number;
43
+ headers: string[];
44
+ toCells: (row: T) => string[];
45
+ log: (line: string) => void;
46
+ }): void;
35
47
  /**
36
48
  * The `▸ label` banner printed above a group's output when a run spans
37
49
  * several groups (deployments for deploy, resources for fanned-out reads),
package/dist/lib/ui.js CHANGED
@@ -31,6 +31,15 @@ export function clipToLimit(rows, limit) {
31
31
  note: styleText('dim', `showing the first ${limit} — raise --limit to see more`),
32
32
  };
33
33
  }
34
+ export function logClippedTable({ rows, limit, headers, toCells, log, }) {
35
+ const { rows: clipped, note } = clipToLimit(rows, limit);
36
+ for (const line of formatTable(headers, clipped.map(toCells))) {
37
+ log(line);
38
+ }
39
+ if (note) {
40
+ log(note);
41
+ }
42
+ }
34
43
  export function groupBanner({ label, index, total, }) {
35
44
  if (total <= 1) {
36
45
  return undefined;