@vzn/vx 0.0.39 → 0.0.41
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/cli/help.ts +1 -1
- package/src/cli/index.ts +9 -1
- package/src/cli/workspace-config.ts +1 -0
- package/src/orchestrator/doctor.ts +1 -0
- package/src/orchestrator/prepare.ts +1 -1
- package/src/orchestrator/projects.ts +1 -0
- package/src/workspace/config-cache.ts +52 -9
- package/src/workspace/project-loader.ts +3 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vzn/vx",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.41",
|
|
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.41",
|
|
21
|
+
"@vzn/vx-linux-arm64": "0.0.41",
|
|
22
|
+
"@vzn/vx-darwin-x64": "0.0.41",
|
|
23
|
+
"@vzn/vx-darwin-arm64": "0.0.41"
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
26
|
"@anthropic-ai/sandbox-runtime": "^0.0.76"
|
package/src/cli/help.ts
CHANGED
|
@@ -49,7 +49,7 @@ export function helpText(pluginCommands: readonly string[] = []): string {
|
|
|
49
49
|
' vx why [TASK | PKG#TASK] [--run <runId>] [--format pretty|json] [--cache-dir <path>]',
|
|
50
50
|
' vx last [RUNID] [--list[=N]] [--format pretty|json] [--cache-dir <path>]',
|
|
51
51
|
' vx completions bash|zsh|fish',
|
|
52
|
-
' vx help',
|
|
52
|
+
' vx help [VERB]',
|
|
53
53
|
' vx version',
|
|
54
54
|
'',
|
|
55
55
|
'Selection (for run):',
|
package/src/cli/index.ts
CHANGED
|
@@ -32,9 +32,17 @@ export async function run(argv: readonly string[]): Promise<number> {
|
|
|
32
32
|
case undefined:
|
|
33
33
|
case '--help':
|
|
34
34
|
case '-h':
|
|
35
|
-
case 'help':
|
|
36
35
|
printHelp(await pluginCommandHelp())
|
|
37
36
|
return 0
|
|
37
|
+
case 'help': {
|
|
38
|
+
// `vx help run` is the same question as `vx run --help`. A plugin verb
|
|
39
|
+
// owns its own help, so anything but a core verb gets the whole
|
|
40
|
+
// reference, which lists the plugin verbs.
|
|
41
|
+
const verb = rest[0]
|
|
42
|
+
const core = verb !== undefined && (CORE_VERBS as readonly string[]).includes(verb)
|
|
43
|
+
printHelp(await pluginCommandHelp(), core ? verb : undefined)
|
|
44
|
+
return 0
|
|
45
|
+
}
|
|
38
46
|
case '--version':
|
|
39
47
|
case 'version':
|
|
40
48
|
process.stdout.write(`vx ${VERSION}\n`)
|
|
@@ -258,7 +258,7 @@ export async function prepareRun(options: RunOptions, log: Logger): Promise<Prep
|
|
|
258
258
|
projectMetas,
|
|
259
259
|
packageGraph,
|
|
260
260
|
lock,
|
|
261
|
-
evalCache: { store: localCache, workspaceFingerprint: fingerprints.all },
|
|
261
|
+
evalCache: { store: localCache, workspaceRoot, workspaceFingerprint: fingerprints.all },
|
|
262
262
|
warn: (m: string) => log.status(m),
|
|
263
263
|
}
|
|
264
264
|
let loaded: LoadedProjects
|
|
@@ -109,6 +109,13 @@ export interface ConfigEvalKeyArgs {
|
|
|
109
109
|
*/
|
|
110
110
|
hashFile?: (file: string) => Promise<string>
|
|
111
111
|
bytes: Uint8Array
|
|
112
|
+
/**
|
|
113
|
+
* The workspace root, as the config path spells it. A symlink BELOW it on
|
|
114
|
+
* the way to the config makes the closure unindexable; one above it
|
|
115
|
+
* (macOS's `/var`) moves nothing the warm path could miss. Absent, any
|
|
116
|
+
* symlink on the way does.
|
|
117
|
+
*/
|
|
118
|
+
workspaceRoot?: string | undefined
|
|
112
119
|
workspaceFingerprint: string
|
|
113
120
|
}
|
|
114
121
|
|
|
@@ -132,9 +139,10 @@ const MAX_CLOSURE_FILES = 32
|
|
|
132
139
|
// literal naming it was stripped before the test ran; listed 2026-09-27
|
|
133
140
|
// (item 957). A key assembled at run time (`'constru' + 'ctor'`) still
|
|
134
141
|
// passes: the gate stops accidental impurity, not a config written to
|
|
135
|
-
// defeat it.
|
|
142
|
+
// defeat it. `random` is denied as a word, not as `Math.random`: a
|
|
143
|
+
// destructured `const { random } = Math` was cached as pure (item 1036).
|
|
136
144
|
const IMPURE_RE =
|
|
137
|
-
/\b(?:process|Bun|globalThis|global|self|fetch|Date|Temporal|Intl|crypto|performance|navigator|require|eval|Function|constructor|localeCompare|await|toLocale\w*|Reflect|getPrototypeOf|setPrototypeOf|getOwnPropertyNames|getOwnPropertyDescriptor|getOwnPropertyDescriptors|__proto__|prototype|__defineGetter__|__defineSetter__|__lookupGetter__|__lookupSetter__)\b|import\s*\.\s*meta
|
|
145
|
+
/\b(?:process|Bun|globalThis|global|self|fetch|Date|Temporal|Intl|crypto|performance|navigator|require|eval|Function|constructor|localeCompare|await|toLocale\w*|Reflect|getPrototypeOf|setPrototypeOf|getOwnPropertyNames|getOwnPropertyDescriptor|getOwnPropertyDescriptors|__proto__|prototype|__defineGetter__|__defineSetter__|__lookupGetter__|__lookupSetter__|random)\b|import\s*\.\s*meta|\bimport\s*\(/
|
|
138
146
|
|
|
139
147
|
/** Literal text that names a way to `Function` when used as a computed key. */
|
|
140
148
|
const IMPURE_LITERAL_RE = /constructor|__proto__|prototype/
|
|
@@ -143,10 +151,15 @@ const IMPURE_LITERAL_RE = /constructor|__proto__|prototype/
|
|
|
143
151
|
// matched on `stripLiterals` output with its strings kept as placeholders
|
|
144
152
|
// (`\0<n>\0`): a comment is gone, so an import after `/* … */` on its line
|
|
145
153
|
// is seen, and a string-named binding (`import { 'a-b' as x }`) is a
|
|
146
|
-
// placeholder like any other.
|
|
147
|
-
// specifier lists match
|
|
154
|
+
// placeholder like any other. The body spans newlines, so multi-line
|
|
155
|
+
// specifier lists match, but never another statement's `import` or
|
|
156
|
+
// `export`: in a file without semicolons a lazy `[^;]*?` ran from
|
|
157
|
+
// `export type Mode = …` on to the next line's `from '@vzn/vx'`, and the
|
|
158
|
+
// purity check read `type` and passed an impure import. Nor is a line
|
|
159
|
+
// start required: `} export * from './side.ts'` went unscanned, and the
|
|
160
|
+
// fail-closed count below counted only imports (item 1036).
|
|
148
161
|
const IMPORT_RE =
|
|
149
|
-
|
|
162
|
+
/\b(?:import|export)\b(?:(?!\b(?:import|export)\b)[^;])*?\bfrom\s*\0(\d+)\0|\bimport\s*\0(\d+)\0/g
|
|
150
163
|
|
|
151
164
|
/**
|
|
152
165
|
* Every static import in `source` as its specifier and statement, or `null`
|
|
@@ -384,6 +397,31 @@ function realDirOf(file: string): string {
|
|
|
384
397
|
return realpathSync(path.dirname(file))
|
|
385
398
|
}
|
|
386
399
|
|
|
400
|
+
/** Each workspace root's real path, asked once per process. */
|
|
401
|
+
const realRoots = new Map<string, string>()
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* Whether `realDir`, the real directory of `file`, is where its spelling
|
|
405
|
+
* says it is once the root's own links are resolved. A member directory
|
|
406
|
+
* linked in from elsewhere is not: the warm path keys the closure by the
|
|
407
|
+
* paths the last evaluation found, and a link retargeted to a config of
|
|
408
|
+
* the same bytes whose preset differs moves none of them (item 1036).
|
|
409
|
+
*/
|
|
410
|
+
function reachedThroughNoLink(file: string, realDir: string, root: string | undefined): boolean {
|
|
411
|
+
const dir = path.dirname(file)
|
|
412
|
+
// Without a root there is nothing to tell a member's own link from one
|
|
413
|
+
// above the tree (macOS's `/tmp` → `/private/tmp`), and comparing the
|
|
414
|
+
// spelling with the real path refused every config under such a link.
|
|
415
|
+
// Every run passes its root; a caller that does not keeps the index.
|
|
416
|
+
if (root === undefined) return true
|
|
417
|
+
let realRoot = realRoots.get(root)
|
|
418
|
+
if (realRoot === undefined) {
|
|
419
|
+
realRoot = realpathSync(root)
|
|
420
|
+
realRoots.set(root, realRoot)
|
|
421
|
+
}
|
|
422
|
+
return path.relative(realRoot, realDir) === path.relative(root, dir)
|
|
423
|
+
}
|
|
424
|
+
|
|
387
425
|
/** A regular file reached through no symlink: the path IS the file. */
|
|
388
426
|
function isCanonicalFile(file: string): boolean {
|
|
389
427
|
try {
|
|
@@ -439,10 +477,15 @@ export async function configEvalKey(a: ConfigEvalKeyArgs): Promise<ConfigEvalKey
|
|
|
439
477
|
// (item 950). The named file is taken without `Bun.resolveSync`,
|
|
440
478
|
// whose directory cache answers a retargeted link with its old
|
|
441
479
|
// target for the rest of the process.
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
480
|
+
if (dir === undefined) {
|
|
481
|
+
try {
|
|
482
|
+
dir = realDirOf(file)
|
|
483
|
+
if (file === a.configPath && !reachedThroughNoLink(file, dir, a.workspaceRoot)) {
|
|
484
|
+
indexable = false
|
|
485
|
+
}
|
|
486
|
+
} catch {
|
|
487
|
+
return null
|
|
488
|
+
}
|
|
446
489
|
}
|
|
447
490
|
const named = path.resolve(dir, spec)
|
|
448
491
|
let resolved: string
|
|
@@ -233,7 +233,7 @@ export interface LoadProjectConfigOptions {
|
|
|
233
233
|
* Serve a provably-pure config from its cached evaluation (see
|
|
234
234
|
* config-cache.ts) and store a fresh one for next time. Off for `fresh`.
|
|
235
235
|
*/
|
|
236
|
-
evalCache?: { store: ConfigEvalStore; workspaceFingerprint: string }
|
|
236
|
+
evalCache?: { store: ConfigEvalStore; workspaceRoot?: string; workspaceFingerprint: string }
|
|
237
237
|
}
|
|
238
238
|
|
|
239
239
|
/**
|
|
@@ -296,6 +296,7 @@ export async function loadProjectConfigs(
|
|
|
296
296
|
: await configEvalKey({
|
|
297
297
|
configPath,
|
|
298
298
|
bytes,
|
|
299
|
+
workspaceRoot: evalCache.workspaceRoot,
|
|
299
300
|
workspaceFingerprint: evalCache.workspaceFingerprint,
|
|
300
301
|
...slowKeyHash,
|
|
301
302
|
})
|
|
@@ -352,6 +353,7 @@ export async function loadProjectConfigs(
|
|
|
352
353
|
const keyed = await configEvalKey({
|
|
353
354
|
configPath,
|
|
354
355
|
bytes,
|
|
356
|
+
workspaceRoot: evalCache!.workspaceRoot,
|
|
355
357
|
workspaceFingerprint: evalCache!.workspaceFingerprint,
|
|
356
358
|
...slowKeyHash,
|
|
357
359
|
})
|