@vzn/vx 0.0.635 → 0.0.637
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/README.md +47 -19
- package/docs/caching.md +222 -210
- package/docs/cli.md +699 -664
- package/docs/execution.md +67 -75
- package/docs/features.md +44 -40
- package/docs/patterns.md +31 -32
- package/docs/schema.md +418 -202
- package/docs/security.md +76 -39
- package/launcher.sh +43 -0
- package/package.json +5 -5
- package/schemas/cache.json +41 -19
- package/schemas/info.json +23 -11
- package/schemas/last.json +48 -2
- package/schemas/lock.json +1 -1
- package/schemas/plan.json +2 -2
- package/schemas/show.json +13 -4
- package/schemas/why.json +15 -1
- package/skills/vx/SKILL.md +16 -12
- package/src/bin.ts +17 -2
- package/src/cache/archive.ts +11 -0
- package/src/cache/cache.ts +105 -53
- package/src/cache/git-inputs.ts +277 -63
- package/src/cache/index.ts +7 -1
- package/src/cache/inputs.ts +125 -35
- package/src/cache/key-fold.ts +8 -7
- package/src/cache/layer.ts +8 -17
- package/src/cache/layered-cache.ts +16 -102
- package/src/cache/policy.ts +25 -29
- package/src/cache/run-history.ts +36 -14
- package/src/cache/schema.ts +21 -23
- package/src/cache/scoped-cache.ts +224 -0
- package/src/cli/cache.ts +150 -28
- package/src/cli/completions.ts +2 -2
- package/src/cli/core-alias.ts +1 -1
- package/src/cli/foreign-flags.ts +13 -12
- package/src/cli/help.ts +94 -61
- package/src/cli/index.ts +42 -13
- package/src/cli/info.ts +38 -23
- package/src/cli/init.ts +115 -406
- package/src/cli/last.ts +145 -4
- package/src/cli/lock.ts +333 -111
- package/src/cli/plan-format.ts +2 -2
- package/src/cli/plugin-commands.ts +5 -9
- package/src/cli/plugin-templates.ts +10 -10
- package/src/cli/run.ts +123 -144
- package/src/cli/select.ts +33 -39
- package/src/cli/show.ts +17 -19
- package/src/cli/task-verb.ts +2 -9
- package/src/cli/upgrade.ts +402 -145
- package/src/cli/watch-cycle.ts +9 -2
- package/src/cli/watch-filter.ts +33 -60
- package/src/cli/watch-fs.ts +29 -16
- package/src/cli/watch-judge.ts +12 -3
- package/src/cli/watch-set.ts +16 -42
- package/src/cli/watch.ts +188 -215
- package/src/cli/why.ts +28 -17
- package/src/cli/workspace-config.ts +31 -68
- package/src/config.ts +191 -71
- package/src/exec/env.ts +1 -31
- package/src/exec/executor.ts +1 -5
- package/src/exec/kill-tree.ts +2 -1
- package/src/exec/sandbox-runtime.ts +12 -1
- package/src/exec/sandbox-violations.ts +9 -1
- package/src/graph/dependency-spec.ts +1 -1
- package/src/graph/index.ts +1 -0
- package/src/graph/priorities.ts +1 -1
- package/src/graph/scheduler.ts +19 -7
- package/src/graph/task-graph.ts +44 -20
- package/src/handoff.ts +169 -0
- package/src/index.ts +25 -57
- package/src/orchestrator/affected-tasks.ts +7 -5
- package/src/orchestrator/command-windows.ts +44 -0
- package/src/orchestrator/deferred-outputs.ts +12 -10
- package/src/orchestrator/doctor.ts +98 -110
- package/src/orchestrator/download-policy.ts +3 -3
- package/src/orchestrator/events.ts +19 -3
- package/src/orchestrator/excluded-keys.ts +0 -2
- package/src/orchestrator/execute-task.ts +192 -70
- package/src/orchestrator/fingerprint-watch.ts +18 -15
- package/src/orchestrator/forecast.ts +10 -2
- package/src/orchestrator/framed-output.ts +8 -3
- package/src/orchestrator/graph-stage.ts +5 -5
- package/src/orchestrator/history.ts +70 -30
- package/src/orchestrator/hit-restore.ts +15 -3
- package/src/orchestrator/index.ts +18 -4
- package/src/orchestrator/local-shortcircuit.ts +2 -1
- package/src/orchestrator/lockfile-claim.ts +79 -20
- package/src/orchestrator/logger.ts +71 -39
- package/src/orchestrator/metrics.ts +71 -6
- package/src/orchestrator/miss-save.ts +3 -0
- package/src/orchestrator/options.ts +43 -28
- package/src/orchestrator/output-host.ts +72 -0
- package/src/orchestrator/plan.ts +6 -8
- package/src/orchestrator/plugin-host.ts +87 -63
- package/src/orchestrator/plugin.ts +94 -60
- package/src/orchestrator/prepare.ts +114 -73
- package/src/orchestrator/projects.ts +102 -99
- package/src/orchestrator/remote-prefetch.ts +2 -1
- package/src/orchestrator/run-context.ts +25 -6
- package/src/orchestrator/run-failures.ts +17 -8
- package/src/orchestrator/run-lock.ts +1 -1
- package/src/orchestrator/run-report.ts +9 -9
- package/src/orchestrator/run-settings.ts +48 -0
- package/src/orchestrator/run.ts +127 -88
- package/src/orchestrator/sandbox-fix.ts +39 -20
- package/src/orchestrator/sandbox-request.ts +84 -13
- package/src/orchestrator/signals.ts +1 -1
- package/src/orchestrator/stable-keys.ts +13 -11
- package/src/orchestrator/status-line.ts +48 -6
- package/src/orchestrator/summary.ts +46 -62
- package/src/orchestrator/task-hash.ts +12 -83
- package/src/orchestrator/telemetry.ts +3 -3
- package/src/settings.ts +417 -0
- package/src/util/errors.ts +1 -1
- package/src/util/essential-env.ts +31 -0
- package/src/util/hide-env.ts +107 -0
- package/src/util/index.ts +8 -2
- package/src/util/paths.ts +10 -0
- package/src/util/settle.ts +49 -30
- package/src/util/size.ts +23 -4
- package/src/util/timing.ts +3 -2
- package/src/util/ulid.ts +1 -1
- package/src/util/verbs.ts +0 -15
- package/src/workspace/affected.ts +77 -222
- package/src/workspace/config-eval.ts +15 -20
- package/src/workspace/config-imports.ts +78 -3
- package/src/workspace/config-schema.ts +251 -241
- package/src/workspace/dependency-rules.ts +57 -0
- package/src/workspace/filter.ts +2 -2
- package/src/workspace/fingerprint.ts +11 -121
- package/src/workspace/index.ts +22 -27
- package/src/workspace/load-reads.ts +4 -4
- package/src/workspace/lockfile.ts +96 -140
- package/src/workspace/migration.ts +242 -146
- package/src/workspace/package-graph.ts +30 -216
- package/src/workspace/project-loader.ts +80 -285
- package/src/workspace/repo-id.ts +2 -1
- package/src/workspace/workspace.ts +362 -831
- package/src/cache/config-evals.ts +0 -130
- package/src/workspace/config-cache.ts +0 -661
- package/src/workspace/migrate-scripts.ts +0 -1121
|
@@ -8,11 +8,17 @@
|
|
|
8
8
|
// See docs/design/pipeline-2026-09.md.
|
|
9
9
|
|
|
10
10
|
import path from 'node:path'
|
|
11
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
type Cache,
|
|
13
|
+
ChainedCache,
|
|
14
|
+
type CacheLayer,
|
|
15
|
+
type CachePolicy,
|
|
16
|
+
LayeredCache,
|
|
17
|
+
} from '../cache/index.js'
|
|
12
18
|
import { localExecutor, type TaskExecutor } from '../exec/index.js'
|
|
13
19
|
import { maskedLine, settleWithin, teardownTimeoutMs, UserError } from '../util/index.js'
|
|
14
|
-
import type { ProjectConfig, WorkspaceConfig
|
|
15
|
-
import { checkGraph, type TaskNode } from '../graph/index.js'
|
|
20
|
+
import type { ProjectConfig, WorkspaceConfig } from '../config.js'
|
|
21
|
+
import { checkGraph, type GraphRules, type TaskNode } from '../graph/index.js'
|
|
16
22
|
import type {
|
|
17
23
|
CacheContext,
|
|
18
24
|
DiscoverContext,
|
|
@@ -21,7 +27,7 @@ import type {
|
|
|
21
27
|
FingerprintContext,
|
|
22
28
|
GraphHookContext,
|
|
23
29
|
KeyHookContext,
|
|
24
|
-
|
|
30
|
+
DiscoveredProject,
|
|
25
31
|
ProjectHookContext,
|
|
26
32
|
ScheduleHookContext,
|
|
27
33
|
VxPlugin,
|
|
@@ -46,24 +52,8 @@ function isRecord(v: unknown): v is Record<string, unknown> {
|
|
|
46
52
|
return proto === Object.prototype || proto === null
|
|
47
53
|
}
|
|
48
54
|
|
|
49
|
-
/** Every method `
|
|
50
|
-
export const
|
|
51
|
-
'key',
|
|
52
|
-
'get',
|
|
53
|
-
'has',
|
|
54
|
-
'prefetch',
|
|
55
|
-
'loadOutputFilesBatch',
|
|
56
|
-
'isOutputsCurrent',
|
|
57
|
-
'restoreOutputs',
|
|
58
|
-
'save',
|
|
59
|
-
'ingest',
|
|
60
|
-
'recordRunBundle',
|
|
61
|
-
'stats',
|
|
62
|
-
'hashFile',
|
|
63
|
-
'outputsPath',
|
|
64
|
-
'prune',
|
|
65
|
-
'close',
|
|
66
|
-
]
|
|
55
|
+
/** Every method a `cache` hook's `RemoteCacheLayer` requires (`hasMany` is optional). */
|
|
56
|
+
export const REMOTE_CACHE_METHODS: readonly string[] = ['has', 'get', 'put']
|
|
67
57
|
|
|
68
58
|
/** What a capability hook handed back must be what the seam runs. */
|
|
69
59
|
function assertShape(
|
|
@@ -96,7 +86,7 @@ async function safe<T>(plugin: VxPlugin, hook: string, fn: () => T | Promise<T>)
|
|
|
96
86
|
return await fn()
|
|
97
87
|
} catch (err) {
|
|
98
88
|
// Masked here, not by whoever prints it: an embedder of `run()` and
|
|
99
|
-
//
|
|
89
|
+
// a plugin's verb print it as they get it, and the throw quotes what the
|
|
100
90
|
// plugin was told (a remote's reply, a header) (L-11).
|
|
101
91
|
throw new UserError(
|
|
102
92
|
maskedLine(
|
|
@@ -158,15 +148,21 @@ export async function applyConfigHooks(
|
|
|
158
148
|
|
|
159
149
|
/**
|
|
160
150
|
* `discover` stage: each plugin names directories to add to `projects`,
|
|
161
|
-
* seeing the ones earlier plugins added. `add` turns one
|
|
162
|
-
*
|
|
151
|
+
* seeing the ones earlier plugins added. `add` turns one entry into the
|
|
152
|
+
* project it names, `added` when it is new, or refuses it. Returns every
|
|
153
|
+
* entry's edges with the project and plugin they came from, for the caller
|
|
154
|
+
* to resolve once every plugin has run.
|
|
163
155
|
*/
|
|
164
156
|
export async function applyDiscoverHooks(
|
|
165
157
|
plugins: readonly VxPlugin[],
|
|
166
158
|
projects: ProjectMeta[],
|
|
167
159
|
ctx: Omit<DiscoverContext, 'projects'>,
|
|
168
|
-
add: (
|
|
169
|
-
|
|
160
|
+
add: (
|
|
161
|
+
entry: DiscoveredProject,
|
|
162
|
+
plugin: VxPlugin,
|
|
163
|
+
) => Promise<{ meta: ProjectMeta; added: boolean }>,
|
|
164
|
+
): Promise<DiscoveredEdges[]> {
|
|
165
|
+
const edges: DiscoveredEdges[] = []
|
|
170
166
|
for (const plugin of plugins) {
|
|
171
167
|
if (plugin.discover === undefined) continue
|
|
172
168
|
const named = await safe(plugin, 'discover', () => plugin.discover!({ ...ctx, projects }))
|
|
@@ -175,11 +171,23 @@ export async function applyDiscoverHooks(
|
|
|
175
171
|
`plugin '${plugin.name}' failed in discover: expected an array, got ${describeValue(named)}`,
|
|
176
172
|
)
|
|
177
173
|
}
|
|
178
|
-
for (const
|
|
179
|
-
const meta = await add(
|
|
180
|
-
if (
|
|
174
|
+
for (const entry of named as readonly DiscoveredProject[]) {
|
|
175
|
+
const { meta, added } = await add(entry, plugin)
|
|
176
|
+
if (added) projects.push(meta)
|
|
177
|
+
if (entry.deps !== undefined || entry.reach !== undefined) {
|
|
178
|
+
edges.push({ by: plugin.name, meta, deps: entry.deps, reach: entry.reach ?? [] })
|
|
179
|
+
}
|
|
181
180
|
}
|
|
182
181
|
}
|
|
182
|
+
return edges
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export interface DiscoveredEdges {
|
|
186
|
+
readonly by: string
|
|
187
|
+
readonly meta: ProjectMeta
|
|
188
|
+
/** Absent when the entry carried none; `[]` still makes the workspace an edge source. */
|
|
189
|
+
readonly deps: readonly string[] | undefined
|
|
190
|
+
readonly reach: readonly string[]
|
|
183
191
|
}
|
|
184
192
|
|
|
185
193
|
/** `project` stage: every plugin edits one project's config in place. */
|
|
@@ -215,7 +223,7 @@ export async function applyGraphHooks(
|
|
|
215
223
|
ctx: GraphHookContext,
|
|
216
224
|
/** Runs after EACH plugin's edit, so a refused task config names the plugin that wrote it. */
|
|
217
225
|
afterEach?: (plugin: VxPlugin) => void,
|
|
218
|
-
rules?:
|
|
226
|
+
rules?: GraphRules,
|
|
219
227
|
): Promise<void> {
|
|
220
228
|
let last: VxPlugin | undefined
|
|
221
229
|
for (const plugin of plugins) {
|
|
@@ -466,58 +474,74 @@ export function buildAdmission(
|
|
|
466
474
|
}
|
|
467
475
|
}
|
|
468
476
|
|
|
477
|
+
/** What `resolveCache` needs beyond a hook's context: the store and policy core owns. */
|
|
478
|
+
export interface CacheHost extends CacheContext {
|
|
479
|
+
readonly localCache: Cache
|
|
480
|
+
readonly policy: CachePolicy
|
|
481
|
+
}
|
|
482
|
+
|
|
469
483
|
/**
|
|
470
|
-
* Collect every plugin's `cache`
|
|
471
|
-
*
|
|
472
|
-
*
|
|
473
|
-
*
|
|
474
|
-
*
|
|
475
|
-
*
|
|
476
|
-
* one layer that wraps it.
|
|
484
|
+
* Collect every plugin's remote (`cache` hook) in declaration order and
|
|
485
|
+
* layer each over the local store: a lookup asks the local store first,
|
|
486
|
+
* then each remote in order (owner, 2026-10-13), and a save reaches every
|
|
487
|
+
* remote. The run's policy, narrowed by a remote's `access`, and its scope
|
|
488
|
+
* gate each remote; a remote's throw is a miss and a warning naming its
|
|
489
|
+
* plugin. No remote → the local store alone.
|
|
477
490
|
*/
|
|
478
491
|
export async function resolveCache(
|
|
479
492
|
plugins: readonly VxPlugin[],
|
|
480
|
-
|
|
493
|
+
host: CacheHost,
|
|
481
494
|
): Promise<CacheLayer> {
|
|
495
|
+
const { localCache, policy, ...ctx } = host
|
|
482
496
|
const layers: CacheLayer[] = []
|
|
483
|
-
const owners =
|
|
497
|
+
const owners: string[] = []
|
|
484
498
|
for (const plugin of plugins) {
|
|
485
499
|
if (plugin.cache === undefined) continue
|
|
486
|
-
const
|
|
487
|
-
if (
|
|
488
|
-
// The
|
|
489
|
-
//
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
500
|
+
const remote = await safe(plugin, 'cache', () => plugin.cache!(ctx))
|
|
501
|
+
if (remote === undefined) continue
|
|
502
|
+
// The hook returned a whole layer until 0.0.637: name the fix, not the
|
|
503
|
+
// three methods such a layer happens to have.
|
|
504
|
+
if (isRecordLike(remote) && typeof remote['restoreOutputs'] === 'function') {
|
|
505
|
+
throw new UserError(
|
|
506
|
+
`plugin '${plugin.name}' returned a cache layer from cache: since vx 0.0.637 the hook ` +
|
|
507
|
+
'returns the remote alone (a RemoteCacheLayer: has, get, put) and core layers it over ' +
|
|
508
|
+
'the local cache — return the remote you passed to LayeredCache',
|
|
509
|
+
)
|
|
510
|
+
}
|
|
511
|
+
assertShape(plugin, 'cache', remote, REMOTE_CACHE_METHODS, 'a remote cache layer')
|
|
512
|
+
const access = remote.access
|
|
513
|
+
const own: CachePolicy = {
|
|
514
|
+
...policy,
|
|
515
|
+
remoteRead: policy.remoteRead && access?.read !== false,
|
|
516
|
+
remoteWrite: policy.remoteWrite && access?.write !== false,
|
|
517
|
+
}
|
|
518
|
+
layers.push(
|
|
519
|
+
new LayeredCache(localCache, remote, {
|
|
520
|
+
policy: own,
|
|
521
|
+
onRemoteError: (err) => ctx.warn(`[vx] plugin '${plugin.name}': ${err.message}`),
|
|
522
|
+
}),
|
|
523
|
+
)
|
|
524
|
+
owners.push(`the cache layer of plugin '${plugin.name}'`)
|
|
496
525
|
}
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
// the local handle subsumes it below, so a remote plugin does not write
|
|
500
|
-
// the local store twice.
|
|
501
|
-
if (!layers.includes(ctx.localCache)) layers.push(ctx.localCache)
|
|
502
|
-
const wrapsLocal = layers.some((l) => l !== ctx.localCache && l.local === ctx.localCache)
|
|
503
|
-
const distinct = wrapsLocal ? layers.filter((l) => l !== ctx.localCache) : layers
|
|
504
|
-
if (distinct.length === 1) return distinct[0]!
|
|
526
|
+
if (layers.length === 0) return localCache
|
|
527
|
+
if (layers.length === 1) return layers[0]!
|
|
505
528
|
// One line per layer and method: a layer that throws on every lookup
|
|
506
529
|
// would otherwise print once per task.
|
|
507
530
|
const told = new Set<string>()
|
|
508
|
-
return new ChainedCache(
|
|
509
|
-
const layer = distinct[i]!
|
|
510
|
-
const who =
|
|
511
|
-
layer === ctx.localCache ? 'the local cache' : (owners.get(layer) ?? 'a cache layer')
|
|
531
|
+
return new ChainedCache(layers, (i, method, err) => {
|
|
512
532
|
if (told.has(`${i}\0${method}`)) return
|
|
513
533
|
told.add(`${i}\0${method}`)
|
|
514
534
|
const m = err instanceof Error ? err.message : String(err)
|
|
515
535
|
const then =
|
|
516
536
|
method === 'save' ? 'the other layers still save' : 'a miss there; the next layer answers'
|
|
517
|
-
ctx.warn(`[vx] ${
|
|
537
|
+
ctx.warn(`[vx] ${owners[i]} failed in cache ${method}: ${m}; ${then}`)
|
|
518
538
|
})
|
|
519
539
|
}
|
|
520
540
|
|
|
541
|
+
function isRecordLike(v: unknown): v is Record<string, unknown> {
|
|
542
|
+
return typeof v === 'object' && v !== null
|
|
543
|
+
}
|
|
544
|
+
|
|
521
545
|
/** The plugin each resolved executor came from, so a hook of it that throws is named. */
|
|
522
546
|
const executorPlugin = new WeakMap<TaskExecutor, string>()
|
|
523
547
|
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
import { readFileSync } from 'node:fs'
|
|
12
12
|
import path from 'node:path'
|
|
13
13
|
import { fileURLToPath } from 'node:url'
|
|
14
|
-
import type { Cache,
|
|
14
|
+
import type { Cache, RemoteCacheLayer } from '../cache/index.js'
|
|
15
15
|
import { PLUGIN_PACKAGE, type ProjectConfig, type WorkspaceConfig } from '../config.js'
|
|
16
16
|
import type { TaskExecutor } from '../exec/index.js'
|
|
17
17
|
import type { TaskNode, TaskOutcome } from '../graph/index.js'
|
|
@@ -19,13 +19,14 @@ import { maskedLine, nearest, UserError } from '../util/index.js'
|
|
|
19
19
|
import type { ProjectMeta } from '../workspace/index.js'
|
|
20
20
|
import type { EventBus, RunStartInfo } from './events.js'
|
|
21
21
|
import type { TelemetryContext, TelemetrySink } from './telemetry.js'
|
|
22
|
+
import type { Logger } from './logger.js'
|
|
22
23
|
|
|
23
24
|
/**
|
|
24
25
|
* A vx plugin. Contributes any subset of the run-level capabilities —
|
|
25
26
|
* where work runs (executor), which cache is used (cache), who
|
|
26
27
|
* observes the run (telemetry). It never changes WHAT a task is (the
|
|
27
28
|
* command string — principle #3), only where and how that command is
|
|
28
|
-
* executed. Registered explicitly in vx
|
|
29
|
+
* executed. Registered explicitly in .vx/workspace.ts via
|
|
29
30
|
* defineWorkspace({ plugins: [...] }). No auto-discovery.
|
|
30
31
|
*
|
|
31
32
|
* Made by `definePlugin(import.meta, hooks)` only: the loader refuses a
|
|
@@ -59,24 +60,32 @@ export interface VxPlugin {
|
|
|
59
60
|
config?(workspace: WorkspaceConfig, ctx: WorkspaceHookContext): void | Promise<void>
|
|
60
61
|
|
|
61
62
|
/**
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
63
|
+
* Projects beyond the directories holding a vx.config, their names, and
|
|
64
|
+
* edges between projects, as `DiscoveredProject`s (each directory
|
|
65
|
+
* absolute or relative to the workspace root): a lockfile's importers,
|
|
66
|
+
* their package names and the dependencies it records; a runner's root
|
|
67
|
+
* tasks. Runs once per discovery, before any project config loads; each
|
|
68
|
+
* plugin sees the locked projects and the ones earlier plugins added,
|
|
69
|
+
* without their edges. A directory that is not a project becomes one
|
|
70
|
+
* (with no config; named by `name`, else its path). A project named by
|
|
71
|
+
* its path takes `name`; one its config names keeps that name, and a
|
|
72
|
+
* different name from an earlier plugin is refused, as is a directory
|
|
73
|
+
* outside the root. Two projects of one name are refused once the stage
|
|
74
|
+
* ends. `deps` and `reach` from every plugin union then; a target that is
|
|
75
|
+
* no project is refused.
|
|
69
76
|
*/
|
|
70
|
-
discover?(
|
|
77
|
+
discover?(
|
|
78
|
+
ctx: DiscoverContext,
|
|
79
|
+
): readonly DiscoveredProject[] | Promise<readonly DiscoveredProject[]>
|
|
71
80
|
|
|
72
81
|
/**
|
|
73
82
|
* One project's validated config, right after it loaded and before the
|
|
74
83
|
* graph is built. Add, remove or edit tasks. Runs for every loaded project
|
|
75
|
-
* on every run — a config
|
|
76
|
-
*
|
|
77
|
-
* loaded too, as `{ tasks: {} }`, so a
|
|
78
|
-
*
|
|
79
|
-
*
|
|
84
|
+
* on every run — a locked config is the user's file, and the hook is
|
|
85
|
+
* applied on top of it live. A project with NO config file (a
|
|
86
|
+
* `discover` plugin added it) is loaded too, as `{ tasks: {} }`, so a
|
|
87
|
+
* plugin can give it tasks; with no `project` plugin such a project is
|
|
88
|
+
* never visited.
|
|
80
89
|
*/
|
|
81
90
|
project?(config: ProjectConfig, ctx: ProjectHookContext): void | Promise<void>
|
|
82
91
|
|
|
@@ -105,22 +114,14 @@ export interface VxPlugin {
|
|
|
105
114
|
| Promise<Readonly<Record<string, string>> | undefined>
|
|
106
115
|
|
|
107
116
|
/**
|
|
108
|
-
* Workspace-root files this plugin keys on
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
* every project. A file has one claimant.
|
|
117
|
-
*
|
|
118
|
-
* A root file core does NOT fold is claimed the same way when the
|
|
119
|
-
* plugin's stages read it: `turbo()` shapes every task from
|
|
120
|
-
* `turbo.json`, so an edit to it re-keys tasks through their resolved
|
|
121
|
-
* configs while no project owns the path. Nothing is taken out;
|
|
122
|
-
* `--affected` asks `affected` (undefined: every project) and `vx watch`
|
|
123
|
-
* re-runs on an edit (item 961). A claim is a bare name at the root.
|
|
117
|
+
* Workspace-root files this plugin keys on: core folds no root file into
|
|
118
|
+
* any key, so a plugin that reads one (a lockfile it folds per project
|
|
119
|
+
* through `key`, `turbo.json` that shapes every task through its
|
|
120
|
+
* `project` stage) claims it here. `--affected` asks `affected` which
|
|
121
|
+
* projects a change to it touches (undefined: every project), `vx watch`
|
|
122
|
+
* re-runs on an edit (item 961), and a task that rewrites one during the
|
|
123
|
+
* run withholds every later save. A claim is a bare name at the root; a
|
|
124
|
+
* file has one claimant.
|
|
124
125
|
*/
|
|
125
126
|
readonly fingerprint?: FingerprintClaim
|
|
126
127
|
|
|
@@ -167,14 +168,14 @@ export interface VxPlugin {
|
|
|
167
168
|
// --- BEHAVIOR capabilities (change WHAT/HOW work runs — opt-in) -----------
|
|
168
169
|
|
|
169
170
|
/**
|
|
170
|
-
* Contribute a cache
|
|
171
|
-
*
|
|
172
|
-
*
|
|
173
|
-
*
|
|
174
|
-
*
|
|
175
|
-
*
|
|
171
|
+
* Contribute a remote cache: the wire (`has`, `get`, `put`, optional
|
|
172
|
+
* `hasMany`), or undefined to decline. Consulted ONCE per prepareRun.
|
|
173
|
+
* Core layers each one behind its own local cache and owns the rest:
|
|
174
|
+
* the local store is asked first, then each remote in declaration order;
|
|
175
|
+
* the run's policy and scope gate every remote; a remote's throw is a
|
|
176
|
+
* miss and a warning naming the plugin, never a failed run.
|
|
176
177
|
*/
|
|
177
|
-
cache?(ctx: CacheContext):
|
|
178
|
+
cache?(ctx: CacheContext): RemoteCacheLayer | undefined | Promise<RemoteCacheLayer | undefined>
|
|
178
179
|
|
|
179
180
|
/**
|
|
180
181
|
* Contribute a task executor — WHERE one task's command runs. Consulted
|
|
@@ -206,6 +207,16 @@ export interface VxPlugin {
|
|
|
206
207
|
| undefined
|
|
207
208
|
| Promise<TelemetrySink | TelemetrySink[] | undefined>
|
|
208
209
|
|
|
210
|
+
/**
|
|
211
|
+
* Take the terminal for this run: return the `Logger` that renders it
|
|
212
|
+
* (the contract `run({ log })` takes), or `undefined` to leave core's
|
|
213
|
+
* renderer in place. The first plugin, in declaration order, that
|
|
214
|
+
* returns one renders the run; the rest are not asked. Not consulted
|
|
215
|
+
* when an embedder passed its own `log`. A logger that throws hands the
|
|
216
|
+
* rest of the run back to core's renderer with one warning.
|
|
217
|
+
*/
|
|
218
|
+
output?(ctx: OutputContext): Logger | undefined | Promise<Logger | undefined>
|
|
219
|
+
|
|
209
220
|
/**
|
|
210
221
|
* Optional one-time setup per run (validate the workspace, open a
|
|
211
222
|
* connection, read a token). Throwing aborts the run with a clean
|
|
@@ -248,7 +259,7 @@ export interface PluginCommand {
|
|
|
248
259
|
export interface CommandContext extends BaseContext {
|
|
249
260
|
/**
|
|
250
261
|
* The worker count a `vx run` here uses unless `--concurrency` says
|
|
251
|
-
* otherwise:
|
|
262
|
+
* otherwise: `.vx/workspace`'s `concurrency`, else what the machine allows.
|
|
252
263
|
* A verb that budgets a run (`vx history`) reads it here; its own
|
|
253
264
|
* `machineParallelism()` said 4 cores beside `concurrency: 1` (item 1068).
|
|
254
265
|
*/
|
|
@@ -256,7 +267,7 @@ export interface CommandContext extends BaseContext {
|
|
|
256
267
|
/**
|
|
257
268
|
* The argv that runs this vx again: the binary alone when compiled, the
|
|
258
269
|
* runtime and its entry script from source. A verb that runs tasks
|
|
259
|
-
*
|
|
270
|
+
* spawns `[...vx, 'run', …]` rather than guess
|
|
260
271
|
* which `vx` a PATH holds.
|
|
261
272
|
*/
|
|
262
273
|
readonly vx: readonly string[]
|
|
@@ -271,7 +282,7 @@ export interface WorkspaceHookContext {
|
|
|
271
282
|
export interface DiscoverContext extends WorkspaceHookContext {
|
|
272
283
|
/** Where vx's cache lives: a plugin may keep what it read to answer here. */
|
|
273
284
|
readonly cacheDir: string
|
|
274
|
-
/** The projects found so far:
|
|
285
|
+
/** The projects found so far: the lock's, then earlier plugins'. */
|
|
275
286
|
readonly projects: readonly ProjectMeta[]
|
|
276
287
|
/**
|
|
277
288
|
* What `git status` lists in the workspace, workspace-relative: modified,
|
|
@@ -283,24 +294,33 @@ export interface DiscoverContext extends WorkspaceHookContext {
|
|
|
283
294
|
worktreeChanges(): Promise<readonly string[] | null>
|
|
284
295
|
}
|
|
285
296
|
|
|
286
|
-
|
|
297
|
+
/** What a `discover` hook returns per directory — see `VxPlugin.discover`. */
|
|
298
|
+
export interface DiscoveredProject {
|
|
299
|
+
/** Absolute or relative to the workspace root. */
|
|
287
300
|
readonly dir: string
|
|
288
|
-
|
|
301
|
+
/**
|
|
302
|
+
* Its name, when its config names none: replaces a path name, never a
|
|
303
|
+
* config's; a different name from an earlier plugin is refused. Unset on
|
|
304
|
+
* a new directory: its root-relative path.
|
|
305
|
+
*/
|
|
306
|
+
readonly name?: string
|
|
307
|
+
/** Directories this project builds after: `^task` order, and reach for `--filter` and `--affected`. */
|
|
308
|
+
readonly deps?: readonly string[]
|
|
309
|
+
/** Directories a change reaches this project through (`--filter`, `--affected`), with no order. */
|
|
310
|
+
readonly reach?: readonly string[]
|
|
289
311
|
}
|
|
290
312
|
|
|
291
313
|
export interface ProjectHookContext extends BaseContext {
|
|
292
|
-
/** The
|
|
314
|
+
/** The project's name (`ProjectMeta.name`). */
|
|
293
315
|
readonly name: string
|
|
294
316
|
/** The project's directory, absolute. */
|
|
295
317
|
readonly dir: string
|
|
296
|
-
/** The parsed `package.json`, read-only. */
|
|
297
|
-
readonly packageJson: Readonly<Record<string, unknown>>
|
|
298
318
|
/**
|
|
299
|
-
* Every
|
|
300
|
-
* rest of the run's scope and the
|
|
301
|
-
* not — as
|
|
302
|
-
* whole workspace (a `dependsOn` is only valid against every
|
|
303
|
-
* scripts at once) reads it here instead of walking the workspace a
|
|
319
|
+
* Every project the workspace discovered — the one being visited, the
|
|
320
|
+
* rest of the run's scope and the projects outside it, config file or
|
|
321
|
+
* not — as the `discover` stage left them. A plugin whose mapping needs
|
|
322
|
+
* the whole workspace (a `dependsOn` is only valid against every
|
|
323
|
+
* package's scripts at once) reads it here instead of walking the workspace a
|
|
304
324
|
* second time. The same array is handed to every visit of a run and a
|
|
305
325
|
* new one to the next run — a plugin instance outlives a run under `vx
|
|
306
326
|
* watch`, so a per-run memo keys on this array's identity, never on the
|
|
@@ -320,16 +340,16 @@ export interface GraphHookContext extends BaseContext {
|
|
|
320
340
|
|
|
321
341
|
export interface KeyHookContext extends BaseContext {}
|
|
322
342
|
|
|
323
|
-
/** A plugin's claim on workspace
|
|
343
|
+
/** A plugin's claim on workspace-root files — see `VxPlugin.fingerprint`. */
|
|
324
344
|
export interface FingerprintClaim {
|
|
325
|
-
/** Bare names at the workspace root: a lockfile
|
|
345
|
+
/** Bare names at the workspace root the plugin reads: a lockfile (`pnpm-lock.yaml`), a settings file, `turbo.json`. */
|
|
326
346
|
readonly files: readonly string[]
|
|
327
347
|
/**
|
|
328
|
-
* The projects (
|
|
348
|
+
* The projects (by name) a change to a claimed file affects. Called
|
|
329
349
|
* by `--affected` with the file's bytes at the base ref and in the
|
|
330
350
|
* working tree (`null` where it does not exist). Return `undefined` to
|
|
331
351
|
* select every project — the answer a claimant gives when it cannot
|
|
332
|
-
* tell
|
|
352
|
+
* tell.
|
|
333
353
|
*/
|
|
334
354
|
affected(
|
|
335
355
|
change: FingerprintChange,
|
|
@@ -345,7 +365,7 @@ export interface FingerprintChange {
|
|
|
345
365
|
}
|
|
346
366
|
|
|
347
367
|
export interface FingerprintContext extends BaseContext {
|
|
348
|
-
/** Every project in the workspace:
|
|
368
|
+
/** Every project in the workspace: name and absolute directory. */
|
|
349
369
|
readonly projects: ReadonlyArray<{ readonly name: string; readonly dir: string }>
|
|
350
370
|
}
|
|
351
371
|
|
|
@@ -364,13 +384,27 @@ export interface AdmitContext {
|
|
|
364
384
|
export interface ScheduleHookContext extends BaseContext {
|
|
365
385
|
/** The run's local cache handle — its `dbHandle()` holds the run history a policy can learn from. */
|
|
366
386
|
readonly localCache: Cache
|
|
387
|
+
/** The run's branch (`currentBranch`), the timings a forecast reads first: pass it to `LocalHistoryProvider`. */
|
|
388
|
+
readonly branch: string | null
|
|
367
389
|
}
|
|
368
390
|
|
|
391
|
+
/** A `cache` hook's context: the workspace and the run's scope, never the local store or the policy, which core owns. */
|
|
369
392
|
export interface CacheContext extends BaseContext {
|
|
370
|
-
/**
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
393
|
+
/**
|
|
394
|
+
* The run's `cache.scope` (`'pr-123'`), `undefined` when none. Core already keys a scoped run's
|
|
395
|
+
* writes apart; a remote reads it to pick a credential or check a token's
|
|
396
|
+
* claims (an OIDC branch) for every call it makes.
|
|
397
|
+
*/
|
|
398
|
+
readonly scope: string | undefined
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
export interface OutputContext extends BaseContext {
|
|
402
|
+
/** Core would write to a terminal: stdout (stderr under `json`) is a TTY. */
|
|
403
|
+
readonly isTTY: boolean
|
|
404
|
+
/** The run is on CI (`CI` set, or a provider core detects). */
|
|
405
|
+
readonly ci: boolean
|
|
406
|
+
/** `--format json`: stdout carries the machine-readable result. */
|
|
407
|
+
readonly json: boolean
|
|
374
408
|
}
|
|
375
409
|
|
|
376
410
|
export interface ExecutorContext extends BaseContext {
|