@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,410 +0,0 @@
1
- /**
2
- * The local provider: a repository that lives on this machine.
3
- *
4
- * It exists for local development and for tests. A recipe repository under
5
- * development, or a fixture repository built by a test, is an ordinary
6
- * directory; this provider reads one exactly the way the hosted providers read
7
- * a remote, so everything above it (trust, the resolver, the store, the
8
- * lockfile, the build) runs unchanged and without a network.
9
- *
10
- * TRUST SEMANTICS ARE IDENTICAL. A local path is added, and therefore trusted,
11
- * through the same ceremony as any other repository; sous reads nothing from a
12
- * directory a project has not added. "It is on my disk already" is not a reason
13
- * to skip the question, because the recipes in it still run on this machine.
14
- *
15
- * Two URL forms are accepted, and they mean the same thing:
16
- *
17
- * file:///home/me/Projects/my-recipes
18
- * /home/me/Projects/my-recipes
19
- *
20
- * A relative path (`../my-recipes`, `~/my-recipes`) is what people actually
21
- * type, so `sous repo add` and `sous repo link` run it through
22
- * `resolveRepoArgument` before any provider sees it and store the absolute
23
- * result. The provider itself still matches absolute paths only, because a
24
- * stored entry is read from a config file that several working directories may
25
- * run against.
26
- *
27
- * The index is read from the working tree when the file is there, so an
28
- * uncommitted index is picked up while a repository is being authored, and from
29
- * `git show HEAD:sous.index.json` otherwise. A recipe's files come from
30
- * `git clone --branch <tag>` of the local path, which is the same sparse,
31
- * blobless fetch the hosted providers use; a directory that is not a git
32
- * repository (or a tag that does not exist in it) falls back to copying the
33
- * recipe folder out of the working tree.
34
- */
35
-
36
- import fs from "node:fs";
37
- import fsp from "node:fs/promises";
38
- import os from "node:os";
39
- import path from "node:path";
40
- import { fileURLToPath, pathToFileURL } from "node:url";
41
- import { ConfigError } from "../../errors.js";
42
- import {
43
- INDEX_FILENAME,
44
- MANIFEST_EXTENSIONS,
45
- REPO_MANIFEST_BASENAME,
46
- } from "../formats/common.js";
47
- import { findRepoManifest } from "../load-manifest.js";
48
- import { ProviderBase } from "./base.js";
49
- import { fetchSubtree, runGit, tryCommand, type CommandRunner } from "./git.js";
50
- import type {
51
- CanonicalRepo,
52
- FetchedIndex,
53
- ProviderFeature,
54
- ProviderOptions,
55
- } from "./provider.js";
56
-
57
- /** The identifier a repository entry uses to name this provider explicitly. */
58
- export const LOCAL_PROVIDER_ID = "local" as const;
59
-
60
- /**
61
- * The absolute directory a repository URL names, or undefined when the URL is
62
- * not a local path at all. Both the `file://` form and a bare absolute path are
63
- * accepted; a relative path is not, because a repository entry is read from a
64
- * config file that several working directories may run against.
65
- *
66
- * localRepoPath("file:///home/me/recipes"); // -> "/home/me/recipes"
67
- * localRepoPath("https://github.com/o/r"); // -> undefined
68
- *
69
- * @param url - The repository URL, as configured.
70
- */
71
- export function localRepoPath(url: string): string | undefined {
72
- const trimmed = url.trim();
73
- if (trimmed.length === 0) return undefined;
74
-
75
- if (trimmed.toLowerCase().startsWith("file://")) {
76
- try {
77
- return path.normalize(fileURLToPath(trimmed));
78
- } catch {
79
- return undefined;
80
- }
81
- }
82
-
83
- return path.isAbsolute(trimmed) ? path.normalize(trimmed) : undefined;
84
- }
85
-
86
- /**
87
- * A directory spelled as an identity's owner path: its segments joined with `/`.
88
- * The identity becomes directories in the store and the index cache, split on
89
- * `/`, so a Windows path would otherwise land there as one segment holding its
90
- * drive colon and backslashes. A drive (`C:`) or UNC (`\\server\share`) root
91
- * becomes plain segments. A POSIX path comes back unchanged.
92
- *
93
- * identityOwnerPath("/home/me"); // -> "/home/me"
94
- * identityOwnerPath("C:\\Users\\me"); // -> "/C/Users/me" (on Windows)
95
- *
96
- * @param directory - An absolute, normalized directory.
97
- */
98
- function identityOwnerPath(directory: string): string {
99
- const { root } = path.parse(directory);
100
- const rootSegments = root.split(/[\\/]/).map((segment) => segment.replace(/:$/, ""));
101
- const segments = [...rootSegments, ...directory.slice(root.length).split(path.sep)];
102
- return "/" + segments.filter((segment) => segment.length > 0).join("/");
103
- }
104
-
105
- /** A URL that names its scheme, such as `https://` or `ssh://`. */
106
- const SCHEME_PATTERN = /^[a-z][a-z0-9+.-]*:\/\//i;
107
-
108
- /** The `scp`-style SSH form people paste, as in `git@github.com:owner/name.git`. */
109
- const SCP_PATTERN = /^[^@\s/\\]+@[^@\s/\\:]+:.+$/;
110
-
111
- /** A Windows absolute path, as in `C:\Projects\recipes`. */
112
- const WINDOWS_ABSOLUTE_PATTERN = /^[A-Za-z]:[\\/]/;
113
-
114
- /**
115
- * Expands a leading `~` to the current user's home directory. Only a bare `~`
116
- * or a `~/...` prefix is expanded; `~other/x` is left alone, because sous does
117
- * not look other people's home directories up.
118
- *
119
- * @param value - The path as typed.
120
- */
121
- export function expandHomePath(value: string): string {
122
- if (value === "~") return os.homedir();
123
- if (value.startsWith("~/") || value.startsWith("~\\")) {
124
- return path.join(os.homedir(), value.slice(2));
125
- }
126
- return value;
127
- }
128
-
129
- /**
130
- * True when a repository argument is a filesystem path rather than a URL to a
131
- * host. A `file://` URL, an absolute path, a `~` path and an explicitly
132
- * relative path (`./x`, `../x`, `.`, `..`) all count outright. A bare segment
133
- * such as `my-recipes` counts only when a directory of that name really is
134
- * there, so a host name is never mistaken for a folder.
135
- *
136
- * looksLikeLocalPath("../my-recipes"); // -> true
137
- * looksLikeLocalPath("https://github.com/o/r"); // -> false
138
- *
139
- * @param value - The repository argument, as typed.
140
- * @param cwd - The directory a relative path is measured from. Defaults to the working directory.
141
- */
142
- export function looksLikeLocalPath(value: string, cwd: string = process.cwd()): boolean {
143
- const trimmed = value.trim();
144
- if (trimmed.length === 0) return false;
145
-
146
- if (trimmed.toLowerCase().startsWith("file://")) return true;
147
- if (SCHEME_PATTERN.test(trimmed)) return false;
148
-
149
- const expanded = expandHomePath(trimmed);
150
- if (path.isAbsolute(expanded) || WINDOWS_ABSOLUTE_PATTERN.test(expanded)) return true;
151
- if (expanded !== trimmed) return true;
152
-
153
- if (
154
- trimmed === "." ||
155
- trimmed === ".." ||
156
- trimmed.startsWith("./") ||
157
- trimmed.startsWith("../") ||
158
- trimmed.startsWith(".\\") ||
159
- trimmed.startsWith("..\\")
160
- ) {
161
- return true;
162
- }
163
-
164
- if (SCP_PATTERN.test(trimmed)) return false;
165
-
166
- try {
167
- return fs.statSync(path.resolve(cwd, trimmed)).isDirectory();
168
- } catch {
169
- return false;
170
- }
171
- }
172
-
173
- /**
174
- * Normalizes a repository argument before any provider looks at it: a path is
175
- * expanded and resolved to an absolute one, and anything else is handed back
176
- * unchanged. The absolute form is what gets stored, because a repository on
177
- * this machine is machine-specific whichever way it was typed.
178
- *
179
- * resolveRepoArgument("../my-recipes", "/home/me/work"); // -> "/home/me/my-recipes"
180
- * resolveRepoArgument("https://github.com/o/r"); // -> unchanged
181
- *
182
- * @param value - The repository argument, as typed.
183
- * @param cwd - The directory a relative path is measured from. Defaults to the working directory.
184
- */
185
- export function resolveRepoArgument(value: string, cwd: string = process.cwd()): string {
186
- const trimmed = value.trim();
187
- if (!looksLikeLocalPath(trimmed, cwd)) return trimmed;
188
-
189
- if (trimmed.toLowerCase().startsWith("file://")) {
190
- const direct = localRepoPath(trimmed);
191
- return direct ?? trimmed;
192
- }
193
-
194
- return path.resolve(cwd, expandHomePath(trimmed));
195
- }
196
-
197
- /**
198
- * Checks that a resolved local path really is a sous repository, and explains
199
- * the path itself when it is not. A person who typed a path made a path
200
- * mistake, so the message names what they typed, where sous looked, and what
201
- * it expected to find there; providers do not come into it.
202
- *
203
- * @param typed - The path exactly as the user typed it.
204
- * @param resolved - The absolute path sous resolved it to.
205
- */
206
- export function assertLocalRepoDirectory(typed: string, resolved: string): void {
207
- const manifestName = `${REPO_MANIFEST_BASENAME}${MANIFEST_EXTENSIONS[0]}`;
208
- const spellings = MANIFEST_EXTENSIONS.map(
209
- (extension) => `${REPO_MANIFEST_BASENAME}${extension}`
210
- ).join(", ");
211
-
212
- let stats: fs.Stats;
213
- try {
214
- stats = fs.statSync(resolved);
215
- } catch {
216
- throw new ConfigError(
217
- `There is no directory at '${typed}'.\n` +
218
- ` Sous read that as the path ${resolved}, and nothing is there.\n` +
219
- ` A repository on this machine is a directory holding a '${manifestName}' ` +
220
- `file at its root. Check the path, or create one with 'sous repo init'.`
221
- );
222
- }
223
-
224
- if (!stats.isDirectory()) {
225
- throw new ConfigError(
226
- `'${typed}' is a file, not a directory.\n` +
227
- ` Sous read that as the path ${resolved}.\n` +
228
- ` Name the root directory of a repository: the directory holding its ` +
229
- `'${manifestName}' file.`
230
- );
231
- }
232
-
233
- if (findRepoManifest(resolved) === undefined) {
234
- throw new ConfigError(
235
- `The directory '${typed}' is not a sous repository.\n` +
236
- ` Sous read that as the path ${resolved}, and it holds no repository ` +
237
- `manifest.\n` +
238
- ` A repository declares itself with one of ${spellings} at its root. ` +
239
- `Check the path, or create a repository there with 'sous repo init'.`
240
- );
241
- }
242
- }
243
-
244
- /** True when a directory is the root of a git repository or a working tree of one. */
245
- async function isGitRepository(
246
- directory: string,
247
- run?: CommandRunner
248
- ): Promise<boolean> {
249
- const found = await tryCommand("git", ["-C", directory, "rev-parse", "--git-dir"], {
250
- ...(run === undefined ? {} : { run }),
251
- });
252
- return found !== undefined;
253
- }
254
-
255
- /** True when a git repository holds the named tag. */
256
- async function hasTag(
257
- directory: string,
258
- tag: string,
259
- run?: CommandRunner
260
- ): Promise<boolean> {
261
- const found = await tryCommand(
262
- "git",
263
- ["-C", directory, "rev-parse", "--verify", "--quiet", `refs/tags/${tag}`],
264
- { ...(run === undefined ? {} : { run }) }
265
- );
266
- return found !== undefined;
267
- }
268
-
269
- /** The repository that a local provider call is reading. */
270
- function repoDirectory(repo: CanonicalRepo): string {
271
- return repo.httpsUrl;
272
- }
273
-
274
- /** A repository on this machine, read as though it were a hosted one. */
275
- export class LocalProvider extends ProviderBase {
276
- readonly id = LOCAL_PROVIDER_ID;
277
-
278
- /**
279
- * Proposing a change to a directory on your own disk is just editing it, so
280
- * this provider declares no `submit` feature and has no command line tool.
281
- * The write-path calls it inherits from ProviderBase all refuse, naming the
282
- * provider and the feature.
283
- */
284
- readonly features: ProviderFeature[] = ["fetch"];
285
-
286
- matches(url: string): boolean {
287
- return localRepoPath(url) !== undefined;
288
- }
289
-
290
- /**
291
- * Takes a local repository path apart. `httpsUrl` carries the absolute
292
- * directory rather than a URL, because that is what git is handed when a
293
- * recipe is fetched; `sshUrl` carries the canonical `file://` spelling, so a
294
- * caller that wants to show the URL back to a person has one.
295
- *
296
- * @param url - The repository URL or path, as configured.
297
- */
298
- canonicalize(url: string): CanonicalRepo {
299
- const directory = localRepoPath(url);
300
- if (directory === undefined) {
301
- throw new ConfigError(
302
- `'${url}' is not a local repository path that sous can read.\n` +
303
- ` A local repository is named by an absolute path, or by the same path in ` +
304
- `'file:///...' form. A relative path is not accepted, because a repository ` +
305
- `entry is read from a config file that several working directories may run ` +
306
- `against.`
307
- );
308
- }
309
-
310
- return {
311
- host: "localhost",
312
- owner: identityOwnerPath(path.dirname(directory)),
313
- name: path.basename(directory),
314
- httpsUrl: directory,
315
- sshUrl: pathToFileURL(directory).href,
316
- };
317
- }
318
-
319
- /**
320
- * Reads the repository's index: the working tree's copy when there is one, so
321
- * an index being authored right now is picked up, and the committed copy
322
- * otherwise.
323
- *
324
- * @param repo - The canonicalized repository.
325
- * @param options - Subprocess runner override.
326
- */
327
- async fetchIndex(
328
- repo: CanonicalRepo,
329
- options: ProviderOptions = {}
330
- ): Promise<FetchedIndex> {
331
- const directory = repoDirectory(repo);
332
-
333
- if (!fs.existsSync(directory)) {
334
- throw new ConfigError(
335
- `There is no directory at ${directory}.\n` +
336
- ` This project reads a repository from that path, and it is not there. ` +
337
- `Either the checkout moved, or the repository entry names the wrong place.`
338
- );
339
- }
340
-
341
- const working = path.join(directory, INDEX_FILENAME);
342
- if (fs.existsSync(working)) {
343
- return { text: await fsp.readFile(working, "utf8"), ref: "working tree" };
344
- }
345
-
346
- if (!(await isGitRepository(directory, options.run))) {
347
- throw new ConfigError(
348
- `The directory ${directory} publishes no sous index.\n` +
349
- ` A repository publishes '${INDEX_FILENAME}' at its root, written by ` +
350
- `'sous repo release'. This directory has none, and it is not a git ` +
351
- `repository, so there is no committed copy to read either.`
352
- );
353
- }
354
-
355
- const text = await runGit(
356
- ["-C", directory, "show", `HEAD:${INDEX_FILENAME}`],
357
- { ...(options.run === undefined ? {} : { run: options.run }) }
358
- );
359
- return { text, ref: "HEAD" };
360
- }
361
-
362
- /**
363
- * Fetches one recipe folder at one tag. A git repository is cloned at the tag,
364
- * exactly as a hosted repository would be, so a version really is the version
365
- * the tag points at. A plain directory has no versions to honour, so its
366
- * working tree is copied instead.
367
- *
368
- * @param repo - The canonicalized repository.
369
- * @param recipePath - The recipe folder, relative to the repository root.
370
- * @param tag - The git tag carrying the version.
371
- * @param destDir - Where the recipe's files should end up.
372
- * @param options - Subprocess runner override.
373
- */
374
- async fetchRecipeTree(
375
- repo: CanonicalRepo,
376
- recipePath: string,
377
- tag: string,
378
- destDir: string,
379
- options: ProviderOptions = {}
380
- ): Promise<void> {
381
- const directory = repoDirectory(repo);
382
-
383
- if (
384
- (await isGitRepository(directory, options.run)) &&
385
- (await hasTag(directory, tag, options.run))
386
- ) {
387
- await fetchSubtree({
388
- cloneUrl: directory,
389
- tag,
390
- subPath: recipePath,
391
- destDir,
392
- ...(options.run === undefined ? {} : { run: options.run }),
393
- });
394
- return;
395
- }
396
-
397
- const source = path.join(directory, recipePath);
398
- if (!fs.existsSync(source)) {
399
- throw new ConfigError(
400
- `The repository at ${directory} has no folder '${recipePath}'.\n` +
401
- ` Its index says the recipe lives there, so either the index is out of date ` +
402
- `or the folder has been moved.`
403
- );
404
- }
405
-
406
- await fsp.rm(destDir, { recursive: true, force: true });
407
- await fsp.mkdir(path.dirname(destDir), { recursive: true });
408
- await fsp.cp(source, destDir, { recursive: true });
409
- }
410
- }