@vzn/vx 0.0.528 → 0.0.530
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/git-inputs.ts +15 -3
- package/src/cache/inputs.ts +43 -9
- package/src/util/index.ts +1 -0
- package/src/util/paths.ts +10 -0
- package/src/workspace/affected.ts +6 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vzn/vx",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.530",
|
|
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.530",
|
|
25
|
+
"@vzn/vx-linux-arm64": "0.0.530",
|
|
26
|
+
"@vzn/vx-darwin-x64": "0.0.530",
|
|
27
|
+
"@vzn/vx-darwin-arm64": "0.0.530"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
30
|
"@anthropic-ai/sandbox-runtime": "^0.0.76"
|
package/src/cache/git-inputs.ts
CHANGED
|
@@ -7,7 +7,13 @@
|
|
|
7
7
|
|
|
8
8
|
import path from 'node:path'
|
|
9
9
|
import { existsSync, lstatSync, readdirSync, readFileSync, realpathSync } from 'node:fs'
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
UserError,
|
|
12
|
+
executablePath,
|
|
13
|
+
gitSpawnRefusal,
|
|
14
|
+
isInstalledPath,
|
|
15
|
+
xxh3hex,
|
|
16
|
+
} from '../util/index.js'
|
|
11
17
|
import { FILE_HASH_RACY_MS, racyWindowMs } from './layer.js'
|
|
12
18
|
|
|
13
19
|
/** Three facts of the repository a directory is in: read off the disk, or one `git rev-parse`. */
|
|
@@ -517,7 +523,8 @@ function walkFiles(dir: string, rel = ''): string[] {
|
|
|
517
523
|
}
|
|
518
524
|
const out: string[] = []
|
|
519
525
|
for (const e of entries) {
|
|
520
|
-
|
|
526
|
+
// Nothing tracks a vendored tree's files; an install there is not one.
|
|
527
|
+
if (e.name === '.git' || e.name === 'node_modules') continue
|
|
521
528
|
const r = rel === '' ? e.name : `${rel}/${e.name}`
|
|
522
529
|
if (e.isDirectory()) out.push(...walkFiles(dir, r))
|
|
523
530
|
else out.push(r)
|
|
@@ -562,6 +569,7 @@ function parseLsFilesOutput(
|
|
|
562
569
|
if (record.length === 0) continue
|
|
563
570
|
const m = LS_FILES_STAGE_RE.exec(record)
|
|
564
571
|
const filePath = m === null ? record : record.slice(m[0].length) // --others: bare path
|
|
572
|
+
if (m === null && isInstalledPath(filePath)) continue
|
|
565
573
|
files.push(filePath)
|
|
566
574
|
if (undecodableRecords.size > 0 && undecodableRecords.has(record)) undecodable.push(filePath)
|
|
567
575
|
if (m === null) continue
|
|
@@ -1376,7 +1384,11 @@ export async function startGitEnumeration(
|
|
|
1376
1384
|
// is). They come from the status walk, repo-root-relative like the dirty
|
|
1377
1385
|
// set. Without a status answer the enumeration is the index alone.
|
|
1378
1386
|
const untracked =
|
|
1379
|
-
parsedStatus === null
|
|
1387
|
+
parsedStatus === null
|
|
1388
|
+
? []
|
|
1389
|
+
: [...stripPrefixFromSet(new Set(parsedStatus.untracked), gitPrefix)].filter(
|
|
1390
|
+
(rel) => !isInstalledPath(rel),
|
|
1391
|
+
)
|
|
1380
1392
|
const listed = untracked.length === 0 ? tracked : tracked.concat(untracked)
|
|
1381
1393
|
const nested = expandNestedRepos(workspaceRoot, listed, gitlinks)
|
|
1382
1394
|
const all = nested.files
|
package/src/cache/inputs.ts
CHANGED
|
@@ -58,8 +58,10 @@ export {
|
|
|
58
58
|
// re-lock can't bust every cache key the way a tracked source file
|
|
59
59
|
// would. (Literal pattern, not the workspace `LOCKFILE_NAME` constant:
|
|
60
60
|
// cache is a leaf module and must not import from workspace.)
|
|
61
|
+
//
|
|
62
|
+
// `node_modules` is not here: the enumeration drops an untracked file
|
|
63
|
+
// under one (`isInstalledPath`), and a tracked one is a source.
|
|
61
64
|
const ALWAYS_IGNORE = [
|
|
62
|
-
'**/node_modules/**',
|
|
63
65
|
// Defense in depth, and measured as exactly that (item 497): git never
|
|
64
66
|
// reports a path under `.git`, so dropping this line changes NOTHING on
|
|
65
67
|
// any route into the input set. Probed three ways with the pattern in
|
|
@@ -664,10 +666,10 @@ export async function cleanOutputs(args: {
|
|
|
664
666
|
// `force: true` makes rm tolerate ENOENT (e.g. when two output
|
|
665
667
|
// globs overlap and a sibling already deleted a path mid-iteration).
|
|
666
668
|
// A symlink is unlinked, never followed.
|
|
667
|
-
await removeAll(files, args.projectDir)
|
|
669
|
+
const removed = await removeAll(files, args.projectDir)
|
|
668
670
|
await pruneEmptiedDirs(
|
|
669
671
|
args.projectDir,
|
|
670
|
-
|
|
672
|
+
removed,
|
|
671
673
|
args.keepGlobRoots === true ? globRoots(args.projectDir, args.outputs) : undefined,
|
|
672
674
|
)
|
|
673
675
|
// Project-relative posix paths of what was removed — the caller
|
|
@@ -687,8 +689,7 @@ export async function cleanOutputPaths(args: {
|
|
|
687
689
|
rels: readonly string[]
|
|
688
690
|
}): Promise<void> {
|
|
689
691
|
const files = args.rels.map((r) => path.resolve(args.projectDir, r))
|
|
690
|
-
await removeAll(files, args.projectDir)
|
|
691
|
-
await pruneEmptiedDirs(args.projectDir, files)
|
|
692
|
+
await pruneEmptiedDirs(args.projectDir, await removeAll(files, args.projectDir))
|
|
692
693
|
}
|
|
693
694
|
|
|
694
695
|
/** A file's identity for the additive diff: what the hit path's fingerprint trusts too. */
|
|
@@ -789,7 +790,8 @@ const SYNC_CLEAN_MAX = 128
|
|
|
789
790
|
* the scheduler prints any other error as an "internal error", which
|
|
790
791
|
* sends the reader to file a bug against a permission bit.
|
|
791
792
|
*/
|
|
792
|
-
async function removeAll(
|
|
793
|
+
async function removeAll(all: readonly string[], root: string): Promise<string[]> {
|
|
794
|
+
const files = notThroughLink(all, root)
|
|
793
795
|
const refused = (f: string, err: NodeJS.ErrnoException): UserError => {
|
|
794
796
|
const rel = path.relative(root, f).split(path.sep).join('/')
|
|
795
797
|
return new UserError(
|
|
@@ -806,7 +808,7 @@ async function removeAll(files: readonly string[], root: string): Promise<void>
|
|
|
806
808
|
throw refused(f, err as NodeJS.ErrnoException)
|
|
807
809
|
}
|
|
808
810
|
}
|
|
809
|
-
return
|
|
811
|
+
return files
|
|
810
812
|
}
|
|
811
813
|
await Promise.all(
|
|
812
814
|
files.map((f) =>
|
|
@@ -815,6 +817,39 @@ async function removeAll(files: readonly string[], root: string): Promise<void>
|
|
|
815
817
|
}),
|
|
816
818
|
),
|
|
817
819
|
)
|
|
820
|
+
return files
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
/**
|
|
824
|
+
* The paths whose directory is really where it sits, not reached through a
|
|
825
|
+
* symlinked directory. A save follows `dist -> real-out` on purpose
|
|
826
|
+
* (turborepo#13042), but a clean through a link deletes the target's
|
|
827
|
+
* files: `public -> static` in the same project took the tracked
|
|
828
|
+
* `static/logo.svg` before every run (X-5). The link is a declared
|
|
829
|
+
* output's own entry; what it leads to is not.
|
|
830
|
+
*/
|
|
831
|
+
function notThroughLink(files: readonly string[], root: string): string[] {
|
|
832
|
+
const real = (p: string): string | null => {
|
|
833
|
+
try {
|
|
834
|
+
return realpathSync(p)
|
|
835
|
+
} catch {
|
|
836
|
+
return null
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
const realRoot = real(root) ?? root
|
|
840
|
+
const own = new Map<string, boolean>()
|
|
841
|
+
return files.filter((f) => {
|
|
842
|
+
const dir = path.dirname(f)
|
|
843
|
+
let ok = own.get(dir)
|
|
844
|
+
if (ok === undefined) {
|
|
845
|
+
// A directory already gone has nothing to delete through; its path
|
|
846
|
+
// stays so the prune still reaches the parents it emptied.
|
|
847
|
+
const r = real(dir)
|
|
848
|
+
ok = r === null || r === path.join(realRoot, path.relative(root, dir))
|
|
849
|
+
own.set(dir, ok)
|
|
850
|
+
}
|
|
851
|
+
return ok
|
|
852
|
+
})
|
|
818
853
|
}
|
|
819
854
|
|
|
820
855
|
/**
|
|
@@ -920,8 +955,7 @@ export async function cleanWorkspaceOutputs(args: {
|
|
|
920
955
|
outputs: string[]
|
|
921
956
|
}): Promise<string[]> {
|
|
922
957
|
const files = await resolveWorkspaceOutputs(args)
|
|
923
|
-
await removeAll(files, args.workspaceRoot)
|
|
924
|
-
await pruneEmptiedDirs(args.workspaceRoot, files)
|
|
958
|
+
await pruneEmptiedDirs(args.workspaceRoot, await removeAll(files, args.workspaceRoot))
|
|
925
959
|
return files.map((f) => path.relative(args.workspaceRoot, f).split(path.sep).join('/'))
|
|
926
960
|
}
|
|
927
961
|
|
package/src/util/index.ts
CHANGED
package/src/util/paths.ts
CHANGED
|
@@ -366,6 +366,16 @@ export function slashBraceExpansions(pattern: string): string[] {
|
|
|
366
366
|
return [pattern]
|
|
367
367
|
}
|
|
368
368
|
|
|
369
|
+
/**
|
|
370
|
+
* Whether a path git lists as UNTRACKED is an installed dependency: it sits
|
|
371
|
+
* under a `node_modules` directory. Such a file is never an input, and the
|
|
372
|
+
* enumeration drops it. A TRACKED file there (a committed test fixture) is
|
|
373
|
+
* a source like any other: dropped by path, an edit to it was a stale hit.
|
|
374
|
+
*/
|
|
375
|
+
export function isInstalledPath(rel: string): boolean {
|
|
376
|
+
return rel.startsWith('node_modules/') || rel.includes('/node_modules/')
|
|
377
|
+
}
|
|
378
|
+
|
|
369
379
|
// oxlint-disable-next-line no-control-regex
|
|
370
380
|
const CONTROL = /[\x00-\x1f\x7f-\x9f]/g
|
|
371
381
|
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
UserError,
|
|
16
16
|
gitSpawnRefusal,
|
|
17
17
|
isExecutableMissing,
|
|
18
|
+
isInstalledPath,
|
|
18
19
|
relPosix,
|
|
19
20
|
taskGlob,
|
|
20
21
|
} from '../util/index.js'
|
|
@@ -234,7 +235,11 @@ export async function affectedChanges(
|
|
|
234
235
|
|
|
235
236
|
// vx-lock.json (workspace-root metadata) is excluded like a gitignored
|
|
236
237
|
// file: re-running `vx lock` must not mark every project affected.
|
|
237
|
-
|
|
238
|
+
// An untracked file under `node_modules` is an install, which the input
|
|
239
|
+
// enumeration drops too (`isInstalledPath`); a tracked one is a source.
|
|
240
|
+
const changed = [...diffed, ...untracked.filter((s) => !isInstalledPath(s))].filter(
|
|
241
|
+
(s) => s !== LOCKFILE_NAME,
|
|
242
|
+
)
|
|
238
243
|
|
|
239
244
|
// A lockfile or workspace-definition change re-keys EVERY task, because the
|
|
240
245
|
// workspace fingerprint folds those files into every cache key. Mapping
|