@sous-io/sous 0.2.36 → 1.0.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.
Files changed (244) hide show
  1. package/README.md +14 -256
  2. package/dist/bin.js +11 -0
  3. package/dist/sous.js +1093 -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 -237
  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 -391
  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 -412
  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 -1105
  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,62 +0,0 @@
1
- import { locationOf } from "../parts.js";
2
- import type { SousRef } from "../types.js";
3
- import type { RefLookup, RefMatch } from "./ref-lookup.js";
4
-
5
- /** What a repository's index recorded about one dependency, as far as settling needs. */
6
- export type RecordedDependencies = Record<string, { repo?: string } | undefined>;
7
-
8
- /**
9
- * Whether what a version's index entry recorded confirms a candidate reading
10
- * of one of its dependencies. This is the rule `RecordedDependencyLookup`
11
- * answers with, for callers that settle a dependency without waiting.
12
- *
13
- * @param candidate - One reading of the dependency as the manifest wrote it.
14
- * @param recorded - What the declaring version's index entry records, keyed `namespace/recipe`.
15
- */
16
- export function isConfirmedByRecord(
17
- candidate: SousRef,
18
- recorded: RecordedDependencies | undefined
19
- ): boolean {
20
- const identity = locationOf(candidate)?.identity;
21
- const records = Object.entries(recorded ?? {}).filter(([, record]) =>
22
- identity === undefined ? record?.repo === undefined : record?.repo === identity
23
- );
24
-
25
- switch (candidate.kind) {
26
- case "repo":
27
- return candidate.browsed !== undefined && identity !== undefined && records.length > 0;
28
- case "namespace":
29
- return records.some(([key]) => key.startsWith(`${candidate.name}/`));
30
- case "recipe":
31
- return (
32
- candidate.namespace !== undefined &&
33
- records.some(([key]) => key === `${candidate.namespace!.name}/${candidate.name}`)
34
- );
35
- default:
36
- return false;
37
- }
38
- }
39
-
40
- /**
41
- * Answers from what a published version's index entry recorded about its
42
- * dependencies, and never searches.
43
- *
44
- * A release settles a dependency that reads more than one way (a GitLab URL
45
- * with nested groups, a browser URL) and records the repository it settled on
46
- * beside each key it resolved; a consumer reads that record and never probes.
47
- * A candidate is confirmed when a record says so: a namespace by a record
48
- * under a key inside it, a recipe by a record under its own key, a browser
49
- * path by any record carrying its repository. A candidate that names no
50
- * location is confirmed by a record that names none, which is a sibling in the
51
- * declaring recipe's own repository.
52
- */
53
- export class RecordedDependencyLookup implements RefLookup {
54
- /**
55
- * @param recorded - What the declaring version's index entry records, keyed `namespace/recipe`.
56
- */
57
- constructor(private readonly recorded: RecordedDependencies | undefined) {}
58
-
59
- async find(candidate: SousRef): Promise<RefMatch[]> {
60
- return isConfirmedByRecord(candidate, this.recorded) ? [{ ref: candidate, exactSpelling: true }] : [];
61
- }
62
- }
@@ -1,54 +0,0 @@
1
- /**
2
- * Lookups: what exists, as a queryable object rather than a list.
3
- *
4
- * A caller that knows what exists (the cached indexes, a release's recorded
5
- * dependencies, the variable definitions in play) hands the resolver a lookup,
6
- * and the candidate readings narrow to the ones that are actually available.
7
- */
8
-
9
- import type { SousRef } from "../types.js";
10
-
11
- /** One thing a candidate reading turned out to name. */
12
- export type RefMatch = {
13
- /**
14
- * The known ref, spelled the way it is published (not the way it was
15
- * typed), with every parent it has.
16
- */
17
- ref: SousRef;
18
- /**
19
- * True when every part of the candidate matched in exactly the case it was
20
- * written. A lookup returns case-insensitive matches too; the resolver keeps
21
- * them only when nothing matched exactly.
22
- */
23
- exactSpelling: boolean;
24
- };
25
-
26
- /**
27
- * Answers what a candidate reading names.
28
- *
29
- * `find` returns an empty array when nothing known matches the candidate, and
30
- * throws when the lookup could not find out (an index that could not be
31
- * fetched): not finding an answer and failing to look are different things.
32
- */
33
- export interface RefLookup {
34
- /**
35
- * Every known ref a candidate names.
36
- *
37
- * @param candidate - One reading of a written ref.
38
- */
39
- find(candidate: SousRef): Promise<RefMatch[]>;
40
- }
41
-
42
- /**
43
- * A lookup that can also answer without waiting, for a caller whose own
44
- * interface is synchronous (a template engine resolving a path). It answers
45
- * exactly what `find` answers.
46
- */
47
- export interface SyncRefLookup extends RefLookup {
48
- /**
49
- * Every known ref a candidate names, at once.
50
- *
51
- * @param candidate - One reading of a written ref.
52
- */
53
- findSync(candidate: SousRef): RefMatch[];
54
- }
@@ -1,123 +0,0 @@
1
- import type { DefinedVariable } from "../../../lib/vars/definition-source.js";
2
- import { matchName, type NameMatch } from "../glob.js";
3
- import { refKey } from "../format.js";
4
- import type { RepoRef, SousRef, VariableRef } from "../types.js";
5
- import { CatalogMatcher, type CatalogRepo } from "./catalog-matcher.js";
6
- import type { RefLookup, RefMatch } from "./ref-lookup.js";
7
-
8
- /**
9
- * The ref a defined variable is known as: its name, with the recipe that
10
- * published it, that recipe's namespace and the repository's short name nested
11
- * as parents.
12
- *
13
- * @param defined - The definition and the recipe that published it.
14
- */
15
- export function variableRefOf(defined: DefinedVariable): VariableRef {
16
- const { repo, namespace, name } = defined.recipe;
17
- return {
18
- kind: "variable",
19
- name: defined.definition.name,
20
- description: defined.definition.prompt,
21
- recipe: {
22
- kind: "recipe",
23
- name,
24
- namespace: { kind: "namespace", name: namespace, repo: { kind: "repo", name: repo } },
25
- },
26
- };
27
- }
28
-
29
- /** The weaker of two matches: one folded part makes the whole thing folded. */
30
- function both(left: NameMatch, right: NameMatch): NameMatch {
31
- if (left === undefined || right === undefined) return undefined;
32
- return left === "folded" || right === "folded" ? "folded" : "exact";
33
- }
34
-
35
- /**
36
- * Answers from the variable definitions in play, which is what a variables
37
- * command searches: variables themselves (by name, or qualified by recipe,
38
- * namespace and repository, in any combination) and the repositories,
39
- * namespaces and recipes that publish them. A namespace that publishes no
40
- * variable this project holds has no questions to ask, so naming it would
41
- * resolve to nothing rather than to an empty set of questions. Building the
42
- * answer from the definitions also means `sous vars ask --file` resolves refs
43
- * exactly as a subscribed project does.
44
- */
45
- export class VariableLookup implements RefLookup {
46
- private readonly matcher: CatalogMatcher;
47
-
48
- /**
49
- * @param variables - The variable definitions in play, with the recipe that published each.
50
- * @param trusted - The trusted repositories, which lend each repository its location
51
- * and each recipe its folder, so a ref written as a location settles here too.
52
- */
53
- constructor(
54
- private readonly variables: DefinedVariable[],
55
- private readonly trusted: CatalogRepo[] = []
56
- ) {
57
- const repos = new Map<string, CatalogRepo>();
58
-
59
- for (const defined of variables) {
60
- const { repo: repoName, namespace, name: recipe } = defined.recipe;
61
- const known = trusted.find((entry) => entry.name === repoName);
62
-
63
- let repo = repos.get(repoName);
64
- if (repo === undefined) {
65
- repo = {
66
- name: repoName,
67
- namespaces: [],
68
- recipes: [],
69
- ...(known?.location === undefined ? {} : { location: known.location }),
70
- };
71
- repos.set(repoName, repo);
72
- }
73
- if (!repo.namespaces.some((entry) => entry.name === namespace)) {
74
- repo.namespaces.push({ name: namespace });
75
- }
76
- if (!repo.recipes.some((entry) => entry.namespace === namespace && entry.name === recipe)) {
77
- const path = known?.recipes.find(
78
- (entry) => entry.namespace === namespace && entry.name === recipe
79
- )?.path;
80
- repo.recipes.push({ namespace, name: recipe, ...(path === undefined ? {} : { path }) });
81
- }
82
- }
83
- this.matcher = new CatalogMatcher([...repos.values()]);
84
- }
85
-
86
- async find(candidate: SousRef): Promise<RefMatch[]> {
87
- if (candidate.kind !== "variable") return this.matcher.match(candidate);
88
-
89
- const qualifier: RepoRef | undefined =
90
- candidate.recipe?.namespace?.repo ?? candidate.recipe?.repo ?? candidate.repo;
91
- const matches: RefMatch[] = [];
92
-
93
- for (const defined of this.variables) {
94
- const { repo, namespace, name: recipe } = defined.recipe;
95
- const location = this.trusted.find((entry) => entry.name === repo)?.location;
96
-
97
- let spelling: NameMatch = "exact";
98
- if (qualifier?.name !== undefined) spelling = matchName(qualifier.name, repo);
99
- if (qualifier?.location !== undefined && location?.identity !== qualifier.location.identity) {
100
- spelling = undefined;
101
- }
102
- if (candidate.recipe !== undefined) {
103
- spelling = both(spelling, matchName(candidate.recipe.name, recipe));
104
- if (candidate.recipe.namespace !== undefined) {
105
- spelling = both(spelling, matchName(candidate.recipe.namespace.name, namespace));
106
- }
107
- }
108
- spelling = both(spelling, matchName(candidate.name, defined.definition.name));
109
- if (spelling === undefined) continue;
110
-
111
- matches.push({
112
- ref: {
113
- ...variableRefOf(defined),
114
- ...(candidate.vars === undefined ? {} : { vars: candidate.vars }),
115
- },
116
- exactSpelling: spelling === "exact",
117
- });
118
- }
119
- return matches.sort((left, right) =>
120
- refKey(left.ref) < refKey(right.ref) ? -1 : refKey(left.ref) > refKey(right.ref) ? 1 : 0
121
- );
122
- }
123
- }
@@ -1,144 +0,0 @@
1
- /**
2
- * Namespaces and recipes by their stored names: what a lockfile, an index, a
3
- * config key and a manifest entry hold. A `NamedRef` is a namespace, or a
4
- * recipe with its namespace, and `shortKey` (in `pruners/rule-helpers.ts`) is
5
- * the `namespace` or `namespace/recipe` it is stored under.
6
- *
7
- * Callers with a stored key use `splitRecipeKey` and `namespaceOfKey`, which
8
- * read it through the resolver with `RefSource.Lockfile`, so a key is read by
9
- * the same code wherever it came from.
10
- */
11
-
12
- import { ConfigError } from "../../lib/errors.js";
13
- import { sharedRefResolver } from "./container.js";
14
- import { locationOf } from "./parts.js";
15
- import { RefSource } from "./source.js";
16
- import type { NamespaceRef, RecipeRef, SousRef } from "./types.js";
17
-
18
- /** A namespace, or a recipe that names its namespace. */
19
- export type NamedRef = NamespaceRef | RecipeRef;
20
-
21
- /**
22
- * True when a ref is a namespace or a recipe that names its namespace.
23
- *
24
- * @param ref - Any ref.
25
- */
26
- export function isNamedRef(ref: SousRef): ref is NamedRef {
27
- return ref.kind === "namespace" || (ref.kind === "recipe" && ref.namespace !== undefined);
28
- }
29
-
30
- /**
31
- * A namespace ref, optionally inside a repository known by short name.
32
- *
33
- * @param name - The namespace.
34
- * @param repo - The repository's short name, when the ref is qualified by one.
35
- */
36
- export function namespaceRef(name: string, repo?: string): NamespaceRef {
37
- return {
38
- kind: "namespace",
39
- name,
40
- ...(repo === undefined ? {} : { repo: { kind: "repo", name: repo } }),
41
- };
42
- }
43
-
44
- /**
45
- * A recipe ref with its namespace, optionally inside a repository known by
46
- * short name and with a version range.
47
- *
48
- * @param namespace - The recipe's namespace.
49
- * @param name - The recipe.
50
- * @param options - The repository's short name and the range, when there are any.
51
- */
52
- export function recipeRef(
53
- namespace: string,
54
- name: string,
55
- options: { repo?: string; range?: string } = {}
56
- ): RecipeRef {
57
- return {
58
- kind: "recipe",
59
- name,
60
- namespace: namespaceRef(namespace, options.repo),
61
- ...(options.range === undefined ? {} : { range: options.range }),
62
- };
63
- }
64
-
65
- /**
66
- * The same namespace or recipe, qualified by a repository's short name. A
67
- * recipe holds its repository through its namespace.
68
- *
69
- * @param ref - A namespace or a recipe.
70
- * @param repo - The repository's short name; left undefined, the ref comes back as it is.
71
- */
72
- export function withRepoName(ref: NamedRef, repo: string | undefined): NamedRef {
73
- if (repo === undefined) return ref;
74
- if (ref.kind === "namespace") return { ...ref, repo: { kind: "repo", name: repo } };
75
- return { ...ref, namespace: namespaceRef(ref.namespace?.name ?? "", repo) };
76
- }
77
-
78
- /**
79
- * The one reading of a ref that must name a namespace or a recipe by its short
80
- * form: a config key, a stored key, or a ref a caller has already settled.
81
- *
82
- * @param input - The ref as written.
83
- * @param from - Where it was written.
84
- * @throws A ConfigError when the place refuses it, or it names a location or reads several ways.
85
- */
86
- export function parseNamedRef(input: string, from: RefSource = RefSource.CommandLine): NamedRef {
87
- const { refs } = sharedRefResolver().parse(input, from);
88
- const named = refs.filter((ref): ref is NamedRef => isNamedRef(ref) && locationOf(ref) === undefined);
89
- if (named.length === 1 && !refs.some((ref) => locationOf(ref) !== undefined)) return named[0]!;
90
- throw new ConfigError(
91
- `Invalid ref '${input}': this needs a namespace or a recipe named by its short form, such ` +
92
- `as 'workflow/alpha', and this ref names a location.`
93
- );
94
- }
95
-
96
- /** What a stored key was read as, by key. A key's reading never changes, and listings read every key many times. */
97
- const storedKeys = new Map<string, { namespace: string; name?: string }>();
98
-
99
- /**
100
- * Reads a stored key through the resolver, once per distinct key.
101
- *
102
- * @param key - A key sous stored.
103
- */
104
- function readStoredKey(key: string): { namespace: string; name?: string } {
105
- const known = storedKeys.get(key);
106
- if (known !== undefined) return known;
107
- const parsed = parseNamedRef(key, RefSource.Lockfile);
108
- const read =
109
- parsed.kind === "namespace"
110
- ? { namespace: parsed.name }
111
- : { namespace: parsed.namespace!.name, name: parsed.name };
112
- storedKeys.set(key, read);
113
- return read;
114
- }
115
-
116
- /**
117
- * The namespace and name of a stored recipe key, `namespace/recipe`.
118
- *
119
- * splitRecipeKey("workflow/alpha"); // -> { namespace: "workflow", name: "alpha" }
120
- *
121
- * @param key - A key sous stored.
122
- */
123
- export function splitRecipeKey(key: string): { namespace: string; name: string } {
124
- const { namespace, name } = readStoredKey(key);
125
- if (name === undefined) {
126
- throw new ConfigError(
127
- `Invalid ref '${key}': a recipe key names a namespace and a recipe.\n` +
128
- ` A stored key is written as 'namespace' or 'namespace/recipe'.`
129
- );
130
- }
131
- return { namespace, name };
132
- }
133
-
134
- /**
135
- * The namespace a stored key belongs to: the key itself for a namespace, and
136
- * its namespace for a recipe.
137
- *
138
- * namespaceOfKey("workflow/alpha"); // -> "workflow"
139
- *
140
- * @param key - A key sous stored.
141
- */
142
- export function namespaceOfKey(key: string): string {
143
- return readStoredKey(key).namespace;
144
- }
@@ -1,27 +0,0 @@
1
- /**
2
- * Reads an environment variable name.
3
- */
4
-
5
- import { makeInjectable } from "../injectable.js";
6
- import { ENV_VAR_NAME } from "../glob.js";
7
- import { BaseRefSplitter, finishRef, type PartialRef } from "./partial-ref.js";
8
-
9
- /**
10
- * Any text that is a legal environment variable name (a letter or underscore,
11
- * then letters, digits or underscores) is also read as one, whole: `_` is both
12
- * the delimiter and an identifier character, so a name is never split into the
13
- * scopes that might have produced it. The text stays unread for the other
14
- * splitters too, because `workflow` is a namespace as well.
15
- */
16
- export class EnvVarSplitter extends BaseRefSplitter {
17
- readonly order = 600;
18
-
19
- protected read(state: PartialRef): PartialRef[] {
20
- if (state.repo !== undefined || state.range !== undefined || !ENV_VAR_NAME.test(state.rest)) {
21
- return [state];
22
- }
23
- return [state, finishRef(state, { kind: "envVar", name: state.rest })];
24
- }
25
- }
26
-
27
- makeInjectable(EnvVarSplitter);
@@ -1,239 +0,0 @@
1
- /**
2
- * Reads a repository written as a location: a URL, an SSH remote, a
3
- * provider-scheme locator or a host path.
4
- *
5
- * Nothing host-specific lives here. Where a repository path ends and what a
6
- * browser path looks like are each provider's own rule, asked through
7
- * `RepoProvider.readLocation`, which may answer more than one way (a GitLab
8
- * nested group does not say where the project path ends).
9
- */
10
-
11
- import { makeInjectable } from "../injectable.js";
12
- import { REF_TOKENS } from "../tokens.js";
13
- import {
14
- detectProvider,
15
- providerById,
16
- type RepoProvider,
17
- } from "../../../lib/repos/providers/index.js";
18
- import { locationFor } from "../location.js";
19
- import type { RepoRef } from "../types.js";
20
- import { BaseRefSplitter, finishRef, type PartialRef } from "./partial-ref.js";
21
-
22
- /** A URL that names its scheme, such as `https://` or `github://`. */
23
- const SCHEME_PATTERN = /^([a-z][a-z0-9+.-]*):\/\/(.*)$/is;
24
-
25
- /** The `scp`-style SSH form, as in `git@github.com:owner/name.git`. */
26
- const SCP_PATTERN = /^[^@\s/:]+@([^@\s/:]+):(.+)$/s;
27
-
28
- /** Schemes that are ordinary URLs rather than a provider's own identifier. */
29
- const URL_SCHEMES = new Set(["http", "https", "ssh", "git", "git+ssh", "ssh+git"]);
30
-
31
- /** Schemes that name a repository on this machine. */
32
- const LOCAL_SCHEMES = new Set(["local", "file"]);
33
-
34
- /**
35
- * True when a written ref names a location (a URL, an SSH remote, a
36
- * provider-scheme locator or a host path) rather than a short name. A short
37
- * name never carries `://`, an `@` before a `:`, or a dot in its first segment.
38
- *
39
- * @param ref - The ref as written.
40
- */
41
- export function looksLikeLocation(ref: string): boolean {
42
- const trimmed = ref.trim();
43
- if (SCHEME_PATTERN.test(trimmed)) return true;
44
- if (SCP_PATTERN.test(trimmed)) return true;
45
- const first = trimmed.split("/")[0] ?? "";
46
- return trimmed.includes("/") && first.includes(".") && !first.includes(":");
47
- }
48
-
49
- /** A location, taken apart only as far as every host agrees. */
50
- type Taken = { provider: RepoProvider; host: string; segments: string[] };
51
-
52
- /** A URL path's segments, decoded, with empty ones dropped. */
53
- function pathSegments(pathname: string): string[] {
54
- return pathname
55
- .split(/[?#]/)[0]!
56
- .split("/")
57
- .map((segment) => {
58
- try {
59
- return decodeURIComponent(segment);
60
- } catch {
61
- return segment;
62
- }
63
- })
64
- .filter((segment) => segment.length > 0);
65
- }
66
-
67
- /**
68
- * Reads a location into a repository, and whatever is named inside it. A
69
- * repository that a short name does not already qualify gets the location; the
70
- * names after it are left in `rest` for the name splitter, and a browser path
71
- * becomes a finished repository ref of its own.
72
- */
73
- export class LocationSplitter extends BaseRefSplitter {
74
- readonly order = 300;
75
-
76
- /**
77
- * @param providers - The providers that read locations.
78
- */
79
- constructor(private readonly providers: RepoProvider[]) {
80
- super();
81
- }
82
-
83
- protected read(state: PartialRef): PartialRef[] {
84
- if (state.repo !== undefined || !looksLikeLocation(state.rest)) return [state];
85
-
86
- const body = state.rest;
87
- const scheme = SCHEME_PATTERN.exec(body);
88
- if (scheme !== null && LOCAL_SCHEMES.has(scheme[1]!.toLowerCase())) {
89
- return [this.local(state, scheme[2]!)];
90
- }
91
-
92
- const taken = this.take(state, body, scheme);
93
- if (taken === undefined) return [];
94
-
95
- const { provider, host, segments } = taken;
96
- const specific: PartialRef[] = [];
97
- const repositories: RepoRef[] = [];
98
-
99
- for (const reading of provider.readLocation({ host, segments })) {
100
- const location = locationFor(provider, host, reading.repoPath);
101
- if (location === undefined) continue;
102
-
103
- if (reading.browsed !== undefined) {
104
- const browsed = reading.browsed.replace(/^\/+|\/+$/g, "");
105
- if (browsed.length === 0) continue;
106
- specific.push(
107
- finishRef(state, {
108
- kind: "repo",
109
- location,
110
- browsed,
111
- ...(state.range === undefined ? {} : { range: state.range }),
112
- })
113
- );
114
- continue;
115
- }
116
-
117
- const named = reading.named ?? [];
118
- if (named.length === 0) {
119
- repositories.push({ kind: "repo", location });
120
- continue;
121
- }
122
- specific.push({ ...state, rest: named.join("/"), repo: { kind: "repo", location } });
123
- }
124
-
125
- // A reading of the whole repository is only kept when nothing more
126
- // specific was read, because a namespace or a recipe is what a ref is for.
127
- if (specific.length > 0) return specific;
128
-
129
- if (repositories.length === 0) {
130
- state.problems.push(
131
- `the ${provider.id} provider cannot read a namespace or a recipe from this location. ` +
132
- `After the repository, a location names a namespace and optionally a recipe, as in ` +
133
- `'${provider.formatLocator(host, "owner/repository", "workflow/alpha")}'.`
134
- );
135
- return [];
136
- }
137
- if (state.range !== undefined) {
138
- state.problems.push("a version range applies to a recipe, not to a repository.");
139
- return [];
140
- }
141
- return repositories.map((repo) => finishRef(state, repo));
142
- }
143
-
144
- /** A repository on this machine: kept as a location the pruners can refuse. */
145
- private local(state: PartialRef, path: string): PartialRef {
146
- return finishRef(state, {
147
- kind: "repo",
148
- location: {
149
- provider: "local",
150
- host: "localhost",
151
- repoPath: path,
152
- identity: `localhost/${path.replace(/^\/+/, "")}`,
153
- url: state.rest,
154
- },
155
- });
156
- }
157
-
158
- /**
159
- * Takes the host-agnostic parts out of a location: which provider reads it,
160
- * the host, and the path segments after the host. Records why not, and
161
- * returns nothing, when the location cannot be read.
162
- */
163
- private take(
164
- state: PartialRef,
165
- body: string,
166
- scheme: RegExpExecArray | null
167
- ): Taken | undefined {
168
- const providers = this.providers;
169
- const known = providers
170
- .filter((provider) => provider.defaultHost !== undefined)
171
- .map((provider) => provider.id)
172
- .sort()
173
- .join(", ");
174
-
175
- if (scheme !== null) {
176
- const name = scheme[1]!.toLowerCase();
177
-
178
- if (URL_SCHEMES.has(name)) {
179
- let url: URL;
180
- try {
181
- url = new URL(body);
182
- } catch {
183
- state.problems.push("the URL could not be read.");
184
- return undefined;
185
- }
186
- const host = url.host.toLowerCase();
187
- const segments = pathSegments(url.pathname);
188
- return this.forHost(state, host, segments);
189
- }
190
-
191
- const provider = providerById(name, providers);
192
- if (provider === undefined || provider.defaultHost === undefined) {
193
- state.problems.push(
194
- `'${name}' is not a provider sous can read a location from. The scheme of a locator ` +
195
- `is the provider's own identifier; sous ships these: ${known}. An ordinary ` +
196
- "'https://' URL works too."
197
- );
198
- return undefined;
199
- }
200
- let segments = pathSegments(scheme[2]!);
201
- let host = provider.defaultHost;
202
- if (segments[0]?.includes(".") === true) {
203
- host = segments[0].toLowerCase();
204
- segments = segments.slice(1);
205
- }
206
- return { provider, host, segments };
207
- }
208
-
209
- const scp = SCP_PATTERN.exec(body);
210
- if (scp !== null) {
211
- return this.forHost(state, scp[1]!.toLowerCase(), pathSegments(scp[2]!));
212
- }
213
-
214
- const all = pathSegments(body);
215
- return this.forHost(state, all[0]!.toLowerCase(), all.slice(1));
216
- }
217
-
218
- /** The provider that recognizes a host, asked with the URL the location would start with. */
219
- private forHost(state: PartialRef, host: string, segments: string[]): Taken | undefined {
220
- const probe = `https://${host}/${segments.slice(0, 2).join("/")}`;
221
- const provider = detectProvider(probe, this.providers);
222
- if (provider !== undefined && provider.defaultHost !== undefined) {
223
- return { provider, host, segments };
224
- }
225
- const ids = this.providers
226
- .filter((entry) => entry.defaultHost !== undefined)
227
- .map((entry) => entry.id)
228
- .sort();
229
- state.problems.push(
230
- `sous does not recognize the host '${host}'. For a self-hosted instance, write the ` +
231
- `location with the provider's scheme and the host, as in ` +
232
- `'${ids[0] ?? "gitlab"}://${host}/owner/repository/namespace/recipe' (sous ships these ` +
233
- `providers: ${ids.join(", ")}).`
234
- );
235
- return undefined;
236
- }
237
- }
238
-
239
- makeInjectable(LocationSplitter, [{ multi: REF_TOKENS.Provider }]);