@vzn/vx-ci 0.0.650 → 0.0.652

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.
package/README.md CHANGED
@@ -25,7 +25,8 @@ That's the whole setup. On a GitHub Actions runner (`GITHUB_STEP_SUMMARY`
25
25
  set) every `vx run` appends a summary block, on its own line after
26
26
  anything already written in the step: verdict headline, stats
27
27
  (tasks / executed / cache hits / duration, and the ideal time an executor says
28
- the run could have taken, as `@vzn/vx-dte` replays it), failures called out above the
28
+ the run could have taken, as `@vzn/vx-dte` replays it), the workers
29
+ that would have run it best (`@vzn/vx-dte`'s "Workers to declare"), failures called out above the
29
30
  per-task table (rows by status, failures first, then the longest first) with their exit code, the signal an exit above 128
30
31
  stands for (`exit 137 (128 + SIGKILL)`, as the run's own frame and
31
32
  `vx last` say it), a timeout as `timed out, exit 143`, a persistent task
@@ -111,8 +112,12 @@ variables, as `github()` does on Actions. The default branch
111
112
  subgroup's project is `group/sub/app:feat-x`, the scope a laptop on
112
113
  `feat-x` derives. A merge request pipeline names its source branch
113
114
  (`CI_MERGE_REQUEST_SOURCE_BRANCH_NAME`) in the project it lives in (a
114
- fork's `CI_MERGE_REQUEST_SOURCE_PROJECT_PATH`) and its diff base
115
- (`CI_MERGE_REQUEST_DIFF_BASE_SHA`), the base of a bare `--affected`.
115
+ fork's `CI_MERGE_REQUEST_SOURCE_PROJECT_PATH`) and the base of a bare
116
+ `--affected`: its diff base (`CI_MERGE_REQUEST_DIFF_BASE_SHA`); in a
117
+ merged results pipeline the target head it merged onto
118
+ (`CI_MERGE_REQUEST_TARGET_BRANCH_SHA`), so changes the target took
119
+ since are not run again; in a merge train the car ahead (`HEAD^1`),
120
+ whose own pipeline runs its changes.
116
121
  A merge train's pipeline (`CI_MERGE_REQUEST_EVENT_TYPE=merge_train`)
117
122
  builds what lands on the target, so it is trusted: no scope. As on
118
123
  Actions, the scope keys only plugin entries. A remote reads the tag, else
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vzn/vx-ci",
3
- "version": "0.0.650",
3
+ "version": "0.0.652",
4
4
  "license": "MIT",
5
5
  "description": "GitHub Actions and GitLab CI integration for vx — a job summary on Actions and the branch cache scope on both.",
6
6
  "keywords": [
@@ -33,7 +33,7 @@
33
33
  "access": "public"
34
34
  },
35
35
  "peerDependencies": {
36
- "@vzn/vx": "^0.0.650"
36
+ "@vzn/vx": "^0.0.652"
37
37
  },
38
38
  "repository": {
39
39
  "type": "git",
package/src/gitlab.ts CHANGED
@@ -10,7 +10,8 @@ export interface GitlabPluginOptions {
10
10
  * Name the run's cache scope and base commit from GitLab's predefined
11
11
  * variables: none on the default branch or a merge train, else
12
12
  * `<project path>:<branch>`; a merge request is compared with its diff
13
- * base. The scope keys plugin entries only. `VX_CACHE_SCOPE` beats it.
13
+ * base, a merged results pipeline with the target head, a train car
14
+ * with the car ahead. The scope keys plugin entries only. `VX_CACHE_SCOPE` beats it.
14
15
  * Default: on; `false` opts out.
15
16
  */
16
17
  cacheScope?: boolean
@@ -41,7 +42,7 @@ export function gitlab(options: GitlabPluginOptions = {}): VxPlugin | undefined
41
42
  /**
42
43
  * What a GitLab CI job knows of its branch. A merge request pipeline names
43
44
  * its source branch in the project it lives in (a fork's own,
44
- * `CI_MERGE_REQUEST_SOURCE_PROJECT_PATH`) and its diff base; a merge
45
+ * `CI_MERGE_REQUEST_SOURCE_PROJECT_PATH`) and its base (`baseOf`); a merge
45
46
  * train's pipeline builds what lands on the target, so it is trusted; a
46
47
  * branch or tag pipeline names its ref, none on the default branch.
47
48
  * `refs`, what a remote reads (2026-10-25): a merge train's only its
@@ -80,8 +81,24 @@ function gitlabBranch(
80
81
  return refs.length === 0 ? facts : { ...facts, refs }
81
82
  }
82
83
 
84
+ /**
85
+ * The commit a bare `--affected` compares with. A merged results pipeline
86
+ * runs the merge onto the target's head, so it is compared with that head:
87
+ * the diff base took in every change the target got since the branch
88
+ * forked. A train's car is merged onto the car ahead (its first parent,
89
+ * GitLab's `first_parent_ref`), whose pipeline runs that car's changes.
90
+ * Any other merge request pipeline runs its source branch: the diff base.
91
+ */
92
+ function baseOf(value: (key: string) => string | undefined): string | undefined {
93
+ const event = value('CI_MERGE_REQUEST_EVENT_TYPE')
94
+ if (event === 'merge_train') return 'HEAD^1'
95
+ const diffBase = value('CI_MERGE_REQUEST_DIFF_BASE_SHA')
96
+ if (event === 'merged_result') return value('CI_MERGE_REQUEST_TARGET_BRANCH_SHA') ?? diffBase
97
+ return diffBase
98
+ }
99
+
83
100
  function scopeOf(value: (key: string) => string | undefined): BranchFacts {
84
- const base = value('CI_MERGE_REQUEST_DIFF_BASE_SHA')
101
+ const base = baseOf(value)
85
102
  const withBase = (facts: BranchFacts): BranchFacts =>
86
103
  base === undefined ? facts : { ...facts, base }
87
104
  if (value('CI_MERGE_REQUEST_EVENT_TYPE') === 'merge_train') return withBase({ trusted: true })
package/src/summary.ts CHANGED
@@ -198,6 +198,9 @@ export function renderJobSummary(summary: RunSummaryRecord, title = 'vx run'): s
198
198
  if (summary.machines !== undefined && summary.machines.length > 0) {
199
199
  lines.push(...renderWorkers(summary.machines, summary.tasks), '')
200
200
  }
201
+ if (summary.advice !== undefined && summary.advice.length > 0) {
202
+ lines.push(...renderAdvice(summary.advice), '')
203
+ }
201
204
 
202
205
  const header = ['Task', 'Status', 'Duration']
203
206
  lines.push(`| ${header.join(' | ')} |`)
@@ -237,6 +240,36 @@ const tasksCell = (ok: number, failed: number): string =>
237
240
 
238
241
  const pct = (x: number): string => `${Math.round(x * 100)}%`
239
242
 
243
+ /** What each `WorkerAdvice` played. */
244
+ const ADVICE = {
245
+ declared: 'The fewest of the declared sizes',
246
+ more: "Past the sizes' max",
247
+ any: 'Any cores, least core time',
248
+ } as const
249
+
250
+ /**
251
+ * The workers that would have run it best (2026-11-05: "So I can add more
252
+ * or change their specs"): played again from what each task took.
253
+ */
254
+ function renderAdvice(advice: NonNullable<RunSummaryRecord['advice']>): string[] {
255
+ const lines = [
256
+ '### Workers to declare',
257
+ '',
258
+ '| Played on | Workers | Ends at |',
259
+ '| --- | --- | --- |',
260
+ ]
261
+ for (const a of advice)
262
+ lines.push(
263
+ `| ${ADVICE[a.of]} | ${a.workers
264
+ .map(
265
+ (w) =>
266
+ `${w.count} × ${w.size === undefined ? '' : `${escapeMarkdownCell(escapeInline(w.size))} `}(${w.cores} cores, ${gb(w.memoryBytes)} GB${w.platform === undefined ? '' : `, ${escapeMarkdownCell(escapeInline(w.platform))}`})`,
267
+ )
268
+ .join(' + ')} | ${fmtMs(a.endMs)} |`,
269
+ )
270
+ return lines
271
+ }
272
+
240
273
  /**
241
274
  * The workers an executor ran the run on (vx-dte's): the cores its tasks
242
275
  * booked beside the cores they used, each averaged over the time the