@vzn/vx 0.0.153 → 0.0.155
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.155",
|
|
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.155",
|
|
21
|
+
"@vzn/vx-linux-arm64": "0.0.155",
|
|
22
|
+
"@vzn/vx-darwin-x64": "0.0.155",
|
|
23
|
+
"@vzn/vx-darwin-arm64": "0.0.155"
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
26
|
"@anthropic-ai/sandbox-runtime": "^0.0.76"
|
package/src/cli/watch-filter.ts
CHANGED
|
@@ -52,7 +52,7 @@ export function isIgnoredWatchPath(rel: string): boolean {
|
|
|
52
52
|
export function makeWatchIgnore(
|
|
53
53
|
cacheDir: string,
|
|
54
54
|
outputs: ReadonlyMap<string, readonly string[]> = new Map(),
|
|
55
|
-
inputs: ReadonlyMap<string, readonly string[]
|
|
55
|
+
inputs: ReadonlyMap<string, ReadonlyArray<readonly string[]>> = new Map(),
|
|
56
56
|
): (base: string, filename: string) => boolean {
|
|
57
57
|
const cacheAbs = path.resolve(cacheDir)
|
|
58
58
|
// A task's own outputs are not edits: without this every cycle that
|
|
@@ -82,23 +82,37 @@ export function makeWatchIgnore(
|
|
|
82
82
|
// A path some task takes as an INPUT is never an output to ignore, even
|
|
83
83
|
// when another task declares it one: an in-place formatter declaring
|
|
84
84
|
// `src/**` hid every `src` edit from a `build` watched beside it, and no
|
|
85
|
-
// cycle ran (item 946).
|
|
86
|
-
//
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
85
|
+
// cycle ran (item 946). Each task's inputs are judged on their own, less
|
|
86
|
+
// their `!` entries (its own outputs among them): a turbo() task reading
|
|
87
|
+
// `**/*` took its own `dist/` write for an edit, and every save ran one
|
|
88
|
+
// more "up-to-date" cycle (2026-09-28).
|
|
89
|
+
const read = [...inputs].flatMap(([dir, tasks]) =>
|
|
90
|
+
tasks.map(
|
|
91
|
+
(globs) =>
|
|
92
|
+
[
|
|
93
|
+
path.resolve(dir),
|
|
94
|
+
asTrees(globs.filter((g) => !g.startsWith('!'))).map((g) => taskGlob(g)),
|
|
95
|
+
asTrees(globs.filter((g) => g.startsWith('!')).map((g) => g.slice(1))).map((g) =>
|
|
96
|
+
taskGlob(g),
|
|
97
|
+
),
|
|
98
|
+
// The directory an own output lives in is the task's too (see below).
|
|
99
|
+
globs
|
|
100
|
+
.filter((g) => g.startsWith('!'))
|
|
101
|
+
.map((g) => outputContainer(g.slice(1)))
|
|
102
|
+
.filter((c) => c !== ''),
|
|
103
|
+
] as const,
|
|
104
|
+
),
|
|
93
105
|
)
|
|
94
106
|
const isInput = (abs: string): boolean =>
|
|
95
|
-
read.some(([dir, globs]) => {
|
|
107
|
+
read.some(([dir, globs, not, containers]) => {
|
|
96
108
|
if (!abs.startsWith(dir + path.sep)) return false
|
|
97
109
|
const rel = abs
|
|
98
110
|
.slice(dir.length + 1)
|
|
99
111
|
.split(path.sep)
|
|
100
112
|
.join('/')
|
|
101
|
-
|
|
113
|
+
if (!globs.some((g) => g.match(rel))) return false
|
|
114
|
+
if (not.some((g) => g.match(rel))) return false
|
|
115
|
+
return !containers.some((c) => c === rel || c.startsWith(`${rel}/`))
|
|
102
116
|
})
|
|
103
117
|
return (base, filename) => {
|
|
104
118
|
if (isIgnoredWatchPath(filename)) return true
|
package/src/cli/watch-set.ts
CHANGED
|
@@ -56,8 +56,12 @@ export interface ConfigSweep {
|
|
|
56
56
|
workspaceWide: boolean
|
|
57
57
|
workspaceInputs: string[]
|
|
58
58
|
outputs: Map<string, string[]>
|
|
59
|
-
/**
|
|
60
|
-
|
|
59
|
+
/**
|
|
60
|
+
* Each task's input globs per directory they are relative to, the
|
|
61
|
+
* counterweight to `outputs`; the task's own outputs ride along as `!`
|
|
62
|
+
* entries, as its key leaves them out.
|
|
63
|
+
*/
|
|
64
|
+
inputs: Map<string, string[][]>
|
|
61
65
|
/** Projects with a task that has a command and no cache: it reads what it likes, git-ignored files included. */
|
|
62
66
|
uncached: Set<string>
|
|
63
67
|
/** Files the project configs import by relative path (item 949). */
|
|
@@ -86,7 +90,19 @@ export async function sweepConfigs(
|
|
|
86
90
|
load: CliLoadOptions = {},
|
|
87
91
|
): Promise<ConfigSweep> {
|
|
88
92
|
const outputs = new Map<string, string[]>()
|
|
89
|
-
const inputs = new Map<string, string[]>()
|
|
93
|
+
const inputs = new Map<string, string[][]>()
|
|
94
|
+
// One task's inputs, less what it writes itself: the key leaves a task's
|
|
95
|
+
// own outputs out of its inputs, so a turbo() task reading `**/*` does not
|
|
96
|
+
// read the `dist/` it builds, and its write is no edit to watch.
|
|
97
|
+
const addInputs = (
|
|
98
|
+
dir: string,
|
|
99
|
+
files: readonly string[] | undefined,
|
|
100
|
+
own: readonly string[] | undefined,
|
|
101
|
+
): void => {
|
|
102
|
+
if (files === undefined || files.length === 0) return
|
|
103
|
+
const minus = (own ?? []).filter((o) => !o.startsWith('!')).map((o) => `!${o}`)
|
|
104
|
+
inputs.set(dir, [...(inputs.get(dir) ?? []), [...files, ...minus]])
|
|
105
|
+
}
|
|
90
106
|
const addTo = (
|
|
91
107
|
into: Map<string, string[]>,
|
|
92
108
|
dir: string,
|
|
@@ -104,8 +120,12 @@ export async function sweepConfigs(
|
|
|
104
120
|
if (task.cache === undefined && task.exec !== undefined && task.exec.persistent === undefined)
|
|
105
121
|
uncached.add(dir)
|
|
106
122
|
for (const g of task.cache?.inputs?.workspaceFiles ?? []) workspaceInputs.add(g)
|
|
107
|
-
|
|
108
|
-
|
|
123
|
+
addInputs(dir, task.cache?.inputs?.files, task.cache?.outputs?.files)
|
|
124
|
+
addInputs(
|
|
125
|
+
workspaceRoot,
|
|
126
|
+
task.cache?.inputs?.workspaceFiles,
|
|
127
|
+
task.cache?.outputs?.workspaceFiles,
|
|
128
|
+
)
|
|
109
129
|
add(dir, task.cache?.outputs?.files)
|
|
110
130
|
add(workspaceRoot, task.cache?.outputs?.workspaceFiles)
|
|
111
131
|
}
|
package/src/cli/watch.ts
CHANGED
|
@@ -272,7 +272,7 @@ interface WatchLoopArgs {
|
|
|
272
272
|
/** Declared output globs per directory they are relative to (project dir, or the root for `workspaceFiles`). */
|
|
273
273
|
outputs: ReadonlyMap<string, readonly string[]>
|
|
274
274
|
/** Declared input globs per directory: never ignored as another task's output. */
|
|
275
|
-
inputs: ReadonlyMap<string, readonly string[]
|
|
275
|
+
inputs: ReadonlyMap<string, ReadonlyArray<readonly string[]>>
|
|
276
276
|
/** Projects whose uncached task may read a git-ignored file: such a path there is still an edit. */
|
|
277
277
|
uncached: ReadonlySet<string>
|
|
278
278
|
/** Files the configs import: an edit to one outside the watched projects is a cycle that re-reads them. */
|
|
@@ -299,7 +299,7 @@ interface Rediscovered {
|
|
|
299
299
|
workspaceWide: boolean
|
|
300
300
|
workspaceInputs: readonly string[]
|
|
301
301
|
outputs: ReadonlyMap<string, readonly string[]>
|
|
302
|
-
inputs: ReadonlyMap<string, readonly string[]
|
|
302
|
+
inputs: ReadonlyMap<string, ReadonlyArray<readonly string[]>>
|
|
303
303
|
uncached: ReadonlySet<string>
|
|
304
304
|
configImports: readonly string[]
|
|
305
305
|
workspaceConfigImports: readonly string[]
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// ownership moves to the run's registry, and this is the one place that
|
|
4
4
|
// decides which of them outlive the graph and how the rest go down.
|
|
5
5
|
|
|
6
|
-
import type
|
|
6
|
+
import { isGroupTask, type TaskNode } from '../graph/index.js'
|
|
7
7
|
import { killGraceMs } from '../util/index.js'
|
|
8
8
|
import { holdGroups, killTree, untilGroupsGone } from '../exec/index.js'
|
|
9
9
|
|
|
@@ -25,9 +25,12 @@ export interface KeepAlive {
|
|
|
25
25
|
/**
|
|
26
26
|
* A persistent task the user REQUESTED (or one surfaced for display) is the
|
|
27
27
|
* run's whole purpose — it is left running and blocked on at the very end,
|
|
28
|
-
* after the summary.
|
|
29
|
-
*
|
|
30
|
-
*
|
|
28
|
+
* after the summary. So is every persistent task a kept one depends on,
|
|
29
|
+
* directly or through groups: `vx run dev --filter app` stopped the api#dev
|
|
30
|
+
* its app#dev was started against (C-46). A one-shot's persistent deps are
|
|
31
|
+
* not kept on its account; it has finished with them. Only in the real CLI
|
|
32
|
+
* foreground: a custom logger or `handleSignals: false` (watch mode,
|
|
33
|
+
* embedders) expects `run()` to return, not to sit on a server.
|
|
31
34
|
*/
|
|
32
35
|
export function selectKeepAlive(
|
|
33
36
|
registry: ReadonlyMap<string, Child>,
|
|
@@ -36,13 +39,25 @@ export function selectKeepAlive(
|
|
|
36
39
|
): KeepAlive {
|
|
37
40
|
const out: KeepAlive = { nodes: [], children: [] }
|
|
38
41
|
if (!foreground) return out
|
|
39
|
-
|
|
42
|
+
const stack = [...registry.keys()].filter((id) => {
|
|
40
43
|
const n = nodes.get(id)
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
+
return n !== undefined && (n.requested || n.surfaced === true)
|
|
45
|
+
})
|
|
46
|
+
const reached = new Set<string>()
|
|
47
|
+
while (stack.length > 0) {
|
|
48
|
+
const id = stack.pop()!
|
|
49
|
+
if (reached.has(id)) continue
|
|
50
|
+
reached.add(id)
|
|
51
|
+
for (const dep of nodes.get(id)?.deps ?? []) {
|
|
52
|
+
const d = nodes.get(dep)
|
|
53
|
+
if (d !== undefined && (registry.has(dep) || isGroupTask(d))) stack.push(dep)
|
|
44
54
|
}
|
|
45
55
|
}
|
|
56
|
+
for (const [id, child] of registry) {
|
|
57
|
+
if (!reached.has(id)) continue
|
|
58
|
+
out.nodes.push(nodes.get(id)!)
|
|
59
|
+
out.children.push(child)
|
|
60
|
+
}
|
|
46
61
|
return out
|
|
47
62
|
}
|
|
48
63
|
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
// that implements neither still prefetches, per-hash.
|
|
21
21
|
|
|
22
22
|
import type { TaskNode } from '../graph/index.js'
|
|
23
|
+
import { maskedCommand } from '../util/index.js'
|
|
23
24
|
import type { CacheLayer, GitFilesCache } from '../cache/index.js'
|
|
24
25
|
import type { HashCache } from './task-hash.js'
|
|
25
26
|
import { deriveStableKeys } from './stable-keys.js'
|
|
@@ -114,7 +115,9 @@ export function getContext(node: TaskNode, command: string) {
|
|
|
114
115
|
const outputs = node.config.cache?.outputs
|
|
115
116
|
return {
|
|
116
117
|
taskId: node.id,
|
|
117
|
-
|
|
118
|
+
// Stored in the entry row a remote hit inserts: masked as a save's is,
|
|
119
|
+
// or the token a config inlined sat in cache.db (L-27).
|
|
120
|
+
command: maskedCommand(command, node.config.exec?.env),
|
|
118
121
|
outputs: { files: outputs?.files ?? [], workspaceFiles: outputs?.workspaceFiles ?? [] },
|
|
119
122
|
}
|
|
120
123
|
}
|