@sanity/workflow-cli 0.20.0 → 0.22.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 +48 -0
- package/README.md +4 -3
- package/dist/commands/editorial-workflows/definition/list.d.ts +16 -0
- package/dist/commands/editorial-workflows/definition/list.js +46 -16
- package/dist/commands/editorial-workflows/definition/show.d.ts +1 -0
- package/dist/commands/editorial-workflows/definition/show.js +6 -1
- package/dist/commands/editorial-workflows/deploy.d.ts +18 -0
- package/dist/commands/editorial-workflows/deploy.js +23 -9
- package/dist/commands/editorial-workflows/list.d.ts +21 -7
- package/dist/commands/editorial-workflows/list.js +55 -12
- package/dist/commands/editorial-workflows/reset-activity.d.ts +26 -4
- package/dist/commands/editorial-workflows/reset-activity.js +56 -6
- package/dist/commands/editorial-workflows/show.d.ts +1 -0
- package/dist/commands/editorial-workflows/show.js +16 -8
- package/dist/help.d.ts +8 -0
- package/dist/help.js +23 -0
- package/dist/lib/context.d.ts +11 -37
- package/dist/lib/context.js +44 -40
- package/dist/lib/fail.js +1 -1
- package/dist/lib/flags.d.ts +12 -7
- package/dist/lib/flags.js +2 -2
- package/dist/lib/read-fanout.d.ts +23 -0
- package/dist/lib/read-fanout.js +17 -1
- package/dist/lib/select-deployment.d.ts +0 -4
- package/dist/lib/select-deployment.js +1 -1
- package/dist/lib/ui.d.ts +9 -2
- package/dist/lib/ui.js +10 -5
- package/oclif.manifest.json +82 -25
- package/package.json +6 -5
|
@@ -4,7 +4,7 @@ import { abortReason, displayTitle, terminalState, } from '@sanity/workflow-engi
|
|
|
4
4
|
import logSymbols from 'log-symbols';
|
|
5
5
|
import { WorkflowCommand } from "../../lib/base-command.js";
|
|
6
6
|
import { findInstance, resolveReadTargets } from "../../lib/context.js";
|
|
7
|
-
import { tagFlags } from "../../lib/flags.js";
|
|
7
|
+
import { jsonFlags, tagFlags } from "../../lib/flags.js";
|
|
8
8
|
import { formatKeyValue, formatTimestamp, sectionHeader, activityIcon } from "../../lib/ui.js";
|
|
9
9
|
export default class Show extends WorkflowCommand {
|
|
10
10
|
static aliases = ['show'];
|
|
@@ -12,6 +12,7 @@ export default class Show extends WorkflowCommand {
|
|
|
12
12
|
static examples = [
|
|
13
13
|
'<%= config.bin %> show wf-instance.abc123',
|
|
14
14
|
'<%= config.bin %> show wf-instance.abc123 --include history',
|
|
15
|
+
'<%= config.bin %> show wf-instance.abc123 --json',
|
|
15
16
|
];
|
|
16
17
|
static args = {
|
|
17
18
|
instanceId: Args.string({
|
|
@@ -22,11 +23,12 @@ export default class Show extends WorkflowCommand {
|
|
|
22
23
|
static flags = {
|
|
23
24
|
...tagFlags,
|
|
24
25
|
include: Flags.string({
|
|
25
|
-
description: 'Optional sections to include in output.',
|
|
26
|
+
description: 'Optional sections to include in rendered output (--json always carries the full document).',
|
|
26
27
|
options: ['history'],
|
|
27
28
|
multiple: true,
|
|
28
29
|
default: [],
|
|
29
30
|
}),
|
|
31
|
+
...jsonFlags,
|
|
30
32
|
};
|
|
31
33
|
async run() {
|
|
32
34
|
const { args, flags } = await this.parse(Show);
|
|
@@ -36,6 +38,10 @@ export default class Show extends WorkflowCommand {
|
|
|
36
38
|
this.error(`${logSymbols.error} no instance with id "${args.instanceId}"`, { exit: 1 });
|
|
37
39
|
}
|
|
38
40
|
const { instance } = hit;
|
|
41
|
+
if (flags.json) {
|
|
42
|
+
this.log(JSON.stringify(instance, null, 2));
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
39
45
|
this.log(instanceHeader(instance));
|
|
40
46
|
this.log('');
|
|
41
47
|
for (const line of describeInstance(instance, {
|
|
@@ -55,14 +61,16 @@ export function instanceHeader(instance) {
|
|
|
55
61
|
if (terminalState(instance) === 'aborted' && instance.abortedAt !== undefined) {
|
|
56
62
|
const reason = abortReason(instance);
|
|
57
63
|
const tail = reason ? ` — ${reason}` : '';
|
|
58
|
-
const value =
|
|
59
|
-
rows.push(formatKeyValue({ key: 'Aborted', value, padTo: HEADER_PAD }));
|
|
64
|
+
const value = `${formatTimestamp(instance.abortedAt)}${tail}`;
|
|
65
|
+
rows.push(formatKeyValue({ key: 'Aborted', value, padTo: HEADER_PAD, emphasis: 'yellow' }));
|
|
66
|
+
}
|
|
67
|
+
else if (instance.completedAt) {
|
|
68
|
+
const value = formatTimestamp(instance.completedAt);
|
|
69
|
+
rows.push(formatKeyValue({ key: 'Completed', value, padTo: HEADER_PAD, emphasis: 'green' }));
|
|
60
70
|
}
|
|
61
71
|
else {
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
: styleText('dim', '—');
|
|
65
|
-
rows.push(formatKeyValue({ key: 'Completed', value: completed, padTo: HEADER_PAD }));
|
|
72
|
+
const value = styleText('dim', '—');
|
|
73
|
+
rows.push(formatKeyValue({ key: 'Completed', value, padTo: HEADER_PAD }));
|
|
66
74
|
}
|
|
67
75
|
rows.push(formatKeyValue({ key: 'Tag', value: instance.tag, padTo: HEADER_PAD }));
|
|
68
76
|
return [title, ...rows].join('\n');
|
package/dist/help.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { Help } from '@oclif/core';
|
|
2
|
+
export declare const isBareSurface: (idOrName: string) => boolean;
|
|
3
|
+
export declare const keepCommandAtRoot: (id: string) => boolean;
|
|
4
|
+
/** Standalone-binary help that lists the nested `definition` commands flat in
|
|
5
|
+
* `--help` rather than under a topic. A mounted host governs its own root help. */
|
|
6
|
+
export default class WorkflowHelp extends Help {
|
|
7
|
+
protected showRootHelp(): Promise<void>;
|
|
8
|
+
}
|
package/dist/help.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Help } from '@oclif/core';
|
|
2
|
+
export const isBareSurface = (idOrName) => !idOrName.startsWith('editorial-workflows');
|
|
3
|
+
export const keepCommandAtRoot = (id) => id !== '' && isBareSurface(id);
|
|
4
|
+
export default class WorkflowHelp extends Help {
|
|
5
|
+
async showRootHelp() {
|
|
6
|
+
if (this.opts.all) {
|
|
7
|
+
await super.showRootHelp();
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
this.log(this.formatRoot());
|
|
11
|
+
this.log('');
|
|
12
|
+
const topics = this.sortedTopics.filter((topic) => isBareSurface(topic.name));
|
|
13
|
+
if (topics.length > 0) {
|
|
14
|
+
this.log(this.formatTopics(topics));
|
|
15
|
+
this.log('');
|
|
16
|
+
}
|
|
17
|
+
const commands = this.sortedCommands.filter((command) => keepCommandAtRoot(command.id));
|
|
18
|
+
if (commands.length > 0) {
|
|
19
|
+
this.log(this.formatCommands(commands));
|
|
20
|
+
this.log('');
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
package/dist/lib/context.d.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import type { SanityClient } from '@sanity/client';
|
|
2
2
|
import { type WorkflowConfig, type WorkflowDeployment, type WorkflowInstance, type WorkflowResource } from '@sanity/workflow-engine';
|
|
3
3
|
import type { EngineScope } from './operation-args.ts';
|
|
4
|
-
import { type ChooseDeploymentName } from './select-deployment.ts';
|
|
5
4
|
export interface DeploymentContext {
|
|
6
5
|
deployment: WorkflowDeployment;
|
|
7
6
|
client: SanityClient;
|
|
@@ -51,20 +50,18 @@ export declare function dedupeResources(resources: WorkflowResource[]): Workflow
|
|
|
51
50
|
* tag (tags may repeat across deployments), or — untagged — every distinct
|
|
52
51
|
* one the config's deployments mention ({@link dedupeResources}). */
|
|
53
52
|
export declare function resolveReadResources(config: WorkflowConfig, tag: string | undefined): WorkflowResource[];
|
|
54
|
-
/** The single resource an instance-keyed command reads from when it isn't
|
|
55
|
-
* disambiguated interactively: the sole resource, the `--tag`-narrowed one, or
|
|
56
|
-
* a `fail` asking for `--deployment`/`--tag` when the config spans several. The
|
|
57
|
-
* interactive picker lives in {@link resolveInstanceResource}; listings fan out
|
|
58
|
-
* via {@link resolveReadResources} instead. */
|
|
59
|
-
export declare function resolveReadResource(config: WorkflowConfig, tag: string | undefined): WorkflowResource;
|
|
60
53
|
/**
|
|
61
|
-
* The resolution an instance-id-targeted command shares, read or write:
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
54
|
+
* The resolution an instance-id-targeted command shares, read or write: locate
|
|
55
|
+
* the instance by the same cross-resource fan-out the read/list commands use
|
|
56
|
+
* ({@link findInstance}), then hand back an authenticated client for the
|
|
57
|
+
* resource that held it plus a scope whose `tag` comes from the instance
|
|
58
|
+
* itself — never from the config's declared deployments. An instance id is
|
|
59
|
+
* globally unique and carries its own `tag`, so naming one is enough to act on
|
|
60
|
+
* it: the config only locates candidate resources, and the fan-out's
|
|
61
|
+
* {@link soleHitOrFail} rejects the collision case where several datasets hold
|
|
62
|
+
* the id (the `<tag>.` fast-path may skip that full-set check — safe, ids are
|
|
63
|
+
* collision-free). `--deployment` / `--tag` stay optional narrowers, not
|
|
64
|
+
* required disambiguators.
|
|
68
65
|
*
|
|
69
66
|
* Contrast {@link resolveContext}: the deploy/diff/delete path IS scoped to a
|
|
70
67
|
* declared deployment because it acts on the authored definition set (or a
|
|
@@ -74,29 +71,6 @@ export declare function resolveInstanceContext(flags: {
|
|
|
74
71
|
deployment?: string | undefined;
|
|
75
72
|
tag?: string | undefined;
|
|
76
73
|
}, instanceId: string): Promise<InstanceContext>;
|
|
77
|
-
/**
|
|
78
|
-
* Which resource an instance-keyed command reads from: the one `--deployment`
|
|
79
|
-
* names (resolved by its unique deployment name); otherwise, when the config
|
|
80
|
-
* spans several resources, an interactive terminal is prompted to pick a
|
|
81
|
-
* deployment ({@link canPromptOnStderr}) — its resource is the read source, the
|
|
82
|
-
* interactive counterpart to `--deployment`. A run that can't prompt falls to
|
|
83
|
-
* the sole or `--tag`-narrowed resource, or the ambiguity error
|
|
84
|
-
* ({@link resolveReadResource}). The instance id is globally unique, so this
|
|
85
|
-
* only locates the resource to look in — never the instance's `tag` partition,
|
|
86
|
-
* which the caller takes from the loaded instance. The shared path behind all
|
|
87
|
-
* four instance-keyed commands.
|
|
88
|
-
*/
|
|
89
|
-
export declare function resolveInstanceResource(config: WorkflowConfig, { deployment, tag, interactive, chooseDeployment, }: {
|
|
90
|
-
deployment?: string | undefined;
|
|
91
|
-
tag?: string | undefined;
|
|
92
|
-
interactive?: boolean | undefined;
|
|
93
|
-
chooseDeployment?: ChooseDeploymentName | undefined;
|
|
94
|
-
}): Promise<WorkflowResource>;
|
|
95
|
-
/** Fetch an instance by id, exiting cleanly when the resource has no such
|
|
96
|
-
* document — the diagnostic an operator sees on a mistyped id. Split out from
|
|
97
|
-
* {@link resolveInstanceContext} so the not-found path is unit-testable
|
|
98
|
-
* without a live config/token/client. */
|
|
99
|
-
export declare function loadInstanceOrFail(client: Pick<SanityClient, 'getDocument'>, instanceId: string): Promise<WorkflowInstance>;
|
|
100
74
|
/** A read target that turned out to hold the instance being looked up. */
|
|
101
75
|
export interface InstanceHit extends ReadTarget {
|
|
102
76
|
instance: WorkflowInstance;
|
package/dist/lib/context.js
CHANGED
|
@@ -2,19 +2,20 @@ 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 {
|
|
6
|
-
import { availableDeployments, deploymentsForTag, selectDeployment, } from "./select-deployment.js";
|
|
5
|
+
import { deploymentsForTag, selectDeployment } from "./select-deployment.js";
|
|
7
6
|
import { resourceLabel } from "./ui.js";
|
|
8
7
|
export async function resolveContext(flags) {
|
|
9
8
|
const config = await loadWorkflowConfig();
|
|
10
9
|
const deployment = await selectDeployment(config, { name: flags.deployment, tag: flags.tag });
|
|
11
10
|
return { deployment, client: clientFor(deployment.workflowResource, await resolveTokenOrFail()) };
|
|
12
11
|
}
|
|
12
|
+
function targetsFor(resources, token) {
|
|
13
|
+
return resources.map((resource) => ({ resource, client: clientFor(resource, token) }));
|
|
14
|
+
}
|
|
13
15
|
export async function resolveReadTargets(flags) {
|
|
14
16
|
const config = await loadWorkflowConfig();
|
|
15
17
|
const resources = resolveReadResources(config, flags.tag);
|
|
16
|
-
|
|
17
|
-
return resources.map((resource) => ({ resource, client: clientFor(resource, token) }));
|
|
18
|
+
return targetsFor(resources, await resolveTokenOrFail());
|
|
18
19
|
}
|
|
19
20
|
export function dedupeResources(resources) {
|
|
20
21
|
return [...new Map(resources.map((resource) => [resourceGdr(resource), resource])).values()];
|
|
@@ -29,50 +30,53 @@ export function resolveReadResources(config, tag) {
|
|
|
29
30
|
}
|
|
30
31
|
return resources;
|
|
31
32
|
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
33
|
+
const LOCATE_TAG = 'instance.load';
|
|
34
|
+
export async function resolveInstanceContext(flags, instanceId) {
|
|
35
|
+
const config = await loadWorkflowConfig();
|
|
36
|
+
const token = await resolveTokenOrFail();
|
|
37
|
+
const hit = await locateInstance({ config, flags, instanceId, token });
|
|
38
|
+
if (hit === undefined) {
|
|
39
|
+
fail(`Workflow instance ${instanceId} not found`);
|
|
37
40
|
}
|
|
38
|
-
|
|
39
|
-
|
|
41
|
+
return { client: hit.client, scope: { tag: hit.instance.tag, workflowResource: hit.resource } };
|
|
42
|
+
}
|
|
43
|
+
async function locateInstance({ config, flags, instanceId, token, }) {
|
|
44
|
+
const resources = await instanceCandidateResources(config, flags);
|
|
45
|
+
const fanOut = () => findInstance({ targets: targetsFor(resources, token), instanceId, requestTag: LOCATE_TAG });
|
|
46
|
+
const explicit = flags.deployment !== undefined || flags.tag !== undefined;
|
|
47
|
+
const hinted = explicit || resources.length <= 1
|
|
48
|
+
? []
|
|
49
|
+
: hintedResources({ config, instanceId, candidates: resources });
|
|
50
|
+
if (hinted.length === 0 || hinted.length === resources.length) {
|
|
51
|
+
return fanOut();
|
|
40
52
|
}
|
|
41
|
-
const
|
|
42
|
-
|
|
53
|
+
const hit = await hintedProbe(targetsFor(hinted, token), instanceId);
|
|
54
|
+
return hit ?? fanOut();
|
|
43
55
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
56
|
+
async function hintedProbe(targets, instanceId) {
|
|
57
|
+
try {
|
|
58
|
+
return await findInstance({ targets, instanceId, requestTag: LOCATE_TAG });
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
return undefined;
|
|
62
|
+
}
|
|
50
63
|
}
|
|
51
|
-
|
|
64
|
+
async function instanceCandidateResources(config, { deployment, tag }) {
|
|
52
65
|
if (deployment !== undefined) {
|
|
53
|
-
return (await selectDeployment(config, { name: deployment, tag: undefined })).workflowResource;
|
|
54
|
-
}
|
|
55
|
-
if (tag === undefined &&
|
|
56
|
-
(interactive ?? canPromptOnStderr()) &&
|
|
57
|
-
resolveReadResources(config, undefined).length > 1) {
|
|
58
|
-
const chosen = await selectDeployment(config, {
|
|
59
|
-
name: undefined,
|
|
60
|
-
tag: undefined,
|
|
61
|
-
interactive: true,
|
|
62
|
-
chooseName: chooseDeployment,
|
|
63
|
-
});
|
|
64
|
-
return chosen.workflowResource;
|
|
66
|
+
return [(await selectDeployment(config, { name: deployment, tag: undefined })).workflowResource];
|
|
65
67
|
}
|
|
66
|
-
return
|
|
68
|
+
return resolveReadResources(config, tag);
|
|
67
69
|
}
|
|
68
|
-
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
if (!instance) {
|
|
73
|
-
fail(`Workflow instance ${instanceId} not found`);
|
|
70
|
+
function hintedResources({ config, instanceId, candidates, }) {
|
|
71
|
+
const dot = instanceId.indexOf('.');
|
|
72
|
+
if (dot <= 0) {
|
|
73
|
+
return [];
|
|
74
74
|
}
|
|
75
|
-
|
|
75
|
+
const prefix = instanceId.slice(0, dot);
|
|
76
|
+
const taggedGdrs = new Set(config.deployments
|
|
77
|
+
.filter((deployment) => deployment.tag === prefix)
|
|
78
|
+
.map((deployment) => resourceGdr(deployment.workflowResource)));
|
|
79
|
+
return candidates.filter((resource) => taggedGdrs.has(resourceGdr(resource)));
|
|
76
80
|
}
|
|
77
81
|
export function soleHitOrFail(hits, subject) {
|
|
78
82
|
const [sole, ...rest] = hits;
|
package/dist/lib/fail.js
CHANGED
|
@@ -8,7 +8,7 @@ export function fail(headline, detail) {
|
|
|
8
8
|
process.stderr.write(`${styleText('red', `${logSymbols.error} ${headline}`)}\n`);
|
|
9
9
|
if (detail !== undefined && detail !== '') {
|
|
10
10
|
for (const line of detail.split('\n')) {
|
|
11
|
-
process.stderr.write(` ${
|
|
11
|
+
process.stderr.write(` ${line}\n`);
|
|
12
12
|
}
|
|
13
13
|
}
|
|
14
14
|
return Errors.exit(1);
|
package/dist/lib/flags.d.ts
CHANGED
|
@@ -7,13 +7,13 @@
|
|
|
7
7
|
export declare const tagFlags: {
|
|
8
8
|
tag: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
9
9
|
};
|
|
10
|
-
/** The
|
|
11
|
-
* `fire-action`) takes.
|
|
12
|
-
*
|
|
13
|
-
* instance
|
|
14
|
-
*
|
|
15
|
-
* `--tag`
|
|
16
|
-
*
|
|
10
|
+
/** The optional narrowers an instance-keyed command (`diagnose`, `abort`,
|
|
11
|
+
* `set-stage`, `fire-action`, `reset-activity`) takes. The command locates the
|
|
12
|
+
* instance by fanning out across every configured resource, so neither is ever
|
|
13
|
+
* required — an instance id is globally unique and carries its own `tag`. They
|
|
14
|
+
* only narrow WHERE to look: `--deployment` to the resource one deployment
|
|
15
|
+
* targets, `--tag` to the resources under that tag. The instance's partition
|
|
16
|
+
* always comes from the loaded instance, never from these. Mutually exclusive. */
|
|
17
17
|
export declare const instanceFlags: {
|
|
18
18
|
deployment: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
19
19
|
tag: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
@@ -28,6 +28,11 @@ export declare const deploymentFlags: {
|
|
|
28
28
|
deployment: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
29
29
|
tag: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
30
30
|
};
|
|
31
|
+
/** Structured stdout instead of the rendered view. One payload convention:
|
|
32
|
+
* verb commands emit their operation envelope (`instanceId` carries the doc
|
|
33
|
+
* `_id`); read commands emit document-shaped data — `show` commands print
|
|
34
|
+
* the fetched document as stored, list commands print the table's row view
|
|
35
|
+
* (plus each row's `resource`) under a `truncated` flag. */
|
|
31
36
|
export declare const jsonFlags: {
|
|
32
37
|
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
33
38
|
};
|
package/dist/lib/flags.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { Flags } from '@oclif/core';
|
|
2
|
-
const tagAsFilterDescription = 'Workflow environment tag (e.g. prod, test) — an optional query filter
|
|
2
|
+
const tagAsFilterDescription = 'Workflow environment tag (e.g. prod, test) — an optional query filter that also narrows which resources are searched; omit to span them all.';
|
|
3
3
|
export const tagFlags = {
|
|
4
4
|
tag: Flags.string({ description: tagAsFilterDescription }),
|
|
5
5
|
};
|
|
6
6
|
export const instanceFlags = {
|
|
7
7
|
deployment: Flags.string({
|
|
8
|
-
description: 'Deployment name —
|
|
8
|
+
description: 'Deployment name — narrow the instance search to the resource that deployment targets; the tag partition still comes from the loaded instance.',
|
|
9
9
|
exclusive: ['tag'],
|
|
10
10
|
}),
|
|
11
11
|
tag: Flags.string({ description: tagAsFilterDescription, exclusive: ['deployment'] }),
|
|
@@ -6,6 +6,29 @@ export interface ReadFailure {
|
|
|
6
6
|
resource: WorkflowResource;
|
|
7
7
|
message: string;
|
|
8
8
|
}
|
|
9
|
+
/**
|
|
10
|
+
* The `--json` tail of a list command — the structured counterpart of
|
|
11
|
+
* {@link runReadAcrossTargets} + the clipped table: fetch every target's
|
|
12
|
+
* rows, {@link clipToLimit} each target's page, map rows through the
|
|
13
|
+
* command's row view (`toRow`, the structured analogue of the table's
|
|
14
|
+
* `toCells`), annotate each with the resource it came from (the analogue of
|
|
15
|
+
* the banner), and log one `{<key>: rows, truncated}` payload.
|
|
16
|
+
* All-or-nothing, unlike the human path's per-resource tolerance: a script
|
|
17
|
+
* would mistake a listing that silently omitted an unreadable resource for
|
|
18
|
+
* the complete result, so any failure rejects the whole read.
|
|
19
|
+
*/
|
|
20
|
+
export declare function logJsonListing<T, R>({ targets, fetch, toRow, key, limit, log, }: {
|
|
21
|
+
targets: ReadTarget[];
|
|
22
|
+
fetch: (target: ReadTarget) => Promise<{
|
|
23
|
+
rows: T[];
|
|
24
|
+
hasMore: boolean;
|
|
25
|
+
}>;
|
|
26
|
+
toRow: (row: T) => R;
|
|
27
|
+
/** The payload's row key — names what the rows are (`instances`, `definitions`). */
|
|
28
|
+
key: string;
|
|
29
|
+
limit: number;
|
|
30
|
+
log: (line: string) => void;
|
|
31
|
+
}): Promise<void>;
|
|
9
32
|
/**
|
|
10
33
|
* Run a read over every target, grouping output per resource: a banner when
|
|
11
34
|
* the read spans several, then whatever `run` logs for that target. A throw
|
package/dist/lib/read-fanout.js
CHANGED
|
@@ -1,6 +1,22 @@
|
|
|
1
1
|
import logSymbols from 'log-symbols';
|
|
2
2
|
import { failureDetail } from "./fail.js";
|
|
3
|
-
import { groupBanner, resourceLabel } from "./ui.js";
|
|
3
|
+
import { clipToLimit, groupBanner, resourceLabel } from "./ui.js";
|
|
4
|
+
export async function logJsonListing({ targets, fetch, toRow, key, limit, log, }) {
|
|
5
|
+
const fetched = await Promise.all(targets.map(async (target) => {
|
|
6
|
+
const { rows, hasMore } = await fetch(target);
|
|
7
|
+
return {
|
|
8
|
+
rows: clipToLimit(rows, limit).rows.map((row) => ({
|
|
9
|
+
...toRow(row),
|
|
10
|
+
resource: target.resource,
|
|
11
|
+
})),
|
|
12
|
+
hasMore: hasMore || rows.length > limit,
|
|
13
|
+
};
|
|
14
|
+
}));
|
|
15
|
+
log(JSON.stringify({
|
|
16
|
+
[key]: fetched.flatMap((result) => result.rows),
|
|
17
|
+
truncated: fetched.some((result) => result.hasMore),
|
|
18
|
+
}, null, 2));
|
|
19
|
+
}
|
|
4
20
|
export async function runReadAcrossTargets({ targets, log, run, }) {
|
|
5
21
|
const failures = [];
|
|
6
22
|
for (const [index, target] of targets.entries()) {
|
|
@@ -48,10 +48,6 @@ export declare function deploymentsForTag(config: WorkflowConfig, tag: string):
|
|
|
48
48
|
* explicit opt-in — never a default.
|
|
49
49
|
*/
|
|
50
50
|
export declare function selectDeployments(config: WorkflowConfig, { name, tag, allTags, interactive, chooseName }: DeploymentSelectionOptions): Promise<WorkflowDeployment[]>;
|
|
51
|
-
/** The "Available deployments: name (tag), …" hint every ambiguity failure
|
|
52
|
-
* prints — one vocabulary across the write and instance-keyed paths, so a
|
|
53
|
-
* reader always sees both the `--deployment` names and their `--tag`s. */
|
|
54
|
-
export declare function availableDeployments(config: WorkflowConfig, deployments?: WorkflowDeployment[]): string;
|
|
55
51
|
/**
|
|
56
52
|
* Project a deployment into the engine's {@link DeployTarget} — the shape the
|
|
57
53
|
* deploy/diff verbs (`deployDefinitions`, `computeDiffEntries`, `diffEntry`)
|
|
@@ -66,7 +66,7 @@ async function chooseDeploymentName(deployments) {
|
|
|
66
66
|
choices: deployments.map(({ name, tag }) => ({ name, value: name, description: `tag: ${tag}` })),
|
|
67
67
|
});
|
|
68
68
|
}
|
|
69
|
-
|
|
69
|
+
function availableDeployments(config, deployments = config.deployments) {
|
|
70
70
|
return `Available deployments: ${deployments.map(deploymentLabel).join(', ')}`;
|
|
71
71
|
}
|
|
72
72
|
export function deploymentToTarget(deployment) {
|
package/dist/lib/ui.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { styleText } from 'node:util';
|
|
1
2
|
import type { ActivityStatus, WorkflowResource } from '@sanity/workflow-engine';
|
|
2
3
|
/**
|
|
3
4
|
* A bold `Title:` section heading. Mirrors the Sanity CLI's `sectionHeader`
|
|
@@ -8,12 +9,17 @@ export declare function sectionHeader(title: string): string;
|
|
|
8
9
|
* A ` key: value` detail row — a dim, padded key then the value. Mirrors
|
|
9
10
|
* the Sanity CLI's `formatKeyValue` so it folds into the shared helper on
|
|
10
11
|
* merge; pass `padTo` (the longest key's length) to align a block of rows.
|
|
12
|
+
*
|
|
13
|
+
* Pass `emphasis` (a {@link styleText} colour) to render the whole row — key
|
|
14
|
+
* and value together — bold in that colour instead of the dim-key default, so
|
|
15
|
+
* a standout row (a terminal instance status) pops from the neutral rows.
|
|
11
16
|
*/
|
|
12
|
-
export declare function formatKeyValue({ key, value, indent, padTo, }: {
|
|
17
|
+
export declare function formatKeyValue({ key, value, indent, padTo, emphasis, }: {
|
|
13
18
|
key: string;
|
|
14
19
|
value: string;
|
|
15
20
|
indent?: number;
|
|
16
21
|
padTo?: number;
|
|
22
|
+
emphasis?: Parameters<typeof styleText>[0];
|
|
17
23
|
}): string;
|
|
18
24
|
/**
|
|
19
25
|
* A borderless column table as printable lines: cyan header row, every
|
|
@@ -37,9 +43,10 @@ export declare function clipToLimit<T>(rows: T[], limit: number): {
|
|
|
37
43
|
* rows, render them through {@link formatTable}, print every line, and print
|
|
38
44
|
* the clip note (when present) beneath the table.
|
|
39
45
|
*/
|
|
40
|
-
export declare function logClippedTable<T>({ rows, limit, headers, toCells, log, }: {
|
|
46
|
+
export declare function logClippedTable<T>({ rows, limit, moreAvailable, headers, toCells, log, }: {
|
|
41
47
|
rows: T[];
|
|
42
48
|
limit: number;
|
|
49
|
+
moreAvailable?: boolean;
|
|
43
50
|
headers: string[];
|
|
44
51
|
toCells: (row: T) => string[];
|
|
45
52
|
log: (line: string) => void;
|
package/dist/lib/ui.js
CHANGED
|
@@ -6,9 +6,13 @@ import logSymbols from 'log-symbols';
|
|
|
6
6
|
export function sectionHeader(title) {
|
|
7
7
|
return styleText('bold', `${title}:`);
|
|
8
8
|
}
|
|
9
|
-
export function formatKeyValue({ key, value, indent = 2, padTo = 0, }) {
|
|
9
|
+
export function formatKeyValue({ key, value, indent = 2, padTo = 0, emphasis, }) {
|
|
10
10
|
const paddedKey = `${key}:`.padEnd(padTo > 0 ? padTo + 1 : key.length + 1);
|
|
11
|
-
|
|
11
|
+
const pad = ' '.repeat(indent);
|
|
12
|
+
if (emphasis) {
|
|
13
|
+
return `${pad}${styleText('bold', styleText(emphasis, `${paddedKey} ${value}`))}`;
|
|
14
|
+
}
|
|
15
|
+
return `${pad}${styleText('dim', paddedKey)} ${value}`;
|
|
12
16
|
}
|
|
13
17
|
export function formatTable(headers, rows) {
|
|
14
18
|
const width = (cell) => stripVTControlCharacters(cell).length;
|
|
@@ -31,13 +35,14 @@ export function clipToLimit(rows, limit) {
|
|
|
31
35
|
note: styleText('dim', `showing the first ${limit} — raise --limit to see more`),
|
|
32
36
|
};
|
|
33
37
|
}
|
|
34
|
-
export function logClippedTable({ rows, limit, headers, toCells, log, }) {
|
|
38
|
+
export function logClippedTable({ rows, limit, moreAvailable, headers, toCells, log, }) {
|
|
35
39
|
const { rows: clipped, note } = clipToLimit(rows, limit);
|
|
36
40
|
for (const line of formatTable(headers, clipped.map(toCells))) {
|
|
37
41
|
log(line);
|
|
38
42
|
}
|
|
39
|
-
if (note) {
|
|
40
|
-
log(note
|
|
43
|
+
if (note || moreAvailable) {
|
|
44
|
+
log(note ??
|
|
45
|
+
styleText('dim', `searched the first ${limit} candidates — raise --limit to see more`));
|
|
41
46
|
}
|
|
42
47
|
}
|
|
43
48
|
export function groupBanner({ label, index, total, }) {
|