@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.
@@ -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 = styleText('yellow', `${formatTimestamp(instance.abortedAt)}${tail}`);
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 completed = instance.completedAt
63
- ? styleText('green', formatTimestamp(instance.completedAt))
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
+ }
@@ -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: an
62
- * authenticated client for the resource, plus the instance itself — so the
63
- * command derives its partition (`tag`) from the instance, never from the
64
- * config's declared deployments. An instance id is globally unique and carries
65
- * its own `tag`, so a command that names one acts on that instance regardless
66
- * of which tags the config happens to deploy; the config only locates the
67
- * resource (via {@link resolveInstanceResource}, no tag filter).
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;
@@ -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 { canPromptOnStderr } from "./prompt.js";
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
- const token = await resolveTokenOrFail();
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
- export function resolveReadResource(config, tag) {
33
- const resources = resolveReadResources(config, tag);
34
- const [sole] = resources;
35
- if (resources.length === 1 && sole !== undefined) {
36
- return sole;
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
- if (tag === undefined) {
39
- fail('Config spans multiple resources — pass --deployment or --tag to choose one.', availableDeployments(config));
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 carriers = config.deployments.filter((d) => d.tag === tag);
42
- fail(`Tag "${tag}" spans multiple resources — pass --deployment to choose one.`, availableDeployments(config, carriers));
53
+ const hit = await hintedProbe(targetsFor(hinted, token), instanceId);
54
+ return hit ?? fanOut();
43
55
  }
44
- export async function resolveInstanceContext(flags, instanceId) {
45
- const config = await loadWorkflowConfig();
46
- const workflowResource = await resolveInstanceResource(config, flags);
47
- const client = clientFor(workflowResource, await resolveTokenOrFail());
48
- const instance = await loadInstanceOrFail(client, instanceId);
49
- return { client, scope: { tag: instance.tag, workflowResource } };
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
- export async function resolveInstanceResource(config, { deployment, tag, interactive, chooseDeployment, }) {
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 resolveReadResource(config, tag);
68
+ return resolveReadResources(config, tag);
67
69
  }
68
- export async function loadInstanceOrFail(client, instanceId) {
69
- const instance = await client.getDocument(instanceId, {
70
- tag: 'instance.load',
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
- return assertReadableModel(instance);
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(` ${styleText('red', line)}\n`);
11
+ process.stderr.write(` ${line}\n`);
12
12
  }
13
13
  }
14
14
  return Errors.exit(1);
@@ -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 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. */
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, and the resource disambiguator when the config spans several.';
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 — read the instance from the resource that deployment targets; the tag partition still comes from the loaded instance.',
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
@@ -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
- export function availableDeployments(config, deployments = config.deployments) {
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
- return `${' '.repeat(indent)}${styleText('dim', paddedKey)} ${value}`;
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, }) {