@sous-io/sous 0.2.37 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (244) hide show
  1. package/README.md +14 -256
  2. package/dist/bin.js +11 -0
  3. package/dist/sous.js +1094 -0
  4. package/docs/markdown/README.md +5 -4
  5. package/docs/markdown/_sidebar.md +21 -1
  6. package/docs/markdown/commands.md +294 -431
  7. package/docs/markdown/config-discovery.md +25 -14
  8. package/docs/markdown/config-inspection.md +53 -31
  9. package/docs/markdown/config-layers.md +168 -66
  10. package/docs/markdown/config-variables.md +44 -11
  11. package/docs/markdown/configuration.md +104 -17
  12. package/docs/markdown/design-principles.md +4 -1
  13. package/docs/markdown/repositories-authoring.md +30 -0
  14. package/docs/markdown/repositories-consuming.md +72 -14
  15. package/docs/markdown/repositories-file-formats.md +31 -13
  16. package/docs/markdown/repositories-providers.md +48 -57
  17. package/docs/markdown/repositories-quickstart.md +3 -2
  18. package/docs/markdown/repositories-troubleshooting.md +27 -1
  19. package/docs/markdown/repositories-variables.md +6 -1
  20. package/docs/markdown/repositories.md +18 -8
  21. package/package.json +48 -107
  22. package/recipes/core/sous-skills/skills/about-liquid-templates/SKILL.tpl.md +4 -6
  23. package/recipes/core/sous-skills/skills/about-liquid-templates/references/liquid-filters.tpl.md +2 -0
  24. package/recipes/core/sous-skills/skills/about-sous/SKILL.tpl.md +10 -9
  25. package/recipes/core/sous-skills/skills/about-sous-configuration/SKILL.tpl.md +35 -14
  26. package/recipes/core/sous-skills/skills/about-sous-recipes/SKILL.tpl.md +51 -20
  27. package/recipes/core/sous-skills/sous.recipe.yaml +1 -1
  28. package/sous.config.schema.json +351 -132
  29. package/bin/run.js +0 -51
  30. package/src/base-command.ts +0 -457
  31. package/src/commands/build.ts +0 -181
  32. package/src/commands/clear.ts +0 -79
  33. package/src/commands/compile.ts +0 -155
  34. package/src/commands/config/get.ts +0 -110
  35. package/src/commands/config/show.ts +0 -32
  36. package/src/commands/config/validate.ts +0 -53
  37. package/src/commands/help.ts +0 -46
  38. package/src/commands/init.ts +0 -289
  39. package/src/commands/launch.ts +0 -139
  40. package/src/commands/lock/rebuild.ts +0 -239
  41. package/src/commands/lock/show.ts +0 -115
  42. package/src/commands/namespace/list.ts +0 -138
  43. package/src/commands/namespace/show.ts +0 -130
  44. package/src/commands/prune.ts +0 -43
  45. package/src/commands/recipe/list.ts +0 -112
  46. package/src/commands/recipe/show.ts +0 -439
  47. package/src/commands/repo/add.ts +0 -145
  48. package/src/commands/repo/contribute.ts +0 -604
  49. package/src/commands/repo/gc.ts +0 -173
  50. package/src/commands/repo/init.ts +0 -136
  51. package/src/commands/repo/link.ts +0 -899
  52. package/src/commands/repo/list.ts +0 -236
  53. package/src/commands/repo/release.ts +0 -685
  54. package/src/commands/repo/remove.ts +0 -194
  55. package/src/commands/repo/search.ts +0 -243
  56. package/src/commands/repo/submit.ts +0 -341
  57. package/src/commands/repo/unlink.ts +0 -461
  58. package/src/commands/subscription/add.ts +0 -286
  59. package/src/commands/subscription/list.ts +0 -208
  60. package/src/commands/subscription/remove.ts +0 -182
  61. package/src/commands/subscription/update.ts +0 -216
  62. package/src/commands/update.ts +0 -178
  63. package/src/commands/vars/ask.ts +0 -417
  64. package/src/commands/vars/index.ts +0 -79
  65. package/src/commands/vars/list.ts +0 -67
  66. package/src/commands/vars/show.ts +0 -77
  67. package/src/config-command.ts +0 -30
  68. package/src/lib/build-preparation.ts +0 -128
  69. package/src/lib/build-service.ts +0 -580
  70. package/src/lib/config-discovery.ts +0 -378
  71. package/src/lib/config-inspect.ts +0 -145
  72. package/src/lib/config-kernel.mjs +0 -379
  73. package/src/lib/config-schema.ts +0 -401
  74. package/src/lib/env-file.ts +0 -328
  75. package/src/lib/env-local.ts +0 -212
  76. package/src/lib/errors.ts +0 -32
  77. package/src/lib/glob-pattern.ts +0 -24
  78. package/src/lib/include-resolver.ts +0 -523
  79. package/src/lib/interactive.ts +0 -165
  80. package/src/lib/markdown-compiler.ts +0 -947
  81. package/src/lib/package-info.ts +0 -25
  82. package/src/lib/pid-service.ts +0 -99
  83. package/src/lib/project-install.d.mts +0 -44
  84. package/src/lib/project-install.mjs +0 -234
  85. package/src/lib/project-scaffold/index.ts +0 -332
  86. package/src/lib/project-scaffold/templates.ts +0 -240
  87. package/src/lib/repos/catalog-display.ts +0 -215
  88. package/src/lib/repos/catalog-inputs.ts +0 -288
  89. package/src/lib/repos/catalog.ts +0 -842
  90. package/src/lib/repos/contribute.ts +0 -452
  91. package/src/lib/repos/core-recipe.ts +0 -105
  92. package/src/lib/repos/declarations.ts +0 -187
  93. package/src/lib/repos/defaults.ts +0 -175
  94. package/src/lib/repos/formats/common.ts +0 -440
  95. package/src/lib/repos/formats/index-file.ts +0 -253
  96. package/src/lib/repos/formats/links-map.ts +0 -98
  97. package/src/lib/repos/formats/lockfile.ts +0 -169
  98. package/src/lib/repos/formats/patterns.ts +0 -57
  99. package/src/lib/repos/formats/recipe-manifest.ts +0 -205
  100. package/src/lib/repos/formats/repo-manifest.ts +0 -97
  101. package/src/lib/repos/formats/store-entry.ts +0 -84
  102. package/src/lib/repos/formats/variable-definition.ts +0 -268
  103. package/src/lib/repos/freshness.ts +0 -264
  104. package/src/lib/repos/git-clone.ts +0 -850
  105. package/src/lib/repos/identity.ts +0 -89
  106. package/src/lib/repos/index.ts +0 -57
  107. package/src/lib/repos/links.ts +0 -356
  108. package/src/lib/repos/load-manifest.ts +0 -237
  109. package/src/lib/repos/lock-service.ts +0 -454
  110. package/src/lib/repos/locked-namespace-resolver.ts +0 -232
  111. package/src/lib/repos/locked-recipe-lookup.ts +0 -123
  112. package/src/lib/repos/locked-recipes.ts +0 -254
  113. package/src/lib/repos/managed-layer.ts +0 -422
  114. package/src/lib/repos/memories-hash-name.ts +0 -33
  115. package/src/lib/repos/namespace-resolver.ts +0 -527
  116. package/src/lib/repos/providers/base.ts +0 -268
  117. package/src/lib/repos/providers/git.ts +0 -245
  118. package/src/lib/repos/providers/github.ts +0 -589
  119. package/src/lib/repos/providers/gitlab.ts +0 -332
  120. package/src/lib/repos/providers/http.ts +0 -128
  121. package/src/lib/repos/providers/index-cache.ts +0 -421
  122. package/src/lib/repos/providers/index.ts +0 -106
  123. package/src/lib/repos/providers/local.ts +0 -410
  124. package/src/lib/repos/providers/provider.ts +0 -615
  125. package/src/lib/repos/recipe-config-layers.ts +0 -316
  126. package/src/lib/repos/recipe-key-matcher.ts +0 -62
  127. package/src/lib/repos/recipe-memories.ts +0 -178
  128. package/src/lib/repos/recipe-targets.ts +0 -236
  129. package/src/lib/repos/ref-search.ts +0 -46
  130. package/src/lib/repos/release/bump.ts +0 -161
  131. package/src/lib/repos/release/changelog.ts +0 -461
  132. package/src/lib/repos/release/git-state.ts +0 -460
  133. package/src/lib/repos/release/index-builder.ts +0 -1108
  134. package/src/lib/repos/release/index.ts +0 -19
  135. package/src/lib/repos/release/plan.ts +0 -521
  136. package/src/lib/repos/release/settle.ts +0 -154
  137. package/src/lib/repos/release/submissions.ts +0 -214
  138. package/src/lib/repos/release/submit-checkout.ts +0 -290
  139. package/src/lib/repos/release/submit-questions.ts +0 -153
  140. package/src/lib/repos/release/submit-service.ts +0 -898
  141. package/src/lib/repos/release/tags.ts +0 -239
  142. package/src/lib/repos/release/validate.ts +0 -463
  143. package/src/lib/repos/resolver.ts +0 -1028
  144. package/src/lib/repos/scaffold/index.ts +0 -245
  145. package/src/lib/repos/scaffold/templates.ts +0 -517
  146. package/src/lib/repos/seed.ts +0 -473
  147. package/src/lib/repos/store/contract.ts +0 -64
  148. package/src/lib/repos/store/hash.ts +0 -191
  149. package/src/lib/repos/store/recipe-store.ts +0 -599
  150. package/src/lib/repos/store/settings.ts +0 -58
  151. package/src/lib/repos/subscription-service.ts +0 -3859
  152. package/src/lib/repos/trust.ts +0 -447
  153. package/src/lib/repos/update-plan.ts +0 -235
  154. package/src/lib/self-update/index.ts +0 -14
  155. package/src/lib/self-update/installs.ts +0 -413
  156. package/src/lib/self-update/managers.ts +0 -126
  157. package/src/lib/self-update/plan.ts +0 -315
  158. package/src/lib/self-update/registry.ts +0 -99
  159. package/src/lib/self-update/run.ts +0 -506
  160. package/src/lib/self-update/versions.ts +0 -243
  161. package/src/lib/settings.ts +0 -1107
  162. package/src/lib/sous-home.ts +0 -104
  163. package/src/lib/state.ts +0 -167
  164. package/src/lib/vars/answers.ts +0 -192
  165. package/src/lib/vars/ask.ts +0 -1152
  166. package/src/lib/vars/definition-source.ts +0 -261
  167. package/src/lib/vars/display.ts +0 -231
  168. package/src/lib/vars/index.ts +0 -20
  169. package/src/lib/vars/ladder.ts +0 -282
  170. package/src/lib/vars/mappings.ts +0 -260
  171. package/src/lib/vars/names.ts +0 -94
  172. package/src/lib/vars/preanswers.ts +0 -395
  173. package/src/lib/vars/question-plan.ts +0 -241
  174. package/src/lib/vars/report.ts +0 -228
  175. package/src/lib/vars/safe-regex.ts +0 -235
  176. package/src/lib/vars/secrets.ts +0 -131
  177. package/src/lib/vars/validate.ts +0 -312
  178. package/src/lib/version-report.ts +0 -90
  179. package/src/lib/watch-loop.ts +0 -148
  180. package/src/lib/watch-service.ts +0 -116
  181. package/src/services/ref-resolver/container.ts +0 -107
  182. package/src/services/ref-resolver/dependency.ts +0 -60
  183. package/src/services/ref-resolver/format.ts +0 -197
  184. package/src/services/ref-resolver/glob.ts +0 -115
  185. package/src/services/ref-resolver/hash-names.ts +0 -155
  186. package/src/services/ref-resolver/index.ts +0 -55
  187. package/src/services/ref-resolver/injectable.ts +0 -36
  188. package/src/services/ref-resolver/location.ts +0 -62
  189. package/src/services/ref-resolver/lookups/cached-index-lookup.ts +0 -53
  190. package/src/services/ref-resolver/lookups/catalog-lookup.ts +0 -23
  191. package/src/services/ref-resolver/lookups/catalog-matcher.ts +0 -261
  192. package/src/services/ref-resolver/lookups/chained-lookup.ts +0 -27
  193. package/src/services/ref-resolver/lookups/env-var-lookup.ts +0 -65
  194. package/src/services/ref-resolver/lookups/fetched-index-lookup.ts +0 -150
  195. package/src/services/ref-resolver/lookups/recorded-dependency-lookup.ts +0 -62
  196. package/src/services/ref-resolver/lookups/ref-lookup.ts +0 -54
  197. package/src/services/ref-resolver/lookups/variable-lookup.ts +0 -123
  198. package/src/services/ref-resolver/named-refs.ts +0 -144
  199. package/src/services/ref-resolver/parser/env-var-splitter.ts +0 -27
  200. package/src/services/ref-resolver/parser/location-splitter.ts +0 -239
  201. package/src/services/ref-resolver/parser/name-path-splitter.ts +0 -200
  202. package/src/services/ref-resolver/parser/partial-ref.ts +0 -84
  203. package/src/services/ref-resolver/parser/query-splitter.ts +0 -61
  204. package/src/services/ref-resolver/parser/range-splitter.ts +0 -52
  205. package/src/services/ref-resolver/parser/ref-parser.ts +0 -115
  206. package/src/services/ref-resolver/parser/repo-qualifier-splitter.ts +0 -51
  207. package/src/services/ref-resolver/parts.ts +0 -115
  208. package/src/services/ref-resolver/pruners/command-line-pruner.ts +0 -47
  209. package/src/services/ref-resolver/pruners/include-pruner.ts +0 -66
  210. package/src/services/ref-resolver/pruners/manifest-pruner.ts +0 -130
  211. package/src/services/ref-resolver/pruners/ref-pruner.ts +0 -135
  212. package/src/services/ref-resolver/pruners/rule-helpers.ts +0 -44
  213. package/src/services/ref-resolver/pruners/stored-key-pruner.ts +0 -136
  214. package/src/services/ref-resolver/ref-helpers.ts +0 -62
  215. package/src/services/ref-resolver/ref-picker-service.ts +0 -204
  216. package/src/services/ref-resolver/ref-report.ts +0 -84
  217. package/src/services/ref-resolver/ref-resolve-arguments.ts +0 -47
  218. package/src/services/ref-resolver/ref-resolve-result.ts +0 -36
  219. package/src/services/ref-resolver/ref-resolver-service.ts +0 -323
  220. package/src/services/ref-resolver/source.ts +0 -48
  221. package/src/services/ref-resolver/tokens.ts +0 -25
  222. package/src/services/ref-resolver/types.ts +0 -155
  223. package/src/templating/filters/bullet-list.ts +0 -9
  224. package/src/templating/filters/index.ts +0 -8
  225. package/src/templating/init-liquid-engine.ts +0 -139
  226. package/src/templating/lib/glob-files.ts +0 -74
  227. package/src/templating/lib/import-export.ts +0 -32
  228. package/src/templating/lib/secret-scope.ts +0 -41
  229. package/src/templating/lib/tag-args.ts +0 -19
  230. package/src/templating/tags/exportScalarVarsJs.ts +0 -46
  231. package/src/templating/tags/getFiles.ts +0 -89
  232. package/src/templating/tags/index.ts +0 -14
  233. package/src/templating/tags/listFiles.ts +0 -54
  234. package/src/templating/tags/showVars.ts +0 -28
  235. package/src/utils/choice-prompt.ts +0 -143
  236. package/src/utils/command-errors.ts +0 -186
  237. package/src/utils/command-help.ts +0 -45
  238. package/src/utils/confirm-prompt.ts +0 -110
  239. package/src/utils/flags.ts +0 -177
  240. package/src/utils/formatting.ts +0 -849
  241. package/src/utils/prompts.ts +0 -53
  242. package/src/utils/sous-directory.ts +0 -245
  243. package/src/utils/table.ts +0 -603
  244. package/src/utils/value-prompt.ts +0 -119
