@vzn/vx 0.0.245 → 0.0.247
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/cache/inputs.ts +36 -3
- package/src/cli/watch-fs.ts +10 -1
- package/src/orchestrator/execute-task.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.247",
|
|
4
4
|
"description": "A fast, correct task runner and build cache for JavaScript monorepos.",
|
|
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.247",
|
|
21
|
+
"@vzn/vx-linux-arm64": "0.0.247",
|
|
22
|
+
"@vzn/vx-darwin-x64": "0.0.247",
|
|
23
|
+
"@vzn/vx-darwin-arm64": "0.0.247"
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
26
|
"@anthropic-ai/sandbox-runtime": "^0.0.76"
|
package/src/cache/inputs.ts
CHANGED
|
@@ -29,6 +29,7 @@ import {
|
|
|
29
29
|
shellArgv,
|
|
30
30
|
splitNegations,
|
|
31
31
|
slashBraceExpansions,
|
|
32
|
+
staticPrefix,
|
|
32
33
|
taskGlob,
|
|
33
34
|
UserError,
|
|
34
35
|
} from '../util/index.js'
|
|
@@ -615,13 +616,25 @@ export async function cleanOutputs(args: {
|
|
|
615
616
|
projectDir: string
|
|
616
617
|
outputs: string[]
|
|
617
618
|
nestedProjectDirs: string[]
|
|
619
|
+
/**
|
|
620
|
+
* Before a miss: keep the directory each wildcard glob is rooted at
|
|
621
|
+
* (`dist` for `dist/**`). The task writes its matches under it, so a
|
|
622
|
+
* remove there bought only an rmdir and the task's mkdir: 0.4 ms of a
|
|
623
|
+
* 4.9 ms one-file miss (B-49). A restore prunes it, since the entry's
|
|
624
|
+
* shape decides there.
|
|
625
|
+
*/
|
|
626
|
+
keepGlobRoots?: boolean
|
|
618
627
|
}): Promise<string[]> {
|
|
619
628
|
const files = await resolveOutputs(args)
|
|
620
629
|
// `force: true` makes rm tolerate ENOENT (e.g. when two output
|
|
621
630
|
// globs overlap and a sibling already deleted a path mid-iteration).
|
|
622
631
|
// A symlink is unlinked, never followed.
|
|
623
632
|
await removeAll(files, args.projectDir)
|
|
624
|
-
await pruneEmptiedDirs(
|
|
633
|
+
await pruneEmptiedDirs(
|
|
634
|
+
args.projectDir,
|
|
635
|
+
files,
|
|
636
|
+
args.keepGlobRoots === true ? globRoots(args.projectDir, args.outputs) : undefined,
|
|
637
|
+
)
|
|
625
638
|
// Project-relative posix paths of what was removed — the caller
|
|
626
639
|
// feeds these to GitFilesCache.markOutputsChanged after a restore.
|
|
627
640
|
return files.map((f) => path.relative(args.projectDir, f).split(path.sep).join('/'))
|
|
@@ -778,7 +791,11 @@ async function removeAll(files: readonly string[], root: string): Promise<void>
|
|
|
778
791
|
* holds something — a stray the globs do not cover — stays, and the restore
|
|
779
792
|
* says so if it is in the way.
|
|
780
793
|
*/
|
|
781
|
-
async function pruneEmptiedDirs(
|
|
794
|
+
async function pruneEmptiedDirs(
|
|
795
|
+
root: string,
|
|
796
|
+
removed: readonly string[],
|
|
797
|
+
keep?: ReadonlySet<string>,
|
|
798
|
+
): Promise<void> {
|
|
782
799
|
const rootResolved = path.resolve(root)
|
|
783
800
|
// LEVEL ORDER, not a walk-up per directory. A parent is attempted only
|
|
784
801
|
// once every one of its children has had its turn, which is what makes
|
|
@@ -794,7 +811,8 @@ async function pruneEmptiedDirs(root: string, removed: readonly string[]): Promi
|
|
|
794
811
|
while (level.size > 0) {
|
|
795
812
|
const parents = new Set<string>()
|
|
796
813
|
const dirs = [...level].filter(
|
|
797
|
-
(dir) =>
|
|
814
|
+
(dir) =>
|
|
815
|
+
dir !== rootResolved && dir.startsWith(rootResolved + path.sep) && keep?.has(dir) !== true,
|
|
798
816
|
)
|
|
799
817
|
const gone = (err: NodeJS.ErrnoException): boolean => err.code === 'ENOENT'
|
|
800
818
|
if (dirs.length <= SYNC_CLEAN_MAX) {
|
|
@@ -817,6 +835,21 @@ async function pruneEmptiedDirs(root: string, removed: readonly string[]): Promi
|
|
|
817
835
|
}
|
|
818
836
|
}
|
|
819
837
|
|
|
838
|
+
/**
|
|
839
|
+
* The directories the wildcard output globs are rooted at, absolute. A
|
|
840
|
+
* literal names a file or a tree whose shape the task decides, so it has
|
|
841
|
+
* none; nor has a glob rooted at the project itself.
|
|
842
|
+
*/
|
|
843
|
+
function globRoots(projectDir: string, outputs: readonly string[]): Set<string> {
|
|
844
|
+
const roots = new Set<string>()
|
|
845
|
+
for (const g of outputs) {
|
|
846
|
+
if (g.startsWith('!') || isLiteralPattern(g)) continue
|
|
847
|
+
const prefix = staticPrefix(g)
|
|
848
|
+
if (prefix !== '.') roots.add(path.resolve(projectDir, prefix))
|
|
849
|
+
}
|
|
850
|
+
return roots
|
|
851
|
+
}
|
|
852
|
+
|
|
820
853
|
/**
|
|
821
854
|
* Resolve declared `outputs.workspaceFiles` globs (workspace-root-
|
|
822
855
|
* relative) to actual produced files. Live-FS glob like
|
package/src/cli/watch-fs.ts
CHANGED
|
@@ -153,6 +153,7 @@ export function armWatcher(
|
|
|
153
153
|
timeoutMs = WATCH_PROBE_TIMEOUT_MS,
|
|
154
154
|
): ArmedWatcher {
|
|
155
155
|
let markReady: (ok: boolean) => void = () => {}
|
|
156
|
+
let arrived = false
|
|
156
157
|
const seen = new Promise<boolean>((resolve) => {
|
|
157
158
|
markReady = resolve
|
|
158
159
|
})
|
|
@@ -163,6 +164,7 @@ export function armWatcher(
|
|
|
163
164
|
// can see; the write's own event names the file.
|
|
164
165
|
if (filename === '' || filename === '.') return
|
|
165
166
|
if (filename === WATCH_PROBE) {
|
|
167
|
+
arrived = true
|
|
166
168
|
markReady(true)
|
|
167
169
|
return
|
|
168
170
|
}
|
|
@@ -191,7 +193,14 @@ export function armWatcher(
|
|
|
191
193
|
break // an unwritable dir gets no proof; the watcher is kept
|
|
192
194
|
}
|
|
193
195
|
const remaining = deadline - Date.now()
|
|
194
|
-
if (remaining <= 0)
|
|
196
|
+
if (remaining <= 0) {
|
|
197
|
+
// The write itself can outlast the budget (a loaded macOS runner:
|
|
198
|
+
// 261 ms against 100, E-87), and the loop has not yielded yet: let
|
|
199
|
+
// an event already queued in before giving up on it.
|
|
200
|
+
await new Promise((resolve) => setTimeout(resolve, 0))
|
|
201
|
+
ok = arrived
|
|
202
|
+
break
|
|
203
|
+
}
|
|
195
204
|
const pause = new Promise<boolean>((resolve) => {
|
|
196
205
|
setTimeout(() => resolve(false), Math.min(step, remaining)).unref()
|
|
197
206
|
})
|
|
@@ -758,7 +758,7 @@ async function executeCachedTask(args: ExecuteArgs): Promise<TaskOutcome> {
|
|
|
758
758
|
// re-create would stay in a same-project consumer's input set, keeping
|
|
759
759
|
// that consumer's key unchanged while the file is gone from disk.
|
|
760
760
|
const endClean = span('miss: clean outputs')
|
|
761
|
-
const cleanedRels = await cleanOutputs(cleanArgs)
|
|
761
|
+
const cleanedRels = await cleanOutputs({ ...cleanArgs, keepGlobRoots: true })
|
|
762
762
|
endClean()
|
|
763
763
|
args.gitFilesCache?.noteClean(node.id, node.projectDir, cleanedRels)
|
|
764
764
|
args.gitFilesCache?.markOutputsChanged(node.projectDir, cleanedRels)
|