@vzn/vx 0.0.565 → 0.0.567
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-filter.ts +23 -1
- package/src/cli/watch-judge.ts +7 -3
- package/src/cli/watch.ts +29 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vzn/vx",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.567",
|
|
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.567",
|
|
25
|
+
"@vzn/vx-linux-arm64": "0.0.567",
|
|
26
|
+
"@vzn/vx-darwin-x64": "0.0.567",
|
|
27
|
+
"@vzn/vx-darwin-arm64": "0.0.567"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
30
|
"@anthropic-ai/sandbox-runtime": "^0.0.76"
|
package/src/cli/watch-filter.ts
CHANGED
|
@@ -229,6 +229,7 @@ export function makeRootEventFilter(
|
|
|
229
229
|
projectDirs: readonly string[],
|
|
230
230
|
workspaceInputs: readonly string[],
|
|
231
231
|
claimedRootFiles: ReadonlySet<string> = new Set(),
|
|
232
|
+
fenced: (ownDir: string, abs: string) => boolean = () => false,
|
|
232
233
|
): (filename: string) => boolean {
|
|
233
234
|
const dirs = projectDirs.map((d) => path.resolve(d))
|
|
234
235
|
const globs = workspaceInputs
|
|
@@ -257,12 +258,33 @@ export function makeRootEventFilter(
|
|
|
257
258
|
return true
|
|
258
259
|
}
|
|
259
260
|
const abs = path.resolve(workspaceRoot, filename)
|
|
260
|
-
for (const d of dirs)
|
|
261
|
+
for (const d of dirs)
|
|
262
|
+
if (abs === d || (abs.startsWith(d + path.sep) && !fenced(d, abs))) return true
|
|
261
263
|
for (const g of globs) if (g.match(rel)) return true
|
|
262
264
|
return false
|
|
263
265
|
}
|
|
264
266
|
}
|
|
265
267
|
|
|
268
|
+
/**
|
|
269
|
+
* Whether `abs`, under the project at `ownDir`, lies inside another
|
|
270
|
+
* project nested there: the key's boundary (`computeNestedProjectDirs`)
|
|
271
|
+
* leaves such a file out, so it is no edit of `ownDir`'s. A root project
|
|
272
|
+
* watched its whole tree and ran a cycle for every edit in a nested one
|
|
273
|
+
* (X-42). The fences are the projects with a config, which fence a key
|
|
274
|
+
* whatever the plugins; a config-less one may not, and an edit there
|
|
275
|
+
* still counts. The fence's own config is let through: it coming or going
|
|
276
|
+
* moves the boundary, and the cycle it starts re-reads the set.
|
|
277
|
+
*/
|
|
278
|
+
export function makeFence(fenceDirs: readonly string[]): (ownDir: string, abs: string) => boolean {
|
|
279
|
+
const fences = fenceDirs.map((d) => path.resolve(d))
|
|
280
|
+
return (ownDir, abs) => {
|
|
281
|
+
const config = PROJECT_CONFIGS.has(path.basename(abs)) ? path.dirname(abs) : undefined
|
|
282
|
+
return fences.some(
|
|
283
|
+
(f) => f !== config && f.startsWith(ownDir + path.sep) && abs.startsWith(f + path.sep),
|
|
284
|
+
)
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
266
288
|
/**
|
|
267
289
|
* A file whose edit can change what the loop watches: a `package.json`
|
|
268
290
|
* (a dependency added under `--filter` widens the closure), a project
|
package/src/cli/watch-judge.ts
CHANGED
|
@@ -59,6 +59,8 @@ export interface JudgeContext {
|
|
|
59
59
|
held(): boolean
|
|
60
60
|
/** Projects whose uncached task may read a git-ignored file. */
|
|
61
61
|
uncached(): ReadonlySet<string>
|
|
62
|
+
/** Whether `abs`, under the project at `ownDir`, lies in a project nested there (`makeFence`). */
|
|
63
|
+
fenced?(ownDir: string, abs: string): boolean
|
|
62
64
|
/** The files git listed at the arm; absent when git could not answer. */
|
|
63
65
|
existedAtArm?: ReadonlySet<string>
|
|
64
66
|
}
|
|
@@ -176,10 +178,12 @@ export class ChangeJudge {
|
|
|
176
178
|
// it reads what it likes, and its `.env.local` edit re-ran nothing
|
|
177
179
|
// (item 947). Under such a project an ignored path is judged when the
|
|
178
180
|
// user wrote it; one the last cycle wrote (the pid file an uncached
|
|
179
|
-
// task rewrites every run, the loop the filter exists for) stays out
|
|
181
|
+
// task rewrites every run, the loop the filter exists for) stays out,
|
|
182
|
+
// as does one in a project nested under it, which it does not own.
|
|
180
183
|
const editForUncached = (p: string): boolean =>
|
|
181
|
-
[...this.ctx.uncached()].some(
|
|
182
|
-
|
|
184
|
+
[...this.ctx.uncached()].some(
|
|
185
|
+
(dir) => p.startsWith(dir + path.sep) && this.ctx.fenced?.(dir, p) !== true,
|
|
186
|
+
) && !this.writtenDuringLastCycle(p)
|
|
183
187
|
// A path still there names the cycle before a gone one: an editor or
|
|
184
188
|
// `sed -i` saving through a temporary file fires that file first, and
|
|
185
189
|
// the cycle was announced by a name already renamed away.
|
package/src/cli/watch.ts
CHANGED
|
@@ -37,6 +37,7 @@ import {
|
|
|
37
37
|
isWorkspaceConfigFile,
|
|
38
38
|
gitFiles,
|
|
39
39
|
isWorkspaceFingerprintFile,
|
|
40
|
+
makeFence,
|
|
40
41
|
makeRootEventFilter,
|
|
41
42
|
makeWatchIgnore,
|
|
42
43
|
shapesWatchedSet,
|
|
@@ -242,6 +243,7 @@ export async function watchCmd(args: readonly string[]): Promise<number> {
|
|
|
242
243
|
workspaceConfigImports: swept.workspaceConfigImports,
|
|
243
244
|
memberBases: memberBaseDirs(workspace),
|
|
244
245
|
packageDirs: new Set(allProjects.map((p) => p.dir)),
|
|
246
|
+
fenceDirs: fenceDirs(allProjects),
|
|
245
247
|
// The workspace as the cycle that just ran saw it: a package added or
|
|
246
248
|
// removed since the loop armed joins or leaves the watched set. The
|
|
247
249
|
// scope is the one resolved at start; a new package joins it only as a
|
|
@@ -266,6 +268,7 @@ export async function watchCmd(args: readonly string[]): Promise<number> {
|
|
|
266
268
|
configImports: sweep.configImports,
|
|
267
269
|
workspaceConfigImports: sweep.workspaceConfigImports,
|
|
268
270
|
packageDirs: new Set(all.map((p) => p.dir)),
|
|
271
|
+
fenceDirs: fenceDirs(all),
|
|
269
272
|
}
|
|
270
273
|
},
|
|
271
274
|
// Under --frozen every cycle's configs are the lock's, so a re-lock is
|
|
@@ -312,10 +315,15 @@ interface WatchLoopArgs {
|
|
|
312
315
|
memberBases: readonly string[]
|
|
313
316
|
/** Every package's directory, in scope or not: a member base's other entries are packages still to come. */
|
|
314
317
|
packageDirs: ReadonlySet<string>
|
|
318
|
+
/** Every project with a config, in scope or not: a project's key leaves out what lies in one nested under it. */
|
|
319
|
+
fenceDirs: readonly string[]
|
|
315
320
|
/** The watched set again, after a cycle that followed an event which can change it. */
|
|
316
321
|
rediscover: () => Promise<Rediscovered>
|
|
317
322
|
}
|
|
318
323
|
|
|
324
|
+
const fenceDirs = (all: readonly ProjectMeta[]): string[] =>
|
|
325
|
+
all.filter((p) => p.configPath !== null).map((p) => p.dir)
|
|
326
|
+
|
|
319
327
|
interface Rediscovered {
|
|
320
328
|
projects: readonly ProjectMeta[]
|
|
321
329
|
claimedRootFiles: ReadonlySet<string>
|
|
@@ -328,6 +336,7 @@ interface Rediscovered {
|
|
|
328
336
|
configImports: readonly string[]
|
|
329
337
|
workspaceConfigImports: readonly string[]
|
|
330
338
|
packageDirs: ReadonlySet<string>
|
|
339
|
+
fenceDirs: readonly string[]
|
|
331
340
|
}
|
|
332
341
|
|
|
333
342
|
async function runWatchLoop(args: WatchLoopArgs): Promise<number> {
|
|
@@ -424,7 +433,14 @@ async function runWatchLoop(args: WatchLoopArgs): Promise<number> {
|
|
|
424
433
|
// project would otherwise trigger every save during `bun install` —
|
|
425
434
|
// and vx's own cache writes would trigger a cycle that writes again.
|
|
426
435
|
let isIgnoredPath = makeWatchIgnore(cacheDir, outputs, inputs)
|
|
427
|
-
let
|
|
436
|
+
let fenced = makeFence(args.fenceDirs)
|
|
437
|
+
let matters = makeRootEventFilter(
|
|
438
|
+
workspaceRoot,
|
|
439
|
+
projectDirs,
|
|
440
|
+
workspaceInputs,
|
|
441
|
+
claimedRootFiles,
|
|
442
|
+
fenced,
|
|
443
|
+
)
|
|
428
444
|
/** Since the last cycle, a member came or went, or a file that shapes the watched set changed (`shapesWatchedSet`). */
|
|
429
445
|
let reread = false
|
|
430
446
|
|
|
@@ -446,6 +462,7 @@ async function runWatchLoop(args: WatchLoopArgs): Promise<number> {
|
|
|
446
462
|
armedAt,
|
|
447
463
|
held: () => held !== undefined,
|
|
448
464
|
uncached: () => uncached,
|
|
465
|
+
fenced: (ownDir, abs) => fenced(ownDir, abs),
|
|
449
466
|
...(existedAtArm !== undefined ? { existedAtArm } : {}),
|
|
450
467
|
})
|
|
451
468
|
/** Per-project arms by directory, so `rearm` can add and drop them. */
|
|
@@ -472,9 +489,10 @@ async function runWatchLoop(args: WatchLoopArgs): Promise<number> {
|
|
|
472
489
|
const armProject = (proj: ProjectMeta): void => {
|
|
473
490
|
try {
|
|
474
491
|
const handle = arm(proj.dir, true, (filename) => {
|
|
475
|
-
|
|
492
|
+
const abs = path.join(proj.dir, filename)
|
|
493
|
+
if (isIgnoredPath(proj.dir, filename) || fenced(proj.dir, abs)) return
|
|
476
494
|
if (shapesWatchedSet(filename)) reread = true
|
|
477
|
-
trigger(`${proj.name} ${filename}`,
|
|
495
|
+
trigger(`${proj.name} ${filename}`, abs)
|
|
478
496
|
})
|
|
479
497
|
perProject.set(proj.dir, handle)
|
|
480
498
|
armedAs.set(proj.dir, inodeOf(proj.dir) ?? '')
|
|
@@ -659,7 +677,14 @@ async function runWatchLoop(args: WatchLoopArgs): Promise<number> {
|
|
|
659
677
|
memberBases = next.memberBases
|
|
660
678
|
armBases()
|
|
661
679
|
isIgnoredPath = makeWatchIgnore(cacheDir, outputs, inputs)
|
|
662
|
-
|
|
680
|
+
fenced = makeFence(next.fenceDirs)
|
|
681
|
+
matters = makeRootEventFilter(
|
|
682
|
+
workspaceRoot,
|
|
683
|
+
projectDirs,
|
|
684
|
+
workspaceInputs,
|
|
685
|
+
claimedRootFiles,
|
|
686
|
+
fenced,
|
|
687
|
+
)
|
|
663
688
|
if (next.workspaceWide !== workspaceWide) {
|
|
664
689
|
// A task started or stopped declaring `workspaceFiles`: the other
|
|
665
690
|
// arm's shape. Until item 891 the choice was made once, at start, and
|