@vzn/vx 0.0.54 → 0.0.56
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.56",
|
|
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.56",
|
|
21
|
+
"@vzn/vx-linux-arm64": "0.0.56",
|
|
22
|
+
"@vzn/vx-darwin-x64": "0.0.56",
|
|
23
|
+
"@vzn/vx-darwin-arm64": "0.0.56"
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
26
|
"@anthropic-ai/sandbox-runtime": "^0.0.76"
|
package/src/cache/cache.ts
CHANGED
|
@@ -1655,13 +1655,17 @@ export class Cache implements CacheLayer {
|
|
|
1655
1655
|
* orphan sweep alone when an hour has passed since the last one. A run
|
|
1656
1656
|
* with nothing due pays the accessed-at flush it owed at close anyway, one
|
|
1657
1657
|
* scan of the index and one read of the sweep's clock — never the
|
|
1658
|
-
* sweep's readdir. Null when nothing was due or
|
|
1658
|
+
* sweep's readdir. Null when nothing was due or the directory is not this
|
|
1659
|
+
* handle's to write. The local WRITE axis does not gate it: under
|
|
1660
|
+
* `--cache=local:r,remote:rw` a save writes nothing here, but every remote
|
|
1661
|
+
* hit is still ingested (`ingest`, ungated by design), and gated on that
|
|
1662
|
+
* axis the retention never ran while the store grew hit by hit (A-7).
|
|
1659
1663
|
*/
|
|
1660
1664
|
async evictIfDue(
|
|
1661
1665
|
policy: { maxAgeMs?: number; maxBytes?: number },
|
|
1662
1666
|
now: number = Date.now(),
|
|
1663
1667
|
): Promise<PruneResult | null> {
|
|
1664
|
-
if (
|
|
1668
|
+
if (this.writeBlocked !== null || this.inspecting) return null
|
|
1665
1669
|
// First: an entry this run restored still carries its old `accessed_at`
|
|
1666
1670
|
// until the deferred bump lands, and would read as due for eviction.
|
|
1667
1671
|
this.flushAccessed()
|
package/src/cache/git-inputs.ts
CHANGED
|
@@ -635,6 +635,28 @@ function restampModes(trusted: Map<string, string>, workspaceRoot: string): void
|
|
|
635
635
|
}
|
|
636
636
|
}
|
|
637
637
|
|
|
638
|
+
/**
|
|
639
|
+
* `true` when the repository's config weakens the stat `git status` judges
|
|
640
|
+
* a file clean by: `core.trustctime` off, or `core.checkStat=minimal`
|
|
641
|
+
* (whole-second mtime and size only). Then a same-size rewrite that restores
|
|
642
|
+
* its mtime — `cp -p`, `tar -x`, a formatter that keeps times — reads clean
|
|
643
|
+
* and would keep its index OID, while the file hasher's own memo still keys
|
|
644
|
+
* on ctime and inode (A-6). Read from the `git var -l` the run already
|
|
645
|
+
* spawns; git's false spellings, the last value winning.
|
|
646
|
+
*/
|
|
647
|
+
export function gitStatWeakened(gitVars: string): boolean {
|
|
648
|
+
const vars = gitVarList(gitVars)
|
|
649
|
+
const trustctime = vars.get('core.trustctime')?.trim().toLowerCase()
|
|
650
|
+
const checkStat = vars.get('core.checkstat')?.trim().toLowerCase()
|
|
651
|
+
return (
|
|
652
|
+
trustctime === 'false' ||
|
|
653
|
+
trustctime === 'no' ||
|
|
654
|
+
trustctime === 'off' ||
|
|
655
|
+
trustctime === '0' ||
|
|
656
|
+
checkStat === 'minimal'
|
|
657
|
+
)
|
|
658
|
+
}
|
|
659
|
+
|
|
638
660
|
/** `true` when git may rewrite bytes for EVERY auto-detected text file. */
|
|
639
661
|
export function autocrlfConverts(gitVars: string): boolean {
|
|
640
662
|
const v = gitVarList(gitVars).get('core.autocrlf')?.trim().toLowerCase()
|
|
@@ -945,6 +967,9 @@ export async function startGitEnumeration(
|
|
|
945
967
|
// Dropping the OID sends these back through the probe, where they correctly
|
|
946
968
|
// fall out of the input set while unmaterialized.
|
|
947
969
|
for (const rel of flagged) trusted.delete(rel)
|
|
970
|
+
// Under a weakened stat, `git status` vouches for nothing a same-size,
|
|
971
|
+
// time-keeping rewrite made: every file is hashed from disk instead.
|
|
972
|
+
if (vars !== null && vars.exitCode === 0 && gitStatWeakened(vars.stdout)) trusted.clear()
|
|
948
973
|
// An index OID is only the file's content hash when git stores the worktree
|
|
949
974
|
// bytes VERBATIM. Under a clean filter (`text`/`eol`/`ident`/`filter`/`working-tree-encoding`) the blob is a
|
|
950
975
|
// DIFFERENT sequence of bytes — the LF-normalized form — while the task
|
package/src/cache/inputs.ts
CHANGED
|
@@ -17,7 +17,8 @@ import {
|
|
|
17
17
|
type ProjectMeta,
|
|
18
18
|
} from '../workspace/index.js'
|
|
19
19
|
import type { WorkspaceConfig } from '../config.js'
|
|
20
|
-
import { Cache
|
|
20
|
+
import { Cache } from '../cache/index.js'
|
|
21
|
+
import { UserError } from '../util/index.js'
|
|
21
22
|
import {
|
|
22
23
|
buildPackageGraph,
|
|
23
24
|
computeWorkspaceFingerprint,
|
|
@@ -245,8 +246,15 @@ export async function loadResolvedProjects(
|
|
|
245
246
|
const metas = await listProjects(await loadWorkspace(workspaceRoot, reads))
|
|
246
247
|
const { workspaceConfig, plugins } = await loadWorkspacePlugins(workspaceRoot, warn)
|
|
247
248
|
const cacheDir = resolveCacheDir(workspaceRoot, workspaceConfig)
|
|
248
|
-
|
|
249
|
-
|
|
249
|
+
// Opened as `vx last` opens it: a reader makes no index where there is
|
|
250
|
+
// none and never resets an earlier schema's (C-5). One it cannot read
|
|
251
|
+
// serves nothing; the configs evaluate live.
|
|
252
|
+
let cache: Cache | undefined
|
|
253
|
+
try {
|
|
254
|
+
cache = Cache.inspect(cacheDir)
|
|
255
|
+
} catch (err) {
|
|
256
|
+
if (!(err instanceof UserError)) throw err
|
|
257
|
+
}
|
|
250
258
|
try {
|
|
251
259
|
const loaded = await loadProjects({
|
|
252
260
|
workspaceRoot,
|
|
@@ -257,15 +265,18 @@ export async function loadResolvedProjects(
|
|
|
257
265
|
seeds: opts.scope ?? 'all',
|
|
258
266
|
closure: false,
|
|
259
267
|
lock: null,
|
|
260
|
-
evalCache:
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
268
|
+
evalCache:
|
|
269
|
+
cache === undefined
|
|
270
|
+
? undefined
|
|
271
|
+
: {
|
|
272
|
+
store: cache,
|
|
273
|
+
workspaceRoot,
|
|
274
|
+
workspaceFingerprint: await computeWorkspaceFingerprint(workspaceRoot, reads),
|
|
275
|
+
},
|
|
265
276
|
warn,
|
|
266
277
|
})
|
|
267
278
|
return loaded.projects
|
|
268
279
|
} finally {
|
|
269
|
-
cache
|
|
280
|
+
cache?.close()
|
|
270
281
|
}
|
|
271
282
|
}
|
|
@@ -3,11 +3,13 @@
|
|
|
3
3
|
// Two vx processes on one workspace race on every task's OUTPUT TREE: both
|
|
4
4
|
// clean and restore the same `dist/`, and a clean landing while the other
|
|
5
5
|
// run's restore is staging takes its files out from under it (item 215).
|
|
6
|
-
// The cache itself is safe (SQLite waits,
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
6
|
+
// The cache itself is safe (SQLite waits, and an artifact lands by rename
|
|
7
|
+
// inside its rows' transaction, A-3), across workspaces sharing one
|
|
8
|
+
// `--cache-dir` too; the tree is not, and a run that finished "green" can
|
|
9
|
+
// have had its restored outputs deleted by the other run's clean a moment
|
|
10
|
+
// later. So a run takes this lock before it schedules and releases it with
|
|
11
|
+
// its cache handle — before a persistent task's wait, so a dev server never
|
|
12
|
+
// holds it.
|
|
11
13
|
//
|
|
12
14
|
// Keyed by the workspace, not the cache directory (`--cache-dir` must not
|
|
13
15
|
// make two runs strangers), and kept under the temp directory so a
|
package/src/orchestrator/run.ts
CHANGED
|
@@ -1192,7 +1192,9 @@ async function applyCacheRetention(prepared: PreparedRun, log: Logger): Promise<
|
|
|
1192
1192
|
* existence check — no artifact download, no ingest, no accessed_at bump.
|
|
1193
1193
|
*/
|
|
1194
1194
|
export async function planRun(options: RunOptions): Promise<RunPlan> {
|
|
1195
|
-
|
|
1195
|
+
// The plan is the product and goes to stdout (`--dry=json` is parsed):
|
|
1196
|
+
// what a stage says on the way goes to stderr (C-6).
|
|
1197
|
+
const log = options.log ?? defaultLogger(undefined, undefined, process.stderr)
|
|
1196
1198
|
const prepared = await prepareRun(options, log)
|
|
1197
1199
|
try {
|
|
1198
1200
|
if (prepared.unresolvedTasks.length > 0) {
|