@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,421 +0,0 @@
1
- /**
2
- * The index cache.
3
- *
4
- * Adding a repository fetches exactly one file, its `sous.index.json`, and that
5
- * file is all sous needs to resolve a ref, enumerate versions and decide what to
6
- * download. The cache keeps one copy per repository under
7
- * `<storeRoot>/_indexes/`, beside a small sidecar recording when it was fetched
8
- * and what the host called that version of it.
9
- *
10
- * The rule the whole Repositories system follows applies here too: a failed
11
- * upstream check never breaks a build. When a refetch fails and a cached copy
12
- * exists, the cached copy is used and the failure is reported as a warning.
13
- *
14
- * One thing the cache can do beyond storing and serving: an OVERLAY. Sous ships
15
- * a copy of the core recipe inside its own package, and that copy has to be
16
- * resolvable even when the repository publishing it has not published that
17
- * version yet. An overlay folds such a fact into every copy of an index the
18
- * cache hands out, and never into the file on disk, so the cached copy stays an
19
- * honest record of what the repository actually served.
20
- *
21
- * The cache takes the store's root directory as a plain string, so it does not
22
- * depend on the store implementation at all.
23
- */
24
-
25
- import fs from "node:fs";
26
- import path from "node:path";
27
- import { parseIndexFile, type IndexFile } from "../formats/index-file.js";
28
- import { INDEX_CACHE_DIRNAME, stableJsonStringify } from "../formats/common.js";
29
- import { identitySegments } from "../identity.js";
30
- import { ConfigError, isConfigError } from "../../errors.js";
31
- import { warning } from "../../../utils/formatting.js";
32
- // The freshness window has one definition, and it lives with the rest of the
33
- // freshness rules; that module only borrows a type from here, so nothing loads
34
- // in a circle at run time.
35
- import { DEFAULT_FRESHNESS_SECONDS } from "../store/settings.js";
36
- import type { ProviderOptions, RepoProvider } from "./provider.js";
37
- import { ensureIndexCacheDirectory } from "../../../utils/sous-directory.js";
38
-
39
- export { INDEX_CACHE_DIRNAME };
40
-
41
- /** The suffix of the sidecar written beside each cached index. */
42
- export const INDEX_SIDECAR_SUFFIX = ".meta.json";
43
-
44
- /**
45
- * What sous remembers about a cached index. Written as JSON beside the index
46
- * itself; a missing or unreadable sidecar simply means "no idea", never an
47
- * error.
48
- */
49
- export type IndexMeta = {
50
- /** When the cached copy was fetched. */
51
- fetchedAt: string;
52
- /** The entity tag the host sent for that copy, when it sent one. */
53
- etag?: string;
54
- /** The git ref it was read at. */
55
- ref?: string;
56
- /** When sous last asked upstream whether there was anything newer. */
57
- lastCheckedAt?: string;
58
- };
59
-
60
- /** Where a returned index came from. */
61
- export type IndexSource = "cache" | "network" | "stale";
62
-
63
- /** What a cache lookup returns. */
64
- export type IndexLookup = {
65
- /** The validated index. */
66
- index: IndexFile;
67
- /** Whether it came from the cache, from the network, or from a stale fallback. */
68
- source: IndexSource;
69
- /** What sous remembers about the copy that was returned. */
70
- meta: IndexMeta;
71
- };
72
-
73
- /**
74
- * A fact sous knows about a repository that its published index does not carry,
75
- * folded into every copy of that index the cache returns.
76
- *
77
- * An overlay must be pure: it is handed an index and returns one, and it never
78
- * touches the cache's files. It is called for every repository, so it decides
79
- * for itself which identity it applies to and returns the index unchanged for
80
- * all the others.
81
- */
82
- export type IndexOverlay = (identity: string, index: IndexFile) => IndexFile;
83
-
84
- /** Which repository to fetch, and how fresh the answer has to be. */
85
- export type GetIndexOptions = {
86
- /** The repository URL, used when a fetch is needed. */
87
- url: string;
88
- /** The provider the repository entry names, when it names one. */
89
- provider?: string;
90
- /** How old a cached copy may be, in seconds. Defaults to five minutes. */
91
- maxAgeSeconds?: number;
92
- /** When true, refetch regardless of how fresh the cached copy is. */
93
- force?: boolean;
94
- /**
95
- * What to call the repository in a message. A project's short name for it is
96
- * what the person reading wrote in their own config, so that is what they are
97
- * shown; the identity is used when nothing supplies one.
98
- */
99
- label?: string;
100
- /** Cancels the request, for a caller that will not wait past a deadline. */
101
- signal?: AbortSignal;
102
- };
103
-
104
- /** Which repository to read from upstream, without touching the cache. */
105
- export type FetchUpstreamOptions = Pick<GetIndexOptions, "url" | "provider" | "signal">;
106
-
107
- /** How the cache is built. */
108
- export type IndexCacheOptions = {
109
- /** The store's root directory; the cache lives in a subdirectory of it. */
110
- storeRoot: string;
111
- /**
112
- * How a provider is chosen for a repository URL. Defaults to the built-in
113
- * providers; injected in tests, and by anyone with a provider list of their
114
- * own.
115
- */
116
- resolveProvider: (url: string, providerId?: string) => RepoProvider;
117
- /** Options handed to every provider call (environment, fetch, subprocess runner). */
118
- providerOptions?: ProviderOptions;
119
- /** The clock, so tests can decide what "now" means. */
120
- now?: () => Date;
121
- /** Where warnings go. Defaults to the console warning banner. */
122
- warn?: (message: string) => void;
123
- /**
124
- * A fact to fold into every index this cache returns, without ever writing it
125
- * to disk. See `IndexOverlay`; `setOverlay` installs one later.
126
- */
127
- overlay?: IndexOverlay;
128
- };
129
-
130
- /** One cached repository index, plus the machinery to keep it current. */
131
- export class IndexCache {
132
- private readonly storeRoot: string;
133
-
134
- private readonly resolveProvider: (url: string, providerId?: string) => RepoProvider;
135
-
136
- private readonly providerOptions: ProviderOptions;
137
-
138
- private readonly now: () => Date;
139
-
140
- private readonly warn: (message: string) => void;
141
-
142
- /**
143
- * What sous itself knows about a repository, over and above what the
144
- * repository published. Not readonly: the packaged core recipe's hash is only
145
- * known once the store has been seeded, which happens after the cache exists.
146
- */
147
- private overlay: IndexOverlay | undefined;
148
-
149
- constructor(options: IndexCacheOptions) {
150
- this.storeRoot = options.storeRoot;
151
- this.resolveProvider = options.resolveProvider;
152
- this.providerOptions = options.providerOptions ?? {};
153
- this.now = options.now ?? (() => new Date());
154
- this.warn = options.warn ?? warning;
155
- this.overlay = options.overlay;
156
- }
157
-
158
- /**
159
- * Installs the overlay folded into every index this cache returns from now
160
- * on. Nothing already written to disk changes, and nothing written later
161
- * carries it.
162
- *
163
- * @param overlay - The overlay to apply, or undefined to apply none.
164
- */
165
- setOverlay(overlay: IndexOverlay | undefined): void {
166
- this.overlay = overlay;
167
- }
168
-
169
- /**
170
- * Folds the installed overlay, if there is one, into an index on its way out.
171
- * An overlay that throws is ignored and warned about: a fact sous was trying
172
- * to add is never worth losing the index the repository really published.
173
- *
174
- * @param identity - The repository's canonical identity.
175
- * @param index - The index as it was read or fetched.
176
- */
177
- private applyOverlay(identity: string, index: IndexFile): IndexFile {
178
- if (this.overlay === undefined) return index;
179
- try {
180
- return this.overlay(identity, index);
181
- } catch (error) {
182
- this.warn(
183
- `Sous could not add what it knows about the repository '${identity}' to that ` +
184
- `repository's index, so only what the repository published is available.\n` +
185
- `${error instanceof Error ? error.message : String(error)}`
186
- );
187
- return index;
188
- }
189
- }
190
-
191
- /** The directory cached indexes live in. */
192
- get directory(): string {
193
- return path.join(this.storeRoot, INDEX_CACHE_DIRNAME);
194
- }
195
-
196
- /**
197
- * Where a repository's cached index is written. The identity is several
198
- * segments, so it becomes several directories, exactly as it does in the
199
- * store: `_indexes/github.com/sous-io/sous-recipes.json`.
200
- *
201
- * @param identity - The repository's canonical identity.
202
- */
203
- indexPath(identity: string): string {
204
- const segments = identitySegments(identity);
205
- const last = segments.pop() ?? identity;
206
- return path.join(this.directory, ...segments, `${last}.json`);
207
- }
208
-
209
- /**
210
- * Where a repository's index sidecar is written, beside its index.
211
- *
212
- * @param identity - The repository's canonical identity.
213
- */
214
- sidecarPath(identity: string): string {
215
- const segments = identitySegments(identity);
216
- const last = segments.pop() ?? identity;
217
- return path.join(this.directory, ...segments, `${last}${INDEX_SIDECAR_SUFFIX}`);
218
- }
219
-
220
- /**
221
- * Reads what sous remembers about a cached index. Returns undefined when
222
- * there is no sidecar or it cannot be read; the sidecar is a convenience, and
223
- * losing it only costs a refetch.
224
- *
225
- * @param identity - The repository's canonical identity.
226
- */
227
- readMeta(identity: string): IndexMeta | undefined {
228
- try {
229
- const raw = JSON.parse(fs.readFileSync(this.sidecarPath(identity), "utf8")) as IndexMeta;
230
- if (typeof raw?.fetchedAt !== "string") return undefined;
231
- return raw;
232
- } catch {
233
- return undefined;
234
- }
235
- }
236
-
237
- /**
238
- * Writes what sous remembers about a cached index.
239
- *
240
- * @param identity - The repository's canonical identity.
241
- * @param meta - What to record.
242
- */
243
- writeMeta(identity: string, meta: IndexMeta): void {
244
- const file = this.sidecarPath(identity);
245
- this.prepareDirectoryFor(file);
246
- fs.writeFileSync(file, stableJsonStringify(meta), "utf8");
247
- }
248
-
249
- /**
250
- * Makes sure the cache directory exists and explains itself, and that the
251
- * subdirectories an identity turns into are there too.
252
- *
253
- * @param filePath - The file about to be written.
254
- */
255
- private prepareDirectoryFor(filePath: string): void {
256
- ensureIndexCacheDirectory(this.directory);
257
- fs.mkdirSync(path.dirname(filePath), { recursive: true });
258
- }
259
-
260
- /**
261
- * Reads and validates the cached index, or undefined when there is none. A
262
- * cached file that no longer parses is treated as absent, since it is only a
263
- * copy of something upstream still has. Any installed overlay is folded into
264
- * the answer; the file itself is left exactly as it was cached.
265
- *
266
- * @param identity - The repository's canonical identity.
267
- */
268
- readCached(identity: string): IndexFile | undefined {
269
- const file = this.indexPath(identity);
270
- let text: string;
271
- try {
272
- text = fs.readFileSync(file, "utf8");
273
- } catch {
274
- return undefined;
275
- }
276
- try {
277
- return this.applyOverlay(identity, parseIndexFile(JSON.parse(text), file));
278
- } catch {
279
- return undefined;
280
- }
281
- }
282
-
283
- /**
284
- * True when the cached copy is younger than the given window.
285
- *
286
- * @param meta - What sous remembers about the cached copy.
287
- * @param maxAgeSeconds - How old the copy may be.
288
- */
289
- isFresh(meta: IndexMeta | undefined, maxAgeSeconds: number): boolean {
290
- if (meta === undefined) return false;
291
- const fetched = Date.parse(meta.fetchedAt);
292
- if (Number.isNaN(fetched)) return false;
293
- const ageSeconds = (this.now().getTime() - fetched) / 1000;
294
- return ageSeconds >= 0 && ageSeconds < maxAgeSeconds;
295
- }
296
-
297
- /**
298
- * Returns a repository's index: the cached copy while it is fresh, otherwise a
299
- * fresh fetch through the provider. When the fetch fails and a cached copy
300
- * exists, the cached copy is returned and the failure is warned about; when
301
- * there is no cached copy, the failure is raised.
302
- *
303
- * @param identity - The repository's canonical identity.
304
- * @param options - The repository URL, its provider, and the freshness window.
305
- */
306
- async getIndex(identity: string, options: GetIndexOptions): Promise<IndexLookup> {
307
- const maxAgeSeconds = options.maxAgeSeconds ?? DEFAULT_FRESHNESS_SECONDS;
308
- const meta = this.readMeta(identity);
309
-
310
- if (options.force !== true && this.isFresh(meta, maxAgeSeconds)) {
311
- const cached = this.readCached(identity);
312
- if (cached !== undefined) return { index: cached, source: "cache", meta: meta! };
313
- }
314
-
315
- try {
316
- return await this.refresh(identity, options);
317
- } catch (error) {
318
- const cached = this.readCached(identity);
319
- if (cached === undefined) throw error;
320
-
321
- const reason = isConfigError(error)
322
- ? (error as ConfigError).message
323
- : (error as Error).message;
324
- this.warn(
325
- `Sous could not check the repository '${options.label ?? identity}' for updates, ` +
326
- `so it is using the copy of its index that it already had.\n${reason}`
327
- );
328
- const stale: IndexMeta = meta ?? { fetchedAt: new Date(0).toISOString() };
329
- return { index: cached, source: "stale", meta: stale };
330
- }
331
- }
332
-
333
- /**
334
- * Fetches a repository's index through its provider, validates it, and writes
335
- * both the index and its sidecar. Raises rather than falling back; `getIndex`
336
- * is where the last-good behavior lives.
337
- *
338
- * @param identity - The repository's canonical identity.
339
- * @param options - The repository URL and its provider.
340
- */
341
- async refresh(identity: string, options: GetIndexOptions): Promise<IndexLookup> {
342
- const { index, fetched } = await this.fetchFromProvider(options);
343
- const timestamp = this.now().toISOString();
344
- const meta: IndexMeta = {
345
- fetchedAt: timestamp,
346
- lastCheckedAt: timestamp,
347
- ref: fetched.ref,
348
- ...(fetched.etag === undefined ? {} : { etag: fetched.etag }),
349
- };
350
-
351
- const file = this.indexPath(identity);
352
- this.prepareDirectoryFor(file);
353
- fs.writeFileSync(file, stableJsonStringify(index), "utf8");
354
- this.writeMeta(identity, meta);
355
-
356
- // Written first, overlaid second: the file is what the repository served,
357
- // and the overlay exists only in the copy handed back to the caller.
358
- return { index: this.applyOverlay(identity, index), source: "network", meta };
359
- }
360
-
361
- /**
362
- * Reads a repository's index from upstream and returns it, WITHOUT writing
363
- * anything: neither the cached copy nor its sidecar changes. This is what a
364
- * browsing command reading upstream uses, because only a command that
365
- * resolves versions should change what the cache holds. Any installed overlay
366
- * is folded into the answer, as it is for a cached copy. A failure is raised;
367
- * the caller decides whether a cached copy stands in for it.
368
- *
369
- * @param identity - The repository's canonical identity, for the overlay.
370
- * @param options - The repository URL, its provider, and an optional cancel signal.
371
- */
372
- async fetchUpstream(identity: string, options: FetchUpstreamOptions): Promise<IndexFile> {
373
- const { index } = await this.fetchFromProvider(options);
374
- return this.applyOverlay(identity, index);
375
- }
376
-
377
- /**
378
- * Fetches and validates a repository's index through its provider. Writes
379
- * nothing; `refresh` and `fetchUpstream` decide what happens next.
380
- *
381
- * @param options - The repository URL, its provider, and an optional cancel signal.
382
- */
383
- private async fetchFromProvider(
384
- options: FetchUpstreamOptions
385
- ): Promise<{ index: IndexFile; fetched: { ref: string; etag?: string } }> {
386
- const provider = this.resolveProvider(options.url, options.provider);
387
- const repo = provider.canonicalize(options.url);
388
- const fetched = await provider.fetchIndex(
389
- repo,
390
- options.signal === undefined
391
- ? this.providerOptions
392
- : { ...this.providerOptions, signal: options.signal }
393
- );
394
-
395
- let parsed: unknown;
396
- try {
397
- parsed = JSON.parse(fetched.text);
398
- } catch (error) {
399
- throw new ConfigError(
400
- `The index that ${options.url} published is not valid JSON.\n` +
401
- ` ${(error as Error).message}\n` +
402
- ` A repository's index is written by 'sous repo release'; this one may be ` +
403
- `damaged or may not be a sous repository at all.`
404
- );
405
- }
406
-
407
- const index = parseIndexFile(parsed, `${options.url} (${fetched.ref})`);
408
- return { index, fetched };
409
- }
410
-
411
- /**
412
- * Forgets a repository's cached index and sidecar, which is what removing a
413
- * repository from a project does.
414
- *
415
- * @param identity - The repository's canonical identity.
416
- */
417
- forget(identity: string): void {
418
- fs.rmSync(this.indexPath(identity), { force: true });
419
- fs.rmSync(this.sidecarPath(identity), { force: true });
420
- }
421
- }
@@ -1,106 +0,0 @@
1
- /**
2
- * The provider registry.
3
- *
4
- * Import providers from here. This module is the one place that knows which
5
- * concrete providers sous ships, so `provider.ts` stays free of them and the
6
- * built-in list has exactly one definition.
7
- *
8
- * A new provider is one file: a class extending `ProviderBase` that implements
9
- * the read path, declares the features it really answers, and overrides the
10
- * write-path calls it supports. Adding it to `builtInProviders()` below is the
11
- * only other change; nothing above the provider layer learns its name.
12
- */
13
-
14
- import { LocalProvider } from "./local.js";
15
- import { GithubProvider } from "./github.js";
16
- import { GitlabProvider } from "./gitlab.js";
17
- import { IndexCache, type IndexCacheOptions } from "./index-cache.js";
18
- import {
19
- detectProviderIn,
20
- providerByIdIn,
21
- requireProviderIn,
22
- type RepoProvider,
23
- } from "./provider.js";
24
-
25
- // `runGit` is deliberately not re-exported here: `git-clone.ts` exports a `runGit` of its
26
- // own and the repos barrel cannot carry two. Import this one from `providers/git.js` directly.
27
- export { spawnCommand, tryCommand, fetchSubtree } from "./git.js";
28
- export type { CommandResult, CommandRunner, RunOptions } from "./git.js";
29
- export * from "./http.js";
30
- export * from "./provider.js";
31
- export * from "./base.js";
32
- export * from "./github.js";
33
- export * from "./gitlab.js";
34
- export * from "./local.js";
35
- export * from "./index-cache.js";
36
-
37
- /**
38
- * The providers sous ships, in the order they are tried. A fresh array every
39
- * call, so a caller may add to it without affecting anyone else.
40
- */
41
- export function builtInProviders(): RepoProvider[] {
42
- // The local provider is last, and matches only a local absolute path or a
43
- // `file://` URL, so it can never intercept a hosted repository's URL.
44
- return [new GithubProvider(), new GitlabProvider(), new LocalProvider()];
45
- }
46
-
47
- /**
48
- * Finds the provider that handles a repository URL, or undefined when none
49
- * does. A repository entry may also name its provider outright, which is what a
50
- * self-hosted instance behind an unfamiliar host name needs; see
51
- * `requireProvider`.
52
- *
53
- * @param url - The repository URL.
54
- * @param providers - The providers to consider. Defaults to the built-ins.
55
- */
56
- export function detectProvider(
57
- url: string,
58
- providers: RepoProvider[] = builtInProviders()
59
- ): RepoProvider | undefined {
60
- return detectProviderIn(url, providers);
61
- }
62
-
63
- /**
64
- * Looks a provider up by its identifier, or undefined when there is none.
65
- *
66
- * @param id - The provider identifier from a repository entry.
67
- * @param providers - The providers to consider. Defaults to the built-ins.
68
- */
69
- export function providerById(
70
- id: string,
71
- providers: RepoProvider[] = builtInProviders()
72
- ): RepoProvider | undefined {
73
- return providerByIdIn(id, providers);
74
- }
75
-
76
- /**
77
- * Finds the provider for a repository entry: the one it names, otherwise the
78
- * one that recognizes its URL. Raises a ConfigError when neither works.
79
- *
80
- * @param url - The repository URL.
81
- * @param providerId - The provider named by the entry, when it named one.
82
- * @param providers - The providers to consider. Defaults to the built-ins.
83
- */
84
- export function requireProvider(
85
- url: string,
86
- providerId?: string,
87
- providers: RepoProvider[] = builtInProviders()
88
- ): RepoProvider {
89
- return requireProviderIn(url, providerId, providers);
90
- }
91
-
92
- /**
93
- * Builds an index cache that resolves providers through the built-in list. Pass
94
- * `resolveProvider` to override that, which is what tests do.
95
- *
96
- * @param options - The store root, and anything to override.
97
- */
98
- export function createIndexCache(
99
- options: Omit<IndexCacheOptions, "resolveProvider"> &
100
- Partial<Pick<IndexCacheOptions, "resolveProvider">>
101
- ): IndexCache {
102
- return new IndexCache({
103
- resolveProvider: (url, providerId) => requireProvider(url, providerId),
104
- ...options,
105
- });
106
- }