@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
|
@@ -0,0 +1,143 @@
|
|
|
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
|
+
);
|
|
@@ -0,0 +1,186 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
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
|
+
);
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Flags that more than one command shares, defined once.
|
|
3
|
+
*
|
|
4
|
+
* The confirmation flag is the reason this module exists. Several commands stop
|
|
5
|
+
* and ask before they change anything (subscribing, trusting a repository,
|
|
6
|
+
* deleting every file sous wrote), and a caller who wants to answer those
|
|
7
|
+
* questions ahead of time should not have to remember which command spells it
|
|
8
|
+
* `--yes`, which spells it `--force` and which spells it `--trust`. They are one
|
|
9
|
+
* flag with several spellings, built here so the spellings cannot drift apart.
|
|
10
|
+
*
|
|
11
|
+
* Alternate spellings are real oclif flag aliases, not separate flags, so the
|
|
12
|
+
* help screen lists the flag once. The alternates are named in a dim suffix on
|
|
13
|
+
* the description, generated from the alias list rather than typed by hand.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { Flags } from "@oclif/core";
|
|
17
|
+
import { color } from "@oclif/color";
|
|
18
|
+
import type { AlphabetLowercase, AlphabetUppercase } from "@oclif/core/interfaces";
|
|
19
|
+
|
|
20
|
+
/** A short flag character, in the shape oclif wants it. */
|
|
21
|
+
export type FlagChar = AlphabetLowercase | AlphabetUppercase;
|
|
22
|
+
|
|
23
|
+
/** What the confirmation flag is called on a given command. */
|
|
24
|
+
export type ConfirmationPrimary = "yes" | "force";
|
|
25
|
+
|
|
26
|
+
/** How a command asks for the shared confirmation flag. */
|
|
27
|
+
export type ConfirmationFlagOptions = {
|
|
28
|
+
/**
|
|
29
|
+
* Which spelling is the primary one, which must match the key the flag is
|
|
30
|
+
* filed under in the command's `flags` object. Defaults to `yes`; `clear` uses
|
|
31
|
+
* `force`, because that is the spelling it has always had.
|
|
32
|
+
*/
|
|
33
|
+
primary?: ConfirmationPrimary;
|
|
34
|
+
/**
|
|
35
|
+
* Extra long spellings to accept, on top of `--yes` and `--force`. The trust
|
|
36
|
+
* ceremony reads naturally as `--trust`, so the commands that perform it pass
|
|
37
|
+
* it here.
|
|
38
|
+
*/
|
|
39
|
+
extraAliases?: string[];
|
|
40
|
+
/** Replaces the default description, which the alias suffix is still added to. */
|
|
41
|
+
description?: string;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/** What every confirmation flag says it does, before the alias suffix. */
|
|
45
|
+
export const CONFIRMATION_DESCRIPTION =
|
|
46
|
+
"Answer yes to every confirmation this command would ask";
|
|
47
|
+
|
|
48
|
+
/** The long spelling that is not the primary one, keyed by the primary one. */
|
|
49
|
+
const OTHER_SPELLING: Record<ConfirmationPrimary, string> = {
|
|
50
|
+
yes: "force",
|
|
51
|
+
force: "yes",
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/** The short character for each long spelling. */
|
|
55
|
+
const CHAR_FOR: Record<string, FlagChar> = { yes: "y", force: "f" };
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Renders the "(also -f, --force)" suffix that tells a reader every other way
|
|
59
|
+
* to spell a flag, without giving each spelling a help line of its own.
|
|
60
|
+
*
|
|
61
|
+
* Short characters come first, then long names, in the order they were given.
|
|
62
|
+
* The suffix is dim, so it sits behind the description rather than competing
|
|
63
|
+
* with it; on a stream with no colors it is plain text.
|
|
64
|
+
*
|
|
65
|
+
* @param aliases - Long alternate spellings, without leading dashes.
|
|
66
|
+
* @param charAliases - Short alternate characters, without leading dashes.
|
|
67
|
+
* @returns The suffix, including its leading space, or an empty string when
|
|
68
|
+
* there is nothing to say.
|
|
69
|
+
*/
|
|
70
|
+
export function aliasSuffix(aliases: string[] = [], charAliases: FlagChar[] = []): string {
|
|
71
|
+
const spellings = [
|
|
72
|
+
...charAliases.map((char) => `-${char}`),
|
|
73
|
+
...aliases.map((alias) => `--${alias}`),
|
|
74
|
+
];
|
|
75
|
+
if (spellings.length === 0) return "";
|
|
76
|
+
return ` ${color.dim(`(also ${spellings.join(", ")})`)}`;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* The shared confirmation flag: one boolean that answers every yes-or-no
|
|
81
|
+
* question a command would otherwise stop and ask.
|
|
82
|
+
*
|
|
83
|
+
* File it under the key named by `primary`, so `--yes` is the flag's real name
|
|
84
|
+
* on most commands and `--force` is its real name on `clear`. Either way both
|
|
85
|
+
* long spellings, both short characters, and any extra alias are accepted and
|
|
86
|
+
* behave identically.
|
|
87
|
+
*
|
|
88
|
+
* @param options - Which spelling is primary, and any extra aliases.
|
|
89
|
+
*/
|
|
90
|
+
export function confirmationFlag(options: ConfirmationFlagOptions = {}) {
|
|
91
|
+
const primary = options.primary ?? "yes";
|
|
92
|
+
const other = OTHER_SPELLING[primary];
|
|
93
|
+
const aliases = [other, ...(options.extraAliases ?? [])];
|
|
94
|
+
const charAliases: FlagChar[] = [CHAR_FOR[other]!];
|
|
95
|
+
const description = options.description ?? CONFIRMATION_DESCRIPTION;
|
|
96
|
+
|
|
97
|
+
return Flags.boolean({
|
|
98
|
+
char: CHAR_FOR[primary]!,
|
|
99
|
+
aliases,
|
|
100
|
+
charAliases,
|
|
101
|
+
description: `${description}${aliasSuffix(aliases, charAliases)}`,
|
|
102
|
+
default: false,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* The two flags that answer a recipe's questions ahead of time, on every
|
|
108
|
+
* command that can ask one.
|
|
109
|
+
*
|
|
110
|
+
* They exist for callers with no terminal: a script, a continuous integration
|
|
111
|
+
* job, or an agent that read the question plan out of a dry run and knows every
|
|
112
|
+
* answer already. Both spellings are defined here so the commands that take
|
|
113
|
+
* them cannot describe them differently. The semantics live in
|
|
114
|
+
* `lib/vars/preanswers.ts`.
|
|
115
|
+
*/
|
|
116
|
+
export function answerFlags() {
|
|
117
|
+
return {
|
|
118
|
+
answer: Flags.string({
|
|
119
|
+
description:
|
|
120
|
+
"Answer one of the questions ahead of time, written as '<name>=<value>'. Repeat it for each answer",
|
|
121
|
+
multiple: true,
|
|
122
|
+
}),
|
|
123
|
+
"answers-file": Flags.string({
|
|
124
|
+
description:
|
|
125
|
+
"Read answers from a YAML or JSON file of '<name>: <value>' pairs. An '--answer' wins over the file",
|
|
126
|
+
}),
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** What `--non-interactive` says it does, on every command that carries it. */
|
|
131
|
+
export const NON_INTERACTIVE_DESCRIPTION =
|
|
132
|
+
"Never ask a question; fail instead, naming the flag that would have answered it";
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* The global flag that turns every prompt into an error, defined once.
|
|
136
|
+
*
|
|
137
|
+
* `BaseCommand` puts it in `baseFlags`, so every command that discovers a
|
|
138
|
+
* config carries it without asking. The three repository authoring commands
|
|
139
|
+
* (`repo init`, `repo release`, `repo submit`) do not extend `BaseCommand`,
|
|
140
|
+
* because they run inside a recipe repository rather than a sous project; they
|
|
141
|
+
* add this flag themselves, so the rule reads the same everywhere.
|
|
142
|
+
*
|
|
143
|
+
* The flag's effect is not in its parsed value: `lib/interactive.ts` reads the
|
|
144
|
+
* raw command line, so a prompt is already blocked by the time a command looks
|
|
145
|
+
* at its flags. Declaring it here is what stops oclif rejecting it as unknown,
|
|
146
|
+
* and what puts it on the help screen.
|
|
147
|
+
*/
|
|
148
|
+
export function nonInteractiveFlag() {
|
|
149
|
+
return Flags.boolean({
|
|
150
|
+
description: NON_INTERACTIVE_DESCRIPTION,
|
|
151
|
+
default: false,
|
|
152
|
+
});
|
|
153
|
+
}
|