@intentius/chant 0.124.34 → 0.124.36

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 (171) hide show
  1. package/dist/cli/handlers/graph.js +2 -2
  2. package/dist/cli/handlers/pr.d.ts.map +1 -1
  3. package/dist/cli/handlers/pr.js +147 -51
  4. package/dist/cli/handlers/promote.d.ts.map +1 -1
  5. package/dist/cli/handlers/promote.js +23 -8
  6. package/dist/cli/handlers/release.d.ts +12 -0
  7. package/dist/cli/handlers/release.d.ts.map +1 -1
  8. package/dist/cli/handlers/release.js +125 -10
  9. package/dist/cli/handlers/run.d.ts +3 -1
  10. package/dist/cli/handlers/run.d.ts.map +1 -1
  11. package/dist/cli/handlers/run.js +12 -2
  12. package/dist/cli/main.d.ts.map +1 -1
  13. package/dist/cli/main.js +19 -1
  14. package/dist/cli/parse-args.d.ts.map +1 -1
  15. package/dist/cli/parse-args.js +17 -0
  16. package/dist/cli/registry.d.ts +14 -0
  17. package/dist/cli/registry.d.ts.map +1 -1
  18. package/dist/components/auto-release.d.ts +8 -1
  19. package/dist/components/auto-release.d.ts.map +1 -1
  20. package/dist/components/auto-release.js +3 -2
  21. package/dist/components/green-cover.d.ts +77 -0
  22. package/dist/components/green-cover.d.ts.map +1 -0
  23. package/dist/components/green-cover.js +153 -0
  24. package/dist/components/pr-pipeline.d.ts +34 -0
  25. package/dist/components/pr-pipeline.d.ts.map +1 -1
  26. package/dist/components/pr-pipeline.js +39 -5
  27. package/dist/components/promote.d.ts +4 -1
  28. package/dist/components/promote.d.ts.map +1 -1
  29. package/dist/components/promote.js +6 -3
  30. package/dist/components/release-promote.d.ts +9 -0
  31. package/dist/components/release-promote.d.ts.map +1 -1
  32. package/dist/components/release-promote.js +15 -0
  33. package/dist/config.d.ts +3 -0
  34. package/dist/config.d.ts.map +1 -1
  35. package/dist/deep-observation.d.ts +11 -0
  36. package/dist/deep-observation.d.ts.map +1 -1
  37. package/dist/graph-declared.d.ts +5 -1
  38. package/dist/graph-declared.d.ts.map +1 -1
  39. package/dist/graph-declared.js +7 -2
  40. package/dist/graph-detail.d.ts.map +1 -1
  41. package/dist/graph-detail.js +30 -1
  42. package/dist/graph-ir.d.ts.map +1 -1
  43. package/dist/graph-ir.js +13 -2
  44. package/dist/lifecycle/__fixtures__/publish-fakes.d.ts +5 -0
  45. package/dist/lifecycle/__fixtures__/publish-fakes.d.ts.map +1 -1
  46. package/dist/lifecycle/__fixtures__/publish-fakes.js +20 -2
  47. package/dist/lifecycle/compose-refs.d.ts +52 -0
  48. package/dist/lifecycle/compose-refs.d.ts.map +1 -0
  49. package/dist/lifecycle/compose-refs.js +115 -0
  50. package/dist/lifecycle/deep-observe.d.ts.map +1 -1
  51. package/dist/lifecycle/deep-observe.js +3 -2
  52. package/dist/lifecycle/green-apply.d.ts +39 -0
  53. package/dist/lifecycle/green-apply.d.ts.map +1 -0
  54. package/dist/lifecycle/green-apply.js +51 -0
  55. package/dist/lifecycle/publish-declaration.d.ts +6 -0
  56. package/dist/lifecycle/publish-declaration.d.ts.map +1 -1
  57. package/dist/lifecycle/publish-declaration.js +21 -0
  58. package/dist/lifecycle/publish-npm.d.ts +11 -4
  59. package/dist/lifecycle/publish-npm.d.ts.map +1 -1
  60. package/dist/lifecycle/publish-npm.js +104 -14
  61. package/dist/lifecycle/release-compose.d.ts +91 -0
  62. package/dist/lifecycle/release-compose.d.ts.map +1 -0
  63. package/dist/lifecycle/release-compose.js +235 -0
  64. package/dist/lifecycle/release-cut.d.ts +8 -0
  65. package/dist/lifecycle/release-cut.d.ts.map +1 -1
  66. package/dist/lifecycle/release-cut.js +2 -0
  67. package/dist/lifecycle/release-publish.d.ts +6 -0
  68. package/dist/lifecycle/release-publish.d.ts.map +1 -1
  69. package/dist/lifecycle/release-publish.js +2 -1
  70. package/dist/lifecycle/release-store.d.ts +15 -0
  71. package/dist/lifecycle/release-store.d.ts.map +1 -1
  72. package/dist/lifecycle/release-store.js +5 -1
  73. package/dist/wave-runtime.mjs +225 -223
  74. package/dist/workspace/checks/ci-workflow.d.ts.map +1 -1
  75. package/dist/workspace/checks/ci-workflow.js +8 -1
  76. package/dist/workspace/ci-green-cli.d.ts.map +1 -1
  77. package/dist/workspace/ci-green-cli.js +5 -3
  78. package/dist/workspace/ci-green-workflow.d.ts +18 -2
  79. package/dist/workspace/ci-green-workflow.d.ts.map +1 -1
  80. package/dist/workspace/ci-green-workflow.js +27 -5
  81. package/dist/workspace/ci-green.d.ts +31 -0
  82. package/dist/workspace/ci-green.d.ts.map +1 -1
  83. package/dist/workspace/ci-green.js +78 -4
  84. package/dist/workspace/ci-run.d.ts +6 -0
  85. package/dist/workspace/ci-run.d.ts.map +1 -1
  86. package/dist/workspace/ci-run.js +2 -1
  87. package/dist/workspace/ci-serve.d.ts +94 -13
  88. package/dist/workspace/ci-serve.d.ts.map +1 -1
  89. package/dist/workspace/ci-serve.js +248 -30
  90. package/dist/workspace/ci-workflow.d.ts +4 -4
  91. package/dist/workspace/ci-workflow.d.ts.map +1 -1
  92. package/dist/workspace/ci-workflow.js +2 -2
  93. package/dist/workspace/delivery-metrics.d.ts +3 -1
  94. package/dist/workspace/delivery-metrics.d.ts.map +1 -1
  95. package/dist/workspace/delivery-metrics.js +12 -3
  96. package/dist/workspace/member-commands.d.ts.map +1 -1
  97. package/dist/workspace/member-commands.js +4 -0
  98. package/dist/workspace/nested-graph.d.ts.map +1 -1
  99. package/dist/workspace/nested-graph.js +1 -0
  100. package/dist/workspace/release-candidates.d.ts +28 -0
  101. package/dist/workspace/release-candidates.d.ts.map +1 -1
  102. package/dist/workspace/release-candidates.js +38 -2
  103. package/dist/workspace/release-candidates.schema.json +51 -0
  104. package/dist/workspace/releases.schema.json +30 -0
  105. package/package.json +1 -1
  106. package/src/cli/handlers/graph.ts +2 -2
  107. package/src/cli/handlers/pr-green.test.ts +230 -0
  108. package/src/cli/handlers/pr.ts +148 -66
  109. package/src/cli/handlers/promote.test.ts +34 -0
  110. package/src/cli/handlers/promote.ts +22 -8
  111. package/src/cli/handlers/release-compose.e2e.test.ts +133 -0
  112. package/src/cli/handlers/release-publish-targets.e2e.test.ts +4 -1
  113. package/src/cli/handlers/release.ts +112 -10
  114. package/src/cli/handlers/run.ts +12 -1
  115. package/src/cli/main.ts +19 -1
  116. package/src/cli/parse-args.ts +13 -0
  117. package/src/cli/registry.ts +14 -0
  118. package/src/codegen/release-wiring.test.ts +56 -9
  119. package/src/components/auto-release.test.ts +22 -0
  120. package/src/components/auto-release.ts +9 -2
  121. package/src/components/green-cover.test.ts +170 -0
  122. package/src/components/green-cover.ts +186 -0
  123. package/src/components/pr-pipeline.test.ts +14 -4
  124. package/src/components/pr-pipeline.ts +56 -5
  125. package/src/components/promote.test.ts +7 -0
  126. package/src/components/promote.ts +9 -4
  127. package/src/components/release-promote.test.ts +24 -1
  128. package/src/components/release-promote.ts +15 -0
  129. package/src/deep-observation.ts +8 -0
  130. package/src/graph-declared.test.ts +18 -0
  131. package/src/graph-declared.ts +8 -1
  132. package/src/graph-detail.test.ts +68 -1
  133. package/src/graph-detail.ts +29 -1
  134. package/src/graph-ir.ts +12 -4
  135. package/src/lifecycle/__fixtures__/publish-fakes.ts +20 -2
  136. package/src/lifecycle/compose-refs.ts +125 -0
  137. package/src/lifecycle/deep-observe.test.ts +20 -0
  138. package/src/lifecycle/deep-observe.ts +4 -2
  139. package/src/lifecycle/green-apply.ts +71 -0
  140. package/src/lifecycle/publish-declaration.ts +21 -0
  141. package/src/lifecycle/publish-npm.ts +100 -14
  142. package/src/lifecycle/publish-targets.test.ts +108 -0
  143. package/src/lifecycle/release-compose.test.ts +125 -0
  144. package/src/lifecycle/release-compose.ts +312 -0
  145. package/src/lifecycle/release-cut.ts +6 -0
  146. package/src/lifecycle/release-publish.ts +8 -1
  147. package/src/lifecycle/release-store.ts +16 -1
  148. package/src/workspace/checks/ci-workflow.test.ts +12 -2
  149. package/src/workspace/checks/ci-workflow.ts +7 -1
  150. package/src/workspace/ci-green-cli.ts +5 -3
  151. package/src/workspace/ci-green-workflow.test.ts +52 -0
  152. package/src/workspace/ci-green-workflow.ts +34 -5
  153. package/src/workspace/ci-green.test.ts +33 -0
  154. package/src/workspace/ci-green.ts +85 -4
  155. package/src/workspace/ci-run.test.ts +18 -13
  156. package/src/workspace/ci-run.ts +8 -1
  157. package/src/workspace/ci-serve-land.e2e.test.ts +138 -0
  158. package/src/workspace/ci-serve-land.test.ts +176 -0
  159. package/src/workspace/ci-serve.test.ts +77 -43
  160. package/src/workspace/ci-serve.ts +268 -34
  161. package/src/workspace/ci-workflow.ts +4 -4
  162. package/src/workspace/delivery-metrics.ts +12 -3
  163. package/src/workspace/graph-contract.test.ts +3 -2
  164. package/src/workspace/member-commands.test.ts +11 -4
  165. package/src/workspace/member-commands.ts +3 -0
  166. package/src/workspace/nested-graph.test.ts +4 -0
  167. package/src/workspace/nested-graph.ts +1 -0
  168. package/src/workspace/release-candidates.schema.json +51 -0
  169. package/src/workspace/release-candidates.test.ts +30 -3
  170. package/src/workspace/release-candidates.ts +54 -2
  171. package/src/workspace/releases.schema.json +30 -0
