@vzn/vx 0.0.16 → 0.0.21
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/LICENSE +1 -1
- package/README.md +135 -124
- package/index.ts +10 -0
- package/launcher.mjs +10 -11
- package/package.json +11 -7
- package/src/bin.ts +14 -4
- package/src/cache/archive.ts +659 -0
- package/src/cache/cache.ts +769 -1101
- package/src/cache/chained-cache.ts +194 -0
- package/src/cache/config-evals.ts +89 -0
- package/src/cache/file-hashes.ts +262 -0
- package/src/cache/git-inputs.ts +637 -0
- package/src/cache/index.ts +24 -5
- package/src/cache/inputs.ts +373 -456
- package/src/cache/layer.ts +604 -0
- package/src/cache/layered-cache.ts +96 -43
- package/src/cache/output-index.ts +224 -0
- package/src/cache/policy.ts +84 -0
- package/src/cache/run-history.ts +147 -0
- package/src/cache/tar-stream.ts +358 -0
- package/src/cache/zstd.ts +160 -0
- package/src/cli/cache.ts +75 -21
- package/src/cli/completions.ts +104 -0
- package/src/cli/core-alias.ts +25 -0
- package/src/cli/help.ts +195 -128
- package/src/cli/index.ts +116 -46
- package/src/cli/info.ts +122 -63
- package/src/cli/init.ts +75 -0
- package/src/cli/last.ts +172 -0
- package/src/cli/lock.ts +2 -1
- package/src/cli/plan-format.ts +72 -6
- package/src/cli/plugin-commands.ts +103 -0
- package/src/cli/run.ts +245 -271
- package/src/cli/select.ts +332 -0
- package/src/cli/show.ts +144 -69
- package/src/cli/upgrade.ts +9 -7
- package/src/cli/watch.ts +858 -110
- package/src/cli/why.ts +223 -0
- package/src/cli/workspace-config.ts +126 -0
- package/src/config.ts +187 -150
- package/src/exec/env.ts +18 -1
- package/src/exec/executor.ts +224 -0
- package/src/exec/index.ts +24 -2
- package/src/exec/local-executor.ts +41 -0
- package/src/exec/runner.ts +166 -40
- package/src/exec/sandbox-binds.ts +182 -0
- package/src/exec/sandbox-paths.ts +50 -0
- package/src/exec/sandbox-runtime.ts +642 -260
- package/src/exec/sandbox-violations.ts +280 -0
- package/src/graph/index.ts +4 -10
- package/src/graph/scheduler.ts +232 -203
- package/src/graph/task-graph.ts +222 -8
- package/src/index.ts +146 -153
- package/src/orchestrator/admission.ts +137 -0
- package/src/orchestrator/deferred-outputs.ts +147 -0
- package/src/orchestrator/doctor.ts +255 -0
- package/src/orchestrator/download-policy.ts +171 -0
- package/src/orchestrator/events.ts +72 -8
- package/src/orchestrator/execute-task.ts +374 -549
- package/src/orchestrator/failure-mode.ts +202 -0
- package/src/orchestrator/framed-output.ts +126 -69
- package/src/orchestrator/history.ts +126 -59
- package/src/orchestrator/hit-restore.ts +221 -0
- package/src/orchestrator/index.ts +77 -93
- package/src/orchestrator/local-shortcircuit.ts +22 -0
- package/src/orchestrator/lockfile-claim.ts +281 -0
- package/src/orchestrator/logger.ts +208 -35
- package/src/orchestrator/metrics.ts +102 -1388
- package/src/orchestrator/miss-save.ts +169 -0
- package/src/orchestrator/options.ts +55 -34
- package/src/orchestrator/persistent.ts +79 -0
- package/src/orchestrator/placement.ts +182 -0
- package/src/orchestrator/plan.ts +144 -1
- package/src/orchestrator/plugin-host.ts +357 -120
- package/src/orchestrator/plugin.ts +298 -58
- package/src/orchestrator/prepare.ts +202 -106
- package/src/orchestrator/projects.ts +259 -0
- package/src/orchestrator/remote-prefetch.ts +12 -8
- package/src/orchestrator/run-artifacts.ts +63 -16
- package/src/orchestrator/run-context.ts +167 -29
- package/src/orchestrator/run-records.ts +149 -0
- package/src/orchestrator/run-report.ts +74 -47
- package/src/orchestrator/run.ts +534 -417
- package/src/orchestrator/sandbox-request.ts +243 -0
- package/src/orchestrator/save-lane.ts +47 -0
- package/src/orchestrator/signals.ts +111 -0
- package/src/orchestrator/stable-keys.ts +129 -31
- package/src/orchestrator/status-line.ts +49 -9
- package/src/orchestrator/summary.ts +90 -26
- package/src/orchestrator/tally.ts +78 -35
- package/src/orchestrator/task-hash.ts +111 -15
- package/src/orchestrator/task-log-buffer.ts +288 -0
- package/src/orchestrator/telemetry-host.ts +34 -6
- package/src/orchestrator/telemetry.ts +182 -36
- package/src/orchestrator/upstream.ts +72 -31
- package/src/util/cgroup.ts +160 -0
- package/src/util/edit-distance.ts +70 -0
- package/src/util/errors.ts +15 -1
- package/src/util/index.ts +15 -2
- package/src/util/num.ts +37 -0
- package/src/util/paths.ts +64 -0
- package/src/util/settle.ts +67 -0
- package/src/util/size.ts +5 -1
- package/src/util/tail.ts +66 -0
- package/src/util/timing.ts +63 -0
- package/src/util/verbs.ts +39 -0
- package/src/workspace/affected.ts +250 -23
- package/src/workspace/config-cache.ts +313 -0
- package/src/workspace/config-eval.ts +217 -0
- package/src/workspace/config-imports.ts +162 -0
- package/src/workspace/config-schema.ts +837 -0
- package/src/workspace/filter.ts +65 -10
- package/src/workspace/fingerprint.ts +53 -4
- package/src/workspace/index.ts +42 -3
- package/src/workspace/lockfile.ts +25 -9
- package/src/workspace/migrate-scripts.ts +134 -0
- package/src/workspace/migration.ts +314 -0
- package/src/workspace/nested-dirs.ts +21 -7
- package/src/workspace/package-graph.ts +114 -41
- package/src/workspace/project-loader.ts +224 -450
- package/src/workspace/workspace.ts +298 -72
- package/src/cache/cas-backend.ts +0 -97
- package/src/cache/digest.ts +0 -47
- package/src/cache/tar.ts +0 -343
- package/src/cli/backend.ts +0 -23
- package/src/cli/mcp-rpc.ts +0 -292
- package/src/cli/mcp.ts +0 -92
- package/src/cli/migrate-nx.ts +0 -450
- package/src/cli/migrate-turbo.ts +0 -364
- package/src/cli/migrate.ts +0 -219
- package/src/orchestrator/devframe-surface.ts +0 -74
- package/src/orchestrator/predict.ts +0 -106
- package/src/orchestrator/protocol.ts +0 -156
- package/src/orchestrator/resources.ts +0 -59
- package/src/orchestrator/run-state.ts +0 -145
- package/src/orchestrator/verify.ts +0 -164
- package/src/orchestrator/wire-render.ts +0 -92
- package/src/orchestrator/wire.ts +0 -212
|
@@ -8,43 +8,58 @@
|
|
|
8
8
|
// try/finally around its plan() call.
|
|
9
9
|
|
|
10
10
|
import path from 'node:path'
|
|
11
|
-
import type {
|
|
12
|
-
import { UserError } from '../util/index.js'
|
|
11
|
+
import type { WorkspaceConfig } from '../config.js'
|
|
12
|
+
import { mark, UserError } from '../util/index.js'
|
|
13
13
|
import {
|
|
14
14
|
Cache,
|
|
15
|
+
noteSchemaReset,
|
|
15
16
|
type CacheLayer,
|
|
16
17
|
type CachePolicy,
|
|
17
18
|
FULL_CACHE_POLICY,
|
|
18
19
|
GitFilesCache,
|
|
19
20
|
LayeredCache,
|
|
20
|
-
|
|
21
|
+
applyGitEnumeration,
|
|
22
|
+
gitPathspecs,
|
|
23
|
+
startGitEnumeration,
|
|
24
|
+
type GitEnumeration,
|
|
21
25
|
} from '../cache/index.js'
|
|
22
26
|
import {
|
|
23
27
|
buildPackageGraph,
|
|
24
28
|
computeNestedProjectDirs,
|
|
25
|
-
|
|
29
|
+
computeWorkspaceFingerprints,
|
|
26
30
|
findWorkspaceRoot,
|
|
27
|
-
frozenProjectConfig,
|
|
28
31
|
listProjects,
|
|
29
|
-
loadProjectConfig,
|
|
30
32
|
loadWorkspace,
|
|
31
|
-
|
|
33
|
+
FROZEN_WITHOUT_LOCK,
|
|
32
34
|
readLockfile,
|
|
33
35
|
resolveCacheDir,
|
|
34
36
|
type ProjectEntry,
|
|
35
37
|
} from '../workspace/index.js'
|
|
36
|
-
import {
|
|
37
|
-
|
|
38
|
+
import {
|
|
39
|
+
buildTaskGraph,
|
|
40
|
+
expandRequested,
|
|
41
|
+
type TaskNode,
|
|
42
|
+
unresolvedRequests,
|
|
43
|
+
} from '../graph/index.js'
|
|
44
|
+
import {
|
|
45
|
+
applyGraphHooks,
|
|
46
|
+
applyKeyHooks,
|
|
47
|
+
applyScheduleHooks,
|
|
48
|
+
fingerprintClaims,
|
|
49
|
+
hasHook,
|
|
50
|
+
resolveCache,
|
|
51
|
+
} from './plugin-host.js'
|
|
52
|
+
import { loadProjects, loadWorkspacePlugins, type LoadedProjects } from './projects.js'
|
|
38
53
|
import type { VxPlugin } from './plugin.js'
|
|
39
54
|
import { createHashCache, type HashCache } from './task-hash.js'
|
|
40
55
|
import type { Logger } from './logger.js'
|
|
41
56
|
import type { RunOptions } from './options.js'
|
|
42
|
-
import { type HistoryTable, LocalHistoryProvider } from './history.js'
|
|
43
|
-
import { computePredictedPriorities } from './predict.js'
|
|
44
57
|
|
|
45
58
|
export interface PreparedRun {
|
|
46
59
|
workspaceRoot: string
|
|
47
60
|
workspaceConfig: WorkspaceConfig | null
|
|
61
|
+
/** The workspace's declared plugins, in declaration order. Nothing is added. */
|
|
62
|
+
plugins: readonly VxPlugin[]
|
|
48
63
|
cacheDir: string
|
|
49
64
|
cache: CacheLayer
|
|
50
65
|
/**
|
|
@@ -54,12 +69,35 @@ export interface PreparedRun {
|
|
|
54
69
|
*/
|
|
55
70
|
localCache: Cache
|
|
56
71
|
/**
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
72
|
+
* True when `cache` is something other than the bare local handle — an
|
|
73
|
+
* injected remote layer, or one a plugin's `cache` capability built. The
|
|
74
|
+
* remote axes of the policy only mean anything then: without a remote
|
|
75
|
+
* layer a `remote:w` policy writes NOWHERE, so a task that believed it
|
|
76
|
+
* would be saved still cleans its outputs before executing.
|
|
77
|
+
*/
|
|
78
|
+
hasRemoteLayer: boolean
|
|
79
|
+
/**
|
|
80
|
+
* Scheduling priorities from plugins' `schedule` stage (task id → weight,
|
|
81
|
+
* merged over the structural baseline by the scheduler). Empty when no
|
|
82
|
+
* plugin declares the stage.
|
|
60
83
|
*/
|
|
61
84
|
priorities: ReadonlyMap<string, number>
|
|
62
85
|
nodes: Map<string, TaskNode>
|
|
86
|
+
/**
|
|
87
|
+
* Requested task specs that matched NO project — a typo, or a stray
|
|
88
|
+
* positional (the value of an `=`-only flag written with a space).
|
|
89
|
+
* Nothing they asked for is in `nodes`, so callers must fail rather
|
|
90
|
+
* than run the remainder silently.
|
|
91
|
+
*/
|
|
92
|
+
unresolvedTasks: readonly string[]
|
|
93
|
+
/** Every discovered project — the declared task names a typo is measured against. */
|
|
94
|
+
projects: ReadonlyMap<string, ProjectEntry>
|
|
95
|
+
/**
|
|
96
|
+
* True iff at least one package in the workspace has a `vx.config.*` at
|
|
97
|
+
* all — regardless of scope, so a `--filter` that matched nothing is not
|
|
98
|
+
* mistaken for a workspace vx was never set up in.
|
|
99
|
+
*/
|
|
100
|
+
anyProjectConfig: boolean
|
|
63
101
|
workspaceFingerprint: string
|
|
64
102
|
nestedDirsByProject: Map<string, string[]>
|
|
65
103
|
/**
|
|
@@ -104,31 +142,46 @@ export interface PreparedRun {
|
|
|
104
142
|
* empty plan).
|
|
105
143
|
*/
|
|
106
144
|
export async function prepareRun(options: RunOptions, log: Logger): Promise<PreparedRun> {
|
|
145
|
+
mark('startup')
|
|
107
146
|
const workspaceRoot = await findWorkspaceRoot(options.cwd)
|
|
147
|
+
// An UNSCOPED run (no explicit scope, at least one bare task name)
|
|
148
|
+
// enumerates the whole tree whatever the configs say, so git starts
|
|
149
|
+
// HERE — the walk needs only the root — and overlaps the workspace
|
|
150
|
+
// config, discovery, the cache open and the config evaluation. Its
|
|
151
|
+
// ~60 ms is the warm run's wall floor on a 1000-project tree; it used to
|
|
152
|
+
// start after discovery and the cache open, ~25 ms later. A scoped run
|
|
153
|
+
// waits: its pathspecs depend on which projects the configs pull in.
|
|
154
|
+
const unscoped =
|
|
155
|
+
options.projects === undefined && options.tasks.some((spec) => spec.indexOf('#') <= 0)
|
|
156
|
+
const earlyGit: Promise<GitEnumeration> | undefined = unscoped
|
|
157
|
+
? startGitEnumeration(workspaceRoot, ['.'])
|
|
158
|
+
: undefined
|
|
159
|
+
// A broken config below throws before this is awaited; the detached
|
|
160
|
+
// handler keeps that from surfacing as an unhandled rejection (the real
|
|
161
|
+
// await further down still sees the error).
|
|
162
|
+
earlyGit?.catch(() => {})
|
|
108
163
|
const workspace = await loadWorkspace(workspaceRoot)
|
|
109
|
-
const workspaceConfig = await
|
|
164
|
+
const { workspaceConfig, plugins } = await loadWorkspacePlugins(workspaceRoot, (m) =>
|
|
165
|
+
log.status(m),
|
|
166
|
+
)
|
|
167
|
+
mark('workspace config')
|
|
110
168
|
const projectMetas = await listProjects(workspace)
|
|
169
|
+
mark('discover projects')
|
|
111
170
|
|
|
112
171
|
// SCOPED config loading: configs are programs, and evaluating 1090
|
|
113
172
|
// of them costs ~200 ms — the dominant fixed cost of small runs.
|
|
114
|
-
// Only the in-scope projects
|
|
115
|
-
//
|
|
116
|
-
//
|
|
117
|
-
// Side effect, deliberate and
|
|
118
|
-
//
|
|
119
|
-
// when that package enters
|
|
173
|
+
// Only the in-scope projects, their transitive dependency closure
|
|
174
|
+
// (which bounds '^task' frontier expansion) and any project named by
|
|
175
|
+
// a `pkg#task` dependsOn entry can contribute graph nodes, so only
|
|
176
|
+
// those configs are evaluated. Side effect, deliberate and
|
|
177
|
+
// Turbo-like: a broken config in an unrelated package no longer
|
|
178
|
+
// fails a scoped run — it surfaces when that package enters scope.
|
|
120
179
|
const packageGraph = buildPackageGraph(projectMetas)
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
// Seeds: explicit scope, plus anchored pkg#task targets (which
|
|
128
|
-
// bypass scope by design). With no explicit scope, bare task names
|
|
129
|
-
// fan out across the whole workspace — but when EVERY spec is
|
|
130
|
-
// anchored, the anchors alone are the scope and nothing else needs
|
|
131
|
-
// its config evaluated.
|
|
180
|
+
// Seeds: explicit scope, plus anchored pkg#task targets (which bypass
|
|
181
|
+
// scope by design). With no explicit scope, bare task names fan out
|
|
182
|
+
// across the whole workspace — but when EVERY spec is anchored, the
|
|
183
|
+
// anchors alone are the scope and nothing else needs its config
|
|
184
|
+
// evaluated.
|
|
132
185
|
const anchored: string[] = []
|
|
133
186
|
let hasBare = false
|
|
134
187
|
for (const spec of options.tasks) {
|
|
@@ -136,23 +189,29 @@ export async function prepareRun(options: RunOptions, log: Logger): Promise<Prep
|
|
|
136
189
|
if (hashIdx > 0) anchored.push(spec.slice(0, hashIdx))
|
|
137
190
|
else hasBare = true
|
|
138
191
|
}
|
|
139
|
-
const seeds
|
|
140
|
-
options.projects
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
192
|
+
const seeds: 'all' | string[] =
|
|
193
|
+
options.projects !== undefined ? [...options.projects, ...anchored] : hasBare ? 'all' : anchored
|
|
194
|
+
|
|
195
|
+
// The local cache opens BEFORE the configs load: it is also where their
|
|
196
|
+
// cached evaluations live. `--cache-dir <path>` (RunOptions.cacheDir)
|
|
197
|
+
// overrides the workspace `cacheDir` field + the `.vx/cache` default;
|
|
198
|
+
// resolved relative to cwd.
|
|
199
|
+
const policy: CachePolicy = options.cache ?? FULL_CACHE_POLICY
|
|
200
|
+
const cacheDir = options.cacheDir
|
|
201
|
+
? path.resolve(options.cwd, options.cacheDir)
|
|
202
|
+
: resolveCacheDir(workspaceRoot, workspaceConfig)
|
|
203
|
+
const localCache = new Cache(cacheDir, { read: policy.localRead, write: policy.localWrite })
|
|
204
|
+
noteSchemaReset(localCache, (m) => log.status(m))
|
|
205
|
+
// Two digests from one read: the config-evaluation cache keys on every
|
|
206
|
+
// file (a config may import a dependency), the task keys on the files no
|
|
207
|
+
// plugin claims (`VxPlugin.fingerprint`).
|
|
208
|
+
const fingerprints = await computeWorkspaceFingerprints(
|
|
209
|
+
workspaceRoot,
|
|
210
|
+
new Set(fingerprintClaims(plugins).keys()),
|
|
145
211
|
)
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
}
|
|
149
|
-
const needed = new Set<string>(seeds)
|
|
150
|
-
for (const seed of seeds) {
|
|
151
|
-
for (const dep of packageGraph.transitiveDeps(seed)) needed.add(dep)
|
|
152
|
-
}
|
|
212
|
+
const workspaceFingerprint = fingerprints.unclaimed
|
|
213
|
+
mark('open cache')
|
|
153
214
|
|
|
154
|
-
const projects = new Map<string, ProjectEntry>()
|
|
155
|
-
const toLoad = projectsWithConfigs.filter((m) => needed.has(m.name))
|
|
156
215
|
// Frozen mode (--frozen, CI): configs load FROM vx-lock.json after a
|
|
157
216
|
// content-hash tripwire — no evaluation; env-dependent configs keep
|
|
158
217
|
// their locked values. Default (local) runs ALWAYS evaluate live:
|
|
@@ -162,20 +221,32 @@ export async function prepareRun(options: RunOptions, log: Logger): Promise<Prep
|
|
|
162
221
|
// See docs/design/config-lock-2026-06.md.
|
|
163
222
|
const lock = options.frozen === true ? await readLockfile(workspaceRoot) : null
|
|
164
223
|
if (options.frozen === true && lock === null) {
|
|
165
|
-
throw new UserError(
|
|
166
|
-
`--frozen requires vx-lock.json at the workspace root — run 'vx lock' and commit it`,
|
|
167
|
-
)
|
|
224
|
+
throw new UserError(FROZEN_WITHOUT_LOCK)
|
|
168
225
|
}
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
226
|
+
|
|
227
|
+
let loaded: LoadedProjects
|
|
228
|
+
try {
|
|
229
|
+
loaded = await loadProjects({
|
|
230
|
+
workspaceRoot,
|
|
231
|
+
cacheDir,
|
|
232
|
+
plugins,
|
|
233
|
+
projectMetas,
|
|
234
|
+
packageGraph,
|
|
235
|
+
seeds,
|
|
236
|
+
closure: true,
|
|
237
|
+
lock,
|
|
238
|
+
evalCache: { store: localCache, workspaceFingerprint: fingerprints.all },
|
|
239
|
+
warn: (m) => log.status(m),
|
|
240
|
+
...(options.staged !== undefined ? { staged: options.staged } : {}),
|
|
241
|
+
})
|
|
242
|
+
} catch (err) {
|
|
243
|
+
// The cache opened before the configs loaded (it holds their cached
|
|
244
|
+
// evaluations); a config error must not leak the handle.
|
|
245
|
+
localCache.close()
|
|
246
|
+
throw err
|
|
178
247
|
}
|
|
248
|
+
const { projects, configured: projectsWithConfigs } = loaded
|
|
249
|
+
mark('load configs')
|
|
179
250
|
|
|
180
251
|
// Boundary geometry considers every config-bearing project in the
|
|
181
252
|
// workspace, loaded or not — an out-of-scope nested project must
|
|
@@ -189,35 +260,43 @@ export async function prepareRun(options: RunOptions, log: Logger): Promise<Prep
|
|
|
189
260
|
: [...projects.keys()]
|
|
190
261
|
|
|
191
262
|
const requested = expandRequested(options.tasks, candidateProjects, projects)
|
|
263
|
+
const unresolvedTasks = unresolvedRequests(options.tasks, candidateProjects, projects)
|
|
192
264
|
|
|
193
|
-
const policy: CachePolicy = options.cache ?? FULL_CACHE_POLICY
|
|
194
|
-
// `--cache-dir <path>` (RunOptions.cacheDir) overrides the workspace
|
|
195
|
-
// `cacheDir` field + the `.vx/cache` default; resolved relative to cwd.
|
|
196
|
-
const cacheDir = options.cacheDir
|
|
197
|
-
? path.resolve(options.cwd, options.cacheDir)
|
|
198
|
-
: resolveCacheDir(workspaceRoot, workspaceConfig)
|
|
199
|
-
const localCache = new Cache(cacheDir, { read: policy.localRead, write: policy.localWrite })
|
|
200
265
|
// Cache seam precedence: an EXPLICITLY injected remote layer
|
|
201
|
-
// (RunOptions.remoteCache — a distribution agent or
|
|
266
|
+
// (RunOptions.remoteCache — a distribution agent or daemon that already
|
|
202
267
|
// holds a wire client) wins outright; else a plugin's `cache` capability;
|
|
203
268
|
// else the local cache alone. Core ships no wire client — the remote
|
|
204
|
-
// cache is a plugin concern (
|
|
269
|
+
// cache is a plugin concern (docs/patterns.md § Remote cache wire). Injection
|
|
205
270
|
// winning prevents double-wrapping when the workspace also declares a
|
|
206
271
|
// cache plugin.
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
272
|
+
let cache: CacheLayer
|
|
273
|
+
try {
|
|
274
|
+
cache = options.remoteCache
|
|
275
|
+
? new LayeredCache(localCache, options.remoteCache, {
|
|
276
|
+
policy,
|
|
277
|
+
onRemoteError: (err) => log.status(`[vx] remote cache: ${err.message}`),
|
|
278
|
+
})
|
|
279
|
+
: await resolveCache(plugins, {
|
|
280
|
+
workspaceRoot,
|
|
281
|
+
cacheDir,
|
|
282
|
+
warn: (m) => log.status(m),
|
|
283
|
+
localCache,
|
|
284
|
+
policy,
|
|
285
|
+
})
|
|
286
|
+
} catch (err) {
|
|
287
|
+
// A cache plugin that throws or returns something off-contract is
|
|
288
|
+
// refused by name; the local handle opened above must not leak with it.
|
|
289
|
+
localCache.close()
|
|
290
|
+
throw err
|
|
291
|
+
}
|
|
292
|
+
// Ask the LAYER, don't infer. Identity against `localCache` answers a
|
|
293
|
+
// DIFFERENT question — "did the plugin hand back something other than the
|
|
294
|
+
// handle I passed in?" — which an ordinary pass-through decorator (a
|
|
295
|
+
// metrics wrapper, a cache-dir redirect) with no remote at all answers
|
|
296
|
+
// yes to, skipping the remote-axis clamp below. `hasRemote` is the
|
|
297
|
+
// layer's own truthful answer; `LayeredCache` sets it, a bare `Cache`
|
|
298
|
+
// doesn't, and a third-party layer opts in when it really has a remote.
|
|
299
|
+
const hasRemoteLayer = cache.hasRemote === true
|
|
221
300
|
|
|
222
301
|
const gitFilesCache = new GitFilesCache()
|
|
223
302
|
// Bulk-populate via a single `git ls-files` at the workspace root —
|
|
@@ -231,12 +310,14 @@ export async function prepareRun(options: RunOptions, log: Logger): Promise<Prep
|
|
|
231
310
|
(t) => (t.cache?.inputs.workspaceFiles?.length ?? 0) > 0,
|
|
232
311
|
),
|
|
233
312
|
)
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
313
|
+
const projectDirs = [...projects.values()].map((p) => p.dir)
|
|
314
|
+
const enumeration = await (earlyGit ??
|
|
315
|
+
startGitEnumeration(
|
|
316
|
+
workspaceRoot,
|
|
317
|
+
gitPathspecs(workspaceRoot, projectDirs, usesWorkspaceInputs),
|
|
318
|
+
))
|
|
319
|
+
applyGitEnumeration(enumeration, workspaceRoot, projectDirs, gitFilesCache, usesWorkspaceInputs)
|
|
320
|
+
mark('git enumeration')
|
|
240
321
|
const hashCache = createHashCache()
|
|
241
322
|
|
|
242
323
|
// Empty-cases bookkeeping. We still construct the cache + fingerprint
|
|
@@ -245,11 +326,16 @@ export async function prepareRun(options: RunOptions, log: Logger): Promise<Prep
|
|
|
245
326
|
return {
|
|
246
327
|
workspaceRoot,
|
|
247
328
|
workspaceConfig,
|
|
329
|
+
plugins,
|
|
248
330
|
cacheDir,
|
|
249
331
|
cache,
|
|
250
332
|
localCache,
|
|
333
|
+
hasRemoteLayer,
|
|
251
334
|
priorities: new Map(),
|
|
252
335
|
nodes: new Map(),
|
|
336
|
+
unresolvedTasks,
|
|
337
|
+
projects,
|
|
338
|
+
anyProjectConfig: projectsWithConfigs.length > 0,
|
|
253
339
|
workspaceFingerprint,
|
|
254
340
|
nestedDirsByProject,
|
|
255
341
|
gitFilesCache,
|
|
@@ -267,35 +353,45 @@ export async function prepareRun(options: RunOptions, log: Logger): Promise<Prep
|
|
|
267
353
|
? { excludeDependencies: options.excludeDependencies }
|
|
268
354
|
: {}),
|
|
269
355
|
})
|
|
270
|
-
|
|
271
|
-
//
|
|
272
|
-
//
|
|
273
|
-
//
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
356
|
+
// The graph is built; what follows is the plugins' (graph, key,
|
|
357
|
+
// schedule). Two rows, so a plugin's key stage reads as its own cost
|
|
358
|
+
// and not as graph building — a lockfile plugin's 1000 stats per run
|
|
359
|
+
// hid inside one `prepare (graph)` row until 2026-09-10.
|
|
360
|
+
mark('build graph')
|
|
361
|
+
if (hasHook(plugins, 'graph')) {
|
|
362
|
+
await applyGraphHooks(plugins, nodes, {
|
|
363
|
+
workspaceRoot,
|
|
364
|
+
cacheDir,
|
|
365
|
+
warn: (m) => log.status(m),
|
|
366
|
+
requested: [...nodes.values()].filter((n) => n.requested).map((n) => n.id),
|
|
367
|
+
})
|
|
368
|
+
}
|
|
369
|
+
if (hasHook(plugins, 'key')) {
|
|
370
|
+
await applyKeyHooks(plugins, nodes, { workspaceRoot, cacheDir, warn: (m) => log.status(m) })
|
|
371
|
+
}
|
|
277
372
|
let priorities: ReadonlyMap<string, number> = new Map()
|
|
278
|
-
if (
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
}
|
|
285
|
-
log.status(
|
|
286
|
-
`[vx] predictive scheduling fell back to baseline: ${err instanceof Error ? err.message : String(err)}`,
|
|
287
|
-
)
|
|
288
|
-
}
|
|
373
|
+
if (hasHook(plugins, 'schedule')) {
|
|
374
|
+
priorities = await applyScheduleHooks(plugins, nodes, {
|
|
375
|
+
workspaceRoot,
|
|
376
|
+
cacheDir,
|
|
377
|
+
warn: (m) => log.status(m),
|
|
378
|
+
localCache,
|
|
379
|
+
})
|
|
289
380
|
}
|
|
290
381
|
|
|
291
382
|
return {
|
|
292
383
|
workspaceRoot,
|
|
293
384
|
workspaceConfig,
|
|
385
|
+
plugins,
|
|
294
386
|
cacheDir,
|
|
295
387
|
cache,
|
|
296
388
|
localCache,
|
|
389
|
+
hasRemoteLayer,
|
|
297
390
|
priorities,
|
|
298
391
|
nodes,
|
|
392
|
+
unresolvedTasks,
|
|
393
|
+
projects,
|
|
394
|
+
anyProjectConfig: projectsWithConfigs.length > 0,
|
|
299
395
|
workspaceFingerprint,
|
|
300
396
|
nestedDirsByProject,
|
|
301
397
|
gitFilesCache,
|