@sanity/workflow-cli 0.12.0 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +51 -0
- package/README.md +77 -48
- package/dist/commands/{abort.d.ts → editorial-workflows/abort.d.ts} +4 -2
- package/dist/commands/{abort.js → editorial-workflows/abort.js} +8 -7
- package/dist/commands/{definition → editorial-workflows/definition}/delete.d.ts +4 -2
- package/dist/commands/{definition → editorial-workflows/definition}/delete.js +7 -6
- package/dist/commands/{definition → editorial-workflows/definition}/diff.d.ts +3 -1
- package/dist/commands/{definition → editorial-workflows/definition}/diff.js +8 -7
- package/dist/commands/{definition → editorial-workflows/definition}/list.d.ts +2 -1
- package/dist/commands/{definition → editorial-workflows/definition}/list.js +21 -21
- package/dist/commands/{definition → editorial-workflows/definition}/show.d.ts +3 -2
- package/dist/commands/{definition → editorial-workflows/definition}/show.js +7 -6
- package/dist/commands/{deploy.d.ts → editorial-workflows/deploy.d.ts} +4 -2
- package/dist/commands/{deploy.js → editorial-workflows/deploy.js} +26 -19
- package/dist/commands/{diagnose.d.ts → editorial-workflows/diagnose.d.ts} +3 -1
- package/dist/commands/{diagnose.js → editorial-workflows/diagnose.js} +8 -7
- package/dist/commands/{fire-action.d.ts → editorial-workflows/fire-action.d.ts} +4 -2
- package/dist/commands/{fire-action.js → editorial-workflows/fire-action.js} +9 -8
- package/dist/commands/{list.d.ts → editorial-workflows/list.d.ts} +2 -1
- package/dist/commands/{list.js → editorial-workflows/list.js} +24 -24
- package/dist/commands/editorial-workflows/nuke.d.ts +13 -0
- package/dist/commands/{nuke.js → editorial-workflows/nuke.js} +22 -17
- package/dist/commands/{reset-activity.d.ts → editorial-workflows/reset-activity.d.ts} +2 -1
- package/dist/commands/{reset-activity.js → editorial-workflows/reset-activity.js} +2 -1
- package/dist/commands/{set-stage.d.ts → editorial-workflows/set-stage.d.ts} +5 -3
- package/dist/commands/{set-stage.js → editorial-workflows/set-stage.js} +7 -6
- package/dist/commands/{show.d.ts → editorial-workflows/show.d.ts} +2 -1
- package/dist/commands/{show.js → editorial-workflows/show.js} +40 -26
- package/dist/commands/{start.d.ts → editorial-workflows/start.d.ts} +4 -2
- package/dist/commands/{start.js → editorial-workflows/start.js} +10 -9
- package/dist/commands/{tail.d.ts → editorial-workflows/tail.d.ts} +2 -1
- package/dist/commands/{tail.js → editorial-workflows/tail.js} +6 -5
- package/dist/hooks/finally/telemetry.js +2 -2
- package/dist/hooks/prerun/telemetry.d.ts +4 -3
- package/dist/lib/base-command.d.ts +4 -6
- package/dist/lib/base-command.js +6 -0
- package/dist/lib/context.d.ts +24 -9
- package/dist/lib/context.js +15 -6
- package/dist/lib/flags.d.ts +27 -4
- package/dist/lib/flags.js +17 -1
- package/dist/lib/nuke.d.ts +15 -1
- package/dist/lib/nuke.js +22 -1
- package/dist/lib/prompt.d.ts +3 -2
- package/dist/lib/select-deployment.d.ts +43 -16
- package/dist/lib/select-deployment.js +66 -15
- package/dist/lib/share-definitions.d.ts +23 -28
- package/dist/lib/share-definitions.js +26 -38
- package/dist/lib/telemetry-setup.d.ts +2 -2
- package/dist/lib/telemetry.d.ts +18 -10
- package/dist/lib/telemetry.js +5 -2
- package/dist/lib/ui.d.ts +12 -0
- package/dist/lib/ui.js +9 -0
- package/oclif.manifest.json +223 -66
- package/package.json +11 -9
- package/dist/commands/nuke.d.ts +0 -11
|
@@ -2,13 +2,14 @@ import { styleText } from 'node:util';
|
|
|
2
2
|
import { Args } from '@oclif/core';
|
|
3
3
|
import { assertReadableModel, displayTitle, errorMessage, } from '@sanity/workflow-engine';
|
|
4
4
|
import logSymbols from 'log-symbols';
|
|
5
|
-
import { WorkflowCommand } from "
|
|
6
|
-
import { findInstance, resolveReadTargets } from "
|
|
7
|
-
import { fail, failureDetail } from "
|
|
8
|
-
import { tagFlags } from "
|
|
9
|
-
import { formatTimestamp } from "
|
|
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";
|
|
9
|
+
import { formatTimestamp } from "../../lib/ui.js";
|
|
10
10
|
const TAIL_TAG = 'tail';
|
|
11
11
|
export default class Tail extends WorkflowCommand {
|
|
12
|
+
static aliases = ['tail'];
|
|
12
13
|
static description = 'Stream new history entries on a workflow instance as they land in the dataset.';
|
|
13
14
|
static examples = ['<%= config.bin %> tail wf-instance.abc123'];
|
|
14
15
|
static args = {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { finishCliTelemetry, usedFlagNames } from "../../lib/telemetry.js";
|
|
1
|
+
import { finishCliTelemetry, traceCommandId, usedFlagNames } from "../../lib/telemetry.js";
|
|
2
2
|
const hook = async function (options) {
|
|
3
3
|
try {
|
|
4
4
|
await finishCliTelemetry({
|
|
5
|
-
command: options
|
|
5
|
+
command: traceCommandId(options),
|
|
6
6
|
flags: usedFlagNames(options.argv, options.Command?.flags),
|
|
7
7
|
success: options.error === undefined,
|
|
8
8
|
});
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import type { Hook } from '@oclif/core';
|
|
2
2
|
/** Builds and installs the invocation's telemetry shell before the command
|
|
3
3
|
* runs; the `finally` hook completes the trace and flushes. Never throws.
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* before oclif parses
|
|
4
|
+
* A deploy that may share definitions—flagless by default or explicitly
|
|
5
|
+
* enabled—forces the store to send despite CI / `DO_NOT_TRACK`. This is
|
|
6
|
+
* resolved from raw argv because the store is built before oclif parses
|
|
7
|
+
* flags. */
|
|
7
8
|
declare const hook: Hook<'prerun'>;
|
|
8
9
|
export default hook;
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import { Command } from '@oclif/core';
|
|
2
2
|
/**
|
|
3
|
-
* Base for
|
|
4
|
-
*
|
|
5
|
-
* {@link fail} path
|
|
6
|
-
*
|
|
7
|
-
* detail at their own `fail` sites; every other error propagates to oclif
|
|
8
|
-
* unchanged.
|
|
3
|
+
* Base for workflow commands: prompt interrupts exit with the conventional
|
|
4
|
+
* SIGINT code and no stack, while an auth rejection escaping `run` renders
|
|
5
|
+
* through the clean {@link fail} path with {@link failureDetail}'s recovery
|
|
6
|
+
* hint. Every other error propagates to oclif unchanged.
|
|
9
7
|
*/
|
|
10
8
|
export declare abstract class WorkflowCommand extends Command {
|
|
11
9
|
protected catch(err: Error & {
|
package/dist/lib/base-command.js
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
|
+
import { styleText } from 'node:util';
|
|
1
2
|
import { Command } from '@oclif/core';
|
|
3
|
+
import { exitCodes } from '@sanity/cli-core';
|
|
2
4
|
import { fail, failureDetail, isAuthRejection } from "./fail.js";
|
|
3
5
|
export class WorkflowCommand extends Command {
|
|
4
6
|
async catch(err) {
|
|
7
|
+
if (err.name === 'ExitPromptError' || err.message === 'SIGINT') {
|
|
8
|
+
this.logToStderr(`${styleText('yellow', '›')} Aborted by user`);
|
|
9
|
+
return this.exit(exitCodes.SIGINT);
|
|
10
|
+
}
|
|
5
11
|
if (isAuthRejection(err)) {
|
|
6
12
|
fail('Authentication failed:', failureDetail(err));
|
|
7
13
|
}
|
package/dist/lib/context.d.ts
CHANGED
|
@@ -13,11 +13,12 @@ export interface InstanceContext {
|
|
|
13
13
|
}
|
|
14
14
|
/**
|
|
15
15
|
* The resolution a WRITE shares: discover the config, pick the deployment for
|
|
16
|
-
* `--tag` (or the sole one), and build an authenticated
|
|
17
|
-
* deployment's resource. A write acts on one specific
|
|
18
|
-
* single, definite target is exactly what it needs.
|
|
16
|
+
* `--deployment` or `--tag` (or the sole one), and build an authenticated
|
|
17
|
+
* client for that deployment's resource. A write acts on one specific
|
|
18
|
+
* deployment, so a single, definite target is exactly what it needs.
|
|
19
19
|
*/
|
|
20
20
|
export declare function resolveContext(flags: {
|
|
21
|
+
deployment?: string | undefined;
|
|
21
22
|
tag?: string | undefined;
|
|
22
23
|
}): Promise<DeploymentContext>;
|
|
23
24
|
/** One resource a read inspects, with its authenticated client. */
|
|
@@ -34,8 +35,9 @@ export interface ReadTarget {
|
|
|
34
35
|
*
|
|
35
36
|
* Untagged, this is every distinct resource the config mentions — a read is
|
|
36
37
|
* harmless to fan out, so "show me what's deployed" spans the whole config.
|
|
37
|
-
* `--tag` narrows to
|
|
38
|
-
* the run, however many
|
|
38
|
+
* `--tag` narrows to the resources deployed under that tag (tags may repeat
|
|
39
|
+
* across deployments). The token resolves once for the run, however many
|
|
40
|
+
* targets it spans.
|
|
39
41
|
*/
|
|
40
42
|
export declare function resolveReadTargets(flags: {
|
|
41
43
|
tag?: string | undefined;
|
|
@@ -44,9 +46,9 @@ export declare function resolveReadTargets(flags: {
|
|
|
44
46
|
* The first occurrence wins its position, so a caller-ordered list (e.g. the
|
|
45
47
|
* engine resource first) keeps its lead entry. */
|
|
46
48
|
export declare function dedupeResources(resources: WorkflowResource[]): WorkflowResource[];
|
|
47
|
-
/** The distinct resources a read targets:
|
|
48
|
-
*
|
|
49
|
-
* mention ({@link dedupeResources}). */
|
|
49
|
+
/** The distinct resources a read targets: every resource deployed under the
|
|
50
|
+
* tag (tags may repeat across deployments), or — untagged — every distinct
|
|
51
|
+
* one the config's deployments mention ({@link dedupeResources}). */
|
|
50
52
|
export declare function resolveReadResources(config: WorkflowConfig, tag: string | undefined): WorkflowResource[];
|
|
51
53
|
/** The single resource a read should target, or fail asking which when the
|
|
52
54
|
* config spans more than one. For the paths that need one definite dataset
|
|
@@ -60,15 +62,28 @@ export declare function resolveReadResource(config: WorkflowConfig, tag: string
|
|
|
60
62
|
* config's declared deployments. An instance id is globally unique and carries
|
|
61
63
|
* its own `tag`, so a command that names one acts on that instance regardless
|
|
62
64
|
* of which tags the config happens to deploy; the config only locates the
|
|
63
|
-
* resource (via {@link
|
|
65
|
+
* resource (via {@link resolveInstanceResource}, no tag filter).
|
|
64
66
|
*
|
|
65
67
|
* Contrast {@link resolveContext}: the deploy/diff/delete path IS scoped to a
|
|
66
68
|
* declared deployment because it acts on the authored definition set (or a
|
|
67
69
|
* definition name, which — unlike an instance id — isn't unique across tags).
|
|
68
70
|
*/
|
|
69
71
|
export declare function resolveInstanceContext(flags: {
|
|
72
|
+
deployment?: string | undefined;
|
|
70
73
|
tag?: string | undefined;
|
|
71
74
|
}, instanceId: string): Promise<InstanceContext>;
|
|
75
|
+
/**
|
|
76
|
+
* Which resource an instance-keyed command reads from: the one `--deployment`
|
|
77
|
+
* names (resolved by its unique deployment name), else the sole or
|
|
78
|
+
* `--tag`-narrowed resource ({@link resolveReadResource}). The instance id is
|
|
79
|
+
* globally unique, so this only locates the resource to look in — never the
|
|
80
|
+
* instance's `tag` partition, which the caller takes from the loaded instance.
|
|
81
|
+
* The shared path behind all four instance-keyed commands.
|
|
82
|
+
*/
|
|
83
|
+
export declare function resolveInstanceResource(config: WorkflowConfig, { deployment, tag }: {
|
|
84
|
+
deployment?: string | undefined;
|
|
85
|
+
tag?: string | undefined;
|
|
86
|
+
}): WorkflowResource;
|
|
72
87
|
/** Fetch an instance by id, exiting cleanly when the resource has no such
|
|
73
88
|
* document — the diagnostic an operator sees on a mistyped id. Split out from
|
|
74
89
|
* {@link resolveInstanceContext} so the not-found path is unit-testable
|
package/dist/lib/context.js
CHANGED
|
@@ -2,11 +2,11 @@ import { assertReadableModel, resourceGdr, } from '@sanity/workflow-engine';
|
|
|
2
2
|
import { clientFor, resolveTokenOrFail } from "./client.js";
|
|
3
3
|
import { fail } from "./fail.js";
|
|
4
4
|
import { loadWorkflowConfig } from "./load-config.js";
|
|
5
|
-
import { selectDeployment } from "./select-deployment.js";
|
|
5
|
+
import { availableDeployments, deploymentsForTag, selectDeployment } from "./select-deployment.js";
|
|
6
6
|
import { resourceLabel } from "./ui.js";
|
|
7
7
|
export async function resolveContext(flags) {
|
|
8
8
|
const config = await loadWorkflowConfig();
|
|
9
|
-
const deployment = selectDeployment(config, { tag: flags.tag });
|
|
9
|
+
const deployment = selectDeployment(config, { name: flags.deployment, tag: flags.tag });
|
|
10
10
|
return { deployment, client: clientFor(deployment.workflowResource, await resolveTokenOrFail()) };
|
|
11
11
|
}
|
|
12
12
|
export async function resolveReadTargets(flags) {
|
|
@@ -20,7 +20,7 @@ export function dedupeResources(resources) {
|
|
|
20
20
|
}
|
|
21
21
|
export function resolveReadResources(config, tag) {
|
|
22
22
|
if (tag !== undefined) {
|
|
23
|
-
return
|
|
23
|
+
return dedupeResources(deploymentsForTag(config, tag).map((deployment) => deployment.workflowResource));
|
|
24
24
|
}
|
|
25
25
|
const resources = dedupeResources(config.deployments.map((d) => d.workflowResource));
|
|
26
26
|
if (resources.length === 0) {
|
|
@@ -34,16 +34,25 @@ export function resolveReadResource(config, tag) {
|
|
|
34
34
|
if (resources.length === 1 && sole !== undefined) {
|
|
35
35
|
return sole;
|
|
36
36
|
}
|
|
37
|
-
|
|
38
|
-
|
|
37
|
+
if (tag === undefined) {
|
|
38
|
+
fail('Config spans multiple resources — pass --deployment or --tag to choose one.', availableDeployments(config));
|
|
39
|
+
}
|
|
40
|
+
const carriers = config.deployments.filter((d) => d.tag === tag);
|
|
41
|
+
fail(`Tag "${tag}" spans multiple resources — pass --deployment to choose one.`, availableDeployments(config, carriers));
|
|
39
42
|
}
|
|
40
43
|
export async function resolveInstanceContext(flags, instanceId) {
|
|
41
44
|
const config = await loadWorkflowConfig();
|
|
42
|
-
const workflowResource =
|
|
45
|
+
const workflowResource = resolveInstanceResource(config, flags);
|
|
43
46
|
const client = clientFor(workflowResource, await resolveTokenOrFail());
|
|
44
47
|
const instance = await loadInstanceOrFail(client, instanceId);
|
|
45
48
|
return { client, scope: { tag: instance.tag, workflowResource } };
|
|
46
49
|
}
|
|
50
|
+
export function resolveInstanceResource(config, { deployment, tag }) {
|
|
51
|
+
if (deployment !== undefined) {
|
|
52
|
+
return selectDeployment(config, { name: deployment, tag: undefined }).workflowResource;
|
|
53
|
+
}
|
|
54
|
+
return resolveReadResource(config, tag);
|
|
55
|
+
}
|
|
47
56
|
export async function loadInstanceOrFail(client, instanceId) {
|
|
48
57
|
const instance = await client.getDocument(instanceId, {
|
|
49
58
|
tag: 'instance.load',
|
package/dist/lib/flags.d.ts
CHANGED
|
@@ -1,10 +1,33 @@
|
|
|
1
|
-
/** The environment tag (e.g. prod, test).
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
1
|
+
/** The environment tag (e.g. prod, test). Tags group deployments by
|
|
2
|
+
* environment and may repeat across a config. For the read commands that
|
|
3
|
+
* consume this directly it's an optional query filter — and the resource
|
|
4
|
+
* disambiguator when a config spans more than one. Write commands layer
|
|
5
|
+
* selection semantics on top via {@link deploymentFlags}; instance-keyed
|
|
6
|
+
* commands add `--deployment` alongside it via {@link instanceFlags}. */
|
|
5
7
|
export declare const tagFlags: {
|
|
6
8
|
tag: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
7
9
|
};
|
|
10
|
+
/** The selectors an instance-keyed command (`diagnose`, `abort`, `set-stage`,
|
|
11
|
+
* `fire-action`) takes. An instance id is globally unique and carries its own
|
|
12
|
+
* `tag`, so these only pick WHICH resource to read it from — never the
|
|
13
|
+
* instance's partition, which always comes from the loaded instance.
|
|
14
|
+
* `--deployment` names one deployment and reads from the resource it targets;
|
|
15
|
+
* `--tag` stays the optional narrower {@link tagFlags} describes. Mutually
|
|
16
|
+
* exclusive; a sole-resource config needs neither. */
|
|
17
|
+
export declare const instanceFlags: {
|
|
18
|
+
deployment: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
19
|
+
tag: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
20
|
+
};
|
|
21
|
+
/** The selectors a single-deployment (write) command takes: `--deployment` —
|
|
22
|
+
* the deployment's unique name identity — or `--tag`, which resolves while the
|
|
23
|
+
* tag names exactly one deployment and errors asking for `--deployment` when
|
|
24
|
+
* it spans several. Mutually exclusive; a sole-deployment config needs neither.
|
|
25
|
+
* `deploy` overrides the `tag` description with its group semantics (every
|
|
26
|
+
* deployment carrying the tag). */
|
|
27
|
+
export declare const deploymentFlags: {
|
|
28
|
+
deployment: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
29
|
+
tag: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
30
|
+
};
|
|
8
31
|
export declare const jsonFlags: {
|
|
9
32
|
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
10
33
|
};
|
package/dist/lib/flags.js
CHANGED
|
@@ -1,7 +1,23 @@
|
|
|
1
1
|
import { Flags } from '@oclif/core';
|
|
2
|
+
const tagAsFilterDescription = 'Workflow environment tag (e.g. prod, test) — an optional query filter, and the resource disambiguator when the config spans several.';
|
|
2
3
|
export const tagFlags = {
|
|
4
|
+
tag: Flags.string({ description: tagAsFilterDescription }),
|
|
5
|
+
};
|
|
6
|
+
export const instanceFlags = {
|
|
7
|
+
deployment: Flags.string({
|
|
8
|
+
description: 'Deployment name — read the instance from the resource that deployment targets; the tag partition still comes from the loaded instance.',
|
|
9
|
+
exclusive: ['tag'],
|
|
10
|
+
}),
|
|
11
|
+
tag: Flags.string({ description: tagAsFilterDescription, exclusive: ['deployment'] }),
|
|
12
|
+
};
|
|
13
|
+
export const deploymentFlags = {
|
|
14
|
+
deployment: Flags.string({
|
|
15
|
+
description: 'Deployment name — the unique identity of one deployment in the config.',
|
|
16
|
+
exclusive: ['tag'],
|
|
17
|
+
}),
|
|
3
18
|
tag: Flags.string({
|
|
4
|
-
description: 'Workflow environment tag (e.g. prod, test) — the deployment to
|
|
19
|
+
description: 'Workflow environment tag (e.g. prod, test) — selects the deployment to act on while the tag names exactly one; pass --deployment when it spans several.',
|
|
20
|
+
exclusive: ['deployment'],
|
|
5
21
|
}),
|
|
6
22
|
};
|
|
7
23
|
export const jsonFlags = {
|
package/dist/lib/nuke.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type WorkflowClient, type WorkflowResource } from '@sanity/workflow-engine';
|
|
1
|
+
import { type WorkflowClient, type WorkflowDeployment, type WorkflowResource } from '@sanity/workflow-engine';
|
|
2
2
|
/** A resource a nuke may delete from, with the client that reads/writes it and
|
|
3
3
|
* whether it is the engine's own resource — the only one that holds instances
|
|
4
4
|
* and definitions (guards can co-locate with subjects in any resource). */
|
|
@@ -32,6 +32,20 @@ export interface PlanCounts {
|
|
|
32
32
|
datasets: number;
|
|
33
33
|
total: number;
|
|
34
34
|
}
|
|
35
|
+
/** Every resource a deployment's nuke sweeps: its own workflow resource (which
|
|
36
|
+
* holds instances + definitions + guards) plus each alias-bound resource
|
|
37
|
+
* (guards co-locate with the subjects they lock). */
|
|
38
|
+
export declare function sweptResources(deployment: WorkflowDeployment): WorkflowResource[];
|
|
39
|
+
/**
|
|
40
|
+
* Fail before any nuke work when another same-tag deployment sweeps a resource
|
|
41
|
+
* the selected one does. Guard document ids embed only the tag
|
|
42
|
+
* (`temp.system.guard.<tag>.wf-instance.*`), so within a shared resource the
|
|
43
|
+
* sweep cannot tell such deployments' guards apart — nuking one would delete
|
|
44
|
+
* the others' live locks, invisibly to the plan. Config validation already
|
|
45
|
+
* forbids sharing tag + workflow resource, so an overlap can only arrive
|
|
46
|
+
* through alias bindings.
|
|
47
|
+
*/
|
|
48
|
+
export declare function refuseOverlappingNuke(deployments: WorkflowDeployment[], selected: WorkflowDeployment): void;
|
|
35
49
|
/**
|
|
36
50
|
* Resolve what a tag-scoped nuke would delete, per resource — the dry-run plan
|
|
37
51
|
* printed before any deletion. Instances and definitions are read only from the
|
package/dist/lib/nuke.js
CHANGED
|
@@ -1,8 +1,29 @@
|
|
|
1
1
|
import { styleText } from 'node:util';
|
|
2
|
-
import { GUARD_DOC_TYPE, WORKFLOW_DEFINITION_TYPE, WORKFLOW_INSTANCE_TYPE, tagScopeFilter, } from '@sanity/workflow-engine';
|
|
2
|
+
import { GUARD_DOC_TYPE, WORKFLOW_DEFINITION_TYPE, WORKFLOW_INSTANCE_TYPE, resourceGdr, tagScopeFilter, } from '@sanity/workflow-engine';
|
|
3
|
+
import { fail } from "./fail.js";
|
|
4
|
+
import { deploymentLabel } from "./select-deployment.js";
|
|
3
5
|
import { formatTable, resourceLabel, sectionHeader } from "./ui.js";
|
|
4
6
|
const CHUNK = 200;
|
|
5
7
|
const REQUEST_TAG = 'nuke';
|
|
8
|
+
export function sweptResources(deployment) {
|
|
9
|
+
return [
|
|
10
|
+
deployment.workflowResource,
|
|
11
|
+
...(deployment.resourceAliases ?? []).map((binding) => binding.resource),
|
|
12
|
+
];
|
|
13
|
+
}
|
|
14
|
+
export function refuseOverlappingNuke(deployments, selected) {
|
|
15
|
+
const swept = new Set(sweptResources(selected).map(resourceGdr));
|
|
16
|
+
const clashing = deployments.filter((candidate) => candidate.name !== selected.name &&
|
|
17
|
+
candidate.tag === selected.tag &&
|
|
18
|
+
sweptResources(candidate).some((resource) => swept.has(resourceGdr(resource))));
|
|
19
|
+
if (clashing.length === 0) {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
fail(`Refusing to nuke "${deploymentLabel(selected)}" — its guards are indistinguishable ` +
|
|
23
|
+
`from ${clashing.map(deploymentLabel).join(', ')}'s in the shared resource(s).`, `Guard ids embed only the tag, and these deployments share tag "${selected.tag}" plus ` +
|
|
24
|
+
"a swept resource — the sweep would delete the other deployments' live guards. " +
|
|
25
|
+
'Give them distinct tags first.');
|
|
26
|
+
}
|
|
6
27
|
export async function resolveNukePlan(args) {
|
|
7
28
|
const { tag, targets } = args;
|
|
8
29
|
const resources = await Promise.all(targets.map((target) => resolveResourcePlan({ tag, target })));
|
package/dist/lib/prompt.d.ts
CHANGED
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
* `TERM` not dumb, no `CI` — so its `NonInteractiveError` can never escape a
|
|
5
5
|
* caller that checked here first), adding stderr-TTY because this package's
|
|
6
6
|
* prompts render on stderr: a redirected stderr must refuse rather than block
|
|
7
|
-
* on stdin with the question swallowed.
|
|
8
|
-
*
|
|
7
|
+
* on stdin with the question swallowed. It classifies whether definition
|
|
8
|
+
* sharing can show its full first-run disclosure and guards interactive
|
|
9
|
+
* prompts such as nuke's confirmation.
|
|
9
10
|
*/
|
|
10
11
|
export declare function canPromptOnStderr(): boolean;
|
|
@@ -1,31 +1,58 @@
|
|
|
1
1
|
import { type DeployTarget, type WorkflowConfig, type WorkflowDeployment } from '@sanity/workflow-engine';
|
|
2
|
+
type ChooseDeploymentName = (deployments: WorkflowDeployment[]) => Promise<string>;
|
|
3
|
+
interface DeploymentSelectionOptions {
|
|
4
|
+
name: string | undefined;
|
|
5
|
+
tag: string | undefined;
|
|
6
|
+
allTags: boolean;
|
|
7
|
+
interactive?: boolean;
|
|
8
|
+
chooseName?: ChooseDeploymentName;
|
|
9
|
+
}
|
|
10
|
+
/** The one identity format for a deployment in output — banners, failure
|
|
11
|
+
* summaries, and "available deployments" listings all render it the same
|
|
12
|
+
* way. */
|
|
13
|
+
export declare function deploymentLabel({ name, tag }: Pick<WorkflowDeployment, 'name' | 'tag'>): string;
|
|
2
14
|
/**
|
|
3
|
-
* Pick the deployment for the requested `--tag`.
|
|
4
|
-
* config (enforced by defineWorkflowConfig), so a
|
|
5
|
-
* deployment.
|
|
15
|
+
* Pick the deployment for the requested `--deployment` or `--tag`. Names are
|
|
16
|
+
* unique across a config (enforced by defineWorkflowConfig), so a name resolves
|
|
17
|
+
* exactly one deployment. Tags are repeatable — a tag resolves only while it
|
|
18
|
+
* names exactly one deployment; when it spans several, the failure lists them
|
|
19
|
+
* so `--deployment` can disambiguate.
|
|
6
20
|
*
|
|
7
|
-
* With no
|
|
8
|
-
* (the common single-environment case), otherwise fail asking for
|
|
9
|
-
* a multi-deployment config is ambiguous without
|
|
10
|
-
*
|
|
21
|
+
* With no selector: fall back to the sole deployment when there's exactly one
|
|
22
|
+
* (the common single-environment case), otherwise fail asking for
|
|
23
|
+
* `--deployment` or `--tag` — a multi-deployment config is ambiguous without
|
|
24
|
+
* one. `orAlternative`
|
|
25
|
+
* extends that ambiguity message for callers with another way out (deploy's
|
|
26
|
+
* `--all-tags`).
|
|
11
27
|
*/
|
|
12
|
-
export declare function selectDeployment(config: WorkflowConfig, { tag, orAlternative }: {
|
|
28
|
+
export declare function selectDeployment(config: WorkflowConfig, { name, tag, orAlternative, }: {
|
|
29
|
+
name: string | undefined;
|
|
13
30
|
tag: string | undefined;
|
|
14
31
|
orAlternative?: string;
|
|
15
32
|
}): WorkflowDeployment;
|
|
16
33
|
/**
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* ambiguous bare `deploy` can't fan a write out across environments.
|
|
34
|
+
* Every deployment carrying `tag`, failing when the tag matches none — the
|
|
35
|
+
* shared resolution for the callers that accept a whole tag group (deploy's
|
|
36
|
+
* tag-as-environment run, the read paths' resource narrowing).
|
|
21
37
|
*/
|
|
22
|
-
export declare function
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
38
|
+
export declare function deploymentsForTag(config: WorkflowConfig, tag: string): WorkflowDeployment[];
|
|
39
|
+
/**
|
|
40
|
+
* The deployments a `deploy` run acts on: every one with `--all-tags`, one
|
|
41
|
+
* definite deployment via `--deployment`, every deployment carrying the tag with
|
|
42
|
+
* `--tag` (a tag is an environment group), otherwise the sole deployment. An
|
|
43
|
+
* ambiguous bare interactive run asks which deployment; a run that cannot
|
|
44
|
+
* prompt keeps the explicit flag guidance. Deploying everything is an
|
|
45
|
+
* explicit opt-in — never a default.
|
|
46
|
+
*/
|
|
47
|
+
export declare function selectDeployments(config: WorkflowConfig, { name, tag, allTags, interactive, chooseName }: DeploymentSelectionOptions): Promise<WorkflowDeployment[]>;
|
|
48
|
+
/** The "Available deployments: name (tag), …" hint every ambiguity failure
|
|
49
|
+
* prints — one vocabulary across the write and instance-keyed paths, so a
|
|
50
|
+
* reader always sees both the `--deployment` names and their `--tag`s. */
|
|
51
|
+
export declare function availableDeployments(config: WorkflowConfig, deployments?: WorkflowDeployment[]): string;
|
|
26
52
|
/**
|
|
27
53
|
* Project a deployment into the engine's {@link DeployTarget} — the shape the
|
|
28
54
|
* deploy/diff verbs (`deployDefinitions`, `computeDiffEntries`, `diffEntry`)
|
|
29
55
|
* consume — expanding its handle bindings into the alias map in the same step.
|
|
30
56
|
*/
|
|
31
57
|
export declare function deploymentToTarget(deployment: WorkflowDeployment): DeployTarget;
|
|
58
|
+
export {};
|
|
@@ -1,35 +1,86 @@
|
|
|
1
|
+
import { select } from '@sanity/cli-core/ux';
|
|
1
2
|
import { resourceAliasesToMap, } from '@sanity/workflow-engine';
|
|
2
3
|
import { fail } from "./fail.js";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
import { canPromptOnStderr } from "./prompt.js";
|
|
5
|
+
export function deploymentLabel({ name, tag }) {
|
|
6
|
+
return `${name} (${tag})`;
|
|
7
|
+
}
|
|
8
|
+
export function selectDeployment(config, { name, tag, orAlternative = '', }) {
|
|
9
|
+
if (name !== undefined) {
|
|
10
|
+
const deployment = config.deployments.find((candidate) => candidate.name === name);
|
|
11
|
+
if (deployment === undefined) {
|
|
12
|
+
fail(`No deployment named "${name}".`, availableDeployments(config));
|
|
7
13
|
}
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
14
|
+
return deployment;
|
|
15
|
+
}
|
|
16
|
+
if (tag !== undefined) {
|
|
17
|
+
const matches = deploymentsForTag(config, tag);
|
|
18
|
+
const [sole, ...rest] = matches;
|
|
19
|
+
if (sole === undefined || rest.length > 0) {
|
|
20
|
+
fail(`Multiple deployments tagged "${tag}" — pass --deployment to choose one.`, availableDeployments(config, matches));
|
|
11
21
|
}
|
|
12
22
|
return sole;
|
|
13
23
|
}
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
24
|
+
if (config.deployments.length > 1) {
|
|
25
|
+
fail(`Multiple deployments configured — pass --deployment or --tag${orAlternative}.`, availableDeployments(config));
|
|
26
|
+
}
|
|
27
|
+
const [sole] = config.deployments;
|
|
28
|
+
if (sole === undefined) {
|
|
29
|
+
fail('No deployments configured.');
|
|
30
|
+
}
|
|
31
|
+
return sole;
|
|
32
|
+
}
|
|
33
|
+
export function deploymentsForTag(config, tag) {
|
|
34
|
+
const matches = config.deployments.filter((candidate) => candidate.tag === tag);
|
|
35
|
+
if (matches.length === 0) {
|
|
36
|
+
fail(`No deployment for tag "${tag}".`, availableDeployments(config));
|
|
17
37
|
}
|
|
18
|
-
return
|
|
38
|
+
return matches;
|
|
19
39
|
}
|
|
20
|
-
export function selectDeployments(config, { tag, allTags }) {
|
|
40
|
+
export async function selectDeployments(config, { name, tag, allTags, interactive, chooseName = chooseDeploymentName }) {
|
|
21
41
|
if (allTags) {
|
|
22
42
|
return config.deployments;
|
|
23
43
|
}
|
|
44
|
+
if (tag !== undefined) {
|
|
45
|
+
return deploymentsForTag(config, tag);
|
|
46
|
+
}
|
|
47
|
+
if (name === undefined && config.deployments.length > 1) {
|
|
48
|
+
if (!(interactive ?? canPromptOnStderr())) {
|
|
49
|
+
return [
|
|
50
|
+
selectDeployment(config, {
|
|
51
|
+
name,
|
|
52
|
+
tag,
|
|
53
|
+
orAlternative: ', or --all-tags to deploy every deployment',
|
|
54
|
+
}),
|
|
55
|
+
];
|
|
56
|
+
}
|
|
57
|
+
const selectedName = await chooseName(config.deployments);
|
|
58
|
+
return [selectDeployment(config, { name: selectedName, tag: undefined })];
|
|
59
|
+
}
|
|
24
60
|
return [
|
|
25
|
-
selectDeployment(config, {
|
|
61
|
+
selectDeployment(config, {
|
|
62
|
+
name,
|
|
63
|
+
tag,
|
|
64
|
+
orAlternative: ', or --all-tags to deploy every deployment',
|
|
65
|
+
}),
|
|
26
66
|
];
|
|
27
67
|
}
|
|
28
|
-
function
|
|
29
|
-
return
|
|
68
|
+
async function chooseDeploymentName(deployments) {
|
|
69
|
+
return select({
|
|
70
|
+
message: 'Select a deployment',
|
|
71
|
+
choices: deployments.map(({ name, tag }) => ({
|
|
72
|
+
name,
|
|
73
|
+
value: name,
|
|
74
|
+
description: `tag: ${tag}`,
|
|
75
|
+
})),
|
|
76
|
+
}, { output: process.stderr });
|
|
77
|
+
}
|
|
78
|
+
export function availableDeployments(config, deployments = config.deployments) {
|
|
79
|
+
return `Available deployments: ${deployments.map(deploymentLabel).join(', ')}`;
|
|
30
80
|
}
|
|
31
81
|
export function deploymentToTarget(deployment) {
|
|
32
82
|
return {
|
|
83
|
+
expectedMinReaderModel: deployment.expectedMinReaderModel,
|
|
33
84
|
tag: deployment.tag,
|
|
34
85
|
workflowResource: deployment.workflowResource,
|
|
35
86
|
resourceAliases: resourceAliasesToMap(deployment.resourceAliases),
|
|
@@ -10,12 +10,11 @@
|
|
|
10
10
|
* Consent model: opt-OUT. A deploy that creates new definition versions
|
|
11
11
|
* donates by default. The choice is skipped by an explicit flag:
|
|
12
12
|
* `--share-defs` (donate, no prompt) or `--no-share-defs` (don't). Without a
|
|
13
|
-
* flag,
|
|
14
|
-
* in the shared Sanity user config
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* "silent in CI / `DO_NOT_TRACK`" rule; opt in per run with `--share-defs`.
|
|
13
|
+
* flag, the CLI shares. An interactive terminal gets a ONE-TIME notice
|
|
14
|
+
* (persisted in the shared Sanity user config) before the first donation;
|
|
15
|
+
* every later flagless deploy, and every unattended deploy, prints a concise
|
|
16
|
+
* reminder. CI, `DO_NOT_TRACK`, and non-TTY execution do not change the
|
|
17
|
+
* content-sharing default. Only `--no-share-defs` opts out.
|
|
19
18
|
*/
|
|
20
19
|
import { type DeployDefinitionResult, type DeployedDefinition, type WorkflowResource } from '@sanity/workflow-engine';
|
|
21
20
|
import type { UserConfigStore } from './telemetry-setup.ts';
|
|
@@ -50,22 +49,20 @@ export interface ShareCandidate {
|
|
|
50
49
|
* routed project-agnostically through the API gateway). */
|
|
51
50
|
export declare const SHARE_ENDPOINT_URI = "/workflow/definition-feedback";
|
|
52
51
|
/**
|
|
53
|
-
* The first-run
|
|
52
|
+
* The first-run disclosure — a product contract, pinned by test. It must
|
|
54
53
|
* name the recipient (Sanity), state that the document ships VERBATIM with its
|
|
55
54
|
* deployment coordinates and is kept, name what is never shared, and disclose
|
|
56
|
-
* that the
|
|
57
|
-
*
|
|
58
|
-
*
|
|
55
|
+
* that the notice is remembered machine-wide and name the per-run opt-out.
|
|
56
|
+
* This is a notice, not a choice: sharing is the default unless the invocation
|
|
57
|
+
* supplies `--no-share-defs`.
|
|
59
58
|
*/
|
|
60
|
-
export declare const
|
|
59
|
+
export declare const SHARE_FIRST_RUN_NOTICE: string;
|
|
61
60
|
/**
|
|
62
|
-
* The
|
|
63
|
-
* first
|
|
64
|
-
*
|
|
65
|
-
* override. `_ON` shows when the remembered answer shares, `_OFF` when it does not.
|
|
61
|
+
* The status block printed on every flagless deploy that does not show the
|
|
62
|
+
* first-run disclosure. It keeps the default donation obvious in terminals and
|
|
63
|
+
* unattended logs and names both explicit controls.
|
|
66
64
|
*/
|
|
67
|
-
export declare const
|
|
68
|
-
export declare const SHARE_REMINDER_OFF: string;
|
|
65
|
+
export declare const SHARE_REMINDER: string;
|
|
69
66
|
/**
|
|
70
67
|
* The content-free telemetry marker for one shared definition — projected
|
|
71
68
|
* from the engine's own deploy-event derivation ({@link definitionDeployedData})
|
|
@@ -80,9 +77,9 @@ export declare function definitionShareMarker(args: {
|
|
|
80
77
|
}): WorkflowDefinitionSharedData;
|
|
81
78
|
/**
|
|
82
79
|
* Whether the invocation's telemetry should send despite the environment
|
|
83
|
-
* denial (CI / `DO_NOT_TRACK`).
|
|
84
|
-
*
|
|
85
|
-
*
|
|
80
|
+
* denial (CI / `DO_NOT_TRACK`). Every flagless or explicitly enabled deploy
|
|
81
|
+
* that can create versions forces it, because definition donation defaults on
|
|
82
|
+
* in those environments too. Account-level consent is never
|
|
86
83
|
* overridden (the intake still reads `/intake/telemetry-status`).
|
|
87
84
|
*
|
|
88
85
|
* Computed from raw argv because the prerun hook builds the store before oclif
|
|
@@ -98,12 +95,12 @@ export declare function shouldForceShareTelemetry(args: {
|
|
|
98
95
|
argv: string[];
|
|
99
96
|
}): boolean;
|
|
100
97
|
/**
|
|
101
|
-
* The whole post-deploy sharing flow.
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
* adoption dashboards can measure opt-out rate. No newly created
|
|
105
|
-
* (unchanged re-deploys, dry runs, nothing succeeded) means no
|
|
106
|
-
*
|
|
98
|
+
* The whole post-deploy sharing flow. An explicit flag decides directly;
|
|
99
|
+
* otherwise sharing defaults on after emitting either the one-time disclosure
|
|
100
|
+
* or recurring reminder. It always records the content-free per-invocation
|
|
101
|
+
* decision so adoption dashboards can measure opt-out rate. No newly created
|
|
102
|
+
* versions (unchanged re-deploys, dry runs, nothing succeeded) means no
|
|
103
|
+
* decision, notice, or POST.
|
|
107
104
|
*
|
|
108
105
|
* The optional args exist for the flow's dependencies, mirroring
|
|
109
106
|
* `setupCliTelemetry` — omitted, the real process surfaces apply.
|
|
@@ -113,8 +110,6 @@ export declare function shareDefinitionsAfterDeploy(args: {
|
|
|
113
110
|
candidates: ShareCandidate[];
|
|
114
111
|
warn: (message: string) => void;
|
|
115
112
|
interactive?: boolean;
|
|
116
|
-
envDenied?: boolean;
|
|
117
113
|
writeStderr?: (message: string) => void;
|
|
118
114
|
userConfig?: UserConfigStore;
|
|
119
|
-
confirmShare?: () => Promise<boolean>;
|
|
120
115
|
}): Promise<void>;
|