@sous-io/sous 0.2.37 → 1.0.2
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 +14 -256
- package/dist/bin.js +11 -0
- package/dist/sous.js +1094 -0
- package/docs/markdown/README.md +5 -4
- package/docs/markdown/_sidebar.md +21 -1
- package/docs/markdown/commands.md +294 -431
- package/docs/markdown/config-discovery.md +25 -14
- package/docs/markdown/config-inspection.md +53 -31
- package/docs/markdown/config-layers.md +168 -66
- package/docs/markdown/config-variables.md +44 -11
- package/docs/markdown/configuration.md +104 -17
- package/docs/markdown/design-principles.md +4 -1
- package/docs/markdown/repositories-authoring.md +30 -0
- package/docs/markdown/repositories-consuming.md +72 -14
- package/docs/markdown/repositories-file-formats.md +31 -13
- package/docs/markdown/repositories-providers.md +48 -57
- package/docs/markdown/repositories-quickstart.md +3 -2
- package/docs/markdown/repositories-troubleshooting.md +27 -1
- package/docs/markdown/repositories-variables.md +6 -1
- package/docs/markdown/repositories.md +18 -8
- package/package.json +48 -107
- package/recipes/core/sous-skills/skills/about-liquid-templates/SKILL.tpl.md +4 -6
- package/recipes/core/sous-skills/skills/about-liquid-templates/references/liquid-filters.tpl.md +2 -0
- package/recipes/core/sous-skills/skills/about-sous/SKILL.tpl.md +10 -9
- package/recipes/core/sous-skills/skills/about-sous-configuration/SKILL.tpl.md +35 -14
- package/recipes/core/sous-skills/skills/about-sous-recipes/SKILL.tpl.md +51 -20
- package/recipes/core/sous-skills/sous.recipe.yaml +1 -1
- package/sous.config.schema.json +351 -132
- package/bin/run.js +0 -51
- package/src/base-command.ts +0 -457
- package/src/commands/build.ts +0 -181
- package/src/commands/clear.ts +0 -79
- package/src/commands/compile.ts +0 -155
- package/src/commands/config/get.ts +0 -110
- package/src/commands/config/show.ts +0 -32
- package/src/commands/config/validate.ts +0 -53
- package/src/commands/help.ts +0 -46
- package/src/commands/init.ts +0 -289
- package/src/commands/launch.ts +0 -139
- package/src/commands/lock/rebuild.ts +0 -239
- package/src/commands/lock/show.ts +0 -115
- package/src/commands/namespace/list.ts +0 -138
- package/src/commands/namespace/show.ts +0 -130
- package/src/commands/prune.ts +0 -43
- package/src/commands/recipe/list.ts +0 -112
- package/src/commands/recipe/show.ts +0 -439
- package/src/commands/repo/add.ts +0 -145
- package/src/commands/repo/contribute.ts +0 -604
- package/src/commands/repo/gc.ts +0 -173
- package/src/commands/repo/init.ts +0 -136
- package/src/commands/repo/link.ts +0 -899
- package/src/commands/repo/list.ts +0 -236
- package/src/commands/repo/release.ts +0 -685
- package/src/commands/repo/remove.ts +0 -194
- package/src/commands/repo/search.ts +0 -243
- package/src/commands/repo/submit.ts +0 -341
- package/src/commands/repo/unlink.ts +0 -461
- package/src/commands/subscription/add.ts +0 -286
- package/src/commands/subscription/list.ts +0 -208
- package/src/commands/subscription/remove.ts +0 -182
- package/src/commands/subscription/update.ts +0 -216
- package/src/commands/update.ts +0 -178
- package/src/commands/vars/ask.ts +0 -417
- package/src/commands/vars/index.ts +0 -79
- package/src/commands/vars/list.ts +0 -67
- package/src/commands/vars/show.ts +0 -77
- package/src/config-command.ts +0 -30
- package/src/lib/build-preparation.ts +0 -128
- package/src/lib/build-service.ts +0 -580
- package/src/lib/config-discovery.ts +0 -378
- package/src/lib/config-inspect.ts +0 -145
- package/src/lib/config-kernel.mjs +0 -379
- package/src/lib/config-schema.ts +0 -401
- package/src/lib/env-file.ts +0 -328
- package/src/lib/env-local.ts +0 -212
- package/src/lib/errors.ts +0 -32
- package/src/lib/glob-pattern.ts +0 -24
- package/src/lib/include-resolver.ts +0 -523
- package/src/lib/interactive.ts +0 -165
- package/src/lib/markdown-compiler.ts +0 -947
- package/src/lib/package-info.ts +0 -25
- package/src/lib/pid-service.ts +0 -99
- package/src/lib/project-install.d.mts +0 -44
- package/src/lib/project-install.mjs +0 -234
- package/src/lib/project-scaffold/index.ts +0 -332
- package/src/lib/project-scaffold/templates.ts +0 -240
- package/src/lib/repos/catalog-display.ts +0 -215
- package/src/lib/repos/catalog-inputs.ts +0 -288
- package/src/lib/repos/catalog.ts +0 -842
- package/src/lib/repos/contribute.ts +0 -452
- package/src/lib/repos/core-recipe.ts +0 -105
- package/src/lib/repos/declarations.ts +0 -187
- package/src/lib/repos/defaults.ts +0 -175
- package/src/lib/repos/formats/common.ts +0 -440
- package/src/lib/repos/formats/index-file.ts +0 -253
- package/src/lib/repos/formats/links-map.ts +0 -98
- package/src/lib/repos/formats/lockfile.ts +0 -169
- package/src/lib/repos/formats/patterns.ts +0 -57
- package/src/lib/repos/formats/recipe-manifest.ts +0 -205
- package/src/lib/repos/formats/repo-manifest.ts +0 -97
- package/src/lib/repos/formats/store-entry.ts +0 -84
- package/src/lib/repos/formats/variable-definition.ts +0 -268
- package/src/lib/repos/freshness.ts +0 -264
- package/src/lib/repos/git-clone.ts +0 -850
- package/src/lib/repos/identity.ts +0 -89
- package/src/lib/repos/index.ts +0 -57
- package/src/lib/repos/links.ts +0 -356
- package/src/lib/repos/load-manifest.ts +0 -237
- package/src/lib/repos/lock-service.ts +0 -454
- package/src/lib/repos/locked-namespace-resolver.ts +0 -232
- package/src/lib/repos/locked-recipe-lookup.ts +0 -123
- package/src/lib/repos/locked-recipes.ts +0 -254
- package/src/lib/repos/managed-layer.ts +0 -422
- package/src/lib/repos/memories-hash-name.ts +0 -33
- package/src/lib/repos/namespace-resolver.ts +0 -527
- package/src/lib/repos/providers/base.ts +0 -268
- package/src/lib/repos/providers/git.ts +0 -245
- package/src/lib/repos/providers/github.ts +0 -589
- package/src/lib/repos/providers/gitlab.ts +0 -332
- package/src/lib/repos/providers/http.ts +0 -128
- package/src/lib/repos/providers/index-cache.ts +0 -421
- package/src/lib/repos/providers/index.ts +0 -106
- package/src/lib/repos/providers/local.ts +0 -410
- package/src/lib/repos/providers/provider.ts +0 -615
- package/src/lib/repos/recipe-config-layers.ts +0 -316
- package/src/lib/repos/recipe-key-matcher.ts +0 -62
- package/src/lib/repos/recipe-memories.ts +0 -178
- package/src/lib/repos/recipe-targets.ts +0 -236
- package/src/lib/repos/ref-search.ts +0 -46
- package/src/lib/repos/release/bump.ts +0 -161
- package/src/lib/repos/release/changelog.ts +0 -461
- package/src/lib/repos/release/git-state.ts +0 -460
- package/src/lib/repos/release/index-builder.ts +0 -1108
- package/src/lib/repos/release/index.ts +0 -19
- package/src/lib/repos/release/plan.ts +0 -521
- package/src/lib/repos/release/settle.ts +0 -154
- package/src/lib/repos/release/submissions.ts +0 -214
- package/src/lib/repos/release/submit-checkout.ts +0 -290
- package/src/lib/repos/release/submit-questions.ts +0 -153
- package/src/lib/repos/release/submit-service.ts +0 -898
- package/src/lib/repos/release/tags.ts +0 -239
- package/src/lib/repos/release/validate.ts +0 -463
- package/src/lib/repos/resolver.ts +0 -1028
- package/src/lib/repos/scaffold/index.ts +0 -245
- package/src/lib/repos/scaffold/templates.ts +0 -517
- package/src/lib/repos/seed.ts +0 -473
- package/src/lib/repos/store/contract.ts +0 -64
- package/src/lib/repos/store/hash.ts +0 -191
- package/src/lib/repos/store/recipe-store.ts +0 -599
- package/src/lib/repos/store/settings.ts +0 -58
- package/src/lib/repos/subscription-service.ts +0 -3859
- package/src/lib/repos/trust.ts +0 -447
- package/src/lib/repos/update-plan.ts +0 -235
- package/src/lib/self-update/index.ts +0 -14
- package/src/lib/self-update/installs.ts +0 -413
- package/src/lib/self-update/managers.ts +0 -126
- package/src/lib/self-update/plan.ts +0 -315
- package/src/lib/self-update/registry.ts +0 -99
- package/src/lib/self-update/run.ts +0 -506
- package/src/lib/self-update/versions.ts +0 -243
- package/src/lib/settings.ts +0 -1107
- package/src/lib/sous-home.ts +0 -104
- package/src/lib/state.ts +0 -167
- package/src/lib/vars/answers.ts +0 -192
- package/src/lib/vars/ask.ts +0 -1152
- package/src/lib/vars/definition-source.ts +0 -261
- package/src/lib/vars/display.ts +0 -231
- package/src/lib/vars/index.ts +0 -20
- package/src/lib/vars/ladder.ts +0 -282
- package/src/lib/vars/mappings.ts +0 -260
- package/src/lib/vars/names.ts +0 -94
- package/src/lib/vars/preanswers.ts +0 -395
- package/src/lib/vars/question-plan.ts +0 -241
- package/src/lib/vars/report.ts +0 -228
- package/src/lib/vars/safe-regex.ts +0 -235
- package/src/lib/vars/secrets.ts +0 -131
- package/src/lib/vars/validate.ts +0 -312
- package/src/lib/version-report.ts +0 -90
- package/src/lib/watch-loop.ts +0 -148
- package/src/lib/watch-service.ts +0 -116
- package/src/services/ref-resolver/container.ts +0 -107
- package/src/services/ref-resolver/dependency.ts +0 -60
- package/src/services/ref-resolver/format.ts +0 -197
- package/src/services/ref-resolver/glob.ts +0 -115
- package/src/services/ref-resolver/hash-names.ts +0 -155
- package/src/services/ref-resolver/index.ts +0 -55
- package/src/services/ref-resolver/injectable.ts +0 -36
- package/src/services/ref-resolver/location.ts +0 -62
- package/src/services/ref-resolver/lookups/cached-index-lookup.ts +0 -53
- package/src/services/ref-resolver/lookups/catalog-lookup.ts +0 -23
- package/src/services/ref-resolver/lookups/catalog-matcher.ts +0 -261
- package/src/services/ref-resolver/lookups/chained-lookup.ts +0 -27
- package/src/services/ref-resolver/lookups/env-var-lookup.ts +0 -65
- package/src/services/ref-resolver/lookups/fetched-index-lookup.ts +0 -150
- package/src/services/ref-resolver/lookups/recorded-dependency-lookup.ts +0 -62
- package/src/services/ref-resolver/lookups/ref-lookup.ts +0 -54
- package/src/services/ref-resolver/lookups/variable-lookup.ts +0 -123
- package/src/services/ref-resolver/named-refs.ts +0 -144
- package/src/services/ref-resolver/parser/env-var-splitter.ts +0 -27
- package/src/services/ref-resolver/parser/location-splitter.ts +0 -239
- package/src/services/ref-resolver/parser/name-path-splitter.ts +0 -200
- package/src/services/ref-resolver/parser/partial-ref.ts +0 -84
- package/src/services/ref-resolver/parser/query-splitter.ts +0 -61
- package/src/services/ref-resolver/parser/range-splitter.ts +0 -52
- package/src/services/ref-resolver/parser/ref-parser.ts +0 -115
- package/src/services/ref-resolver/parser/repo-qualifier-splitter.ts +0 -51
- package/src/services/ref-resolver/parts.ts +0 -115
- package/src/services/ref-resolver/pruners/command-line-pruner.ts +0 -47
- package/src/services/ref-resolver/pruners/include-pruner.ts +0 -66
- package/src/services/ref-resolver/pruners/manifest-pruner.ts +0 -130
- package/src/services/ref-resolver/pruners/ref-pruner.ts +0 -135
- package/src/services/ref-resolver/pruners/rule-helpers.ts +0 -44
- package/src/services/ref-resolver/pruners/stored-key-pruner.ts +0 -136
- package/src/services/ref-resolver/ref-helpers.ts +0 -62
- package/src/services/ref-resolver/ref-picker-service.ts +0 -204
- package/src/services/ref-resolver/ref-report.ts +0 -84
- package/src/services/ref-resolver/ref-resolve-arguments.ts +0 -47
- package/src/services/ref-resolver/ref-resolve-result.ts +0 -36
- package/src/services/ref-resolver/ref-resolver-service.ts +0 -323
- package/src/services/ref-resolver/source.ts +0 -48
- package/src/services/ref-resolver/tokens.ts +0 -25
- package/src/services/ref-resolver/types.ts +0 -155
- package/src/templating/filters/bullet-list.ts +0 -9
- package/src/templating/filters/index.ts +0 -8
- package/src/templating/init-liquid-engine.ts +0 -139
- package/src/templating/lib/glob-files.ts +0 -74
- package/src/templating/lib/import-export.ts +0 -32
- package/src/templating/lib/secret-scope.ts +0 -41
- package/src/templating/lib/tag-args.ts +0 -19
- package/src/templating/tags/exportScalarVarsJs.ts +0 -46
- package/src/templating/tags/getFiles.ts +0 -89
- package/src/templating/tags/index.ts +0 -14
- package/src/templating/tags/listFiles.ts +0 -54
- package/src/templating/tags/showVars.ts +0 -28
- package/src/utils/choice-prompt.ts +0 -143
- package/src/utils/command-errors.ts +0 -186
- package/src/utils/command-help.ts +0 -45
- package/src/utils/confirm-prompt.ts +0 -110
- package/src/utils/flags.ts +0 -177
- package/src/utils/formatting.ts +0 -849
- package/src/utils/prompts.ts +0 -53
- package/src/utils/sous-directory.ts +0 -245
- package/src/utils/table.ts +0 -603
- package/src/utils/value-prompt.ts +0 -119
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
import { Hash, type Liquid } from "liquidjs";
|
|
2
|
-
import type { Context } from "liquidjs/dist/context/context.js";
|
|
3
|
-
import type { TagToken } from "liquidjs/dist/tokens/tag-token.js";
|
|
4
|
-
import { globFiles, parseGlobList, type GlobFile } from "../lib/glob-files.js";
|
|
5
|
-
import { splitPositionalName } from "../lib/tag-args.js";
|
|
6
|
-
import { importNamedExport } from "../lib/import-export.js";
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Registers the `{% getFiles <varName> root="..." include="..." exclude="..." import="..." %}`
|
|
10
|
-
* tag. It globs files under `root` and assigns the resulting array to `<varName>`
|
|
11
|
-
* in the template scope. It renders no output — use a `{% for %}` loop to present
|
|
12
|
-
* the results.
|
|
13
|
-
*
|
|
14
|
-
* Each result has `{ path, dir, relPath, name }`. When the optional `import`
|
|
15
|
-
* attribute names an export (e.g. `import="meta"`), each file is dynamically
|
|
16
|
-
* imported and that export is attached under the same key (e.g. `file.meta`);
|
|
17
|
-
* files that fail to import or lack the export are omitted from the results.
|
|
18
|
-
*
|
|
19
|
-
* Attribute values may be quoted strings or scope variables (e.g. `root=tasksDir`).
|
|
20
|
-
* `include`/`exclude` are comma-separated glob patterns matched relative to `root`.
|
|
21
|
-
*/
|
|
22
|
-
export function registerGetFilesTag(engine: Liquid): void {
|
|
23
|
-
engine.registerTag("getFiles", {
|
|
24
|
-
parse(token: TagToken) {
|
|
25
|
-
const { name, rest } = splitPositionalName(token.args);
|
|
26
|
-
this.varName = name;
|
|
27
|
-
this.hash = new Hash(rest, true);
|
|
28
|
-
},
|
|
29
|
-
|
|
30
|
-
*render(ctx: Context): Generator<unknown, string, Record<string, unknown>> {
|
|
31
|
-
const hash = yield this.hash.render(ctx);
|
|
32
|
-
const varName: string | null = this.varName;
|
|
33
|
-
|
|
34
|
-
const files = (yield resolveFiles(hash)) as unknown;
|
|
35
|
-
|
|
36
|
-
if (varName) {
|
|
37
|
-
(ctx.bottom() as Record<string, unknown>)[varName] = files;
|
|
38
|
-
}
|
|
39
|
-
return "";
|
|
40
|
-
},
|
|
41
|
-
});
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/** A file record optionally carrying a dynamically-imported export. */
|
|
45
|
-
type ResolvedFile = GlobFile & Record<string, unknown>;
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Resolve the glob attributes from a rendered hash into file records, applying
|
|
49
|
-
* the optional `import` export attachment.
|
|
50
|
-
*
|
|
51
|
-
* @param hash - The rendered tag attributes (`root`, `include`, `exclude`, `import`).
|
|
52
|
-
* @returns The matched files, each optionally carrying the imported export.
|
|
53
|
-
*/
|
|
54
|
-
async function resolveFiles(hash: Record<string, unknown>): Promise<ResolvedFile[]> {
|
|
55
|
-
const root = hash.root != null ? String(hash.root) : "";
|
|
56
|
-
if (!root) {
|
|
57
|
-
throw new Error('getFiles: a "root" attribute is required');
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
const files = await globFiles({
|
|
61
|
-
root,
|
|
62
|
-
include: parseGlobList(hash.include != null ? String(hash.include) : undefined),
|
|
63
|
-
exclude: parseGlobList(hash.exclude != null ? String(hash.exclude) : undefined),
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
const importName = hash.import != null ? String(hash.import).trim() : "";
|
|
67
|
-
if (!importName) return files as ResolvedFile[];
|
|
68
|
-
|
|
69
|
-
return attachImports(files, importName);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* For each file, dynamically import `importName` and attach it under that key.
|
|
74
|
-
* Files whose import fails or lacks the export are dropped from the result.
|
|
75
|
-
*
|
|
76
|
-
* @param files - The globbed file records.
|
|
77
|
-
* @param importName - The export to read from each file.
|
|
78
|
-
* @returns Files that successfully yielded the export, with it attached.
|
|
79
|
-
*/
|
|
80
|
-
async function attachImports(files: GlobFile[], importName: string): Promise<ResolvedFile[]> {
|
|
81
|
-
const enriched = await Promise.all(
|
|
82
|
-
files.map(async (file) => {
|
|
83
|
-
const value = await importNamedExport(file.path, importName);
|
|
84
|
-
if (value === undefined) return null;
|
|
85
|
-
return { ...file, [importName]: value };
|
|
86
|
-
})
|
|
87
|
-
);
|
|
88
|
-
return enriched.filter((f): f is ResolvedFile => f !== null);
|
|
89
|
-
}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import type { Liquid } from "liquidjs";
|
|
2
|
-
import { registerShowVarsTag } from "./showVars.js";
|
|
3
|
-
import { registerExportScalarVarsJsTag } from "./exportScalarVarsJs.js";
|
|
4
|
-
import { registerGetFilesTag } from "./getFiles.js";
|
|
5
|
-
import { registerListFilesTag } from "./listFiles.js";
|
|
6
|
-
|
|
7
|
-
const tagRegistrars: Array<(engine: Liquid) => void> = [
|
|
8
|
-
registerShowVarsTag,
|
|
9
|
-
registerExportScalarVarsJsTag,
|
|
10
|
-
registerGetFilesTag,
|
|
11
|
-
registerListFilesTag,
|
|
12
|
-
];
|
|
13
|
-
|
|
14
|
-
export default tagRegistrars;
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
import { Hash, type Liquid } from "liquidjs";
|
|
2
|
-
import type { Context } from "liquidjs/dist/context/context.js";
|
|
3
|
-
import type { TagToken } from "liquidjs/dist/tokens/tag-token.js";
|
|
4
|
-
import { globFiles, parseGlobList, type GlobFile } from "../lib/glob-files.js";
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Registers the `{% listFiles root="..." include="..." exclude="..." %}` tag.
|
|
8
|
-
* It globs files under `root` and renders them directly as a simple markdown
|
|
9
|
-
* bullet list of file names — the convenience counterpart to `getFiles`.
|
|
10
|
-
*
|
|
11
|
-
* For custom presentation (or to read file metadata), use `getFiles` with a
|
|
12
|
-
* `{% for %}` loop instead. `listFiles` is intentionally glob-only and emits a
|
|
13
|
-
* fixed format.
|
|
14
|
-
*
|
|
15
|
-
* Attribute values may be quoted strings or scope variables. `include`/`exclude`
|
|
16
|
-
* are comma-separated glob patterns matched relative to `root`. An optional
|
|
17
|
-
* `relative="true"` renders relative paths instead of bare file names.
|
|
18
|
-
*/
|
|
19
|
-
export function registerListFilesTag(engine: Liquid): void {
|
|
20
|
-
engine.registerTag("listFiles", {
|
|
21
|
-
parse(token: TagToken) {
|
|
22
|
-
this.hash = new Hash(token.args, true);
|
|
23
|
-
},
|
|
24
|
-
|
|
25
|
-
*render(ctx: Context): Generator<unknown, string, Record<string, unknown>> {
|
|
26
|
-
const hash = yield this.hash.render(ctx);
|
|
27
|
-
const files = (yield resolveFiles(hash)) as unknown as GlobFile[];
|
|
28
|
-
const useRelative = String(hash.relative ?? "") === "true";
|
|
29
|
-
|
|
30
|
-
if (files.length === 0) return "";
|
|
31
|
-
return files
|
|
32
|
-
.map((f) => `- ${useRelative ? f.relPath : f.name}`)
|
|
33
|
-
.join("\n");
|
|
34
|
-
},
|
|
35
|
-
});
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Resolve the glob attributes from a rendered hash into file records.
|
|
40
|
-
*
|
|
41
|
-
* @param hash - The rendered tag attributes (`root`, `include`, `exclude`).
|
|
42
|
-
* @returns The matched files.
|
|
43
|
-
*/
|
|
44
|
-
async function resolveFiles(hash: Record<string, unknown>): Promise<GlobFile[]> {
|
|
45
|
-
const root = hash.root != null ? String(hash.root) : "";
|
|
46
|
-
if (!root) {
|
|
47
|
-
throw new Error('listFiles: a "root" attribute is required');
|
|
48
|
-
}
|
|
49
|
-
return globFiles({
|
|
50
|
-
root,
|
|
51
|
-
include: parseGlobList(hash.include != null ? String(hash.include) : undefined),
|
|
52
|
-
exclude: parseGlobList(hash.exclude != null ? String(hash.exclude) : undefined),
|
|
53
|
-
});
|
|
54
|
-
}
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import type { Liquid } from "liquidjs";
|
|
2
|
-
import type { Context } from "liquidjs/dist/context/context.js";
|
|
3
|
-
import { sortObjectKeys } from "../../utils/formatting.js";
|
|
4
|
-
import { HIDDEN_VALUE, isSecretEntry } from "../lib/secret-scope.js";
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Dumps all variables currently in scope as a fenced JSON block. A secret (one a
|
|
8
|
-
* recipe declared, or one that very probably is) prints as `(hidden)`, so the
|
|
9
|
-
* dump never carries it into compiled output.
|
|
10
|
-
*/
|
|
11
|
-
export function registerShowVarsTag(engine: Liquid): void {
|
|
12
|
-
engine.registerTag("showVars", {
|
|
13
|
-
render(ctx: Context) {
|
|
14
|
-
const seen = new WeakSet();
|
|
15
|
-
const scope = sortObjectKeys(ctx.getAll() as Record<string, unknown>);
|
|
16
|
-
const json = JSON.stringify(scope, (key, value) => {
|
|
17
|
-
if (typeof value === "object" && value !== null) {
|
|
18
|
-
if (seen.has(value)) return "[Circular]";
|
|
19
|
-
seen.add(value);
|
|
20
|
-
}
|
|
21
|
-
if (key !== "" && isSecretEntry(engine, key, value)) return HIDDEN_VALUE;
|
|
22
|
-
return value;
|
|
23
|
-
}, 2);
|
|
24
|
-
|
|
25
|
-
return "# Sous Debug: Variable Dump\n```json\n" + json + "\n```";
|
|
26
|
-
},
|
|
27
|
-
});
|
|
28
|
-
}
|
|
@@ -1,143 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The pick-one question sous asks for a recipe variable with a fixed set of
|
|
3
|
-
* allowed values.
|
|
4
|
-
*
|
|
5
|
-
* It behaves like the stock `select` prompt (arrow keys to move, Enter to
|
|
6
|
-
* choose, the default preselected) with the one addition every sous question
|
|
7
|
-
* shares: Tab resolves the question with `{ kind: "advanced" }` instead of
|
|
8
|
-
* moving the cursor, so the caller can show the advanced view and then ask the
|
|
9
|
-
* same question again. Built on the public `@inquirer/core` hooks only.
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
import {
|
|
13
|
-
createPrompt,
|
|
14
|
-
isDownKey,
|
|
15
|
-
isEnterKey,
|
|
16
|
-
isTabKey,
|
|
17
|
-
isUpKey,
|
|
18
|
-
useKeypress,
|
|
19
|
-
usePrefix,
|
|
20
|
-
useState,
|
|
21
|
-
type Status,
|
|
22
|
-
} from "@inquirer/core";
|
|
23
|
-
import { promptBottom } from "./formatting.js";
|
|
24
|
-
|
|
25
|
-
/** One value the question offers, and how it is written on screen. */
|
|
26
|
-
export interface PromptChoice {
|
|
27
|
-
/** The line shown in the list. */
|
|
28
|
-
name: string;
|
|
29
|
-
/** The value handed back when the line is chosen. */
|
|
30
|
-
value: string;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/** What the pick-one question needs in order to ask itself. */
|
|
34
|
-
export interface ChoicePromptConfig {
|
|
35
|
-
/** The one-line question. */
|
|
36
|
-
message: string;
|
|
37
|
-
/** The values on offer, in the order they are listed. */
|
|
38
|
-
choices: PromptChoice[];
|
|
39
|
-
/** The value that starts out highlighted, so Enter alone accepts it. */
|
|
40
|
-
default?: string;
|
|
41
|
-
/** A line under the list: the key legend, in the stock prompts' style. */
|
|
42
|
-
hint?: string;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/** How the question ended: with a choice, or with a request for the advanced view. */
|
|
46
|
-
export type ChoicePromptResult = { kind: "value"; value: string } | { kind: "advanced" };
|
|
47
|
-
|
|
48
|
-
/** Everything the renderer draws, so it can be tested without a terminal. */
|
|
49
|
-
export interface ChoicePromptView {
|
|
50
|
-
/** The prompt prefix (the question mark, or the check mark once answered). */
|
|
51
|
-
prefix: string;
|
|
52
|
-
/** The one-line question. */
|
|
53
|
-
message: string;
|
|
54
|
-
/** The values on offer, in the order they are listed. */
|
|
55
|
-
choices: PromptChoice[];
|
|
56
|
-
/** Which choice is highlighted, counting from zero. */
|
|
57
|
-
active: number;
|
|
58
|
-
/** A line under the list: the key legend, in the stock prompts' style. */
|
|
59
|
-
hint?: string;
|
|
60
|
-
/** Whether the question has been answered, which replaces the list with the answer. */
|
|
61
|
-
answered?: boolean;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/** The pointer drawn beside the highlighted choice. */
|
|
65
|
-
const POINTER = ">";
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Draws the question: prefix and message on the first line, then one line per
|
|
69
|
-
* choice with a pointer beside the highlighted one, and the hint underneath.
|
|
70
|
-
* Once the question is answered the list gives way to the chosen value, so the
|
|
71
|
-
* scrollback keeps one tidy line per question.
|
|
72
|
-
*
|
|
73
|
-
* @param view - The prompt's state.
|
|
74
|
-
* @returns The question line and the block under it.
|
|
75
|
-
*/
|
|
76
|
-
export function renderChoicePrompt(view: ChoicePromptView): [string, string] {
|
|
77
|
-
const chosen = view.choices[view.active];
|
|
78
|
-
|
|
79
|
-
if (view.answered === true) {
|
|
80
|
-
return [`${view.prefix} ${view.message}: ${chosen?.name ?? ""}`.trimEnd(), ""];
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
const lines = view.choices.map(
|
|
84
|
-
(choice, index) => `${index === view.active ? POINTER : " "} ${choice.name}`
|
|
85
|
-
);
|
|
86
|
-
if (view.hint !== undefined && view.hint !== "") lines.push(view.hint);
|
|
87
|
-
|
|
88
|
-
return [`${view.prefix} ${view.message}`.trimEnd(), promptBottom(lines.join("\n"))];
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/** Asks one pick-one question, with Tab bound to the advanced view. */
|
|
92
|
-
export const choicePrompt = createPrompt<ChoicePromptResult, ChoicePromptConfig>(
|
|
93
|
-
(config, done) => {
|
|
94
|
-
const [status, setStatus] = useState<Status>("idle");
|
|
95
|
-
const [answered, setAnswered] = useState(false);
|
|
96
|
-
const [active, setActive] = useState(
|
|
97
|
-
Math.max(
|
|
98
|
-
0,
|
|
99
|
-
config.choices.findIndex((choice) => choice.value === config.default)
|
|
100
|
-
)
|
|
101
|
-
);
|
|
102
|
-
const prefix = usePrefix({ status });
|
|
103
|
-
|
|
104
|
-
useKeypress((key, rl) => {
|
|
105
|
-
if (status !== "idle") return;
|
|
106
|
-
// Readline echoes whatever was pressed into its buffer; take it back out.
|
|
107
|
-
rl.clearLine(0);
|
|
108
|
-
|
|
109
|
-
if (isTabKey(key)) {
|
|
110
|
-
setStatus("done");
|
|
111
|
-
done({ kind: "advanced" });
|
|
112
|
-
return;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
if (isEnterKey(key)) {
|
|
116
|
-
const chosen = config.choices[active];
|
|
117
|
-
if (chosen === undefined) return;
|
|
118
|
-
setAnswered(true);
|
|
119
|
-
setStatus("done");
|
|
120
|
-
done({ kind: "value", value: chosen.value });
|
|
121
|
-
return;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
if (isUpKey(key)) {
|
|
125
|
-
setActive((active - 1 + config.choices.length) % config.choices.length);
|
|
126
|
-
return;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
if (isDownKey(key)) {
|
|
130
|
-
setActive((active + 1) % config.choices.length);
|
|
131
|
-
}
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
return renderChoicePrompt({
|
|
135
|
-
prefix,
|
|
136
|
-
message: config.message,
|
|
137
|
-
choices: config.choices,
|
|
138
|
-
active,
|
|
139
|
-
...(config.hint === undefined ? {} : { hint: config.hint }),
|
|
140
|
-
...(answered ? { answered: true } : {}),
|
|
141
|
-
});
|
|
142
|
-
}
|
|
143
|
-
);
|
|
@@ -1,186 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* One way for every sous command to report a failure.
|
|
3
|
-
*
|
|
4
|
-
* A person who forgets an argument, misspells a flag, or runs sous where there
|
|
5
|
-
* is no config has made an ordinary mistake, and what they need back is the
|
|
6
|
-
* sentence describing it (plus, when the mistake is a usage one, the command's
|
|
7
|
-
* own help). A stack trace pointing into oclif's parser tells them nothing, so
|
|
8
|
-
* no expected failure prints one. A trace is still one environment variable
|
|
9
|
-
* away: set `SOUS_DEBUG` and every reported error prints its stack to stderr.
|
|
10
|
-
*
|
|
11
|
-
* The same reporting is used by every command. `BaseCommand.catch` calls it for
|
|
12
|
-
* the commands that discover a config; the repository authoring commands
|
|
13
|
-
* (`repo init`, `repo release`, `repo submit`) extend oclif's `Command`
|
|
14
|
-
* directly and call it from their own `catch`, so all of sous fails the same
|
|
15
|
-
* way.
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
import { Errors, type Command } from "@oclif/core";
|
|
19
|
-
import { isConfigError } from "../lib/errors.js";
|
|
20
|
-
import { wantsHelp } from "../lib/interactive.js";
|
|
21
|
-
import { displayErrorBlock, log } from "./formatting.js";
|
|
22
|
-
import { printCommandHelpToStderr } from "./command-help.js";
|
|
23
|
-
|
|
24
|
-
/** The environment variable that turns stack traces back on. */
|
|
25
|
-
export const DEBUG_ENV_VAR = "SOUS_DEBUG";
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Values of `SOUS_DEBUG` that mean "not set". Everything else counts as set,
|
|
29
|
-
* because people spell it `1`, `true` and `yes` in roughly equal measure. The
|
|
30
|
-
* same list gates the `CI` variable in `lib/interactive.ts`.
|
|
31
|
-
*/
|
|
32
|
-
const FALSY_DEBUG_VALUES = new Set(["", "0", "false", "no", "off"]);
|
|
33
|
-
|
|
34
|
-
/** The line oclif appends to a parse error, replaced by the help screen itself. */
|
|
35
|
-
const OCLIF_HELP_HINT = "See more help with --help";
|
|
36
|
-
|
|
37
|
-
/** The sentence an unexpected failure ends with, since sous cannot explain it. */
|
|
38
|
-
const UNEXPECTED_REMEDY = `Sous did not expect this error; set '${DEBUG_ENV_VAR}=1' and run the command again for the full stack trace.`;
|
|
39
|
-
|
|
40
|
-
/** Where the report is written, and what it reads to decide about traces. */
|
|
41
|
-
export type ErrorReportOptions = {
|
|
42
|
-
/**
|
|
43
|
-
* Line sink for the error text. Defaults to stdout via `log`; commands whose
|
|
44
|
-
* stdout must stay machine-readable pass a stderr writer.
|
|
45
|
-
*/
|
|
46
|
-
write?: (line: string) => void;
|
|
47
|
-
/** The environment, read for `SOUS_DEBUG`. Defaults to `process.env`. */
|
|
48
|
-
env?: NodeJS.ProcessEnv;
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* True when this run was asked for stack traces, either by `SOUS_DEBUG` or by
|
|
53
|
-
* oclif's own debug setting (which `--debug`-style bootstrapping turns on).
|
|
54
|
-
*
|
|
55
|
-
* @param env - The environment to read. Defaults to the real one.
|
|
56
|
-
*/
|
|
57
|
-
export function stackTracesRequested(env: NodeJS.ProcessEnv = process.env): boolean {
|
|
58
|
-
const oclifDebug = (globalThis as { oclif?: { debug?: boolean } }).oclif?.debug;
|
|
59
|
-
if (oclifDebug === true) return true;
|
|
60
|
-
|
|
61
|
-
const raw = env[DEBUG_ENV_VAR];
|
|
62
|
-
if (raw === undefined) return false;
|
|
63
|
-
return !FALSY_DEBUG_VALUES.has(raw.trim().toLowerCase());
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* True when the "error" is only a request to end the process, which every
|
|
68
|
-
* `this.exit(code)` raises. It carries no message and must reach oclif
|
|
69
|
-
* untouched, or a clean exit would be reported as a failure.
|
|
70
|
-
*
|
|
71
|
-
* @param error - The thrown value.
|
|
72
|
-
*/
|
|
73
|
-
export function isExitSignal(error: unknown): boolean {
|
|
74
|
-
if (error instanceof Errors.ExitError) return true;
|
|
75
|
-
return typeof error === "object" && error !== null && (error as { code?: string }).code === "EEXIT";
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* True when the caller got the command line wrong (a missing argument, an
|
|
80
|
-
* unknown flag, a value outside a flag's options) or asked a question sous
|
|
81
|
-
* could not ask. Both are answered by showing the command's own help, so both
|
|
82
|
-
* are treated the same way.
|
|
83
|
-
*
|
|
84
|
-
* oclif's parse errors all carry a `parse` property; nothing else oclif throws
|
|
85
|
-
* does, which is what separates a usage mistake from a reported failure.
|
|
86
|
-
*
|
|
87
|
-
* @param error - The thrown value.
|
|
88
|
-
*/
|
|
89
|
-
export function isUsageError(error: unknown): boolean {
|
|
90
|
-
if (wantsHelp(error)) return true;
|
|
91
|
-
return error instanceof Errors.CLIError && "parse" in error;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* Reports one command failure in sous's own voice: the message, the command's
|
|
96
|
-
* help underneath it when the message alone is not enough, and a stack trace
|
|
97
|
-
* only when one was asked for.
|
|
98
|
-
*
|
|
99
|
-
* @param command - The command that failed, whose help may be drawn.
|
|
100
|
-
* @param error - The error it failed with.
|
|
101
|
-
* @param options - Where to write, and the environment to read.
|
|
102
|
-
* @returns The exit code the run should end with, or undefined when this error
|
|
103
|
-
* is not sous's to report and must fall through to oclif's own handling.
|
|
104
|
-
*/
|
|
105
|
-
export async function reportCommandError(
|
|
106
|
-
command: Command,
|
|
107
|
-
error: Error & { exitCode?: number; oclif?: { exit?: number | false } },
|
|
108
|
-
options: ErrorReportOptions = {}
|
|
109
|
-
): Promise<number | undefined> {
|
|
110
|
-
// A clean exit, and a command rendering its own JSON error, both belong to
|
|
111
|
-
// oclif; neither is a failure for sous to describe.
|
|
112
|
-
if (isExitSignal(error)) return undefined;
|
|
113
|
-
if (jsonRequested(command)) return undefined;
|
|
114
|
-
|
|
115
|
-
const write = options.write ?? log;
|
|
116
|
-
const showHelp = isUsageError(error);
|
|
117
|
-
const expected = showHelp || isConfigError(error) || error instanceof Errors.CLIError;
|
|
118
|
-
|
|
119
|
-
const body = [messageOf(error, showHelp)];
|
|
120
|
-
if (!expected) body.push("", UNEXPECTED_REMEDY);
|
|
121
|
-
|
|
122
|
-
displayErrorBlock(body.join("\n"), write);
|
|
123
|
-
|
|
124
|
-
if (stackTracesRequested(options.env ?? process.env)) writeStackToStderr(error);
|
|
125
|
-
if (showHelp) await printCommandHelpToStderr(command);
|
|
126
|
-
|
|
127
|
-
return exitCodeOf(error);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
/**
|
|
131
|
-
* The message to print, with oclif's "See more help with --help" line removed
|
|
132
|
-
* when the help itself is about to be printed underneath it.
|
|
133
|
-
*
|
|
134
|
-
* @param error - The error being reported.
|
|
135
|
-
* @param showHelp - Whether the command's help follows the message.
|
|
136
|
-
*/
|
|
137
|
-
function messageOf(error: Error, showHelp: boolean): string {
|
|
138
|
-
const message = error.message?.trim() === "" ? String(error) : error.message;
|
|
139
|
-
if (!showHelp) return message;
|
|
140
|
-
return message
|
|
141
|
-
.split("\n")
|
|
142
|
-
.filter((line) => line.trim() !== OCLIF_HELP_HINT)
|
|
143
|
-
.join("\n")
|
|
144
|
-
.trimEnd();
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
/**
|
|
148
|
-
* Writes an error's stack to stderr, verbatim and uncolored. It is diagnostic
|
|
149
|
-
* output for whoever set `SOUS_DEBUG`, never part of what the command prints.
|
|
150
|
-
*
|
|
151
|
-
* @param error - The error whose stack to write.
|
|
152
|
-
*/
|
|
153
|
-
function writeStackToStderr(error: Error): void {
|
|
154
|
-
const stack = error.stack ?? "";
|
|
155
|
-
if (stack.trim() === "") return;
|
|
156
|
-
process.stderr.write(`${stack}\n`);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
/**
|
|
160
|
-
* The exit code a failure ends with: the one oclif assigned when it assigned
|
|
161
|
-
* one (a parse error exits 2), and 1 for everything else.
|
|
162
|
-
*
|
|
163
|
-
* @param error - The error being reported.
|
|
164
|
-
*/
|
|
165
|
-
function exitCodeOf(error: Error & { exitCode?: number; oclif?: { exit?: number | false } }): number {
|
|
166
|
-
const fromOclif = error.oclif?.exit;
|
|
167
|
-
if (typeof fromOclif === "number" && Number.isInteger(fromOclif)) return fromOclif;
|
|
168
|
-
if (typeof error.exitCode === "number" && Number.isInteger(error.exitCode)) return error.exitCode;
|
|
169
|
-
return 1;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
/**
|
|
173
|
-
* True when the command is rendering machine-readable JSON, in which case oclif
|
|
174
|
-
* owns the error output and sous must not print a human error over it.
|
|
175
|
-
*
|
|
176
|
-
* @param command - The command that failed.
|
|
177
|
-
*/
|
|
178
|
-
function jsonRequested(command: Command): boolean {
|
|
179
|
-
const enabled = (command as { jsonEnabled?: () => boolean }).jsonEnabled;
|
|
180
|
-
if (typeof enabled !== "function") return false;
|
|
181
|
-
try {
|
|
182
|
-
return enabled.call(command) === true;
|
|
183
|
-
} catch {
|
|
184
|
-
return false;
|
|
185
|
-
}
|
|
186
|
-
}
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Printing a command's own help underneath an error.
|
|
3
|
-
*
|
|
4
|
-
* A run that failed because a question could not be asked should show the
|
|
5
|
-
* caller every flag that would have answered it, without sending them off to
|
|
6
|
-
* `sous help <command>`. The same goes for a run that failed because the
|
|
7
|
-
* command line itself was wrong. `utils/command-errors.ts` decides when a
|
|
8
|
-
* failure earns a help screen; this module draws it, so every command in sous
|
|
9
|
-
* draws exactly the same one.
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
import { loadHelpClass, type Command } from "@oclif/core";
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Prints one command's help, always to stderr.
|
|
16
|
-
*
|
|
17
|
-
* An error is not output, and a command whose stdout is being piped must not
|
|
18
|
-
* have a help screen spliced into its stream. oclif's help writes to stdout, so
|
|
19
|
-
* stdout is pointed at stderr for the duration and put back afterwards. The help
|
|
20
|
-
* class is the one oclif itself uses for `--help`, `-h` and the `help` command,
|
|
21
|
-
* so all four routes draw the same screen.
|
|
22
|
-
*
|
|
23
|
-
* Drawing the help is a courtesy: a failure to draw it must never replace the
|
|
24
|
-
* error that is actually being reported, so everything here is swallowed.
|
|
25
|
-
*
|
|
26
|
-
* @param command - The command whose help to draw.
|
|
27
|
-
*/
|
|
28
|
-
export async function printCommandHelpToStderr(command: Command): Promise<void> {
|
|
29
|
-
const writeToStdout = process.stdout.write.bind(process.stdout);
|
|
30
|
-
process.stdout.write = ((chunk: string | Uint8Array, ...rest: unknown[]) =>
|
|
31
|
-
(process.stderr.write as (...args: unknown[]) => boolean)(
|
|
32
|
-
chunk,
|
|
33
|
-
...rest
|
|
34
|
-
)) as typeof process.stdout.write;
|
|
35
|
-
|
|
36
|
-
try {
|
|
37
|
-
const HelpClass = await loadHelpClass(command.config);
|
|
38
|
-
const help = new HelpClass(command.config);
|
|
39
|
-
await help.showHelp([command.id ?? ""]);
|
|
40
|
-
} catch {
|
|
41
|
-
// The help screen is a courtesy; never let it replace the real error.
|
|
42
|
-
} finally {
|
|
43
|
-
process.stdout.write = writeToStdout;
|
|
44
|
-
}
|
|
45
|
-
}
|
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The yes-or-no question sous asks for a recipe variable that holds a boolean.
|
|
3
|
-
*
|
|
4
|
-
* It behaves like the stock `confirm` prompt (the y and n keys answer it, Enter
|
|
5
|
-
* alone accepts the default, which is the capitalised letter) with the one
|
|
6
|
-
* addition every sous question shares: Tab resolves the question with
|
|
7
|
-
* `{ kind: "advanced" }`, so the caller can show the advanced view and then ask
|
|
8
|
-
* the same question again. Built on the public `@inquirer/core` hooks only.
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import {
|
|
12
|
-
createPrompt,
|
|
13
|
-
isEnterKey,
|
|
14
|
-
isTabKey,
|
|
15
|
-
useKeypress,
|
|
16
|
-
usePrefix,
|
|
17
|
-
useState,
|
|
18
|
-
type Status,
|
|
19
|
-
} from "@inquirer/core";
|
|
20
|
-
import { promptBottom } from "./formatting.js";
|
|
21
|
-
|
|
22
|
-
/** What the yes-or-no question needs in order to ask itself. */
|
|
23
|
-
export interface ConfirmPromptConfig {
|
|
24
|
-
/** The one-line question. */
|
|
25
|
-
message: string;
|
|
26
|
-
/** The answer Enter alone accepts. Defaults to yes. */
|
|
27
|
-
default?: boolean;
|
|
28
|
-
/** A line under the question: the key legend, in the stock prompts' style. */
|
|
29
|
-
hint?: string;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/** How the question ended: with an answer, or with a request for the advanced view. */
|
|
33
|
-
export type ConfirmPromptResult = { kind: "value"; value: boolean } | { kind: "advanced" };
|
|
34
|
-
|
|
35
|
-
/** Everything the renderer draws, so it can be tested without a terminal. */
|
|
36
|
-
export interface ConfirmPromptView {
|
|
37
|
-
/** The prompt prefix (the question mark, or the check mark once answered). */
|
|
38
|
-
prefix: string;
|
|
39
|
-
/** The one-line question. */
|
|
40
|
-
message: string;
|
|
41
|
-
/** The answer Enter alone accepts. Defaults to yes. */
|
|
42
|
-
default?: boolean;
|
|
43
|
-
/** The answer given, once there is one. */
|
|
44
|
-
answer?: boolean;
|
|
45
|
-
/** A line under the question: the key legend, in the stock prompts' style. */
|
|
46
|
-
hint?: string;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Draws the question: prefix, message and the two keys with the default
|
|
51
|
-
* capitalised, replaced by the answer itself once one is given, plus the hint
|
|
52
|
-
* on the line underneath. A yes or no is never a secret, so nothing here is
|
|
53
|
-
* ever masked.
|
|
54
|
-
*
|
|
55
|
-
* @param view - The prompt's state.
|
|
56
|
-
* @returns The question line and the line under it.
|
|
57
|
-
*/
|
|
58
|
-
export function renderConfirmPrompt(view: ConfirmPromptView): [string, string] {
|
|
59
|
-
const fallback = view.default ?? true;
|
|
60
|
-
const keys = fallback ? "(Y/n)" : "(y/N)";
|
|
61
|
-
const tail = view.answer === undefined ? keys : view.answer ? "yes" : "no";
|
|
62
|
-
const line = `${view.prefix} ${view.message} ${tail}`.trimEnd();
|
|
63
|
-
|
|
64
|
-
if (view.answer !== undefined) return [line, ""];
|
|
65
|
-
return [line, promptBottom(view.hint ?? "")];
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/** Asks one yes-or-no question, with Tab bound to the advanced view. */
|
|
69
|
-
export const confirmPrompt = createPrompt<ConfirmPromptResult, ConfirmPromptConfig>(
|
|
70
|
-
(config, done) => {
|
|
71
|
-
const [status, setStatus] = useState<Status>("idle");
|
|
72
|
-
const [answer, setAnswer] = useState<boolean | undefined>(undefined);
|
|
73
|
-
const prefix = usePrefix({ status });
|
|
74
|
-
|
|
75
|
-
/** Settles the question with one answer, and draws it in place of the keys. */
|
|
76
|
-
const settle = (value: boolean): void => {
|
|
77
|
-
setAnswer(value);
|
|
78
|
-
setStatus("done");
|
|
79
|
-
done({ kind: "value", value });
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
useKeypress((key, rl) => {
|
|
83
|
-
if (status !== "idle") return;
|
|
84
|
-
// Readline echoes whatever was pressed into its buffer; take it back out.
|
|
85
|
-
rl.clearLine(0);
|
|
86
|
-
|
|
87
|
-
if (isTabKey(key)) {
|
|
88
|
-
setStatus("done");
|
|
89
|
-
done({ kind: "advanced" });
|
|
90
|
-
return;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
if (isEnterKey(key)) {
|
|
94
|
-
settle(config.default ?? true);
|
|
95
|
-
return;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
if (key.name === "y") settle(true);
|
|
99
|
-
else if (key.name === "n") settle(false);
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
return renderConfirmPrompt({
|
|
103
|
-
prefix,
|
|
104
|
-
message: config.message,
|
|
105
|
-
...(config.default === undefined ? {} : { default: config.default }),
|
|
106
|
-
...(answer === undefined ? {} : { answer }),
|
|
107
|
-
...(config.hint === undefined ? {} : { hint: config.hint }),
|
|
108
|
-
});
|
|
109
|
-
}
|
|
110
|
-
);
|