@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,89 +0,0 @@
1
- /**
2
- * Canonical repository identity: what the machine-wide store, the index cache
3
- * and the lockfile key a repository by.
4
- *
5
- * A repository's short name (`sous-recipes`) is a CONSUMER's label. Two
6
- * projects may call the same repository different things, and two different
7
- * repositories may be called the same thing in two projects, so a short name
8
- * can never key anything shared between projects. The identity can: it is
9
- * derived from the location the repository actually lives at, so every project
10
- * on a machine agrees about it.
11
- *
12
- * github.com/sous-io/sous-recipes
13
- * gitlab.example.com/group/subgroup/project
14
- * localhost/home/me/Projects/my-recipes
15
- *
16
- * The shape is `<host>/<owner path>/<name>`, lowercased, with any `.git`
17
- * suffix already gone (the providers strip it while canonicalizing). Short
18
- * names stay exactly where they were: in a project's config, in its lockfile
19
- * keys, and in everything sous prints, because that is what a person typed.
20
- */
21
-
22
- import type { CanonicalRepo } from "./providers/provider.js";
23
- import { REPO_NAME_PATTERN } from "./formats/patterns.js";
24
-
25
- /**
26
- * The canonical identity of a repository, as every machine-wide key spells it.
27
- *
28
- * repoIdentity({ host: "GitHub.com", owner: "sous-io", name: "sous-recipes" });
29
- * // -> "github.com/sous-io/sous-recipes"
30
- *
31
- * @param canonical - The repository, taken apart by its provider.
32
- */
33
- export function repoIdentity(canonical: CanonicalRepo): string {
34
- const name = canonical.name.replace(/\.git$/i, "");
35
- return identitySegments(`${canonical.host}/${canonical.owner}/${name}`).join("/");
36
- }
37
-
38
- /**
39
- * The path segments an identity is made of, with empty segments dropped and
40
- * every segment lowercased. A local repository's owner is an absolute path, so
41
- * its leading separator would otherwise produce an empty first segment.
42
- *
43
- * identitySegments("localhost//home/me/recipes");
44
- * // -> ["localhost", "home", "me", "recipes"]
45
- *
46
- * @param identity - The identity, or the pieces of one joined with slashes.
47
- */
48
- export function identitySegments(identity: string): string[] {
49
- return identity
50
- .split("/")
51
- .map((segment) => segment.trim().toLowerCase())
52
- .filter((segment) => segment.length > 0);
53
- }
54
-
55
- /**
56
- * A short name derived from an identity, used when sous has to name a
57
- * repository a project has never added (a dependency's locator URL, for
58
- * instance) before the person has chosen a name for it.
59
- *
60
- * shortNameFromIdentity("github.com/sous-io/sous-recipes"); // -> "sous-recipes"
61
- *
62
- * @param identity - The repository's canonical identity.
63
- */
64
- export function shortNameFromIdentity(identity: string): string {
65
- const segments = identitySegments(identity);
66
- const last = segments[segments.length - 1] ?? "";
67
- const cleaned = last
68
- .replace(/[^a-z0-9-]+/g, "-")
69
- .replace(/-+/g, "-")
70
- .replace(/^-+|-+$/g, "");
71
-
72
- // A short name is lowercase kebab-case starting with a letter. A repository
73
- // whose name starts with a digit, or is made entirely of characters a name
74
- // may not carry, still needs something to be called.
75
- if (REPO_NAME_PATTERN.test(cleaned)) return cleaned;
76
- return cleaned.length === 0 ? "repository" : `repo-${cleaned}`;
77
- }
78
-
79
- /**
80
- * True when two identities name the same repository. Identities are already
81
- * normalized, so this is a plain comparison; it exists so callers read as what
82
- * they mean rather than as a string equality.
83
- *
84
- * @param left - One identity.
85
- * @param right - The other.
86
- */
87
- export function sameRepoIdentity(left: string, right: string): boolean {
88
- return left === right;
89
- }
@@ -1,57 +0,0 @@
1
- /**
2
- * The Repositories layer: every on-disk format, the loaders that read them, and
3
- * the ref parser that names what is inside them.
4
- *
5
- * Import from here rather than from the individual modules, so later phases
6
- * (the store, the providers, the resolver, the CLI surface) have one place to
7
- * look for what this layer offers.
8
- */
9
-
10
- // The regular expressions in formats/patterns.js reach here through
11
- // formats/common.js, which re-exports them; listing patterns.js again would
12
- // make every one of those names an ambiguous star export.
13
- export * from "./formats/common.js";
14
- export * from "./formats/repo-manifest.js";
15
- export * from "./formats/recipe-manifest.js";
16
- export * from "./formats/index-file.js";
17
- export * from "./formats/lockfile.js";
18
- export * from "./formats/store-entry.js";
19
- export * from "./formats/links-map.js";
20
- export * from "./load-manifest.js";
21
- export * from "./identity.js";
22
-
23
- // Phase 2a: the machine-wide recipe store, under the user-level sous directory.
24
- export * from "./store/contract.js";
25
- export * from "./store/hash.js";
26
- export * from "./store/recipe-store.js";
27
- export * from "./store/settings.js";
28
-
29
- // Phase 6a: the CLI surface for editable checkouts (`sous repo init`, `link`,
30
- // `unlink`). The links map and its ignore hygiene, the thin git layer both of
31
- // them use, and the scaffold that `repo init` writes.
32
- export * from "./links.js";
33
- export * from "./git-clone.js";
34
- export * from "./scaffold/index.js";
35
-
36
- // Phase 2b: the store contract, the providers, the resolver, the trust layer
37
- // and the lockfile service.
38
- export * from "./providers/index.js";
39
- export * from "./resolver.js";
40
- export * from "./managed-layer.js";
41
- export * from "./trust.js";
42
- export * from "./lock-service.js";
43
- export * from "./freshness.js";
44
-
45
- // Phase 3: the consumer surface. Where a locked recipe's files are, the
46
- // namespace resolver and compile targets built from that, and the service the
47
- // `repo`, `subscribe` and `unsubscribe` commands drive.
48
- export * from "./locked-recipes.js";
49
- export * from "./locked-namespace-resolver.js";
50
- export * from "./subscription-service.js";
51
-
52
- // Phase 7: the core namespace. The recipe that ships inside the package, the
53
- // seed that puts it in the store on first run, and the built-in repository and
54
- // subscription every project gets unless it opts out.
55
- export * from "./core-recipe.js";
56
- export * from "./seed.js";
57
- export * from "./defaults.js";
@@ -1,356 +0,0 @@
1
- /**
2
- * Reading and writing the links map, and the ignore hygiene that goes with it.
3
- *
4
- * A link redirects one repository's resolution away from the store and at a
5
- * real working copy on disk, which is how a maintainer edits recipes: edits
6
- * happen in a checkout, never in the store. `sous repo link` writes an entry
7
- * here; `sous repo unlink` removes it and leaves the checkout alone unless
8
- * `--remove` asks it to delete one sous cloned.
9
- *
10
- * Two maps exist. The project's `.sous/sous.links.json` covers one project; the
11
- * machine-wide `$SOUS_HOME/sous.links.json` covers every project on the machine,
12
- * which is how two projects share one checkout. Both are read, and the project's
13
- * entries win, because the narrower decision is the more deliberate one.
14
- *
15
- * Neither map is committed. A link bypasses versions, the lockfile and freshness
16
- * checks, and those bypasses belong to one person's machine rather than to the
17
- * team; the ignore hygiene in this module is what keeps them out of the
18
- * repository, and `describeLinkedRepos` is what keeps them visible in build
19
- * output instead of silently changing what a build produces.
20
- */
21
-
22
- import fs from "node:fs";
23
- import path from "node:path";
24
- import { ConfigError } from "../errors.js";
25
- import { resolveSousHome, resolveStoreRoot } from "../sous-home.js";
26
- import { LINKS_FILENAME } from "./formats/common.js";
27
- import {
28
- ensureProjectReposDirectory,
29
- ensureSousHomeDirectory,
30
- } from "../../utils/sous-directory.js";
31
- import {
32
- createEmptyLinksMap,
33
- mergeLinksMaps,
34
- parseLinksMap,
35
- stringifyLinksMap,
36
- type LinksMap,
37
- type RepoLink,
38
- } from "./formats/links-map.js";
39
- import { loadJsonFile } from "./load-manifest.js";
40
-
41
- /** Directory name, inside `.sous/` or `$SOUS_HOME`, holding linked checkouts. */
42
- export const REPOS_DIRNAME = "repos";
43
-
44
- /** Opening marker of the block sous maintains in `.sous/.gitignore`. */
45
- export const IGNORE_BLOCK_START = "# >>> sous managed (do not edit between these markers)";
46
-
47
- /** Closing marker of the block sous maintains in `.sous/.gitignore`. */
48
- export const IGNORE_BLOCK_END = "# <<< sous managed";
49
-
50
- /**
51
- * The entries sous keeps inside its managed block in `.sous/.gitignore`. All of
52
- * them are machine-local: the links map, the build state file, the watcher's
53
- * PID file, the local answers file, and the directory linked checkouts are
54
- * cloned into.
55
- */
56
- export const IGNORE_BLOCK_ENTRIES = [
57
- LINKS_FILENAME,
58
- "sous.state.json",
59
- "sous.pid",
60
- ".env.local",
61
- `${REPOS_DIRNAME}/`,
62
- ] as const;
63
-
64
- // --- Locations ----------------------------------------------------------------------------------
65
-
66
- /**
67
- * The user-level sous directory, re-exported under the name this module has
68
- * always used. The one definition lives in `src/lib/sous-home.ts`, so the store,
69
- * the links maps and the auto-injected `${sousHome}` variable can never disagree
70
- * about where it is.
71
- *
72
- * @param env - The environment to read, so tests need not mutate the real one.
73
- */
74
- export function resolveSousHomeDir(env: NodeJS.ProcessEnv = process.env): string {
75
- return resolveSousHome(env);
76
- }
77
-
78
- /**
79
- * Path to the project's links map.
80
- *
81
- * @param sousDir - The project's discovered `.sous/` directory.
82
- */
83
- export function projectLinksPath(sousDir: string): string {
84
- return path.join(sousDir, LINKS_FILENAME);
85
- }
86
-
87
- /**
88
- * Path to the machine-wide links map.
89
- *
90
- * @param env - The environment to read.
91
- */
92
- export function globalLinksPath(env: NodeJS.ProcessEnv = process.env): string {
93
- return path.join(resolveSousHomeDir(env), LINKS_FILENAME);
94
- }
95
-
96
- /**
97
- * The directory a project's own linked checkouts are cloned into.
98
- *
99
- * @param sousDir - The project's discovered `.sous/` directory.
100
- */
101
- export function projectReposDir(sousDir: string): string {
102
- return path.join(sousDir, REPOS_DIRNAME);
103
- }
104
-
105
- /**
106
- * The directory machine-wide linked checkouts are cloned into, shared by every
107
- * project on the machine.
108
- *
109
- * @param env - The environment to read.
110
- */
111
- export function globalReposDir(env: NodeJS.ProcessEnv = process.env): string {
112
- return path.join(resolveSousHomeDir(env), REPOS_DIRNAME);
113
- }
114
-
115
- /**
116
- * The directories nothing sous deletes may ever reach into: the project's linked
117
- * checkouts, the machine-wide linked checkouts, and the machine-wide recipe
118
- * store.
119
- *
120
- * A linked checkout is somebody's working copy with unpushed edits in it, and
121
- * the store is shared by every project on the machine, so a stale state entry
122
- * pointing into either one must never turn `sous prune` or `sous clear` into a
123
- * data loss. Prune and clear only ever touch paths recorded in the state file,
124
- * which nothing here writes; this is the belt that survives a future bug.
125
- *
126
- * @param sousDir - The project's discovered `.sous/` directory.
127
- * @param env - The environment to read.
128
- */
129
- export function protectedRepoPaths(
130
- sousDir: string,
131
- env: NodeJS.ProcessEnv = process.env
132
- ): string[] {
133
- return [projectReposDir(sousDir), globalReposDir(env), resolveStoreRoot(env)];
134
- }
135
-
136
- // --- Reading ------------------------------------------------------------------------------------
137
-
138
- /**
139
- * Reads a links map from a path, returning an empty map when the file does not
140
- * exist. A file that exists but cannot be read or does not validate raises a
141
- * ConfigError naming it, rather than being quietly treated as empty: a link
142
- * changes what a build produces, so losing one must never pass unnoticed.
143
- *
144
- * @param filePath - Absolute path to the links file.
145
- */
146
- export function readLinksFile(filePath: string): LinksMap {
147
- if (!fs.existsSync(filePath)) return createEmptyLinksMap();
148
- return parseLinksMap(loadJsonFile(filePath, "links map"), filePath);
149
- }
150
-
151
- /**
152
- * Reads the project's links map.
153
- *
154
- * @param sousDir - The project's discovered `.sous/` directory.
155
- */
156
- export function readProjectLinks(sousDir: string): LinksMap {
157
- return readLinksFile(projectLinksPath(sousDir));
158
- }
159
-
160
- /**
161
- * Reads the machine-wide links map.
162
- *
163
- * @param env - The environment to read.
164
- */
165
- export function readGlobalLinks(env: NodeJS.ProcessEnv = process.env): LinksMap {
166
- return readLinksFile(globalLinksPath(env));
167
- }
168
-
169
- /**
170
- * Reads both maps and merges them into the one map the rest of sous consults,
171
- * with the project's entries winning over the machine-wide ones.
172
- *
173
- * @param sousDir - The project's discovered `.sous/` directory.
174
- * @param env - The environment to read.
175
- */
176
- export function readEffectiveLinks(
177
- sousDir: string,
178
- env: NodeJS.ProcessEnv = process.env
179
- ): Record<string, RepoLink> {
180
- return mergeLinksMaps(readGlobalLinks(env), readProjectLinks(sousDir));
181
- }
182
-
183
- /**
184
- * The working copy sous should read for a repository, or undefined when the
185
- * repository is not linked and resolution should fall through to the store.
186
- *
187
- * @param repoName - The repository's configured short name.
188
- * @param sousDir - The project's discovered `.sous/` directory.
189
- * @param env - The environment to read.
190
- */
191
- export function linkedPathFor(
192
- repoName: string,
193
- sousDir: string,
194
- env: NodeJS.ProcessEnv = process.env
195
- ): string | undefined {
196
- return readEffectiveLinks(sousDir, env)[repoName]?.path;
197
- }
198
-
199
- // --- Writing ------------------------------------------------------------------------------------
200
-
201
- /**
202
- * Writes a links map, creating its directory if needed. Returns the path it was
203
- * written to.
204
- *
205
- * @param filePath - Absolute path to the links file.
206
- * @param map - The map to write.
207
- */
208
- export function writeLinksFile(filePath: string, map: LinksMap): string {
209
- fs.mkdirSync(path.dirname(filePath), { recursive: true });
210
- fs.writeFileSync(filePath, stringifyLinksMap(map), "utf8");
211
- return filePath;
212
- }
213
-
214
- /**
215
- * Writes the project's links map.
216
- *
217
- * @param sousDir - The project's discovered `.sous/` directory.
218
- * @param map - The map to write.
219
- */
220
- export function writeProjectLinks(sousDir: string, map: LinksMap): string {
221
- return writeLinksFile(projectLinksPath(sousDir), map);
222
- }
223
-
224
- /**
225
- * Writes the machine-wide links map.
226
- *
227
- * @param map - The map to write.
228
- * @param env - The environment to read.
229
- */
230
- export function writeGlobalLinks(
231
- map: LinksMap,
232
- env: NodeJS.ProcessEnv = process.env
233
- ): string {
234
- // The machine-wide map is the first thing many users ever put in `$SOUS_HOME`,
235
- // so this is where that directory usually gets its own explanation.
236
- ensureSousHomeDirectory(resolveSousHomeDir(env));
237
- return writeLinksFile(globalLinksPath(env), map);
238
- }
239
-
240
- // --- Build output notice ------------------------------------------------------------------------
241
-
242
- /**
243
- * The lines a build prints when anything is linked, so a checkout standing in
244
- * for a published repository is never a silent change. Returns an empty array
245
- * when nothing is linked, which is the signal to print nothing at all.
246
- *
247
- * @param sousDir - The project's discovered `.sous/` directory.
248
- * @param env - The environment to read.
249
- */
250
- export function describeLinkedRepos(
251
- sousDir: string,
252
- env: NodeJS.ProcessEnv = process.env
253
- ): string[] {
254
- const links = readEffectiveLinks(sousDir, env);
255
- const names = Object.keys(links).sort();
256
- if (names.length === 0) return [];
257
-
258
- const lines = [
259
- names.length === 1
260
- ? "One repository is LINKED to a working copy on this machine."
261
- : `${names.length} repositories are LINKED to working copies on this machine.`,
262
- "Their recipes are read from those checkouts, so versions, the lockfile and",
263
- "freshness checks do not apply to them.",
264
- "",
265
- ];
266
-
267
- for (const name of names) {
268
- lines.push(`${name} -> ${links[name]!.path}`);
269
- }
270
-
271
- lines.push("");
272
- lines.push("Run 'sous repo unlink <name>' to go back to the published versions.");
273
- return lines;
274
- }
275
-
276
- // --- Ignore hygiene -----------------------------------------------------------------------------
277
-
278
- /**
279
- * Makes sure git ignores everything sous keeps inside `.sous/` that belongs to
280
- * one machine rather than to the team. Two files are maintained, and both are
281
- * safe to write again on every link:
282
- *
283
- * - `.sous/repos/.gitignore`, holding a single `*`, so a linked checkout cloned
284
- * underneath it is invisible to the project's own repository (the `*` covers
285
- * the ignore file itself, so the directory contributes nothing at all).
286
- * - a delimited managed block inside `.sous/.gitignore`. Only the lines between
287
- * the markers are ever rewritten; anything the user put above or below them is
288
- * left exactly as it was.
289
- *
290
- * @param sousDir - The project's discovered `.sous/` directory.
291
- */
292
- export function ensureReposIgnoreFiles(sousDir: string): void {
293
- const reposDir = ensureProjectReposDirectory(projectReposDir(sousDir));
294
- writeIfChanged(path.join(reposDir, ".gitignore"), "*\n");
295
-
296
- const gitignorePath = path.join(sousDir, ".gitignore");
297
- const existing = fs.existsSync(gitignorePath)
298
- ? fs.readFileSync(gitignorePath, "utf8")
299
- : undefined;
300
- writeIfChanged(gitignorePath, applyManagedIgnoreBlock(existing, gitignorePath));
301
- }
302
-
303
- /**
304
- * Returns the contents of `.sous/.gitignore` with sous's managed block present
305
- * and up to date, leaving every line outside the markers untouched. Exported so
306
- * the behavior can be tested without touching a filesystem.
307
- *
308
- * @param existing - The file's current contents, or undefined when there is no file.
309
- * @param label - The file's path, named if the block turns out to be damaged.
310
- */
311
- export function applyManagedIgnoreBlock(
312
- existing: string | undefined,
313
- label = ".sous/.gitignore"
314
- ): string {
315
- const block = [IGNORE_BLOCK_START, ...IGNORE_BLOCK_ENTRIES, IGNORE_BLOCK_END];
316
-
317
- if (existing === undefined || existing.trim() === "") {
318
- return `${block.join("\n")}\n`;
319
- }
320
-
321
- const lines = existing.split("\n");
322
- const start = lines.findIndex((line) => line.trim() === IGNORE_BLOCK_START);
323
-
324
- if (start === -1) {
325
- const prefix = existing.endsWith("\n") ? existing : `${existing}\n`;
326
- const separator = prefix.endsWith("\n\n") ? "" : "\n";
327
- return `${prefix}${separator}${block.join("\n")}\n`;
328
- }
329
-
330
- const end = lines.findIndex(
331
- (line, index) => index > start && line.trim() === IGNORE_BLOCK_END
332
- );
333
-
334
- if (end === -1) {
335
- throw new ConfigError(
336
- `The sous managed block in ${label} has no closing marker.\n` +
337
- ` It opens with '${IGNORE_BLOCK_START}' but the line '${IGNORE_BLOCK_END}' ` +
338
- `is missing, so sous cannot tell where the block ends.\n` +
339
- ` Add the closing marker back, or delete the opening one, and run the ` +
340
- `command again.`
341
- );
342
- }
343
-
344
- const rebuilt = [...lines.slice(0, start), ...block, ...lines.slice(end + 1)];
345
- const joined = rebuilt.join("\n");
346
- return joined.endsWith("\n") ? joined : `${joined}\n`;
347
- }
348
-
349
- // --- Helpers ------------------------------------------------------------------------------------
350
-
351
- /** Writes a file only when its contents would change, so links stay idempotent. */
352
- function writeIfChanged(filePath: string, contents: string): void {
353
- if (fs.existsSync(filePath) && fs.readFileSync(filePath, "utf8") === contents) return;
354
- fs.mkdirSync(path.dirname(filePath), { recursive: true });
355
- fs.writeFileSync(filePath, contents, "utf8");
356
- }