@vzn/vx 0.0.244 → 0.0.246
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.246",
|
|
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.246",
|
|
21
|
+
"@vzn/vx-linux-arm64": "0.0.246",
|
|
22
|
+
"@vzn/vx-darwin-x64": "0.0.246",
|
|
23
|
+
"@vzn/vx-darwin-arm64": "0.0.246"
|
|
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
|
|
@@ -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)
|
|
@@ -96,8 +96,8 @@ const OWN_COMMANDS: Readonly<Record<string, ReadonlySet<string>>> = {
|
|
|
96
96
|
}
|
|
97
97
|
|
|
98
98
|
/**
|
|
99
|
-
*
|
|
100
|
-
* around a script. The manager is the nearest `packageManager` field or
|
|
99
|
+
* The package manager that owns `dir`, when it runs `pre` / `post` hooks
|
|
100
|
+
* around a script; null when it runs none. The manager is the nearest `packageManager` field or
|
|
101
101
|
* lockfile, and its settings are read beside it, as each reads them for a
|
|
102
102
|
* member too. Probed with a `prebuild` / `build` / `postbuild` trio: Yarn
|
|
103
103
|
* 2+ runs none (D-31); npm runs none under `ignore-scripts=true` in
|
|
@@ -105,7 +105,7 @@ const OWN_COMMANDS: Readonly<Record<string, ReadonlySet<string>>> = {
|
|
|
105
105
|
* `enablePrePostScripts: false` in `pnpm-workspace.yaml` (D-33); Bun and
|
|
106
106
|
* Yarn 1 run them whatever those say. No manager found is npm's default.
|
|
107
107
|
*/
|
|
108
|
-
function runsScriptHooks(dir: string, memo: Map<string,
|
|
108
|
+
function runsScriptHooks(dir: string, memo: Map<string, string | null>): string | null {
|
|
109
109
|
const known = memo.get(dir)
|
|
110
110
|
if (known !== undefined) return known
|
|
111
111
|
const read = (f: string): string | undefined => {
|
|
@@ -129,15 +129,15 @@ function runsScriptHooks(dir: string, memo: Map<string, boolean>): boolean {
|
|
|
129
129
|
else if (existsSync(path.join(dir, 'package-lock.json'))) manager = 'npm'
|
|
130
130
|
else if (['bun.lock', 'bun.lockb'].some((f) => existsSync(path.join(dir, f)))) manager = 'bun'
|
|
131
131
|
}
|
|
132
|
-
let answer:
|
|
132
|
+
let answer: string | null
|
|
133
133
|
if (manager === undefined) {
|
|
134
134
|
const up = path.dirname(dir)
|
|
135
|
-
answer = up === dir ?
|
|
135
|
+
answer = up === dir ? 'npm' : runsScriptHooks(up, memo)
|
|
136
136
|
} else {
|
|
137
137
|
const npmrc = read('.npmrc') ?? ''
|
|
138
138
|
const set = (key: string, value: string): boolean =>
|
|
139
139
|
new RegExp(`^\\s*${key}\\s*=\\s*${value}\\s*$`, 'm').test(npmrc)
|
|
140
|
-
|
|
140
|
+
const runs =
|
|
141
141
|
manager === 'berry'
|
|
142
142
|
? false
|
|
143
143
|
: manager === 'npm'
|
|
@@ -146,6 +146,7 @@ function runsScriptHooks(dir: string, memo: Map<string, boolean>): boolean {
|
|
|
146
146
|
? !set('enable-pre-post-scripts', 'false') &&
|
|
147
147
|
!/^enablePrePostScripts:\s*false\s*$/m.test(read('pnpm-workspace.yaml') ?? '')
|
|
148
148
|
: true
|
|
149
|
+
answer = runs ? manager : null
|
|
149
150
|
}
|
|
150
151
|
memo.set(dir, answer)
|
|
151
152
|
return answer
|
|
@@ -242,7 +243,7 @@ function upstreamBuildOnWorker(tasks: GeneratedTask[]): void {
|
|
|
242
243
|
|
|
243
244
|
export function migrateScripts(metas: readonly ProjectMeta[]): MigrationPlan {
|
|
244
245
|
const projects: GeneratedProject[] = []
|
|
245
|
-
const hookMemo = new Map<string,
|
|
246
|
+
const hookMemo = new Map<string, string | null>()
|
|
246
247
|
// The workspace root among members: its scripts run the workspace
|
|
247
248
|
// (`npm run build --workspaces`, `pnpm -r build`), and a root task made
|
|
248
249
|
// of one ran every member's build again under `--all`. Since D-39 a
|
|
@@ -258,7 +259,8 @@ export function migrateScripts(metas: readonly ProjectMeta[]): MigrationPlan {
|
|
|
258
259
|
for (const meta of metas) {
|
|
259
260
|
if (meta === root) continue
|
|
260
261
|
const scripts = scriptsOf(meta)
|
|
261
|
-
const
|
|
262
|
+
const hooksBy = runsScriptHooks(meta.dir, hookMemo)
|
|
263
|
+
const runsHooks = hooksBy !== null
|
|
262
264
|
const runnable = Object.keys(scripts).filter(
|
|
263
265
|
(n) => typeof scripts[n] === 'string' && scripts[n] !== '',
|
|
264
266
|
)
|
|
@@ -314,7 +316,7 @@ export function migrateScripts(metas: readonly ProjectMeta[]): MigrationPlan {
|
|
|
314
316
|
)
|
|
315
317
|
if (hooks.length > 0) {
|
|
316
318
|
todos.push(
|
|
317
|
-
|
|
319
|
+
`${hooksBy} ran ${hooks.map((h) => `\`${h}\``).join(' and ')} around this script without being asked; folded into the command in that order`,
|
|
318
320
|
)
|
|
319
321
|
}
|
|
320
322
|
const exec: Record<string, unknown> = { command }
|