@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,422 +0,0 @@
1
- /**
2
- * The managed config layers.
3
- *
4
- * Three files in a project's `conf.d/` directory are written by sous rather
5
- * than by a person: `500-repos.jsonc`, which holds the repositories the project
6
- * trusts, `510-subscriptions.jsonc`, which holds what it subscribes to, and
7
- * `520-var-mappings.jsonc` (written from `vars/mappings.ts`), which holds
8
- * variable mapping records. All three sit in the 5xx band reserved for
9
- * machine-written layers, so a user's own primary config and non-5xx layers are
10
- * never touched.
11
- *
12
- * They are `.jsonc`, not `.json`, so they can carry real comments: each one
13
- * opens with a header saying what it holds and who writes it. Sous edits them
14
- * BY KEY, through `jsonc-parser`, which rewrites only the bytes of the entry it
15
- * is changing. A comment somebody adds beside an entry, the order they put the
16
- * keys in, and the way they formatted the file all survive a sous edit.
17
- *
18
- * A layer that still exists under its old `.json` name is read as a fallback
19
- * and migrates on the next write: the `.jsonc` file is written and the `.json`
20
- * one is removed, so a project never ends up with both (two layers with the
21
- * same baseName are a hard config error).
22
- */
23
-
24
- import fs from "node:fs";
25
- import path from "node:path";
26
- import {
27
- applyEdits,
28
- findNodeAtLocation,
29
- modify,
30
- parse as parseJsonc,
31
- parseTree,
32
- type ParseError,
33
- } from "jsonc-parser";
34
- import { CONFD_DIR_NAME } from "../config-discovery.js";
35
- import { ConfigError } from "../errors.js";
36
- import { stableJsonStringify } from "./formats/common.js";
37
- import { ensureConfdDirectory } from "../../utils/sous-directory.js";
38
-
39
- /** The machine-written layer holding the repositories a project trusts. */
40
- export const REPOS_LAYER_FILENAME = "500-repos.jsonc";
41
-
42
- /** The machine-written layer holding a project's subscriptions. */
43
- export const SUBSCRIPTIONS_LAYER_FILENAME = "510-subscriptions.jsonc";
44
-
45
- /**
46
- * The policy every managed layer states in its own header: sous edits it by
47
- * key, and a person may edit it too.
48
- */
49
- export const MANAGED_LAYER_COMMENT =
50
- "This file is managed by sous. Sous edits these files by key; you may edit them " +
51
- "too, and your comments, key order and formatting are kept.";
52
-
53
- /** The closing lines of every managed layer header, describing the format. */
54
- const MANAGED_LAYER_FORMAT_NOTE = [
55
- "It is JSON with comments (.jsonc): line comments, block comments and trailing",
56
- "commas are all allowed here.",
57
- ];
58
-
59
- /** What each managed layer holds, and which commands write it. */
60
- const MANAGED_LAYER_DESCRIPTIONS: Record<string, string[]> = {
61
- [REPOS_LAYER_FILENAME]: [
62
- "It records the repositories this project trusts. The 'sous repo add' and",
63
- "'sous repo remove' commands write the entries under 'repos'.",
64
- ],
65
- [SUBSCRIPTIONS_LAYER_FILENAME]: [
66
- "It records what this project subscribes to. The 'sous subscription add' and",
67
- "'sous subscription remove' commands write the entries under 'subscriptions'.",
68
- ],
69
- };
70
-
71
- /** Wraps a sentence into `//` comment lines of at most `width` characters. */
72
- function commentLines(text: string, width = 78): string[] {
73
- const lines: string[] = [];
74
- let current = "";
75
- for (const word of text.split(/\s+/)) {
76
- if (current.length === 0) {
77
- current = word;
78
- } else if (`${current} ${word}`.length + 3 <= width) {
79
- current = `${current} ${word}`;
80
- } else {
81
- lines.push(current);
82
- current = word;
83
- }
84
- }
85
- if (current.length > 0) lines.push(current);
86
- return lines;
87
- }
88
-
89
- /**
90
- * The header comment block a managed layer opens with. It states the policy,
91
- * says what the file holds, and names the format.
92
- *
93
- * @param fileName - The layer's file name, which selects the description.
94
- * @param description - Lines describing the file, for a layer this module does
95
- * not know about (the variable mapping layer passes its own).
96
- */
97
- export function managedLayerHeader(fileName: string, description?: string[]): string {
98
- const what = description ?? MANAGED_LAYER_DESCRIPTIONS[fileName] ?? [];
99
- const blocks = [commentLines(MANAGED_LAYER_COMMENT), what, MANAGED_LAYER_FORMAT_NOTE].filter(
100
- (block) => block.length > 0
101
- );
102
-
103
- return (
104
- blocks
105
- .map((block) => block.map((line) => `// ${line}`.trimEnd()).join("\n"))
106
- .join("\n//\n") + "\n"
107
- );
108
- }
109
-
110
- /** Where a managed layer lives, and how it is written. */
111
- export type ManagedLayerOptions = {
112
- /**
113
- * The `conf.d/` directory to use instead of `<sousDir>/conf.d`. Set it from
114
- * the discovered config context, so `--sous-confd` and `SOUS_CONFD` are
115
- * respected.
116
- */
117
- confDir?: string;
118
- /**
119
- * The header comment block a newly created layer opens with, for a layer this
120
- * module has no description for. Defaults to the header for `fileName`.
121
- */
122
- header?: string;
123
- };
124
-
125
- /**
126
- * The directory a managed layer is written into.
127
- *
128
- * @param sousDir - The project's `.sous/` directory.
129
- * @param options - An explicit `conf.d/` directory, when there is one.
130
- */
131
- export function managedLayerDir(sousDir: string, options: ManagedLayerOptions = {}): string {
132
- return options.confDir ?? path.join(sousDir, CONFD_DIR_NAME);
133
- }
134
-
135
- /**
136
- * The full path of a managed layer.
137
- *
138
- * @param sousDir - The project's `.sous/` directory.
139
- * @param fileName - The layer's file name, such as `500-repos.jsonc`.
140
- * @param options - An explicit `conf.d/` directory, when there is one.
141
- */
142
- export function managedLayerPath(
143
- sousDir: string,
144
- fileName: string,
145
- options: ManagedLayerOptions = {}
146
- ): string {
147
- return path.join(managedLayerDir(sousDir, options), fileName);
148
- }
149
-
150
- /**
151
- * The path the same layer had before managed layers became `.jsonc`, or
152
- * undefined when the name is not a `.jsonc` one. Read as a fallback, and
153
- * removed by the first write that migrates the layer.
154
- *
155
- * @param sousDir - The project's `.sous/` directory.
156
- * @param fileName - The layer's file name.
157
- * @param options - An explicit `conf.d/` directory, when there is one.
158
- */
159
- export function legacyManagedLayerPath(
160
- sousDir: string,
161
- fileName: string,
162
- options: ManagedLayerOptions = {}
163
- ): string | undefined {
164
- if (!fileName.endsWith(".jsonc")) return undefined;
165
- return path.join(managedLayerDir(sousDir, options), `${fileName.slice(0, -1)}`);
166
- }
167
-
168
- /** True when the path exists and is a regular file. */
169
- function isFile(candidate: string): boolean {
170
- try {
171
- return fs.statSync(candidate).isFile();
172
- } catch {
173
- return false;
174
- }
175
- }
176
-
177
- /**
178
- * The managed layer file that actually exists: the `.jsonc` one, or the old
179
- * `.json` one when only that is there. Undefined when the layer has never been
180
- * written.
181
- */
182
- function existingManagedLayerPath(
183
- sousDir: string,
184
- fileName: string,
185
- options: ManagedLayerOptions
186
- ): string | undefined {
187
- const current = managedLayerPath(sousDir, fileName, options);
188
- if (isFile(current)) return current;
189
-
190
- const legacy = legacyManagedLayerPath(sousDir, fileName, options);
191
- if (legacy !== undefined && isFile(legacy)) return legacy;
192
-
193
- return undefined;
194
- }
195
-
196
- /** Parses a managed layer's text, allowing comments and trailing commas. */
197
- function parseManagedLayerText(text: string, filePath: string): unknown {
198
- const errors: ParseError[] = [];
199
- const value = parseJsonc(text, errors, {
200
- allowTrailingComma: true,
201
- disallowComments: false,
202
- });
203
-
204
- if (errors.length > 0) {
205
- throw new ConfigError(
206
- `Sous could not read its own config layer at ${filePath}.\n` +
207
- ` The file is not valid JSON with comments.\n` +
208
- ` Sous writes this file itself. Restoring it from version control, or deleting ` +
209
- `it and adding the entries again, both fix this.`
210
- );
211
- }
212
-
213
- return value;
214
- }
215
-
216
- /**
217
- * Reads a managed layer, returning an empty object when the file does not exist
218
- * yet. A file that does not parse is an error naming it, because sous wrote it
219
- * and is about to edit it: silently discarding somebody's edits would be worse
220
- * than stopping.
221
- *
222
- * @param sousDir - The project's `.sous/` directory.
223
- * @param fileName - The layer's file name.
224
- * @param options - An explicit `conf.d/` directory, when there is one.
225
- */
226
- export function readManagedLayer(
227
- sousDir: string,
228
- fileName: string,
229
- options: ManagedLayerOptions = {}
230
- ): Record<string, unknown> {
231
- const filePath = existingManagedLayerPath(sousDir, fileName, options);
232
- if (filePath === undefined) return {};
233
-
234
- let text: string;
235
- try {
236
- text = fs.readFileSync(filePath, "utf8");
237
- } catch (error) {
238
- if ((error as NodeJS.ErrnoException).code === "ENOENT") return {};
239
- throw new ConfigError(
240
- `Sous could not read its own config layer at ${filePath}.\n` +
241
- ` ${(error as Error).message}`
242
- );
243
- }
244
-
245
- const parsed = parseManagedLayerText(text, filePath);
246
-
247
- if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
248
- throw new ConfigError(
249
- `The config layer at ${filePath} is not a JSON object.\n` +
250
- ` Sous writes this file itself; it always holds one object.`
251
- );
252
- }
253
-
254
- return parsed as Record<string, unknown>;
255
- }
256
-
257
- /**
258
- * Writes a layer's text to its `.jsonc` path and removes the old `.json` name
259
- * when there was one, so a migrated layer never exists twice. The file is
260
- * staged under a temporary name in the same directory and renamed into place,
261
- * so a reader never sees a half-written layer.
262
- */
263
- function writeLayerText(
264
- sousDir: string,
265
- fileName: string,
266
- body: string,
267
- options: ManagedLayerOptions
268
- ): string {
269
- const directory = managedLayerDir(sousDir, options);
270
- const filePath = path.join(directory, fileName);
271
-
272
- // Created with its README, so somebody who finds a machine-written layer can
273
- // read what the directory is for without leaving it.
274
- ensureConfdDirectory(directory);
275
-
276
- const temporary = path.join(directory, `.${fileName}.tmp-${process.pid}`);
277
- try {
278
- fs.writeFileSync(temporary, body, "utf8");
279
- fs.renameSync(temporary, filePath);
280
- } catch (error) {
281
- fs.rmSync(temporary, { force: true });
282
- throw new ConfigError(
283
- `Sous could not write its config layer at ${filePath}.\n ${(error as Error).message}`
284
- );
285
- }
286
-
287
- const legacy = legacyManagedLayerPath(sousDir, fileName, options);
288
- if (legacy !== undefined && legacy !== filePath) fs.rmSync(legacy, { force: true });
289
-
290
- return filePath;
291
- }
292
-
293
- /**
294
- * Writes a managed layer, replacing whatever was there: the header comment,
295
- * then the content as sorted JSON. Use `updateManagedLayer` for an ordinary
296
- * change; this is for creating a layer from nothing, or for replacing one whose
297
- * whole content sous is generating.
298
- *
299
- * The header comment is added for you; there is no need to pass one.
300
- *
301
- * @param sousDir - The project's `.sous/` directory.
302
- * @param fileName - The layer's file name.
303
- * @param content - The layer's whole content.
304
- * @param options - An explicit `conf.d/` directory, when there is one.
305
- */
306
- export function writeManagedLayer(
307
- sousDir: string,
308
- fileName: string,
309
- content: Record<string, unknown>,
310
- options: ManagedLayerOptions = {}
311
- ): string {
312
- const header = options.header ?? managedLayerHeader(fileName);
313
- return writeLayerText(sousDir, fileName, header + stableJsonStringify(content), options);
314
- }
315
-
316
- /**
317
- * True when the layer's text actually holds something at a key path. Used to
318
- * tell a removal that has work to do from one that does not.
319
- *
320
- * @param text - The layer's text.
321
- * @param keyPath - The key path to look for.
322
- */
323
- function hasNodeAt(text: string, keyPath: (string | number)[]): boolean {
324
- const root = parseTree(text);
325
- if (root === undefined) return false;
326
- return findNodeAtLocation(root, keyPath) !== undefined;
327
- }
328
-
329
- /** One key-path edit to a managed layer. `undefined` removes the key. */
330
- export type ManagedLayerEdit = {
331
- /** The key path to change, such as `["repos", "team-recipes"]`. */
332
- path: (string | number)[];
333
- /** The value to write there, or undefined to remove the key. */
334
- value: unknown | undefined;
335
- };
336
-
337
- /**
338
- * Applies key-path edits to a managed layer, rewriting only the bytes of the
339
- * entries that change. Comments, key order and formatting elsewhere in the file
340
- * are left exactly as they were, which is what lets a person keep notes beside
341
- * the entries sous manages.
342
- *
343
- * A layer that does not exist yet is created with its header comment and an
344
- * empty object, and the edits are applied to that. A layer still under its old
345
- * `.json` name is edited and written back as `.jsonc`, and the `.json` file is
346
- * removed.
347
- *
348
- * New keys are inserted in sorted position, so a layer sous has written from
349
- * the start stays in a stable order and its diffs stay small.
350
- *
351
- * @param sousDir - The project's `.sous/` directory.
352
- * @param fileName - The layer's file name.
353
- * @param edits - The key paths to set, or to remove by passing undefined.
354
- * @param options - An explicit `conf.d/` directory, and a header for a layer
355
- * this module has no description for.
356
- * @returns The path of the layer file that was written.
357
- */
358
- export function updateManagedLayer(
359
- sousDir: string,
360
- fileName: string,
361
- edits: ManagedLayerEdit[],
362
- options: ManagedLayerOptions = {}
363
- ): string {
364
- const existing = existingManagedLayerPath(sousDir, fileName, options);
365
-
366
- let text: string;
367
- if (existing === undefined) {
368
- text = (options.header ?? managedLayerHeader(fileName)) + "{}\n";
369
- } else {
370
- try {
371
- text = fs.readFileSync(existing, "utf8");
372
- } catch (error) {
373
- throw new ConfigError(
374
- `Sous could not read its own config layer at ${existing}.\n` +
375
- ` ${(error as Error).message}`
376
- );
377
- }
378
- // Refuse to edit a file that does not parse, rather than writing over it.
379
- parseManagedLayerText(text, existing);
380
- }
381
-
382
- for (const edit of edits) {
383
- // Removing a key that is not there is already done. Asking `modify` to do it
384
- // anyway throws, because there is no parent object to remove it from, and a
385
- // layer somebody has hand-edited is exactly where that happens.
386
- if (edit.value === undefined && !hasNodeAt(text, edit.path)) continue;
387
-
388
- const last = edit.path[edit.path.length - 1];
389
- const changes = modify(text, edit.path, edit.value, {
390
- formattingOptions: { tabSize: 2, insertSpaces: true, eol: "\n" },
391
- getInsertionIndex:
392
- typeof last === "string"
393
- ? (properties) => properties.filter((name) => name < last).length
394
- : undefined,
395
- });
396
- text = applyEdits(text, changes);
397
- }
398
-
399
- if (!text.endsWith("\n")) text += "\n";
400
-
401
- return writeLayerText(sousDir, fileName, text, options);
402
- }
403
-
404
- /**
405
- * Removes a managed layer entirely, which is what emptying one comes down to: a
406
- * layer holding nothing but its own header comment is noise in a project. The
407
- * old `.json` name is removed too, so a migration in progress leaves nothing
408
- * behind.
409
- *
410
- * @param sousDir - The project's `.sous/` directory.
411
- * @param fileName - The layer's file name.
412
- * @param options - An explicit `conf.d/` directory, when there is one.
413
- */
414
- export function removeManagedLayer(
415
- sousDir: string,
416
- fileName: string,
417
- options: ManagedLayerOptions = {}
418
- ): void {
419
- fs.rmSync(managedLayerPath(sousDir, fileName, options), { force: true });
420
- const legacy = legacyManagedLayerPath(sousDir, fileName, options);
421
- if (legacy !== undefined) fs.rmSync(legacy, { force: true });
422
- }
@@ -1,33 +0,0 @@
1
- /**
2
- * The built-in `#memories` view: every memory an active recipe publishes, at
3
- * `#memories/<namespace>/<recipe>/<path>`. See `recipe-memories.ts` for what is
4
- * listed and in which order.
5
- */
6
-
7
- import { makeInjectable } from "../../services/ref-resolver/injectable.js";
8
- import type { HashName, HashViewContext, ViewFile } from "../../services/ref-resolver/hash-names.js";
9
- import { listMemories } from "./recipe-memories.js";
10
-
11
- /** The `#memories` view. */
12
- export class MemoriesHashName implements HashName {
13
- readonly name = "memories";
14
- readonly registeredBy = "sous";
15
- readonly description =
16
- "the memories of every active recipe, listed in dependency order as #memories/<namespace>/<recipe>/<path>.";
17
-
18
- bases(): string[] {
19
- return [];
20
- }
21
-
22
- view(context: HashViewContext): ViewFile[] {
23
- const memories = context.settings.recipes?.memories;
24
- return listMemories({
25
- sousDir: context.sousDir,
26
- ...(context.env === undefined ? {} : { env: context.env }),
27
- first: memories?.first,
28
- exclude: memories?.exclude,
29
- }).map((memory) => ({ path: memory.path, file: memory.file }));
30
- }
31
- }
32
-
33
- makeInjectable(MemoriesHashName);