@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,286 +0,0 @@
1
- /**
2
- * `sous subscription add <ref>`, also reachable as `sous subscribe <ref>`.
3
- *
4
- * Subscribing is how a recipe enters a project. The ref names a namespace (every
5
- * recipe in it, including ones published later) or one recipe, with an optional
6
- * version range:
7
- *
8
- * sous subscription add workflow/task-files
9
- * sous subscription add workflow/task-files@^1.2.0
10
- * sous subscription add core
11
- * sous subscription add my-recipes:workflow/task-files
12
- *
13
- * A dry run prints the whole plan, including every question these recipes ask
14
- * and where each answer would be stored. Those answers can be supplied on the
15
- * command line with `--answer name=value` (or `--answers-file <path>`), which
16
- * is how a run with no terminal subscribes to a recipe that asks questions.
17
- *
18
- * The whole dependency closure is resolved before anything is downloaded. If it
19
- * reaches a repository this project has not added, sous stops and asks about it
20
- * by name, showing which recipe requires it; a run with no terminal fails
21
- * instead, naming the command that grants the trust. Installs are whole or not
22
- * at all.
23
- */
24
-
25
- import { Args, Flags } from "@oclif/core";
26
- import { BaseCommand } from "../../base-command.js";
27
- import { runProjectBuild } from "../../lib/build-service.js";
28
- import { ConfigError } from "../../lib/errors.js";
29
- import { subscriptionServiceFor } from "../../lib/repos/subscription-service.js";
30
- import { formatAskReport } from "../../lib/vars/ask.js";
31
- import { renderTable, type TableColumn } from "../../utils/table.js";
32
- import {
33
- collectProvidedAnswers,
34
- formatQuestionPlan,
35
- } from "../../lib/vars/index.js";
36
- import {
37
- blankLine,
38
- dryRunNotice,
39
- footer,
40
- heading,
41
- indent,
42
- log,
43
- paragraph,
44
- showCommandVars,
45
- subheading,
46
- warning,
47
- } from "../../utils/formatting.js";
48
- import { answerFlags, confirmationFlag } from "../../utils/flags.js";
49
-
50
- /** How far every line of this command's output is indented. */
51
- const INDENT = 2;
52
-
53
- /**
54
- * The columns the installation report shows: what was installed, at what
55
- * version, and why it is there. The reason wraps rather than being cut, because
56
- * it is the answer to the question a reader is most likely to have.
57
- */
58
- const INSTALLED_COLUMNS: TableColumn[] = [
59
- { key: "key", header: "Recipe", kind: "path", overflow: "truncate", minWidth: 12 },
60
- { key: "version", header: "Version", overflow: "truncate" },
61
- { key: "repo", header: "Repository", overflow: "truncate", priority: "medium" },
62
- { key: "why", header: "Why", overflow: "wrap", flex: 1, minWidth: 16 },
63
- ];
64
-
65
- export default class SubscriptionAdd extends BaseCommand {
66
- static description =
67
- "Subscribe this project to a recipe, or to a whole namespace of them";
68
-
69
- /**
70
- * `subscriptions:add` is the plural spelling of the topic. `subscribe` is the
71
- * original spelling of this command and still works; it is hidden so the
72
- * top-level listing names the command once, under its topic.
73
- */
74
- static aliases = ["subscriptions:add"];
75
-
76
- static hiddenAliases = ["subscribe"];
77
-
78
- static examples = [
79
- "<%= config.bin %> subscription add workflow/task-files",
80
- "<%= config.bin %> subscription add workflow/task-files@^1.2.0",
81
- "<%= config.bin %> subscription add core",
82
- "<%= config.bin %> subscription add workflow/task-files --always-pull",
83
- "<%= config.bin %> subscription add workflow/task-files --dry-run",
84
- "<%= config.bin %> subscription add workflow/task-files --yes --answer apiUrl=https://api.example.com",
85
- ];
86
-
87
- static args = {
88
- ref: Args.string({
89
- description:
90
- "What to subscribe to: 'namespace', 'namespace/recipe', or either with an '@<range>'",
91
- required: true,
92
- }),
93
- };
94
-
95
- static flags = {
96
- ...BaseCommand.baseFlags,
97
- prerelease: Flags.boolean({
98
- description: "Let prerelease versions take part in version range matching",
99
- default: false,
100
- }),
101
- "always-pull": Flags.boolean({
102
- description:
103
- "Install a newer in-range version whenever one exists, rather than holding the locked one",
104
- default: false,
105
- }),
106
- // One flag answers both questions this command can ask: the trust question
107
- // for a repository it has to add, and the subscribe confirmation. `--trust`
108
- // is kept as a spelling of it because the trust ceremony reads naturally
109
- // with that word.
110
- yes: confirmationFlag({ extraAliases: ["trust"] }),
111
- "accept-first": Flags.boolean({
112
- description:
113
- "When a one-word ref matches several things, take the first one listed",
114
- default: false,
115
- }),
116
- "dry-run": Flags.boolean({
117
- description: "Print what would be installed without writing or downloading anything",
118
- default: false,
119
- }),
120
- "no-build": Flags.boolean({
121
- description: "Change the subscription without rebuilding the project",
122
- default: false,
123
- }),
124
- ...answerFlags(),
125
- };
126
-
127
- async run(): Promise<void> {
128
- const { args, flags } = await this.parse(SubscriptionAdd);
129
- const dryRun = flags["dry-run"];
130
-
131
- showCommandVars({
132
- Project: this.projectLabel,
133
- Config: this.configContext.configPath,
134
- Subscribing: args.ref,
135
- "Dry Run": dryRun,
136
- });
137
-
138
- heading("Subscribing");
139
-
140
- if (dryRun) dryRunNotice("Nothing will be downloaded, written or asked.");
141
-
142
- const service = subscriptionServiceFor({
143
- configContext: this.configContext,
144
- settings: this.settings,
145
- shellEnv: this.shellEnv,
146
- });
147
-
148
- // Answers supplied ahead of the questions. They are validated before
149
- // anything is installed, so a typo fails the run rather than being stored
150
- // under a name nothing reads.
151
- const provided = collectProvidedAnswers({
152
- ...(flags.answer === undefined ? {} : { answer: flags.answer }),
153
- ...(flags["answers-file"] === undefined
154
- ? {}
155
- : { answersFile: flags["answers-file"] }),
156
- });
157
-
158
- const outcome = await service.subscribe({
159
- ref: args.ref,
160
- answers: provided,
161
- prerelease: flags.prerelease,
162
- alwaysPull: flags["always-pull"],
163
- trust: flags.yes,
164
- yes: flags.yes,
165
- acceptFirst: flags["accept-first"],
166
- dryRun,
167
- });
168
-
169
- blankLine();
170
- subheading(dryRun ? "What would be installed" : "What was installed");
171
- blankLine();
172
-
173
- const rows = outcome.resolved.map((recipe) => ({
174
- key: recipe.key,
175
- version: recipe.version,
176
- repo: recipe.repo,
177
- why: recipe.requestedBy.includes("project")
178
- ? "you subscribed to it"
179
- : recipe.kind === "subscribes"
180
- ? `co-subscribed by ${recipe.requestedBy.join(", ")}`
181
- : `needed by ${recipe.requestedBy.join(", ")}`,
182
- }));
183
-
184
- for (const line of renderTable(INSTALLED_COLUMNS, rows, { indent: INDENT })) {
185
- log(indent(line, INDENT));
186
- }
187
-
188
- if (outcome.trusted.length > 0) {
189
- blankLine();
190
- paragraph(
191
- `Repositories trusted along the way: ${outcome.trusted.join(", ")}. ` +
192
- `They are now recorded in this project's config, and your colleagues ` +
193
- `inherit them.`
194
- );
195
- }
196
-
197
- blankLine();
198
- subheading("Lockfile");
199
- blankLine();
200
- if (outcome.diff.unchanged) {
201
- paragraph("Nothing changed; everything asked for was already locked.");
202
- } else {
203
- for (const line of outcome.diff.lines) log(indent(line));
204
- }
205
-
206
- if (outcome.answers !== undefined) {
207
- blankLine();
208
- subheading("Variables");
209
- for (const line of formatAskReport(outcome.answers, dryRun)) {
210
- log(line === "" ? "" : indent(line));
211
- }
212
- }
213
-
214
- // The question plan, printed only by a dry run: it is how a caller with no
215
- // terminal learns what this subscription will want to know, so that the
216
- // real run can answer everything with '--answer'.
217
- if (outcome.questions !== undefined) {
218
- blankLine();
219
- subheading("Questions these recipes ask");
220
- blankLine();
221
- // Recipes this machine does not hold yet are named inside the plan, so a
222
- // closure that is only partly readable still lists everything it can.
223
- for (const line of formatQuestionPlan(outcome.questions, {
224
- ...(outcome.unreadable === undefined ? {} : { unreadable: outcome.unreadable }),
225
- })) {
226
- log(line === "" ? "" : indent(line));
227
- }
228
- }
229
-
230
- if (outcome.cycles.length > 0) {
231
- warning(
232
- `Some of these recipes co-subscribe to each other in a circle:\n` +
233
- outcome.cycles.map((cycle) => ` ${cycle.join(" -> ")}`).join("\n") +
234
- `\nThat is unusual but not broken, and everything above was installed.`
235
- );
236
- }
237
-
238
- const rebuilding = !dryRun && !flags["no-build"];
239
-
240
- blankLine();
241
- paragraph(
242
- dryRun
243
- ? `Nothing was written. Run the same command without '--dry-run' to install it.`
244
- : `The subscription to '${outcome.key}' is recorded in this project's config, ` +
245
- `and the exact versions above are recorded in its lockfile.` +
246
- (rebuilding ? `` : ` Run 'sous build' to compile what they contribute.`)
247
- );
248
-
249
- footer();
250
-
251
- if (rebuilding) await this.rebuildProject(outcome.key);
252
- }
253
-
254
- /**
255
- * Rebuilds the project now that the subscription has been written, so the
256
- * files the new recipes contribute are on disk when this command returns.
257
- *
258
- * The subscription lives in a managed `conf.d/` layer, so the settings loaded
259
- * when this command started no longer describe the project; they are reloaded
260
- * before the build, or it would compile the old subscription set. A build that
261
- * fails leaves the subscription in place, because it is already written and
262
- * locked; the message says so and names the command to run once the cause is
263
- * fixed.
264
- *
265
- * @param key - The subscription that was just added, for the failure message.
266
- */
267
- private async rebuildProject(key: string): Promise<void> {
268
- await this.reloadDiscoveredConfig();
269
-
270
- const succeeded = await runProjectBuild({
271
- settings: this.settings,
272
- configContext: this.configContext,
273
- shellEnv: this.shellEnv,
274
- heading: "Building the project",
275
- });
276
-
277
- if (!succeeded) {
278
- throw new ConfigError(
279
- `The subscription to '${key}' was saved, but the build that followed it ` +
280
- `failed, so this project's outputs may be incomplete. The subscription ` +
281
- `itself is recorded and locked; fix what the build reported above and run ` +
282
- `'sous build' again.`
283
- );
284
- }
285
- }
286
- }
@@ -1,208 +0,0 @@
1
- /**
2
- * `sous subscription list`.
3
- *
4
- * Prints every subscription this project declares: the ref, the version range
5
- * it resolves within, the versions its lockfile currently pins, the latest
6
- * version each of those recipes has published, where the subscription came
7
- * from, and whether it is switched on. Entries switched off with
8
- * `enabled: false` are listed too, because an opt-out is part of what a project
9
- * declares.
10
- *
11
- * By default it reads only the config, the lockfile and the cached indexes, so
12
- * it is safe offline and never downloads anything. `--latest` reads the latest
13
- * versions from upstream instead, saving nothing, and `--installed` narrows the
14
- * listing to the subscriptions the lockfile pins something for.
15
- */
16
-
17
- import { BaseCommand } from "../../base-command.js";
18
- import { subscriptionServiceFor } from "../../lib/repos/subscription-service.js";
19
- import { BUILT_IN_ADDED_BY } from "../../lib/repos/defaults.js";
20
- import { USER_ADDED_BY } from "../../lib/repos/trust.js";
21
- import { loadCatalogContext } from "../../lib/repos/catalog-inputs.js";
22
- import { listRecipes } from "../../lib/repos/catalog.js";
23
- import {
24
- describeIndexSource,
25
- pinnedCell,
26
- printBrowsingNotes,
27
- } from "../../lib/repos/catalog-display.js";
28
- import { renderTable, type TableColumn } from "../../utils/table.js";
29
- import { browsingFlags } from "../../utils/flags.js";
30
- import {
31
- blankLine,
32
- footer,
33
- heading,
34
- indent,
35
- log,
36
- paragraph,
37
- showCommandVars,
38
- } from "../../utils/formatting.js";
39
-
40
- /** How far every line of this command's output is indented. */
41
- const INDENT = 2;
42
-
43
- /**
44
- * The columns the listing shows. What a project subscribed to, the range it
45
- * asked for and the versions it actually holds are the whole point of the
46
- * command, so all three stay whatever the terminal's width; where a
47
- * subscription came from and whether it is switched on give way first.
48
- */
49
- const COLUMNS: TableColumn[] = [
50
- { key: "key", header: "Subscription", kind: "path", overflow: "truncate", minWidth: 12 },
51
- { key: "range", header: "Range", overflow: "truncate", minWidth: 7 },
52
- { key: "pinned", header: "Pinned version", flex: 1, minWidth: 14 },
53
- { key: "latest", header: "Latest version", overflow: "wrap", priority: "medium", minWidth: 14 },
54
- { key: "origin", header: "Origin", priority: "medium" },
55
- { key: "enabled", header: "Enabled", priority: "medium" },
56
- ];
57
-
58
- export default class SubscriptionList extends BaseCommand {
59
- static description = "List the recipes and namespaces this project subscribes to";
60
-
61
- /**
62
- * The other spelling of the topic. It lives under a hidden topic, so it is
63
- * typable everywhere without ever reaching the top-level listing.
64
- */
65
- static aliases = ["subscriptions:list"];
66
-
67
- static examples = [
68
- "<%= config.bin %> subscription list",
69
- "<%= config.bin %> subscription list --installed --latest",
70
- ];
71
-
72
- static flags = { ...BaseCommand.baseFlags, ...browsingFlags() };
73
-
74
- async run(): Promise<void> {
75
- const { flags } = await this.parse(SubscriptionList);
76
-
77
- showCommandVars({
78
- Project: this.projectLabel,
79
- Config: this.configContext.configPath,
80
- Reading: describeIndexSource(flags.latest),
81
- ...(flags.installed ? { Showing: "only what this project has installed" } : {}),
82
- });
83
-
84
- heading("Subscriptions");
85
-
86
- const service = subscriptionServiceFor({
87
- configContext: this.configContext,
88
- settings: this.settings,
89
- shellEnv: this.shellEnv,
90
- });
91
-
92
- const listings = service
93
- .listSubscriptions()
94
- .filter((entry) => !flags.installed || entry.pinned.length > 0);
95
-
96
- blankLine();
97
-
98
- if (listings.length === 0) {
99
- paragraph(
100
- flags.installed
101
- ? "No subscription of this project has installed anything yet."
102
- : "This project subscribes to nothing yet."
103
- );
104
- footer();
105
- return;
106
- }
107
-
108
- // The latest version of each pinned recipe, from the repository the
109
- // lockfile says it came from; the linked checkout, when there is one.
110
- const { inputs, notChecked } = await loadCatalogContext({
111
- service,
112
- sousDir: this.configContext.sousDir,
113
- settings: this.settings,
114
- latest: flags.latest,
115
- });
116
- const published = new Map(
117
- listRecipes(inputs).map((recipe) => [`${recipe.repo}:${recipe.key}`, recipe])
118
- );
119
- const lock = inputs.lock;
120
-
121
- let anyLinked = false;
122
- const rows = listings.map((entry) => {
123
- const held = entry.pinned.map((pin) => {
124
- const recipe = published.get(`${lock.recipes[pin.key]?.repo}:${pin.key}`);
125
- if (recipe?.linkedPath !== undefined) anyLinked = true;
126
- return { ...pin, recipe };
127
- });
128
- return {
129
- key: entry.key,
130
- range: entry.range ?? "any version",
131
- pinned:
132
- held.length === 0
133
- ? describePinned([], entry.enabled)
134
- : held
135
- .map((pin) => `${pin.key} ${pinnedCell(pin.version, pin.recipe?.linkedPath)}`)
136
- .join(", "),
137
- latest: describeLatest(held.map((pin) => ({ key: pin.key, latest: pin.recipe?.latest }))),
138
- origin: describeOrigin(entry.addedBy),
139
- enabled: entry.enabled ? "yes" : "no",
140
- };
141
- });
142
-
143
- const columns = flags.installed
144
- ? COLUMNS.map((column) =>
145
- column.key === "pinned" ? { ...column, header: "Installed version" } : column
146
- )
147
- : COLUMNS;
148
-
149
- for (const line of renderTable(columns, rows, { indent: INDENT })) {
150
- log(indent(line, INDENT));
151
- }
152
-
153
- printBrowsingNotes({ notChecked, anyLinked });
154
-
155
- footer();
156
- }
157
- }
158
-
159
- /**
160
- * The versions the lockfile pins for one subscription. A namespace subscription
161
- * holds several recipes, so each is named with the version beside it.
162
- *
163
- * A subscription with nothing pinned has simply not been built yet, which is
164
- * what the cell says; the one exception is a subscription switched off, which
165
- * no build will pin.
166
- *
167
- * @param pinned - The locked recipes the subscription holds.
168
- * @param enabled - Whether the subscription is switched on.
169
- */
170
- export function describePinned(
171
- pinned: Array<{ key: string; version: string }>,
172
- enabled: boolean
173
- ): string {
174
- if (pinned.length > 0) return pinned.map((entry) => `${entry.key} ${entry.version}`).join(", ");
175
- return enabled ? "pinned on first build" : "not pinned";
176
- }
177
-
178
- /**
179
- * The latest published version of each recipe a subscription holds. One held
180
- * recipe is the usual case, and it shows the version alone; several (a
181
- * namespace subscription) are each named with the version beside them. A
182
- * recipe no readable index publishes says so; a subscription holding nothing
183
- * yet has no latest version to show.
184
- *
185
- * describeLatest([{ key: "workflow/task-files", latest: "1.2.0" }])
186
- * // -> "1.2.0"
187
- *
188
- * @param held - Each held recipe, with its latest version when an index names one.
189
- */
190
- export function describeLatest(held: Array<{ key: string; latest: string | undefined }>): string {
191
- if (held.length === 0) return "";
192
- const unknown = "not in any index sous has read";
193
- if (held.length === 1) return held[0]!.latest ?? unknown;
194
- return held
195
- .map((entry) => `${entry.key} ${entry.latest ?? unknown}`)
196
- .join(", ");
197
- }
198
-
199
- /**
200
- * Plain-language wording for a subscription entry's `addedBy` field.
201
- *
202
- * @param addedBy - What the entry recorded, when it recorded anything.
203
- */
204
- function describeOrigin(addedBy: string | undefined): string {
205
- if (addedBy === BUILT_IN_ADDED_BY) return "built in";
206
- if (addedBy === undefined || addedBy === USER_ADDED_BY) return "user";
207
- return addedBy;
208
- }
@@ -1,182 +0,0 @@
1
- /**
2
- * `sous subscription remove <ref>`, also reachable as `sous unsubscribe <ref>`.
3
- *
4
- * The exact reverse of adding a subscription, and refcounted: removing a subscription
5
- * removes what it alone brought in, and leaves alone anything another
6
- * subscription or another recipe still needs. Whatever stays is reported, with
7
- * who is holding it, so a removal that appears to do nothing explains itself.
8
- *
9
- * The repository the recipes came from stays trusted; withdrawing that is a
10
- * separate, deliberate act.
11
- */
12
-
13
- import { Args, Flags } from "@oclif/core";
14
- import { BaseCommand } from "../../base-command.js";
15
- import { runProjectBuild } from "../../lib/build-service.js";
16
- import { ConfigError } from "../../lib/errors.js";
17
- import { subscriptionServiceFor } from "../../lib/repos/subscription-service.js";
18
- import { renderTable, type TableColumn } from "../../utils/table.js";
19
- import {
20
- blankLine,
21
- dryRunNotice,
22
- footer,
23
- heading,
24
- indent,
25
- log,
26
- paragraph,
27
- showCommandVars,
28
- subheading,
29
- } from "../../utils/formatting.js";
30
-
31
- /** How far every line of this command's output is indented. */
32
- const INDENT = 2;
33
-
34
- /** The columns the report of what stayed behind shows. */
35
- const STAYED_COLUMNS: TableColumn[] = [
36
- { key: "key", header: "Recipe", kind: "path", overflow: "truncate", minWidth: 12 },
37
- { key: "heldBy", header: "Still held by", overflow: "wrap", flex: 1, minWidth: 16 },
38
- ];
39
-
40
- export default class SubscriptionRemove extends BaseCommand {
41
- static description = "Remove a subscription, and everything only it brought in";
42
-
43
- /**
44
- * `subscriptions:remove` is the plural spelling of the topic. `unsubscribe` is
45
- * the original spelling of this command and still works; it is hidden so the
46
- * top-level listing names the command once, under its topic.
47
- */
48
- static aliases = ["subscriptions:remove"];
49
-
50
- static hiddenAliases = ["unsubscribe"];
51
-
52
- static examples = [
53
- "<%= config.bin %> subscription remove workflow/task-files",
54
- "<%= config.bin %> subscription remove core",
55
- "<%= config.bin %> subscription remove workflow/task-files --dry-run",
56
- ];
57
-
58
- static args = {
59
- ref: Args.string({
60
- description: "What to unsubscribe from: 'namespace' or 'namespace/recipe'",
61
- required: true,
62
- }),
63
- };
64
-
65
- static flags = {
66
- ...BaseCommand.baseFlags,
67
- "dry-run": Flags.boolean({
68
- description: "Print what would be removed without writing anything",
69
- default: false,
70
- }),
71
- "no-build": Flags.boolean({
72
- description: "Change the subscription without rebuilding the project",
73
- default: false,
74
- }),
75
- };
76
-
77
- async run(): Promise<void> {
78
- const { args, flags } = await this.parse(SubscriptionRemove);
79
- const dryRun = flags["dry-run"];
80
-
81
- showCommandVars({
82
- Project: this.projectLabel,
83
- Config: this.configContext.configPath,
84
- Unsubscribing: args.ref,
85
- "Dry Run": dryRun,
86
- });
87
-
88
- heading("Unsubscribing");
89
-
90
- if (dryRun) dryRunNotice("Nothing will be written.");
91
-
92
- const service = subscriptionServiceFor({
93
- configContext: this.configContext,
94
- settings: this.settings,
95
- shellEnv: this.shellEnv,
96
- });
97
-
98
- const outcome = await service.unsubscribe({ ref: args.ref, dryRun });
99
-
100
- blankLine();
101
- subheading("Lockfile");
102
- blankLine();
103
- if (outcome.diff.unchanged) {
104
- paragraph("Nothing changed; nothing was locked because of this subscription.");
105
- } else {
106
- for (const line of outcome.diff.lines) log(indent(line));
107
- }
108
-
109
- if (outcome.stayed.length > 0) {
110
- blankLine();
111
- subheading("What stayed, and why");
112
- blankLine();
113
- const rows = outcome.stayed.map((entry) => ({
114
- key: entry.key,
115
- heldBy: entry.heldBy.join(", "),
116
- }));
117
-
118
- for (const line of renderTable(STAYED_COLUMNS, rows, { indent: INDENT })) {
119
- log(indent(line, INDENT));
120
- }
121
- }
122
-
123
- const rebuilding = !dryRun && !flags["no-build"];
124
-
125
- // The closing sentence names the build only when this run is not about to do
126
- // it, so nobody is told to run a command that is already running.
127
- const pruneHint = rebuilding
128
- ? ``
129
- : ` Run 'sous build' to prune what it used to write.`;
130
-
131
- blankLine();
132
- paragraph(
133
- dryRun
134
- ? "Nothing was written. Run the same command without '--dry-run' to remove it."
135
- : outcome.optedOut
136
- ? `The subscription to '${outcome.key}' is one sous provides itself, so it ` +
137
- `was switched off rather than deleted: this project's config now records ` +
138
- `'${outcome.key}: { enabled: false }'. The repository it came from is ` +
139
- `still trusted.${pruneHint}`
140
- : `The subscription to '${outcome.key}' is gone. The repositories it came ` +
141
- `from are still trusted; remove one of those deliberately if you want ` +
142
- `to withdraw that too.${pruneHint}`
143
- );
144
-
145
- footer();
146
-
147
- if (rebuilding) await this.rebuildProject(outcome.key);
148
- }
149
-
150
- /**
151
- * Rebuilds the project now that the subscription has been removed, so the
152
- * files it used to contribute are pruned before this command returns.
153
- *
154
- * The subscription lives in a managed `conf.d/` layer, so the settings loaded
155
- * when this command started no longer describe the project; they are reloaded
156
- * before the build, or it would compile the old subscription set straight back
157
- * onto disk. A build that fails leaves the removal in place, because it is
158
- * already written and locked; the message says so and names the command to run
159
- * once the cause is fixed.
160
- *
161
- * @param key - The subscription that was just removed, for the failure message.
162
- */
163
- private async rebuildProject(key: string): Promise<void> {
164
- await this.reloadDiscoveredConfig();
165
-
166
- const succeeded = await runProjectBuild({
167
- settings: this.settings,
168
- configContext: this.configContext,
169
- shellEnv: this.shellEnv,
170
- heading: "Building the project",
171
- });
172
-
173
- if (!succeeded) {
174
- throw new ConfigError(
175
- `The subscription to '${key}' was removed, but the build that followed it ` +
176
- `failed, so this project may still hold files it used to write. The removal ` +
177
- `itself is recorded and locked; fix what the build reported above and run ` +
178
- `'sous build' again.`
179
- );
180
- }
181
- }
182
- }