@vzn/vx 0.0.450 → 0.0.452
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 +5 -5
- package/src/cli/watch.ts +31 -20
- package/src/cli/why.ts +10 -7
- package/src/orchestrator/metrics.ts +10 -5
- package/src/util/index.ts +1 -0
- package/src/util/secret-mask.ts +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vzn/vx",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.452",
|
|
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.452",
|
|
25
|
+
"@vzn/vx-linux-arm64": "0.0.452",
|
|
26
|
+
"@vzn/vx-darwin-x64": "0.0.452",
|
|
27
|
+
"@vzn/vx-darwin-arm64": "0.0.452"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
30
|
"@anthropic-ai/sandbox-runtime": "^0.0.76"
|
package/src/cli/watch.ts
CHANGED
|
@@ -530,7 +530,7 @@ async function runWatchLoop(args: WatchLoopArgs): Promise<number> {
|
|
|
530
530
|
if (!packageDirs.has(dir)) want.add(dir)
|
|
531
531
|
}
|
|
532
532
|
for (const [dir, handle] of pending) {
|
|
533
|
-
if (want.has(dir)) continue
|
|
533
|
+
if (want.has(dir) && !stale(dir)) continue
|
|
534
534
|
handle.close()
|
|
535
535
|
pending.delete(dir)
|
|
536
536
|
}
|
|
@@ -548,6 +548,7 @@ async function runWatchLoop(args: WatchLoopArgs): Promise<number> {
|
|
|
548
548
|
if (filename === 'package.json') arrived()
|
|
549
549
|
}),
|
|
550
550
|
)
|
|
551
|
+
armedAs.set(dir, inodeOf(dir) ?? '')
|
|
551
552
|
} catch (err) {
|
|
552
553
|
sayCannot(`cannot watch ${dir}`, err)
|
|
553
554
|
continue
|
|
@@ -578,12 +579,14 @@ async function runWatchLoop(args: WatchLoopArgs): Promise<number> {
|
|
|
578
579
|
}
|
|
579
580
|
importNames = want
|
|
580
581
|
for (const [dir, handle] of importArms) {
|
|
581
|
-
if (want.has(dir)) continue
|
|
582
|
+
if (want.has(dir) && !stale(dir)) continue
|
|
582
583
|
handle.close()
|
|
583
584
|
importArms.delete(dir)
|
|
584
585
|
}
|
|
585
586
|
for (const dir of want.keys()) {
|
|
586
587
|
if (importArms.has(dir)) continue
|
|
588
|
+
// Gone for now: its ancestor's arm (`armAncestors`) hears it return.
|
|
589
|
+
if (inodeOf(dir) === undefined) continue
|
|
587
590
|
try {
|
|
588
591
|
importArms.set(
|
|
589
592
|
dir,
|
|
@@ -605,10 +608,12 @@ async function runWatchLoop(args: WatchLoopArgs): Promise<number> {
|
|
|
605
608
|
)
|
|
606
609
|
}),
|
|
607
610
|
)
|
|
611
|
+
armedAs.set(dir, inodeOf(dir) ?? '')
|
|
608
612
|
} catch (err) {
|
|
609
613
|
sayCannot(`cannot watch ${dir}`, err)
|
|
610
614
|
}
|
|
611
615
|
}
|
|
616
|
+
armAncestors()
|
|
612
617
|
}
|
|
613
618
|
|
|
614
619
|
const watchingLine = (count: number): string =>
|
|
@@ -676,34 +681,35 @@ async function runWatchLoop(args: WatchLoopArgs): Promise<number> {
|
|
|
676
681
|
// A base removed and made again is no event to its own watch (it holds
|
|
677
682
|
// the deleted directory) nor to the root arm, which drops every name but
|
|
678
683
|
// its own files: `watching 0 project(s)` and silence until a restart. So
|
|
679
|
-
// each base
|
|
680
|
-
// next name on the way down:
|
|
681
|
-
// when the parent went too
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
684
|
+
// each base, and each directory a config imports from, is watched from
|
|
685
|
+
// its nearest directory that exists, for the next name on the way down:
|
|
686
|
+
// its parent while it is there, an ancestor when the parent went too
|
|
687
|
+
// (`apps/` removed under `apps/web/*`).
|
|
688
|
+
const ancestorArms = new Map<string, WatchHandle>()
|
|
689
|
+
let ancestorNames = new Map<string, Set<string>>()
|
|
690
|
+
const armAncestors = (): void => {
|
|
691
|
+
ancestorNames = new Map()
|
|
692
|
+
for (const base of [...memberBases, ...importNames.keys()]) {
|
|
687
693
|
let dir = path.dirname(base)
|
|
688
694
|
let name = path.basename(base)
|
|
689
695
|
while (inodeOf(dir) === undefined && dir !== workspaceRoot && dir !== path.dirname(dir)) {
|
|
690
696
|
name = path.basename(dir)
|
|
691
697
|
dir = path.dirname(dir)
|
|
692
698
|
}
|
|
693
|
-
|
|
699
|
+
ancestorNames.set(dir, (ancestorNames.get(dir) ?? new Set()).add(name))
|
|
694
700
|
}
|
|
695
|
-
for (const [dir, handle] of
|
|
696
|
-
if (
|
|
701
|
+
for (const [dir, handle] of ancestorArms) {
|
|
702
|
+
if (ancestorNames.has(dir) && !stale(dir)) continue
|
|
697
703
|
handle.close()
|
|
698
|
-
|
|
704
|
+
ancestorArms.delete(dir)
|
|
699
705
|
}
|
|
700
|
-
for (const dir of
|
|
701
|
-
if (
|
|
706
|
+
for (const dir of ancestorNames.keys()) {
|
|
707
|
+
if (ancestorArms.has(dir)) continue
|
|
702
708
|
try {
|
|
703
|
-
|
|
709
|
+
ancestorArms.set(
|
|
704
710
|
dir,
|
|
705
711
|
arm(dir, false, (filename) => {
|
|
706
|
-
if (!
|
|
712
|
+
if (!ancestorNames.get(dir)?.has(filename)) return
|
|
707
713
|
reread = true
|
|
708
714
|
const abs = path.join(dir, filename)
|
|
709
715
|
trigger(path.relative(workspaceRoot, abs), abs)
|
|
@@ -716,7 +722,7 @@ async function runWatchLoop(args: WatchLoopArgs): Promise<number> {
|
|
|
716
722
|
}
|
|
717
723
|
}
|
|
718
724
|
const armBases = (): void => {
|
|
719
|
-
|
|
725
|
+
armAncestors()
|
|
720
726
|
const want = new Set(memberBases)
|
|
721
727
|
for (const [base, handle] of baseArms) {
|
|
722
728
|
if (want.has(base) && !stale(base)) continue
|
|
@@ -740,7 +746,12 @@ async function runWatchLoop(args: WatchLoopArgs): Promise<number> {
|
|
|
740
746
|
// and cost an uncached task one execution per cycle (CI,
|
|
741
747
|
// 2026-09-10).
|
|
742
748
|
const now = memberEntries(base)
|
|
743
|
-
|
|
749
|
+
// The same names, but a directory still without a package made
|
|
750
|
+
// again (or replaced by a rename): its pending watch holds the
|
|
751
|
+
// deleted one, and the `package.json` that lands next is heard
|
|
752
|
+
// only by a new arm.
|
|
753
|
+
const entry = path.join(base, filename)
|
|
754
|
+
if (sameMembers(members, now) && !(pending.has(entry) && stale(entry))) return
|
|
744
755
|
members = now
|
|
745
756
|
reread = true
|
|
746
757
|
trigger(`${path.relative(workspaceRoot, base)}/${filename}`, path.join(base, filename))
|
package/src/cli/why.ts
CHANGED
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
whyDidThisRerunQuery as whyDidThisRerun,
|
|
17
17
|
resolveRunId,
|
|
18
18
|
} from '../orchestrator/index.js'
|
|
19
|
-
import { nearMatches, printable, UserError } from '../util/index.js'
|
|
19
|
+
import { MASKED, nearMatches, printable, UserError } from '../util/index.js'
|
|
20
20
|
import { findWorkspaceRoot } from '../workspace/index.js'
|
|
21
21
|
import { cliCacheDir, parseCacheDirFlag, warnToStderr } from './workspace-config.js'
|
|
22
22
|
|
|
@@ -245,14 +245,17 @@ export async function whyCmd(args: readonly string[]): Promise<number> {
|
|
|
245
245
|
)
|
|
246
246
|
const kindW = Math.max(...diff.entries.map((e) => e.kind.length), 4)
|
|
247
247
|
for (const e of diff.entries) {
|
|
248
|
+
// A secret-named env input shows its name and its change, no hash.
|
|
248
249
|
const beforeAfter =
|
|
249
|
-
e.
|
|
250
|
-
?
|
|
251
|
-
: e.change === '
|
|
252
|
-
?
|
|
253
|
-
:
|
|
250
|
+
e.before === MASKED || e.after === MASKED
|
|
251
|
+
? ''
|
|
252
|
+
: e.change === 'added'
|
|
253
|
+
? `+ ${e.after}`
|
|
254
|
+
: e.change === 'removed'
|
|
255
|
+
? `- ${e.before}`
|
|
256
|
+
: `${e.before} → ${e.after}`
|
|
254
257
|
lines.push(
|
|
255
|
-
` ${e.change.padEnd(7)} ${e.kind.padEnd(kindW)} ${printable(e.name)} ${beforeAfter}
|
|
258
|
+
` ${e.change.padEnd(7)} ${e.kind.padEnd(kindW)} ${printable(e.name)} ${beforeAfter}`.trimEnd(),
|
|
256
259
|
)
|
|
257
260
|
}
|
|
258
261
|
const kinds = [...new Set(diff.entries.map((e) => e.kind))].filter((k) => k in WHAT_TO_DO)
|
|
@@ -14,7 +14,7 @@ import type { Database } from 'bun:sqlite'
|
|
|
14
14
|
// rule written once cannot drift.
|
|
15
15
|
import { KEYED_RUNS_SQL } from '../cache/index.js'
|
|
16
16
|
import { splitTaskId } from '../graph/index.js'
|
|
17
|
-
import { clampInt } from '../util/index.js'
|
|
17
|
+
import { clampInt, MASKED, secretNamed } from '../util/index.js'
|
|
18
18
|
import type { TaskInputComponent } from './task-hash.js'
|
|
19
19
|
import { isPassStatus } from './telemetry.js'
|
|
20
20
|
|
|
@@ -557,6 +557,11 @@ export function diffKeyComponents(
|
|
|
557
557
|
const old = new Map(before.map((c) => [id(c), c]))
|
|
558
558
|
const entries: InputDiffEntry[] = []
|
|
559
559
|
let unchangedCount = 0
|
|
560
|
+
// A secret-named env input's hash is its value, unsalted: a short one is
|
|
561
|
+
// recoverable from a log that printed `vx why` (M-62). The diff says it
|
|
562
|
+
// changed and never shows the hash.
|
|
563
|
+
const shown = (c: TaskInputComponent): string =>
|
|
564
|
+
c.kind === 'env' && secretNamed(c.name) ? MASKED : c.hash
|
|
560
565
|
const keys = new Set<string>([...cur.keys(), ...old.keys()])
|
|
561
566
|
for (const key of keys) {
|
|
562
567
|
const a = cur.get(key)
|
|
@@ -568,13 +573,13 @@ export function diffKeyComponents(
|
|
|
568
573
|
kind: a.kind,
|
|
569
574
|
name: a.name,
|
|
570
575
|
change: 'changed',
|
|
571
|
-
before: b
|
|
572
|
-
after: a
|
|
576
|
+
before: shown(b),
|
|
577
|
+
after: shown(a),
|
|
573
578
|
})
|
|
574
579
|
} else if (a) {
|
|
575
|
-
entries.push({ kind: a.kind, name: a.name, change: 'added', before: null, after: a
|
|
580
|
+
entries.push({ kind: a.kind, name: a.name, change: 'added', before: null, after: shown(a) })
|
|
576
581
|
} else if (b) {
|
|
577
|
-
entries.push({ kind: b.kind, name: b.name, change: 'removed', before: b
|
|
582
|
+
entries.push({ kind: b.kind, name: b.name, change: 'removed', before: shown(b), after: null })
|
|
578
583
|
}
|
|
579
584
|
}
|
|
580
585
|
// Code-unit order, not `localeCompare`: collation follows the machine's
|
package/src/util/index.ts
CHANGED
package/src/util/secret-mask.ts
CHANGED
|
@@ -23,7 +23,7 @@ const NOT_SECRET = /_(FILE|PATH|DIR)$|^GIT_CONFIG_KEY_\d+$/i
|
|
|
23
23
|
*/
|
|
24
24
|
const secretNames = new Map<string, boolean>()
|
|
25
25
|
|
|
26
|
-
function secretNamed(name: string): boolean {
|
|
26
|
+
export function secretNamed(name: string): boolean {
|
|
27
27
|
let secret = secretNames.get(name)
|
|
28
28
|
if (secret === undefined) {
|
|
29
29
|
secret = SECRET_NAME.test(name) && !NOT_SECRET.test(name)
|