@vzn/vx 0.0.437 → 0.0.439
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/cache.ts +2 -1
- package/src/cli/run.ts +5 -3
- package/src/cli/watch.ts +1 -1
- package/src/workspace/config-cache.ts +16 -0
- package/src/workspace/workspace.ts +38 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vzn/vx",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.439",
|
|
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.439",
|
|
25
|
+
"@vzn/vx-linux-arm64": "0.0.439",
|
|
26
|
+
"@vzn/vx-darwin-x64": "0.0.439",
|
|
27
|
+
"@vzn/vx-darwin-arm64": "0.0.439"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
30
|
"@anthropic-ai/sandbox-runtime": "^0.0.76"
|
package/src/cli/cache.ts
CHANGED
|
@@ -36,7 +36,8 @@ function cacheSubHint(sub: string): string {
|
|
|
36
36
|
const elsewhere = CACHE_ELSEWHERE[sub]
|
|
37
37
|
if (elsewhere !== undefined) return ` — ${elsewhere}`
|
|
38
38
|
const best = nearest(sub, ['prune'])
|
|
39
|
-
|
|
39
|
+
// `prune` is the one subcommand: name it when nothing is near (M-58).
|
|
40
|
+
return best === undefined ? '. The subcommand is prune' : `. Did you mean ${best}?`
|
|
40
41
|
}
|
|
41
42
|
|
|
42
43
|
export async function cacheCmd(args: readonly string[]): Promise<number> {
|
package/src/cli/run.ts
CHANGED
|
@@ -112,7 +112,9 @@ export function parseConcurrency(v: string, cpus = machineParallelism()): number
|
|
|
112
112
|
return n === null || n < 1 ? null : n
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
-
|
|
115
|
+
/** `verb` is the one being parsed for: `vx watch` reads `vx run`'s flags, and
|
|
116
|
+
* its refusals pointed at `vx run --help` (M-58). */
|
|
117
|
+
export function parseRunArgs(rawArgs: readonly string[], verb: 'run' | 'watch' = 'run'): RunArgs {
|
|
116
118
|
const out: RunArgs = {
|
|
117
119
|
tasks: [],
|
|
118
120
|
filters: [],
|
|
@@ -182,7 +184,7 @@ export function parseRunArgs(rawArgs: readonly string[]): RunArgs {
|
|
|
182
184
|
} else if (RETIRED_EXCLUDE_DEPENDENCIES.test(a ?? '')) {
|
|
183
185
|
return {
|
|
184
186
|
...out,
|
|
185
|
-
error: `unknown flag: ${a} (the flag is --exclude-dependencies)${seeHelp(
|
|
187
|
+
error: `unknown flag: ${a} (the flag is --exclude-dependencies)${seeHelp(verb)}`,
|
|
186
188
|
}
|
|
187
189
|
} else if (a === '--exclude-dependencies') {
|
|
188
190
|
out.excludeDependencies = 'all'
|
|
@@ -363,7 +365,7 @@ export function parseRunArgs(rawArgs: readonly string[]): RunArgs {
|
|
|
363
365
|
}
|
|
364
366
|
out.report = fmt
|
|
365
367
|
} else if (a !== undefined && a.startsWith('-')) {
|
|
366
|
-
return { ...out, error: `unknown flag: ${a}${flagHint(
|
|
368
|
+
return { ...out, error: `unknown flag: ${a}${flagHint(verb, a)}${seeHelp(verb)}` }
|
|
367
369
|
} else if (a !== undefined) {
|
|
368
370
|
out.tasks.push(a)
|
|
369
371
|
}
|
package/src/cli/watch.ts
CHANGED
|
@@ -76,7 +76,7 @@ export function watchRefusal(parsed: RunArgs): string | null {
|
|
|
76
76
|
}
|
|
77
77
|
|
|
78
78
|
export async function watchCmd(args: readonly string[]): Promise<number> {
|
|
79
|
-
const parsed = parseRunArgs(args)
|
|
79
|
+
const parsed = parseRunArgs(args, 'watch')
|
|
80
80
|
if (parsed.error) {
|
|
81
81
|
process.stderr.write(`vx watch: ${parsed.error}\n`)
|
|
82
82
|
return 1
|
|
@@ -254,6 +254,22 @@ export function stripLiterals(source: string, strings?: string[]): string | null
|
|
|
254
254
|
// expression is told apart from one closing an object literal inside it.
|
|
255
255
|
const templateDepth: number[] = []
|
|
256
256
|
while (i < n) {
|
|
257
|
+
// Plain code is copied a run at a time, up to the next `/`, quote or
|
|
258
|
+
// backtick: a character at a time took 9 ms over 1,000 cold configs,
|
|
259
|
+
// runs take 2 (2026-10-03).
|
|
260
|
+
if (templateDepth.length === 0) {
|
|
261
|
+
let j = i
|
|
262
|
+
while (j < n) {
|
|
263
|
+
const k = source.charCodeAt(j)
|
|
264
|
+
if (k === 0x2f || k === 0x27 || k === 0x22 || k === 0x60) break
|
|
265
|
+
j++
|
|
266
|
+
}
|
|
267
|
+
if (j > i) {
|
|
268
|
+
out += source.slice(i, j)
|
|
269
|
+
i = j
|
|
270
|
+
continue
|
|
271
|
+
}
|
|
272
|
+
}
|
|
257
273
|
const c = source[i]!
|
|
258
274
|
const next = source[i + 1]
|
|
259
275
|
if (c === '/' && next === '/') {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { constants, type Dirent } from 'node:fs'
|
|
2
|
-
import { access, readdir, stat } from 'node:fs/promises'
|
|
2
|
+
import { access, readdir, realpath, stat } from 'node:fs/promises'
|
|
3
3
|
import path from 'node:path'
|
|
4
4
|
import type { ProjectConfig, WorkspaceConfig } from '../config.js'
|
|
5
5
|
import {
|
|
@@ -675,7 +675,17 @@ export async function discoverProjects(
|
|
|
675
675
|
}
|
|
676
676
|
const projects: ProjectMeta[] = []
|
|
677
677
|
const shared: string[] = []
|
|
678
|
-
|
|
678
|
+
let rootReal: string | undefined
|
|
679
|
+
for (const [name, found] of byName) {
|
|
680
|
+
// One package reached by two paths (a member and a link to it) is one
|
|
681
|
+
// project, not a name two packages share: `apps/docs -> ../packages/docs`
|
|
682
|
+
// was refused as a duplicate and told to rename one (D-135). Resolved
|
|
683
|
+
// only here, so a workspace without a shared name pays nothing.
|
|
684
|
+
let group = found
|
|
685
|
+
if (group.length > 1) {
|
|
686
|
+
rootReal ??= await realpath(workspace.root)
|
|
687
|
+
group = await oneEntryPerPackage(group, workspace.root, rootReal)
|
|
688
|
+
}
|
|
679
689
|
if (group.length === 1) {
|
|
680
690
|
const { dir, pkg, configPath } = group[0]!
|
|
681
691
|
projects.push({ name, dir, packageJson: pkg, configPath })
|
|
@@ -715,6 +725,32 @@ export async function discoverProjects(
|
|
|
715
725
|
return projects.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0))
|
|
716
726
|
}
|
|
717
727
|
|
|
728
|
+
/**
|
|
729
|
+
* The entries of a name group, one per real directory. Of several paths to
|
|
730
|
+
* one package, the one that reaches it through no link inside the
|
|
731
|
+
* workspace is kept, else the first in path order.
|
|
732
|
+
*/
|
|
733
|
+
async function oneEntryPerPackage<T extends { dir: string }>(
|
|
734
|
+
group: T[],
|
|
735
|
+
root: string,
|
|
736
|
+
rootReal: string,
|
|
737
|
+
): Promise<T[]> {
|
|
738
|
+
const byReal = new Map<string, T[]>()
|
|
739
|
+
for (const entry of group) {
|
|
740
|
+
const real = await realpath(entry.dir)
|
|
741
|
+
const same = byReal.get(real)
|
|
742
|
+
if (same === undefined) byReal.set(real, [entry])
|
|
743
|
+
else same.push(entry)
|
|
744
|
+
}
|
|
745
|
+
if (byReal.size === group.length) return group
|
|
746
|
+
const kept: T[] = []
|
|
747
|
+
for (const [real, paths] of byReal) {
|
|
748
|
+
const direct = paths.find((e) => relPosix(root, e.dir) === relPosix(rootReal, real))
|
|
749
|
+
kept.push(direct ?? paths.sort((a, b) => (a.dir < b.dir ? -1 : 1))[0]!)
|
|
750
|
+
}
|
|
751
|
+
return kept
|
|
752
|
+
}
|
|
753
|
+
|
|
718
754
|
/**
|
|
719
755
|
* A directory a plugin's `discover` hook names, as a project meta; null
|
|
720
756
|
* when `known` already holds it under that name. `by` names the plugin in
|