@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,452 +0,0 @@
1
- /**
2
- * What `sous repo contribute` chains together, and how it decides whether a
3
- * branch still has something to propose.
4
- *
5
- * Contributing to a recipe repository is a lifecycle: start (link the
6
- * repository on a fresh branch of an up-to-date checkout), edit, propose,
7
- * revise, and finish (propose whatever is left, then go back to the published
8
- * versions). Each part already has a command of its own (`sous repo link`,
9
- * `sous repo submit`, `sous repo unlink`), and the contribution command adds
10
- * nothing those commands lack: this module only works out which of them to run,
11
- * with which flags, in which order.
12
- *
13
- * A step is described as the command it runs and the arguments it passes, so
14
- * the command can run it in-process, print it for a dry run, and name it in a
15
- * failure, all from one description. Everything here that reads the checkout
16
- * goes through the injectable git runner, so no test needs a network.
17
- */
18
-
19
- import { ConfigError } from "../errors.js";
20
- import {
21
- currentBranch,
22
- defaultBranch,
23
- runGit,
24
- UPSTREAM_REMOTE,
25
- type GitOptions,
26
- } from "./git-clone.js";
27
- import type { ProposalSummary } from "./providers/provider.js";
28
- import { FORK_REMOTE } from "./release/submit-service.js";
29
-
30
- /** The commands a contribution is made of, by their oclif ids. */
31
- export type ContributionCommand = "repo:link" | "repo:submit" | "repo:unlink";
32
-
33
- /** One command a contribution runs, and how to talk about it. */
34
- export type ContributionStep = {
35
- /** The command's oclif id. */
36
- command: ContributionCommand;
37
- /** The arguments and flags passed to it, exactly as a command line would carry them. */
38
- argv: string[];
39
- /** What the step does, as a heading printed when it starts. */
40
- running: string;
41
- /** What the step did, as a line in the list of completed steps. */
42
- done: string;
43
- };
44
-
45
- /**
46
- * Config-locating flags the contribution command was given, which every step
47
- * that discovers a project config has to be given as well, or it could find a
48
- * different project from the one the contribution started in.
49
- */
50
- export type LocatorFlags = {
51
- config?: string;
52
- "sous-dir"?: string;
53
- "sous-confd"?: string;
54
- };
55
-
56
- /** The flags that shape the start of a contribution. */
57
- export type StartFlags = {
58
- /** `--branch`: an existing branch to work on. */
59
- branch?: string;
60
- /** `--create-branch`: a new branch to create. */
61
- createBranch?: string;
62
- /** `--generate-branch`: a new branch with a generated name. */
63
- generateBranch?: boolean;
64
- /** `--from`: the branch a new branch starts from. */
65
- from?: string;
66
- /** `--global`: the machine-wide link. */
67
- global?: boolean;
68
- /** The confirmation flag. */
69
- yes?: boolean;
70
- };
71
-
72
- /** The flags that shape the end of a contribution. */
73
- export type FinishFlags = {
74
- /** `--title`: the proposal's title. */
75
- title?: string;
76
- /** `--body`: the proposal's description. */
77
- body?: string;
78
- /** `--draft`: open a new proposal as a draft. */
79
- draft?: boolean;
80
- /** `--commit`: commit uncommitted changes as part of the submission. */
81
- commit?: boolean;
82
- /** `--branch`: the branch to submit, instead of the one checked out. */
83
- branch?: string;
84
- /** `--remove`: delete the checkout when unlinking. */
85
- remove?: boolean;
86
- /** `--global`: the machine-wide link. */
87
- global?: boolean;
88
- /** The confirmation flag. */
89
- yes?: boolean;
90
- };
91
-
92
- /**
93
- * The config-locating flags, as arguments.
94
- *
95
- * @param locator - The flags the contribution command was given.
96
- */
97
- export function locatorArgs(locator: LocatorFlags): string[] {
98
- const args: string[] = [];
99
- if (locator.config !== undefined) args.push("--config", locator.config);
100
- if (locator["sous-dir"] !== undefined) args.push("--sous-dir", locator["sous-dir"]);
101
- if (locator["sous-confd"] !== undefined) args.push("--sous-confd", locator["sous-confd"]);
102
- return args;
103
- }
104
-
105
- /**
106
- * The step that starts a contribution: `sous repo link <repo> --latest` on a
107
- * new branch. The branch is generated unless one was named, either as a branch
108
- * to create or as an existing branch to work on.
109
- *
110
- * startStep("sous-recipes", {}, {}).argv
111
- * // -> ["sous-recipes", "--latest", "--generate-branch"]
112
- *
113
- * @param repo - The repository's short name.
114
- * @param flags - The start flags, passed through.
115
- * @param locator - The config-locating flags, passed through.
116
- */
117
- export function startStep(
118
- repo: string,
119
- flags: StartFlags,
120
- locator: LocatorFlags = {}
121
- ): ContributionStep {
122
- const argv = [repo, "--latest"];
123
- let running: string;
124
- let done: string;
125
-
126
- if (flags.branch !== undefined) {
127
- argv.push("--branch", flags.branch);
128
- running = `Linking '${repo}' on the branch '${flags.branch}', made to match upstream's`;
129
- done = `Linked '${repo}' on the branch '${flags.branch}'`;
130
- } else if (flags.createBranch !== undefined) {
131
- argv.push("--create-branch", flags.createBranch);
132
- running = `Linking '${repo}' on a new branch named '${flags.createBranch}'`;
133
- done = `Linked '${repo}' on the new branch '${flags.createBranch}'`;
134
- } else {
135
- argv.push("--generate-branch");
136
- running = `Linking '${repo}' on a new branch with a generated name`;
137
- done = `Linked '${repo}' on a new branch`;
138
- }
139
-
140
- if (flags.from !== undefined) argv.push("--from", flags.from);
141
- if (flags.global === true) argv.push("--global");
142
- if (flags.yes === true) argv.push("--yes");
143
- argv.push(...locatorArgs(locator));
144
-
145
- return { command: "repo:link", argv, running, done };
146
- }
147
-
148
- /**
149
- * The step that proposes what the branch holds: `sous repo submit <repo>`,
150
- * with the proposal flags passed through. The confirmation flag is passed only
151
- * when it was given, so a question the contributor has not answered ahead of
152
- * time is still asked.
153
- *
154
- * submitStep("sous-recipes", { title: "Fix a typo" }).argv
155
- * // -> ["sous-recipes", "--title", "Fix a typo"]
156
- *
157
- * @param repo - The repository's short name.
158
- * @param flags - The finish flags, passed through.
159
- */
160
- export function submitStep(repo: string, flags: FinishFlags): ContributionStep {
161
- const argv = [repo];
162
- if (flags.title !== undefined) argv.push("--title", flags.title);
163
- if (flags.body !== undefined) argv.push("--body", flags.body);
164
- if (flags.branch !== undefined) argv.push("--branch", flags.branch);
165
- if (flags.draft === true) argv.push("--draft");
166
- if (flags.commit === true) argv.push("--commit");
167
- if (flags.yes === true) argv.push("--yes");
168
-
169
- return {
170
- command: "repo:submit",
171
- argv,
172
- running: "Proposing what the branch holds",
173
- done: "Proposed what the branch holds",
174
- };
175
- }
176
-
177
- /**
178
- * The step that ends a contribution: `sous repo unlink <repo> --update`, which
179
- * goes back to the published versions and moves the pins to the newest ones
180
- * their ranges allow, so a release carrying the change is picked up.
181
- *
182
- * unlinkStep("sous-recipes", { remove: true }).argv
183
- * // -> ["sous-recipes", "--update", "--remove"]
184
- *
185
- * @param repo - The repository's short name.
186
- * @param flags - The finish flags, passed through.
187
- * @param locator - The config-locating flags, passed through.
188
- */
189
- export function unlinkStep(
190
- repo: string,
191
- flags: FinishFlags,
192
- locator: LocatorFlags = {}
193
- ): ContributionStep {
194
- const argv = [repo, "--update"];
195
- if (flags.remove === true) argv.push("--remove");
196
- if (flags.global === true) argv.push("--global");
197
- if (flags.yes === true) argv.push("--yes");
198
- argv.push(...locatorArgs(locator));
199
-
200
- return {
201
- command: "repo:unlink",
202
- argv,
203
- running:
204
- flags.remove === true
205
- ? `Unlinking '${repo}', updating its pins and deleting the checkout`
206
- : `Unlinking '${repo}' and updating its pins`,
207
- done:
208
- flags.remove === true
209
- ? `Unlinked '${repo}', updated its pins and deleted the checkout`
210
- : `Unlinked '${repo}' and updated its pins`,
211
- };
212
- }
213
-
214
- /**
215
- * A step written out as the command line that would run it, for a dry run and
216
- * for anyone who wants to run the parts by hand.
217
- *
218
- * commandLine(startStep("sous-recipes", {}))
219
- * // -> "sous repo link sous-recipes --latest --generate-branch"
220
- *
221
- * @param step - The step.
222
- */
223
- export function commandLine(step: ContributionStep): string {
224
- const words = ["sous", ...step.command.split(":"), ...step.argv];
225
- return words.map(quoteArgument).join(" ");
226
- }
227
-
228
- /** An argument as a shell would need it written: quoted when it holds anything unusual. */
229
- function quoteArgument(value: string): string {
230
- if (/^[A-Za-z0-9_@%+=:,./~-]+$/.test(value)) return value;
231
- return `'${value.replace(/'/g, `'\\''`)}'`;
232
- }
233
-
234
- // --- What the branch still has to propose ----------------------------------------------------------
235
-
236
- /** What a checkout's branch holds that a proposal may not carry yet. */
237
- export type PendingWork = {
238
- /** The branch that was examined, or undefined when HEAD is detached. */
239
- branch?: string;
240
- /** The upstream default branch, when it could be worked out. */
241
- baseBranch?: string;
242
- /** Every uncommitted change, as `git status --porcelain` prints it. */
243
- uncommitted: string[];
244
- /** Commits on the branch that the upstream default branch lacks, one line each. */
245
- ahead: string[];
246
- /** Of those, the commits no pushed copy of the branch holds, one line each. */
247
- unpushed: string[];
248
- /** The pushed copies of the branch this checkout knows about, such as `origin/my-change`. */
249
- pushedCopies: string[];
250
- };
251
-
252
- /**
253
- * Reads what a checkout's branch holds that has not been proposed: its
254
- * uncommitted changes, its commits beyond the upstream default branch, and
255
- * which of those no pushed copy of the branch holds. Nothing is fetched; the
256
- * pushed copies are the remote-tracking branches a push leaves behind.
257
- *
258
- * pendingWork("/path/to/checkout")
259
- * // -> { branch: "sous/edit-20260927-1200", baseBranch: "main", uncommitted: [],
260
- * // ahead: ["1a2b3c4 Fix a typo"], unpushed: ["1a2b3c4 Fix a typo"], pushedCopies: [] }
261
- *
262
- * @param directory - The checkout.
263
- * @param branch - The branch to examine; the checked-out one when omitted.
264
- * @param options - The git runner to use.
265
- */
266
- export function pendingWork(
267
- directory: string,
268
- branch?: string,
269
- options: GitOptions = {}
270
- ): PendingWork {
271
- const runner = options.runner ?? runGit;
272
- const git = (args: string[]) => runner(args, { cwd: directory });
273
- const lines = (text: string): string[] =>
274
- text
275
- .split("\n")
276
- .map((line) => line.trim())
277
- .filter((line) => line.length > 0);
278
- const refExists = (ref: string) => git(["rev-parse", "--verify", "--quiet", ref]).status === 0;
279
-
280
- const status = git(["status", "--porcelain"]);
281
- if (status.status !== 0) {
282
- throw new ConfigError(
283
- `Sous could not read the checkout at ${directory}.\n` +
284
- (status.stderr.length > 0 ? ` git said: ${status.stderr}\n` : "") +
285
- ` Check that it is still a git checkout, then run the command again.`
286
- );
287
- }
288
-
289
- const examined = branch ?? currentBranch(directory, options);
290
- const tip = examined === undefined ? "HEAD" : `refs/heads/${examined}`;
291
- if (!refExists(tip)) {
292
- throw new ConfigError(
293
- `The checkout at ${directory} has no branch named '${examined}'.\n` +
294
- ` Name a branch it has with '--branch', or leave the flag off to use the one ` +
295
- `that is checked out.`
296
- );
297
- }
298
-
299
- const baseBranch = defaultBranch(directory, options);
300
- const baseRef =
301
- baseBranch !== undefined && refExists(`refs/remotes/${UPSTREAM_REMOTE}/${baseBranch}`)
302
- ? `refs/remotes/${UPSTREAM_REMOTE}/${baseBranch}`
303
- : undefined;
304
-
305
- const pushedCopies =
306
- examined === undefined
307
- ? []
308
- : [UPSTREAM_REMOTE, FORK_REMOTE]
309
- .map((remote) => `${remote}/${examined}`)
310
- .filter((copy) => refExists(`refs/remotes/${copy}`));
311
-
312
- const commitsNotIn = (exclude: string[]): string[] => {
313
- const args = ["log", "--oneline", "--no-decorate", tip];
314
- // With no upstream default branch to measure against, a commit counts as
315
- // the branch's own when no remote holds it.
316
- if (exclude.length === 0) args.push("--not", "--remotes");
317
- else args.push("--not", ...exclude);
318
- const result = git(args);
319
- return result.status === 0 ? lines(result.stdout) : [];
320
- };
321
-
322
- const base = baseRef === undefined ? [] : [baseRef];
323
- const ahead = commitsNotIn(base);
324
- const unpushed =
325
- pushedCopies.length === 0
326
- ? ahead
327
- : commitsNotIn([...base, ...pushedCopies.map((copy) => `refs/remotes/${copy}`)]);
328
-
329
- return {
330
- ...(examined === undefined ? {} : { branch: examined }),
331
- ...(baseBranch === undefined ? {} : { baseBranch }),
332
- uncommitted: lines(status.stdout),
333
- ahead,
334
- unpushed,
335
- pushedCopies,
336
- };
337
- }
338
-
339
- /**
340
- * Whether a branch has something to propose.
341
- *
342
- * - `nothing`: it holds nothing a proposal lacks, so there is nothing to submit.
343
- * - `pending`: it holds work no proposal carries yet.
344
- * - `lookup`: everything on it was pushed, so only the proposal itself can say
345
- * whether that work is proposed; the caller has to look it up.
346
- */
347
- export type PendingVerdict =
348
- | { kind: "nothing"; reason: string }
349
- | { kind: "pending"; reason: string }
350
- | { kind: "lookup"; reason: string };
351
-
352
- /**
353
- * Judges what `pendingWork` found, without asking the repository host anything.
354
- *
355
- * assessPendingWork({ branch: "fix", baseBranch: "main", uncommitted: [], ahead: [],
356
- * unpushed: [], pushedCopies: [] })
357
- * // -> { kind: "nothing", reason: "The branch 'fix' holds no commits that origin/main lacks, ..." }
358
- *
359
- * @param work - What the checkout's branch holds.
360
- */
361
- export function assessPendingWork(work: PendingWork): PendingVerdict {
362
- const branch = work.branch === undefined ? "the checked-out commit" : `the branch '${work.branch}'`;
363
- const Branch = capitalize(branch);
364
- const base =
365
- work.baseBranch === undefined ? "any remote" : `${UPSTREAM_REMOTE}/${work.baseBranch}`;
366
-
367
- if (work.uncommitted.length > 0) {
368
- return {
369
- kind: "pending",
370
- reason: `The checkout holds ${count(work.uncommitted.length, "uncommitted change")}.`,
371
- };
372
- }
373
- if (work.ahead.length === 0) {
374
- return {
375
- kind: "nothing",
376
- reason: `${Branch} holds no commits that ${base} lacks, so there is nothing to submit.`,
377
- };
378
- }
379
- if (work.unpushed.length > 0) {
380
- return {
381
- kind: "pending",
382
- reason:
383
- `${Branch} holds ${count(work.unpushed.length, "commit")} that ` +
384
- (work.pushedCopies.length === 0
385
- ? "has never been pushed"
386
- : `${work.pushedCopies.join(" and ")} lacks`) +
387
- `, so no proposal carries ${work.unpushed.length === 1 ? "it" : "them"} yet.`,
388
- };
389
- }
390
- return {
391
- kind: "lookup",
392
- reason: `Every commit on ${branch} was pushed to ${work.pushedCopies.join(" and ")}.`,
393
- };
394
- }
395
-
396
- /**
397
- * Judges a pushed branch by its proposal: an open proposal already carries
398
- * everything, and so does a merged one; with no proposal, or one closed
399
- * without being merged, the pushed commits are not proposed.
400
- *
401
- * assessProposal("fix", undefined, "pull request")
402
- * // -> { kind: "pending", reason: "Every commit on the branch 'fix' was pushed, but no pull request is open for it." }
403
- *
404
- * @param branch - The branch.
405
- * @param proposal - The branch's proposal, when it has one.
406
- * @param noun - What the provider calls a proposal.
407
- */
408
- export function assessProposal(
409
- branch: string,
410
- proposal: ProposalSummary | undefined,
411
- noun: string
412
- ): PendingVerdict {
413
- if (proposal === undefined) {
414
- return {
415
- kind: "pending",
416
- reason: `Every commit on the branch '${branch}' was pushed, but no ${noun} is open for it.`,
417
- };
418
- }
419
- switch (proposal.state) {
420
- case "open":
421
- return {
422
- kind: "nothing",
423
- reason:
424
- `Every commit on the branch '${branch}' is already in its open ${noun}` +
425
- `${proposal.url === undefined ? "" : `, ${proposal.url}`}.`,
426
- };
427
- case "merged":
428
- return {
429
- kind: "nothing",
430
- reason:
431
- `The ${noun} for the branch '${branch}' was merged` +
432
- `${proposal.url === undefined ? "" : `: ${proposal.url}`}.`,
433
- };
434
- default:
435
- return {
436
- kind: "pending",
437
- reason:
438
- `The ${noun} for the branch '${branch}' was closed without being merged, so no ` +
439
- `open ${noun} carries its commits.`,
440
- };
441
- }
442
- }
443
-
444
- /** "1 commit", "3 commits". */
445
- function count(value: number, noun: string): string {
446
- return `${value} ${noun}${value === 1 ? "" : "s"}`;
447
- }
448
-
449
- /** The text with its first letter in capitals. */
450
- function capitalize(text: string): string {
451
- return text.length === 0 ? text : `${text[0]!.toUpperCase()}${text.slice(1)}`;
452
- }
@@ -1,105 +0,0 @@
1
- /**
2
- * The core recipe that ships inside the sous package.
3
- *
4
- * One namespace is special: `core`, the skills that teach an agent what sous is
5
- * and how it works. Every project gets it, and a project that has never seen a
6
- * network must still get it, so a copy of the recipe lives in the package at
7
- * `recipes/core/sous-skills/` and seeds the machine-wide store on first run.
8
- *
9
- * Two rules hold this arrangement together:
10
- *
11
- * - VERSION PARITY. The packaged recipe's version is always exactly the
12
- * version of the sous package shipping it, and the implicit `core`
13
- * subscription's range is exactly the running sous version. Core therefore
14
- * always matches the CLI, and upgrading sous upgrades core with it.
15
- * `core-recipe.spec.ts` enforces the first half of that rule, and
16
- * `scripts/sync-core-version.mts` is what satisfies it; the release
17
- * pipeline (`.github/workflows/publish.yml`) enforces it again at publish
18
- * time.
19
- * - NO DEPENDENCIES. The packaged copy is the offline seed, so nothing in it
20
- * may point at a recipe that has to be fetched.
21
- *
22
- * The same recipe is also published by the official repository, so an online
23
- * project resolves it exactly like any other recipe; the package copy is a seed,
24
- * not a private fork.
25
- */
26
-
27
- import path from "node:path";
28
- import { CLI_ROOT, SOUS_VERSION } from "../package-info.js";
29
- import { ConfigError } from "../errors.js";
30
- import { findRecipeManifest, loadManifestFile } from "./load-manifest.js";
31
- import { parseRecipeManifest, type RecipeManifest } from "./formats/recipe-manifest.js";
32
-
33
- /** The directory inside the package that holds every recipe sous ships. */
34
- export const PACKAGED_RECIPES_DIRNAME = "recipes";
35
-
36
- /** The short name the official repository is recorded under in every project. */
37
- export const OFFICIAL_REPO_NAME = "sous-recipes";
38
-
39
- /** Where the official repository lives. */
40
- export const OFFICIAL_REPO_URL = "https://github.com/sous-io/sous-recipes";
41
-
42
- /**
43
- * The official repository's canonical identity: what the machine-wide store and
44
- * the index cache file it under, whatever a project happens to call it.
45
- */
46
- export const OFFICIAL_REPO_IDENTITY = "github.com/sous-io/sous-recipes";
47
-
48
- /** The provider that handles the official repository. */
49
- export const OFFICIAL_REPO_PROVIDER = "github";
50
-
51
- /** The namespace every project is subscribed to unless it opts out. */
52
- export const CORE_NAMESPACE = "core";
53
-
54
- /** The name of the recipe published in that namespace. */
55
- export const CORE_RECIPE_NAME = "sous-skills";
56
-
57
- /** The core recipe's key, `core/sous-skills`. */
58
- export const CORE_RECIPE_KEY = `${CORE_NAMESPACE}/${CORE_RECIPE_NAME}`;
59
-
60
- /** The core recipe's folder, relative to the repository root, as the index records it. */
61
- export const CORE_RECIPE_PATH = `${PACKAGED_RECIPES_DIRNAME}/${CORE_NAMESPACE}/${CORE_RECIPE_NAME}`;
62
-
63
- /**
64
- * The directory holding the packaged copy of the core recipe.
65
- *
66
- * @param packageRoot - The installed package's root directory. Defaults to the
67
- * running CLI's own root, which is what every caller outside a test wants.
68
- */
69
- export function packagedCoreRecipeDir(packageRoot: string = CLI_ROOT): string {
70
- return path.join(packageRoot, PACKAGED_RECIPES_DIRNAME, CORE_NAMESPACE, CORE_RECIPE_NAME);
71
- }
72
-
73
- /**
74
- * Reads and validates the packaged core recipe's manifest.
75
- *
76
- * A missing or unreadable manifest is a broken installation rather than a user
77
- * mistake, so the error says so plainly instead of suggesting a fix the user
78
- * cannot make.
79
- *
80
- * @param packageRoot - The installed package's root directory.
81
- */
82
- export function readPackagedCoreManifest(packageRoot: string = CLI_ROOT): RecipeManifest {
83
- const dir = packagedCoreRecipeDir(packageRoot);
84
- const manifestPath = findRecipeManifest(dir);
85
-
86
- if (manifestPath === undefined) {
87
- throw new ConfigError(
88
- `This installation of sous is missing the core recipe it ships with.\n` +
89
- ` Sous expected to find a recipe manifest in ${dir}.\n` +
90
- ` Reinstalling the sous package will restore it.`
91
- );
92
- }
93
-
94
- return parseRecipeManifest(loadManifestFile(manifestPath), manifestPath);
95
- }
96
-
97
- /**
98
- * The version of the core recipe this installation seeds, which is by rule the
99
- * version of sous itself. Read from the constant rather than from the manifest
100
- * so a damaged package cannot quietly seed a version that does not match the
101
- * CLI; the parity test proves the two agree.
102
- */
103
- export function coreRecipeVersion(): string {
104
- return SOUS_VERSION;
105
- }