@sanity/workflow-cli 0.8.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +99 -61
- package/dist/commands/abort.d.ts +5 -7
- package/dist/commands/abort.js +15 -14
- package/dist/commands/definition/delete.d.ts +10 -6
- package/dist/commands/definition/delete.js +8 -11
- package/dist/commands/definition/diff.d.ts +8 -1
- package/dist/commands/definition/diff.js +30 -24
- package/dist/commands/definition/list.d.ts +4 -4
- package/dist/commands/definition/list.js +8 -8
- package/dist/commands/definition/show.d.ts +0 -10
- package/dist/commands/definition/show.js +11 -20
- package/dist/commands/deploy.d.ts +58 -5
- package/dist/commands/deploy.js +134 -27
- package/dist/commands/diagnose.d.ts +5 -1
- package/dist/commands/diagnose.js +18 -28
- package/dist/commands/fire-action.d.ts +15 -28
- package/dist/commands/fire-action.js +37 -72
- package/dist/commands/list.d.ts +2 -2
- package/dist/commands/list.js +8 -8
- package/dist/commands/{retry-activity.d.ts → reset-activity.d.ts} +1 -1
- package/dist/commands/{retry-activity.js → reset-activity.js} +2 -2
- package/dist/commands/set-stage.d.ts +27 -3
- package/dist/commands/set-stage.js +80 -13
- package/dist/commands/show.d.ts +1 -0
- package/dist/commands/show.js +14 -8
- package/dist/commands/start.d.ts +59 -0
- package/dist/commands/start.js +169 -0
- package/dist/commands/tail.d.ts +3 -0
- package/dist/commands/tail.js +9 -5
- package/dist/lib/client.d.ts +11 -14
- package/dist/lib/client.js +45 -33
- package/dist/lib/context.d.ts +62 -0
- package/dist/lib/context.js +82 -0
- package/dist/lib/definitions.d.ts +38 -29
- package/dist/lib/definitions.js +45 -85
- package/dist/lib/diff.js +8 -0
- package/dist/lib/env.d.ts +0 -6
- package/dist/lib/env.js +3 -9
- package/dist/lib/fail.d.ts +6 -0
- package/dist/lib/fail.js +11 -1
- package/dist/lib/flags.d.ts +14 -2
- package/dist/lib/flags.js +24 -3
- package/dist/lib/load-config.d.ts +11 -0
- package/dist/lib/load-config.js +69 -0
- package/dist/lib/operation-args.d.ts +25 -7
- package/dist/lib/operation-args.js +12 -11
- package/dist/lib/ops-report.d.ts +19 -7
- package/dist/lib/ops-report.js +6 -7
- package/dist/lib/params.d.ts +7 -0
- package/dist/lib/params.js +26 -0
- package/dist/lib/select-deployment.d.ts +31 -0
- package/dist/lib/select-deployment.js +58 -0
- package/dist/lib/ui.d.ts +3 -1
- package/dist/lib/ui.js +1 -3
- package/oclif.manifest.json +145 -86
- package/package.json +6 -4
- package/dist/commands/move-stage.d.ts +0 -47
- package/dist/commands/move-stage.js +0 -77
- package/dist/lib/config.d.ts +0 -18
- package/dist/lib/config.js +0 -50
|
@@ -1,24 +1,11 @@
|
|
|
1
1
|
import { styleText } from 'node:util';
|
|
2
2
|
import { Args, Command, Flags } from '@oclif/core';
|
|
3
|
-
import { isTerminalStage,
|
|
3
|
+
import { isTerminalStage, } from '@sanity/workflow-engine';
|
|
4
4
|
import logSymbols from 'log-symbols';
|
|
5
|
-
import {
|
|
5
|
+
import { resolveReadContext } from "../../lib/context.js";
|
|
6
|
+
import { buildDefinitionShowQuery } from "../../lib/definitions.js";
|
|
6
7
|
import { tagFlags } from "../../lib/flags.js";
|
|
7
8
|
import { formatKeyValue, sectionHeader } from "../../lib/ui.js";
|
|
8
|
-
export function buildDefinitionShowQuery(flags) {
|
|
9
|
-
const params = { name: flags.name };
|
|
10
|
-
const filters = [`_type == "${WORKFLOW_DEFINITION_TYPE}"`, 'name == $name'];
|
|
11
|
-
if (flags.version !== undefined) {
|
|
12
|
-
params.version = flags.version;
|
|
13
|
-
filters.push('version == $version');
|
|
14
|
-
}
|
|
15
|
-
if (flags.tag) {
|
|
16
|
-
params.tag = flags.tag;
|
|
17
|
-
filters.push(tagScopeFilter());
|
|
18
|
-
}
|
|
19
|
-
const groq = `*[${filters.join(' && ')}] | order(version desc) [0]`;
|
|
20
|
-
return { groq, params };
|
|
21
|
-
}
|
|
22
9
|
export default class DefinitionShow extends Command {
|
|
23
10
|
static description = 'Show a deployed workflow definition.';
|
|
24
11
|
static args = {
|
|
@@ -30,12 +17,12 @@ export default class DefinitionShow extends Command {
|
|
|
30
17
|
};
|
|
31
18
|
async run() {
|
|
32
19
|
const { args, flags } = await this.parse(DefinitionShow);
|
|
20
|
+
const { client } = await resolveReadContext(flags);
|
|
33
21
|
const { groq, params } = buildDefinitionShowQuery({
|
|
34
22
|
name: args.name,
|
|
35
23
|
tag: flags.tag,
|
|
36
24
|
version: flags.version,
|
|
37
25
|
});
|
|
38
|
-
const client = loadClient();
|
|
39
26
|
// A deployed document: the authored {@link WorkflowDefinition} plus the
|
|
40
27
|
// deploy-stamped envelope (`version`, `contentHash`, `tag`).
|
|
41
28
|
const def = await client.fetch(groq, params);
|
|
@@ -57,11 +44,15 @@ const HEADER_PAD = 11; // "Description" — the longest label in the block
|
|
|
57
44
|
* authored definition, so it's passed separately and omitted when unknown. */
|
|
58
45
|
export function definitionHeader(def, tag) {
|
|
59
46
|
const rows = [
|
|
60
|
-
formatKeyValue('Title', def.title,
|
|
61
|
-
formatKeyValue(
|
|
47
|
+
formatKeyValue({ key: 'Title', value: def.title, padTo: HEADER_PAD }),
|
|
48
|
+
formatKeyValue({
|
|
49
|
+
key: 'Description',
|
|
50
|
+
value: def.description ?? styleText('dim', '—'),
|
|
51
|
+
padTo: HEADER_PAD,
|
|
52
|
+
}),
|
|
62
53
|
];
|
|
63
54
|
if (tag !== undefined) {
|
|
64
|
-
rows.push(formatKeyValue('Tag', tag,
|
|
55
|
+
rows.push(formatKeyValue({ key: 'Tag', value: tag, padTo: HEADER_PAD }));
|
|
65
56
|
}
|
|
66
57
|
return [styleText('bold', `${def.name} v${def.version}`), ...rows].join('\n');
|
|
67
58
|
}
|
|
@@ -1,22 +1,75 @@
|
|
|
1
1
|
import { Command } from '@oclif/core';
|
|
2
|
+
import { type DeployDefinitionResult, type WorkflowDefinition, type WorkflowDeployment } from '@sanity/workflow-engine';
|
|
3
|
+
/** A deployment paired with the definitions selected + validated for it. */
|
|
4
|
+
interface DeployBatch {
|
|
5
|
+
deployment: WorkflowDeployment;
|
|
6
|
+
defs: WorkflowDefinition[];
|
|
7
|
+
}
|
|
8
|
+
/** A deployment whose deploy/diff threw, kept for the closing summary. */
|
|
9
|
+
interface DeployFailure {
|
|
10
|
+
deployment: Pick<WorkflowDeployment, 'name' | 'tag'>;
|
|
11
|
+
message: string;
|
|
12
|
+
}
|
|
2
13
|
export default class Deploy extends Command {
|
|
3
14
|
static description: string;
|
|
4
15
|
static examples: string[];
|
|
5
16
|
static flags: {
|
|
17
|
+
'all-tags': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
6
18
|
'dry-run': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
7
19
|
check: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
8
20
|
only: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
9
21
|
tag: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
22
|
};
|
|
11
23
|
run(): Promise<void>;
|
|
24
|
+
private processDeployment;
|
|
12
25
|
private executeDeploy;
|
|
13
26
|
private runDryRun;
|
|
14
27
|
}
|
|
15
28
|
/** Reject the contradictory `--check --dry-run` combination. */
|
|
16
29
|
export declare function validateModeFlags(check: boolean, dryRun: boolean): void;
|
|
30
|
+
/**
|
|
31
|
+
* Select and validate every deployment's definitions up front. Validation runs
|
|
32
|
+
* before any network write ({@link validateOrFail} exits on a bad definition),
|
|
33
|
+
* so a whole-config deploy can never leave a partial reconcile behind a broken
|
|
34
|
+
* batch. In a multi-deployment run the failure names its deployment — it exits
|
|
35
|
+
* before any banner could attribute it.
|
|
36
|
+
*/
|
|
37
|
+
export declare function buildBatches(deployments: WorkflowDeployment[], only: string | undefined): DeployBatch[];
|
|
38
|
+
/**
|
|
39
|
+
* Run `processBatch` over each batch, continuing past a failure so one bad
|
|
40
|
+
* deployment can't strand the rest — deployments are independent, and deploys
|
|
41
|
+
* are idempotent, so a partial run is safe to re-run. Failures are flattened
|
|
42
|
+
* to their message for {@link deploySummary}. A single-batch run rethrows
|
|
43
|
+
* instead: it has no summary, so a throw from before the deploy spinner starts
|
|
44
|
+
* would otherwise surface nowhere — and propagating keeps the stack visible.
|
|
45
|
+
*/
|
|
46
|
+
export declare function reconcileBatches({ batches, log, processBatch, }: {
|
|
47
|
+
batches: DeployBatch[];
|
|
48
|
+
log: (line: string) => void;
|
|
49
|
+
processBatch: (batch: DeployBatch) => Promise<void>;
|
|
50
|
+
}): Promise<DeployFailure[]>;
|
|
51
|
+
/**
|
|
52
|
+
* The closing summary for a multi-deployment run that had failures: the
|
|
53
|
+
* succeeded/failed tally and each failed deployment. Empty for a clean run.
|
|
54
|
+
* The wording is mode-neutral because a `--dry-run` failure lands here too,
|
|
55
|
+
* where nothing was deployed. Single-batch runs never reach it — their lone
|
|
56
|
+
* failure rethrows out of {@link reconcileBatches}.
|
|
57
|
+
*/
|
|
58
|
+
export declare function deploySummary({ total, failures, }: {
|
|
59
|
+
total: number;
|
|
60
|
+
failures: DeployFailure[];
|
|
61
|
+
}): string[];
|
|
62
|
+
/**
|
|
63
|
+
* The banner printed above a deployment's output so a multi-deployment run is
|
|
64
|
+
* attributable. `undefined` for a single-target run — it keeps its original
|
|
65
|
+
* unprefixed output. A leading blank line separates every deployment after the
|
|
66
|
+
* first.
|
|
67
|
+
*/
|
|
68
|
+
export declare function deploymentBanner({ deployment, index, total, }: {
|
|
69
|
+
deployment: Pick<WorkflowDeployment, 'name' | 'tag'>;
|
|
70
|
+
index: number;
|
|
71
|
+
total: number;
|
|
72
|
+
}): string | undefined;
|
|
17
73
|
/** One line per deployed definition, symbol-tagged by its result status. */
|
|
18
|
-
export declare function deployResultLines(results:
|
|
19
|
-
|
|
20
|
-
definition: string;
|
|
21
|
-
version: number;
|
|
22
|
-
}[]): string[];
|
|
74
|
+
export declare function deployResultLines(results: DeployDefinitionResult[]): string[];
|
|
75
|
+
export {};
|
package/dist/commands/deploy.js
CHANGED
|
@@ -1,23 +1,31 @@
|
|
|
1
|
+
import { styleText } from 'node:util';
|
|
1
2
|
import { Command, Flags } from '@oclif/core';
|
|
2
|
-
import { computeDiffEntries, workflow } from '@sanity/workflow-engine';
|
|
3
|
+
import { computeDiffEntries, workflow, } from '@sanity/workflow-engine';
|
|
3
4
|
import logSymbols from 'log-symbols';
|
|
4
5
|
import ora from 'ora';
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import { loadValidatedDefinitions } from "../lib/definitions.js";
|
|
6
|
+
import { clientFor, resolveTokenOrFail } from "../lib/client.js";
|
|
7
|
+
import { selectDefinitions, validateOrFail } from "../lib/definitions.js";
|
|
8
8
|
import { diffReport } from "../lib/diff.js";
|
|
9
|
-
import { fail } from "../lib/fail.js";
|
|
9
|
+
import { errorMessage, fail } from "../lib/fail.js";
|
|
10
10
|
import { tagFlags } from "../lib/flags.js";
|
|
11
|
+
import { loadWorkflowConfig } from "../lib/load-config.js";
|
|
12
|
+
import { deploymentToTarget, selectDeployments } from "../lib/select-deployment.js";
|
|
11
13
|
export default class Deploy extends Command {
|
|
12
|
-
static description = 'Validate, diff, and deploy workflow definitions to the
|
|
14
|
+
static description = 'Validate, diff, and deploy workflow definitions to the resource bound by the selected deployment.';
|
|
13
15
|
static examples = [
|
|
14
|
-
'<%= config.bin %> deploy',
|
|
16
|
+
'<%= config.bin %> deploy --tag prod',
|
|
17
|
+
'<%= config.bin %> deploy --all-tags',
|
|
15
18
|
'<%= config.bin %> deploy --check',
|
|
16
19
|
'<%= config.bin %> deploy --dry-run',
|
|
17
20
|
'<%= config.bin %> deploy --only productLaunch',
|
|
18
21
|
];
|
|
19
22
|
static flags = {
|
|
20
23
|
...tagFlags,
|
|
24
|
+
'all-tags': Flags.boolean({
|
|
25
|
+
description: 'Deploy every deployment in the config, not just one tag.',
|
|
26
|
+
default: false,
|
|
27
|
+
exclusive: ['tag'],
|
|
28
|
+
}),
|
|
21
29
|
'dry-run': Flags.boolean({
|
|
22
30
|
description: 'Validate + diff against the deployed version; do not write.',
|
|
23
31
|
default: false,
|
|
@@ -27,53 +35,75 @@ export default class Deploy extends Command {
|
|
|
27
35
|
default: false,
|
|
28
36
|
}),
|
|
29
37
|
only: Flags.string({
|
|
30
|
-
description: 'Limit deploy/check/diff to a single workflow definition by name.',
|
|
38
|
+
description: 'Limit deploy/check/diff to a single workflow definition by name. Every targeted deployment must contain it.',
|
|
31
39
|
}),
|
|
32
40
|
};
|
|
33
41
|
async run() {
|
|
34
42
|
const { flags } = await this.parse(Deploy);
|
|
35
43
|
validateModeFlags(flags.check, flags['dry-run']);
|
|
36
|
-
const config = loadWorkflowConfig(
|
|
37
|
-
|
|
44
|
+
const config = await loadWorkflowConfig();
|
|
45
|
+
// Validate every deployment up front (below), so a bad definition fails
|
|
46
|
+
// before any write — never a partial reconcile from a validation error.
|
|
47
|
+
const batches = buildBatches(selectDeployments(config, { tag: flags.tag, allTags: flags['all-tags'] }), flags.only);
|
|
48
|
+
const log = (line) => this.log(line);
|
|
49
|
+
// buildBatches already validated everything offline — --check only has to
|
|
50
|
+
// report that pass, so it never resolves a token or builds a client.
|
|
38
51
|
if (flags.check) {
|
|
39
|
-
|
|
52
|
+
await reconcileBatches({
|
|
53
|
+
batches,
|
|
54
|
+
log,
|
|
55
|
+
processBatch: async ({ defs }) => {
|
|
56
|
+
log(`${logSymbols.success} ${defs.length} definition(s) passed validation (check only — dataset not contacted).`);
|
|
57
|
+
},
|
|
58
|
+
});
|
|
40
59
|
return;
|
|
41
60
|
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
61
|
+
// One token up front, so an --all-tags run authenticates once, not per deployment.
|
|
62
|
+
const token = await resolveTokenOrFail();
|
|
63
|
+
const failures = await reconcileBatches({
|
|
64
|
+
batches,
|
|
65
|
+
log,
|
|
66
|
+
processBatch: ({ deployment, defs }) => this.processDeployment({ deployment, defs, dryRun: flags['dry-run'], token }),
|
|
67
|
+
});
|
|
68
|
+
for (const line of deploySummary({ total: batches.length, failures })) {
|
|
69
|
+
log(line);
|
|
70
|
+
}
|
|
71
|
+
if (failures.length > 0) {
|
|
72
|
+
this.exit(1);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
async processDeployment({ deployment, defs, dryRun, token, }) {
|
|
76
|
+
const target = deploymentToTarget(deployment);
|
|
77
|
+
const client = clientFor(deployment.workflowResource, token);
|
|
78
|
+
if (dryRun) {
|
|
79
|
+
await this.runDryRun({ client, defs, target });
|
|
45
80
|
return;
|
|
46
81
|
}
|
|
47
|
-
await this.executeDeploy(client,
|
|
82
|
+
await this.executeDeploy({ client, defs, target });
|
|
48
83
|
}
|
|
49
|
-
async executeDeploy(client, defs,
|
|
84
|
+
async executeDeploy({ client, defs, target, }) {
|
|
50
85
|
const spinner = ora(`Deploying ${defs.length} definition(s)…`).start();
|
|
51
86
|
try {
|
|
52
|
-
const { results } = await workflow.deployDefinitions({
|
|
53
|
-
workflowResource: config.workflowResource,
|
|
54
|
-
tag: config.tag,
|
|
55
|
-
client,
|
|
56
|
-
definitions: defs,
|
|
57
|
-
});
|
|
87
|
+
const { results } = await workflow.deployDefinitions({ ...target, client, definitions: defs });
|
|
58
88
|
spinner.succeed(`Processed ${results.length} definition(s)`);
|
|
59
89
|
for (const line of deployResultLines(results))
|
|
60
90
|
this.log(line);
|
|
61
91
|
}
|
|
62
92
|
catch (error) {
|
|
63
|
-
spinner.fail(
|
|
93
|
+
spinner.fail(`Deploy failed — ${errorMessage(error)}`);
|
|
64
94
|
throw error;
|
|
65
95
|
}
|
|
66
96
|
}
|
|
67
|
-
async runDryRun(client, defs,
|
|
97
|
+
async runDryRun({ client, defs, target, }) {
|
|
68
98
|
const spinner = ora(`Diffing ${defs.length} definition(s) against dataset…`).start();
|
|
69
99
|
try {
|
|
70
|
-
const entries = await computeDiffEntries(client, defs,
|
|
100
|
+
const entries = await computeDiffEntries({ client, defs, target });
|
|
71
101
|
spinner.succeed(`Diffed ${defs.length} definition(s)`);
|
|
72
102
|
for (const line of diffReport(entries))
|
|
73
103
|
this.log(line);
|
|
74
104
|
}
|
|
75
105
|
catch (error) {
|
|
76
|
-
spinner.fail(
|
|
106
|
+
spinner.fail(`Diff failed — ${errorMessage(error)}`);
|
|
77
107
|
throw error;
|
|
78
108
|
}
|
|
79
109
|
}
|
|
@@ -84,6 +114,83 @@ export function validateModeFlags(check, dryRun) {
|
|
|
84
114
|
fail('Pass either --check or --dry-run, not both.');
|
|
85
115
|
}
|
|
86
116
|
}
|
|
117
|
+
/** The one identity format for a deployment in output — banner, failure
|
|
118
|
+
* summary, and validation attribution all render it the same way. */
|
|
119
|
+
function deploymentLabel({ name, tag }) {
|
|
120
|
+
return `${name} (${tag})`;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Select and validate every deployment's definitions up front. Validation runs
|
|
124
|
+
* before any network write ({@link validateOrFail} exits on a bad definition),
|
|
125
|
+
* so a whole-config deploy can never leave a partial reconcile behind a broken
|
|
126
|
+
* batch. In a multi-deployment run the failure names its deployment — it exits
|
|
127
|
+
* before any banner could attribute it.
|
|
128
|
+
*/
|
|
129
|
+
export function buildBatches(deployments, only) {
|
|
130
|
+
return deployments.map((deployment) => {
|
|
131
|
+
const context = deployments.length > 1 ? `${deploymentLabel(deployment)} — ` : '';
|
|
132
|
+
const defs = selectDefinitions(deployment.definitions, { only, context });
|
|
133
|
+
validateOrFail(defs, context);
|
|
134
|
+
return { deployment, defs };
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Run `processBatch` over each batch, continuing past a failure so one bad
|
|
139
|
+
* deployment can't strand the rest — deployments are independent, and deploys
|
|
140
|
+
* are idempotent, so a partial run is safe to re-run. Failures are flattened
|
|
141
|
+
* to their message for {@link deploySummary}. A single-batch run rethrows
|
|
142
|
+
* instead: it has no summary, so a throw from before the deploy spinner starts
|
|
143
|
+
* would otherwise surface nowhere — and propagating keeps the stack visible.
|
|
144
|
+
*/
|
|
145
|
+
export async function reconcileBatches({ batches, log, processBatch, }) {
|
|
146
|
+
const failures = [];
|
|
147
|
+
for (const [index, batch] of batches.entries()) {
|
|
148
|
+
const banner = deploymentBanner({ deployment: batch.deployment, index, total: batches.length });
|
|
149
|
+
if (banner !== undefined) {
|
|
150
|
+
log(banner);
|
|
151
|
+
}
|
|
152
|
+
try {
|
|
153
|
+
await processBatch(batch);
|
|
154
|
+
}
|
|
155
|
+
catch (error) {
|
|
156
|
+
if (batches.length === 1) {
|
|
157
|
+
throw error;
|
|
158
|
+
}
|
|
159
|
+
failures.push({ deployment: batch.deployment, message: errorMessage(error) });
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return failures;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* The closing summary for a multi-deployment run that had failures: the
|
|
166
|
+
* succeeded/failed tally and each failed deployment. Empty for a clean run.
|
|
167
|
+
* The wording is mode-neutral because a `--dry-run` failure lands here too,
|
|
168
|
+
* where nothing was deployed. Single-batch runs never reach it — their lone
|
|
169
|
+
* failure rethrows out of {@link reconcileBatches}.
|
|
170
|
+
*/
|
|
171
|
+
export function deploySummary({ total, failures, }) {
|
|
172
|
+
if (failures.length === 0) {
|
|
173
|
+
return [];
|
|
174
|
+
}
|
|
175
|
+
return [
|
|
176
|
+
'',
|
|
177
|
+
styleText('bold', `${total - failures.length} of ${total} deployments succeeded — ${failures.length} failed:`),
|
|
178
|
+
...failures.map((f) => styleText('red', ` ${logSymbols.error} ${deploymentLabel(f.deployment)} — ${f.message}`)),
|
|
179
|
+
];
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* The banner printed above a deployment's output so a multi-deployment run is
|
|
183
|
+
* attributable. `undefined` for a single-target run — it keeps its original
|
|
184
|
+
* unprefixed output. A leading blank line separates every deployment after the
|
|
185
|
+
* first.
|
|
186
|
+
*/
|
|
187
|
+
export function deploymentBanner({ deployment, index, total, }) {
|
|
188
|
+
if (total <= 1) {
|
|
189
|
+
return undefined;
|
|
190
|
+
}
|
|
191
|
+
const gap = index > 0 ? '\n' : '';
|
|
192
|
+
return `${gap}${styleText('bold', `▸ ${deploymentLabel(deployment)}`)}`;
|
|
193
|
+
}
|
|
87
194
|
/** One line per deployed definition, symbol-tagged by its result status. */
|
|
88
195
|
export function deployResultLines(results) {
|
|
89
196
|
const symbols = {
|
|
@@ -91,6 +198,6 @@ export function deployResultLines(results) {
|
|
|
91
198
|
};
|
|
92
199
|
return results.map((r) => {
|
|
93
200
|
const symbol = symbols[r.status] ?? logSymbols.warning;
|
|
94
|
-
return ` ${symbol} ${r.status.padEnd(9)} ${r.
|
|
201
|
+
return ` ${symbol} ${r.status.padEnd(9)} ${r.name} v${r.version}`;
|
|
95
202
|
});
|
|
96
203
|
}
|
|
@@ -8,7 +8,11 @@ import { type Diagnosis, type DiagnoseInput, type SuggestedRemediation } from '@
|
|
|
8
8
|
* {@link SuggestedRemediation}s rather than re-deriving them, so the rendered
|
|
9
9
|
* fix block and the `--json` output stay one computation.
|
|
10
10
|
*/
|
|
11
|
-
export declare function renderDiagnosis(diagnosis
|
|
11
|
+
export declare function renderDiagnosis({ diagnosis, input, remediations, }: {
|
|
12
|
+
diagnosis: Diagnosis;
|
|
13
|
+
input: DiagnoseInput;
|
|
14
|
+
remediations: SuggestedRemediation[];
|
|
15
|
+
}): string[];
|
|
12
16
|
export default class Diagnose extends Command {
|
|
13
17
|
static description: string;
|
|
14
18
|
static examples: string[];
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { styleText } from 'node:util';
|
|
2
|
-
import { Args, Command
|
|
2
|
+
import { Args, Command } from '@oclif/core';
|
|
3
3
|
import { diagnoseInputFromEvaluation, workflow, } from '@sanity/workflow-engine';
|
|
4
4
|
import logSymbols from 'log-symbols';
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import { tagFlags } from "../lib/flags.js";
|
|
5
|
+
import { resolveInstanceContext } from "../lib/context.js";
|
|
6
|
+
import { errorMessage, fail } from "../lib/fail.js";
|
|
7
|
+
import { jsonFlags, tagFlags } from "../lib/flags.js";
|
|
9
8
|
import { formatTimestamp, sectionHeader, activityIcon } from "../lib/ui.js";
|
|
10
9
|
import { instanceHeader } from "./show.js";
|
|
11
10
|
function formatAssignee(a) {
|
|
@@ -121,23 +120,18 @@ function transitionUnevaluableDetail(transitions) {
|
|
|
121
120
|
`that is missing or unreadable (GROQ null), so routing is held rather than`,
|
|
122
121
|
`falling through. Make the data the filter reads readable — publish the`,
|
|
123
122
|
`subject (or fill the field) — and the instance advances on its own; no`,
|
|
124
|
-
`
|
|
123
|
+
`set-stage needed.`,
|
|
125
124
|
],
|
|
126
125
|
};
|
|
127
126
|
}
|
|
128
|
-
const REMEDIATION_LABEL = {
|
|
129
|
-
'retry-effect': 'retry-effect',
|
|
130
|
-
'drain-effects': 're-run the effect drainer',
|
|
131
|
-
'reset-activity': 'reset-activity',
|
|
132
|
-
'set-stage': 'move-stage',
|
|
133
|
-
abort: 'abort',
|
|
134
|
-
};
|
|
135
127
|
/** Render the engine's structured remediations as the "suggested fix" block.
|
|
136
|
-
*
|
|
137
|
-
*
|
|
138
|
-
*
|
|
128
|
+
* Verbs render under their engine names — the same vocabulary `--json`
|
|
129
|
+
* emits, so scripts and humans read one language. Only runnable ones reach
|
|
130
|
+
* here: {@link renderDiagnosis} filters on the engine's `available` flag,
|
|
131
|
+
* so verbs that aren't callable engine operations yet never render as a
|
|
132
|
+
* fix (the full set, planned verbs included, still rides `--json`). */
|
|
139
133
|
function remediationLines(remediations) {
|
|
140
|
-
return remediations.map((r) => ` • ${
|
|
134
|
+
return remediations.map((r) => ` • ${r.verb} — ${r.rationale}`);
|
|
141
135
|
}
|
|
142
136
|
function causeDetail(cause) {
|
|
143
137
|
switch (cause.kind) {
|
|
@@ -179,7 +173,7 @@ function statusLine(diagnosis) {
|
|
|
179
173
|
* {@link SuggestedRemediation}s rather than re-deriving them, so the rendered
|
|
180
174
|
* fix block and the `--json` output stay one computation.
|
|
181
175
|
*/
|
|
182
|
-
export function renderDiagnosis(diagnosis, input, remediations) {
|
|
176
|
+
export function renderDiagnosis({ diagnosis, input, remediations, }) {
|
|
183
177
|
const lines = [statusLine(diagnosis)];
|
|
184
178
|
const inFlight = diagnosis.state === 'progressing' ||
|
|
185
179
|
diagnosis.state === 'waiting' ||
|
|
@@ -219,20 +213,16 @@ export default class Diagnose extends Command {
|
|
|
219
213
|
};
|
|
220
214
|
static flags = {
|
|
221
215
|
...tagFlags,
|
|
222
|
-
|
|
223
|
-
description: 'Emit the structured diagnosis as JSON instead of rendered output.',
|
|
224
|
-
default: false,
|
|
225
|
-
}),
|
|
216
|
+
...jsonFlags,
|
|
226
217
|
};
|
|
227
218
|
async run() {
|
|
228
219
|
const { args, flags } = await this.parse(Diagnose);
|
|
229
|
-
const
|
|
230
|
-
const client = loadClient();
|
|
220
|
+
const { client, scope } = await resolveInstanceContext(flags, args.instanceId);
|
|
231
221
|
const { evaluation, diagnosis, remediations } = await workflow
|
|
232
222
|
.diagnose({
|
|
233
223
|
client,
|
|
234
|
-
tag:
|
|
235
|
-
workflowResource:
|
|
224
|
+
tag: scope.tag,
|
|
225
|
+
workflowResource: scope.workflowResource,
|
|
236
226
|
instanceId: args.instanceId,
|
|
237
227
|
// Token-auth CLI can't hit /users/me; pin the same system actor the
|
|
238
228
|
// write verbs use. Transition filters and activity status are actor-
|
|
@@ -242,7 +232,7 @@ export default class Diagnose extends Command {
|
|
|
242
232
|
// specific assignee's.
|
|
243
233
|
access: { actor: { kind: 'system', id: 'workflow-cli' } },
|
|
244
234
|
})
|
|
245
|
-
.catch((error) => fail('diagnose failed:',
|
|
235
|
+
.catch((error) => fail('diagnose failed:', errorMessage(error)));
|
|
246
236
|
const input = diagnoseInputFromEvaluation(evaluation);
|
|
247
237
|
if (flags.json) {
|
|
248
238
|
this.log(JSON.stringify({
|
|
@@ -255,7 +245,7 @@ export default class Diagnose extends Command {
|
|
|
255
245
|
}
|
|
256
246
|
this.log(instanceHeader(evaluation.instance));
|
|
257
247
|
this.log('');
|
|
258
|
-
for (const line of renderDiagnosis(diagnosis, input, remediations)) {
|
|
248
|
+
for (const line of renderDiagnosis({ diagnosis, input, remediations })) {
|
|
259
249
|
this.log(line);
|
|
260
250
|
}
|
|
261
251
|
}
|
|
@@ -1,46 +1,34 @@
|
|
|
1
1
|
import { Command } from '@oclif/core';
|
|
2
2
|
import { type AvailableAction } from '@sanity/workflow-engine';
|
|
3
|
-
import { type
|
|
4
|
-
/**
|
|
5
|
-
* Parse repeated `--param key=value` flags into a params object. Each
|
|
6
|
-
* value is JSON-parsed so numbers/booleans/objects type correctly,
|
|
7
|
-
* falling back to the raw string. The engine validates the result against
|
|
8
|
-
* the action's declared param types.
|
|
9
|
-
*/
|
|
10
|
-
export declare function parseParams(pairs: string[]): Record<string, unknown>;
|
|
3
|
+
import { type WriteOutcome, type WriteReport } from '../lib/ops-report.ts';
|
|
11
4
|
/**
|
|
12
5
|
* The list-mode body: every action on the current stage's activities, marked
|
|
13
6
|
* fireable or not (with the disabling reason), plus a hint showing the
|
|
14
7
|
* flags to fire one. Pure so the populated/empty permutations assert
|
|
15
8
|
* without a live evaluation.
|
|
16
9
|
*/
|
|
17
|
-
export declare function renderAvailableActions(actions
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
currentStage: string;
|
|
23
|
-
};
|
|
24
|
-
ranOps?: RanOp[];
|
|
25
|
-
}
|
|
10
|
+
export declare function renderAvailableActions({ actions, stage, instanceId, }: {
|
|
11
|
+
actions: AvailableAction[];
|
|
12
|
+
stage: string;
|
|
13
|
+
instanceId: string;
|
|
14
|
+
}): string[];
|
|
26
15
|
/** The `--json` payload for a fired action — the structured counterpart of
|
|
27
16
|
* {@link fireActionReport}. Pure so its shape is asserted directly. */
|
|
28
|
-
export declare function fireResultJson(result:
|
|
17
|
+
export declare function fireResultJson(result: WriteOutcome, ids: {
|
|
29
18
|
instanceId: string;
|
|
30
19
|
activity: string;
|
|
31
20
|
action: string;
|
|
32
21
|
}): Record<string, unknown>;
|
|
33
|
-
export interface FireActionReport {
|
|
34
|
-
fired: boolean;
|
|
35
|
-
message: string;
|
|
36
|
-
opsLines: string[];
|
|
37
|
-
}
|
|
38
22
|
/**
|
|
39
23
|
* Turn a `fireAction` result into the spinner message + ops block. Pure so
|
|
40
24
|
* the fired / no-op / cascaded / ops permutations assert without a live
|
|
41
25
|
* fire.
|
|
42
26
|
*/
|
|
43
|
-
export declare function fireActionReport(result
|
|
27
|
+
export declare function fireActionReport({ result, activity, action, }: {
|
|
28
|
+
result: WriteOutcome;
|
|
29
|
+
activity: string;
|
|
30
|
+
action: string;
|
|
31
|
+
}): WriteReport;
|
|
44
32
|
export default class FireAction extends Command {
|
|
45
33
|
static description: string;
|
|
46
34
|
static examples: string[];
|
|
@@ -48,16 +36,15 @@ export default class FireAction extends Command {
|
|
|
48
36
|
instanceId: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
49
37
|
};
|
|
50
38
|
static flags: {
|
|
39
|
+
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
40
|
+
as: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
41
|
+
'as-role': import("@oclif/core/interfaces").OptionFlag<string[], import("@oclif/core/interfaces").CustomOptions>;
|
|
51
42
|
activity: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
52
43
|
action: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
53
44
|
param: import("@oclif/core/interfaces").OptionFlag<string[], import("@oclif/core/interfaces").CustomOptions>;
|
|
54
|
-
as: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
55
|
-
'as-role': import("@oclif/core/interfaces").OptionFlag<string[], import("@oclif/core/interfaces").CustomOptions>;
|
|
56
|
-
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
57
45
|
tag: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
58
46
|
};
|
|
59
47
|
run(): Promise<void>;
|
|
60
48
|
private listActions;
|
|
61
49
|
private fireOne;
|
|
62
50
|
}
|
|
63
|
-
export {};
|