@vzn/vx 0.0.548 → 0.0.550
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vzn/vx",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.550",
|
|
4
4
|
"description": "The fastest task runner and build cache for JavaScript monorepos.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -21,10 +21,10 @@
|
|
|
21
21
|
"node": ">=18"
|
|
22
22
|
},
|
|
23
23
|
"optionalDependencies": {
|
|
24
|
-
"@vzn/vx-linux-x64": "0.0.
|
|
25
|
-
"@vzn/vx-linux-arm64": "0.0.
|
|
26
|
-
"@vzn/vx-darwin-x64": "0.0.
|
|
27
|
-
"@vzn/vx-darwin-arm64": "0.0.
|
|
24
|
+
"@vzn/vx-linux-x64": "0.0.550",
|
|
25
|
+
"@vzn/vx-linux-arm64": "0.0.550",
|
|
26
|
+
"@vzn/vx-darwin-x64": "0.0.550",
|
|
27
|
+
"@vzn/vx-darwin-arm64": "0.0.550"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
30
|
"@anthropic-ai/sandbox-runtime": "^0.0.76"
|
|
@@ -23,16 +23,19 @@ import {
|
|
|
23
23
|
computeWorkspaceFingerprint,
|
|
24
24
|
findWorkspaceRoot,
|
|
25
25
|
type LoadReads,
|
|
26
|
-
loadProjectConfig,
|
|
27
26
|
loadWorkspace,
|
|
28
27
|
lockfilePath,
|
|
29
|
-
type ProjectMeta,
|
|
30
28
|
resolveCacheDir,
|
|
31
29
|
resolveStoreRoot,
|
|
32
30
|
} from '../workspace/index.js'
|
|
33
31
|
import { flakyTasks, type FlakyTask } from './failure-mode.js'
|
|
34
32
|
import type { VxPlugin } from './plugin.js'
|
|
35
|
-
import {
|
|
33
|
+
import {
|
|
34
|
+
discoverProjects,
|
|
35
|
+
type LoadProjectsArgs,
|
|
36
|
+
loadProjects,
|
|
37
|
+
loadWorkspacePlugins,
|
|
38
|
+
} from './projects.js'
|
|
36
39
|
|
|
37
40
|
const warnToStderr = (message: string): void => {
|
|
38
41
|
process.stderr.write(`${message}\n`)
|
|
@@ -167,23 +170,23 @@ async function collectWorkspaceInfo(
|
|
|
167
170
|
// The run path's load — a plugin's `project` stage counts — so the
|
|
168
171
|
// doctor's task count is the number a run would see. A broken config
|
|
169
172
|
// must not take the doctor down with it: the count then falls back to
|
|
170
|
-
// the
|
|
171
|
-
|
|
172
|
-
|
|
173
|
+
// the same load, one project at a time, the broken ones as zero.
|
|
174
|
+
const load = {
|
|
175
|
+
workspaceRoot: root,
|
|
176
|
+
cacheDir,
|
|
177
|
+
plugins,
|
|
178
|
+
projectMetas: metas,
|
|
179
|
+
closure: false,
|
|
180
|
+
lock: null,
|
|
181
|
+
evalCache: {
|
|
182
|
+
store: cache,
|
|
173
183
|
workspaceRoot: root,
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
evalCache: {
|
|
181
|
-
store: cache,
|
|
182
|
-
workspaceRoot: root,
|
|
183
|
-
workspaceFingerprint: await computeWorkspaceFingerprint(root, reads),
|
|
184
|
-
},
|
|
185
|
-
warn,
|
|
186
|
-
})
|
|
184
|
+
workspaceFingerprint: await computeWorkspaceFingerprint(root, reads),
|
|
185
|
+
},
|
|
186
|
+
warn,
|
|
187
|
+
} as const
|
|
188
|
+
try {
|
|
189
|
+
const loaded = await loadProjects({ ...load, seeds: 'all' })
|
|
187
190
|
for (const p of loaded.projects.values()) {
|
|
188
191
|
const tasks = p.config.tasks ?? {}
|
|
189
192
|
taskCount += Object.keys(tasks).length
|
|
@@ -197,7 +200,7 @@ async function collectWorkspaceInfo(
|
|
|
197
200
|
tasks: taskCount,
|
|
198
201
|
sandboxed,
|
|
199
202
|
errors: configErrors,
|
|
200
|
-
} = await countLoadableTasks(
|
|
203
|
+
} = await countLoadableTasks(load, root))
|
|
201
204
|
}
|
|
202
205
|
} finally {
|
|
203
206
|
cache.close()
|
|
@@ -362,18 +365,19 @@ function gitVersion(): string | null {
|
|
|
362
365
|
}
|
|
363
366
|
|
|
364
367
|
async function countLoadableTasks(
|
|
365
|
-
|
|
368
|
+
load: Omit<LoadProjectsArgs, 'seeds'> & { closure: false },
|
|
366
369
|
root: string,
|
|
367
370
|
): Promise<{ tasks: number; sandboxed: number; errors: InfoFacts['configErrors'] }> {
|
|
368
371
|
let tasks = 0
|
|
369
372
|
let sandboxed = 0
|
|
370
373
|
const errors: InfoFacts['configErrors'] = []
|
|
374
|
+
// No seeds loads nothing and answers which projects an 'all' load seeds.
|
|
375
|
+
const { configured } = await loadProjects({ ...load, seeds: [] })
|
|
371
376
|
await Promise.all(
|
|
372
|
-
|
|
373
|
-
if (meta.configPath === null) return
|
|
377
|
+
configured.map(async (meta) => {
|
|
374
378
|
try {
|
|
375
|
-
const
|
|
376
|
-
const declared = Object.values(config.tasks ?? {})
|
|
379
|
+
const loaded = await loadProjects({ ...load, seeds: [meta.name] })
|
|
380
|
+
const declared = Object.values(loaded.projects.get(meta.name)?.config.tasks ?? {})
|
|
377
381
|
tasks += declared.length
|
|
378
382
|
for (const t of declared) if (t?.exec?.sandbox !== undefined) sandboxed++
|
|
379
383
|
} catch (err) {
|
|
@@ -385,11 +389,12 @@ async function countLoadableTasks(
|
|
|
385
389
|
// still did).
|
|
386
390
|
const raw = err instanceof Error ? err.message : String(err)
|
|
387
391
|
const bare = raw.startsWith('Project config ') ? raw.slice('Project config '.length) : raw
|
|
388
|
-
const
|
|
389
|
-
|
|
392
|
+
const where = meta.configPath ?? meta.dir
|
|
393
|
+
const message = bare.startsWith(`${where}:`)
|
|
394
|
+
? bare.slice(where.length + 1).trimStart()
|
|
390
395
|
: raw
|
|
391
396
|
errors.push({
|
|
392
|
-
path: path.relative(root,
|
|
397
|
+
path: path.relative(root, where).split(path.sep).join('/'),
|
|
393
398
|
message,
|
|
394
399
|
})
|
|
395
400
|
}
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
// too. `--report-file` is the sink that carries the property end to end.
|
|
11
11
|
|
|
12
12
|
import { failedLabel, skippedLabel, type OutcomeView } from './events.js'
|
|
13
|
+
import { neverStarted } from './summary.js'
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
16
|
* One finished run, reduced to what a report needs. Used to live in
|
|
@@ -35,8 +36,10 @@ import { tallyViews, type Tally } from './tally.js'
|
|
|
35
36
|
* a row claiming `success | miss | 0ms`.
|
|
36
37
|
*/
|
|
37
38
|
interface ReportTally extends Tally {
|
|
38
|
-
/** Wall-clock ms summed over tasks that
|
|
39
|
+
/** Wall-clock ms summed over tasks that ran a command, an aborted one's time so far included. */
|
|
39
40
|
executedMs: number
|
|
41
|
+
/** The aborted tasks the stop reached before they ran, as the terminal splits them. */
|
|
42
|
+
notRun: number
|
|
40
43
|
/**
|
|
41
44
|
* Ms of work the cache SKIPPED — the sum of the hits' STORED exec times.
|
|
42
45
|
* Deliberately not `durationMs`, which for a hit is the restore this run
|
|
@@ -46,7 +49,7 @@ interface ReportTally extends Tally {
|
|
|
46
49
|
}
|
|
47
50
|
|
|
48
51
|
function tally(outcomes: readonly OutcomeView[]): ReportTally {
|
|
49
|
-
const t: ReportTally = { ...tallyViews(outcomes), executedMs: 0, savedMs: 0 }
|
|
52
|
+
const t: ReportTally = { ...tallyViews(outcomes), executedMs: 0, savedMs: 0, notRun: 0 }
|
|
50
53
|
for (const o of outcomes) {
|
|
51
54
|
if (o.isGroup === true) continue
|
|
52
55
|
switch (o.status) {
|
|
@@ -54,6 +57,10 @@ function tally(outcomes: readonly OutcomeView[]): ReportTally {
|
|
|
54
57
|
case 'failed':
|
|
55
58
|
t.executedMs += o.durationMs
|
|
56
59
|
break
|
|
60
|
+
case 'aborted':
|
|
61
|
+
if (neverStarted(o)) t.notRun++
|
|
62
|
+
else t.executedMs += o.durationMs
|
|
63
|
+
break
|
|
57
64
|
case 'cache-hit':
|
|
58
65
|
case 'cache-hit-remote':
|
|
59
66
|
// An outcome that doesn't know what it skipped contributes nothing:
|
|
@@ -78,6 +85,8 @@ function statusWord(o: OutcomeView): string {
|
|
|
78
85
|
return failedLabel(o.exitCode, o.timedOut, o.sandboxViolations, o.notReady)
|
|
79
86
|
case 'skipped':
|
|
80
87
|
return skippedLabel(o.blockedBy)
|
|
88
|
+
case 'aborted':
|
|
89
|
+
return neverStarted(o) ? 'not run' : 'aborted'
|
|
81
90
|
default:
|
|
82
91
|
return o.status
|
|
83
92
|
}
|
|
@@ -145,12 +154,21 @@ export function formatRunReportMarkdown(result: RunResult): string {
|
|
|
145
154
|
: '0 cached',
|
|
146
155
|
]
|
|
147
156
|
if (t.skipped > 0) parts.push(`${t.skipped} skipped`)
|
|
148
|
-
if (t.aborted > 0) parts.push(`${t.aborted} aborted`)
|
|
149
157
|
parts.push(`${fmtDuration(t.executedMs)} total`)
|
|
150
158
|
if (t.savedMs > 0) parts.push(`${fmtDuration(t.savedMs)} saved`)
|
|
159
|
+
// The terminal summary's words: neither is in `total`, and a task the stop
|
|
160
|
+
// reached before it ran was never aborted.
|
|
161
|
+
const aborted = t.aborted - t.notRun
|
|
162
|
+
const uncounted: string[] = []
|
|
163
|
+
if (aborted > 0) uncounted.push(`${aborted} aborted`)
|
|
164
|
+
if (t.notRun > 0) uncounted.push(`${t.notRun} not run`)
|
|
165
|
+
if (uncounted.length > 0) parts.push(`not counted: ${uncounted.join(', ')}`)
|
|
151
166
|
|
|
167
|
+
// Only a stop (a signal, `RunOptions.signal`) makes an `aborted` outcome,
|
|
168
|
+
// so one is how an interrupted run tells itself from a failed one.
|
|
169
|
+
const heading = t.aborted > 0 ? 'interrupted' : result.ok ? 'passed' : 'failed'
|
|
152
170
|
const lines: string[] = []
|
|
153
|
-
lines.push(`## vx run — ${
|
|
171
|
+
lines.push(`## vx run — ${heading}`)
|
|
154
172
|
lines.push('')
|
|
155
173
|
lines.push(parts.join(' · '))
|
|
156
174
|
lines.push('')
|
|
@@ -163,7 +181,7 @@ export function formatRunReportMarkdown(result: RunResult): string {
|
|
|
163
181
|
if (o.isGroup === true) continue
|
|
164
182
|
lines.push(
|
|
165
183
|
`| ${cell(o.taskId)} | ${cell(statusWord(o))} | ${cell(cacheWord(o))} | ${cell(
|
|
166
|
-
fmtDuration(o.durationMs),
|
|
184
|
+
neverStarted(o) ? '—' : fmtDuration(o.durationMs),
|
|
167
185
|
)} |`,
|
|
168
186
|
)
|
|
169
187
|
}
|
|
@@ -381,7 +381,9 @@ export function formatRunSummary(
|
|
|
381
381
|
* it an invented exit 1; only a started task carries a wall-clock start, so
|
|
382
382
|
* that is what tells "never ran" from "killed" (item 1062).
|
|
383
383
|
*/
|
|
384
|
-
export function neverStarted(
|
|
384
|
+
export function neverStarted(
|
|
385
|
+
o: Pick<TaskOutcome, 'status'> & { wallclockStartNs?: unknown },
|
|
386
|
+
): boolean {
|
|
385
387
|
return o.status === 'aborted' && o.wallclockStartNs === undefined
|
|
386
388
|
}
|
|
387
389
|
|
|
@@ -175,7 +175,14 @@ self.onmessage = async (e) => {
|
|
|
175
175
|
fn: typeof mod === 'function',
|
|
176
176
|
changed: report,
|
|
177
177
|
})
|
|
178
|
-
} catch (
|
|
178
|
+
} catch (thrown) {
|
|
179
|
+
// Two syntax errors arrive as one AggregateError, whose own message
|
|
180
|
+
// names no line; the in-process load reports the first (X-6), so the
|
|
181
|
+
// hop does too (X-26).
|
|
182
|
+
const err =
|
|
183
|
+
thrown?.name === 'AggregateError' && Array.isArray(thrown.errors) && thrown.errors.length > 0
|
|
184
|
+
? thrown.errors[0]
|
|
185
|
+
: thrown
|
|
179
186
|
const report = changed()
|
|
180
187
|
umaskBack()
|
|
181
188
|
postMessage({
|