@@ -1,204 +0,0 @@
1
- /**
2
- * Settling on one of the things a ref could have meant.
3
- *
4
- * Resolving is separate from choosing on purpose: the resolver is pure and
5
- * tells the caller everything a ref could have meant, and this is the one place
6
- * that decides which of them the run proceeds with. Every command that takes a
7
- * ref goes through it, so a word that means two things behaves the same
8
- * everywhere:
9
- *
10
- * - one match proceeds, and what it resolved to is written as the facts about
11
- * it followed by one sentence, so the reader sees what the word they typed
12
- * actually meant;
13
- * - several matches are offered as a list to choose from;
14
- * - `--accept-first` takes the first one in the documented listing order;
15
- * - a run with no terminal fails, naming the question it could not ask and
16
- * the flag that would have answered it (`src/lib/interactive.ts` holds that
17
- * rule, and every prompt in sous is gated by it).
18
- */
19
-
20
- import { makeInjectable } from "./injectable.js";
21
- import { ConfigError } from "../../lib/errors.js";
22
- import { nonInteractiveError } from "../../lib/interactive.js";
23
- import {
24
- formatResolvedReference,
25
- type ResolvedReferenceFacts,
26
- } from "./ref-report.js";
27
- import { formatParagraph, log } from "../../utils/formatting.js";
28
- import { askChoice } from "../../utils/prompts.js";
29
- import { describeRef, refKey, refKindLabel, refSpelling } from "./format.js";
30
- import { namespaceOf, recipeOf, repoOf } from "./parts.js";
31
- import type { SousRef } from "./types.js";
32
-
33
- /** The flag that answers "which one did you mean?" ahead of time. */
34
- export const ACCEPT_FIRST_FLAG = "--accept-first";
35
-
36
- /** How `RefPickerService.pick` should behave. */
37
- export class RefPickArguments {
38
- /** The ref exactly as it was written, for every message. */
39
- readonly search: string;
40
- /** Whether a question may be asked. A run that may not fails instead. */
41
- readonly interactive: boolean;
42
- /** Take the first match rather than asking, because `--accept-first` was passed. */
43
- readonly acceptFirst: boolean;
44
- /** The question to ask when there is more than one match. */
45
- readonly prompt?: string;
46
- /**
47
- * Write the facts about what the ref resolved to. On by default; a caller
48
- * that reports the resolution itself turns it off, and is still told when
49
- * `--accept-first` settled an ambiguous word.
50
- */
51
- readonly announce: boolean;
52
- /** Extra lines for the error a ref that matched nothing raises. */
53
- readonly details: string[];
54
- /** Where a resolution is reported. Defaults to the console. */
55
- readonly write: (message: string) => void;
56
- /** How the choice is asked. Defaults to the shared choice prompt. */
57
- readonly choose?: (message: string, matches: SousRef[]) => Promise<SousRef>;
58
-
59
- /**
60
- * @param init - What was searched for, and whether a question may be asked.
61
- */
62
- constructor(init: {
63
- search: string;
64
- interactive: boolean;
65
- acceptFirst?: boolean;
66
- prompt?: string;
67
- announce?: boolean;
68
- details?: string[];
69
- write?: (message: string) => void;
70
- choose?: (message: string, matches: SousRef[]) => Promise<SousRef>;
71
- }) {
72
- this.search = init.search;
73
- this.interactive = init.interactive;
74
- this.acceptFirst = init.acceptFirst ?? false;
75
- if (init.prompt !== undefined) this.prompt = init.prompt;
76
- this.announce = init.announce ?? true;
77
- this.details = init.details ?? [];
78
- this.write = init.write ?? ((message: string) => log(message));
79
- if (init.choose !== undefined) this.choose = init.choose;
80
- }
81
- }
82
-
83
- /**
84
- * The facts one resolved ref carries, ready for `formatResolvedReference`.
85
- *
86
- * The ref knows what it is; this decides which of its fields are facts worth
87
- * showing. A repository's own name is already the resolved spelling, so it is
88
- * not repeated as a line of its own. What a ref resolves to is always its
89
- * fully qualified key; for an environment variable name that is the key of the
90
- * variable it answers, because that is what the rest of the run proceeds with.
91
- *
92
- * @param ref - The ref the run proceeds with.
93
- * @param search - The ref exactly as it was written.
94
- * @param reason - The closing sentence, when the caller has one of its own.
95
- */
96
- export function resolvedRefFacts(
97
- ref: SousRef,
98
- search: string,
99
- reason?: string
100
- ): ResolvedReferenceFacts {
101
- const subject = ref.kind === "envVar" ? (ref.variables?.[0] ?? ref) : ref;
102
- const resolvedTo = refKey(subject);
103
- const repoName = repoOf(subject)?.name;
104
- const recipe = recipeOf(subject);
105
- const namespace = namespaceOf(subject);
106
-
107
- return {
108
- search,
109
- resolvedTo,
110
- kind: refKindLabel(ref),
111
- ...(subject.kind === "variable" ? { variable: subject.name } : {}),
112
- ...(recipe === undefined || subject.kind === "namespace" ? {} : { recipe: recipe.name }),
113
- ...(namespace === undefined ? {} : { namespace: namespace.name }),
114
- ...(repoName === undefined || repoName === resolvedTo ? {} : { repository: repoName }),
115
- ...(ref.kind === "repo" && ref.location !== undefined ? { location: ref.location.url } : {}),
116
- ...(ref.kind !== "repo" && subject.description !== undefined
117
- ? { description: subject.description }
118
- : {}),
119
- ...(reason === undefined ? {} : { reason }),
120
- };
121
- }
122
-
123
- /** Chooses the one ref a run proceeds with. */
124
- export class RefPickerService {
125
- /**
126
- * The one ref a run proceeds with.
127
- *
128
- * @param matches - What the ref could have meant, in listing order.
129
- * @param args - What was searched for, and whether a question may be asked.
130
- * @returns The ref the run proceeds with.
131
- */
132
- async pick(matches: SousRef[], args: RefPickArguments): Promise<SousRef> {
133
- if (matches.length === 0) {
134
- throw new ConfigError(
135
- [`Nothing called '${args.search}' was found.`, ...args.details].join("\n")
136
- );
137
- }
138
-
139
- const first = matches[0]!;
140
-
141
- if (matches.length === 1) {
142
- if (args.announce) this.announce(args, first);
143
- return first;
144
- }
145
-
146
- if (args.acceptFirst) {
147
- const reason =
148
- `'${args.search}' named ${matches.length} things, and ` +
149
- `'${ACCEPT_FIRST_FLAG}' was passed, so the first one listed is being used.`;
150
-
151
- // A caller that reports the resolution itself still has to be told that the
152
- // word was ambiguous, so the sentence is written even when the facts are not.
153
- if (args.announce) {
154
- this.announce(args, first, reason);
155
- } else {
156
- for (const line of formatParagraph(reason)) args.write(line);
157
- }
158
- return first;
159
- }
160
-
161
- if (!args.interactive) {
162
- // The example is a spelling that says more than what was typed, so a
163
- // ref that is already its own fully qualified name is not offered back
164
- // unchanged.
165
- const example = refSpelling(
166
- matches.find((match) => refSpelling(match) !== args.search) ?? first
167
- );
168
-
169
- throw nonInteractiveError({
170
- prompt: `which '${args.search}' you meant`,
171
- remedy:
172
- `write the full reference (for example '${example}'), or pass ` +
173
- `'${ACCEPT_FIRST_FLAG}' to take the first candidate listed above.`,
174
- details: [
175
- `'${args.search}' matched ${matches.length} things:`,
176
- ...matches.map((match) => ` ${describeRef(match)}`),
177
- ],
178
- });
179
- }
180
-
181
- const choose =
182
- args.choose ??
183
- ((message: string, offered: SousRef[]) =>
184
- askChoice(
185
- message,
186
- offered.map((match) => ({ name: describeRef(match), value: match }))
187
- ));
188
-
189
- return choose(args.prompt ?? `Which '${args.search}' did you mean?`, matches);
190
- }
191
-
192
- /**
193
- * Writes what a ref resolved to, as the key and value list every other set
194
- * of facts in the CLI is written as, followed by one sentence saying why that
195
- * candidate won.
196
- */
197
- private announce(args: RefPickArguments, ref: SousRef, reason?: string): void {
198
- for (const line of formatResolvedReference(resolvedRefFacts(ref, args.search, reason))) {
199
- args.write(line);
200
- }
201
- }
202
- }
203
-
204
- makeInjectable(RefPickerService);
@@ -1,84 +0,0 @@
1
- /**
2
- * Saying what a reference resolved to.
3
- *
4
- * A word on the command line can name a repository, a namespace, a recipe or a
5
- * variable, and when sous settles which one it means it has to say so. What it
6
- * resolved to is a set of facts, so it is written as the same key and value
7
- * list every other set of facts in the CLI is written as, with one short
8
- * sentence after it saying why that candidate won. One function builds it, so
9
- * every command that resolves a reference reports it the same way; `RefPickerService`
10
- * in `ref-picker-service.ts` is the single caller, which is how every command gets it.
11
- */
12
-
13
- import {
14
- formatVariable,
15
- indent,
16
- wrapColumns,
17
- wrapText,
18
- type VariableEntry,
19
- } from "../../utils/formatting.js";
20
-
21
- /** What a resolved reference is, in the words the report shows. */
22
- export interface ResolvedReferenceFacts {
23
- /** The reference exactly as the person wrote it. */
24
- search: string;
25
- /** The fully qualified spelling the run proceeds with. */
26
- resolvedTo: string;
27
- /** What kind of thing it turned out to be: "recipe", "namespace", "repository". */
28
- kind: string;
29
- /** The repository publishing it. */
30
- repository?: string;
31
- /** The namespace it lives in, when it has one. */
32
- namespace?: string;
33
- /** The recipe itself, when the reference named one. */
34
- recipe?: string;
35
- /** The variable itself, when the reference named one. */
36
- variable?: string;
37
- /** Where a repository lives, when the reference named a repository. */
38
- location?: string;
39
- /** The publisher's one-line summary, when there is one. */
40
- description?: string;
41
- /**
42
- * Why this candidate won, as the closing sentence. The default says the
43
- * reference named one thing and nothing else; a caller that settled it some
44
- * other way (taking the first of several, say) supplies its own.
45
- */
46
- reason?: string;
47
- }
48
-
49
- /**
50
- * The report as lines to write: the facts, a blank line, and the sentence.
51
- *
52
- * @param facts - What the reference resolved to.
53
- * @returns The lines to write, ready for the console.
54
- */
55
- export function formatResolvedReference(facts: ResolvedReferenceFacts): string[] {
56
- const entries: VariableEntry[] = [
57
- { label: "Resolved to", value: facts.resolvedTo },
58
- ...(facts.variable === undefined ? [] : [{ label: "Variable", value: facts.variable }]),
59
- ...(facts.recipe === undefined ? [] : [{ label: "Recipe", value: facts.recipe }]),
60
- ...(facts.namespace === undefined ? [] : [{ label: "Namespace", value: facts.namespace }]),
61
- ...(facts.repository === undefined
62
- ? []
63
- : [{ label: "Repository", value: facts.repository }]),
64
- ...(facts.location === undefined ? [] : [{ label: "Location", value: facts.location }]),
65
- ...(facts.description === undefined
66
- ? []
67
- : [{ label: "Description", value: facts.description }]),
68
- ];
69
-
70
- const labelWidth = Math.max(...entries.map((entry) => entry.label.length));
71
- const lines = entries.flatMap((entry) => formatVariable(entry, { labelWidth }));
72
-
73
- const reason =
74
- facts.reason ??
75
- `'${facts.search}' named one ${facts.kind}, and nothing else, so that is what ` +
76
- `is being used.`;
77
-
78
- lines.push("");
79
- for (const line of wrapText(reason, wrapColumns() - 4)) {
80
- lines.push(indent(line));
81
- }
82
-
83
- return lines;
84
- }
@@ -1,47 +0,0 @@
1
- import type { RefLookup } from "./lookups/ref-lookup.js";
2
- import { RefSource } from "./source.js";
3
- import type { RefKind } from "./types.js";
4
-
5
- /** What `RefResolverService.resolve` is asked. */
6
- export class RefResolveArguments {
7
- /** The ref exactly as it was written. */
8
- readonly input: string;
9
- /** Where it was written, which decides the forms allowed. */
10
- readonly from: RefSource;
11
- /**
12
- * What exists. When given, the readings narrow to the ones it knows, spelled
13
- * the way they are published; when absent, the readings the place allows are
14
- * returned as they were read.
15
- */
16
- readonly lookup?: RefLookup;
17
- /**
18
- * The kinds of ref the caller accepts. Readings of any other kind are set
19
- * aside before the lookup is asked, so a word that is a repository exactly
20
- * and a namespace only ignoring case is still found as the namespace by a
21
- * caller that accepts namespaces alone.
22
- */
23
- readonly kinds?: readonly RefKind[];
24
- /**
25
- * True when a ref the place refuses in every reading (an empty word, a path
26
- * of three segments) names nothing instead of raising the refusal. A command
27
- * that takes any word and answers "nothing called that was found" uses it.
28
- */
29
- readonly refusedIsEmpty: boolean;
30
-
31
- /**
32
- * @param init - The ref, where it was written (the command line by default), what exists, the kinds accepted, and whether a refused ref names nothing.
33
- */
34
- constructor(init: {
35
- input: string;
36
- from?: RefSource;
37
- lookup?: RefLookup;
38
- kinds?: readonly RefKind[];
39
- refusedIsEmpty?: boolean;
40
- }) {
41
- this.input = init.input;
42
- this.from = init.from ?? RefSource.CommandLine;
43
- if (init.lookup !== undefined) this.lookup = init.lookup;
44
- if (init.kinds !== undefined) this.kinds = init.kinds;
45
- this.refusedIsEmpty = init.refusedIsEmpty ?? false;
46
- }
47
- }
@@ -1,36 +0,0 @@
1
- import type { DroppedRef } from "./pruners/ref-pruner.js";
2
- import type { SousRef } from "./types.js";
3
-
4
- /** What `RefResolverService.resolve` found. */
5
- export class RefResolveResult {
6
- /**
7
- * @param input - The ref exactly as it was written.
8
- * @param refs - The refs it names, best first: the more qualified the spelling that
9
- * matched, the earlier. Empty when a lookup knew none of the readings.
10
- * @param dropped - The readings the place refused, with the reason for each.
11
- * @param warnings - Sentences about readings that were kept all the same.
12
- * @param usedLookup - True when `refs` were narrowed against what exists.
13
- */
14
- constructor(
15
- readonly input: string,
16
- readonly refs: SousRef[],
17
- readonly dropped: DroppedRef[],
18
- readonly warnings: string[],
19
- readonly usedLookup: boolean
20
- ) {}
21
-
22
- /** True when exactly one ref is left, so the caller has nothing to decide. */
23
- get isUnique(): boolean {
24
- return this.refs.length === 1;
25
- }
26
-
27
- /** True when no ref is left. */
28
- get isEmpty(): boolean {
29
- return this.refs.length === 0;
30
- }
31
-
32
- /** The best ref, or undefined when none is left. */
33
- first(): SousRef | undefined {
34
- return this.refs[0];
35
- }
36
- }
@@ -1,323 +0,0 @@
1
- /**
2
- * The ref resolver: parse a written ref, prune it by where it was written,
3
- * and, when the caller knows what exists, narrow it to what is there.
4
- *
5
- * It orchestrates three parts that know nothing of one another: a `RefParser`
6
- * (every reading of the text), a `RefPruner` per place (which readings that
7
- * place allows) and, per call, a `RefLookup` (which readings exist). What to do
8
- * about a ref that is still ambiguous, and how to find the ref inside the text
9
- * around it, are the caller's decisions; the helpers here and in
10
- * `ref-helpers.ts` assist with both.
11
- */
12
-
13
- import { makeInjectable } from "./injectable.js";
14
- import { REF_TOKENS } from "./tokens.js";
15
- import { ConfigError } from "../../lib/errors.js";
16
- import { refIdentity } from "./format.js";
17
- import type { RefLookup, RefMatch, SyncRefLookup } from "./lookups/ref-lookup.js";
18
- import { repoOf } from "./parts.js";
19
- import type { RefParser } from "./parser/ref-parser.js";
20
- import { DroppedRef, type RefPruner } from "./pruners/ref-pruner.js";
21
- import { RefResolveArguments } from "./ref-resolve-arguments.js";
22
- import { RefResolveResult } from "./ref-resolve-result.js";
23
- import { RefSource } from "./source.js";
24
- import { REF_KINDS, type SousRef } from "./types.js";
25
-
26
- /** One ref found inside a longer text. */
27
- export type RefInString = {
28
- /** The text that parsed, with the punctuation around it taken off. */
29
- text: string;
30
- /** Where it starts in the string. */
31
- index: number;
32
- /** What it resolved to. */
33
- result: RefResolveResult;
34
- };
35
-
36
- /**
37
- * How qualified the spelling that produced a candidate was. Lower is more
38
- * specific, and the listing order follows it: a fully qualified ref first, then
39
- * a partly qualified one, then a bare name, and an environment variable name
40
- * last (the least likely reading of a plain word).
41
- *
42
- * @param candidate - One reading of a written ref.
43
- */
44
- export function qualificationOf(candidate: SousRef): number {
45
- if (candidate.kind === "envVar") return 3;
46
- if (candidate.kind === "repo" || repoOf(candidate) !== undefined) return 0;
47
- const partial =
48
- (candidate.kind === "recipe" && candidate.namespace !== undefined) ||
49
- candidate.kind === "recipeFile" ||
50
- (candidate.kind === "variable" && candidate.recipe !== undefined);
51
- return partial ? 1 : 2;
52
- }
53
-
54
- /** Resolves written refs. */
55
- export class RefResolverService {
56
- /**
57
- * @param parser - Reads every way a text can be a ref.
58
- * @param pruners - Decide, one place each, which readings count there.
59
- */
60
- constructor(
61
- private readonly parser: RefParser,
62
- private readonly pruners: RefPruner[]
63
- ) {}
64
-
65
- /**
66
- * Parses a ref and prunes it by where it was written, with no lookup.
67
- *
68
- * @param input - The ref exactly as it was written.
69
- * @param from - Where it was written.
70
- * @throws A ConfigError listing every reason when the place refuses every reading.
71
- */
72
- parse(input: string, from: RefSource = RefSource.CommandLine): RefResolveResult {
73
- const { kept, dropped, warnings, place } = this.prune(input, from);
74
- if (kept.length === 0) throw this.refusal(input, place, dropped);
75
- return new RefResolveResult(input, this.order(kept.map((ref) => ({ ref, rank: ref }))), dropped, warnings, false);
76
- }
77
-
78
- /**
79
- * Parses a ref, prunes it by where it was written and, when a lookup is
80
- * given, narrows it to what exists. When `kinds` is given, readings of other
81
- * kinds are set aside first, and so are the matches of other kinds. A browser
82
- * URL is the one reading that is a repository until a lookup settles it into
83
- * the namespace or recipe it names, so it is kept for a caller that accepts
84
- * namespaces or recipes.
85
- *
86
- * With a lookup, every kept reading is asked; the exact-spelling matches are
87
- * kept when there are any, and the case-insensitive ones otherwise; matches
88
- * are de-duplicated and returned in the spelling they are published in. A
89
- * ref nothing knows comes back with no refs; that is for the caller to
90
- * report. A lookup that could not find out throws.
91
- *
92
- * @param args - The ref, where it was written, and what exists.
93
- * @throws A ConfigError listing every reason when the place refuses every reading.
94
- */
95
- async resolve(args: RefResolveArguments): Promise<RefResolveResult> {
96
- const early = this.beginResolve(args);
97
- if ("result" in early) return early.result;
98
- const found: Array<{ match: RefMatch; rank: SousRef }> = [];
99
- if (early.lookup !== undefined) {
100
- for (const candidate of early.kept) {
101
- for (const match of await early.lookup.find(candidate)) {
102
- found.push({ match, rank: candidate });
103
- }
104
- }
105
- }
106
- return this.finishResolve(args, early, found);
107
- }
108
-
109
- /**
110
- * The same as `resolve`, for a caller that cannot wait: it asks the lookup's
111
- * `findSync`, so the lookup must have one. A template engine's path
112
- * resolution is synchronous, and is why this exists.
113
- *
114
- * @param args - The ref, where it was written, and what exists (a lookup with `findSync`).
115
- * @throws A ConfigError listing every reason when the place refuses every reading.
116
- */
117
- resolveSync(args: RefResolveArguments): RefResolveResult {
118
- const early = this.beginResolve(args);
119
- if ("result" in early) return early.result;
120
- const found: Array<{ match: RefMatch; rank: SousRef }> = [];
121
- if (early.lookup !== undefined) {
122
- const lookup = early.lookup as Partial<SyncRefLookup>;
123
- if (lookup.findSync === undefined) {
124
- throw new ConfigError("resolveSync needs a lookup that has a findSync method.");
125
- }
126
- for (const candidate of early.kept) {
127
- for (const match of lookup.findSync(candidate)) found.push({ match, rank: candidate });
128
- }
129
- }
130
- return this.finishResolve(args, early, found);
131
- }
132
-
133
- /**
134
- * Parses and prunes without refusing: the readings a place keeps and the
135
- * ones it drops, with the reason for each. For a caller that explains a
136
- * refusal in its own words.
137
- *
138
- * @param input - The ref exactly as it was written.
139
- * @param from - Where it was written.
140
- * @throws A ConfigError when the text is not a ref in any reading.
141
- */
142
- inspect(input: string, from: RefSource): { kept: SousRef[]; dropped: DroppedRef[] } {
143
- const { kept, dropped } = this.prune(input, from);
144
- return { kept, dropped };
145
- }
146
-
147
- /** Everything `resolve` does before it asks a lookup, or the answer when it needs none. */
148
- private beginResolve(
149
- args: RefResolveArguments
150
- ):
151
- | { result: RefResolveResult }
152
- | {
153
- kept: SousRef[];
154
- dropped: DroppedRef[];
155
- warnings: string[];
156
- lookup: RefLookup | undefined;
157
- } {
158
- let pruned;
159
- try {
160
- pruned = this.prune(args.input, args.from);
161
- } catch (error) {
162
- // A text no reading exists for (empty, say) is refused the same way.
163
- if (args.refusedIsEmpty) return { result: new RefResolveResult(args.input, [], [], [], true) };
164
- throw error;
165
- }
166
- const { dropped, warnings, place } = pruned;
167
- if (pruned.kept.length === 0) {
168
- if (args.refusedIsEmpty) {
169
- return { result: new RefResolveResult(args.input, [], dropped, warnings, true) };
170
- }
171
- throw this.refusal(args.input, place, dropped);
172
- }
173
- const kinds = args.kinds;
174
- const kept =
175
- kinds === undefined
176
- ? pruned.kept
177
- : pruned.kept.filter(
178
- (ref) =>
179
- kinds.includes(ref.kind) ||
180
- (ref.kind === "repo" &&
181
- ref.browsed !== undefined &&
182
- (kinds.includes("namespace") || kinds.includes("recipe")))
183
- );
184
-
185
- if (args.lookup === undefined) {
186
- return {
187
- result: new RefResolveResult(
188
- args.input,
189
- this.order(kept.map((ref) => ({ ref, rank: ref }))),
190
- dropped,
191
- warnings,
192
- false
193
- ),
194
- };
195
- }
196
- return { kept, dropped, warnings, lookup: args.lookup };
197
- }
198
-
199
- /** Everything `resolve` does with what a lookup found. */
200
- private finishResolve(
201
- args: RefResolveArguments,
202
- early: { dropped: DroppedRef[]; warnings: string[] },
203
- found: Array<{ match: RefMatch; rank: SousRef }>
204
- ): RefResolveResult {
205
- const kinds = args.kinds;
206
- const matching = found.filter(
207
- (entry) => kinds === undefined || kinds.includes(entry.match.ref.kind)
208
- );
209
- const exact = matching.filter((entry) => entry.match.exactSpelling);
210
- const chosen = exact.length > 0 ? exact : matching;
211
-
212
- return new RefResolveResult(
213
- args.input,
214
- this.order(chosen.map((entry) => ({ ref: entry.match.ref, rank: entry.rank }))),
215
- early.dropped,
216
- early.warnings,
217
- true
218
- );
219
- }
220
-
221
- /**
222
- * True when the ref parses and the place allows at least one reading of it.
223
- *
224
- * @param input - The ref exactly as it was written.
225
- * @param from - Where it was written.
226
- */
227
- isValidRef(input: string, from: RefSource = RefSource.CommandLine): boolean {
228
- try {
229
- this.parse(input, from);
230
- return true;
231
- } catch {
232
- return false;
233
- }
234
- }
235
-
236
- /**
237
- * Every ref found in a longer text, one per whitespace-separated word that
238
- * is a valid ref where it was written, with the quotes, brackets and
239
- * sentence punctuation around the word taken off. Words that are not refs
240
- * are left out.
241
- *
242
- * getRefsInString("see 'workflow/task-files', then run it.");
243
- * // -> workflow/task-files, and the other words that happen to read as a ref
244
- *
245
- * @param text - The text to search.
246
- * @param from - Where the text was written.
247
- */
248
- getRefsInString(text: string, from: RefSource = RefSource.CommandLine): RefInString[] {
249
- const found: RefInString[] = [];
250
- for (const word of text.matchAll(/\S+/g)) {
251
- const bare = word[0].replace(/^[`"'([<{]+/, "").replace(/[`"')\]>},.;:!]+$/, "");
252
- if (bare.length === 0) continue;
253
- const start = word.index + word[0].indexOf(bare);
254
- try {
255
- found.push({ text: bare, index: start, result: this.parse(bare, from) });
256
- } catch {
257
- // Not a ref; the words around it may be.
258
- }
259
- }
260
- return found;
261
- }
262
-
263
- /** Parses and prunes, and says where the ref was written in words. */
264
- private prune(
265
- input: string,
266
- from: RefSource
267
- ): { kept: SousRef[]; dropped: DroppedRef[]; warnings: string[]; place: string } {
268
- const pruners = this.pruners.filter((pruner) => pruner.source === from);
269
- if (pruners.length === 0) {
270
- throw new ConfigError(`No pruner is registered for a ref written as '${from}'.`);
271
- }
272
-
273
- let kept = this.parser.parse(input);
274
- const dropped: DroppedRef[] = [];
275
- const warnings: string[] = [];
276
- for (const pruner of pruners) {
277
- const result = pruner.prune(kept, input.trim());
278
- kept = result.kept;
279
- dropped.push(...result.dropped);
280
- warnings.push(...result.warnings);
281
- }
282
- return { kept, dropped, warnings, place: pruners[0]!.place };
283
- }
284
-
285
- /** The error for a ref the place refuses in every reading. */
286
- private refusal(input: string, place: string, dropped: DroppedRef[]): ConfigError {
287
- const reasons = [...new Set(dropped.map((entry) => entry.describe()))];
288
- if (reasons.length === 1) {
289
- return new ConfigError(`The ref '${input.trim()}' cannot be written ${place}: ${reasons[0]}`);
290
- }
291
- return new ConfigError(
292
- `The ref '${input.trim()}' cannot be written ${place}. Every way it reads is refused:\n` +
293
- reasons.map((reason) => ` - ${reason}`).join("\n")
294
- );
295
- }
296
-
297
- /**
298
- * Sorts refs into the documented listing order and drops any the same
299
- * search found twice: by how qualified the spelling that produced each was,
300
- * then by kind, then in the order they came (a lookup lists repositories in
301
- * the order the project searches them).
302
- */
303
- private order(entries: Array<{ ref: SousRef; rank: SousRef }>): SousRef[] {
304
- const indexed = entries.map((entry, position) => ({ ...entry, position }));
305
- indexed.sort(
306
- (left, right) =>
307
- qualificationOf(left.rank) - qualificationOf(right.rank) ||
308
- REF_KINDS.indexOf(left.ref.kind) - REF_KINDS.indexOf(right.ref.kind) ||
309
- left.position - right.position
310
- );
311
- const seen = new Set<string>();
312
- return indexed
313
- .map((entry) => entry.ref)
314
- .filter((ref) => {
315
- const identity = `${refIdentity(ref)} ${JSON.stringify(ref.vars ?? {})}`;
316
- if (seen.has(identity)) return false;
317
- seen.add(identity);
318
- return true;
319
- });
320
- }
321
- }
322
-
323
- makeInjectable(RefResolverService, [REF_TOKENS.Parser, { multi: REF_TOKENS.Pruner }]);