@vzn/vx 0.0.241 → 0.0.243
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 +38 -1
- package/src/cache/index.ts +2 -0
- package/src/cli/run.ts +4 -3
- package/src/cli/select.ts +16 -3
- package/src/orchestrator/options.ts +2 -1
- package/src/orchestrator/plugin.ts +8 -0
- package/src/orchestrator/prepare.ts +15 -12
- package/src/orchestrator/projects.ts +21 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vzn/vx",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.243",
|
|
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.243",
|
|
21
|
+
"@vzn/vx-linux-arm64": "0.0.243",
|
|
22
|
+
"@vzn/vx-darwin-x64": "0.0.243",
|
|
23
|
+
"@vzn/vx-darwin-arm64": "0.0.243"
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
26
|
"@anthropic-ai/sandbox-runtime": "^0.0.76"
|
package/src/cache/git-inputs.ts
CHANGED
|
@@ -847,6 +847,11 @@ export interface GitEnumeration {
|
|
|
847
847
|
trusted: Map<string, string>
|
|
848
848
|
/** Whether the worktree had uncommitted changes; null when `git status` failed. */
|
|
849
849
|
dirty: boolean | null
|
|
850
|
+
/**
|
|
851
|
+
* What `git status` listed, workspace-relative: modified, staged or deleted
|
|
852
|
+
* paths (both sides of a rename) and untracked files. Null when it failed.
|
|
853
|
+
*/
|
|
854
|
+
changed: readonly string[] | null
|
|
850
855
|
/** Workspace-relative paths whose names are not UTF-8 (`decodeGitZ`). */
|
|
851
856
|
undecodable: readonly string[]
|
|
852
857
|
/** `Date.now()` before the spawns: what `trusted` says is true as of no earlier. */
|
|
@@ -1038,7 +1043,39 @@ export async function startGitEnumeration(
|
|
|
1038
1043
|
if (parsedStatus !== null && parsedStatus.undecodable.size > 0) {
|
|
1039
1044
|
undecodable.push(...stripPrefixFromSet(parsedStatus.undecodable, gitPrefix))
|
|
1040
1045
|
}
|
|
1041
|
-
return {
|
|
1046
|
+
return {
|
|
1047
|
+
all,
|
|
1048
|
+
trusted,
|
|
1049
|
+
dirty: worktreeDirty,
|
|
1050
|
+
changed: dirty === null ? null : [...dirty, ...untracked],
|
|
1051
|
+
undecodable,
|
|
1052
|
+
startedAtMs,
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
/** A run's whole-tree enumeration, started on its first ask and shared after. */
|
|
1057
|
+
export interface LazyGitEnumeration {
|
|
1058
|
+
start(): Promise<GitEnumeration>
|
|
1059
|
+
/** The enumeration, once something started it. */
|
|
1060
|
+
readonly started: Promise<GitEnumeration> | undefined
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
export function lazyGitEnumeration(workspaceRoot: string): LazyGitEnumeration {
|
|
1064
|
+
let started: Promise<GitEnumeration> | undefined
|
|
1065
|
+
return {
|
|
1066
|
+
start() {
|
|
1067
|
+
if (started === undefined) {
|
|
1068
|
+
started = startGitEnumeration(workspaceRoot, ['.'])
|
|
1069
|
+
// Its first reader may never await it (a config throws first); the
|
|
1070
|
+
// one that does still sees the error.
|
|
1071
|
+
started.catch(() => {})
|
|
1072
|
+
}
|
|
1073
|
+
return started
|
|
1074
|
+
},
|
|
1075
|
+
get started() {
|
|
1076
|
+
return started
|
|
1077
|
+
},
|
|
1078
|
+
}
|
|
1042
1079
|
}
|
|
1043
1080
|
|
|
1044
1081
|
/** The partition half of `populateGitFilesCache`: store per-project slices of one enumeration. */
|
package/src/cache/index.ts
CHANGED
package/src/cli/run.ts
CHANGED
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
} from '../orchestrator/index.js'
|
|
16
16
|
import type { ContinueMode } from '../graph/index.js'
|
|
17
17
|
import { type CachePolicy, FULL_CACHE_POLICY, parseCachePolicy } from '../cache/index.js'
|
|
18
|
-
import {
|
|
18
|
+
import { findCwdSelection, pickTask, resolveFilters } from './select.js'
|
|
19
19
|
import { nxTargetHint, taskNamesHere } from './task-verb.js'
|
|
20
20
|
import { MAX_TIMEOUT_MS, isUserError, parseDecimalInt, machineParallelism } from '../util/index.js'
|
|
21
21
|
import { formatGraphDot, formatPlanJson, formatPlanText } from './plan-format.js'
|
|
@@ -511,7 +511,7 @@ export async function resolveRunOptions(
|
|
|
511
511
|
} else if (parsed.all) {
|
|
512
512
|
projects = undefined
|
|
513
513
|
} else {
|
|
514
|
-
const cwdProject = await
|
|
514
|
+
const cwdProject = await findCwdSelection(cwd)
|
|
515
515
|
if (!cwdProject) {
|
|
516
516
|
const nx = await nxTargetHint(tasks, cwd)
|
|
517
517
|
if (nx !== null) return { error: nx }
|
|
@@ -520,7 +520,8 @@ export async function resolveRunOptions(
|
|
|
520
520
|
'not inside a project. Pass --all for every project, --filter <pattern> to filter, or run from within a project directory.',
|
|
521
521
|
}
|
|
522
522
|
}
|
|
523
|
-
projects = [cwdProject]
|
|
523
|
+
projects = [cwdProject.name]
|
|
524
|
+
discovered = cwdProject.discovered
|
|
524
525
|
}
|
|
525
526
|
|
|
526
527
|
const opts: RunOptions = {
|
package/src/cli/select.ts
CHANGED
|
@@ -118,7 +118,20 @@ async function loadWorkspaceProjects(cwd: string): Promise<ProjectMeta[]> {
|
|
|
118
118
|
}
|
|
119
119
|
|
|
120
120
|
export async function findCwdProject(cwd: string): Promise<string | null> {
|
|
121
|
-
|
|
121
|
+
return (await findCwdSelection(cwd))?.name ?? null
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* The project `cwd` is in, and the discovery that found it, for the run to
|
|
126
|
+
* reuse (`RunOptions.discovered`): discovering again ran every `discover`
|
|
127
|
+
* hook twice (`nx()`'s graph load and worktree key among them).
|
|
128
|
+
*/
|
|
129
|
+
export async function findCwdSelection(
|
|
130
|
+
cwd: string,
|
|
131
|
+
): Promise<{ name: string; discovered: { root: string; projects: ProjectMeta[] } } | null> {
|
|
132
|
+
const reads: LoadReads = new Map()
|
|
133
|
+
const root = await findWorkspaceRoot(cwd, reads)
|
|
134
|
+
const projects = await discoverCliProjects(await loadWorkspace(root, reads))
|
|
122
135
|
const abs = path.resolve(cwd)
|
|
123
136
|
const within = (dirOf: (p: ProjectMeta) => string): string | null => {
|
|
124
137
|
let best: string | null = null
|
|
@@ -137,7 +150,7 @@ export async function findCwdProject(cwd: string): Promise<string | null> {
|
|
|
137
150
|
// so inside such a member nothing matched and a run from there was "not
|
|
138
151
|
// inside a project" (item 1025). Only then are the members realpathed:
|
|
139
152
|
// one syscall each, paid only by a run no plain match could place.
|
|
140
|
-
|
|
153
|
+
const name =
|
|
141
154
|
within((p) => p.dir) ??
|
|
142
155
|
within((p) => {
|
|
143
156
|
try {
|
|
@@ -146,7 +159,7 @@ export async function findCwdProject(cwd: string): Promise<string | null> {
|
|
|
146
159
|
return p.dir
|
|
147
160
|
}
|
|
148
161
|
})
|
|
149
|
-
|
|
162
|
+
return name === null ? null : { name, discovered: { root, projects } }
|
|
150
163
|
}
|
|
151
164
|
|
|
152
165
|
export type FilterResolution =
|
|
@@ -41,7 +41,8 @@ export interface RunOptions {
|
|
|
41
41
|
staged?: ReadonlyMap<string, ProjectEntry>
|
|
42
42
|
/**
|
|
43
43
|
* The projects a selection pass in this same process discovered under
|
|
44
|
-
* `root` (`--filter`, `--affected
|
|
44
|
+
* `root` (`--filter`, `--affected`, the project a run from inside one
|
|
45
|
+
* is in). The run takes them instead of
|
|
45
46
|
* walking the workspace again when its root is the same. One run only,
|
|
46
47
|
* as `staged` is.
|
|
47
48
|
*/
|
|
@@ -259,6 +259,14 @@ export interface DiscoverContext extends WorkspaceHookContext {
|
|
|
259
259
|
readonly cacheDir: string
|
|
260
260
|
/** The projects found so far: core's, then earlier plugins'. */
|
|
261
261
|
readonly projects: readonly ProjectMeta[]
|
|
262
|
+
/**
|
|
263
|
+
* What `git status` lists in the workspace, workspace-relative: modified,
|
|
264
|
+
* staged or deleted paths (both sides of a rename) and untracked files;
|
|
265
|
+
* null outside a git worktree. It is the run's own enumeration, so a
|
|
266
|
+
* plugin whose answer depends on the worktree (nx()'s graph key) reads it
|
|
267
|
+
* here instead of walking the tree a second time.
|
|
268
|
+
*/
|
|
269
|
+
worktreeChanges(): Promise<readonly string[] | null>
|
|
262
270
|
}
|
|
263
271
|
|
|
264
272
|
export interface NamedProject {
|
|
@@ -21,7 +21,7 @@ import {
|
|
|
21
21
|
applyGitEnumeration,
|
|
22
22
|
gitPathspecs,
|
|
23
23
|
startGitEnumeration,
|
|
24
|
-
|
|
24
|
+
lazyGitEnumeration,
|
|
25
25
|
} from '../cache/index.js'
|
|
26
26
|
import {
|
|
27
27
|
buildPackageGraph,
|
|
@@ -54,6 +54,7 @@ import {
|
|
|
54
54
|
} from './plugin-host.js'
|
|
55
55
|
import {
|
|
56
56
|
discoverProjects,
|
|
57
|
+
gitOfDiscovery,
|
|
57
58
|
loadProjects,
|
|
58
59
|
loadWorkspacePlugins,
|
|
59
60
|
type LoadedProjects,
|
|
@@ -176,13 +177,15 @@ export async function prepareRun(options: RunOptions, log: Logger): Promise<Prep
|
|
|
176
177
|
// waits: its pathspecs depend on which projects the configs pull in.
|
|
177
178
|
const unscoped =
|
|
178
179
|
options.projects === undefined && options.tasks.some((spec) => spec.indexOf('#') <= 0)
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
//
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
180
|
+
// A `discover` hook that reads the worktree (nx()'s graph key) starts the
|
|
181
|
+
// whole-tree enumeration a scoped run would otherwise scope later, here or
|
|
182
|
+
// in the CLI's selection pass; the run reuses it instead of walking the
|
|
183
|
+
// tree twice (G-75).
|
|
184
|
+
const reused = options.discovered?.root === workspaceRoot ? options.discovered : undefined
|
|
185
|
+
const git =
|
|
186
|
+
(reused !== undefined ? gitOfDiscovery(reused.projects) : undefined) ??
|
|
187
|
+
lazyGitEnumeration(workspaceRoot)
|
|
188
|
+
if (unscoped) void git.start()
|
|
186
189
|
const workspace = await loadWorkspace(workspaceRoot, reads)
|
|
187
190
|
const { workspaceConfig, plugins } = await loadWorkspacePlugins(workspaceRoot, (m) =>
|
|
188
191
|
log.status(m),
|
|
@@ -194,9 +197,9 @@ export async function prepareRun(options: RunOptions, log: Logger): Promise<Prep
|
|
|
194
197
|
? path.resolve(options.cwd, options.cacheDir)
|
|
195
198
|
: resolveCacheDir(workspaceRoot, workspaceConfig)
|
|
196
199
|
const projectMetas =
|
|
197
|
-
|
|
198
|
-
?
|
|
199
|
-
: await discoverProjects(workspace, plugins, cacheDir, (m) => log.status(m))
|
|
200
|
+
reused !== undefined
|
|
201
|
+
? reused.projects
|
|
202
|
+
: await discoverProjects(workspace, plugins, cacheDir, (m) => log.status(m), git)
|
|
200
203
|
mark('discover projects')
|
|
201
204
|
|
|
202
205
|
// SCOPED config loading: configs are programs, and evaluating 1090
|
|
@@ -377,7 +380,7 @@ export async function prepareRun(options: RunOptions, log: Logger): Promise<Prep
|
|
|
377
380
|
),
|
|
378
381
|
)
|
|
379
382
|
const projectDirs = [...projects.values()].map((p) => p.dir)
|
|
380
|
-
const enumeration = await (
|
|
383
|
+
const enumeration = await (git.started ??
|
|
381
384
|
startGitEnumeration(
|
|
382
385
|
workspaceRoot,
|
|
383
386
|
gitPathspecs(workspaceRoot, projectDirs, usesWorkspaceInputs),
|
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
type Workspace,
|
|
19
19
|
} from '../workspace/index.js'
|
|
20
20
|
import type { WorkspaceConfig } from '../config.js'
|
|
21
|
-
import { Cache } from '../cache/index.js'
|
|
21
|
+
import { Cache, lazyGitEnumeration, type LazyGitEnumeration } from '../cache/index.js'
|
|
22
22
|
import { UserError } from '../util/index.js'
|
|
23
23
|
import {
|
|
24
24
|
buildPackageGraph,
|
|
@@ -56,6 +56,17 @@ export async function loadWorkspacePlugins(
|
|
|
56
56
|
return { workspaceConfig, plugins }
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
+
/**
|
|
60
|
+
* The enumeration a discovery's `discover` hooks shared, by the array it
|
|
61
|
+
* returned: a run handed that array (`RunOptions.discovered`, the CLI's
|
|
62
|
+
* `--filter` pass) reuses the walk a hook started instead of a second one.
|
|
63
|
+
*/
|
|
64
|
+
const discoveredGit = new WeakMap<readonly ProjectMeta[], LazyGitEnumeration>()
|
|
65
|
+
|
|
66
|
+
export function gitOfDiscovery(projects: readonly ProjectMeta[]): LazyGitEnumeration | undefined {
|
|
67
|
+
return discoveredGit.get(projects)
|
|
68
|
+
}
|
|
69
|
+
|
|
59
70
|
/**
|
|
60
71
|
* The workspace's projects: core's discovery, then the plugin `discover`
|
|
61
72
|
* stage. With no plugin declaring it, `listProjects` alone.
|
|
@@ -65,13 +76,21 @@ export async function discoverProjects(
|
|
|
65
76
|
plugins: readonly VxPlugin[],
|
|
66
77
|
cacheDir: string,
|
|
67
78
|
warn: (message: string) => void,
|
|
79
|
+
/** The run's enumeration, which `DiscoverContext.worktreeChanges` reads (and starts). */
|
|
80
|
+
git: LazyGitEnumeration = lazyGitEnumeration(workspace.root),
|
|
68
81
|
): Promise<ProjectMeta[]> {
|
|
69
82
|
const projects = await listProjects(workspace)
|
|
70
83
|
if (!hasHook(plugins, 'discover')) return projects
|
|
84
|
+
discoveredGit.set(projects, git)
|
|
85
|
+
const worktreeChanges = (): Promise<readonly string[] | null> =>
|
|
86
|
+
git.start().then(
|
|
87
|
+
(e) => e.changed,
|
|
88
|
+
() => null,
|
|
89
|
+
)
|
|
71
90
|
await applyDiscoverHooks(
|
|
72
91
|
plugins,
|
|
73
92
|
projects,
|
|
74
|
-
{ workspaceRoot: workspace.root, cacheDir, warn },
|
|
93
|
+
{ workspaceRoot: workspace.root, cacheDir, warn, worktreeChanges },
|
|
75
94
|
(named, plugin) => namedProject(workspace, projects, named, plugin.name),
|
|
76
95
|
)
|
|
77
96
|
return projects.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0))
|