@@ -212,6 +212,7 @@
212
212
  "newFor",
213
213
  "cut",
214
214
  "changes",
215
+ "groups",
215
216
  "flags"
216
217
  ],
217
218
  "properties": {
@@ -245,6 +246,13 @@
245
246
  },
246
247
  "description": "The first-parent commits after the previous candidate, up to and including this one, newest first."
247
248
  },
249
+ "groups": {
250
+ "type": "array",
251
+ "items": {
252
+ "$ref": "#/$defs/group"
253
+ },
254
+ "description": "The same changes by work item, then pull request, then commit (#4168 4.4). Work items come first, in the order the oldest change names them, then `work: null` for changes that name none. A change with two work items is under both."
255
+ },
248
256
  "flags": {
249
257
  "type": "array",
250
258
  "items": {
@@ -254,6 +262,49 @@
254
262
  }
255
263
  }
256
264
  },
265
+ "group": {
266
+ "type": "object",
267
+ "required": [
268
+ "work",
269
+ "pullRequests"
270
+ ],
271
+ "properties": {
272
+ "work": {
273
+ "type": [
274
+ "string",
275
+ "null"
276
+ ],
277
+ "description": "The work item id a `Chant-Record: work:<id>` trailer names, or null."
278
+ },
279
+ "pullRequests": {
280
+ "type": "array",
281
+ "items": {
282
+ "type": "object",
283
+ "required": [
284
+ "pr",
285
+ "commits"
286
+ ],
287
+ "properties": {
288
+ "pr": {
289
+ "type": [
290
+ "integer",
291
+ "null"
292
+ ],
293
+ "description": "The pull request, or null for commits that landed without one."
294
+ },
295
+ "commits": {
296
+ "type": "array",
297
+ "items": {
298
+ "type": "string"
299
+ },
300
+ "description": "Newest first."
301
+ }
302
+ }
303
+ },
304
+ "description": "Pull requests in the order their oldest commit landed, then `pr: null`."
305
+ }
306
+ }
307
+ },
257
308
  "change": {
258
309
  "type": "object",
259
310
  "required": [
@@ -11,7 +11,7 @@ import { writeFileSync } from "node:fs";
11
11
  import { join } from "node:path";
12
12
  import { contract } from "./__fixtures__/contract-repo";
13
13
  import candidatesSchema from "./release-candidates.schema.json";
14
- import { prFromSubject, recordTrailers, releaseCandidatesDocument, RELEASE_CANDIDATES_SCHEMA_ID } from "./release-candidates";
14
+ import { groupChanges, mergeRecordPr, prFromSubject, recordTrailers, releaseCandidatesDocument, RELEASE_CANDIDATES_SCHEMA_ID, type CandidateChange } from "./release-candidates";
15
15
  import { appendCut, makeRelease, persistRelease } from "../lifecycle/release-store";
16
16
  import { appendReleaseRecord } from "../lifecycle/release-ledger";
17
17
  import { writeLedgerFiles } from "../lifecycle/git";
@@ -46,6 +46,33 @@ describe("recordTrailers and prFromSubject", () => {
46
46
  });
47
47
  });
