@sous-io/sous 0.2.37 → 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 -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,460 +0,0 @@
1
- /**
2
- * What git says about the working tree a release or a submission is standing in.
3
- *
4
- * Both `sous repo release` and `sous repo submit` need the same handful of
5
- * facts before they are allowed to do anything: is anything uncommitted, which
6
- * branch is checked out, which branch is the default one, and where does
7
- * `origin` point. A release makes exactly one commit of its own (its version
8
- * bumps and its index, through `commitPaths`) and refuses while anything else
9
- * is uncommitted; a submission commits only when `--commit` asks it to
10
- * (`commitEverything`). Everything else here is read to refuse politely rather
11
- * than to fix anything.
12
- *
13
- * Every function takes the injectable command runner, so tests never spawn git
14
- * unless they mean to.
15
- */
16
-
17
- import { runGit, type RunOptions } from "../providers/git.js";
18
-
19
- /** One path git reports as changed, with the two-letter status it reported. */
20
- export type ChangedPath = {
21
- /** The status code from `git status --porcelain`, such as `M `, `??` or `A `. */
22
- status: string;
23
- /** The path, relative to the repository root. */
24
- path: string;
25
- };
26
-
27
- /**
28
- * Everything `git status --porcelain` reports: staged changes, unstaged changes
29
- * and untracked files alike. An empty list means the working tree is clean.
30
- *
31
- * @param rootDir - The repository's root directory.
32
- * @param options - The command runner to use.
33
- */
34
- export async function uncommittedChanges(
35
- rootDir: string,
36
- options: RunOptions = {}
37
- ): Promise<ChangedPath[]> {
38
- const output = await runGit(["status", "--porcelain"], {
39
- cwd: rootDir,
40
- run: options.run,
41
- });
42
- if (output.length === 0) return [];
43
-
44
- const changed: ChangedPath[] = [];
45
- for (const line of output.split("\n")) {
46
- // The status field is one or two characters, and the command runner trims
47
- // its output, so a leading space (an unstaged edit) is already gone by the
48
- // time the line arrives here. Split on the whitespace instead of counting
49
- // columns. A rename is reported as 'old -> new'; the new name is the one
50
- // that still exists, so that is the one reported.
51
- const match = /^(\S{1,2})\s+(.*)$/.exec(line.trim());
52
- if (match === null) continue;
53
- const target = match[2]!;
54
- const arrow = target.indexOf(" -> ");
55
- changed.push({
56
- status: match[1]!,
57
- path: arrow === -1 ? target : target.slice(arrow + 4),
58
- });
59
- }
60
- return changed;
61
- }
62
-
63
- /**
64
- * The name of the checked-out branch, or undefined when HEAD is detached (which
65
- * is what a CI checkout of a tag looks like).
66
- *
67
- * @param rootDir - The repository's root directory.
68
- * @param options - The command runner to use.
69
- */
70
- export async function currentBranch(
71
- rootDir: string,
72
- options: RunOptions = {}
73
- ): Promise<string | undefined> {
74
- try {
75
- const name = await runGit(["rev-parse", "--abbrev-ref", "HEAD"], {
76
- cwd: rootDir,
77
- run: options.run,
78
- });
79
- return name === "HEAD" ? undefined : name;
80
- } catch {
81
- return undefined;
82
- }
83
- }
84
-
85
- /**
86
- * The repository's default branch, taken from what `origin/HEAD` points at.
87
- * Returns undefined when the remote never told this clone, which is normal for
88
- * a repository cloned with `--depth 1` or created locally.
89
- *
90
- * @param rootDir - The repository's root directory.
91
- * @param options - The command runner to use.
92
- */
93
- export async function defaultBranch(
94
- rootDir: string,
95
- options: RunOptions = {}
96
- ): Promise<string | undefined> {
97
- try {
98
- const ref = await runGit(["symbolic-ref", "--short", "refs/remotes/origin/HEAD"], {
99
- cwd: rootDir,
100
- run: options.run,
101
- });
102
- const slash = ref.indexOf("/");
103
- return slash === -1 ? ref : ref.slice(slash + 1);
104
- } catch {
105
- return undefined;
106
- }
107
- }
108
-
109
- /**
110
- * The commit where this checkout's own work starts: the point HEAD shares with
111
- * the remote's copy of a branch. Returns undefined when this checkout holds no
112
- * copy of that branch, or when git cannot find a commit the two share.
113
- *
114
- * A shallow clone answers this as long as HEAD was built on the branch it was
115
- * cloned from, which is why a submission can rely on it where it cannot rely on
116
- * tags.
117
- *
118
- * @param rootDir - The repository's root directory.
119
- * @param remote - The remote whose branch is compared, normally `origin`.
120
- * @param branch - The branch on that remote, normally the default one.
121
- * @param options - The command runner to use.
122
- */
123
- export async function forkPoint(
124
- rootDir: string,
125
- remote: string,
126
- branch: string,
127
- options: RunOptions = {}
128
- ): Promise<string | undefined> {
129
- try {
130
- const commit = await runGit(["merge-base", `refs/remotes/${remote}/${branch}`, "HEAD"], {
131
- cwd: rootDir,
132
- run: options.run,
133
- });
134
- return commit.length > 0 ? commit : undefined;
135
- } catch {
136
- return undefined;
137
- }
138
- }
139
-
140
- /**
141
- * True when the commits since `since` changed a path, comparing that commit
142
- * with HEAD.
143
- *
144
- * @param rootDir - The repository's root directory.
145
- * @param since - The commit to compare HEAD with.
146
- * @param relativePath - The path to check, relative to the repository root.
147
- * @param options - The command runner to use.
148
- */
149
- export async function pathChangedSince(
150
- rootDir: string,
151
- since: string,
152
- relativePath: string,
153
- options: RunOptions = {}
154
- ): Promise<boolean> {
155
- const changed = await runGit(["diff", "--name-only", since, "HEAD", "--", relativePath], {
156
- cwd: rootDir,
157
- run: options.run,
158
- });
159
- return changed.length > 0;
160
- }
161
-
162
- /**
163
- * The URL of a remote, or undefined when the repository has no such remote.
164
- *
165
- * @param rootDir - The repository's root directory.
166
- * @param remote - The remote's name, normally `origin`.
167
- * @param options - The command runner to use.
168
- */
169
- export async function remoteUrl(
170
- rootDir: string,
171
- remote: string,
172
- options: RunOptions = {}
173
- ): Promise<string | undefined> {
174
- try {
175
- const url = await runGit(["remote", "get-url", remote], {
176
- cwd: rootDir,
177
- run: options.run,
178
- });
179
- return url.length > 0 ? url : undefined;
180
- } catch {
181
- return undefined;
182
- }
183
- }
184
-
185
- /**
186
- * True when a path is tracked by git and identical to what HEAD holds. It is how
187
- * a caller confirms that a file it is about to act on is the one the repository
188
- * actually committed.
189
- *
190
- * @param rootDir - The repository's root directory.
191
- * @param relativePath - The path to check, relative to the repository root.
192
- * @param options - The command runner to use.
193
- */
194
- export async function isCommittedAndUnchanged(
195
- rootDir: string,
196
- relativePath: string,
197
- options: RunOptions = {}
198
- ): Promise<boolean> {
199
- const tracked = await runGit(["ls-files", "--", relativePath], {
200
- cwd: rootDir,
201
- run: options.run,
202
- });
203
- if (tracked.length === 0) return false;
204
-
205
- const changed = await runGit(["status", "--porcelain", "--", relativePath], {
206
- cwd: rootDir,
207
- run: options.run,
208
- });
209
- return changed.length === 0;
210
- }
211
-
212
- /**
213
- * True when git can work out who is committing, which is what it needs before
214
- * it will make a commit or an annotated tag.
215
- *
216
- * `git var GIT_AUTHOR_IDENT` answers the exact question git asks itself: it
217
- * honours the `user.name` and `user.email` settings, the `GIT_AUTHOR_*` and
218
- * `GIT_COMMITTER_*` environment variables, and the strict rules that reject a
219
- * guessed identity. It fails with the same "empty ident name" or "unable to
220
- * auto-detect email address" that a commit would have failed with, which is
221
- * why the check is made ahead of time rather than left to the commit.
222
- *
223
- * @param rootDir - The repository's root directory.
224
- * @param options - The command runner to use.
225
- */
226
- export async function hasCommitIdentity(
227
- rootDir: string,
228
- options: RunOptions = {}
229
- ): Promise<boolean> {
230
- for (const name of ["GIT_AUTHOR_IDENT", "GIT_COMMITTER_IDENT"]) {
231
- try {
232
- const ident = await runGit(["var", name], { cwd: rootDir, run: options.run });
233
- if (ident.length === 0) return false;
234
- } catch {
235
- return false;
236
- }
237
- }
238
- return true;
239
- }
240
-
241
- /**
242
- * Stages exactly the given paths and commits them.
243
- *
244
- * This is how `sous repo release` commits on an author's behalf, and it is
245
- * deliberately narrow: a release writes version bumps and an index, and those
246
- * are the only paths it stages. Anything else in the working tree is left
247
- * exactly as it was. The only other commit sous makes is `commitEverything`,
248
- * for `sous repo submit --commit`.
249
- *
250
- * @param rootDir - The repository's root directory.
251
- * @param paths - The paths to stage, relative to the repository root.
252
- * @param message - The commit message.
253
- * @param options - The command runner to use.
254
- */
255
- export async function commitPaths(
256
- rootDir: string,
257
- paths: ReadonlyArray<string>,
258
- message: string,
259
- options: RunOptions = {}
260
- ): Promise<void> {
261
- if (paths.length === 0) return;
262
- await runGit(["add", "--", ...paths], { cwd: rootDir, run: options.run });
263
- await runGit(["commit", "--message", message, "--", ...paths], {
264
- cwd: rootDir,
265
- run: options.run,
266
- });
267
- }
268
-
269
- /**
270
- * True when any of the given paths differs from what HEAD holds, so a release
271
- * knows whether it has anything to commit.
272
- *
273
- * @param rootDir - The repository's root directory.
274
- * @param paths - The paths to check, relative to the repository root.
275
- * @param options - The command runner to use.
276
- */
277
- export async function anythingToCommit(
278
- rootDir: string,
279
- paths: ReadonlyArray<string>,
280
- options: RunOptions = {}
281
- ): Promise<boolean> {
282
- if (paths.length === 0) return false;
283
- const changed = await runGit(["status", "--porcelain", "--", ...paths], {
284
- cwd: rootDir,
285
- run: options.run,
286
- });
287
- return changed.length > 0;
288
- }
289
-
290
- /**
291
- * Creates a branch at HEAD and checks it out.
292
- *
293
- * @param rootDir - The repository's root directory.
294
- * @param branch - The branch to create.
295
- * @param options - The command runner to use.
296
- */
297
- export async function createBranch(
298
- rootDir: string,
299
- branch: string,
300
- options: RunOptions = {}
301
- ): Promise<void> {
302
- await runGit(["checkout", "-b", branch], { cwd: rootDir, run: options.run });
303
- }
304
-
305
- /** What a push did: sent new commits, or found the remote already had them. */
306
- export type PushOutcome = "updated" | "up-to-date";
307
-
308
- /**
309
- * Pushes one branch to a remote, setting it as the branch's upstream, and says
310
- * whether anything was sent.
311
- *
312
- * The push is never forced. When the remote branch holds commits the local one
313
- * lacks, git refuses, and its own explanation is what the caller receives: git
314
- * is the authority on whether a push is safe, so nothing here second-guesses it.
315
- * Whether anything was sent is read from git's machine-readable report, where a
316
- * ref that was already current is flagged with `=`.
317
- *
318
- * @param rootDir - The repository's root directory.
319
- * @param remote - The remote to push to.
320
- * @param branch - The branch to push.
321
- * @param options - The command runner to use.
322
- */
323
- export async function pushBranch(
324
- rootDir: string,
325
- remote: string,
326
- branch: string,
327
- options: RunOptions = {}
328
- ): Promise<PushOutcome> {
329
- const report = await runGit(["push", "--porcelain", "--set-upstream", remote, branch], {
330
- cwd: rootDir,
331
- run: options.run,
332
- });
333
-
334
- // Git records what was pushed in the remote-tracking branch only when the
335
- // remote's fetch configuration covers the branch, and a clone `sous repo
336
- // link` makes covers only the default branch. Recording it here keeps the
337
- // checkout's own answer to "was this pushed?" true, which is what `sous repo
338
- // unlink --remove` and `sous repo contribute --finish` read. It is a record,
339
- // not a step: a failure to write it changes nothing about the push.
340
- try {
341
- await runGit(["update-ref", `refs/remotes/${remote}/${branch}`, `refs/heads/${branch}`], {
342
- cwd: rootDir,
343
- run: options.run,
344
- });
345
- } catch {
346
- // The push succeeded; only the local record of it is missing.
347
- }
348
-
349
- return pushReportIsUpToDate(report) ? "up-to-date" : "updated";
350
- }
351
-
352
- /**
353
- * True when git's machine-readable push report says every ref it pushed was
354
- * already current on the remote.
355
- *
356
- * pushReportIsUpToDate("To origin\n=\trefs/heads/a:refs/heads/a\t[up to date]\nDone");
357
- * // -> true
358
- *
359
- * @param report - What `git push --porcelain` printed.
360
- */
361
- export function pushReportIsUpToDate(report: string): boolean {
362
- const refLines = report.split("\n").filter((line) => /^[ +\-*!=]\t/.test(line));
363
- return refLines.length > 0 && refLines.every((line) => line.startsWith("=\t"));
364
- }
365
-
366
- /**
367
- * True when a local branch of that name exists.
368
- *
369
- * @param rootDir - The repository's root directory.
370
- * @param branch - The branch name.
371
- * @param options - The command runner to use.
372
- */
373
- export async function branchExists(
374
- rootDir: string,
375
- branch: string,
376
- options: RunOptions = {}
377
- ): Promise<boolean> {
378
- try {
379
- await runGit(["rev-parse", "--verify", "--quiet", `refs/heads/${branch}`], {
380
- cwd: rootDir,
381
- run: options.run,
382
- });
383
- return true;
384
- } catch {
385
- return false;
386
- }
387
- }
388
-
389
- /**
390
- * Checks out an existing branch. Git refuses when uncommitted changes would be
391
- * overwritten, and that refusal reaches the caller unchanged.
392
- *
393
- * @param rootDir - The repository's root directory.
394
- * @param branch - The branch to check out.
395
- * @param options - The command runner to use.
396
- */
397
- export async function switchBranch(
398
- rootDir: string,
399
- branch: string,
400
- options: RunOptions = {}
401
- ): Promise<void> {
402
- await runGit(["switch", branch], { cwd: rootDir, run: options.run });
403
- }
404
-
405
- /**
406
- * Every path the commits since `since` changed, comparing that commit with HEAD.
407
- *
408
- * @param rootDir - The repository's root directory.
409
- * @param since - The commit to compare HEAD with.
410
- * @param options - The command runner to use.
411
- */
412
- export async function pathsChangedSince(
413
- rootDir: string,
414
- since: string,
415
- options: RunOptions = {}
416
- ): Promise<string[]> {
417
- const changed = await runGit(["diff", "--name-only", since, "HEAD"], {
418
- cwd: rootDir,
419
- run: options.run,
420
- });
421
- return changed.length === 0 ? [] : changed.split("\n").filter((line) => line.length > 0);
422
- }
423
-
424
- /**
425
- * Stages everything the working tree holds (edits, deletions and untracked
426
- * files alike) and commits it with the given message.
427
- *
428
- * This is the second place sous commits on an author's behalf, and it runs only
429
- * for `sous repo submit --commit`, after the contributor has seen every path it
430
- * stages and agreed to it.
431
- *
432
- * @param rootDir - The repository's root directory.
433
- * @param message - The commit message.
434
- * @param options - The command runner to use.
435
- */
436
- export async function commitEverything(
437
- rootDir: string,
438
- message: string,
439
- options: RunOptions = {}
440
- ): Promise<void> {
441
- await runGit(["add", "--all"], { cwd: rootDir, run: options.run });
442
- await runGit(["commit", "--quiet", "--message", message], {
443
- cwd: rootDir,
444
- run: options.run,
445
- });
446
- }
447
-
448
- /**
449
- * The branch name sous proposes for a submission, stamped with the minute it
450
- * was made so two submissions from one checkout never collide.
451
- *
452
- * @param now - The moment the branch is being created.
453
- */
454
- export function submitBranchName(now: Date): string {
455
- const pad = (value: number) => String(value).padStart(2, "0");
456
- const stamp =
457
- `${now.getFullYear()}${pad(now.getMonth() + 1)}${pad(now.getDate())}` +
458
- `-${pad(now.getHours())}${pad(now.getMinutes())}`;
459
- return `sous/submit-${stamp}`;
460
- }