@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,447 +0,0 @@
1
- /**
2
- * The trust layer.
3
- *
4
- * Added equals trusted. There is no separate trust command and no trusted-but-
5
- * not-added state: a repository sous will read from is one written into the
6
- * project's `repos:` map, and removing the entry withdraws the trust. Until a
7
- * repository is added, sous downloads nothing from it, not even its index;
8
- * there is no peeking before trusting, because the decision rests on the URL
9
- * and the publisher's reputation, which are inspected outside sous.
10
- *
11
- * Resolution can turn up repositories a dependency needs but the project has
12
- * not added. Those come back from the resolver as `MissingRepo` entries with
13
- * their provenance, and this is where the user is asked about them: one
14
- * consolidated question per round, listing every new repository with its URL
15
- * and the recipe that requires it. A refusal aborts. A non-interactive run
16
- * fails hard, naming the repositories and the command that grants trust, and
17
- * `--trust` acknowledges without asking.
18
- */
19
-
20
- import { color } from "@oclif/color";
21
- import { ConfigError } from "../errors.js";
22
- import type { Settings } from "../settings.js";
23
- import {
24
- blankLine,
25
- formatVariable,
26
- log as writeLine,
27
- palette,
28
- wrapColumns,
29
- wrapText,
30
- type VariableEntry,
31
- } from "../../utils/formatting.js";
32
- import { askYesNo } from "../../utils/prompts.js";
33
- import {
34
- isInteractive,
35
- nonInteractiveReason,
36
- NonInteractiveError,
37
- } from "../interactive.js";
38
- import {
39
- REPOS_LAYER_FILENAME,
40
- readManagedLayer,
41
- updateManagedLayer,
42
- type ManagedLayerOptions,
43
- } from "./managed-layer.js";
44
- import type { MissingRepo } from "./resolver.js";
45
- import type { ProviderId } from "./providers/provider.js";
46
- import { enabledRepos } from "./defaults.js";
47
-
48
- /** The `addedBy` value meaning a person deliberately added the repository. */
49
- export const USER_ADDED_BY = "user";
50
-
51
- /** One repository entry, as it is written into the managed layer. */
52
- export type TrustedRepo = {
53
- /** Where the repository lives. */
54
- url: string;
55
- /** The provider that handles it, when the URL does not give it away. */
56
- provider?: ProviderId;
57
- /** Whether a newer in-range version is preferred over the locked one. */
58
- alwaysPull?: boolean;
59
- /** When it was added. */
60
- addedAt?: string;
61
- /** Who required it: "user", or the ref of the recipe whose dependency pulled it in. */
62
- addedBy?: string;
63
- };
64
-
65
- /** What `addRepo` is told. */
66
- export type AddRepoRequest = {
67
- /** The repository's short name, which refs use as the `repo:` qualifier. */
68
- name: string;
69
- /** Where the repository lives. */
70
- url: string;
71
- /** The provider that handles it, when the URL does not give it away. */
72
- provider?: ProviderId;
73
- /** Whether a newer in-range version is preferred over the locked one. */
74
- alwaysPull?: boolean;
75
- /** Who required it. Defaults to "user". */
76
- addedBy?: string;
77
- };
78
-
79
- /** How trust is confirmed for a round of missing repositories. */
80
- export type ConfirmTrustOptions = {
81
- /** Whether sous may ask. Defaults to whether both streams are a terminal. */
82
- interactive?: boolean;
83
- /**
84
- * The confirmation flag, spelled `--trust` on the commands that perform this
85
- * ceremony (and `--yes`, `-y`, `-f` or `--force`): acknowledge every
86
- * repository this command adds, without asking.
87
- */
88
- trustFlag?: boolean;
89
- };
90
-
91
- /** What a confirmation round produced. */
92
- export type ConfirmTrustResult = {
93
- /** The repositories the user accepted. */
94
- accepted: MissingRepo[];
95
- /** The ones whose URL sous knew, and has now written into the managed layer. */
96
- added: string[];
97
- /**
98
- * The ones sous still cannot add on its own, because nothing told it their
99
- * URL. A recipe names a repository by its short name, so the URL has to come
100
- * from the person adding it.
101
- */
102
- needUrl: string[];
103
- };
104
-
105
- /** How the trust service is built. */
106
- export type TrustServiceOptions = ManagedLayerOptions & {
107
- /** The project's `.sous/` directory; the managed layer lives under it. */
108
- sousDir: string;
109
- /** The merged settings, which is where hand-written `repos:` entries come from. */
110
- settings?: Settings;
111
- /** Whether sous may ask questions. Defaults to whether both streams are a terminal. */
112
- interactive?: boolean;
113
- /** How a yes or no question is asked. Injected in tests. */
114
- ask?: (message: string) => Promise<boolean>;
115
- /** Where the consolidated trust notice is written. Defaults to the console. */
116
- write?: (message: string) => void;
117
- /** The clock, so a recorded `addedAt` is predictable in tests. */
118
- now?: () => Date;
119
- };
120
-
121
- /** Reads and changes the list of repositories a project trusts. */
122
- export class TrustService {
123
- private readonly sousDir: string;
124
-
125
- private readonly layerOptions: ManagedLayerOptions;
126
-
127
- private readonly settings: Settings | undefined;
128
-
129
- private readonly interactive: boolean;
130
-
131
- private readonly ask: (message: string) => Promise<boolean>;
132
-
133
- private readonly write: (message: string) => void;
134
-
135
- private readonly now: () => Date;
136
-
137
- constructor(options: TrustServiceOptions) {
138
- this.sousDir = options.sousDir;
139
- this.layerOptions = options.confDir === undefined ? {} : { confDir: options.confDir };
140
- this.settings = options.settings;
141
- this.interactive = options.interactive ?? isInteractive();
142
- this.ask = options.ask ?? ((message: string) => askYesNo(message));
143
- this.write = options.write ?? writeLine;
144
- this.now = options.now ?? (() => new Date());
145
- }
146
-
147
- /**
148
- * Every repository the project trusts, keyed by short name. This is the
149
- * merged view: entries hand-written in the primary config and entries sous
150
- * wrote into the managed layer both appear, because by the time settings are
151
- * loaded they are one map.
152
- *
153
- * @param settings - The merged settings. Defaults to the ones given at construction.
154
- */
155
- listTrusted(settings: Settings | undefined = this.settings): Record<string, TrustedRepo> {
156
- return { ...enabledRepos(settings) } as Record<string, TrustedRepo>;
157
- }
158
-
159
- /**
160
- * True when the project trusts a repository by that name.
161
- *
162
- * @param repoName - The repository's short name.
163
- * @param settings - The merged settings. Defaults to the ones given at construction.
164
- */
165
- isTrusted(repoName: string, settings: Settings | undefined = this.settings): boolean {
166
- return Object.hasOwn(this.listTrusted(settings), repoName);
167
- }
168
-
169
- /** Every repository written in the managed layer, keyed by short name. */
170
- listManaged(): Record<string, TrustedRepo> {
171
- const layer = readManagedLayer(this.sousDir, REPOS_LAYER_FILENAME, this.layerOptions);
172
- const repos = layer["repos"];
173
- if (typeof repos !== "object" || repos === null || Array.isArray(repos)) return {};
174
- return repos as Record<string, TrustedRepo>;
175
- }
176
-
177
- /**
178
- * Adds a repository to the managed layer, which is what trusting one means.
179
- * Adding a name that is already there replaces its entry, so this is also how
180
- * a URL or a provider is corrected.
181
- *
182
- * @param request - The repository's name, URL and provenance.
183
- */
184
- addRepo(request: AddRepoRequest): TrustedRepo {
185
- const entry: TrustedRepo = {
186
- url: request.url,
187
- ...(request.provider === undefined ? {} : { provider: request.provider }),
188
- ...(request.alwaysPull === undefined ? {} : { alwaysPull: request.alwaysPull }),
189
- addedAt: this.now().toISOString(),
190
- addedBy: request.addedBy ?? USER_ADDED_BY,
191
- };
192
-
193
- updateManagedLayer(
194
- this.sousDir,
195
- REPOS_LAYER_FILENAME,
196
- [{ path: ["repos", request.name], value: entry }],
197
- this.layerOptions
198
- );
199
- return entry;
200
- }
201
-
202
- /**
203
- * Removes a repository from the managed layer, withdrawing the trust. A
204
- * repository written by hand in the primary config is not sous's to remove:
205
- * that is an error saying so, since silently doing nothing would look like it
206
- * worked.
207
- *
208
- * @param name - The repository's short name.
209
- */
210
- removeRepo(name: string): void {
211
- const existing = this.listManaged();
212
- if (!Object.hasOwn(existing, name)) {
213
- if (this.isTrusted(name)) {
214
- throw new ConfigError(
215
- `The repository '${name}' is written in this project's own config, not in the ` +
216
- `layer sous manages.\n` +
217
- ` Remove its entry from the 'repos' block of your config file; sous never ` +
218
- `edits a config file you wrote.`
219
- );
220
- }
221
- throw new ConfigError(
222
- `This project has no repository called '${name}'.\n` +
223
- ` Run 'sous repo list' to see the repositories it trusts.`
224
- );
225
- }
226
-
227
- updateManagedLayer(
228
- this.sousDir,
229
- REPOS_LAYER_FILENAME,
230
- [{ path: ["repos", name], value: undefined }],
231
- this.layerOptions
232
- );
233
- }
234
-
235
- /**
236
- * Switches a repository off without deleting an entry, which is how trust is
237
- * withdrawn from the repository sous provides itself. That entry is recreated
238
- * from the installed package on every run, so only a recorded `enabled: false`
239
- * outlives it; the shape is the same one a person writes by hand to opt out.
240
- *
241
- * @param name - The repository's short name.
242
- */
243
- disableRepo(name: string): void {
244
- updateManagedLayer(
245
- this.sousDir,
246
- REPOS_LAYER_FILENAME,
247
- [{ path: ["repos", name], value: { enabled: false } }],
248
- this.layerOptions
249
- );
250
- }
251
-
252
- /**
253
- * Asks about every repository a round of resolution turned up that the
254
- * project has not added, in ONE consolidated question. Any refusal aborts,
255
- * because a half-trusted install is not something sous will produce.
256
- *
257
- * @param missing - The repositories resolution says are needed.
258
- * @param options - Whether sous may ask, and whether `--trust` was passed.
259
- */
260
- async confirmTrust(
261
- missing: MissingRepo[],
262
- options: ConfirmTrustOptions = {}
263
- ): Promise<ConfirmTrustResult> {
264
- if (missing.length === 0) return { accepted: [], added: [], needUrl: [] };
265
-
266
- const interactive = options.interactive ?? this.interactive;
267
- const trustFlag = options.trustFlag ?? false;
268
-
269
- if (!trustFlag && !interactive) {
270
- throw new NonInteractiveError(this.nonInteractiveMessage(missing));
271
- }
272
-
273
- if (!trustFlag) {
274
- // The notice opens with a blank line of its own, so it never lands
275
- // pressed up against whatever the command printed before it, wherever the
276
- // ceremony runs from.
277
- this.write(" ");
278
- this.write(this.trustNotice(missing));
279
- const plural = missing.length === 1 ? "this repository" : "these repositories";
280
- const accepted = await this.ask(`Do you trust ${plural}?`);
281
- if (!accepted) {
282
- throw new ConfigError(
283
- `Nothing was installed: trust was declined for ` +
284
- `${missing.map((repo) => `'${repo.name}'`).join(", ")}.\n` +
285
- ` Sous installs a dependency closure whole or not at all, so declining any ` +
286
- `repository in it stops the whole install.`
287
- );
288
- }
289
- }
290
-
291
- const added: string[] = [];
292
- const needUrl: string[] = [];
293
- for (const repo of missing) {
294
- if (repo.url === undefined) {
295
- needUrl.push(repo.name);
296
- continue;
297
- }
298
- // Every requester is recorded, not just the first. Removal hygiene reads
299
- // this to say why a repository is there, and a repository three recipes
300
- // need looks removable when the entry names only one of them.
301
- const requesters = [
302
- ...new Set(repo.requiredBy.map((entry) => entry.requestedBy)),
303
- ].sort();
304
- this.addRepo({
305
- name: repo.name,
306
- url: repo.url,
307
- // A dependency's locator names the provider in its scheme, which is the
308
- // one thing a URL alone cannot say for a self-hosted host.
309
- ...(repo.provider === undefined ? {} : { provider: repo.provider as ProviderId }),
310
- addedBy: requesters.length > 0 ? requesters.join(", ") : USER_ADDED_BY,
311
- });
312
- added.push(repo.name);
313
- }
314
-
315
- return { accepted: missing, added, needUrl };
316
- }
317
-
318
- /**
319
- * The consolidated notice shown before the question: every new repository,
320
- * its URL, the recipe that requires it, and what trusting it actually means.
321
- *
322
- * @param missing - The repositories being asked about.
323
- */
324
- trustNotice(missing: MissingRepo[]): string {
325
- const width = wrapColumns() - 2;
326
- const lines: string[] = [];
327
- lines.push(
328
- palette.warning(
329
- missing.length === 1
330
- ? "One repository has to be trusted before this can continue."
331
- : `${missing.length} repositories have to be trusted before this can continue.`
332
- )
333
- );
334
- lines.push(" ");
335
-
336
- // Every repository is written as the same key and value block the rest of
337
- // the CLI uses, so the facts under a repository name are read the same way
338
- // as the facts anywhere else.
339
- for (const repo of missing) {
340
- lines.push(` ${color.bold(repo.name)}`);
341
-
342
- const entries: VariableEntry[] = [
343
- {
344
- label: "Location",
345
- value:
346
- repo.url === undefined
347
- ? "not known to sous; a ref named it by its short name only"
348
- : repo.url,
349
- },
350
- ...(repo.identity === undefined
351
- ? []
352
- : [{ label: "Identity", value: repo.identity }]),
353
- ...repo.requiredBy.map((entry) => ({
354
- label: "Required",
355
- value: entry.ref,
356
- detail: `required by ${
357
- entry.requestedBy === "project" ? "this project" : `'${entry.requestedBy}'`
358
- }`,
359
- })),
360
- ];
361
- const labelWidth = Math.max(...entries.map((entry) => entry.label.length));
362
- for (const entry of entries) {
363
- lines.push(...formatVariable(entry, { labelWidth, width }));
364
- }
365
- lines.push(" ");
366
- }
367
-
368
- // The two phrases that carry the actual risk are highlighted in orange
369
- // inside the yellow, so a reader skimming the block still takes in the part
370
- // that matters.
371
- const body = [
372
- `Trusting a repository trusts ${palette.highlight(
373
- "every namespace and every recipe"
374
- )} in it, including ones published later. Trusting on its own executes ` +
375
- `nothing; subscribing to something inside it ${palette.highlight(
376
- "can, and probably will,"
377
- )} run scripts on this machine. This question is the last gate before ` +
378
- `that happens.`,
379
- "",
380
- "Sous cannot tell you whether a repository deserves trust. Look at the " +
381
- "location above, and at who publishes it, before answering.",
382
- ].join("\n");
383
-
384
- for (const line of wrapText(body, width - 2)) {
385
- lines.push(line === "" ? " " : ` ${palette.warning(line)}`);
386
- }
387
-
388
- // The block closes on a blank line, so the question that follows it stands
389
- // on its own rather than reading as the last line of the notice.
390
- lines.push(" ");
391
- return lines.join("\n");
392
- }
393
-
394
- /**
395
- * The error a non-interactive run gets: it names every repository needing
396
- * trust and the exact command that grants it, because a script cannot answer
397
- * a question.
398
- *
399
- * @param missing - The repositories being asked about.
400
- */
401
- private nonInteractiveMessage(missing: MissingRepo[]): string {
402
- const lines: string[] = [
403
- missing.length === 1
404
- ? "One repository has to be trusted before this can continue, and sous is not " +
405
- "running where it can ask."
406
- : `${missing.length} repositories have to be trusted before this can continue, ` +
407
- `and sous is not running where it can ask.`,
408
- ` Why: ${nonInteractiveReason() ?? "there is no terminal to ask on"}.`,
409
- "",
410
- ];
411
-
412
- for (const repo of missing) {
413
- const provenance = repo.requiredBy
414
- .map((entry) => `${entry.ref} (required by ${entry.requestedBy})`)
415
- .join(", ");
416
- lines.push(` ${repo.name}: ${repo.url ?? "URL not known to sous"}`);
417
- lines.push(` ${provenance}`);
418
- }
419
-
420
- lines.push("");
421
- lines.push(" Trusting a repository trusts every namespace and recipe in it, and");
422
- lines.push(" subscribing to something inside it can run scripts on this machine.");
423
- lines.push(" Add each repository deliberately, with its URL:");
424
- lines.push("");
425
- for (const repo of missing) {
426
- lines.push(` sous repo add ${repo.url ?? "<url>"} --name ${repo.name} --trust`);
427
- }
428
- lines.push("");
429
- lines.push(
430
- " '--trust' is one spelling of the confirmation flag; '--yes', '-y' and"
431
- );
432
- lines.push(" '--force' mean exactly the same thing.");
433
-
434
- return lines.join("\n");
435
- }
436
- }
437
-
438
- /**
439
- * Writes a blank line and then a block of text, which is how the trust notice
440
- * reaches the console when nothing overrides it.
441
- *
442
- * @param message - The block to write.
443
- */
444
- export function writeTrustNotice(message: string): void {
445
- blankLine();
446
- writeLine(message);
447
- }
@@ -1,235 +0,0 @@
1
- /**
2
- * What an update is about to do, written out before it asks.
3
- *
4
- * `sous subscription update` (and `sous repo unlink --update`, which runs the
5
- * same code) works out the whole change first: which pins move, which
6
- * dependencies arrive or leave, which repositories a newer version needs that
7
- * the project does not trust yet, and which questions the new versions ask.
8
- * This module turns those facts into the lines printed above the one question.
9
- * It is pure: it formats what it is handed and decides nothing.
10
- */
11
-
12
- import type { LockDiff } from "./lock-service.js";
13
- import type { MissingRepo } from "./resolver.js";
14
- import { formatQuestionPlan, type PlannedVariable } from "../vars/index.js";
15
- import { BULLET, palette, wrapColumns, wrapText } from "../../utils/formatting.js";
16
-
17
- /** What an update narrowed itself to. */
18
- export type UpdateScope =
19
- | { kind: "all" }
20
- | { kind: "repository"; repo: string }
21
- | { kind: "namespace"; repo: string; namespace: string }
22
- | { kind: "recipe"; repo: string; key: string };
23
-
24
- /** Everything the plan says, gathered by the subscription service. */
25
- export type UpdatePlanFacts = {
26
- /** What the update covers. */
27
- scope: UpdateScope;
28
- /** What would change in the lockfile. */
29
- diff: LockDiff;
30
- /** Repositories a newer version needs that the project does not trust yet. */
31
- missingRepos: MissingRepo[];
32
- /** The questions the new versions ask that nothing answers yet. */
33
- questions: PlannedVariable[];
34
- /** Recipes whose files are not on this machine and whose index does not record their questions. */
35
- unreadable: string[];
36
- /** Repositories whose index could not be fetched, so their pins stay where they are. */
37
- unreachable: Array<{ repo: string; reason: string }>;
38
- /** Linked repositories whose pins move, which builds keep bypassing. */
39
- linked: string[];
40
- /** Subscriptions sous provides itself, held at the version this sous ships. */
41
- builtIn: Array<{ key: string; version?: string }>;
42
- /** Switched-off subscriptions the update left alone. */
43
- switchedOff: string[];
44
- /** Subscriptions that could not be resolved, each with the reason. */
45
- failed: Array<{ key: string; reason: string }>;
46
- };
47
-
48
- /**
49
- * The scope in words, for a sentence that has to say what was updated.
50
- *
51
- * describeUpdateScope({ kind: "repository", repo: "sous-recipes" })
52
- * // -> "the recipes this project takes from the repository 'sous-recipes'"
53
- *
54
- * @param scope - What the update covers.
55
- */
56
- export function describeUpdateScope(scope: UpdateScope): string {
57
- switch (scope.kind) {
58
- case "all":
59
- return "every subscription in this project";
60
- case "repository":
61
- return `the recipes this project takes from the repository '${scope.repo}'`;
62
- case "namespace":
63
- return `the recipes in the namespace '${scope.repo}:${scope.namespace}'`;
64
- case "recipe":
65
- return `the recipe '${scope.repo}:${scope.key}'`;
66
- }
67
- }
68
-
69
- /**
70
- * True when the plan has nothing to do: no pin moves, nothing arrives or
71
- * leaves, and no new repository is needed.
72
- *
73
- * @param facts - What the service worked out.
74
- */
75
- export function isEmptyUpdate(facts: Pick<UpdatePlanFacts, "diff" | "missingRepos">): boolean {
76
- return facts.diff.unchanged && facts.missingRepos.length === 0;
77
- }
78
-
79
- /**
80
- * The plan, as lines ready to be indented and printed. Pin changes come first,
81
- * then the repositories that need trusting, then the questions, then the notes
82
- * about what the update deliberately left alone.
83
- *
84
- * @param facts - What the service worked out.
85
- * @param options - The width to wrap to, for tests.
86
- */
87
- export function formatUpdatePlan(
88
- facts: UpdatePlanFacts,
89
- options: { width?: number } = {}
90
- ): string[] {
91
- const width = (options.width ?? wrapColumns()) - 4;
92
- const lines: string[] = [""];
93
-
94
- /** One sentence, wrapped. */
95
- const sentence = (text: string, paint = (line: string): string => line): string[] =>
96
- wrapText(text, width).map(paint);
97
-
98
- /** One bullet, wrapped so its continuation hangs under the text. */
99
- const bullet = (text: string): string[] =>
100
- wrapText(` ${BULLET} ${text}`, width, { hangingIndent: 2 });
101
-
102
- if (isEmptyUpdate(facts)) {
103
- lines.push(
104
- ...sentence(
105
- `Nothing to update: every pin in ${describeUpdateScope(facts.scope)} is ` +
106
- `already the newest published version its range allows.`
107
- )
108
- );
109
- } else {
110
- lines.push(...sentence(`Updating ${describeUpdateScope(facts.scope)} changes the lockfile:`));
111
- lines.push("");
112
- if (facts.diff.unchanged) {
113
- lines.push(
114
- ...bullet("No pin moves until the repositories below are trusted and read.")
115
- );
116
- } else {
117
- for (const change of facts.diff.lines) lines.push(...bullet(change));
118
- }
119
- }
120
-
121
- if (facts.missingRepos.length > 0) {
122
- lines.push("");
123
- lines.push(
124
- ...sentence(
125
- `A newer version needs ${
126
- facts.missingRepos.length === 1 ? "a repository" : "repositories"
127
- } this project ${palette.highlight("does not trust yet")}. You are asked about ` +
128
- `each one by name before anything is fetched from it:`,
129
- palette.warning
130
- )
131
- );
132
- lines.push("");
133
- for (const missing of facts.missingRepos) {
134
- const where = missing.url === undefined ? "" : ` at ${missing.url}`;
135
- const needers = missing.requiredBy.map((entry) => entry.requestedBy).join(", ");
136
- lines.push(...bullet(`${missing.name}${where}, needed by ${needers}`));
137
- }
138
- }
139
-
140
- if (facts.questions.length > 0 || facts.unreadable.length > 0) {
141
- lines.push("");
142
- lines.push(...sentence("Questions the new versions ask that nothing answers yet:"));
143
- lines.push("");
144
- for (const line of formatQuestionPlan(facts.questions, {
145
- unreadable: facts.unreadable,
146
- width,
147
- })) {
148
- lines.push(line === "" ? "" : ` ${line}`);
149
- }
150
- }
151
-
152
- const notes: string[] = [];
153
- if (facts.unreadable.length > 0) {
154
- notes.push(
155
- `A dry run downloads no recipe, so the dependencies of the versions not on this ` +
156
- `machine yet, and not described by their index, are not shown: ` +
157
- `${facts.unreadable.join(", ")}.`
158
- );
159
- }
160
- for (const entry of facts.unreachable) {
161
- notes.push(
162
- `The index of '${entry.repo}' could not be fetched, so its pins stay where ` +
163
- `they are. ${firstLine(entry.reason)}`
164
- );
165
- }
166
- for (const entry of facts.failed) {
167
- notes.push(
168
- `The subscription to '${entry.key}' could not be resolved, so its pins stay ` +
169
- `where they are. ${firstLine(entry.reason)}`
170
- );
171
- }
172
- for (const repo of facts.linked) {
173
- notes.push(
174
- `The repository '${repo}' is linked to a working copy, so builds keep ` +
175
- `reading that checkout until it is unlinked; the pins above take effect then.`
176
- );
177
- }
178
- for (const entry of facts.builtIn) {
179
- notes.push(
180
- `The subscription to '${entry.key}' is one sous provides itself, and it stays ` +
181
- `at ${
182
- entry.version === undefined ? "the version" : `version ${entry.version}, the version`
183
- } this installation of sous ships.`
184
- );
185
- }
186
- if (facts.switchedOff.length > 0) {
187
- notes.push(
188
- `Switched off, so left alone: ${facts.switchedOff.join(", ")}.`
189
- );
190
- }
191
-
192
- if (notes.length > 0) {
193
- lines.push("");
194
- for (const text of notes) {
195
- lines.push(...wrapText(`${BULLET} ${text}`, width, { hangingIndent: 2 }).map(palette.note));
196
- }
197
- }
198
-
199
- lines.push("");
200
- return lines;
201
- }
202
-
203
- /**
204
- * The first line of a possibly multi-line reason, which is what fits in a note.
205
- *
206
- * @param text - The reason.
207
- */
208
- function firstLine(text: string): string {
209
- return text.split("\n")[0]!.trim();
210
- }
211
-
212
- /**
213
- * True when an update of this scope may move a recipe, judged by the recipe's
214
- * key and the repository it comes from.
215
- *
216
- * recipeInScope({ kind: "namespace", repo: "r", namespace: "workflow" }, "workflow/x", "r")
217
- * // -> true
218
- *
219
- * @param scope - What the update covers.
220
- * @param key - The recipe key, `namespace/recipe`.
221
- * @param repo - The short name of the repository it comes from.
222
- */
223
- export function recipeInScope(scope: UpdateScope, key: string, repo: string): boolean {
224
- switch (scope.kind) {
225
- case "all":
226
- return true;
227
- case "repository":
228
- return repo === scope.repo;
229
- case "namespace":
230
- return repo === scope.repo && key.startsWith(`${scope.namespace}/`);
231
- case "recipe":
232
- return repo === scope.repo && key === scope.key;
233
- }
234
- }
235
-
@@ -1,14 +0,0 @@
1
- /**
2
- * The self-update layer: which copies of sous are installed, which version
3
- * each should move to, and the package manager command that moves it.
4
- *
5
- * Import from here rather than from the individual modules. Nothing exported
6
- * here installs anything; `run.ts`, imported on its own by `sous update`, is
7
- * the flow that runs the commands the plans name.
8
- */
9
-
10
- export * from "./versions.js";
11
- export * from "./registry.js";
12
- export * from "./managers.js";
13
- export * from "./installs.js";
14
- export * from "./plan.js";