48
48
 
49
+ describe("groupChanges (#4168 4.4)", () => {
50
+ const change = (commit: string, work: string[], pr: number | null): CandidateChange => ({ commit, subject: commit, date: "", work, records: work.map((w) => `work:${w}`), pr, merge: null });
51
+
52
+ test("groups by work item, then pull request, then commit, with the ungrouped last", () => {
53
+ // Newest first, as a candidate's changes are.
54
+ const groups = groupChanges([
55
+ change("f", [], null),
56
+ change("e", ["w-2"], null),
57
+ change("d", ["w-1"], 4),
58
+ change("c", [], 3),
59
+ change("b", ["w-1", "w-2"], 2),
60
+ change("a", ["w-1"], 2),
61
+ ]);
62
+ expect(groups).toEqual([
63
+ { work: "w-1", pullRequests: [{ pr: 2, commits: ["b", "a"] }, { pr: 4, commits: ["d"] }] },
64
+ { work: "w-2", pullRequests: [{ pr: 2, commits: ["b"] }, { pr: null, commits: ["e"] }] },
65
+ { work: null, pullRequests: [{ pr: 3, commits: ["c"] }, { pr: null, commits: ["f"] }] },
66
+ ]);
67
+ });
68
+
69
+ test("a merge record names its pull request in source", () => {
70
+ expect(mergeRecordPr({ source: { kind: "pull-request", pr: 12 } })).toBe(12);
71
+ expect(mergeRecordPr({ source: { kind: "direct-push" } })).toBeNull();
72
+ expect(mergeRecordPr(null)).toBeNull();
73
+ });
74
+ });
75
+
49
76
  describe("releaseCandidatesDocument", () => {
50
77
  test("partitions the walk at green commits, skips revoked ones, and says which environments each is new for", async () => {
51
78
  await withTestDir(async (dir) => {
@@ -63,7 +90,7 @@ describe("releaseCandidatesDocument", () => {
63
90
  { cwd: dir },
64
91
  );
65
92
  // A merge record for d names its pull request.
66
- await writeLedgerFiles([{ path: `_merges/${d}.json`, content: JSON.stringify({ pr: 40, approvedDigest: "sha256:x" }) }], "merge record", { cwd: dir });
93
+ await writeLedgerFiles([{ path: `_merges/${d}.json`, content: JSON.stringify({ source: { kind: "pull-request", pr: 40 }, approvedDigest: "sha256:x" }) }], "merge record", { cwd: dir });
67
94
 
68
95
  const doc = await releaseCandidatesDocument({ cwd: dir });
69
96
  expectValid(doc);
@@ -78,7 +105,7 @@ describe("releaseCandidatesDocument", () => {
78
105
  expect(doc.candidates.map((x) => x.commit)).toEqual([d, b, init]);
79
106
  expect(doc.candidates[0]).toMatchObject({ newFor: ["staging", "prod"], cut: [] });
80
107
  expect(doc.candidates[0].changes.map((x) => x.commit)).toEqual([d, c]);
81
- expect(doc.candidates[0].changes[0]).toMatchObject({ pr: 40, merge: { pr: 40, approvedDigest: "sha256:x" } });
108
+ expect(doc.candidates[0].changes[0]).toMatchObject({ pr: 40, merge: { source: { kind: "pull-request", pr: 40 }, approvedDigest: "sha256:x" } });
82
109
  expect(doc.candidates[1].changes.map((x) => x.commit)).toEqual([b, a]);
83
110
  expect(doc.candidates[1].changes[0].work).toEqual(["w-7"]);
84
111
  expect(doc.candidates[1].changes[1].pr).toBe(1);
@@ -24,6 +24,11 @@
24
24
  * flags whose record files changed in its range, from the declared flag kinds
25
25
  * (#4024).
26
26
  *
27
+ * Each candidate also groups its changes (#4168 4.4) by work item, then by
28
+ * pull request, then by commit: a change lands under every work item its
29
+ * trailers name, or under no work item, and within that under its pull
30
+ * request, or under none for a commit that landed without one.
31
+ *
27
32
  * It reads local refs and tags only and never fetches. Git is read through a
28
33
  * local `git` subprocess.
29
34
  */
@@ -76,6 +81,20 @@ export interface CandidateChange {
76
81
  merge: Record<string, unknown> | null;
77
82
  }
78
83
 
84
+ /** A candidate's changes under one pull request, or under none (`pr: null`) for commits that landed without one. */
85
+ export interface CandidatePullRequestGroup {
86
+ pr: number | null;
87
+ /** Newest first. */
88
+ commits: string[];
89
+ }
90
+
91
+ /** A candidate's changes under one work item, or under none (`work: null`). Work items come first, in the order the oldest change names them. */
92
+ export interface CandidateGroup {
93
+ work: string | null;
94
+ /** Pull requests in the order their oldest commit landed, then `pr: null`. */
95
+ pullRequests: CandidatePullRequestGroup[];
96
+ }
97
+
79
98
  export interface CandidateFlag {
80
99
  id: string;
81
100
  member: string;
@@ -95,6 +114,8 @@ export interface Candidate {
95
114
  /** The labels of releases already cut from it. */
96
115
  cut: string[];
97
116
  changes: CandidateChange[];
117
+ /** The same changes by work item, then pull request, then commit (#4168 4.4). */
118
+ groups: CandidateGroup[];
98
119
  flags: CandidateFlag[];
99
120
  }
100
121
 
@@ -156,6 +177,34 @@ export function prFromSubject(subject: string): number | null {
156
177
  return m ? Number(m[1]) : null;
157
178
  }
158
179
 
180
+ /** The pull request a merge record names in its `source`, or null. */
181
+ export function mergeRecordPr(merge: Record<string, unknown> | null): number | null {
182
+ const source = merge?.source as { kind?: unknown; pr?: unknown } | undefined;
183
+ return source?.kind === "pull-request" && typeof source.pr === "number" ? source.pr : null;
184
+ }
185
+
186
+ /**
187
+ * Group changes, given newest first, by work item, then pull request, then
188
+ * commit. A change with work items lands in each one's group; the rest land
189
+ * in the `work: null` group, last. Within a group, pull requests come in the
190
+ * order their oldest commit landed, and commits without one last.
191
+ */
192
+ export function groupChanges(changes: readonly CandidateChange[]): CandidateGroup[] {
193
+ const oldestFirst = [...changes].reverse();
194
+ const groups = new Map<string | null, Map<number | null, string[]>>();
195
+ const add = (work: string | null, pr: number | null, commit: string): void => {
196
+ const byPr = groups.get(work) ?? new Map<number | null, string[]>();
197
+ groups.set(work, byPr);
198
+ byPr.set(pr, [commit, ...(byPr.get(pr) ?? [])]);
199
+ };
200
+ for (const c of oldestFirst) for (const work of c.work.length > 0 ? c.work : [null]) add(work, c.pr, c.commit);
201
+ const nullLast = <K>(keys: K[]): K[] => [...keys.filter((k) => k !== null), ...keys.filter((k) => k === null)];
202
+ return nullLast([...groups.keys()]).map((work) => {
203
+ const byPr = groups.get(work)!;
204
+ return { work, pullRequests: nullLast([...byPr.keys()]).map((pr) => ({ pr, commits: byPr.get(pr)! })) };
205
+ });
206
+ }
207
+
159
208
  /** Read the release candidates of the member at `query.cwd`. */
160
209
  export async function releaseCandidatesDocument(query: ReleaseCandidatesQuery): Promise<ReleaseCandidatesDocument> {
161
210
  const head = { $schema: RELEASE_CANDIDATES_SCHEMA_ID, contract: RELEASE_CANDIDATES_CONTRACT_VERSION, chant: readerVersion() };
@@ -240,7 +289,8 @@ export async function releaseCandidatesDocument(query: ReleaseCandidatesQuery):
240
289
  const d = details.get(sha);
241
290
  const { work, records } = recordTrailers(d?.trailers ?? {});
242
291
  const merge = merges.get(sha) ?? null;
243
- const mergePr = merge && typeof merge.pr === "number" ? merge.pr : null;
292
+ // The merge record names its pull request in `source` (../lifecycle/merge-record.ts).
293
+ const mergePr = mergeRecordPr(merge);
244
294
  const subject = d?.subject ?? "";
245
295
  return { commit: sha, subject, date: d?.date ?? "", work, records, pr: mergePr ?? prFromSubject(subject), merge };
246
296
  };
@@ -265,13 +315,15 @@ export async function releaseCandidatesDocument(query: ReleaseCandidatesQuery):
265
315
  }
266
316
  if (!tags.green.has(sha)) continue;
267
317
  const own = change(sha);
318
+ const changes = open.map(change).reverse();
268
319
  candidates.push({
269
320
  commit: sha,
270
321
  subject: own.subject,
271
322
  date: own.date,
272
323
  newFor: environments.filter((e) => i < newestRun.get(e.env)!).map((e) => e.env),
273
324
  cut: cutLabels.get(sha) ?? [],
274
- changes: open.map(change).reverse(),
325
+ changes,
326
+ groups: groupChanges(changes),
275
327
  flags: flagReader(open),
276
328
  });
277
329
  open = [];
@@ -363,6 +363,36 @@
363
363
  "type": "string"
364
364
  }
365
365
  }
366
+ },
367
+ "composed": {
368
+ "type": "object",
369
+ "required": [
370
+ "branch",
371
+ "base",
372
+ "sources"
373
+ ],
374
+ "properties": {
375
+ "branch": {
376
+ "type": [
377
+ "string",
378
+ "null"
379
+ ]
380
+ },
381
+ "base": {
382
+ "type": "string"
383
+ },
384
+ "sources": {
385
+ "type": "array",
386
+ "items": {
387
+ "type": "string"
388
+ }
389
+ }
390
+ },
391
+ "description": "Added by #4168 4.3, written by chant 0.124.35 and newer. On a cut of a composed commit only: its chant/compose/<name> branch, the trunk commit it was composed on, and the trunk commits picked onto that base, oldest first."
392
+ },
393
+ "channel": {
394
+ "type": "string",
395
+ "description": "Added by #4168 4.3, written by chant 0.124.35 and newer. The tag release publish moves in place of latest (an npm dist-tag, an image tag), from release cut --channel. Absent for latest."
366
396
  }
367
397
  }
368
398
  },