@vzn/vx 0.0.554 → 0.0.556
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.556",
|
|
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.556",
|
|
25
|
+
"@vzn/vx-linux-arm64": "0.0.556",
|
|
26
|
+
"@vzn/vx-darwin-x64": "0.0.556",
|
|
27
|
+
"@vzn/vx-darwin-arm64": "0.0.556"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
30
|
"@anthropic-ai/sandbox-runtime": "^0.0.76"
|
package/src/cache/inputs.ts
CHANGED
|
@@ -739,11 +739,15 @@ function stampFiles(files: readonly string[]): Map<string, OutputStamp> {
|
|
|
739
739
|
* a current tree, so no new trust is introduced. A file the run rewrote
|
|
740
740
|
* with identical bytes counts as its own (the mtime moved), which is the
|
|
741
741
|
* rewrite-in-place cost the design note records.
|
|
742
|
+
*
|
|
743
|
+
* Undefined when the run REMOVED a file it found: an artifact holds what a
|
|
744
|
+
* run wrote, never what it took away, and the upstream's restore puts the
|
|
745
|
+
* file back, so no entry reproduces that run and none is saved.
|
|
742
746
|
*/
|
|
743
747
|
export async function ownOutputsSince(
|
|
744
748
|
args: { projectDir: string; outputs: string[]; nestedProjectDirs: string[] },
|
|
745
749
|
before: ReadonlyMap<string, OutputStamp>,
|
|
746
|
-
): Promise<string[]> {
|
|
750
|
+
): Promise<string[] | undefined> {
|
|
747
751
|
return changedSince(await resolveOutputs(args), before)
|
|
748
752
|
}
|
|
749
753
|
|
|
@@ -751,15 +755,16 @@ export async function ownOutputsSince(
|
|
|
751
755
|
export async function ownWorkspaceOutputsSince(
|
|
752
756
|
args: { workspaceRoot: string; outputs: string[] },
|
|
753
757
|
before: ReadonlyMap<string, OutputStamp>,
|
|
754
|
-
): Promise<string[]> {
|
|
758
|
+
): Promise<string[] | undefined> {
|
|
755
759
|
return changedSince(await resolveWorkspaceOutputs(args), before)
|
|
756
760
|
}
|
|
757
761
|
|
|
758
762
|
function changedSince(
|
|
759
763
|
after: readonly string[],
|
|
760
764
|
before: ReadonlyMap<string, OutputStamp>,
|
|
761
|
-
): string[] {
|
|
765
|
+
): string[] | undefined {
|
|
762
766
|
const own: string[] = []
|
|
767
|
+
let kept = 0
|
|
763
768
|
for (const f of after) {
|
|
764
769
|
const was = before.get(f)
|
|
765
770
|
if (was === undefined) {
|
|
@@ -768,12 +773,13 @@ function changedSince(
|
|
|
768
773
|
}
|
|
769
774
|
try {
|
|
770
775
|
const st = lstatSync(f)
|
|
776
|
+
kept++
|
|
771
777
|
if (st.size !== was.size || st.mtimeMs !== was.mtimeMs) own.push(f)
|
|
772
778
|
} catch {
|
|
773
|
-
// Vanished since the walk:
|
|
779
|
+
// Vanished since the walk: removed, as one the walk missed is.
|
|
774
780
|
}
|
|
775
781
|
}
|
|
776
|
-
return own
|
|
782
|
+
return kept < before.size ? undefined : own
|
|
777
783
|
}
|
|
778
784
|
|
|
779
785
|
/**
|
|
@@ -1213,6 +1213,7 @@ async function executeCachedTask(args: ExecuteArgs): Promise<TaskOutcome> {
|
|
|
1213
1213
|
|
|
1214
1214
|
const wallclockEndNs = process.hrtime.bigint() - args.runStartHrTimeNs
|
|
1215
1215
|
|
|
1216
|
+
let own: Awaited<ReturnType<typeof ownOutputs>>
|
|
1216
1217
|
if (effectiveExitCode === 0 && willSave && deferralRequested) {
|
|
1217
1218
|
// The outputs never landed here: no artifact, no rows. A partial local
|
|
1218
1219
|
// record (a row with no artifact) is exactly the corrupt-entry shape
|
|
@@ -1234,18 +1235,14 @@ async function executeCachedTask(args: ExecuteArgs): Promise<TaskOutcome> {
|
|
|
1234
1235
|
},
|
|
1235
1236
|
})
|
|
1236
1237
|
}
|
|
1237
|
-
} else if (
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
additive && wsStampedBefore !== undefined
|
|
1244
|
-
? await ownWorkspaceOutputsSince(wsCleanArgs, wsStampedBefore)
|
|
1245
|
-
: undefined
|
|
1238
|
+
} else if (
|
|
1239
|
+
effectiveExitCode === 0 &&
|
|
1240
|
+
willSave &&
|
|
1241
|
+
(await keyStillTrue()) &&
|
|
1242
|
+
(own = await ownOutputs()) !== undefined
|
|
1243
|
+
) {
|
|
1246
1244
|
const { landed } = await saveMiss({
|
|
1247
|
-
...
|
|
1248
|
-
...(ownWsOutputFiles !== undefined ? { ownWsOutputFiles } : {}),
|
|
1245
|
+
...own,
|
|
1249
1246
|
node,
|
|
1250
1247
|
hash,
|
|
1251
1248
|
cache,
|
|
@@ -1302,6 +1299,28 @@ async function executeCachedTask(args: ExecuteArgs): Promise<TaskOutcome> {
|
|
|
1302
1299
|
return true
|
|
1303
1300
|
}
|
|
1304
1301
|
|
|
1302
|
+
/**
|
|
1303
|
+
* What an additive run saves as its own, or undefined when it removed a
|
|
1304
|
+
* file it found: no entry replays a removal (`ownOutputsSince`), so the
|
|
1305
|
+
* task saves nothing and runs again.
|
|
1306
|
+
*/
|
|
1307
|
+
async function ownOutputs(): Promise<
|
|
1308
|
+
{ ownOutputFiles?: string[]; ownWsOutputFiles?: string[] } | undefined
|
|
1309
|
+
> {
|
|
1310
|
+
const mine: { ownOutputFiles?: string[]; ownWsOutputFiles?: string[] } = {}
|
|
1311
|
+
if (stampedBefore !== undefined) {
|
|
1312
|
+
const files = await ownOutputsSince(cleanArgs, stampedBefore)
|
|
1313
|
+
if (files === undefined) return undefined
|
|
1314
|
+
mine.ownOutputFiles = files
|
|
1315
|
+
}
|
|
1316
|
+
if (wsStampedBefore !== undefined) {
|
|
1317
|
+
const files = await ownWorkspaceOutputsSince(wsCleanArgs, wsStampedBefore)
|
|
1318
|
+
if (files === undefined) return undefined
|
|
1319
|
+
mine.ownWsOutputFiles = files
|
|
1320
|
+
}
|
|
1321
|
+
return mine
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1305
1324
|
/**
|
|
1306
1325
|
* The input that no longer holds what the key folded: `null` when the
|
|
1307
1326
|
* key re-derived just before the command already differed (the file
|
|
@@ -24,6 +24,9 @@ import {
|
|
|
24
24
|
UserError,
|
|
25
25
|
BUN_GLOB_WILDCARDS,
|
|
26
26
|
EXTGLOB,
|
|
27
|
+
anyTaskGlob,
|
|
28
|
+
asTrees,
|
|
29
|
+
isLiteralPattern,
|
|
27
30
|
} from '../util/index.js'
|
|
28
31
|
import { nonJsonMessage, nonJsonPaths } from './json-data.js'
|
|
29
32
|
|
|
@@ -722,6 +725,7 @@ export function validateProjectConfig(config: ProjectConfig, configPath: string)
|
|
|
722
725
|
// error for the same reason. Refusing costs nothing: such a config was
|
|
723
726
|
// already silently broken, so no working key changes.
|
|
724
727
|
assertNotNegationOnly((inputs as { files: string[] }).files, `${where}.cache.inputs.files`)
|
|
728
|
+
assertNoLiteralTakenBack((inputs as { files: string[] }).files, `${where}.cache.inputs.files`)
|
|
725
729
|
// The only CacheInputs field that reached the run unvalidated: a
|
|
726
730
|
// non-string entry crashes deep in `filterUpstreamHashes` /
|
|
727
731
|
// `parseDependencySpec` with a raw TypeError naming neither the task
|
|
@@ -1338,6 +1342,28 @@ function assertNotNegationOnly(globs: readonly string[], where: string): void {
|
|
|
1338
1342
|
)
|
|
1339
1343
|
}
|
|
1340
1344
|
|
|
1345
|
+
/**
|
|
1346
|
+
* A `!` entry subtracts wherever it sits in the list, so a file named
|
|
1347
|
+
* outright after it (`['src/**', '!src/gen/**', 'src/gen/keep.ts']`) never
|
|
1348
|
+
* entered the key, and an edit to it replayed the old output. Refusing is
|
|
1349
|
+
* free: the literal already selected nothing.
|
|
1350
|
+
*/
|
|
1351
|
+
function assertNoLiteralTakenBack(globs: readonly string[], where: string): void {
|
|
1352
|
+
const negative = globs.filter((g) => g.startsWith('!')).map((g) => g.slice(1))
|
|
1353
|
+
if (negative.length === 0) return
|
|
1354
|
+
const taken = anyTaskGlob(asTrees(negative))
|
|
1355
|
+
for (const g of globs) {
|
|
1356
|
+
if (g.startsWith('!')) continue
|
|
1357
|
+
const lit = normalizeGlob(g).replace(/\/+$/, '')
|
|
1358
|
+
if (!isLiteralPattern(lit) || !taken(lit)) continue
|
|
1359
|
+
const by = negative.find((n) => anyTaskGlob(asTrees([n]))(lit))
|
|
1360
|
+
throw new UserError(
|
|
1361
|
+
`${where}: "${g}" is taken back by "!${by}" — a \`!\` entry subtracts wherever it sits in ` +
|
|
1362
|
+
`the list, so the file never enters the key. Narrow the negation so it leaves "${g}".`,
|
|
1363
|
+
)
|
|
1364
|
+
}
|
|
1365
|
+
}
|
|
1366
|
+
|
|
1341
1367
|
/**
|
|
1342
1368
|
* `negation: false` for OUTPUT globs: a `!` entry takes a path back (A-44),
|
|
1343
1369
|
* and a list of only negations selects nothing and is refused.
|
|
@@ -1370,8 +1396,10 @@ function validateWorkspaceGlobs(v: unknown, where: string, negation: boolean): v
|
|
|
1370
1396
|
}
|
|
1371
1397
|
assertNotDoubleNegated(g, where)
|
|
1372
1398
|
}
|
|
1373
|
-
if (negation)
|
|
1374
|
-
|
|
1399
|
+
if (negation) {
|
|
1400
|
+
assertNotNegationOnly(v as string[], where)
|
|
1401
|
+
assertNoLiteralTakenBack(v as string[], where)
|
|
1402
|
+
} else assertOutputsNotNegationOnly(v as string[], where)
|
|
1375
1403
|
}
|
|
1376
1404
|
|
|
1377
1405
|
/**
|