@sous-io/sous 0.1.0 → 0.2.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 +121 -35
- package/bin/run.js +10 -1
- package/docs/markdown/README.md +27 -0
- package/docs/markdown/_sidebar.md +18 -0
- package/docs/markdown/commands.md +308 -0
- package/docs/markdown/config-discovery.md +74 -0
- package/docs/markdown/config-inspection.md +69 -0
- package/docs/markdown/config-layers.md +92 -0
- package/docs/markdown/config-variables.md +79 -0
- package/docs/markdown/configuration.md +71 -0
- package/docs/markdown/design-principles.md +59 -0
- package/docs/markdown/repositories-authoring.md +408 -0
- package/docs/markdown/repositories-consuming.md +580 -0
- package/docs/markdown/repositories-file-formats.md +1084 -0
- package/docs/markdown/repositories-variables.md +387 -0
- package/docs/markdown/repositories.md +303 -0
- package/docs/markdown/skill-categories.md +58 -0
- package/package.json +73 -9
- package/{shared-prompts/skills/sous-skills → recipes/core/sous-skills/skills}/about-agent-skills/SKILL.tpl.md +20 -20
- package/{shared-prompts/skills/sous-skills → recipes/core/sous-skills/skills}/about-agent-skills/examples/about-something.md +2 -2
- package/{shared-prompts/skills/sous-skills → recipes/core/sous-skills/skills}/about-agent-skills/examples/do-something.md +1 -1
- package/{shared-prompts/skills/sous-skills → recipes/core/sous-skills/skills}/about-agent-skills/references/advanced-patterns.md +6 -6
- package/{shared-prompts/skills/sous-skills → recipes/core/sous-skills/skills}/about-agent-skills/references/commands.md +5 -5
- package/{shared-prompts/skills/sous-skills → recipes/core/sous-skills/skills}/about-agent-skills/references/frontmatter.md +3 -3
- package/{shared-prompts/skills/sous-skills → recipes/core/sous-skills/skills}/about-liquid-templates/SKILL.tpl.md +40 -25
- package/recipes/core/sous-skills/skills/about-sous/SKILL.tpl.md +70 -0
- package/recipes/core/sous-skills/skills/about-sous-configuration/SKILL.tpl.md +75 -0
- package/{shared-prompts/skills/sous-skills → recipes/core/sous-skills/skills}/create-skill/SKILL.tpl.md +8 -9
- package/recipes/core/sous-skills/sous.recipe.yaml +45 -0
- package/sous.config.schema.json +337 -0
- package/src/base-command.ts +220 -67
- package/src/commands/build.ts +150 -73
- package/src/commands/clear.ts +23 -15
- package/src/commands/compile.ts +74 -16
- package/src/commands/config/get.ts +110 -0
- package/src/commands/config/show.ts +32 -0
- package/src/commands/config/validate.ts +53 -0
- package/src/commands/help.ts +46 -0
- package/src/commands/launch.ts +36 -14
- package/src/commands/lock/rebuild.ts +241 -0
- package/src/commands/lock/show.ts +115 -0
- package/src/commands/namespace/list.ts +117 -0
- package/src/commands/namespace/show.ts +110 -0
- package/src/commands/prune.ts +3 -11
- package/src/commands/recipe/list.ts +95 -0
- package/src/commands/recipe/show.ts +301 -0
- package/src/commands/repo/add.ts +145 -0
- package/src/commands/repo/gc.ts +172 -0
- package/src/commands/repo/init.ts +136 -0
- package/src/commands/repo/link.ts +500 -0
- package/src/commands/repo/list.ts +179 -0
- package/src/commands/repo/release.ts +619 -0
- package/src/commands/repo/remove.ts +193 -0
- package/src/commands/repo/search.ts +189 -0
- package/src/commands/repo/submit.ts +133 -0
- package/src/commands/repo/unlink.ts +147 -0
- package/src/commands/subscription/add.ts +285 -0
- package/src/commands/subscription/list.ts +129 -0
- package/src/commands/subscription/remove.ts +181 -0
- package/src/commands/vars/ask.ts +374 -0
- package/src/commands/vars/index.ts +79 -0
- package/src/commands/vars/list.ts +67 -0
- package/src/commands/vars/show.ts +77 -0
- package/src/config-command.ts +30 -0
- package/src/lib/build-service.ts +206 -54
- package/src/lib/config-discovery.ts +220 -27
- package/src/lib/config-inspect.ts +145 -0
- package/src/lib/config-kernel.mjs +377 -0
- package/src/lib/config-schema.ts +361 -0
- package/src/lib/env-file.ts +328 -0
- package/src/lib/env-local.ts +18 -1
- package/src/lib/errors.ts +32 -0
- package/src/lib/include-resolver.ts +108 -15
- package/src/lib/interactive.ts +165 -0
- package/src/lib/markdown-compiler.ts +118 -37
- package/src/lib/package-info.ts +25 -0
- package/src/lib/pid-service.ts +32 -21
- package/src/lib/refs/find.ts +589 -0
- package/src/lib/refs/index.ts +12 -0
- package/src/lib/refs/pick.ts +147 -0
- package/src/lib/refs/scopes.ts +61 -0
- package/src/lib/repos/catalog-display.ts +116 -0
- package/src/lib/repos/catalog-inputs.ts +160 -0
- package/src/lib/repos/catalog.ts +722 -0
- package/src/lib/repos/core-recipe.ts +105 -0
- package/src/lib/repos/defaults.ts +175 -0
- package/src/lib/repos/formats/common.ts +389 -0
- package/src/lib/repos/formats/index-file.ts +215 -0
- package/src/lib/repos/formats/links-map.ts +96 -0
- package/src/lib/repos/formats/lockfile.ts +167 -0
- package/src/lib/repos/formats/patterns.ts +57 -0
- package/src/lib/repos/formats/recipe-manifest.ts +395 -0
- package/src/lib/repos/formats/repo-manifest.ts +88 -0
- package/src/lib/repos/formats/store-entry.ts +84 -0
- package/src/lib/repos/freshness.ts +208 -0
- package/src/lib/repos/git-clone.ts +312 -0
- package/src/lib/repos/identity.ts +89 -0
- package/src/lib/repos/index.ts +58 -0
- package/src/lib/repos/links.ts +353 -0
- package/src/lib/repos/load-manifest.ts +236 -0
- package/src/lib/repos/lock-service.ts +453 -0
- package/src/lib/repos/locked-namespace-resolver.ts +90 -0
- package/src/lib/repos/locked-recipes.ts +254 -0
- package/src/lib/repos/managed-layer.ts +422 -0
- package/src/lib/repos/namespace-resolver.ts +370 -0
- package/src/lib/repos/providers/base.ts +206 -0
- package/src/lib/repos/providers/git.ts +233 -0
- package/src/lib/repos/providers/github.ts +294 -0
- package/src/lib/repos/providers/gitlab.ts +263 -0
- package/src/lib/repos/providers/http.ts +102 -0
- package/src/lib/repos/providers/index-cache.ts +382 -0
- package/src/lib/repos/providers/index.ts +106 -0
- package/src/lib/repos/providers/local.ts +391 -0
- package/src/lib/repos/providers/provider.ts +401 -0
- package/src/lib/repos/recipe-config-layers.ts +287 -0
- package/src/lib/repos/recipe-targets.ts +223 -0
- package/src/lib/repos/ref-search.ts +46 -0
- package/src/lib/repos/ref.ts +513 -0
- package/src/lib/repos/reference-report.ts +122 -0
- package/src/lib/repos/release/bump.ts +161 -0
- package/src/lib/repos/release/git-state.ts +305 -0
- package/src/lib/repos/release/index-builder.ts +635 -0
- package/src/lib/repos/release/index.ts +16 -0
- package/src/lib/repos/release/plan.ts +512 -0
- package/src/lib/repos/release/submit-service.ts +496 -0
- package/src/lib/repos/release/tags.ts +243 -0
- package/src/lib/repos/release/validate.ts +463 -0
- package/src/lib/repos/resolver.ts +789 -0
- package/src/lib/repos/scaffold/index.ts +238 -0
- package/src/lib/repos/scaffold/templates.ts +413 -0
- package/src/lib/repos/seed.ts +414 -0
- package/src/lib/repos/store/contract.ts +64 -0
- package/src/lib/repos/store/hash.ts +114 -0
- package/src/lib/repos/store/recipe-store.ts +599 -0
- package/src/lib/repos/store/settings.ts +58 -0
- package/src/lib/repos/subscription-service.ts +2678 -0
- package/src/lib/repos/trust.ts +447 -0
- package/src/lib/settings.ts +546 -189
- package/src/lib/sous-home.ts +104 -0
- package/src/lib/state.ts +52 -20
- package/src/lib/vars/ask.ts +1152 -0
- package/src/lib/vars/definition-source.ts +252 -0
- package/src/lib/vars/display.ts +233 -0
- package/src/lib/vars/index.ts +18 -0
- package/src/lib/vars/ladder.ts +282 -0
- package/src/lib/vars/mappings.ts +265 -0
- package/src/lib/vars/names.ts +94 -0
- package/src/lib/vars/preanswers.ts +395 -0
- package/src/lib/vars/question-plan.ts +218 -0
- package/src/lib/vars/report.ts +228 -0
- package/src/lib/vars/safe-regex.ts +235 -0
- package/src/lib/vars/validate.ts +312 -0
- package/src/lib/watch-loop.ts +148 -0
- package/src/templating/init-liquid-engine.ts +58 -16
- package/src/utils/choice-prompt.ts +143 -0
- package/src/utils/command-errors.ts +186 -0
- package/src/utils/command-help.ts +45 -0
- package/src/utils/confirm-prompt.ts +110 -0
- package/src/utils/flags.ts +153 -0
- package/src/utils/formatting.ts +540 -55
- package/src/utils/prompts.ts +35 -1
- package/src/utils/sous-directory.ts +245 -0
- package/src/utils/table.ts +603 -0
- package/src/utils/value-prompt.ts +119 -0
- package/bin/xcv +0 -5
- package/shared-prompts/_partials/resume-task.md +0 -51
- package/shared-prompts/_partials/sub-agent-delegation.md +0 -32
- package/shared-prompts/_partials/update-task-file.md +0 -52
- package/shared-prompts/memories/automated-browser-tasks/INDEX.tpl.md +0 -52
- package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/SKILL.tpl.md +0 -102
- package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/examples/auth-failure-handling.mjs +0 -81
- package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/examples/chained-workflow.mjs +0 -126
- package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/examples/simple-fetch.mjs +0 -92
- package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/references/architecture.md +0 -61
- package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/references/auth-and-sessions.md +0 -65
- package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/references/ctx-api.md +0 -96
- package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/references/installation.md +0 -104
- package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/references/script-conventions.md +0 -243
- package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/scripts/chrome-state.mjs +0 -148
- package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/scripts/debug.mjs +0 -383
- package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/scripts/debug.spec.mjs +0 -267
- package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/scripts/eslint.config.mjs +0 -56
- package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/scripts/harness.mjs +0 -169
- package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/scripts/keyring.mjs +0 -59
- package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/scripts/logger.mjs +0 -25
- package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/scripts/params.mjs +0 -140
- package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/scripts/run.mjs +0 -140
- package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/scripts/settings.tpl.mjs +0 -1
- package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/scripts/utils.mjs +0 -185
- package/shared-prompts/skills/automated-browser-tasks/create-automated-browser-task/SKILL.tpl.md +0 -52
- package/shared-prompts/skills/automated-browser-tasks/running-automated-browser-tasks/SKILL.tpl.md +0 -59
- package/shared-prompts/skills/automated-browser-tasks/update-automated-browser-task/SKILL.tpl.md +0 -47
- package/shared-prompts/skills/control-flow/approve/SKILL.tpl.md +0 -26
- package/shared-prompts/skills/control-flow/opine/SKILL.tpl.md +0 -58
- package/shared-prompts/skills/control-flow/repeat/SKILL.tpl.md +0 -27
- package/shared-prompts/skills/control-flow/research/SKILL.tpl.md +0 -34
- package/shared-prompts/skills/sous-skills/about-sous/SKILL.tpl.md +0 -51
- package/shared-prompts/skills/task-files/about-task-files/SKILL.tpl.md +0 -122
- package/shared-prompts/skills/task-files/continue-task-in-new-branch/SKILL.tpl.md +0 -80
- package/shared-prompts/skills/task-files/go/SKILL.tpl.md +0 -14
- package/shared-prompts/skills/task-files/resume-task/SKILL.tpl.md +0 -13
- package/shared-prompts/skills/task-files/start-task/SKILL.tpl.md +0 -93
- package/shared-prompts/skills/task-files/update/SKILL.tpl.md +0 -14
- package/shared-prompts/skills/task-files/update-task-file/SKILL.tpl.md +0 -13
- /package/{shared-prompts/skills/sous-skills → recipes/core/sous-skills/skills}/about-agent-skills/references/substitutions.md +0 -0
- /package/{shared-prompts/skills/sous-skills → recipes/core/sous-skills/skills}/about-liquid-templates/references/liquid-filters.md +0 -0
package/src/utils/formatting.ts
CHANGED
|
@@ -1,16 +1,58 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
2
|
import { color } from "@oclif/color";
|
|
3
3
|
|
|
4
|
-
const DEFAULT_VAR_NAME_PADDING = 20;
|
|
5
|
-
|
|
6
4
|
const HEADER_LINES = [
|
|
7
5
|
" ▄█████ ▄████▄ ██ ██ ▄█████ ",
|
|
8
6
|
" ▀▀▀▄▄▄ ██ ██ ██ ██ ▀▀▀▄▄▄ ",
|
|
9
7
|
" █████▀ ▀████▀ ▀████▀ █████▀ ",
|
|
10
8
|
];
|
|
11
9
|
|
|
10
|
+
// --- The Color Palette ---------------------------------------------------------------------------
|
|
12
11
|
|
|
12
|
+
/**
|
|
13
|
+
* Every color sous writes comes from this one place, so a reader learns the
|
|
14
|
+
* vocabulary once: a label is cyan, the value beside it is bright white, an
|
|
15
|
+
* aside about that value is grey, a warning is bright yellow with its sharpest
|
|
16
|
+
* words in orange, an explanation is bright teal, and an error is bright red.
|
|
17
|
+
*
|
|
18
|
+
* The orange is given as a hex value on purpose. A terminal that can show it
|
|
19
|
+
* does; one limited to sixteen colors has chalk fold it down to bright yellow,
|
|
20
|
+
* which keeps a highlighted word inside the warning's own color rather than
|
|
21
|
+
* turning it into something that reads as a second kind of message.
|
|
22
|
+
*/
|
|
23
|
+
export const palette = {
|
|
24
|
+
/** The name in a key and value pair. */
|
|
25
|
+
label: (text: string): string => color.cyan(text),
|
|
26
|
+
/** The value beside a label. */
|
|
27
|
+
value: (text: string): string => color.whiteBright(text),
|
|
28
|
+
/** A trailing aside: a location, a provenance, anything secondary. */
|
|
29
|
+
muted: (text: string): string => color.gray(text),
|
|
30
|
+
/** The body of a warning. */
|
|
31
|
+
warning: (text: string): string => color.yellowBright(text),
|
|
32
|
+
/** The words inside a warning that carry the actual risk. */
|
|
33
|
+
highlight: (text: string): string => color.hex("#ff8800")(text),
|
|
34
|
+
/** An explanatory line that is neither a warning nor an error. */
|
|
35
|
+
note: (text: string): string => color.cyanBright(text),
|
|
36
|
+
/** The body of an error. */
|
|
37
|
+
error: (text: string): string => color.redBright(text),
|
|
38
|
+
};
|
|
13
39
|
|
|
40
|
+
/**
|
|
41
|
+
* The prefix every error message carries, so an error is still findable by
|
|
42
|
+
* eye or by grep when the output has no color at all (a pipe, a log file,
|
|
43
|
+
* `CI=true`).
|
|
44
|
+
*/
|
|
45
|
+
export const ERROR_PREFIX = "Error: ";
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Adds the error prefix to a message that does not already begin with one, so
|
|
49
|
+
* a message that says "Error:" itself is never made to say it twice.
|
|
50
|
+
*
|
|
51
|
+
* @param text - The first line of an error message.
|
|
52
|
+
*/
|
|
53
|
+
export function withErrorPrefix(text: string): string {
|
|
54
|
+
return /^error:\s/i.test(text.trimStart()) ? text : `${ERROR_PREFIX}${text}`;
|
|
55
|
+
}
|
|
14
56
|
|
|
15
57
|
|
|
16
58
|
// --- Core Output Functions -----------------------------------------------------------------------
|
|
@@ -84,13 +126,27 @@ export function indent(text: string, count = 2, char = " "): string {
|
|
|
84
126
|
// --- Header & Footer -----------------------------------------------------------------------------
|
|
85
127
|
|
|
86
128
|
/**
|
|
87
|
-
* Writes the CLI header
|
|
129
|
+
* Writes the CLI header using the given line writer.
|
|
130
|
+
*
|
|
131
|
+
* Factored out so commands whose stdout must stay machine-readable (the
|
|
132
|
+
* `sous config *` commands, which emit JSON) can route the decorative banner to
|
|
133
|
+
* stderr instead — see ConfigCommand.emitHeader.
|
|
134
|
+
*
|
|
135
|
+
* @param write - Receives one already-formatted line at a time (no trailing newline).
|
|
136
|
+
*/
|
|
137
|
+
export function headerTo(write: (line: string) => void): void {
|
|
138
|
+
write(" ");
|
|
139
|
+
write(" ");
|
|
140
|
+
write(color.cyan(HEADER_LINES.join("\n")));
|
|
141
|
+
write(" Agent Configuration Manager ");
|
|
142
|
+
write(" ");
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Writes the CLI header to stdout.
|
|
88
147
|
*/
|
|
89
148
|
export function header(): void {
|
|
90
|
-
|
|
91
|
-
log(color.cyan(HEADER_LINES.join("\n")));
|
|
92
|
-
log(" Agent Configuration Manager ");
|
|
93
|
-
blankLine();
|
|
149
|
+
headerTo(log);
|
|
94
150
|
}
|
|
95
151
|
|
|
96
152
|
/**
|
|
@@ -116,6 +172,29 @@ export function heading(text: string, prefixSymbol = "▶"): void {
|
|
|
116
172
|
log(color.yellowBright(final));
|
|
117
173
|
}
|
|
118
174
|
|
|
175
|
+
/**
|
|
176
|
+
* Writes a section heading followed by a blank line, which is how a command
|
|
177
|
+
* opens a section of its output.
|
|
178
|
+
*
|
|
179
|
+
* `heading` on its own leaves the first line of content pressed right up under
|
|
180
|
+
* the heading, which reads as one crowded block. Every command that lays out
|
|
181
|
+
* sections of prose, tables or variable lists uses this instead, so the spacing
|
|
182
|
+
* is decided in one place rather than by a `blankLine()` call remembered at
|
|
183
|
+
* each site.
|
|
184
|
+
*
|
|
185
|
+
* @param text - The heading text; a colon is appended unless it ends in a period.
|
|
186
|
+
* @param prefixSymbol - The marker drawn before the text.
|
|
187
|
+
*
|
|
188
|
+
* @example
|
|
189
|
+
* section("Adding a repository");
|
|
190
|
+
* // ▶ Adding a repository:
|
|
191
|
+
* // (blank line)
|
|
192
|
+
*/
|
|
193
|
+
export function section(text: string, prefixSymbol = "▶"): void {
|
|
194
|
+
heading(text, prefixSymbol);
|
|
195
|
+
blankLine();
|
|
196
|
+
}
|
|
197
|
+
|
|
119
198
|
/**
|
|
120
199
|
* Writes a subheading to the console.
|
|
121
200
|
*
|
|
@@ -171,39 +250,167 @@ export function showCount(count: number, entity = "items", headingText = "", ver
|
|
|
171
250
|
log(line);
|
|
172
251
|
}
|
|
173
252
|
|
|
253
|
+
// --- The One Key and Value Display ---------------------------------------------------------------
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* How far a key and value block is indented. Every one of them sits at the same
|
|
257
|
+
* depth, whether it is a command's opening block, a result, or the facts about
|
|
258
|
+
* a variable.
|
|
259
|
+
*/
|
|
260
|
+
export const VARIABLE_INDENT = 4;
|
|
261
|
+
|
|
262
|
+
/** One key and value pair, as the display functions take it. */
|
|
263
|
+
export interface VariableEntry {
|
|
264
|
+
/**
|
|
265
|
+
* The name, written on the left. An empty label means "the same label as the
|
|
266
|
+
* line above, continued": the label column and its colon are left blank, so a
|
|
267
|
+
* fact that needs several lines still lines up under itself.
|
|
268
|
+
*/
|
|
269
|
+
label: string;
|
|
270
|
+
/** The value, written after the colon. Anything printable. */
|
|
271
|
+
value: unknown;
|
|
272
|
+
/**
|
|
273
|
+
* A secondary fact about the value: where it came from, where it lives. It
|
|
274
|
+
* follows the value in muted grey rather than in parentheses, so the value
|
|
275
|
+
* itself stays the thing the eye lands on.
|
|
276
|
+
*/
|
|
277
|
+
detail?: string;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/** How a key and value block is laid out. */
|
|
281
|
+
export interface ShowVariablesOptions {
|
|
282
|
+
/** How far the block is indented. Four by default. */
|
|
283
|
+
indent?: number;
|
|
284
|
+
/**
|
|
285
|
+
* The column the colons line up at, counted from the start of the label.
|
|
286
|
+
* Worked out from the longest label when it is not given, which is what
|
|
287
|
+
* `showVariables` does for a whole block.
|
|
288
|
+
*/
|
|
289
|
+
labelWidth?: number;
|
|
290
|
+
/** The column to wrap values at. Defaults to the wrap width. */
|
|
291
|
+
width?: number;
|
|
292
|
+
/** Where the lines go. The console by default. */
|
|
293
|
+
write?: (line: string) => void;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Lays one key and value pair out: the label in the label color, padded so
|
|
298
|
+
* every colon in the block lines up, then the value in the value color, then
|
|
299
|
+
* any detail in muted grey. A value too long for the line wraps and hangs under
|
|
300
|
+
* the value column rather than under the label.
|
|
301
|
+
*
|
|
302
|
+
* @param entry - The label, the value and any trailing detail.
|
|
303
|
+
* @param options - Indentation, label column and wrap column.
|
|
304
|
+
* @returns The rendered lines.
|
|
305
|
+
*/
|
|
306
|
+
export function formatVariable(
|
|
307
|
+
entry: VariableEntry,
|
|
308
|
+
options: ShowVariablesOptions = {},
|
|
309
|
+
): string[] {
|
|
310
|
+
const pad = " ".repeat(Math.max(0, options.indent ?? VARIABLE_INDENT));
|
|
311
|
+
const labelWidth = Math.max(options.labelWidth ?? entry.label.length, entry.label.length);
|
|
312
|
+
const gutter = labelWidth + 2; // the padded label, then ": "
|
|
313
|
+
const width = options.width ?? wrapColumns();
|
|
314
|
+
const valueWidth = Math.max(20, width - pad.length - gutter);
|
|
315
|
+
|
|
316
|
+
const valueText = entry.value === undefined || entry.value === null ? "" : String(entry.value);
|
|
317
|
+
const valueLines = wrapText(valueText, valueWidth, { hangingIndent: 0 });
|
|
318
|
+
const detailLines =
|
|
319
|
+
entry.detail === undefined || entry.detail === ""
|
|
320
|
+
? []
|
|
321
|
+
: wrapText(entry.detail, valueWidth, { hangingIndent: 0 });
|
|
322
|
+
|
|
323
|
+
// The detail joins the value's last line when both fit, and starts a line of
|
|
324
|
+
// its own when they do not, so a long location is never cut in half.
|
|
325
|
+
const cells: string[] = valueLines.map(line => palette.value(line));
|
|
326
|
+
if (detailLines.length > 0) {
|
|
327
|
+
const last = valueLines[valueLines.length - 1] ?? "";
|
|
328
|
+
const [first, ...rest] = detailLines;
|
|
329
|
+
if (last !== "" && displayWidth(last) + 1 + displayWidth(first ?? "") <= valueWidth) {
|
|
330
|
+
cells[cells.length - 1] = `${palette.value(last)} ${palette.muted(first ?? "")}`;
|
|
331
|
+
} else {
|
|
332
|
+
cells.push(palette.muted(first ?? ""));
|
|
333
|
+
}
|
|
334
|
+
for (const line of rest) cells.push(palette.muted(line));
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
if (cells.length === 0) cells.push("");
|
|
338
|
+
|
|
339
|
+
return cells.map((cell, index) =>
|
|
340
|
+
index === 0 && entry.label !== ""
|
|
341
|
+
? `${pad}${palette.label(entry.label.padEnd(labelWidth))}: ${cell}`
|
|
342
|
+
: `${pad}${" ".repeat(gutter)}${cell}`,
|
|
343
|
+
);
|
|
344
|
+
}
|
|
345
|
+
|
|
174
346
|
/**
|
|
175
|
-
*
|
|
347
|
+
* Writes one key and value pair to the console.
|
|
348
|
+
*
|
|
349
|
+
* @param label - The name, written on the left.
|
|
350
|
+
* @param value - The value, written after the colon.
|
|
351
|
+
* @param options - Indentation, label column, wrap column and where to write.
|
|
176
352
|
*
|
|
177
353
|
* @example
|
|
178
|
-
*
|
|
179
|
-
* //
|
|
354
|
+
* showVariable("Config", "./my-config.js");
|
|
355
|
+
* // Config: ./my-config.js
|
|
180
356
|
*/
|
|
181
|
-
export function
|
|
182
|
-
|
|
183
|
-
|
|
357
|
+
export function showVariable(
|
|
358
|
+
label: string,
|
|
359
|
+
value: unknown,
|
|
360
|
+
options: ShowVariablesOptions & { detail?: string } = {},
|
|
361
|
+
): void {
|
|
362
|
+
const write = options.write ?? log;
|
|
363
|
+
const entry: VariableEntry = {
|
|
364
|
+
label,
|
|
365
|
+
value,
|
|
366
|
+
...(options.detail === undefined ? {} : { detail: options.detail }),
|
|
367
|
+
};
|
|
368
|
+
for (const line of formatVariable(entry, options)) write(line);
|
|
184
369
|
}
|
|
185
370
|
|
|
186
371
|
/**
|
|
187
|
-
*
|
|
372
|
+
* Writes a list of key and value pairs to the console, with the colons lined
|
|
373
|
+
* up. Every key and value display in sous goes through this function or through
|
|
374
|
+
* `showVariable`, so there is exactly one of them to change.
|
|
375
|
+
*
|
|
376
|
+
* @param entries - The pairs, either as a plain object or as a list carrying details.
|
|
377
|
+
* @param options - Indentation, wrap column and where to write.
|
|
188
378
|
*
|
|
189
379
|
* @example
|
|
190
|
-
*
|
|
191
|
-
* //
|
|
192
|
-
* //
|
|
193
|
-
*/
|
|
194
|
-
export function
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
380
|
+
* showVariables({ Config: "./my-config.js", Strict: "false" });
|
|
381
|
+
* // Config: ./my-config.js
|
|
382
|
+
* // Strict: false
|
|
383
|
+
*/
|
|
384
|
+
export function showVariables(
|
|
385
|
+
entries: Record<string, unknown> | VariableEntry[],
|
|
386
|
+
options: ShowVariablesOptions = {},
|
|
387
|
+
): void {
|
|
388
|
+
const list: VariableEntry[] = Array.isArray(entries)
|
|
389
|
+
? entries
|
|
390
|
+
: Object.entries(entries).map(([label, value]) => ({ label, value }));
|
|
391
|
+
if (list.length === 0) return;
|
|
392
|
+
|
|
393
|
+
const labelWidth =
|
|
394
|
+
options.labelWidth ?? Math.max(...list.map(entry => entry.label.length));
|
|
395
|
+
for (const entry of list) showVariable(entry.label, entry.value, {
|
|
396
|
+
...options,
|
|
397
|
+
labelWidth,
|
|
398
|
+
...(entry.detail === undefined ? {} : { detail: entry.detail }),
|
|
399
|
+
});
|
|
199
400
|
}
|
|
200
401
|
|
|
201
402
|
/**
|
|
202
403
|
* Displays a heading labelled "Command Variables" followed by a variable list.
|
|
404
|
+
*
|
|
405
|
+
* The "Dry Run" entry is a special case: it is noise when dry-run mode is off, so it
|
|
406
|
+
* is only shown when its value is `true`. Every other entry is always shown.
|
|
203
407
|
*/
|
|
204
408
|
export function showCommandVars(vars: Record<string, any>): void {
|
|
409
|
+
const filtered = Object.fromEntries(
|
|
410
|
+
Object.entries(vars).filter(([name, value]) => name !== "Dry Run" || value === true),
|
|
411
|
+
);
|
|
205
412
|
subheading("Command Variables", "$");
|
|
206
|
-
|
|
413
|
+
showVariables(filtered);
|
|
207
414
|
}
|
|
208
415
|
|
|
209
416
|
/**
|
|
@@ -236,17 +443,23 @@ export function deleteStatus(recordsDeleted: number, totalRecordsToDelete: numbe
|
|
|
236
443
|
|
|
237
444
|
/**
|
|
238
445
|
* Writes an error message to the console in red.
|
|
446
|
+
*
|
|
447
|
+
* @param text - The message to display.
|
|
448
|
+
* @param write - Line sink (default stdout via `log`). Commands whose stdout must
|
|
449
|
+
* stay machine-readable (the `sous config *` JSON commands) pass a stderr writer
|
|
450
|
+
* so error text never corrupts a piped stdout stream.
|
|
239
451
|
*/
|
|
240
|
-
export function displayError(text: string): void {
|
|
241
|
-
const lines = text.split("\n");
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
452
|
+
export function displayError(text: string, write: (line: string) => void = log): void {
|
|
453
|
+
const lines = text.split("\n").filter(line => line.trim() !== "");
|
|
454
|
+
write("");
|
|
455
|
+
lines.forEach((line, index) => {
|
|
456
|
+
const body = index === 0 ? withErrorPrefix(line.trim()) : line.trim();
|
|
457
|
+
for (const wrapped of wrapText(body, wrapColumns() - 2)) {
|
|
458
|
+
write(indent(palette.error(wrapped)));
|
|
246
459
|
}
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
|
|
460
|
+
});
|
|
461
|
+
write("");
|
|
462
|
+
write("");
|
|
250
463
|
}
|
|
251
464
|
|
|
252
465
|
/**
|
|
@@ -257,43 +470,74 @@ export function displayError(text: string): void {
|
|
|
257
470
|
* structure (a checked-paths list, a code sample, numbered steps). `displayError`
|
|
258
471
|
* trims every line and drops blanks, which flattens that structure.
|
|
259
472
|
*
|
|
473
|
+
* @param text - The pre-formatted, multi-line message to display.
|
|
474
|
+
* @param write - Line sink (default stdout via `log`). Commands whose stdout must
|
|
475
|
+
* stay machine-readable (the `sous config *` JSON commands) pass a stderr writer
|
|
476
|
+
* so error text never corrupts a piped stdout stream.
|
|
477
|
+
*
|
|
260
478
|
* @example
|
|
261
479
|
* displayErrorBlock("No config found.\n\n Checked:\n /a/.sous/");
|
|
262
480
|
*/
|
|
263
|
-
export function displayErrorBlock(text: string): void {
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
481
|
+
export function displayErrorBlock(text: string, write: (line: string) => void = log): void {
|
|
482
|
+
const lines = text.split("\n");
|
|
483
|
+
// The prefix goes on the first line that has any text on it, which is the
|
|
484
|
+
// line a reader (or a grep) looks at.
|
|
485
|
+
const first = lines.findIndex(line => line.trim() !== "");
|
|
486
|
+
|
|
487
|
+
write("");
|
|
488
|
+
lines.forEach((line, index) => {
|
|
489
|
+
if (line === "") {
|
|
490
|
+
write(" ");
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
493
|
+
const body = index === first ? withErrorPrefix(line) : line;
|
|
494
|
+
for (const wrapped of wrapText(body, wrapColumns() - 2)) {
|
|
495
|
+
write(indent(palette.error(wrapped)));
|
|
496
|
+
}
|
|
497
|
+
});
|
|
498
|
+
write("");
|
|
499
|
+
write("");
|
|
270
500
|
}
|
|
271
501
|
|
|
272
502
|
/**
|
|
273
|
-
* Writes a notice indicating that the operation is running in dry-run mode.
|
|
503
|
+
* Writes a notice indicating that the operation is running in dry-run mode. It
|
|
504
|
+
* is an explanation of what is about to not happen, so it carries the note
|
|
505
|
+
* color rather than the warning one.
|
|
274
506
|
*
|
|
275
507
|
* @example
|
|
276
508
|
* dryRunNotice("File will not be written.");
|
|
277
509
|
* // [Dry Run] File will not be written.
|
|
278
510
|
*/
|
|
279
511
|
export function dryRunNotice(text: string): void {
|
|
280
|
-
|
|
512
|
+
note(`[Dry Run] ${text}`, { indent: 4 });
|
|
281
513
|
}
|
|
282
514
|
|
|
283
515
|
/**
|
|
284
|
-
* Writes a warning message to the console with a yellow banner.
|
|
516
|
+
* Writes a warning message to the console with a yellow banner. The body is
|
|
517
|
+
* bright yellow; words written in capitals inside it are taken as the ones
|
|
518
|
+
* carrying the risk and are drawn in orange, so a reader skimming the block
|
|
519
|
+
* still takes in the part that matters.
|
|
520
|
+
*
|
|
521
|
+
* @param text - The message to display.
|
|
522
|
+
* @param write - Line sink (default stdout via `log`). Commands whose stdout must
|
|
523
|
+
* stay machine-readable (the `sous config *` JSON commands) pass a stderr writer
|
|
524
|
+
* so warning text never corrupts a piped stdout stream.
|
|
285
525
|
*/
|
|
286
|
-
export function warning(text: string): void {
|
|
287
|
-
|
|
288
|
-
|
|
526
|
+
export function warning(text: string, write: (line: string) => void = log): void {
|
|
527
|
+
write("");
|
|
528
|
+
write("");
|
|
529
|
+
write(color.bgYellowBright(color.black(" WARNING: ")));
|
|
289
530
|
|
|
290
|
-
const
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
531
|
+
for (const line of text.split("\n")) {
|
|
532
|
+
if (line.trim() === "") {
|
|
533
|
+
write(" ");
|
|
534
|
+
continue;
|
|
535
|
+
}
|
|
536
|
+
for (const wrapped of wrapText(line.trim(), wrapColumns() - 2)) {
|
|
537
|
+
write(indent(palette.warning(highlightUpperCaseWords(wrapped))));
|
|
294
538
|
}
|
|
295
539
|
}
|
|
296
|
-
|
|
540
|
+
write("");
|
|
297
541
|
}
|
|
298
542
|
|
|
299
543
|
// --- Miscellaneous Helpers -----------------------------------------------------------------------
|
|
@@ -303,7 +547,7 @@ export function warning(text: string): void {
|
|
|
303
547
|
*/
|
|
304
548
|
function highlightUpperCaseWords(
|
|
305
549
|
str: string,
|
|
306
|
-
highlightFn: (word: string) => string =
|
|
550
|
+
highlightFn: (word: string) => string = palette.highlight,
|
|
307
551
|
): string {
|
|
308
552
|
return str.replace(/\b[A-Z][A-Z_'"()\[\]{}<>|&*!@#%^\\-]+\b/g, match => highlightFn(match));
|
|
309
553
|
}
|
|
@@ -330,9 +574,250 @@ export function sortObjectKeys<T extends Record<string, any>>(obj: T): T {
|
|
|
330
574
|
}
|
|
331
575
|
}
|
|
332
576
|
|
|
577
|
+
// --- ANSI-Safe Measurement -----------------------------------------------------------------------
|
|
578
|
+
|
|
579
|
+
/**
|
|
580
|
+
* Matches the SGR (Select Graphic Rendition) escape sequences the coloring
|
|
581
|
+
* helpers emit, which is every escape sequence sous writes into a string.
|
|
582
|
+
*/
|
|
583
|
+
const SGR_PATTERN = /\u001B\[[0-9;]*m/g;
|
|
584
|
+
|
|
585
|
+
/**
|
|
586
|
+
* Removes the color escape sequences from a string, leaving the characters a
|
|
587
|
+
* reader actually sees.
|
|
588
|
+
*
|
|
589
|
+
* @param text - The text to strip.
|
|
590
|
+
* @returns The same text, without any color escape sequence.
|
|
591
|
+
*/
|
|
592
|
+
export function stripAnsi(text: string): string {
|
|
593
|
+
return String(text ?? "").replace(SGR_PATTERN, "");
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
/**
|
|
597
|
+
* How many terminal columns a string occupies once its color codes are taken
|
|
598
|
+
* out. Every alignment decision measures with this, because a colored cell is
|
|
599
|
+
* longer than it looks.
|
|
600
|
+
*
|
|
601
|
+
* East Asian wide characters and emoji are out of scope: this counts one column
|
|
602
|
+
* per code unit, so a cell holding them aligns a little short. Everything sous
|
|
603
|
+
* lays out in a table is an identifier, a path, a URL or English prose, so the
|
|
604
|
+
* simple measure holds; widening it later means changing this one function.
|
|
605
|
+
*
|
|
606
|
+
* @param text - The text to measure.
|
|
607
|
+
* @returns The visible width, in columns.
|
|
608
|
+
*/
|
|
609
|
+
export function displayWidth(text: string): number {
|
|
610
|
+
return stripAnsi(text).length;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
// --- Width-Aware Wrapping ------------------------------------------------------------------------
|
|
614
|
+
|
|
615
|
+
/**
|
|
616
|
+
* The width sous wraps prose to when the output is not a terminal, or when the
|
|
617
|
+
* terminal never said how wide it is.
|
|
618
|
+
*/
|
|
619
|
+
export const DEFAULT_WRAP_COLUMNS = 100;
|
|
620
|
+
|
|
621
|
+
/**
|
|
622
|
+
* How many columns the output has to work with: the real terminal width when
|
|
623
|
+
* there is a terminal, and the default width otherwise, so a piped or recorded
|
|
624
|
+
* run always wraps the same way.
|
|
625
|
+
*
|
|
626
|
+
* @param stream - The stream to measure. Defaults to stdout.
|
|
627
|
+
*/
|
|
628
|
+
export function terminalColumns(stream: { columns?: number } = process.stdout): number {
|
|
629
|
+
const columns = stream.columns;
|
|
630
|
+
return typeof columns === "number" && columns > 0 ? columns : DEFAULT_WRAP_COLUMNS;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
/**
|
|
634
|
+
* How far short of the right edge text stops, so a wrapped paragraph never
|
|
635
|
+
* touches the last column of the terminal.
|
|
636
|
+
*/
|
|
637
|
+
export const RIGHT_MARGIN = 2;
|
|
638
|
+
|
|
639
|
+
/**
|
|
640
|
+
* The width every paragraph sous prints is wrapped to: the columns available,
|
|
641
|
+
* less the right margin.
|
|
642
|
+
*
|
|
643
|
+
* @param stream - The stream to measure. Defaults to stdout.
|
|
644
|
+
*/
|
|
645
|
+
export function wrapColumns(stream: { columns?: number } = process.stdout): number {
|
|
646
|
+
return Math.max(20, terminalColumns(stream) - RIGHT_MARGIN);
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
/** How a paragraph is wrapped. */
|
|
650
|
+
export interface WrapOptions {
|
|
651
|
+
/**
|
|
652
|
+
* How far a continuation line hangs past the first line of its paragraph.
|
|
653
|
+
* Two by default, which is what makes a wrapped sentence read as one
|
|
654
|
+
* paragraph rather than as several. Pass zero where the caller lays out its
|
|
655
|
+
* own hanging indent, as the labeled blocks do.
|
|
656
|
+
*/
|
|
657
|
+
hangingIndent?: number;
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
/**
|
|
661
|
+
* Wraps a paragraph to a width, breaking on spaces and never inside a word. A
|
|
662
|
+
* word longer than the width (a URL, a path) is left whole on a line of its
|
|
663
|
+
* own, because half a URL is worse than a long line.
|
|
664
|
+
*
|
|
665
|
+
* Newlines already in the text are honored: each line is wrapped on its own,
|
|
666
|
+
* and each keeps whatever indentation it was written with, so an already laid
|
|
667
|
+
* out block survives being passed through. Continuation lines hang two spaces
|
|
668
|
+
* past the line they continue, unless the caller asks for something else.
|
|
669
|
+
*
|
|
670
|
+
* Width is measured with the color codes taken out, so a colored word is
|
|
671
|
+
* counted by what a reader sees rather than by what the escape sequences add.
|
|
672
|
+
*
|
|
673
|
+
* @param text - The prose to wrap.
|
|
674
|
+
* @param width - The column to wrap at. Defaults to the terminal width, less the right margin.
|
|
675
|
+
* @param options - How far continuation lines hang.
|
|
676
|
+
* @returns One string per rendered line, without trailing spaces.
|
|
677
|
+
*
|
|
678
|
+
* @example
|
|
679
|
+
* wrapText("one two three", 7, { hangingIndent: 0 });
|
|
680
|
+
* // -> ["one two", "three"]
|
|
681
|
+
*/
|
|
682
|
+
export function wrapText(
|
|
683
|
+
text: string,
|
|
684
|
+
width: number = wrapColumns(),
|
|
685
|
+
options: WrapOptions = {},
|
|
686
|
+
): string[] {
|
|
687
|
+
const limit = Math.max(1, Math.floor(width));
|
|
688
|
+
const hanging = Math.max(0, Math.floor(options.hangingIndent ?? 2));
|
|
689
|
+
const lines: string[] = [];
|
|
690
|
+
|
|
691
|
+
for (const paragraph of String(text ?? "").split("\n")) {
|
|
692
|
+
// The line's own indentation is kept, and continuation lines are measured
|
|
693
|
+
// and drawn from inside it, so a block that arrives already laid out is
|
|
694
|
+
// still laid out when it leaves.
|
|
695
|
+
const leading = /^[ \t]*/.exec(paragraph)?.[0] ?? "";
|
|
696
|
+
const body = paragraph.slice(leading.length);
|
|
697
|
+
const words = body.split(/\s+/).filter(word => word.length > 0);
|
|
698
|
+
if (words.length === 0) {
|
|
699
|
+
lines.push("");
|
|
700
|
+
continue;
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
const continuation = leading + " ".repeat(hanging);
|
|
704
|
+
let prefix = leading;
|
|
705
|
+
let current = "";
|
|
706
|
+
|
|
707
|
+
for (const word of words) {
|
|
708
|
+
if (current.length === 0) {
|
|
709
|
+
current = word;
|
|
710
|
+
continue;
|
|
711
|
+
}
|
|
712
|
+
const projected = displayWidth(prefix) + displayWidth(current) + 1 + displayWidth(word);
|
|
713
|
+
if (projected <= limit) {
|
|
714
|
+
current = `${current} ${word}`;
|
|
715
|
+
continue;
|
|
716
|
+
}
|
|
717
|
+
lines.push(`${prefix}${current}`);
|
|
718
|
+
prefix = continuation;
|
|
719
|
+
current = word;
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
lines.push(`${prefix}${current}`);
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
return lines;
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
/** How a paragraph is laid out for printing. */
|
|
729
|
+
export interface ParagraphOptions extends WrapOptions {
|
|
730
|
+
/** How far the whole paragraph is indented. Two by default. */
|
|
731
|
+
indent?: number;
|
|
732
|
+
/** The column to wrap at, indentation included. Defaults to the wrap width. */
|
|
733
|
+
width?: number;
|
|
734
|
+
/** A color to paint every line with. */
|
|
735
|
+
color?: (text: string) => string;
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
/**
|
|
739
|
+
* Lays a paragraph out for printing: wrapped to the width left after its own
|
|
740
|
+
* indentation, indented, and colored.
|
|
741
|
+
*
|
|
742
|
+
* @param text - The prose to lay out.
|
|
743
|
+
* @param options - Indentation, wrap column and color.
|
|
744
|
+
* @returns The rendered lines.
|
|
745
|
+
*/
|
|
746
|
+
export function formatParagraph(text: string, options: ParagraphOptions = {}): string[] {
|
|
747
|
+
const pad = Math.max(0, options.indent ?? 2);
|
|
748
|
+
const width = options.width ?? wrapColumns();
|
|
749
|
+
const wrapOptions: WrapOptions =
|
|
750
|
+
options.hangingIndent === undefined ? {} : { hangingIndent: options.hangingIndent };
|
|
751
|
+
const paint = options.color ?? ((line: string) => line);
|
|
752
|
+
|
|
753
|
+
return wrapText(text, Math.max(20, width - pad), wrapOptions).map(line =>
|
|
754
|
+
line === "" ? " " : " ".repeat(pad) + paint(line),
|
|
755
|
+
);
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
/**
|
|
759
|
+
* Writes a wrapped, indented paragraph to the console. Every long sentence the
|
|
760
|
+
* CLI prints goes through this, so nothing is ever left to the terminal's own
|
|
761
|
+
* idea of where a line should break.
|
|
762
|
+
*
|
|
763
|
+
* @param text - The prose to print.
|
|
764
|
+
* @param options - Indentation, wrap column and color.
|
|
765
|
+
*/
|
|
766
|
+
export function paragraph(text: string, options: ParagraphOptions = {}): void {
|
|
767
|
+
for (const line of formatParagraph(text, options)) log(line);
|
|
768
|
+
}
|
|
769
|
+
|
|
333
770
|
/**
|
|
334
|
-
*
|
|
771
|
+
* Writes an explanatory line: something that is neither a warning nor an error,
|
|
772
|
+
* but a note about what just happened or what is about to. It is bright teal,
|
|
773
|
+
* which is the one color reserved for explanation.
|
|
774
|
+
*
|
|
775
|
+
* @param text - The note to print.
|
|
776
|
+
* @param options - Indentation and wrap column.
|
|
777
|
+
*/
|
|
778
|
+
export function note(text: string, options: Omit<ParagraphOptions, "color"> = {}): void {
|
|
779
|
+
paragraph(text, { ...options, color: palette.note });
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
// --- Key Bindings --------------------------------------------------------------------------------
|
|
783
|
+
|
|
784
|
+
/**
|
|
785
|
+
* The one-line legend a prompt draws under itself, in the style the stock
|
|
786
|
+
* `@inquirer/select` prompt uses: the key in bold, what it does dimmed beside
|
|
787
|
+
* it, pairs separated by a dimmed bullet.
|
|
788
|
+
*
|
|
789
|
+
* @param keys - Key and action pairs, in the order they are shown.
|
|
790
|
+
* @returns The legend line.
|
|
791
|
+
*
|
|
792
|
+
* @example
|
|
793
|
+
* keysHelpTip([["↑↓", "navigate"], ["⏎", "select"]]);
|
|
794
|
+
* // -> "↑↓ navigate • ⏎ select"
|
|
795
|
+
*/
|
|
796
|
+
export function keysHelpTip(keys: Array<[key: string, action: string]>): string {
|
|
797
|
+
return keys
|
|
798
|
+
.map(([key, action]) => `${color.bold(key)} ${color.dim(action)}`)
|
|
799
|
+
.join(color.dim(" • "));
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
/**
|
|
803
|
+
* The character every bulleted line in the CLI is drawn with, so a list of
|
|
804
|
+
* points always looks like a list of points.
|
|
805
|
+
*/
|
|
806
|
+
export const BULLET = "•";
|
|
807
|
+
|
|
808
|
+
/** How many blank lines a question keeps under itself while it is waiting. */
|
|
809
|
+
export const PROMPT_BOTTOM_PADDING = 2;
|
|
810
|
+
|
|
811
|
+
/**
|
|
812
|
+
* The block a prompt draws under its input line: whatever it has to say
|
|
813
|
+
* (a validation message, the key legend), then two blank lines, so a question
|
|
814
|
+
* never sits on the terminal's very last row with its legend scrolled away.
|
|
815
|
+
*
|
|
816
|
+
* @param content - The line under the prompt, or an empty string for none.
|
|
817
|
+
* @returns The bottom block, ready to hand back from a prompt's renderer.
|
|
335
818
|
*/
|
|
336
|
-
function
|
|
337
|
-
|
|
819
|
+
export function promptBottom(content: string): string {
|
|
820
|
+
const lines = content === "" ? [] : [content];
|
|
821
|
+
for (let index = 0; index < PROMPT_BOTTOM_PADDING; index++) lines.push(" ");
|
|
822
|
+
return lines.join("\n");
|
|
338
823
|
}
|