@sous-io/sous 0.2.36 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (244) hide show
  1. package/README.md +14 -256
  2. package/dist/bin.js +11 -0
  3. package/dist/sous.js +1093 -0
  4. package/docs/markdown/README.md +5 -4
  5. package/docs/markdown/_sidebar.md +21 -1
  6. package/docs/markdown/commands.md +294 -431
  7. package/docs/markdown/config-discovery.md +25 -14
  8. package/docs/markdown/config-inspection.md +53 -31
  9. package/docs/markdown/config-layers.md +168 -66
  10. package/docs/markdown/config-variables.md +44 -11
  11. package/docs/markdown/configuration.md +104 -17
  12. package/docs/markdown/design-principles.md +4 -1
  13. package/docs/markdown/repositories-authoring.md +30 -0
  14. package/docs/markdown/repositories-consuming.md +72 -14
  15. package/docs/markdown/repositories-file-formats.md +31 -13
  16. package/docs/markdown/repositories-providers.md +48 -57
  17. package/docs/markdown/repositories-quickstart.md +3 -2
  18. package/docs/markdown/repositories-troubleshooting.md +27 -1
  19. package/docs/markdown/repositories-variables.md +6 -1
  20. package/docs/markdown/repositories.md +18 -8
  21. package/package.json +48 -107
  22. package/recipes/core/sous-skills/skills/about-liquid-templates/SKILL.tpl.md +4 -6
  23. package/recipes/core/sous-skills/skills/about-liquid-templates/references/liquid-filters.tpl.md +2 -0
  24. package/recipes/core/sous-skills/skills/about-sous/SKILL.tpl.md +10 -9
  25. package/recipes/core/sous-skills/skills/about-sous-configuration/SKILL.tpl.md +35 -14
  26. package/recipes/core/sous-skills/skills/about-sous-recipes/SKILL.tpl.md +51 -20
  27. package/recipes/core/sous-skills/sous.recipe.yaml +1 -1
  28. package/sous.config.schema.json +351 -132
  29. package/bin/run.js +0 -51
  30. package/src/base-command.ts +0 -457
  31. package/src/commands/build.ts +0 -181
  32. package/src/commands/clear.ts +0 -79
  33. package/src/commands/compile.ts +0 -155
  34. package/src/commands/config/get.ts +0 -110
  35. package/src/commands/config/show.ts +0 -32
  36. package/src/commands/config/validate.ts +0 -53
  37. package/src/commands/help.ts +0 -46
  38. package/src/commands/init.ts +0 -289
  39. package/src/commands/launch.ts +0 -139
  40. package/src/commands/lock/rebuild.ts +0 -239
  41. package/src/commands/lock/show.ts +0 -115
  42. package/src/commands/namespace/list.ts +0 -138
  43. package/src/commands/namespace/show.ts +0 -130
  44. package/src/commands/prune.ts +0 -43
  45. package/src/commands/recipe/list.ts +0 -112
  46. package/src/commands/recipe/show.ts +0 -439
  47. package/src/commands/repo/add.ts +0 -145
  48. package/src/commands/repo/contribute.ts +0 -604
  49. package/src/commands/repo/gc.ts +0 -173
  50. package/src/commands/repo/init.ts +0 -136
  51. package/src/commands/repo/link.ts +0 -899
  52. package/src/commands/repo/list.ts +0 -236
  53. package/src/commands/repo/release.ts +0 -685
  54. package/src/commands/repo/remove.ts +0 -194
  55. package/src/commands/repo/search.ts +0 -243
  56. package/src/commands/repo/submit.ts +0 -341
  57. package/src/commands/repo/unlink.ts +0 -461
  58. package/src/commands/subscription/add.ts +0 -286
  59. package/src/commands/subscription/list.ts +0 -208
  60. package/src/commands/subscription/remove.ts +0 -182
  61. package/src/commands/subscription/update.ts +0 -216
  62. package/src/commands/update.ts +0 -178
  63. package/src/commands/vars/ask.ts +0 -417
  64. package/src/commands/vars/index.ts +0 -79
  65. package/src/commands/vars/list.ts +0 -67
  66. package/src/commands/vars/show.ts +0 -77
  67. package/src/config-command.ts +0 -30
  68. package/src/lib/build-preparation.ts +0 -128
  69. package/src/lib/build-service.ts +0 -580
  70. package/src/lib/config-discovery.ts +0 -378
  71. package/src/lib/config-inspect.ts +0 -145
  72. package/src/lib/config-kernel.mjs +0 -379
  73. package/src/lib/config-schema.ts +0 -401
  74. package/src/lib/env-file.ts +0 -328
  75. package/src/lib/env-local.ts +0 -212
  76. package/src/lib/errors.ts +0 -32
  77. package/src/lib/glob-pattern.ts +0 -24
  78. package/src/lib/include-resolver.ts +0 -523
  79. package/src/lib/interactive.ts +0 -165
  80. package/src/lib/markdown-compiler.ts +0 -947
  81. package/src/lib/package-info.ts +0 -25
  82. package/src/lib/pid-service.ts +0 -99
  83. package/src/lib/project-install.d.mts +0 -44
  84. package/src/lib/project-install.mjs +0 -234
  85. package/src/lib/project-scaffold/index.ts +0 -332
  86. package/src/lib/project-scaffold/templates.ts +0 -240
  87. package/src/lib/repos/catalog-display.ts +0 -215
  88. package/src/lib/repos/catalog-inputs.ts +0 -288
  89. package/src/lib/repos/catalog.ts +0 -842
  90. package/src/lib/repos/contribute.ts +0 -452
  91. package/src/lib/repos/core-recipe.ts +0 -105
  92. package/src/lib/repos/declarations.ts +0 -187
  93. package/src/lib/repos/defaults.ts +0 -175
  94. package/src/lib/repos/formats/common.ts +0 -440
  95. package/src/lib/repos/formats/index-file.ts +0 -253
  96. package/src/lib/repos/formats/links-map.ts +0 -98
  97. package/src/lib/repos/formats/lockfile.ts +0 -169
  98. package/src/lib/repos/formats/patterns.ts +0 -57
  99. package/src/lib/repos/formats/recipe-manifest.ts +0 -205
  100. package/src/lib/repos/formats/repo-manifest.ts +0 -97
  101. package/src/lib/repos/formats/store-entry.ts +0 -84
  102. package/src/lib/repos/formats/variable-definition.ts +0 -268
  103. package/src/lib/repos/freshness.ts +0 -264
  104. package/src/lib/repos/git-clone.ts +0 -850
  105. package/src/lib/repos/identity.ts +0 -89
  106. package/src/lib/repos/index.ts +0 -57
  107. package/src/lib/repos/links.ts +0 -356
  108. package/src/lib/repos/load-manifest.ts +0 -237
  109. package/src/lib/repos/lock-service.ts +0 -454
  110. package/src/lib/repos/locked-namespace-resolver.ts +0 -232
  111. package/src/lib/repos/locked-recipe-lookup.ts +0 -123
  112. package/src/lib/repos/locked-recipes.ts +0 -254
  113. package/src/lib/repos/managed-layer.ts +0 -422
  114. package/src/lib/repos/memories-hash-name.ts +0 -33
  115. package/src/lib/repos/namespace-resolver.ts +0 -527
  116. package/src/lib/repos/providers/base.ts +0 -268
  117. package/src/lib/repos/providers/git.ts +0 -237
  118. package/src/lib/repos/providers/github.ts +0 -589
  119. package/src/lib/repos/providers/gitlab.ts +0 -332
  120. package/src/lib/repos/providers/http.ts +0 -128
  121. package/src/lib/repos/providers/index-cache.ts +0 -421
  122. package/src/lib/repos/providers/index.ts +0 -106
  123. package/src/lib/repos/providers/local.ts +0 -391
  124. package/src/lib/repos/providers/provider.ts +0 -615
  125. package/src/lib/repos/recipe-config-layers.ts +0 -316
  126. package/src/lib/repos/recipe-key-matcher.ts +0 -62
  127. package/src/lib/repos/recipe-memories.ts +0 -178
  128. package/src/lib/repos/recipe-targets.ts +0 -236
  129. package/src/lib/repos/ref-search.ts +0 -46
  130. package/src/lib/repos/release/bump.ts +0 -161
  131. package/src/lib/repos/release/changelog.ts +0 -461
  132. package/src/lib/repos/release/git-state.ts +0 -460
  133. package/src/lib/repos/release/index-builder.ts +0 -1108
  134. package/src/lib/repos/release/index.ts +0 -19
  135. package/src/lib/repos/release/plan.ts +0 -521
  136. package/src/lib/repos/release/settle.ts +0 -154
  137. package/src/lib/repos/release/submissions.ts +0 -214
  138. package/src/lib/repos/release/submit-checkout.ts +0 -290
  139. package/src/lib/repos/release/submit-questions.ts +0 -153
  140. package/src/lib/repos/release/submit-service.ts +0 -898
  141. package/src/lib/repos/release/tags.ts +0 -239
  142. package/src/lib/repos/release/validate.ts +0 -463
  143. package/src/lib/repos/resolver.ts +0 -1028
  144. package/src/lib/repos/scaffold/index.ts +0 -245
  145. package/src/lib/repos/scaffold/templates.ts +0 -517
  146. package/src/lib/repos/seed.ts +0 -473
  147. package/src/lib/repos/store/contract.ts +0 -64
  148. package/src/lib/repos/store/hash.ts +0 -191
  149. package/src/lib/repos/store/recipe-store.ts +0 -599
  150. package/src/lib/repos/store/settings.ts +0 -58
  151. package/src/lib/repos/subscription-service.ts +0 -3859
  152. package/src/lib/repos/trust.ts +0 -447
  153. package/src/lib/repos/update-plan.ts +0 -235
  154. package/src/lib/self-update/index.ts +0 -14
  155. package/src/lib/self-update/installs.ts +0 -412
  156. package/src/lib/self-update/managers.ts +0 -126
  157. package/src/lib/self-update/plan.ts +0 -315
  158. package/src/lib/self-update/registry.ts +0 -99
  159. package/src/lib/self-update/run.ts +0 -506
  160. package/src/lib/self-update/versions.ts +0 -243
  161. package/src/lib/settings.ts +0 -1105
  162. package/src/lib/sous-home.ts +0 -104
  163. package/src/lib/state.ts +0 -167
  164. package/src/lib/vars/answers.ts +0 -192
  165. package/src/lib/vars/ask.ts +0 -1152
  166. package/src/lib/vars/definition-source.ts +0 -261
  167. package/src/lib/vars/display.ts +0 -231
  168. package/src/lib/vars/index.ts +0 -20
  169. package/src/lib/vars/ladder.ts +0 -282
  170. package/src/lib/vars/mappings.ts +0 -260
  171. package/src/lib/vars/names.ts +0 -94
  172. package/src/lib/vars/preanswers.ts +0 -395
  173. package/src/lib/vars/question-plan.ts +0 -241
  174. package/src/lib/vars/report.ts +0 -228
  175. package/src/lib/vars/safe-regex.ts +0 -235
  176. package/src/lib/vars/secrets.ts +0 -131
  177. package/src/lib/vars/validate.ts +0 -312
  178. package/src/lib/version-report.ts +0 -90
  179. package/src/lib/watch-loop.ts +0 -148
  180. package/src/lib/watch-service.ts +0 -116
  181. package/src/services/ref-resolver/container.ts +0 -107
  182. package/src/services/ref-resolver/dependency.ts +0 -60
  183. package/src/services/ref-resolver/format.ts +0 -197
  184. package/src/services/ref-resolver/glob.ts +0 -115
  185. package/src/services/ref-resolver/hash-names.ts +0 -155
  186. package/src/services/ref-resolver/index.ts +0 -55
  187. package/src/services/ref-resolver/injectable.ts +0 -36
  188. package/src/services/ref-resolver/location.ts +0 -62
  189. package/src/services/ref-resolver/lookups/cached-index-lookup.ts +0 -53
  190. package/src/services/ref-resolver/lookups/catalog-lookup.ts +0 -23
  191. package/src/services/ref-resolver/lookups/catalog-matcher.ts +0 -261
  192. package/src/services/ref-resolver/lookups/chained-lookup.ts +0 -27
  193. package/src/services/ref-resolver/lookups/env-var-lookup.ts +0 -65
  194. package/src/services/ref-resolver/lookups/fetched-index-lookup.ts +0 -150
  195. package/src/services/ref-resolver/lookups/recorded-dependency-lookup.ts +0 -62
  196. package/src/services/ref-resolver/lookups/ref-lookup.ts +0 -54
  197. package/src/services/ref-resolver/lookups/variable-lookup.ts +0 -123
  198. package/src/services/ref-resolver/named-refs.ts +0 -144
  199. package/src/services/ref-resolver/parser/env-var-splitter.ts +0 -27
  200. package/src/services/ref-resolver/parser/location-splitter.ts +0 -239
  201. package/src/services/ref-resolver/parser/name-path-splitter.ts +0 -200
  202. package/src/services/ref-resolver/parser/partial-ref.ts +0 -84
  203. package/src/services/ref-resolver/parser/query-splitter.ts +0 -61
  204. package/src/services/ref-resolver/parser/range-splitter.ts +0 -52
  205. package/src/services/ref-resolver/parser/ref-parser.ts +0 -115
  206. package/src/services/ref-resolver/parser/repo-qualifier-splitter.ts +0 -51
  207. package/src/services/ref-resolver/parts.ts +0 -115
  208. package/src/services/ref-resolver/pruners/command-line-pruner.ts +0 -47
  209. package/src/services/ref-resolver/pruners/include-pruner.ts +0 -66
  210. package/src/services/ref-resolver/pruners/manifest-pruner.ts +0 -130
  211. package/src/services/ref-resolver/pruners/ref-pruner.ts +0 -135
  212. package/src/services/ref-resolver/pruners/rule-helpers.ts +0 -44
  213. package/src/services/ref-resolver/pruners/stored-key-pruner.ts +0 -136
  214. package/src/services/ref-resolver/ref-helpers.ts +0 -62
  215. package/src/services/ref-resolver/ref-picker-service.ts +0 -204
  216. package/src/services/ref-resolver/ref-report.ts +0 -84
  217. package/src/services/ref-resolver/ref-resolve-arguments.ts +0 -47
  218. package/src/services/ref-resolver/ref-resolve-result.ts +0 -36
  219. package/src/services/ref-resolver/ref-resolver-service.ts +0 -323
  220. package/src/services/ref-resolver/source.ts +0 -48
  221. package/src/services/ref-resolver/tokens.ts +0 -25
  222. package/src/services/ref-resolver/types.ts +0 -155
  223. package/src/templating/filters/bullet-list.ts +0 -9
  224. package/src/templating/filters/index.ts +0 -8
  225. package/src/templating/init-liquid-engine.ts +0 -139
  226. package/src/templating/lib/glob-files.ts +0 -74
  227. package/src/templating/lib/import-export.ts +0 -32
  228. package/src/templating/lib/secret-scope.ts +0 -41
  229. package/src/templating/lib/tag-args.ts +0 -19
  230. package/src/templating/tags/exportScalarVarsJs.ts +0 -46
  231. package/src/templating/tags/getFiles.ts +0 -89
  232. package/src/templating/tags/index.ts +0 -14
  233. package/src/templating/tags/listFiles.ts +0 -54
  234. package/src/templating/tags/showVars.ts +0 -28
  235. package/src/utils/choice-prompt.ts +0 -143
  236. package/src/utils/command-errors.ts +0 -186
  237. package/src/utils/command-help.ts +0 -45
  238. package/src/utils/confirm-prompt.ts +0 -110
  239. package/src/utils/flags.ts +0 -177
  240. package/src/utils/formatting.ts +0 -849
  241. package/src/utils/prompts.ts +0 -53
  242. package/src/utils/sous-directory.ts +0 -245
  243. package/src/utils/table.ts +0 -603
  244. package/src/utils/value-prompt.ts +0 -119
