@vzn/vx 0.0.429 → 0.0.431
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.431",
|
|
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.431",
|
|
25
|
+
"@vzn/vx-linux-arm64": "0.0.431",
|
|
26
|
+
"@vzn/vx-darwin-x64": "0.0.431",
|
|
27
|
+
"@vzn/vx-darwin-arm64": "0.0.431"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
30
|
"@anthropic-ai/sandbox-runtime": "^0.0.76"
|
package/src/cli/watch-filter.ts
CHANGED
|
@@ -340,7 +340,8 @@ export function gitFiles(workspaceRoot: string): Set<string> | undefined {
|
|
|
340
340
|
const files = new Set<string>()
|
|
341
341
|
for (const p of new TextDecoder().decode(proc.stdout).split('\0')) {
|
|
342
342
|
if (p.length === 0) continue
|
|
343
|
-
|
|
343
|
+
// An untracked nested repository is listed as `dir/`.
|
|
344
|
+
let abs = path.join(workspaceRoot, p.endsWith('/') ? p.slice(0, -1) : p)
|
|
344
345
|
files.add(abs)
|
|
345
346
|
for (abs = path.dirname(abs); abs !== workspaceRoot && !files.has(abs); abs = path.dirname(abs))
|
|
346
347
|
files.add(abs)
|
package/src/cli/watch-judge.ts
CHANGED
|
@@ -111,8 +111,8 @@ export class ChangeJudge {
|
|
|
111
111
|
// says which side of the arm it belongs to. A path already gone is a
|
|
112
112
|
// change: a deletion has no date to read.
|
|
113
113
|
// One exception: a path git did not list at the arm, and gone now, was
|
|
114
|
-
// born and removed since (vim's `4913` write probe, a tool's lock file)
|
|
115
|
-
// it started a cycle with nothing changed. The blind spot: one born in
|
|
114
|
+
// born and removed since (vim's `4913` write probe, a tool's lock file),
|
|
115
|
+
// unless a nested repository holds it (`inNestedRepo`); it started a cycle with nothing changed. The blind spot: one born in
|
|
116
116
|
// the moment between a judgement and that cycle's keys, and gone by the
|
|
117
117
|
// next judgement, was read and its deletion re-runs nothing. Git lists
|
|
118
118
|
// no ignored path, so one of those is a deletion as before.
|
|
@@ -123,11 +123,21 @@ export class ChangeJudge {
|
|
|
123
123
|
if (prev !== undefined) return prev === state
|
|
124
124
|
if (state === ABSENT) {
|
|
125
125
|
const listed = this.ctx.existedAtArm
|
|
126
|
-
return !ignored && listed !== undefined && !listed.has(abs)
|
|
126
|
+
return !ignored && listed !== undefined && !listed.has(abs) && !this.inNestedRepo(abs)
|
|
127
127
|
}
|
|
128
128
|
return modifiedBefore(abs, this.ctx.armedAt)
|
|
129
129
|
}
|
|
130
130
|
|
|
131
|
+
// Git lists a nested repository (a submodule, a vendored clone) as one
|
|
132
|
+
// entry, never the files in it, and keys read them: under one, a gone
|
|
133
|
+
// path the arm did not list may have existed, so it is a deletion.
|
|
134
|
+
private inNestedRepo(abs: string): boolean {
|
|
135
|
+
const root = this.ctx.workspaceRoot
|
|
136
|
+
for (let dir = path.dirname(abs); dir !== root && dir.startsWith(root); dir = path.dirname(dir))
|
|
137
|
+
if (fs.existsSync(path.join(dir, '.git'))) return true
|
|
138
|
+
return false
|
|
139
|
+
}
|
|
140
|
+
|
|
131
141
|
private writtenDuringLastCycle(abs: string, openWhileHeld = false): boolean {
|
|
132
142
|
// A server the last cycle left running is still that cycle's: its
|
|
133
143
|
// writes land after the cycle ended, and with a closed window a dev
|
package/src/exec/executor.ts
CHANGED
|
@@ -278,9 +278,15 @@ export function selectExecutor(
|
|
|
278
278
|
for (const executor of executors) {
|
|
279
279
|
if (task.pinnedLocal && executor.remote === true) continue
|
|
280
280
|
if (executor.accepts === undefined) return executor
|
|
281
|
-
let taken:
|
|
281
|
+
let taken: unknown
|
|
282
282
|
try {
|
|
283
283
|
taken = executor.accepts(task)
|
|
284
|
+
// An `async accepts` answers a Promise: truthy, so the executor took
|
|
285
|
+
// every task, and a rejection went unheard (the shape of H-16's admit).
|
|
286
|
+
if (taken instanceof Promise) {
|
|
287
|
+
taken.catch(() => {})
|
|
288
|
+
throw new Error('returned a Promise; accepts is synchronous')
|
|
289
|
+
}
|
|
284
290
|
} catch (err) {
|
|
285
291
|
const m = err instanceof Error ? err.message : String(err)
|
|
286
292
|
throw new UserError(`${label(executor)} failed in accepts for ${task.taskId}: ${m}`)
|
package/src/orchestrator/run.ts
CHANGED
|
@@ -500,13 +500,20 @@ async function runOnBus(
|
|
|
500
500
|
// it surfaced as a bare stack, or mid-run from the completion path
|
|
501
501
|
// (item 1022).
|
|
502
502
|
const tellDemand = (executor: TaskExecutor, remaining: ReadonlySet<string>): void => {
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
} catch (err) {
|
|
506
|
-
demandOf.delete(executor)
|
|
503
|
+
const failed = (err: unknown): void => {
|
|
504
|
+
if (!demandOf.delete(executor)) return
|
|
507
505
|
const m = err instanceof Error ? err.message : String(err)
|
|
508
506
|
log.status(`[vx] ${executorLabel(executor)} failed in demand: ${m}; not asked again this run`)
|
|
509
507
|
}
|
|
508
|
+
try {
|
|
509
|
+
// An `async demand` that rejects did so where no one listened: an
|
|
510
|
+
// unhandled rejection, a stack, the run killed. Its rejection is a
|
|
511
|
+
// throw like any other.
|
|
512
|
+
const ret: unknown = executor.demand!(remaining)
|
|
513
|
+
if (ret instanceof Promise) ret.catch(failed)
|
|
514
|
+
} catch (err) {
|
|
515
|
+
failed(err)
|
|
516
|
+
}
|
|
510
517
|
}
|
|
511
518
|
for (const [executor, remaining] of demandOf) tellDemand(executor, remaining)
|
|
512
519
|
const narrowDemand =
|
|
@@ -388,9 +388,11 @@ export function createTelemetrySource(args: {
|
|
|
388
388
|
// Never silently. The standing rule is that a never-fail path must still
|
|
389
389
|
// WARN — telemetry that vanishes without a word is indistinguishable from
|
|
390
390
|
// telemetry nobody configured.
|
|
391
|
-
// Every hook call site skips a disabled sink first
|
|
392
|
-
//
|
|
391
|
+
// Every hook call site skips a disabled sink first (item 654), but an
|
|
392
|
+
// async hook's rejections land later, several records' worth of them
|
|
393
|
+
// after the first: those are said once too.
|
|
393
394
|
const disable = (sink: TelemetrySink, hook: string, err: unknown): void => {
|
|
395
|
+
if (disabled.has(sink)) return
|
|
394
396
|
disabled.add(sink)
|
|
395
397
|
warn?.(
|
|
396
398
|
`[vx] telemetry sink '${label(sink)}' threw in ${hook}; disabled for this run: ${err instanceof Error ? err.message : String(err)}`,
|
|
@@ -406,7 +408,10 @@ export function createTelemetrySource(args: {
|
|
|
406
408
|
const kinds = sink.wants ?? DEFAULT_KINDS
|
|
407
409
|
if (!kinds.includes(record.kind)) continue
|
|
408
410
|
try {
|
|
409
|
-
|
|
411
|
+
// Typed `void`, yet an `async onRecord` rejects where no one
|
|
412
|
+
// listened: an unhandled rejection killed the run (C-101).
|
|
413
|
+
const ret: unknown = sink.onRecord(record)
|
|
414
|
+
if (ret instanceof Promise) ret.catch((err) => disable(sink, 'onRecord', err))
|
|
410
415
|
} catch (err) {
|
|
411
416
|
disable(sink, 'onRecord', err)
|
|
412
417
|
}
|
|
@@ -483,7 +488,8 @@ export function createTelemetrySource(args: {
|
|
|
483
488
|
for (const sink of sinks) {
|
|
484
489
|
if (disabled.has(sink) || sink.onRunSummary === undefined) continue
|
|
485
490
|
try {
|
|
486
|
-
sink.onRunSummary(summary)
|
|
491
|
+
const ret: unknown = sink.onRunSummary(summary)
|
|
492
|
+
if (ret instanceof Promise) ret.catch((err) => disable(sink, 'onRunSummary', err))
|
|
487
493
|
} catch (err) {
|
|
488
494
|
disable(sink, 'onRunSummary', err)
|
|
489
495
|
}
|
|
@@ -504,7 +504,10 @@ export async function loadProjectConfigs(
|
|
|
504
504
|
? []
|
|
505
505
|
: [
|
|
506
506
|
...restoreBuiltins(builtins, overlapping),
|
|
507
|
-
|
|
507
|
+
// Overlapping, the env is checked once at the round's end, as most
|
|
508
|
+
// built-ins are: reading all of it after every load was 15–25 µs a
|
|
509
|
+
// config (149 variables, compiled, 2026-10-03).
|
|
510
|
+
...(overlapping ? [] : restoreEnv(env)),
|
|
508
511
|
...restoreCwd(cwd),
|
|
509
512
|
// Alone in the round: nothing else reads the umask now. Beside
|
|
510
513
|
// other loads the round reads it once they are done (see
|
|
@@ -591,7 +594,7 @@ export async function loadProjectConfigs(
|
|
|
591
594
|
...restoreUmask(umask),
|
|
592
595
|
]
|
|
593
596
|
: []
|
|
594
|
-
// Overlapping loads check most built-ins only here, so a change one
|
|
597
|
+
// Overlapping loads check most built-ins and the env only here, so a change one
|
|
595
598
|
// config made may have broken another's load: it is refused first, and
|
|
596
599
|
// nothing the round evaluated is stored.
|
|
597
600
|
const changedInRound =
|