@vzn/vx 0.0.65 → 0.0.67
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.67",
|
|
4
4
|
"description": "An open, extensible monorepo task runner.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -17,10 +17,10 @@
|
|
|
17
17
|
"node": ">=18"
|
|
18
18
|
},
|
|
19
19
|
"optionalDependencies": {
|
|
20
|
-
"@vzn/vx-linux-x64": "0.0.
|
|
21
|
-
"@vzn/vx-linux-arm64": "0.0.
|
|
22
|
-
"@vzn/vx-darwin-x64": "0.0.
|
|
23
|
-
"@vzn/vx-darwin-arm64": "0.0.
|
|
20
|
+
"@vzn/vx-linux-x64": "0.0.67",
|
|
21
|
+
"@vzn/vx-linux-arm64": "0.0.67",
|
|
22
|
+
"@vzn/vx-darwin-x64": "0.0.67",
|
|
23
|
+
"@vzn/vx-darwin-arm64": "0.0.67"
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
26
|
"@anthropic-ai/sandbox-runtime": "^0.0.76"
|
package/src/cli/watch.ts
CHANGED
|
@@ -292,7 +292,13 @@ export function pollWatcher(
|
|
|
292
292
|
}
|
|
293
293
|
if (!e.isFile()) continue
|
|
294
294
|
try {
|
|
295
|
-
|
|
295
|
+
// The later of the two clocks, as `modifiedBefore` reads them: a
|
|
296
|
+
// replacement that carries the old file's mtime (`cp -p`, `rsync
|
|
297
|
+
// -a`, `mv` of a file stamped the same) moved nothing under mtime
|
|
298
|
+
// alone, and the poller never ran it where the native watcher did.
|
|
299
|
+
// A rename or a write moves ctime, and no process can set it.
|
|
300
|
+
const st = fs.statSync(path.join(abs, e.name))
|
|
301
|
+
current.set(childRel, Math.max(st.mtimeMs, st.ctimeMs))
|
|
296
302
|
} catch {
|
|
297
303
|
// raced with a delete; the next scan settles it
|
|
298
304
|
}
|
package/src/graph/scheduler.ts
CHANGED
|
@@ -76,6 +76,13 @@ export interface TaskOutcome {
|
|
|
76
76
|
* outcomes only; never folded into any key.
|
|
77
77
|
*/
|
|
78
78
|
groupUpstream?: readonly TaskOutcome[]
|
|
79
|
+
/**
|
|
80
|
+
* This task RAN over inputs its key no longer describes: one moved while
|
|
81
|
+
* it ran, the workspace fingerprint moved, or an upstream carried this
|
|
82
|
+
* mark. Its save was withheld, and its outputs are not what `hash` names,
|
|
83
|
+
* so a dependant that folds `hash` saves nothing either (A-12).
|
|
84
|
+
*/
|
|
85
|
+
unkeyed?: true
|
|
79
86
|
/**
|
|
80
87
|
* On a `skipped` outcome: the task at the ROOT of what blocked it — the
|
|
81
88
|
* failed (or aborted) upstream, followed through any chain of skips
|
|
@@ -68,7 +68,10 @@ export function createEventBus(): EventBus {
|
|
|
68
68
|
// as wrong output, not a silent hang.
|
|
69
69
|
for (const subscriber of subscribers) {
|
|
70
70
|
try {
|
|
71
|
-
subscriber
|
|
71
|
+
// A subscriber typed `void` may still be async: its rejection is
|
|
72
|
+
// isolated as its throw is.
|
|
73
|
+
const ret: unknown = subscriber(event)
|
|
74
|
+
if (ret instanceof Promise) ret.catch(() => {})
|
|
72
75
|
} catch {
|
|
73
76
|
// isolate: a surface fault can't propagate into the orchestrator
|
|
74
77
|
}
|
|
@@ -62,6 +62,7 @@ import {
|
|
|
62
62
|
type TaskInputComponent,
|
|
63
63
|
} from './task-hash.js'
|
|
64
64
|
import { getContext } from './remote-prefetch.js'
|
|
65
|
+
import { expandGroupUpstream, filterUpstreamHashes } from './upstream.js'
|
|
65
66
|
|
|
66
67
|
export interface ExecuteArgs {
|
|
67
68
|
node: TaskNode
|
|
@@ -412,8 +413,27 @@ async function executeCachedTask(args: ExecuteArgs): Promise<TaskOutcome> {
|
|
|
412
413
|
const willRead = !remoteOnly && cfgCacheable && (policy.localRead || policy.remoteRead)
|
|
413
414
|
const willWrite = !remoteOnly && cfgCacheable && (policy.localWrite || policy.remoteWrite)
|
|
414
415
|
// `willWrite` governs output hygiene (clean before exec); the save itself
|
|
415
|
-
// is also withheld from a task downstream of a failure (see `taintedUpstream`)
|
|
416
|
-
|
|
416
|
+
// is also withheld from a task downstream of a failure (see `taintedUpstream`),
|
|
417
|
+
// and from one downstream of a task that ran over inputs its key no longer
|
|
418
|
+
// describes: this key folds that upstream's key, while the bytes on disk
|
|
419
|
+
// are not the ones that key names. With `a.txt` edited while `gen` ran,
|
|
420
|
+
// `gen` withheld its save, `use` saved over the edit under a key folding
|
|
421
|
+
// `gen`'s, and once `a.txt` was put back `use` hit the edit's output (A-12).
|
|
422
|
+
// Only an upstream whose key this one folds: one read by content
|
|
423
|
+
// (`tasks: []` and a file input) is judged by its bytes. `upstream` holds
|
|
424
|
+
// a hole where a dependency did not run (a skipped one, a restore-tier
|
|
425
|
+
// task's unfinished deps).
|
|
426
|
+
const present = upstream.filter((u): u is TaskOutcome => u !== undefined)
|
|
427
|
+
const folded = new Set(
|
|
428
|
+
filterUpstreamHashes(present, cacheCfg?.inputs?.tasks, node.projectName, node.id).map(
|
|
429
|
+
([id]) => id,
|
|
430
|
+
),
|
|
431
|
+
)
|
|
432
|
+
const unkeyedUpstream = expandGroupUpstream(present.filter((u) => folded.has(u.node.id))).find(
|
|
433
|
+
(u) => u.unkeyed === true,
|
|
434
|
+
)
|
|
435
|
+
let unkeyed = unkeyedUpstream !== undefined
|
|
436
|
+
const willSave = willWrite && args.taintedUpstream !== true && !unkeyed
|
|
417
437
|
|
|
418
438
|
// Retain only what is read back. `cache.save` below is the single consumer
|
|
419
439
|
// of `result.stdout`, and it runs only when this task will WRITE an entry;
|
|
@@ -968,7 +988,10 @@ async function executeCachedTask(args: ExecuteArgs): Promise<TaskOutcome> {
|
|
|
968
988
|
* withholds it too: the key folded the old one.
|
|
969
989
|
*/
|
|
970
990
|
async function keyStillTrue(): Promise<boolean> {
|
|
971
|
-
if (fingerprintMoved())
|
|
991
|
+
if (fingerprintMoved()) {
|
|
992
|
+
unkeyed = true
|
|
993
|
+
return false
|
|
994
|
+
}
|
|
972
995
|
const endCheck = span('miss: recheck inputs')
|
|
973
996
|
const moved = await movedSinceKey()
|
|
974
997
|
endCheck()
|
|
@@ -979,9 +1002,17 @@ async function executeCachedTask(args: ExecuteArgs): Promise<TaskOutcome> {
|
|
|
979
1002
|
`but is not saved under a key that no longer describes it`,
|
|
980
1003
|
)
|
|
981
1004
|
forgetUndeclaredWrites(args, wsOutputs.length > 0 ? 'workspace' : 'project')
|
|
1005
|
+
unkeyed = true
|
|
982
1006
|
return false
|
|
983
1007
|
}
|
|
984
1008
|
|
|
1009
|
+
if (unkeyedUpstream !== undefined && willWrite && effectiveExitCode === 0) {
|
|
1010
|
+
log.status(
|
|
1011
|
+
`[vx] ${node.id}: ran over ${unkeyedUpstream.node.id}'s outputs, which its key no longer ` +
|
|
1012
|
+
`describes — the result stands, but is not saved`,
|
|
1013
|
+
)
|
|
1014
|
+
}
|
|
1015
|
+
|
|
985
1016
|
const finalViolations = violations
|
|
986
1017
|
|
|
987
1018
|
return {
|
|
@@ -991,6 +1022,7 @@ async function executeCachedTask(args: ExecuteArgs): Promise<TaskOutcome> {
|
|
|
991
1022
|
durationMs: spentMs,
|
|
992
1023
|
hash,
|
|
993
1024
|
...(attempt > 1 ? { attempts: attempt } : {}),
|
|
1025
|
+
...(unkeyed ? { unkeyed: true as const } : {}),
|
|
994
1026
|
...(result.timedOut === true && effectiveExitCode !== 0 ? { timedOut: true as const } : {}),
|
|
995
1027
|
...(result.cpuMs !== undefined ? { cpuMs: result.cpuMs } : {}),
|
|
996
1028
|
...(result.peakRssBytes !== undefined ? { peakRssBytes: result.peakRssBytes } : {}),
|
|
@@ -507,47 +507,53 @@ export async function installPlugins(args: InstallPluginsArgs): Promise<() => vo
|
|
|
507
507
|
},
|
|
508
508
|
},
|
|
509
509
|
on(hook, handler) {
|
|
510
|
+
const fail = (err: unknown): void => {
|
|
511
|
+
if (disabled.has(plugin.name)) return
|
|
512
|
+
disabled.add(plugin.name)
|
|
513
|
+
warn(
|
|
514
|
+
`[vx] plugin '${plugin.name}' threw in ${hook}; disabled for this run: ${err instanceof Error ? err.message : String(err)}`,
|
|
515
|
+
)
|
|
516
|
+
}
|
|
510
517
|
const dispose = bus.subscribe((event) => {
|
|
511
518
|
if (disabled.has(plugin.name)) return
|
|
512
|
-
//
|
|
513
|
-
//
|
|
514
|
-
|
|
519
|
+
// Not awaited: the bus is synchronous and plugin work runs off the
|
|
520
|
+
// critical path. A handler's rejection is caught like its throw.
|
|
521
|
+
let ret: unknown
|
|
515
522
|
try {
|
|
516
523
|
switch (hook) {
|
|
517
524
|
case 'onRunStart':
|
|
518
525
|
if (event.kind === 'run:start')
|
|
519
|
-
|
|
526
|
+
ret = (handler as PluginHookHandlers['onRunStart'])(event.info)
|
|
520
527
|
break
|
|
521
528
|
case 'onTaskStart':
|
|
522
529
|
if (event.kind === 'task:start')
|
|
523
|
-
|
|
530
|
+
ret = (handler as PluginHookHandlers['onTaskStart'])(event.node)
|
|
524
531
|
break
|
|
525
532
|
case 'onTaskStdout':
|
|
526
533
|
if (event.kind === 'task:stdout')
|
|
527
|
-
|
|
534
|
+
ret = (handler as PluginHookHandlers['onTaskStdout'])(event.node, event.chunk)
|
|
528
535
|
break
|
|
529
536
|
case 'onTaskStderr':
|
|
530
537
|
if (event.kind === 'task:stderr')
|
|
531
|
-
|
|
538
|
+
ret = (handler as PluginHookHandlers['onTaskStderr'])(event.node, event.chunk)
|
|
532
539
|
break
|
|
533
540
|
case 'onTaskComplete':
|
|
534
541
|
if (event.kind === 'task:complete')
|
|
535
|
-
|
|
542
|
+
ret = (handler as PluginHookHandlers['onTaskComplete'])(event.node, event.outcome)
|
|
536
543
|
break
|
|
537
544
|
case 'onRunStatus':
|
|
538
545
|
if (event.kind === 'run:status')
|
|
539
|
-
|
|
546
|
+
ret = (handler as PluginHookHandlers['onRunStatus'])(event.line)
|
|
540
547
|
break
|
|
541
548
|
case 'onRunEnd':
|
|
542
|
-
if (event.kind === 'run:end')
|
|
549
|
+
if (event.kind === 'run:end') ret = (handler as PluginHookHandlers['onRunEnd'])()
|
|
543
550
|
break
|
|
544
551
|
}
|
|
545
552
|
} catch (err) {
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
`[vx] plugin '${plugin.name}' threw in ${hook}; disabled for this run: ${err instanceof Error ? err.message : String(err)}`,
|
|
549
|
-
)
|
|
553
|
+
fail(err)
|
|
554
|
+
return
|
|
550
555
|
}
|
|
556
|
+
if (ret instanceof Promise) ret.catch(fail)
|
|
551
557
|
})
|
|
552
558
|
disposers.push(dispose)
|
|
553
559
|
},
|