@@ -1,412 +0,0 @@
1
- /**
2
- * Finds every copy of sous an update could move: the project's own install,
3
- * each global install, and the copy that is running now.
4
- *
5
- * Discovery only reads. The package managers are asked through an injectable
6
- * command runner (capturing output, never installing anything), and the
7
- * project's manager is read from its files by package-manager-detector, so a
8
- * test drives every case with a fake runner and a temporary directory.
9
- */
10
-
11
- import fs from "node:fs";
12
- import path from "node:path";
13
- import { detect } from "package-manager-detector/detect";
14
- import {
15
- PACKAGE_NAME,
16
- findProjectCopy,
17
- readPackageJson,
18
- realpathOr,
19
- } from "../project-install.mjs";
20
- import { spawnCommand, type CommandRunner } from "../repos/providers/git.js";
21
- import {
22
- GLOBAL_MANAGERS,
23
- isProjectManager,
24
- type DependencySection,
25
- type GlobalManager,
26
- type ProjectManager,
27
- } from "./managers.js";
28
-
29
- /** The version reported for a copy whose package.json names none. */
30
- export const UNKNOWN_VERSION = "unknown";
31
-
32
- /** How the project's package.json declares sous. */
33
- export type DeclaredDependency = {
34
- section: DependencySection;
35
- /** The range as written, for example `0.2.18` or `^0.2.18`. */
36
- range: string;
37
- };
38
-
39
- /**
40
- * Which manager owns a project, as far as its files say.
41
- *
42
- * - `supported: true`: a manager sous drives. `detected` is false when the
43
- * project's files named none and npm is assumed, which the plan says.
44
- * - `supported: false`: a manager package-manager-detector recognized that
45
- * sous does not drive (`bun`, `deno`, `pnpm-rush`, ...), named by its agent.
46
- */
47
- export type ProjectManagerInfo =
48
- | { supported: true; agent: ProjectManager; detected: boolean }
49
- | { supported: false; agent: string };
50
-
51
- /** The project's own install of sous. */
52
- export type ProjectInstallInfo = {
53
- /**
54
- * The directory holding the project's package.json: the one holding the
55
- * `node_modules` the copy is in, or under Yarn Plug'n'Play the nearest one
56
- * that declares the package.
57
- */
58
- projectRoot: string;
59
- /**
60
- * The copy's path under `node_modules`, as found (not resolved through
61
- * links), or under Yarn Plug'n'Play the `.pnp.cjs` file that maps it, since
62
- * the package itself stays in Yarn's cache.
63
- */
64
- location: string;
65
- /** The copy's real path, which is what is compared with other copies. */
66
- realPath: string;
67
- /**
68
- * The version in the copy's package.json (under Yarn Plug'n'Play, the one
69
- * `yarn.lock` resolves the declared range to), or `UNKNOWN_VERSION`.
70
- */
71
- installed: string;
72
- /** How package.json declares sous; undefined when it is installed but not declared. */
73
- declared?: DeclaredDependency;
74
- manager: ProjectManagerInfo;
75
- /** Whether the project is a workspace root, as its manager reads one. */
76
- workspaceRoot: boolean;
77
- /**
78
- * Set only when the project uses Yarn Plug'n'Play: the directory holding
79
- * `.pnp.cjs` and `yarn.lock` (the project root, or its workspace's root).
80
- */
81
- pnpRoot?: string;
82
- };
83
-
84
- /** One global install of sous, and the manager whose global root holds it. */
85
- export type GlobalInstallInfo = {
86
- manager: GlobalManager;
87
- /** The copy's path under the manager's global root, as found. */
88
- location: string;
89
- /** The copy's real path. */
90
- realPath: string;
91
- /** The version in the copy's package.json, or `UNKNOWN_VERSION`. */
92
- installed: string;
93
- };
94
-
95
- /**
96
- * What the running copy is.
97
- *
98
- * - `project` / `global`: one of the installs found (`global` names its manager).
99
- * - `npx`: a copy in npx's cache, which no install updates.
100
- * - `volta`: a copy Volta manages, which only Volta updates.
101
- * - `unknown`: anywhere else, such as a source checkout.
102
- */
103
- export type RunningInstall = {
104
- kind: "project" | "global" | "npx" | "volta" | "unknown";
105
- /** The global manager, set only when `kind` is `global`. */
106
- manager?: GlobalManager;
107
- /** The running copy's real path. */
108
- realPath: string;
109
- /** The version in the running copy's package.json, or `UNKNOWN_VERSION`. */
110
- installed: string;
111
- };
112
-
113
- /** Everything discovery found. */
114
- export type InstallDiscovery = {
115
- /** The project's own install, when the working directory is inside a project that has one. */
116
- project?: ProjectInstallInfo;
117
- /** Every global install found, one per manager whose global root holds a copy. */
118
- globals: GlobalInstallInfo[];
119
- running: RunningInstall;
120
- };
121
-
122
- /** Options for `discoverInstalls`. */
123
- export type DiscoverInstallsOptions = {
124
- /** Where the project lookup starts. */
125
- cwd: string;
126
- /** The package root of the running copy. */
127
- ownRoot: string;
128
- /** How the managers are asked for their global roots. Defaults to spawning them. */
129
- run?: CommandRunner;
130
- };
131
-
132
- /**
133
- * Finds the project install, the global installs and what the running copy is.
134
- * No step fails the run: a manager that is not installed or cannot answer is
135
- * skipped, and a copy whose package.json is unreadable is left out.
136
- */
137
- export async function discoverInstalls(options: DiscoverInstallsOptions): Promise<InstallDiscovery> {
138
- const run = options.run ?? spawnCommand;
139
- const project = await findProjectInstallInfo(options.cwd);
140
- const globals = await findGlobalInstalls(run);
141
- const running = classifyRunning(options.ownRoot, project, globals);
142
- return project === undefined ? { globals, running } : { project, globals, running };
143
- }
144
-
145
- /**
146
- * The project install nearest `cwd`: the copy the hand-off finds
147
- * (`findProjectCopy`), and when there is none, a Yarn Plug'n'Play project
148
- * that declares the package (`findPnpInstall`). Undefined when there is
149
- * neither, or the `node_modules` copy found is some other package.
150
- */
151
- export async function findProjectInstallInfo(cwd: string): Promise<ProjectInstallInfo | undefined> {
152
- const location = findProjectCopy(cwd);
153
- if (location === undefined) return findPnpInstall(cwd);
154
- const copy = readPackageJson(location);
155
- if (copy?.name !== PACKAGE_NAME) return undefined;
156
-
157
- // <projectRoot>/node_modules/@sous-io/sous
158
- const projectRoot = path.resolve(location, "..", "..", "..");
159
- return describeProject(projectRoot, await projectManager(projectRoot), {
160
- location,
161
- realPath: realpathOr(location),
162
- installed: versionOf(copy),
163
- });
164
- }
165
-
166
- /** The file Yarn Plug'n'Play writes in place of `node_modules`. */
167
- export const PNP_FILE = ".pnp.cjs";
168
-
169
- /**
170
- * A Yarn Plug'n'Play install, which has no `node_modules` copy to find: the
171
- * nearest package.json at or above `cwd` that declares the package, with a
172
- * `.pnp.cjs` in its directory or above it. The version is the one `yarn.lock`
173
- * (beside `.pnp.cjs`) resolves the declared range to, and the manager is Yarn
174
- * Berry, the only Yarn that writes `.pnp.cjs`. Undefined when either is missing.
175
- */
176
- export function findPnpInstall(cwd: string): ProjectInstallInfo | undefined {
177
- const projectRoot = findUp(cwd, (dir) => declaredDependency(readPackageJson(dir)) !== undefined);
178
- if (projectRoot === undefined) return undefined;
179
- const pnpRoot = findUp(projectRoot, (dir) => fs.existsSync(path.join(dir, PNP_FILE)));
180
- if (pnpRoot === undefined) return undefined;
181
-
182
- const range = declaredDependency(readPackageJson(projectRoot))?.range ?? "";
183
- let lockfile = "";
184
- try {
185
- lockfile = fs.readFileSync(path.join(pnpRoot, "yarn.lock"), "utf8");
186
- } catch {
187
- // No lockfile: the version is unknown, and the plan says so.
188
- }
189
- const location = path.join(pnpRoot, PNP_FILE);
190
- const info = describeProject(
191
- projectRoot,
192
- { supported: true, agent: "yarn@berry", detected: true },
193
- {
194
- location,
195
- realPath: realpathOr(location),
196
- installed: yarnLockVersion(lockfile, range) ?? UNKNOWN_VERSION,
197
- }
198
- );
199
- info.pnpRoot = pnpRoot;
200
- return info;
201
- }
202
-
203
- /**
204
- * The version a Yarn Berry `yarn.lock` resolves the package's declared
205
- * `range` to. Each entry opens with an unindented key listing the
206
- * descriptors it answers (`"@sous-io/sous@npm:0.2.28, @sous-io/sous@npm:^0.2.0":`)
207
- * and holds an indented `version:` line. The entry whose key names the range
208
- * wins; when none does and the lockfile holds exactly one version of the
209
- * package, that version is the answer. Undefined otherwise.
210
- *
211
- * yarnLockVersion('"@sous-io/sous@npm:0.2.28":\n version: 0.2.28\n', "0.2.28") -> "0.2.28"
212
- */
213
- export function yarnLockVersion(lockfile: string, range: string): string | undefined {
214
- const prefix = `${PACKAGE_NAME}@`;
215
- const wanted = new Set([`${prefix}${range}`, `${prefix}npm:${range}`]);
216
- const entries: { descriptors: string[]; version?: string }[] = [];
217
- let entry: { descriptors: string[]; version?: string } | undefined;
218
- for (const line of lockfile.split(/\r?\n/)) {
219
- if (/^[^\s#].*:$/.test(line)) {
220
- const descriptors = line
221
- .slice(0, -1)
222
- .split(",")
223
- .map((part) => part.trim().replace(/^"|"$/g, ""))
224
- .filter((descriptor) => descriptor.startsWith(prefix));
225
- entry = descriptors.length > 0 ? { descriptors } : undefined;
226
- if (entry !== undefined) entries.push(entry);
227
- continue;
228
- }
229
- const version = /^\s+version:\s*"?([^"\s]+)"?\s*$/.exec(line);
230
- if (entry !== undefined && version !== null && entry.version === undefined) {
231
- entry.version = version[1];
232
- }
233
- }
234
- const exact = entries.find((candidate) => candidate.descriptors.some((d) => wanted.has(d)));
235
- if (exact !== undefined) return exact.version;
236
- const versions = new Set(entries.flatMap((candidate) => candidate.version ?? []));
237
- return versions.size === 1 ? [...versions][0] : undefined;
238
- }
239
-
240
- /** A project install, from its root, its manager and the facts about its copy. */
241
- function describeProject(
242
- projectRoot: string,
243
- manager: ProjectManagerInfo,
244
- copy: { location: string; realPath: string; installed: string }
245
- ): ProjectInstallInfo {
246
- const pkg = readPackageJson(projectRoot);
247
- const declared = declaredDependency(pkg);
248
- const info: ProjectInstallInfo = {
249
- projectRoot,
250
- ...copy,
251
- manager,
252
- workspaceRoot: isWorkspaceRoot(manager.agent, projectRoot, pkg),
253
- };
254
- if (declared !== undefined) info.declared = declared;
255
- return info;
256
- }
257
-
258
- /** The nearest directory at or above `start` that `test` accepts, or undefined. */
259
- function findUp(start: string, test: (dir: string) => boolean): string | undefined {
260
- let dir = path.resolve(start);
261
- for (;;) {
262
- if (test(dir)) return dir;
263
- const parent = path.dirname(dir);
264
- if (parent === dir) return undefined;
265
- dir = parent;
266
- }
267
- }
268
-
269
- /**
270
- * How a package.json declares sous: `devDependencies` first, then
271
- * `dependencies`; undefined when neither names it.
272
- */
273
- export function declaredDependency(pkg: unknown): DeclaredDependency | undefined {
274
- if (pkg === null || typeof pkg !== "object") return undefined;
275
- for (const section of ["devDependencies", "dependencies"] as const) {
276
- const block = (pkg as Record<string, unknown>)[section];
277
- if (block === null || typeof block !== "object") continue;
278
- const range = (block as Record<string, unknown>)[PACKAGE_NAME];
279
- if (typeof range === "string") return { section, range };
280
- }
281
- return undefined;
282
- }
283
-
284
- /**
285
- * Which manager owns the project at `projectRoot`, read by
286
- * package-manager-detector from that directory alone (its `packageManager`
287
- * and `devEngines` fields and its lockfile). Nothing found means npm, marked
288
- * as not detected.
289
- *
290
- * Two corrections are made on top: a `yarn` answer from a lockfile is Yarn
291
- * Berry when the project has a `.yarnrc.yml` or its `yarn.lock` carries Berry's
292
- * `__metadata` header (the detector reads a lockfile as classic unless the
293
- * `packageManager` field says otherwise), and `pnpm@6` is plain pnpm, whose
294
- * `add` flags have not changed.
295
- */
296
- export async function projectManager(projectRoot: string): Promise<ProjectManagerInfo> {
297
- const found = await detect({ cwd: projectRoot, stopDir: projectRoot });
298
- if (found === null) return { supported: true, agent: "npm", detected: false };
299
- let agent: string = found.agent;
300
- if (agent === "yarn" && isYarnBerryProject(projectRoot)) agent = "yarn@berry";
301
- if (agent === "pnpm@6") agent = "pnpm";
302
- return isProjectManager(agent)
303
- ? { supported: true, agent, detected: true }
304
- : { supported: false, agent };
305
- }
306
-
307
- /** Whether the files at `projectRoot` are Yarn Berry's rather than Yarn classic's. */
308
- function isYarnBerryProject(projectRoot: string): boolean {
309
- if (fs.existsSync(path.join(projectRoot, ".yarnrc.yml"))) return true;
310
- try {
311
- return /^__metadata:/m.test(fs.readFileSync(path.join(projectRoot, "yarn.lock"), "utf8"));
312
- } catch {
313
- return false;
314
- }
315
- }
316
-
317
- /**
318
- * Whether `projectRoot` is a workspace root in the sense its manager needs a
319
- * flag for: pnpm, when it holds a `pnpm-workspace.yaml`; Yarn classic, when
320
- * its package.json has `workspaces`. Every other manager reads as false.
321
- */
322
- export function isWorkspaceRoot(agent: string, projectRoot: string, pkg: unknown): boolean {
323
- if (agent === "pnpm") return fs.existsSync(path.join(projectRoot, "pnpm-workspace.yaml"));
324
- if (agent === "yarn") {
325
- return pkg !== null && typeof pkg === "object" && "workspaces" in pkg;
326
- }
327
- return false;
328
- }
329
-
330
- /** The command that prints each global manager's global `node_modules`, and how to read it. */
331
- const GLOBAL_ROOT_QUERIES: Record<GlobalManager, { args: string[]; suffix?: string }> = {
332
- npm: { args: ["root", "-g"] },
333
- pnpm: { args: ["root", "-g"] },
334
- // `yarn global dir` prints the directory ABOVE its node_modules.
335
- yarn: { args: ["global", "dir"], suffix: "node_modules" },
336
- };
337
-
338
- /**
339
- * Asks each global manager where its global `node_modules` is, and returns a
340
- * global install for each one that holds a copy of the package. A manager that
341
- * is not installed (exit 127), exits non-zero or prints nothing is skipped. Two
342
- * managers naming the same copy (by real path) count once, under the first.
343
- */
344
- export async function findGlobalInstalls(run: CommandRunner): Promise<GlobalInstallInfo[]> {
345
- const installs: GlobalInstallInfo[] = [];
346
- for (const manager of GLOBAL_MANAGERS) {
347
- const root = await globalRoot(manager, run);
348
- if (root === undefined) continue;
349
- const location = path.join(root, ...PACKAGE_NAME.split("/"));
350
- const pkg = readPackageJson(location);
351
- if (pkg?.name !== PACKAGE_NAME) continue;
352
- const realPath = realpathOr(location);
353
- if (installs.some((install) => install.realPath === realPath)) continue;
354
- installs.push({ manager, location, realPath, installed: versionOf(pkg) });
355
- }
356
- return installs;
357
- }
358
-
359
- /** One manager's global `node_modules`, or undefined when it cannot say. */
360
- async function globalRoot(manager: GlobalManager, run: CommandRunner): Promise<string | undefined> {
361
- const query = GLOBAL_ROOT_QUERIES[manager];
362
- let output: string;
363
- try {
364
- const result = await run(manager, query.args, {});
365
- if (result.code !== 0) return undefined;
366
- output = result.stdout.trim().split(/\r?\n/).pop()?.trim() ?? "";
367
- } catch {
368
- return undefined;
369
- }
370
- if (output.length === 0) return undefined;
371
- return query.suffix === undefined ? output : path.join(output, query.suffix);
372
- }
373
-
374
- /**
375
- * What the running copy at `ownRoot` is: the project install or a global one
376
- * when its real path is theirs, otherwise read from where it lives.
377
- */
378
- export function classifyRunning(
379
- ownRoot: string,
380
- project: ProjectInstallInfo | undefined,
381
- globals: readonly GlobalInstallInfo[]
382
- ): RunningInstall {
383
- const realPath = realpathOr(ownRoot);
384
- const pkg = readPackageJson(ownRoot);
385
- const installed = pkg === undefined ? UNKNOWN_VERSION : versionOf(pkg);
386
- if (project !== undefined && (project.realPath === realPath || isPnpCopy(project, realPath))) {
387
- return { kind: "project", realPath, installed };
388
- }
389
- const global = globals.find((install) => install.realPath === realPath);
390
- if (global !== undefined) return { kind: "global", manager: global.manager, realPath, installed };
391
- const slashed = realPath.split(path.sep).join("/");
392
- if (slashed.includes("/_npx/")) return { kind: "npx", realPath, installed };
393
- if (slashed.includes("/.volta/")) return { kind: "volta", realPath, installed };
394
- return { kind: "unknown", realPath, installed };
395
- }
396
-
397
- /**
398
- * Whether `realPath` is the Plug'n'Play project's own copy, unpacked by Yarn
399
- * into `<pnpRoot>/.yarn/unplugged/` (a copy still inside Yarn's zip cache
400
- * cannot run sous at all).
401
- */
402
- function isPnpCopy(project: ProjectInstallInfo, realPath: string): boolean {
403
- if (project.pnpRoot === undefined) return false;
404
- const unplugged = path.join(realpathOr(project.pnpRoot), ".yarn", "unplugged");
405
- const relative = path.relative(unplugged, realPath);
406
- return relative.length > 0 && !relative.startsWith("..") && !path.isAbsolute(relative);
407
- }
408
-
409
- /** The `version` a package.json names, or `UNKNOWN_VERSION`. */
410
- function versionOf(pkg: Record<string, unknown>): string {
411
- return typeof pkg.version === "string" ? pkg.version : UNKNOWN_VERSION;
412
- }
@@ -1,126 +0,0 @@
1
- /**
2
- * The command each package manager needs to install a version of sous.
3
- *
4
- * The commands come from package-manager-detector's table (`resolveCommand`),
5
- * so sous spells nothing a manager already publishes; this module adds the
6
- * flags an update needs on top (an exact pin, the dependency section, the
7
- * workspace root) and runs nothing.
8
- */
9
-
10
- import { resolveCommand } from "package-manager-detector/commands";
11
- import type { Agent } from "package-manager-detector";
12
- import { PACKAGE_NAME } from "../project-install.mjs";
13
-
14
- /** The managers that can update a project's own install. */
15
- export const PROJECT_MANAGERS = ["npm", "pnpm", "yarn", "yarn@berry"] as const;
16
-
17
- /** The managers that can update a global install (Yarn Berry has no global installs). */
18
- export const GLOBAL_MANAGERS = ["npm", "pnpm", "yarn"] as const;
19
-
20
- /** A manager that can update a project install; `yarn` is Yarn classic (1.x). */
21
- export type ProjectManager = (typeof PROJECT_MANAGERS)[number];
22
-
23
- /** A manager that can update a global install; `yarn` is Yarn classic (1.x). */
24
- export type GlobalManager = (typeof GLOBAL_MANAGERS)[number];
25
-
26
- /** A command and its arguments, not yet run. */
27
- export type ManagerCommand = { command: string; args: string[] };
28
-
29
- /** The section of package.json a project declares sous in. */
30
- export type DependencySection = "devDependencies" | "dependencies";
31
-
32
- /** Options for `projectAdd`. */
33
- export type ProjectAddOptions = {
34
- /** The section the dependency is declared in; it stays there. */
35
- section: DependencySection;
36
- /**
37
- * Whether the project is a workspace root, as its manager reads one (see
38
- * `isWorkspaceRoot` in installs.ts). pnpm and Yarn classic then need a flag
39
- * to add a dependency there at all.
40
- */
41
- workspaceRoot: boolean;
42
- };
43
-
44
- /** Whether `agent` is one of `PROJECT_MANAGERS`. */
45
- export function isProjectManager(agent: string): agent is ProjectManager {
46
- return (PROJECT_MANAGERS as readonly string[]).includes(agent);
47
- }
48
-
49
- /** Whether `agent` is one of `GLOBAL_MANAGERS`. */
50
- export function isGlobalManager(agent: string): agent is GlobalManager {
51
- return (GLOBAL_MANAGERS as readonly string[]).includes(agent);
52
- }
53
-
54
- /**
55
- * The package spec an install names: `@sous-io/sous@<version>`.
56
- *
57
- * packageSpec("0.2.34") -> "@sous-io/sous@0.2.34"
58
- */
59
- export function packageSpec(version: string): string {
60
- return `${PACKAGE_NAME}@${version}`;
61
- }
62
-
63
- /**
64
- * The command that installs `version` globally with `manager`.
65
- *
66
- * globalInstall("pnpm", "0.2.34")
67
- * // -> { command: "pnpm", args: ["add", "-g", "@sous-io/sous@0.2.34"] }
68
- */
69
- export function globalInstall(manager: GlobalManager, version: string): ManagerCommand {
70
- return resolve(manager, "global", [packageSpec(version)]);
71
- }
72
-
73
- /**
74
- * The command that pins a project's dependency on sous to exactly `version`
75
- * and updates its lockfile, run in the project root.
76
- *
77
- * `-E` pins the exact version with every supported manager; `-D` is added only
78
- * for a devDependency, so a dependency declared under `dependencies` stays
79
- * there. At a workspace root, pnpm gets `-w` and Yarn classic `-W`; npm adds
80
- * to the root package.json when run there, and Yarn Berry needs no flag.
81
- *
82
- * projectAdd("pnpm", "0.2.34", { section: "devDependencies", workspaceRoot: true })
83
- * // -> { command: "pnpm", args: ["add", "-D", "-E", "-w", "@sous-io/sous@0.2.34"] }
84
- */
85
- export function projectAdd(
86
- manager: ProjectManager,
87
- version: string,
88
- options: ProjectAddOptions
89
- ): ManagerCommand {
90
- const flags: string[] = [];
91
- if (options.section === "devDependencies") flags.push("-D");
92
- flags.push("-E");
93
- if (options.workspaceRoot) {
94
- if (manager === "pnpm") flags.push("-w");
95
- if (manager === "yarn") flags.push("-W");
96
- }
97
- return resolve(manager, "add", [...flags, packageSpec(version)]);
98
- }
99
-
100
- /**
101
- * The command a person would run by hand to pin a project's dependency with a
102
- * manager sous does not drive (bun, deno), or undefined when
103
- * package-manager-detector knows no `add` command for it.
104
- */
105
- export function manualProjectAdd(
106
- agent: string,
107
- version: string,
108
- section: DependencySection
109
- ): ManagerCommand | undefined {
110
- const flags = section === "devDependencies" ? ["-D"] : [];
111
- try {
112
- return resolveCommand(agent as Agent, "add", [...flags, packageSpec(version)]) ?? undefined;
113
- } catch {
114
- return undefined;
115
- }
116
- }
117
-
118
- /** Resolves a command the table is known to hold for every supported manager. */
119
- function resolve(agent: Agent, command: "add" | "global", args: string[]): ManagerCommand {
120
- const resolved = resolveCommand(agent, command, args);
121
- /* c8 ignore next 3 */
122
- if (resolved === null) {
123
- throw new Error(`package-manager-detector has no '${command}' command for ${agent}.`);
124
- }
125
- return { command: resolved.command, args: resolved.args };
126
- }