@sous-io/sous 0.1.1 → 0.2.1
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 +115 -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 +409 -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 +72 -8
- 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 +625 -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 +415 -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/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,589 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Working out what a word on the command line names.
|
|
3
|
+
*
|
|
4
|
+
* Sous commands take a reference where a person would say a name: `sous
|
|
5
|
+
* subscribe task-files`, `sous namespace show workflow`, `sous vars ask apiUrl`.
|
|
6
|
+
* One module settles what such a word means, for every command, so a reference
|
|
7
|
+
* that resolves in one place resolves the same way everywhere.
|
|
8
|
+
*
|
|
9
|
+
* A reference may be written at any level of qualification, and the fully
|
|
10
|
+
* qualified spelling is always accepted:
|
|
11
|
+
*
|
|
12
|
+
* sous-recipes a repository
|
|
13
|
+
* sous-recipes:workflow a namespace, fully qualified
|
|
14
|
+
* workflow the same namespace, bare
|
|
15
|
+
* sous-recipes:workflow/task-files a recipe, fully qualified
|
|
16
|
+
* workflow/task-files the same recipe, partly qualified
|
|
17
|
+
* task-files the same recipe, bare
|
|
18
|
+
* workflow/task-files.taskFileRoot a variable, partly qualified
|
|
19
|
+
* taskFileRoot the same variable, bare
|
|
20
|
+
* SOUS_VAR_TASK_FILE_ROOT the environment variable answering it
|
|
21
|
+
*
|
|
22
|
+
* Matching is case-sensitive, because every identifier in sous is (namespaces
|
|
23
|
+
* and recipes are lowercase kebab-case, variables are camelCase, environment
|
|
24
|
+
* variable names are upper snake case), and a case-insensitive search would
|
|
25
|
+
* report a variable and its own environment variable name as the same thing.
|
|
26
|
+
*
|
|
27
|
+
* The order matches come back in is part of the contract, because it is the
|
|
28
|
+
* order they are offered in and the one `--accept-first` picks from. Matches
|
|
29
|
+
* are sorted by how qualified the spelling that matched was (a fully qualified
|
|
30
|
+
* reference first, then a partly qualified one, then a bare name, and an
|
|
31
|
+
* environment variable name last), then by scope in the order `SCOPE_ORDER`
|
|
32
|
+
* documents, then by repository in search order, then alphabetically by
|
|
33
|
+
* namespace, recipe and variable.
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
import type { IndexFile } from "../repos/formats/index-file.js";
|
|
37
|
+
import type { ParsedRef } from "../repos/ref.js";
|
|
38
|
+
import type { DefinedVariable } from "../vars/definition-source.js";
|
|
39
|
+
import { variableCandidates, type LadderContext } from "../vars/ladder.js";
|
|
40
|
+
import { bareName } from "../vars/names.js";
|
|
41
|
+
import { SCOPE_LABELS, SousScope, scopeRank } from "./scopes.js";
|
|
42
|
+
|
|
43
|
+
// --- What the search reads ----------------------------------------------------------------------
|
|
44
|
+
|
|
45
|
+
/** One namespace a reference could name. */
|
|
46
|
+
export type ReferenceNamespace = {
|
|
47
|
+
/** The namespace name. */
|
|
48
|
+
name: string;
|
|
49
|
+
/** Its one-paragraph summary, when the index carries one. */
|
|
50
|
+
description?: string;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
/** One recipe a reference could name. */
|
|
54
|
+
export type ReferenceRecipe = {
|
|
55
|
+
/** The namespace publishing it. */
|
|
56
|
+
namespace: string;
|
|
57
|
+
/** The recipe name. */
|
|
58
|
+
name: string;
|
|
59
|
+
/** Its one-paragraph summary, when the index carries one. */
|
|
60
|
+
description?: string;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
/** One repository a reference could name, and what it publishes. */
|
|
64
|
+
export type ReferenceRepo = {
|
|
65
|
+
/** The short name this project calls it. */
|
|
66
|
+
name: string;
|
|
67
|
+
/** Where it lives, as the project's config records it. */
|
|
68
|
+
url?: string;
|
|
69
|
+
/** Every namespace it publishes. */
|
|
70
|
+
namespaces: ReferenceNamespace[];
|
|
71
|
+
/** Every recipe it publishes. */
|
|
72
|
+
recipes: ReferenceRecipe[];
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Everything a reference resolves against. Each part is optional, because a
|
|
77
|
+
* command supplies only what it can see: a browsing command has repositories
|
|
78
|
+
* and their indexes, `sous vars ask --file` has definitions and no repository
|
|
79
|
+
* at all.
|
|
80
|
+
*/
|
|
81
|
+
export type ReferenceContext = {
|
|
82
|
+
/**
|
|
83
|
+
* The repositories to search, in the order their matches should be listed:
|
|
84
|
+
* the built-in repository first, then the ones the config names, in config
|
|
85
|
+
* order.
|
|
86
|
+
*/
|
|
87
|
+
repos?: ReferenceRepo[];
|
|
88
|
+
/** The variable definitions in play, with the recipe that published each. */
|
|
89
|
+
variables?: DefinedVariable[];
|
|
90
|
+
/**
|
|
91
|
+
* The environment layers, which decide which generated environment variable
|
|
92
|
+
* names are in use. Without it only a definition's declared `env` name is
|
|
93
|
+
* searched.
|
|
94
|
+
*/
|
|
95
|
+
ladder?: LadderContext;
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
// --- What the search answers --------------------------------------------------------------------
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* How qualified the spelling that matched was. Lower is more specific, and the
|
|
102
|
+
* listing order follows it.
|
|
103
|
+
*/
|
|
104
|
+
export enum Qualification {
|
|
105
|
+
/** The reference named the whole identity, repository included. */
|
|
106
|
+
Full = 0,
|
|
107
|
+
/** The reference named part of the identity, such as `namespace/recipe`. */
|
|
108
|
+
Partial = 1,
|
|
109
|
+
/** The reference was a bare name. */
|
|
110
|
+
Bare = 2,
|
|
111
|
+
/** The reference was an environment variable name. */
|
|
112
|
+
EnvName = 3,
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** One thing a reference could have meant. */
|
|
116
|
+
export type ReferenceMatch = {
|
|
117
|
+
/** What kind of thing it is. */
|
|
118
|
+
scope: SousScope;
|
|
119
|
+
/**
|
|
120
|
+
* The fully qualified name of it: `repo`, `repo:namespace`,
|
|
121
|
+
* `repo:namespace/recipe`, or `repo:namespace/recipe.variable`. Two matches
|
|
122
|
+
* never share a key within one scope, so it is safe to compare and to print.
|
|
123
|
+
*/
|
|
124
|
+
key: string;
|
|
125
|
+
/** The short name of the thing itself, without any qualifier. */
|
|
126
|
+
label: string;
|
|
127
|
+
/** What it is, in one line: a description, a prompt, or where it lives. */
|
|
128
|
+
detail?: string;
|
|
129
|
+
/** The repository it belongs to, when it has one. */
|
|
130
|
+
repo?: string;
|
|
131
|
+
/** The namespace it is, or the one it lives in. */
|
|
132
|
+
namespace?: string;
|
|
133
|
+
/** The recipe it is, or the one that declares it. */
|
|
134
|
+
recipe?: string;
|
|
135
|
+
/** The variable it is, or the one an environment variable name answers. */
|
|
136
|
+
variable?: string;
|
|
137
|
+
/** The environment variable name, when the reference named one. */
|
|
138
|
+
envName?: string;
|
|
139
|
+
/** How qualified the spelling that matched was. */
|
|
140
|
+
qualification: Qualification;
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
// --- The search ---------------------------------------------------------------------------------
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Every meaning a reference could have, in the documented order.
|
|
147
|
+
*
|
|
148
|
+
* Nothing is fetched and nothing is asked: this reads the context it is handed
|
|
149
|
+
* and returns what matched. An empty result means the reference named nothing
|
|
150
|
+
* the caller can see; one result is the answer; several are offered to the
|
|
151
|
+
* caller by `pickReference`.
|
|
152
|
+
*
|
|
153
|
+
* @param search - The reference exactly as it was written.
|
|
154
|
+
* @param scopes - The kinds of thing this command accepts.
|
|
155
|
+
* @param context - The repositories, definitions and environment layers to search.
|
|
156
|
+
*/
|
|
157
|
+
export function findReference(
|
|
158
|
+
search: string,
|
|
159
|
+
scopes: readonly SousScope[],
|
|
160
|
+
context: ReferenceContext
|
|
161
|
+
): ReferenceMatch[] {
|
|
162
|
+
const wanted = new Set(scopes);
|
|
163
|
+
const term = search.trim();
|
|
164
|
+
if (term.length === 0) return [];
|
|
165
|
+
|
|
166
|
+
const matches: ReferenceMatch[] = [];
|
|
167
|
+
const repoOrder = new Map((context.repos ?? []).map((repo, position) => [repo.name, position]));
|
|
168
|
+
|
|
169
|
+
for (const repo of context.repos ?? []) {
|
|
170
|
+
if (wanted.has(SousScope.Repository) && term === repo.name) {
|
|
171
|
+
matches.push({
|
|
172
|
+
scope: SousScope.Repository,
|
|
173
|
+
key: repo.name,
|
|
174
|
+
label: repo.name,
|
|
175
|
+
repo: repo.name,
|
|
176
|
+
qualification: Qualification.Full,
|
|
177
|
+
...(repo.url === undefined ? {} : { detail: repo.url }),
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (wanted.has(SousScope.Namespace)) {
|
|
182
|
+
for (const namespace of repo.namespaces) {
|
|
183
|
+
const key = `${repo.name}:${namespace.name}`;
|
|
184
|
+
const qualification = qualificationOf(term, [
|
|
185
|
+
[key, Qualification.Full],
|
|
186
|
+
[namespace.name, Qualification.Bare],
|
|
187
|
+
]);
|
|
188
|
+
if (qualification === undefined) continue;
|
|
189
|
+
matches.push({
|
|
190
|
+
scope: SousScope.Namespace,
|
|
191
|
+
key,
|
|
192
|
+
label: namespace.name,
|
|
193
|
+
repo: repo.name,
|
|
194
|
+
namespace: namespace.name,
|
|
195
|
+
qualification,
|
|
196
|
+
...(namespace.description === undefined ? {} : { detail: namespace.description }),
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (wanted.has(SousScope.Recipe)) {
|
|
202
|
+
for (const recipe of repo.recipes) {
|
|
203
|
+
const key = `${repo.name}:${recipe.namespace}/${recipe.name}`;
|
|
204
|
+
const qualification = qualificationOf(term, [
|
|
205
|
+
[key, Qualification.Full],
|
|
206
|
+
[`${recipe.namespace}/${recipe.name}`, Qualification.Partial],
|
|
207
|
+
[`${repo.name}:${recipe.name}`, Qualification.Partial],
|
|
208
|
+
[recipe.name, Qualification.Bare],
|
|
209
|
+
]);
|
|
210
|
+
if (qualification === undefined) continue;
|
|
211
|
+
matches.push({
|
|
212
|
+
scope: SousScope.Recipe,
|
|
213
|
+
key,
|
|
214
|
+
label: recipe.name,
|
|
215
|
+
repo: repo.name,
|
|
216
|
+
namespace: recipe.namespace,
|
|
217
|
+
recipe: recipe.name,
|
|
218
|
+
qualification,
|
|
219
|
+
...(recipe.description === undefined ? {} : { detail: recipe.description }),
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (wanted.has(SousScope.VariableName) || wanted.has(SousScope.EnvVarName)) {
|
|
226
|
+
for (const defined of context.variables ?? []) {
|
|
227
|
+
const key = variableReferenceKey(defined);
|
|
228
|
+
const { repo, namespace, name: recipe } = defined.recipe;
|
|
229
|
+
const variable = defined.definition.name;
|
|
230
|
+
|
|
231
|
+
if (wanted.has(SousScope.VariableName)) {
|
|
232
|
+
const qualification = qualificationOf(term, [
|
|
233
|
+
[key, Qualification.Full],
|
|
234
|
+
[`${namespace}/${recipe}.${variable}`, Qualification.Partial],
|
|
235
|
+
[`${recipe}.${variable}`, Qualification.Partial],
|
|
236
|
+
[`${repo}:${variable}`, Qualification.Partial],
|
|
237
|
+
[variable, Qualification.Bare],
|
|
238
|
+
]);
|
|
239
|
+
if (qualification !== undefined) {
|
|
240
|
+
matches.push({
|
|
241
|
+
scope: SousScope.VariableName,
|
|
242
|
+
key,
|
|
243
|
+
label: variable,
|
|
244
|
+
repo,
|
|
245
|
+
namespace,
|
|
246
|
+
recipe,
|
|
247
|
+
variable,
|
|
248
|
+
qualification,
|
|
249
|
+
detail: defined.definition.prompt,
|
|
250
|
+
});
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (wanted.has(SousScope.EnvVarName) && environmentNamesFor(defined, context).has(term)) {
|
|
256
|
+
matches.push({
|
|
257
|
+
scope: SousScope.EnvVarName,
|
|
258
|
+
key,
|
|
259
|
+
label: term,
|
|
260
|
+
repo,
|
|
261
|
+
namespace,
|
|
262
|
+
recipe,
|
|
263
|
+
variable,
|
|
264
|
+
envName: term,
|
|
265
|
+
qualification: Qualification.EnvName,
|
|
266
|
+
detail: defined.definition.prompt,
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return sortMatches(matches, repoOrder);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* The repository a reference names.
|
|
277
|
+
*
|
|
278
|
+
* @param search - The reference exactly as it was written.
|
|
279
|
+
* @param context - What to search.
|
|
280
|
+
*/
|
|
281
|
+
export function findRepository(
|
|
282
|
+
search: string,
|
|
283
|
+
context: ReferenceContext
|
|
284
|
+
): ReferenceMatch[] {
|
|
285
|
+
return findReference(search, [SousScope.Repository], context);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* The namespace a reference names, bare or written as `repository:namespace`.
|
|
290
|
+
*
|
|
291
|
+
* @param search - The reference exactly as it was written.
|
|
292
|
+
* @param context - What to search.
|
|
293
|
+
*/
|
|
294
|
+
export function findNamespace(search: string, context: ReferenceContext): ReferenceMatch[] {
|
|
295
|
+
return findReference(search, [SousScope.Namespace], context);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* The recipe a reference names, at any level of qualification.
|
|
300
|
+
*
|
|
301
|
+
* @param search - The reference exactly as it was written.
|
|
302
|
+
* @param context - What to search.
|
|
303
|
+
*/
|
|
304
|
+
export function findRecipe(search: string, context: ReferenceContext): ReferenceMatch[] {
|
|
305
|
+
return findReference(search, [SousScope.Recipe], context);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* The variable a reference names, by its own name or by the name of an
|
|
310
|
+
* environment variable that answers it.
|
|
311
|
+
*
|
|
312
|
+
* @param search - The reference exactly as it was written.
|
|
313
|
+
* @param context - What to search.
|
|
314
|
+
*/
|
|
315
|
+
export function findVariable(search: string, context: ReferenceContext): ReferenceMatch[] {
|
|
316
|
+
return findReference(search, [SousScope.VariableName, SousScope.EnvVarName], context);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// --- Describing what was found ------------------------------------------------------------------
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Describes one match in the words a person choosing between them needs: the
|
|
323
|
+
* fully qualified name, and what it actually means.
|
|
324
|
+
*
|
|
325
|
+
* @param match - The match to describe.
|
|
326
|
+
*/
|
|
327
|
+
export function describeReference(match: ReferenceMatch): string {
|
|
328
|
+
const summary = match.detail === undefined ? "" : `: ${match.detail}`;
|
|
329
|
+
|
|
330
|
+
switch (match.scope) {
|
|
331
|
+
case SousScope.Repository:
|
|
332
|
+
return (
|
|
333
|
+
`${match.key} (the repository '${match.repo}'` +
|
|
334
|
+
`${match.detail === undefined ? "" : `, at ${match.detail}`})`
|
|
335
|
+
);
|
|
336
|
+
case SousScope.Namespace:
|
|
337
|
+
return (
|
|
338
|
+
`${match.key} (the whole namespace '${match.namespace}' in the ` +
|
|
339
|
+
`repository '${match.repo}')`
|
|
340
|
+
);
|
|
341
|
+
case SousScope.Recipe:
|
|
342
|
+
return (
|
|
343
|
+
`${match.key} (the recipe '${match.recipe}' in the namespace ` +
|
|
344
|
+
`'${match.namespace}' of the repository '${match.repo}'${summary})`
|
|
345
|
+
);
|
|
346
|
+
case SousScope.VariableName:
|
|
347
|
+
return (
|
|
348
|
+
`${match.key} (the variable '${match.variable}' of the recipe ` +
|
|
349
|
+
`'${match.namespace}/${match.recipe}'${summary})`
|
|
350
|
+
);
|
|
351
|
+
case SousScope.EnvVarName:
|
|
352
|
+
return (
|
|
353
|
+
`${match.envName} (the environment variable answering '${match.variable}' ` +
|
|
354
|
+
`of the recipe '${match.namespace}/${match.recipe}'${summary})`
|
|
355
|
+
);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* The plain-language name of what a match is, for a sentence that has to say
|
|
361
|
+
* what kind of thing was found.
|
|
362
|
+
*
|
|
363
|
+
* @param match - The match to name.
|
|
364
|
+
*/
|
|
365
|
+
export function referenceKindLabel(match: ReferenceMatch): string {
|
|
366
|
+
return SCOPE_LABELS[match.scope];
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// --- Building a context -------------------------------------------------------------------------
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Turns cached repository indexes into the repositories a reference searches,
|
|
373
|
+
* in the order they were given.
|
|
374
|
+
*
|
|
375
|
+
* @param repoOrder - The repository short names, in search order.
|
|
376
|
+
* @param indexes - Each repository's cached index, keyed by short name.
|
|
377
|
+
*/
|
|
378
|
+
export function referenceReposFromIndexes(
|
|
379
|
+
repoOrder: readonly string[],
|
|
380
|
+
indexes: Map<string, IndexFile>,
|
|
381
|
+
urls: Record<string, string | undefined> = {}
|
|
382
|
+
): ReferenceRepo[] {
|
|
383
|
+
const repos: ReferenceRepo[] = [];
|
|
384
|
+
|
|
385
|
+
for (const name of repoOrder) {
|
|
386
|
+
const index = indexes.get(name);
|
|
387
|
+
if (index === undefined) continue;
|
|
388
|
+
|
|
389
|
+
const namespaces: ReferenceNamespace[] = Object.entries(index.namespaces).map(
|
|
390
|
+
([namespace, declared]) => ({
|
|
391
|
+
name: namespace,
|
|
392
|
+
...(declared?.description === undefined ? {} : { description: declared.description }),
|
|
393
|
+
})
|
|
394
|
+
);
|
|
395
|
+
|
|
396
|
+
const recipes: ReferenceRecipe[] = [];
|
|
397
|
+
for (const [key, recipe] of Object.entries(index.recipes)) {
|
|
398
|
+
const slash = key.indexOf("/");
|
|
399
|
+
if (slash === -1) continue;
|
|
400
|
+
recipes.push({
|
|
401
|
+
namespace: key.slice(0, slash),
|
|
402
|
+
name: key.slice(slash + 1),
|
|
403
|
+
...(recipe.description === undefined ? {} : { description: recipe.description }),
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
const url = urls[name];
|
|
408
|
+
repos.push({ name, namespaces, recipes, ...(url === undefined ? {} : { url }) });
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
return repos;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/**
|
|
415
|
+
* The repositories, namespaces and recipes the variable definitions in play
|
|
416
|
+
* belong to, derived from the definitions themselves.
|
|
417
|
+
*
|
|
418
|
+
* This is the context a variables command searches: a namespace that publishes
|
|
419
|
+
* no variable this project holds has no questions to ask, so naming it would
|
|
420
|
+
* resolve to an empty set of questions rather than to an answer. Deriving the
|
|
421
|
+
* context from the definitions also means `sous vars ask --file` resolves
|
|
422
|
+
* references exactly as a subscribed project does.
|
|
423
|
+
*
|
|
424
|
+
* @param variables - The variable definitions in play.
|
|
425
|
+
* @param ladder - The environment layers, for environment variable names.
|
|
426
|
+
*/
|
|
427
|
+
export function referenceContextFromVariables(
|
|
428
|
+
variables: DefinedVariable[],
|
|
429
|
+
ladder?: LadderContext
|
|
430
|
+
): ReferenceContext {
|
|
431
|
+
const repos = new Map<string, ReferenceRepo>();
|
|
432
|
+
|
|
433
|
+
for (const defined of variables) {
|
|
434
|
+
const { repo: repoName, namespace, name: recipe } = defined.recipe;
|
|
435
|
+
|
|
436
|
+
let repo = repos.get(repoName);
|
|
437
|
+
if (repo === undefined) {
|
|
438
|
+
repo = { name: repoName, namespaces: [], recipes: [] };
|
|
439
|
+
repos.set(repoName, repo);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
if (!repo.namespaces.some((entry) => entry.name === namespace)) {
|
|
443
|
+
repo.namespaces.push({ name: namespace });
|
|
444
|
+
}
|
|
445
|
+
if (!repo.recipes.some((entry) => entry.namespace === namespace && entry.name === recipe)) {
|
|
446
|
+
repo.recipes.push({ namespace, name: recipe });
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
return {
|
|
451
|
+
repos: [...repos.values()],
|
|
452
|
+
variables,
|
|
453
|
+
...(ladder === undefined ? {} : { ladder }),
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* The fully qualified name of one variable: `repo:namespace/recipe.variable`.
|
|
459
|
+
* It is what a reference to a variable resolves to, and what a command hands
|
|
460
|
+
* back to the asking machinery when it has decided which variables to ask.
|
|
461
|
+
*
|
|
462
|
+
* @param defined - The definition and the recipe that published it.
|
|
463
|
+
*/
|
|
464
|
+
export function variableReferenceKey(defined: DefinedVariable): string {
|
|
465
|
+
const { repo, namespace, name } = defined.recipe;
|
|
466
|
+
return `${repo}:${namespace}/${name}.${defined.definition.name}`;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// --- Turning a match back into a ref --------------------------------------------------------------
|
|
470
|
+
|
|
471
|
+
/**
|
|
472
|
+
* Turns a chosen match into a parsed ref, carrying over the version range the
|
|
473
|
+
* original ref asked for. The repository qualifier is kept, so what gets
|
|
474
|
+
* resolved is exactly the match that was chosen and not another repository's
|
|
475
|
+
* recipe of the same name.
|
|
476
|
+
*
|
|
477
|
+
* @param match - The match that was chosen.
|
|
478
|
+
* @param original - The ref as the user wrote it.
|
|
479
|
+
*/
|
|
480
|
+
export function referenceToRef(match: ReferenceMatch, original: ParsedRef): ParsedRef {
|
|
481
|
+
return {
|
|
482
|
+
...(match.repo === undefined ? {} : { repo: match.repo }),
|
|
483
|
+
namespace: match.namespace ?? original.namespace,
|
|
484
|
+
...(match.recipe === undefined ? {} : { recipe: match.recipe }),
|
|
485
|
+
...(original.range === undefined ? {} : { range: original.range }),
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// --- The pieces -----------------------------------------------------------------------------------
|
|
490
|
+
|
|
491
|
+
/**
|
|
492
|
+
* How qualified a spelling of one thing the search term matched, or undefined
|
|
493
|
+
* when it matched none of them. Spellings are given most specific first, and
|
|
494
|
+
* the first one that matches wins.
|
|
495
|
+
*
|
|
496
|
+
* @param term - The search term.
|
|
497
|
+
* @param spellings - Each spelling of this thing, with how qualified it is.
|
|
498
|
+
*/
|
|
499
|
+
function qualificationOf(
|
|
500
|
+
term: string,
|
|
501
|
+
spellings: [string, Qualification][]
|
|
502
|
+
): Qualification | undefined {
|
|
503
|
+
for (const [spelling, qualification] of spellings) {
|
|
504
|
+
if (spelling === term) return qualification;
|
|
505
|
+
}
|
|
506
|
+
return undefined;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
/**
|
|
510
|
+
* Every environment variable name that would be recognized as naming one
|
|
511
|
+
* variable: the name its definition declares (a recipe may bind an existing
|
|
512
|
+
* variable such as `GITHUB_TOKEN`), plus any generated name on the resolution
|
|
513
|
+
* ladder that is actually in use in `.sous/.env` or `.sous/.env.local`.
|
|
514
|
+
*
|
|
515
|
+
* Generated names are only searched when they are in use, because every
|
|
516
|
+
* variable generates four of them and a project would otherwise be told that a
|
|
517
|
+
* name nothing has ever set names one of its variables.
|
|
518
|
+
*
|
|
519
|
+
* @param defined - The definition and the recipe that published it.
|
|
520
|
+
* @param context - The environment layers, when the caller has them.
|
|
521
|
+
*/
|
|
522
|
+
function environmentNamesFor(
|
|
523
|
+
defined: DefinedVariable,
|
|
524
|
+
context: ReferenceContext
|
|
525
|
+
): Set<string> {
|
|
526
|
+
const names = new Set<string>([bareName(defined.definition)]);
|
|
527
|
+
|
|
528
|
+
const ladder = context.ladder;
|
|
529
|
+
if (ladder === undefined) return names;
|
|
530
|
+
|
|
531
|
+
const inUse = new Set([...Object.keys(ladder.localEnv), ...Object.keys(ladder.sharedEnv)]);
|
|
532
|
+
for (const candidate of variableCandidates(defined, ladder)) {
|
|
533
|
+
if (inUse.has(candidate.envName)) names.add(candidate.envName);
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
return names;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
/**
|
|
540
|
+
* Sorts matches into the documented listing order and drops anything the same
|
|
541
|
+
* search found twice.
|
|
542
|
+
*
|
|
543
|
+
* @param matches - Every match, in the order they were collected.
|
|
544
|
+
* @param repoOrder - Where each repository sits in the search order.
|
|
545
|
+
*/
|
|
546
|
+
function sortMatches(
|
|
547
|
+
matches: ReferenceMatch[],
|
|
548
|
+
repoOrder: Map<string, number>
|
|
549
|
+
): ReferenceMatch[] {
|
|
550
|
+
const position = (repo: string | undefined): number =>
|
|
551
|
+
repo === undefined ? 0 : (repoOrder.get(repo) ?? repoOrder.size);
|
|
552
|
+
|
|
553
|
+
const sorted = [...matches].sort((left, right) => {
|
|
554
|
+
if (left.qualification !== right.qualification) {
|
|
555
|
+
return left.qualification - right.qualification;
|
|
556
|
+
}
|
|
557
|
+
const scopes = scopeRank(left.scope) - scopeRank(right.scope);
|
|
558
|
+
if (scopes !== 0) return scopes;
|
|
559
|
+
|
|
560
|
+
const repos = position(left.repo) - position(right.repo);
|
|
561
|
+
if (repos !== 0) return repos;
|
|
562
|
+
|
|
563
|
+
return compare(
|
|
564
|
+
[left.namespace, left.recipe, left.variable],
|
|
565
|
+
[right.namespace, right.recipe, right.variable]
|
|
566
|
+
);
|
|
567
|
+
});
|
|
568
|
+
|
|
569
|
+
const seen = new Set<string>();
|
|
570
|
+
return sorted.filter((match) => {
|
|
571
|
+
const identity = `${match.scope}${match.key}${match.envName ?? ""}`;
|
|
572
|
+
if (seen.has(identity)) return false;
|
|
573
|
+
seen.add(identity);
|
|
574
|
+
return true;
|
|
575
|
+
});
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
/** Bytewise comparison of the parts of an identity, so listings are stable. */
|
|
579
|
+
function compare(
|
|
580
|
+
left: (string | undefined)[],
|
|
581
|
+
right: (string | undefined)[]
|
|
582
|
+
): number {
|
|
583
|
+
for (let index = 0; index < left.length; index += 1) {
|
|
584
|
+
const one = left[index] ?? "";
|
|
585
|
+
const other = right[index] ?? "";
|
|
586
|
+
if (one !== other) return one < other ? -1 : 1;
|
|
587
|
+
}
|
|
588
|
+
return 0;
|
|
589
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* References: how every sous command turns a word on the command line into the
|
|
3
|
+
* one thing it names.
|
|
4
|
+
*
|
|
5
|
+
* Import from here rather than from the individual modules. `scopes.ts` says
|
|
6
|
+
* what a reference can name, `find.ts` says what one means, and `pick.ts`
|
|
7
|
+
* settles which meaning a run proceeds with.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export * from "./scopes.js";
|
|
11
|
+
export * from "./find.js";
|
|
12
|
+
export * from "./pick.js";
|