@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,374 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `sous vars ask [name]`.
|
|
3
|
+
*
|
|
4
|
+
* Asks the questions the project's variable definitions imply and stores the
|
|
5
|
+
* answers in `.sous/.env` or `.sous/.env.local`. By default it asks only what
|
|
6
|
+
* is unanswered or no longer fits; `--all` asks everything again.
|
|
7
|
+
*
|
|
8
|
+
* The optional name is a reference, resolved through `src/lib/refs/` exactly as
|
|
9
|
+
* every other command resolves one. It may name a variable, an environment
|
|
10
|
+
* variable that answers one, a recipe, a namespace or a repository, at any
|
|
11
|
+
* level of qualification; naming anything larger than a variable asks every
|
|
12
|
+
* question that thing publishes:
|
|
13
|
+
*
|
|
14
|
+
* sous vars ask taskFileRoot one variable
|
|
15
|
+
* sous vars ask SOUS_VAR_TASK_FILE_ROOT the variable that name answers
|
|
16
|
+
* sous vars ask task-files every question one recipe asks
|
|
17
|
+
* sous vars ask workflow every question one namespace asks
|
|
18
|
+
* sous vars ask sous-recipes every question one repository asks
|
|
19
|
+
*
|
|
20
|
+
* `--repo`, `--namespace` and `--var` say outright which kind of thing is
|
|
21
|
+
* meant, and narrow the same way. A reference that means more than one thing is
|
|
22
|
+
* offered as a list to choose between; `--accept-first` takes the first, and a
|
|
23
|
+
* run with no terminal fails naming that flag.
|
|
24
|
+
*
|
|
25
|
+
* `--file` reads a standalone definitions file instead of the project's
|
|
26
|
+
* recipes, which is how a project asks questions no recipe publishes yet.
|
|
27
|
+
*
|
|
28
|
+
* Answers can be supplied ahead of the questions with `--answer name=value` or
|
|
29
|
+
* `--answers-file <path>`; whatever they answer is stored before anything is
|
|
30
|
+
* asked, and only what is left over is asked for.
|
|
31
|
+
*
|
|
32
|
+
* Without a terminal the command never hangs waiting on a prompt: it fails and
|
|
33
|
+
* names the exact environment variables that would answer each question.
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
import path from "node:path";
|
|
37
|
+
import { Args, Flags } from "@oclif/core";
|
|
38
|
+
import { BaseCommand } from "../../base-command.js";
|
|
39
|
+
import { ConfigError } from "../../lib/errors.js";
|
|
40
|
+
import {
|
|
41
|
+
ALL_SCOPES,
|
|
42
|
+
SousScope,
|
|
43
|
+
findNamespace,
|
|
44
|
+
findReference,
|
|
45
|
+
findRepository,
|
|
46
|
+
findVariable,
|
|
47
|
+
pickReference,
|
|
48
|
+
referenceContextFromVariables,
|
|
49
|
+
variableReferenceKey,
|
|
50
|
+
type ReferenceMatch,
|
|
51
|
+
} from "../../lib/refs/index.js";
|
|
52
|
+
import {
|
|
53
|
+
applyProvidedAnswers,
|
|
54
|
+
askForMissing,
|
|
55
|
+
collectProvidedAnswers,
|
|
56
|
+
FileDefinitionSource,
|
|
57
|
+
formatAskReport,
|
|
58
|
+
loadLadderContext,
|
|
59
|
+
loadProjectDefinitions,
|
|
60
|
+
unknownAnswerError,
|
|
61
|
+
type DefinedVariable,
|
|
62
|
+
} from "../../lib/vars/index.js";
|
|
63
|
+
import type { LadderContext } from "../../lib/vars/ladder.js";
|
|
64
|
+
import {
|
|
65
|
+
blankLine,
|
|
66
|
+
dryRunNotice,
|
|
67
|
+
footer,
|
|
68
|
+
heading,
|
|
69
|
+
indent,
|
|
70
|
+
log,
|
|
71
|
+
showCommandVars,
|
|
72
|
+
} from "../../utils/formatting.js";
|
|
73
|
+
import { answerFlags } from "../../utils/flags.js";
|
|
74
|
+
|
|
75
|
+
/** What the caller said should be asked: the reference, and the three flags. */
|
|
76
|
+
type Selection = {
|
|
77
|
+
/** The reference argument, when one was given. */
|
|
78
|
+
name?: string;
|
|
79
|
+
/** The repository named by `--repo`. */
|
|
80
|
+
repo?: string;
|
|
81
|
+
/** The namespace named by `--namespace`. */
|
|
82
|
+
namespace?: string;
|
|
83
|
+
/** Every variable named by `--var`. */
|
|
84
|
+
vars?: string[];
|
|
85
|
+
/** Take the first match rather than asking which was meant. */
|
|
86
|
+
acceptFirst: boolean;
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
/** The line every "this matched nothing" error ends with. */
|
|
90
|
+
const SEE_ALL = " Run 'sous vars' to see every variable this project's recipes define.";
|
|
91
|
+
|
|
92
|
+
export default class VarsAsk extends BaseCommand {
|
|
93
|
+
static description =
|
|
94
|
+
"Answer the variables this project's recipes define, storing the answers in the .sous env files";
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* The other spelling of the topic. It lives under a hidden topic, so it is
|
|
98
|
+
* typable everywhere without ever reaching the top-level listing.
|
|
99
|
+
*/
|
|
100
|
+
static aliases = ["var:ask"];
|
|
101
|
+
|
|
102
|
+
static examples = [
|
|
103
|
+
"<%= config.bin %> vars ask",
|
|
104
|
+
"<%= config.bin %> vars ask apiUrl",
|
|
105
|
+
"<%= config.bin %> vars ask SOUS_VAR_API_URL",
|
|
106
|
+
"<%= config.bin %> vars ask workflow/task-files",
|
|
107
|
+
"<%= config.bin %> vars ask workflow --accept-first",
|
|
108
|
+
"<%= config.bin %> vars ask --namespace workflow --var apiUrl",
|
|
109
|
+
"<%= config.bin %> vars ask --all",
|
|
110
|
+
"<%= config.bin %> vars ask --file ./questions.yaml",
|
|
111
|
+
"<%= config.bin %> vars ask --answer apiUrl=https://api.example.com",
|
|
112
|
+
"<%= config.bin %> vars ask --answers-file ./answers.yaml",
|
|
113
|
+
];
|
|
114
|
+
|
|
115
|
+
static args = {
|
|
116
|
+
name: Args.string({
|
|
117
|
+
description:
|
|
118
|
+
"What to ask about: a variable, an environment variable name, a recipe, a namespace or a repository",
|
|
119
|
+
required: false,
|
|
120
|
+
}),
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
static flags = {
|
|
124
|
+
...BaseCommand.baseFlags,
|
|
125
|
+
all: Flags.boolean({
|
|
126
|
+
description: "Ask every variable again, including the ones already answered",
|
|
127
|
+
default: false,
|
|
128
|
+
}),
|
|
129
|
+
repo: Flags.string({
|
|
130
|
+
description: "Ask only the variables published by this repository",
|
|
131
|
+
}),
|
|
132
|
+
namespace: Flags.string({
|
|
133
|
+
description: "Ask only the variables published in this namespace",
|
|
134
|
+
}),
|
|
135
|
+
var: Flags.string({
|
|
136
|
+
description: "Ask only this variable. Repeat it for each variable",
|
|
137
|
+
multiple: true,
|
|
138
|
+
}),
|
|
139
|
+
"accept-first": Flags.boolean({
|
|
140
|
+
description: "When a name matches several things, take the first one listed",
|
|
141
|
+
default: false,
|
|
142
|
+
}),
|
|
143
|
+
file: Flags.string({
|
|
144
|
+
description:
|
|
145
|
+
"Read the variable definitions from a standalone definitions file instead of the project's recipes",
|
|
146
|
+
}),
|
|
147
|
+
"dry-run": Flags.boolean({
|
|
148
|
+
description: "Report what would be asked and written, without writing anything",
|
|
149
|
+
default: false,
|
|
150
|
+
}),
|
|
151
|
+
...answerFlags(),
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
async run(): Promise<void> {
|
|
155
|
+
const { args, flags } = await this.parse(VarsAsk);
|
|
156
|
+
const dryRun = flags["dry-run"];
|
|
157
|
+
|
|
158
|
+
const selection: Selection = {
|
|
159
|
+
...(args.name === undefined ? {} : { name: args.name }),
|
|
160
|
+
...(flags.repo === undefined ? {} : { repo: flags.repo }),
|
|
161
|
+
...(flags.namespace === undefined ? {} : { namespace: flags.namespace }),
|
|
162
|
+
...(flags.var === undefined ? {} : { vars: flags.var }),
|
|
163
|
+
acceptFirst: flags["accept-first"],
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
showCommandVars({
|
|
167
|
+
Project: this.projectLabel,
|
|
168
|
+
Config: this.configContext.configPath,
|
|
169
|
+
Definitions: flags.file ?? "the project's subscribed recipes",
|
|
170
|
+
Asking: describeSelection(selection, flags.all),
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
if (dryRun) dryRunNotice("No answers will be written.");
|
|
174
|
+
|
|
175
|
+
const provided = collectProvidedAnswers({
|
|
176
|
+
...(flags.answer === undefined ? {} : { answer: flags.answer }),
|
|
177
|
+
...(flags["answers-file"] === undefined
|
|
178
|
+
? {}
|
|
179
|
+
: { answersFile: flags["answers-file"] }),
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
const source =
|
|
183
|
+
flags.file === undefined
|
|
184
|
+
? loadProjectDefinitions(this.settings, this.configContext.sousDir)
|
|
185
|
+
: new FileDefinitionSource(path.resolve(process.cwd(), flags.file));
|
|
186
|
+
const defined = await source.load();
|
|
187
|
+
|
|
188
|
+
heading("Answering variables");
|
|
189
|
+
blankLine();
|
|
190
|
+
|
|
191
|
+
if (defined.length === 0) {
|
|
192
|
+
// A supplied answer names a variable nothing declares, which is a typo
|
|
193
|
+
// until proven otherwise; it fails rather than passing unnoticed.
|
|
194
|
+
if (provided[0] !== undefined) throw unknownAnswerError(provided[0], defined);
|
|
195
|
+
|
|
196
|
+
log(
|
|
197
|
+
indent(
|
|
198
|
+
"No recipe in this project defines any variables yet, so there is nothing " +
|
|
199
|
+
"to ask. Subscribe to a recipe that publishes some, or read a definitions " +
|
|
200
|
+
"file with --file."
|
|
201
|
+
)
|
|
202
|
+
);
|
|
203
|
+
footer();
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const context = loadLadderContext({
|
|
208
|
+
sousDir: this.configContext.sousDir,
|
|
209
|
+
settings: this.settings,
|
|
210
|
+
shellEnv: this.shellEnv,
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
// What the reference and the flags name, as the fully qualified reference of
|
|
214
|
+
// every variable to ask. Undefined means everything the usual rules pick.
|
|
215
|
+
const only = await this.resolveSelection(defined, context, selection);
|
|
216
|
+
|
|
217
|
+
const askOptions = {
|
|
218
|
+
sousDir: this.configContext.sousDir,
|
|
219
|
+
// A project whose conf.d directory has never existed still gets one when
|
|
220
|
+
// a mapping record needs writing; the writer creates it.
|
|
221
|
+
confDir:
|
|
222
|
+
this.configContext.confDir ?? path.join(this.configContext.sousDir, "conf.d"),
|
|
223
|
+
interactive: this.interactive,
|
|
224
|
+
dryRun,
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
// Answers supplied on the command line are validated and stored before any
|
|
228
|
+
// question is asked, so what is left is exactly what nobody answered.
|
|
229
|
+
const supplied = applyProvidedAnswers(defined, provided, context, askOptions);
|
|
230
|
+
|
|
231
|
+
const report = await askForMissing(defined, context, {
|
|
232
|
+
...askOptions,
|
|
233
|
+
...(only === undefined ? {} : { only }),
|
|
234
|
+
skip: supplied.keys,
|
|
235
|
+
reask: flags.all,
|
|
236
|
+
});
|
|
237
|
+
report.answered.unshift(...supplied.stored);
|
|
238
|
+
|
|
239
|
+
blankLine();
|
|
240
|
+
for (const line of formatAskReport(report, dryRun)) {
|
|
241
|
+
log(line === "" ? "" : indent(line));
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
footer();
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Turns the reference and the three narrowing flags into the exact set of
|
|
249
|
+
* variables to ask, as fully qualified references.
|
|
250
|
+
*
|
|
251
|
+
* Each step narrows the pool and the next step resolves against what is left,
|
|
252
|
+
* so `--namespace workflow apiUrl` asks about the `apiUrl` of that namespace
|
|
253
|
+
* even when another namespace declares one too. A step that narrows the pool
|
|
254
|
+
* to nothing is an error naming what did it, rather than a run that silently
|
|
255
|
+
* asks no questions.
|
|
256
|
+
*
|
|
257
|
+
* @param defined - Every variable definition in play.
|
|
258
|
+
* @param ladder - The environment layers, for resolving environment variable names.
|
|
259
|
+
* @param selection - The reference and the flags, as the caller wrote them.
|
|
260
|
+
* @returns The variables to ask, or undefined when nothing narrowed anything.
|
|
261
|
+
*/
|
|
262
|
+
private async resolveSelection(
|
|
263
|
+
defined: DefinedVariable[],
|
|
264
|
+
ladder: LadderContext,
|
|
265
|
+
selection: Selection
|
|
266
|
+
): Promise<string[] | undefined> {
|
|
267
|
+
const { name, repo, namespace, vars } = selection;
|
|
268
|
+
if (name === undefined && repo === undefined && namespace === undefined && vars === undefined) {
|
|
269
|
+
return undefined;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
let pool = defined;
|
|
273
|
+
|
|
274
|
+
if (repo !== undefined) {
|
|
275
|
+
const match = await this.pick(findRepository(repo, contextOf(pool, ladder)), repo, selection);
|
|
276
|
+
pool = pool.filter((entry) => entry.recipe.repo === match.repo);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if (namespace !== undefined) {
|
|
280
|
+
const match = await this.pick(
|
|
281
|
+
findNamespace(namespace, contextOf(pool, ladder)),
|
|
282
|
+
namespace,
|
|
283
|
+
selection
|
|
284
|
+
);
|
|
285
|
+
pool = narrowTo(pool, match);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
if (name !== undefined) {
|
|
289
|
+
const match = await this.pick(
|
|
290
|
+
findReference(name, ALL_SCOPES, contextOf(pool, ladder)),
|
|
291
|
+
name,
|
|
292
|
+
selection
|
|
293
|
+
);
|
|
294
|
+
pool = narrowTo(pool, match);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
if (vars !== undefined) {
|
|
298
|
+
const chosen: DefinedVariable[] = [];
|
|
299
|
+
for (const wanted of vars) {
|
|
300
|
+
const match = await this.pick(
|
|
301
|
+
findVariable(wanted, contextOf(pool, ladder)),
|
|
302
|
+
wanted,
|
|
303
|
+
selection
|
|
304
|
+
);
|
|
305
|
+
chosen.push(...narrowTo(pool, match));
|
|
306
|
+
}
|
|
307
|
+
pool = chosen;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
if (pool.length === 0) {
|
|
311
|
+
throw new ConfigError(
|
|
312
|
+
`Nothing is left to ask: ${describeSelection(selection, false)} names no ` +
|
|
313
|
+
`variable this project holds.\n${SEE_ALL}`
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
return [...new Set(pool.map(variableReferenceKey))];
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Settles which of the things a reference could have meant this run proceeds
|
|
322
|
+
* with, through the rule every command shares.
|
|
323
|
+
*
|
|
324
|
+
* @param matches - What the reference could have meant, in listing order.
|
|
325
|
+
* @param search - The reference exactly as it was written.
|
|
326
|
+
* @param selection - The selection, read for `--accept-first`.
|
|
327
|
+
*/
|
|
328
|
+
private async pick(
|
|
329
|
+
matches: ReferenceMatch[],
|
|
330
|
+
search: string,
|
|
331
|
+
selection: Selection
|
|
332
|
+
): Promise<ReferenceMatch> {
|
|
333
|
+
return pickReference(matches, {
|
|
334
|
+
search,
|
|
335
|
+
interactive: this.interactive,
|
|
336
|
+
acceptFirst: selection.acceptFirst,
|
|
337
|
+
announce: false,
|
|
338
|
+
details: [SEE_ALL],
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/** The variables one match covers: a whole repository, namespace, recipe, or one variable. */
|
|
344
|
+
function narrowTo(pool: DefinedVariable[], match: ReferenceMatch): DefinedVariable[] {
|
|
345
|
+
return pool.filter((entry) => {
|
|
346
|
+
if (match.repo !== undefined && entry.recipe.repo !== match.repo) return false;
|
|
347
|
+
if (match.scope === SousScope.Repository) return true;
|
|
348
|
+
|
|
349
|
+
if (match.namespace !== undefined && entry.recipe.namespace !== match.namespace) return false;
|
|
350
|
+
if (match.scope === SousScope.Namespace) return true;
|
|
351
|
+
|
|
352
|
+
if (match.recipe !== undefined && entry.recipe.name !== match.recipe) return false;
|
|
353
|
+
if (match.scope === SousScope.Recipe) return true;
|
|
354
|
+
|
|
355
|
+
return entry.definition.name === match.variable;
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/** The reference context for the variables still in the running. */
|
|
360
|
+
function contextOf(pool: DefinedVariable[], ladder: LadderContext) {
|
|
361
|
+
return referenceContextFromVariables(pool, ladder);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/** What this run is asking about, in the words the header shows. */
|
|
365
|
+
function describeSelection(selection: Selection, all: boolean): string {
|
|
366
|
+
const parts: string[] = [];
|
|
367
|
+
if (selection.repo !== undefined) parts.push(`the repository '${selection.repo}'`);
|
|
368
|
+
if (selection.namespace !== undefined) parts.push(`the namespace '${selection.namespace}'`);
|
|
369
|
+
if (selection.name !== undefined) parts.push(`'${selection.name}'`);
|
|
370
|
+
for (const wanted of selection.vars ?? []) parts.push(`the variable '${wanted}'`);
|
|
371
|
+
|
|
372
|
+
if (parts.length === 0) return all ? "every variable" : "everything unanswered";
|
|
373
|
+
return parts.join(", ");
|
|
374
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bare `sous vars`, and `sous vars <name>`.
|
|
3
|
+
*
|
|
4
|
+
* The canonical commands are `sous vars list` and `sous vars show <name>`; this
|
|
5
|
+
* is the shorthand that keeps working, so it prints the listing with no argument
|
|
6
|
+
* and the detail with one. It is hidden from the command list so `sous --help`
|
|
7
|
+
* names `vars` once, as a topic.
|
|
8
|
+
*
|
|
9
|
+
* The optional argument works because oclif collates leading space-separated
|
|
10
|
+
* words into a command id only while they keep matching a real command:
|
|
11
|
+
* `sous vars show` finds the `vars:show` command, and `sous vars apiUrl` does
|
|
12
|
+
* not, so it lands here with `apiUrl` as the argument. The one consequence is
|
|
13
|
+
* that a variable named `list`, `show` or `ask` cannot be reached this way;
|
|
14
|
+
* `sous vars show list` reaches it.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import path from "node:path";
|
|
18
|
+
import { Args, Flags } from "@oclif/core";
|
|
19
|
+
import { BaseCommand } from "../../base-command.js";
|
|
20
|
+
import {
|
|
21
|
+
FileDefinitionSource,
|
|
22
|
+
loadLadderContext,
|
|
23
|
+
loadProjectDefinitions,
|
|
24
|
+
printVariableDetail,
|
|
25
|
+
printVariableList,
|
|
26
|
+
} from "../../lib/vars/index.js";
|
|
27
|
+
import { footer, showCommandVars } from "../../utils/formatting.js";
|
|
28
|
+
|
|
29
|
+
export default class Vars extends BaseCommand {
|
|
30
|
+
static description = "List every variable this project's recipes define, with its answer";
|
|
31
|
+
|
|
32
|
+
/** Hidden so `sous --help` names `vars` once, as a topic. */
|
|
33
|
+
static hidden = true;
|
|
34
|
+
|
|
35
|
+
static examples = ["<%= config.bin %> vars", "<%= config.bin %> vars apiUrl"];
|
|
36
|
+
|
|
37
|
+
static args = {
|
|
38
|
+
name: Args.string({
|
|
39
|
+
description:
|
|
40
|
+
"A variable's name (or its namespace/recipe.name key) to show in full",
|
|
41
|
+
required: false,
|
|
42
|
+
}),
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
static flags = {
|
|
46
|
+
...BaseCommand.baseFlags,
|
|
47
|
+
file: Flags.string({
|
|
48
|
+
description:
|
|
49
|
+
"Read the variable definitions from a standalone definitions file instead of the project's recipes",
|
|
50
|
+
}),
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
async run(): Promise<void> {
|
|
54
|
+
const { args, flags } = await this.parse(Vars);
|
|
55
|
+
|
|
56
|
+
showCommandVars({
|
|
57
|
+
Project: this.projectLabel,
|
|
58
|
+
Config: this.configContext.configPath,
|
|
59
|
+
Definitions: flags.file ?? "the project's subscribed recipes",
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
const source =
|
|
63
|
+
flags.file === undefined
|
|
64
|
+
? loadProjectDefinitions(this.settings, this.configContext.sousDir)
|
|
65
|
+
: new FileDefinitionSource(path.resolve(process.cwd(), flags.file));
|
|
66
|
+
const defined = await source.load();
|
|
67
|
+
|
|
68
|
+
const context = loadLadderContext({
|
|
69
|
+
sousDir: this.configContext.sousDir,
|
|
70
|
+
settings: this.settings,
|
|
71
|
+
shellEnv: this.shellEnv,
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
if (args.name === undefined) printVariableList(defined, context);
|
|
75
|
+
else printVariableDetail(defined, context, args.name);
|
|
76
|
+
|
|
77
|
+
footer();
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `sous vars list`.
|
|
3
|
+
*
|
|
4
|
+
* Prints every variable in play: what it is called, which recipe published it,
|
|
5
|
+
* the environment variable that answered it, the value (hidden when the
|
|
6
|
+
* definition says the variable is a secret), and where the value came from.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import path from "node:path";
|
|
10
|
+
import { Flags } from "@oclif/core";
|
|
11
|
+
import { BaseCommand } from "../../base-command.js";
|
|
12
|
+
import {
|
|
13
|
+
FileDefinitionSource,
|
|
14
|
+
loadLadderContext,
|
|
15
|
+
loadProjectDefinitions,
|
|
16
|
+
printVariableList,
|
|
17
|
+
} from "../../lib/vars/index.js";
|
|
18
|
+
import { footer, showCommandVars } from "../../utils/formatting.js";
|
|
19
|
+
|
|
20
|
+
export default class VarsList extends BaseCommand {
|
|
21
|
+
static description = "List every variable this project's recipes define, with its answer";
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* The other spelling of the topic. It lives under a hidden topic, so it is
|
|
25
|
+
* typable everywhere without ever reaching the top-level listing.
|
|
26
|
+
*/
|
|
27
|
+
static aliases = ["var:list"];
|
|
28
|
+
|
|
29
|
+
static examples = [
|
|
30
|
+
"<%= config.bin %> vars list",
|
|
31
|
+
"<%= config.bin %> vars list --file ./questions.yaml",
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
static flags = {
|
|
35
|
+
...BaseCommand.baseFlags,
|
|
36
|
+
file: Flags.string({
|
|
37
|
+
description:
|
|
38
|
+
"Read the variable definitions from a standalone definitions file instead of the project's recipes",
|
|
39
|
+
}),
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
async run(): Promise<void> {
|
|
43
|
+
const { flags } = await this.parse(VarsList);
|
|
44
|
+
|
|
45
|
+
showCommandVars({
|
|
46
|
+
Project: this.projectLabel,
|
|
47
|
+
Config: this.configContext.configPath,
|
|
48
|
+
Definitions: flags.file ?? "the project's subscribed recipes",
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
const source =
|
|
52
|
+
flags.file === undefined
|
|
53
|
+
? loadProjectDefinitions(this.settings, this.configContext.sousDir)
|
|
54
|
+
: new FileDefinitionSource(path.resolve(process.cwd(), flags.file));
|
|
55
|
+
|
|
56
|
+
printVariableList(
|
|
57
|
+
await source.load(),
|
|
58
|
+
loadLadderContext({
|
|
59
|
+
sousDir: this.configContext.sousDir,
|
|
60
|
+
settings: this.settings,
|
|
61
|
+
shellEnv: this.shellEnv,
|
|
62
|
+
})
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
footer();
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `sous vars show <name>`.
|
|
3
|
+
*
|
|
4
|
+
* Prints everything about one variable: the question it asks, its documentation
|
|
5
|
+
* and constraints, the recipe that published it, where an answer would be
|
|
6
|
+
* stored, every environment variable name on the resolution ladder, and which
|
|
7
|
+
* rung actually answered.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import path from "node:path";
|
|
11
|
+
import { Args, Flags } from "@oclif/core";
|
|
12
|
+
import { BaseCommand } from "../../base-command.js";
|
|
13
|
+
import {
|
|
14
|
+
FileDefinitionSource,
|
|
15
|
+
loadLadderContext,
|
|
16
|
+
loadProjectDefinitions,
|
|
17
|
+
printVariableDetail,
|
|
18
|
+
} from "../../lib/vars/index.js";
|
|
19
|
+
import { footer, showCommandVars } from "../../utils/formatting.js";
|
|
20
|
+
|
|
21
|
+
export default class VarsShow extends BaseCommand {
|
|
22
|
+
static description = "Show everything about one variable, including how it was answered";
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* The other spelling of the topic. It lives under a hidden topic, so it is
|
|
26
|
+
* typable everywhere without ever reaching the top-level listing.
|
|
27
|
+
*/
|
|
28
|
+
static aliases = ["var:show"];
|
|
29
|
+
|
|
30
|
+
static examples = [
|
|
31
|
+
"<%= config.bin %> vars show apiUrl",
|
|
32
|
+
"<%= config.bin %> vars show workflow/task-files.apiUrl",
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
static args = {
|
|
36
|
+
name: Args.string({
|
|
37
|
+
description: "A variable's name, or its namespace/recipe.name key",
|
|
38
|
+
required: true,
|
|
39
|
+
}),
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
static flags = {
|
|
43
|
+
...BaseCommand.baseFlags,
|
|
44
|
+
file: Flags.string({
|
|
45
|
+
description:
|
|
46
|
+
"Read the variable definitions from a standalone definitions file instead of the project's recipes",
|
|
47
|
+
}),
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
async run(): Promise<void> {
|
|
51
|
+
const { args, flags } = await this.parse(VarsShow);
|
|
52
|
+
|
|
53
|
+
showCommandVars({
|
|
54
|
+
Project: this.projectLabel,
|
|
55
|
+
Config: this.configContext.configPath,
|
|
56
|
+
Definitions: flags.file ?? "the project's subscribed recipes",
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
const source =
|
|
60
|
+
flags.file === undefined
|
|
61
|
+
? loadProjectDefinitions(this.settings, this.configContext.sousDir)
|
|
62
|
+
: new FileDefinitionSource(path.resolve(process.cwd(), flags.file));
|
|
63
|
+
|
|
64
|
+
printVariableDetail(
|
|
65
|
+
await source.load(),
|
|
66
|
+
loadLadderContext({
|
|
67
|
+
sousDir: this.configContext.sousDir,
|
|
68
|
+
settings: this.settings,
|
|
69
|
+
shellEnv: this.shellEnv,
|
|
70
|
+
}),
|
|
71
|
+
args.name,
|
|
72
|
+
{ sousDir: this.configContext.sousDir }
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
footer();
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { BaseCommand } from "./base-command.js";
|
|
2
|
+
import { headerTo } from "./utils/formatting.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Base class for the `sous config *` inspection commands (`show`, `get`).
|
|
6
|
+
*
|
|
7
|
+
* These commands emit machine-readable output (JSON, or a raw scalar) to stdout
|
|
8
|
+
* so `sous config show | jq` and friends work. The decorative CLI header would
|
|
9
|
+
* otherwise land on stdout during BaseCommand.init() and corrupt that output, so
|
|
10
|
+
* it is routed to stderr here instead. Discovery, env loading, flag parsing and
|
|
11
|
+
* ConfigError rendering are all inherited unchanged from BaseCommand.
|
|
12
|
+
*/
|
|
13
|
+
export abstract class ConfigCommand extends BaseCommand {
|
|
14
|
+
private static writeStderr(line: string): void {
|
|
15
|
+
process.stderr.write(`${line}\n`);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
protected emitHeader(): void {
|
|
19
|
+
headerTo(ConfigCommand.writeStderr);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Route init()/catch() error rendering to stderr too. Otherwise a discovery or
|
|
24
|
+
* config-load failure (a not-found config, an invalid merged config, or a
|
|
25
|
+
* missing `config get` path) would print the red error block to stdout for the
|
|
26
|
+
* very commands whose contract is clean, machine-readable stdout — corrupting
|
|
27
|
+
* `sous config show | jq` and any script capturing stdout.
|
|
28
|
+
*/
|
|
29
|
+
protected errorSink: (line: string) => void = ConfigCommand.writeStderr;
|
|
30
|
+
}
|