@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,332 +0,0 @@
1
- /**
2
- * Scaffolding a project's `.sous/` directory, which is what `sous init` does.
3
- *
4
- * The scaffold is planned in memory first, checked against what is already on
5
- * disk, and only then written. Once written, the config is read back through
6
- * the very loader every command uses, so a scaffold sous itself cannot load is
7
- * never reported as a success.
8
- *
9
- * It refuses to touch a project that is already set up: a `.sous/` directory
10
- * holding a primary config is left exactly as it is, and so is any file the
11
- * scaffold would otherwise write. Two files are merged rather than replaced:
12
- * `.sous/.gitignore`, whose sous-managed block is applied by the same writer
13
- * `sous repo link` uses, so running the scaffold over an existing ignore file
14
- * never duplicates an entry; and the project's `package.json`, when there is
15
- * one, which gains `@sous-io/sous` as a devDependency at the running version
16
- * unless it already depends on it. A project that installs sous pins the
17
- * version its templates were written against, and a global sous hands off to
18
- * that copy, so the dependency is what makes every build of the project use
19
- * one version. Nothing is installed: the project's own package manager does
20
- * that, and sous does not guess which one it is.
21
- */
22
-
23
- import fs from "node:fs";
24
- import path from "node:path";
25
- import { ConfigError } from "../errors.js";
26
- import {
27
- CONFIG_FILE_NAMES,
28
- ENV_DEFAULTS_NAME,
29
- ENV_LOCAL_NAME,
30
- SOUS_DIR_NAME,
31
- findConfigInSousDir,
32
- resolveConfigFlag,
33
- } from "../config-discovery.js";
34
- import { applyManagedIgnoreBlock } from "../repos/links.js";
35
- import { loadSettings } from "../settings.js";
36
- import {
37
- PROJECT_CONFIG_FORMATS,
38
- STARTER_PROMPT_RELATIVE_PATH,
39
- buildConfigJs,
40
- buildConfigJson,
41
- buildEnvDefaults,
42
- buildEnvLocalExample,
43
- buildStarterPrompt,
44
- type ProjectConfigFormat,
45
- type ProjectScaffoldContext,
46
- } from "./templates.js";
47
-
48
- export * from "./templates.js";
49
-
50
- /** The npm package a scaffolded project is made to depend on. */
51
- export const SOUS_PACKAGE_NAME = "@sous-io/sous";
52
-
53
- /** What the scaffold did about the project's package.json. */
54
- export type PackageJsonOutcome =
55
- | { kind: "absent" }
56
- | { kind: "present"; path: string; range: string; section: "dependencies" | "devDependencies" }
57
- | { kind: "added"; path: string; range: string };
58
-
59
- /** What to scaffold, and where. */
60
- export type ProjectScaffoldOptions = {
61
- /**
62
- * Absolute path to the `.sous/` directory to create. Its parent is the
63
- * project root, which is where the starter prompt compiles to.
64
- */
65
- sousDir: string;
66
- /** Which config format to write. Defaults to the first of `PROJECT_CONFIG_FORMATS`. */
67
- format?: ProjectConfigFormat;
68
- /** The project's display name. Defaults to the project root's own directory name. */
69
- name?: string;
70
- /** Work out every file, check the target, but write nothing. */
71
- dryRun?: boolean;
72
- /** The version of sous doing the scaffolding, named in the generated files. */
73
- sousVersion: string;
74
- };
75
-
76
- /** What a scaffold produced. */
77
- export type ProjectScaffoldResult = {
78
- /** The `.sous/` directory the scaffold was written into. */
79
- sousDir: string;
80
- /** The project root: the parent of `sousDir`. */
81
- projectRoot: string;
82
- /** Absolute path of the primary config that was written. */
83
- configPath: string;
84
- /** The format the config was written in. */
85
- format: ProjectConfigFormat;
86
- /** The display name written into the config. */
87
- name: string;
88
- /** Paths of every file written, relative to the project root, in the order written. */
89
- files: string[];
90
- /** Whether `@sous-io/sous` was added to the project's package.json, was already there, or there is no package.json. */
91
- packageJson: PackageJsonOutcome;
92
- /** True when nothing was actually written. */
93
- dryRun: boolean;
94
- };
95
-
96
- /** One planned file: where it goes, and what goes in it. */
97
- type PlannedFile = {
98
- /** Path relative to the project root. */
99
- relativePath: string;
100
- /** The complete file contents. */
101
- contents: string;
102
- /**
103
- * True for a file whose existing contents were merged into `contents`
104
- * rather than a file the scaffold refuses to write over.
105
- */
106
- merged?: boolean;
107
- };
108
-
109
- /** The file name a config of the given format is written under. */
110
- export function configFileNameFor(format: ProjectConfigFormat): string {
111
- const name = `sous.config.${format}`;
112
- /* c8 ignore next 5 */
113
- if (!(CONFIG_FILE_NAMES as readonly string[]).includes(name)) {
114
- throw new ConfigError(
115
- `sous cannot write a '${format}' config: discovery does not recognize ${name}.`
116
- );
117
- }
118
- return name;
119
- }
120
-
121
- /**
122
- * Creates a project's `.sous/` directory: a primary config, the starter prompt
123
- * it compiles, the two answers files, and the sous-managed block in
124
- * `.sous/.gitignore`.
125
- *
126
- * @param options - What to scaffold, and where.
127
- * @throws ConfigError when the target already holds a primary config, or any
128
- * other file the scaffold would write, or when the written config does not
129
- * load.
130
- */
131
- export async function scaffoldProject(
132
- options: ProjectScaffoldOptions
133
- ): Promise<ProjectScaffoldResult> {
134
- const sousDir = path.resolve(options.sousDir);
135
- const projectRoot = path.dirname(sousDir);
136
- const format = options.format ?? PROJECT_CONFIG_FORMATS[0];
137
- const name = (options.name ?? path.basename(projectRoot)).trim() || path.basename(projectRoot);
138
- const dryRun = options.dryRun === true;
139
-
140
- assertNoExistingConfig(sousDir);
141
-
142
- const context: ProjectScaffoldContext = { name, sousVersion: options.sousVersion };
143
- const files = planFiles(sousDir, projectRoot, format, context);
144
- const dependency = planPackageJson(projectRoot, options.sousVersion);
145
- if (dependency.file !== undefined) files.push(dependency.file);
146
-
147
- assertNothingWouldBeOverwritten(projectRoot, files);
148
-
149
- const configPath = path.join(sousDir, configFileNameFor(format));
150
-
151
- if (!dryRun) {
152
- for (const file of files) {
153
- const target = path.join(projectRoot, file.relativePath);
154
- fs.mkdirSync(path.dirname(target), { recursive: true });
155
- fs.writeFileSync(target, file.contents, "utf8");
156
- }
157
- await verifyScaffold(configPath, projectRoot);
158
- }
159
-
160
- return {
161
- sousDir,
162
- projectRoot,
163
- configPath,
164
- format,
165
- name,
166
- files: files.map((file) => file.relativePath),
167
- packageJson: dependency.outcome,
168
- dryRun,
169
- };
170
- }
171
-
172
- /**
173
- * Works out the package.json edit: nothing when the project has no
174
- * package.json or already depends on sous, otherwise the file rewritten with
175
- * `@sous-io/sous` in `devDependencies` at exactly the running version. The
176
- * file's own indentation and trailing newline are kept, and `devDependencies`
177
- * stays sorted the way npm keeps it. A package.json that is not JSON is
178
- * refused by name, before anything is written.
179
- */
180
- function planPackageJson(
181
- projectRoot: string,
182
- sousVersion: string
183
- ): { file?: PlannedFile; outcome: PackageJsonOutcome } {
184
- const packageJsonPath = path.join(projectRoot, "package.json");
185
- if (!fs.existsSync(packageJsonPath)) return { outcome: { kind: "absent" } };
186
-
187
- const raw = fs.readFileSync(packageJsonPath, "utf8");
188
- let parsed: unknown;
189
- try {
190
- parsed = JSON.parse(raw);
191
- } catch (error) {
192
- throw new ConfigError(
193
- `'sous init' would add ${SOUS_PACKAGE_NAME} to ${packageJsonPath}, but could not read it as JSON.\n` +
194
- ` ${(error as Error).message}\n` +
195
- ` Fix the file, or run 'sous init' in a directory without a package.json.`
196
- );
197
- }
198
- if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
199
- throw new ConfigError(
200
- `'sous init' would add ${SOUS_PACKAGE_NAME} to ${packageJsonPath}, but it does not hold a JSON object.`
201
- );
202
- }
203
- const pkg = parsed as Record<string, unknown>;
204
-
205
- for (const section of ["dependencies", "devDependencies"] as const) {
206
- const deps = pkg[section];
207
- if (typeof deps === "object" && deps !== null && SOUS_PACKAGE_NAME in deps) {
208
- const range = String((deps as Record<string, unknown>)[SOUS_PACKAGE_NAME]);
209
- return { outcome: { kind: "present", path: packageJsonPath, range, section } };
210
- }
211
- }
212
-
213
- const existing =
214
- typeof pkg.devDependencies === "object" && pkg.devDependencies !== null
215
- ? (pkg.devDependencies as Record<string, unknown>)
216
- : {};
217
- const devDependencies = Object.fromEntries(
218
- Object.entries({ ...existing, [SOUS_PACKAGE_NAME]: sousVersion }).sort(([a], [b]) =>
219
- a < b ? -1 : a > b ? 1 : 0
220
- )
221
- );
222
-
223
- const indent = raw.match(/^(\s+)"/m)?.[1] ?? " ";
224
- const newline = raw.endsWith("\n") ? "\n" : "";
225
- const contents = JSON.stringify({ ...pkg, devDependencies }, null, indent) + newline;
226
-
227
- return {
228
- file: { relativePath: "package.json", contents, merged: true },
229
- outcome: { kind: "added", path: packageJsonPath, range: sousVersion },
230
- };
231
- }
232
-
233
- /** Builds every file the scaffold writes, in the order they are written. */
234
- function planFiles(
235
- sousDir: string,
236
- projectRoot: string,
237
- format: ProjectConfigFormat,
238
- context: ProjectScaffoldContext
239
- ): PlannedFile[] {
240
- const inSousDir = (name: string): string =>
241
- path.relative(projectRoot, path.join(sousDir, name));
242
-
243
- const gitignorePath = path.join(sousDir, ".gitignore");
244
- const existingIgnore = fs.existsSync(gitignorePath)
245
- ? fs.readFileSync(gitignorePath, "utf8")
246
- : undefined;
247
-
248
- return [
249
- {
250
- relativePath: inSousDir(configFileNameFor(format)),
251
- contents: format === "json" ? buildConfigJson(context) : buildConfigJs(context),
252
- },
253
- {
254
- relativePath: inSousDir(STARTER_PROMPT_RELATIVE_PATH),
255
- contents: buildStarterPrompt(context),
256
- },
257
- { relativePath: inSousDir(ENV_DEFAULTS_NAME), contents: buildEnvDefaults(context) },
258
- {
259
- relativePath: inSousDir(`${ENV_LOCAL_NAME}.example`),
260
- contents: buildEnvLocalExample(context),
261
- },
262
- {
263
- relativePath: inSousDir(".gitignore"),
264
- contents: applyManagedIgnoreBlock(existingIgnore, gitignorePath),
265
- merged: true,
266
- },
267
- ];
268
- }
269
-
270
- /**
271
- * Refuses to scaffold a `.sous/` directory that already holds a primary
272
- * config. The config is what makes a directory a sous project, so that is what
273
- * is checked; a `.sous/` holding only, say, task files is a fine place to set
274
- * one up.
275
- */
276
- function assertNoExistingConfig(sousDir: string): void {
277
- const existing = findConfigInSousDir(sousDir);
278
- if (existing !== null) {
279
- throw new ConfigError(
280
- `${path.dirname(sousDir)} is already set up for sous.\n` +
281
- ` ${existing} exists, and sous will not overwrite it.\n` +
282
- ` Edit that config instead, or run 'sous init' in another directory.`
283
- );
284
- }
285
- }
286
-
287
- /**
288
- * Refuses to write over any file the scaffold produces, so a run that fails
289
- * here has changed nothing. The merged ignore file is exempt: its existing
290
- * lines are carried into what is written.
291
- */
292
- function assertNothingWouldBeOverwritten(projectRoot: string, files: PlannedFile[]): void {
293
- const clashes = files
294
- .filter((file) => file.merged !== true)
295
- .map((file) => path.join(projectRoot, file.relativePath))
296
- .filter((target) => fs.existsSync(target));
297
-
298
- if (clashes.length === 0) return;
299
-
300
- throw new ConfigError(
301
- `${projectRoot} already holds ${clashes.length === 1 ? "a file" : "files"} that ` +
302
- `'sous init' would write, and sous will not overwrite ${clashes.length === 1 ? "it" : "them"}:\n` +
303
- clashes.map((target) => ` ${target}`).join("\n") +
304
- `\n Move ${clashes.length === 1 ? "it" : "them"} aside, or run 'sous init' in another directory.`
305
- );
306
- }
307
-
308
- /**
309
- * Loads the written config through the same loader every command uses, so
310
- * the scaffold is never reported as a success unless sous can actually read
311
- * it. A failure here is a bug in the scaffold, and is reported as one.
312
- */
313
- async function verifyScaffold(configPath: string, projectRoot: string): Promise<void> {
314
- try {
315
- await loadSettings(resolveConfigFlag(configPath, projectRoot));
316
- } catch (error) {
317
- const reason = error instanceof Error ? error.message : String(error);
318
- throw new ConfigError(
319
- `The config 'sous init' wrote at ${configPath} does not load.\n` +
320
- ` This is a bug in sous; please report it. The loader said:\n` +
321
- `${reason
322
- .split("\n")
323
- .map((line) => ` ${line}`)
324
- .join("\n")}`
325
- );
326
- }
327
- }
328
-
329
- /** The `.sous/` directory a project root would hold. */
330
- export function sousDirFor(projectRoot: string): string {
331
- return path.join(path.resolve(projectRoot), SOUS_DIR_NAME);
332
- }
@@ -1,240 +0,0 @@
1
- /**
2
- * The files `sous init` writes, as plain string builders.
3
- *
4
- * As with the repository scaffold, these are deliberately not templates run
5
- * through a template engine. A scaffolded project is read by a person before
6
- * it is read by a machine, and the comments are what make the first config
7
- * legible; a rendering step would only stand between the author of the
8
- * scaffold and the person who has to live with the result.
9
- *
10
- * Every builder returns a complete file, ending in a newline. The JSON config
11
- * is the one file that cannot carry a comment: the config schema is strict and
12
- * accepts `$schema` but no comment key, so what the JS variant explains in
13
- * comments the JSON variant leaves to the documentation.
14
- */
15
-
16
- import {
17
- CONFD_DIR_NAME,
18
- ENV_DEFAULTS_NAME,
19
- ENV_LOCAL_NAME,
20
- SOUS_DIR_NAME,
21
- } from "../config-discovery.js";
22
-
23
- /** The config formats `sous init` can write. The first one is the default. */
24
- export const PROJECT_CONFIG_FORMATS = ["js", "json"] as const;
25
-
26
- /** One of the config formats `sous init` can write. */
27
- export type ProjectConfigFormat = (typeof PROJECT_CONFIG_FORMATS)[number];
28
-
29
- /**
30
- * The path, relative to `.sous/`, of the starter prompt the scaffolded config
31
- * compiles. It lives under `memories/`, the directory for what a build composes
32
- * into an agent's always-loaded instruction file, so the source and its
33
- * compiled output (`AGENTS.md` at the project root) are never mistaken for one
34
- * another.
35
- */
36
- export const STARTER_PROMPT_RELATIVE_PATH = "memories/AGENTS.tpl.md";
37
-
38
- /** The name of the file the starter prompt is compiled into, at the project root. */
39
- export const STARTER_OUTPUT_NAME = "AGENTS.md";
40
-
41
- /** What every builder needs to know about the project being scaffolded. */
42
- export type ProjectScaffoldContext = {
43
- /** The project's display name, written into the config's `name`. */
44
- name: string;
45
- /** The version of sous doing the scaffolding, named in the generated files. */
46
- sousVersion: string;
47
- };
48
-
49
- /**
50
- * Where the JSON Schema for a config of this sous version is published. The
51
- * schema ships inside the package too, but an editor resolving `$schema` wants
52
- * a URL, and the tagged copy on GitHub is the one that matches this version
53
- * for as long as the tag exists.
54
- *
55
- * @param sousVersion - The running sous version.
56
- */
57
- export function configSchemaUrl(sousVersion: string): string {
58
- return `https://raw.githubusercontent.com/sous-io/sous/v${sousVersion}/sous.config.schema.json`;
59
- }
60
-
61
- /**
62
- * The primary config as a JavaScript module, commented so a first-time reader
63
- * can see what each block is for without opening the documentation.
64
- *
65
- * @param context - The project being scaffolded.
66
- */
67
- export function buildConfigJs(context: ProjectScaffoldContext): string {
68
- return `// The primary sous config for ${context.name}.
69
- //
70
- // sous finds this file by walking up from the directory it was run in until it
71
- // meets a \`${SOUS_DIR_NAME}/\` directory holding a config. Everything a build needs
72
- // starts here. Drop-in layers under \`${SOUS_DIR_NAME}/${CONFD_DIR_NAME}/\` merge over it, and
73
- // the layers sous writes for itself go into that directory, never into this
74
- // file. Written by \`sous init\` (sous ${context.sousVersion}); it is yours now.
75
-
76
- export const config = {
77
- // Shown in command output. Any string.
78
- name: ${JSON.stringify(context.name)},
79
-
80
- // Variables for the rest of this file. \`\${sousDir}\` is this \`${SOUS_DIR_NAME}/\` directory,
81
- // injected by sous, so nothing here depends on where the project is checked
82
- // out. Reference a variable anywhere below as \`\${name}\`.
83
- _vars: {
84
- projectRoot: "\${sousDir}/..",
85
- },
86
-
87
- // What to compile. Each target reads one source and writes it somewhere. The
88
- // starter target renders \`${SOUS_DIR_NAME}/${STARTER_PROMPT_RELATIVE_PATH}\` to the project root.
89
- // A line holding only \`@path/to/file.md\` in a source pulls that file in, and a
90
- // \`.tpl.\` in a file name turns Liquid templating on for it. Compiled files
91
- // are build output: ignore them or commit them, as your team prefers.
92
- compilation: {
93
- targets: [
94
- {
95
- entryPoint: "\${sousDir}/${STARTER_PROMPT_RELATIVE_PATH}",
96
- outputs: [{ destinationFile: "\${projectRoot}/${STARTER_OUTPUT_NAME}" }],
97
- },
98
- ],
99
- },
100
-
101
- // What to do with the content the recipes this project subscribes to publish.
102
- // Every project subscribes to the \`core\` namespace on its own, which is how
103
- // the skills that teach an agent about sous reach \`.claude/skills\`. Memories
104
- // are not copied anywhere: the starter prompt pulls them in with one include
105
- // line, and the lists below (uncomment to use them) choose which come first
106
- // and which are left out. An entry is a glob over \`namespace/recipe\`, or a
107
- // regular expression written between two slashes.
108
- recipes: {
109
- skills: { outputs: ["\${projectRoot}/.claude/skills"] },
110
- // memories: {
111
- // first: ["communication/*"],
112
- // exclude: ["tool-usage/automated-browser-tasks", "/^experimental\\//"],
113
- // },
114
- },
115
- };
116
- `;
117
- }
118
-
119
- /**
120
- * The same primary config as strict JSON, bound to the shipped schema through
121
- * `$schema` so an editor can validate and complete it.
122
- *
123
- * @param context - The project being scaffolded.
124
- */
125
- export function buildConfigJson(context: ProjectScaffoldContext): string {
126
- const config = {
127
- $schema: configSchemaUrl(context.sousVersion),
128
- name: context.name,
129
- _vars: {
130
- projectRoot: "${sousDir}/..",
131
- },
132
- compilation: {
133
- targets: [
134
- {
135
- entryPoint: `\${sousDir}/${STARTER_PROMPT_RELATIVE_PATH}`,
136
- outputs: [{ destinationFile: `\${projectRoot}/${STARTER_OUTPUT_NAME}` }],
137
- },
138
- ],
139
- },
140
- recipes: {
141
- skills: { outputs: ["${projectRoot}/.claude/skills"] },
142
- },
143
- };
144
- return `${JSON.stringify(config, null, 2)}\n`;
145
- }
146
-
147
- /**
148
- * The starter prompt the config compiles: a short, real instruction file, so
149
- * the first build produces something worth reading rather than a placeholder.
150
- *
151
- * @param context - The project being scaffolded.
152
- */
153
- export function buildStarterPrompt(context: ProjectScaffoldContext): string {
154
- return `# ${context.name}
155
-
156
- This file is compiled by sous into \`${STARTER_OUTPUT_NAME}\` at the project root. Edit this
157
- source, run \`sous build\`, and the compiled copy follows; never edit the compiled copy.
158
-
159
- ## Working in this project
160
-
161
- - Describe the project here: what it is, how it is built, and how it is tested.
162
- - Split long sections into their own files under \`${SOUS_DIR_NAME}/memories/\` and pull
163
- each one in with a line holding only \`@sections/name.md\`.
164
- - The skills under \`.claude/skills\` are written by sous from the recipes this
165
- project subscribes to. \`sous recipe list\` shows what is available.
166
-
167
- {% comment %}This line pulls in the memories of every recipe this project subscribes to.{% endcomment %}
168
- @#memories/**/*.md
169
- `;
170
- }
171
-
172
- /**
173
- * The committed answers file: the layer for what the whole team shares.
174
- *
175
- * @param context - The project being scaffolded.
176
- */
177
- export function buildEnvDefaults(context: ProjectScaffoldContext): string {
178
- return `# ${SOUS_DIR_NAME}/${ENV_DEFAULTS_NAME}
179
- #
180
- # Answers to recipe variables that the whole team shares. Commit this file.
181
- #
182
- # A recipe this project subscribes to may ask questions (a board name, a
183
- # ticket prefix, where task files go), and the answers live here as
184
- # \`KEY=VALUE\` lines. \`sous vars ask\` writes them one question at a time, and
185
- # \`sous vars list\` shows every variable in play with where its answer came from.
186
- #
187
- # Never put a secret, a login or a machine-specific path here. Those belong in
188
- # \`${SOUS_DIR_NAME}/${ENV_LOCAL_NAME}\`, which is gitignored and overrides this file
189
- # key for key. Precedence, highest first: your shell, then \`${ENV_LOCAL_NAME}\`,
190
- # then this file.
191
- #
192
- # Written by \`sous init\` (sous ${context.sousVersion}).
193
- `;
194
- }
195
-
196
- /**
197
- * The example for the gitignored answers file, explaining what belongs in the
198
- * local layer and how to start one.
199
- *
200
- * @param context - The project being scaffolded.
201
- */
202
- export function buildEnvLocalExample(context: ProjectScaffoldContext): string {
203
- return `# ${SOUS_DIR_NAME}/${ENV_LOCAL_NAME}.example
204
- #
205
- # Copy this file to \`${SOUS_DIR_NAME}/${ENV_LOCAL_NAME}\` and fill in your own values:
206
- #
207
- # cp ${SOUS_DIR_NAME}/${ENV_LOCAL_NAME}.example ${SOUS_DIR_NAME}/${ENV_LOCAL_NAME}
208
- #
209
- # ...or let sous write it for you, one question at a time:
210
- #
211
- # sous vars ask
212
- #
213
- # \`${SOUS_DIR_NAME}/${ENV_LOCAL_NAME}\` is gitignored. It is the layer for anything that
214
- # differs between people or machines, or must not be committed: your own login
215
- # and name, absolute paths outside the repository, API tokens.
216
- #
217
- # The COMMITTED layer is \`${SOUS_DIR_NAME}/${ENV_DEFAULTS_NAME}\`. Same syntax, but it holds
218
- # the answers the whole team shares. Never put a secret there.
219
- #
220
- # How both files are loaded:
221
- # - \`KEY=VALUE\` per line. \`#\` starts a comment. \`export KEY=VALUE\` also works.
222
- # - Loaded into the environment before the config resolves any variables.
223
- # - Precedence, highest first: your shell, then \`${ENV_LOCAL_NAME}\`, then \`${ENV_DEFAULTS_NAME}\`.
224
- # So \`FOO=bar sous build\` overrides \`FOO\` from either file for that one run,
225
- # and a key in \`${ENV_LOCAL_NAME}\` overrides the same key in \`${ENV_DEFAULTS_NAME}\`.
226
- # - A recipe's variable answers reach its templates on their own. \`sous vars
227
- # list\` shows each one, with the environment variable that supplied it.
228
- # - The config's own \`_env\` block still works for anything a recipe does not
229
- # ask about: \`_env: { myPath: "MY_PATH" }\` makes \`\${myPath}\` available
230
- # throughout the config from \`MY_PATH\` in either file.
231
- #
232
- # NOT here: the config-location variables \`SOUS_CONFIG\`, \`SOUS_DIR\` and
233
- # \`SOUS_CONFD\`. Those decide where sous looks for \`${SOUS_DIR_NAME}/\`, so they are
234
- # read from the real shell environment only; this file is not found until
235
- # discovery has already run.
236
- #
237
- # Written by \`sous init\` (sous ${context.sousVersion}). Add a line per answer below,
238
- # with a comment saying what it is for.
239
- `;
240
- }