@vzn/vx 0.0.0 → 0.0.12
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 +21 -0
- package/README.md +332 -1
- package/launcher.mjs +84 -0
- package/package.json +44 -3
- package/src/bin.ts +25 -0
- package/src/cache/cache.ts +1832 -0
- package/src/cache/cas-backend.ts +97 -0
- package/src/cache/digest.ts +47 -0
- package/src/cache/index.ts +29 -0
- package/src/cache/inputs.ts +833 -0
- package/src/cache/layered-cache.ts +335 -0
- package/src/cache/remote-cache.ts +251 -0
- package/src/cache/tar.ts +343 -0
- package/src/cli/backend.ts +23 -0
- package/src/cli/cache.ts +101 -0
- package/src/cli/format.ts +15 -0
- package/src/cli/help.ts +123 -0
- package/src/cli/index.ts +85 -0
- package/src/cli/info.ts +83 -0
- package/src/cli/lock.ts +135 -0
- package/src/cli/mcp-rpc.ts +292 -0
- package/src/cli/mcp.ts +92 -0
- package/src/cli/migrate-nx.ts +450 -0
- package/src/cli/migrate-turbo.ts +364 -0
- package/src/cli/migrate.ts +219 -0
- package/src/cli/plan-format.ts +148 -0
- package/src/cli/run.ts +661 -0
- package/src/cli/show.ts +201 -0
- package/src/cli/upgrade.ts +103 -0
- package/src/cli/watch.ts +287 -0
- package/src/config.ts +421 -0
- package/src/exec/env.ts +74 -0
- package/src/exec/index.ts +13 -0
- package/src/exec/runner.ts +470 -0
- package/src/exec/sandbox-runtime.ts +578 -0
- package/src/graph/dependency-spec.ts +69 -0
- package/src/graph/index.ts +18 -0
- package/src/graph/scheduler.ts +482 -0
- package/src/graph/task-graph.ts +325 -0
- package/src/index.ts +197 -0
- package/src/orchestrator/colors.ts +63 -0
- package/src/orchestrator/devframe-surface.ts +74 -0
- package/src/orchestrator/events.ts +277 -0
- package/src/orchestrator/execute-task.ts +883 -0
- package/src/orchestrator/framed-output.ts +461 -0
- package/src/orchestrator/history.ts +148 -0
- package/src/orchestrator/index.ts +163 -0
- package/src/orchestrator/local-shortcircuit.ts +147 -0
- package/src/orchestrator/logger.ts +526 -0
- package/src/orchestrator/metrics.ts +1576 -0
- package/src/orchestrator/options.ts +176 -0
- package/src/orchestrator/plan.ts +129 -0
- package/src/orchestrator/plugin-host.ts +190 -0
- package/src/orchestrator/plugin.ts +283 -0
- package/src/orchestrator/predict.ts +106 -0
- package/src/orchestrator/prepare.ts +297 -0
- package/src/orchestrator/protocol.ts +142 -0
- package/src/orchestrator/remote-cache-setup.ts +60 -0
- package/src/orchestrator/remote-prefetch.ts +81 -0
- package/src/orchestrator/run-artifacts.ts +111 -0
- package/src/orchestrator/run-context.ts +205 -0
- package/src/orchestrator/run-report.ts +132 -0
- package/src/orchestrator/run-state.ts +145 -0
- package/src/orchestrator/run.ts +842 -0
- package/src/orchestrator/stable-keys.ts +177 -0
- package/src/orchestrator/status-line.ts +287 -0
- package/src/orchestrator/summary.ts +291 -0
- package/src/orchestrator/tally.ts +69 -0
- package/src/orchestrator/task-hash.ts +238 -0
- package/src/orchestrator/telemetry-host.ts +80 -0
- package/src/orchestrator/telemetry.ts +350 -0
- package/src/orchestrator/upstream.ts +93 -0
- package/src/orchestrator/verify.ts +141 -0
- package/src/orchestrator/wire-render.ts +92 -0
- package/src/orchestrator/wire.ts +212 -0
- package/src/util/errors.ts +11 -0
- package/src/util/hash.ts +25 -0
- package/src/util/index.ts +7 -0
- package/src/util/paths.ts +16 -0
- package/src/util/ulid.ts +15 -0
- package/src/version.ts +10 -0
- package/src/workspace/affected.ts +118 -0
- package/src/workspace/filter.ts +155 -0
- package/src/workspace/fingerprint.ts +41 -0
- package/src/workspace/index.ts +27 -0
- package/src/workspace/lockfile.ts +118 -0
- package/src/workspace/nested-dirs.ts +34 -0
- package/src/workspace/package-graph.ts +148 -0
- package/src/workspace/project-loader.ts +470 -0
- package/src/workspace/workspace.ts +176 -0
package/src/config.ts
ADDED
|
@@ -0,0 +1,421 @@
|
|
|
1
|
+
// Public schema for vx project and workspace configuration.
|
|
2
|
+
|
|
3
|
+
export interface WorkspaceConfig {
|
|
4
|
+
/** Maximum concurrent tasks. Defaults to the number of CPUs. */
|
|
5
|
+
concurrency?: number
|
|
6
|
+
/** Cache directory, relative to the workspace root. Defaults to `.vx/cache`. */
|
|
7
|
+
cacheDir?: string
|
|
8
|
+
/**
|
|
9
|
+
* Default per-task timeout (ms) — the lowest-precedence fallback for a
|
|
10
|
+
* task that declares no `exec.timeout`. Precedence, highest first:
|
|
11
|
+
* per-task `exec.timeout` → `VX_TASK_TIMEOUT` env → this workspace
|
|
12
|
+
* default. Purely a runaway-process guard; never folded into a cache
|
|
13
|
+
* key (a timed-out task fails and is never cached). Omitted → no default.
|
|
14
|
+
*/
|
|
15
|
+
timeout?: number
|
|
16
|
+
/**
|
|
17
|
+
* Plugins registered for this workspace. Each plugin is an
|
|
18
|
+
* in-process subscriber on the run event bus, installed once per
|
|
19
|
+
* `vx run`. See `docs/design/extension-protocol-2026-06.md` §5.
|
|
20
|
+
*/
|
|
21
|
+
plugins?: readonly Plugin[]
|
|
22
|
+
/**
|
|
23
|
+
* Opt in to history-aware predictive scheduling. When `true` and
|
|
24
|
+
* `cache.db` has prior runs, the scheduler picks the next ready
|
|
25
|
+
* task by expected remaining critical-path duration (HistoryTable
|
|
26
|
+
* p50) instead of the static reverse-deps count.
|
|
27
|
+
*/
|
|
28
|
+
predictive?: boolean
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Structural plugin shape — any subset of the run-level capabilities
|
|
33
|
+
* (`backend`/`cache`/`telemetry`/`eventSink`) plus optional
|
|
34
|
+
* `setup`/`teardown`. The fully-typed `VxPlugin` lives in
|
|
35
|
+
* `src/orchestrator/plugin.ts` and is what users import; this is a
|
|
36
|
+
* re-declaration with opaque function types so `config.ts` stays a leaf
|
|
37
|
+
* module (no orchestrator import). The orchestrator casts to `VxPlugin`
|
|
38
|
+
* before consulting capabilities.
|
|
39
|
+
*/
|
|
40
|
+
export interface Plugin {
|
|
41
|
+
readonly name: string
|
|
42
|
+
backend?(ctx: unknown): unknown
|
|
43
|
+
cache?(ctx: unknown): unknown
|
|
44
|
+
telemetry?(ctx: unknown): unknown
|
|
45
|
+
eventSink?(ctx: unknown): unknown
|
|
46
|
+
setup?(ctx: unknown): void | Promise<void>
|
|
47
|
+
teardown?(): void | Promise<void>
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface ProjectConfig {
|
|
51
|
+
/** Tasks declared by this project, keyed by task name. */
|
|
52
|
+
tasks?: Record<string, TaskConfig>
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface TaskConfig {
|
|
56
|
+
/**
|
|
57
|
+
* Optional one-line description of what this task does. Surfaced in
|
|
58
|
+
* the interactive task picker and the `--dry` text view so users
|
|
59
|
+
* can grep for what each task does without opening every config.
|
|
60
|
+
* Pure metadata — no effect on caching, scheduling, or execution.
|
|
61
|
+
*/
|
|
62
|
+
description?: string
|
|
63
|
+
/**
|
|
64
|
+
* Shell command + optional env. One command per task. Omit `exec` to
|
|
65
|
+
* declare a **group task** — a no-op that exists only to chain
|
|
66
|
+
* `dependsOn`. Running a group is equivalent to running its
|
|
67
|
+
* dependencies; nothing else happens (no spawn, no I/O, no cache
|
|
68
|
+
* read/write). Useful for "install" / "ci" umbrella tasks that fan
|
|
69
|
+
* out to per-package work.
|
|
70
|
+
*/
|
|
71
|
+
exec?: ExecConfig
|
|
72
|
+
/**
|
|
73
|
+
* Tasks that must complete successfully before this task runs.
|
|
74
|
+
* Turbo/Nx-style micro-syntax:
|
|
75
|
+
* - `'name'` — same-project task named `name`. When authored
|
|
76
|
+
* through `defineProject`, a bare entry is
|
|
77
|
+
* **type-checked against this config's task keys**
|
|
78
|
+
* (a typo is a compile error).
|
|
79
|
+
* - `'^name'` — task `name` in every transitive workspace dep
|
|
80
|
+
* (e.g. `'^build'`, `'^all'`); not key-checked.
|
|
81
|
+
* - `'pkg#name'` — specific package's `name` task; not key-checked.
|
|
82
|
+
*/
|
|
83
|
+
dependsOn?: readonly string[]
|
|
84
|
+
/**
|
|
85
|
+
* Caching configuration. **Caching is opt-in.** If this field is omitted,
|
|
86
|
+
* the task always runs and nothing is read from or written to the cache.
|
|
87
|
+
* Provide a `cache` block (with at least `outputs`) to enable caching.
|
|
88
|
+
* Requires `exec` — a group task can't cache anything because nothing
|
|
89
|
+
* runs.
|
|
90
|
+
*/
|
|
91
|
+
cache?: CacheConfig
|
|
92
|
+
/**
|
|
93
|
+
* Sandbox configuration. **Sandbox is opt-in per task.**
|
|
94
|
+
* - omitted entirely → task runs unsandboxed.
|
|
95
|
+
* - `sandbox: {}` → opts in with the minimum baseline (only
|
|
96
|
+
* resolved inputs readable, only static-prefix of outputs
|
|
97
|
+
* writable, network blocked).
|
|
98
|
+
* - `sandbox: { ... }` → opts in with the explicit options below.
|
|
99
|
+
*
|
|
100
|
+
* No inheritance, no workspace defaults, no built-in escapes. The
|
|
101
|
+
* sandbox sees STRICTLY the union of resolved `cache.inputs.files`
|
|
102
|
+
* + this block's `allowRead` (for reads) and the static prefix of
|
|
103
|
+
* `cache.outputs.files` + this block's `allowWrite` (for writes).
|
|
104
|
+
* If a task needs `node_modules` or `/tmp`, declare them here
|
|
105
|
+
* explicitly.
|
|
106
|
+
*
|
|
107
|
+
* **Policy: fail on violation.** If the sandbox detects an
|
|
108
|
+
* undeclared read/write (macOS log monitor) or the child returns
|
|
109
|
+
* non-zero because it couldn't reach a path it expected (Linux
|
|
110
|
+
* structural deny), the task fails. No cache is written for a
|
|
111
|
+
* failed task.
|
|
112
|
+
*
|
|
113
|
+
* Activation requires `exec` (group tasks have no command to wrap)
|
|
114
|
+
* and is silently skipped for persistent tasks (dev servers need
|
|
115
|
+
* unrestricted network + an indefinite process).
|
|
116
|
+
*/
|
|
117
|
+
sandbox?: SandboxConfig
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Per-task sandbox config. Mirrors the user-facing surface of
|
|
122
|
+
* `@anthropic-ai/sandbox-runtime`'s `SandboxRuntimeConfig`, minus
|
|
123
|
+
* deployment-only fields (binary paths, proxy ports, ripgrep config).
|
|
124
|
+
*
|
|
125
|
+
* Path-list fields (`allowRead`, `allowWrite`, `network.allowUnixSockets`)
|
|
126
|
+
* accept:
|
|
127
|
+
* - relative paths → resolved against the project dir
|
|
128
|
+
* - absolute paths → used as-is (`/etc/passwd`, `/tmp`)
|
|
129
|
+
* - tilde paths → expanded against the user's home (`~/.npmrc`)
|
|
130
|
+
*
|
|
131
|
+
* No globs in path lists — bwrap on Linux only accepts path prefixes.
|
|
132
|
+
*/
|
|
133
|
+
export interface SandboxConfig {
|
|
134
|
+
// === Filesystem ===
|
|
135
|
+
/**
|
|
136
|
+
* Additional read-allowed paths beyond resolved `cache.inputs.files`.
|
|
137
|
+
* These are unioned with the declared inputs to form the complete
|
|
138
|
+
* allowRead set passed to the sandbox.
|
|
139
|
+
*/
|
|
140
|
+
allowRead?: string[]
|
|
141
|
+
/**
|
|
142
|
+
* Additional write-allowed paths beyond the static prefix of
|
|
143
|
+
* `cache.outputs.files`. Tasks with no declared outputs and no extra
|
|
144
|
+
* `allowWrite` can write to nowhere — typical for `lint`/`typecheck`.
|
|
145
|
+
*/
|
|
146
|
+
allowWrite?: string[]
|
|
147
|
+
/**
|
|
148
|
+
* Permit writes to `.git/config` files. Default `false` — most build
|
|
149
|
+
* tools should not be reconfiguring git.
|
|
150
|
+
*/
|
|
151
|
+
allowGitConfig?: boolean
|
|
152
|
+
|
|
153
|
+
// === Network ===
|
|
154
|
+
/**
|
|
155
|
+
* Network policy.
|
|
156
|
+
* - `false` (default) — block all outbound traffic.
|
|
157
|
+
* - `true` — allow all outbound traffic.
|
|
158
|
+
* - object — fine-grained control (domains / sockets / etc.).
|
|
159
|
+
*/
|
|
160
|
+
network?: boolean | SandboxNetworkConfig
|
|
161
|
+
|
|
162
|
+
// === Process behavior ===
|
|
163
|
+
/**
|
|
164
|
+
* Allow the task to acquire a pseudo-terminal. Default `false`.
|
|
165
|
+
* Needed by tasks that interact with a TTY (rare in CI).
|
|
166
|
+
*/
|
|
167
|
+
allowPty?: boolean
|
|
168
|
+
/**
|
|
169
|
+
* Linux only. Allow nested sandboxes (a sandboxed task spawning
|
|
170
|
+
* another sandboxed task). Default `false`.
|
|
171
|
+
*/
|
|
172
|
+
enableWeakerNestedSandbox?: boolean
|
|
173
|
+
/**
|
|
174
|
+
* macOS only. Skip the network namespace; routes traffic via the
|
|
175
|
+
* host proxy instead of unsharing networking. Lower overhead but
|
|
176
|
+
* weaker isolation. Default `false`.
|
|
177
|
+
*/
|
|
178
|
+
enableWeakerNetworkIsolation?: boolean
|
|
179
|
+
|
|
180
|
+
// === Violation policy ===
|
|
181
|
+
/**
|
|
182
|
+
* Map of command-substring → list of paths to ignore violations on
|
|
183
|
+
* for that command. Lets you silence known-noisy probes (e.g. a
|
|
184
|
+
* compiler that statx's many candidate header paths). Per SRT's
|
|
185
|
+
* own ignoreViolations field.
|
|
186
|
+
*/
|
|
187
|
+
ignoreViolations?: Record<string, string[]>
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export interface SandboxNetworkConfig {
|
|
191
|
+
/**
|
|
192
|
+
* Domain patterns the task may reach. Wildcards: `*.example.com`,
|
|
193
|
+
* `*` (allow all).
|
|
194
|
+
*/
|
|
195
|
+
allowedDomains?: string[]
|
|
196
|
+
/** Domains explicitly blocked, evaluated before allowedDomains. */
|
|
197
|
+
deniedDomains?: string[]
|
|
198
|
+
/** Unix socket paths the task may connect to. */
|
|
199
|
+
allowUnixSockets?: string[]
|
|
200
|
+
/** Allow any Unix socket. Use with care. */
|
|
201
|
+
allowAllUnixSockets?: boolean
|
|
202
|
+
/**
|
|
203
|
+
* Allow binding to local ports (e.g. a test that boots a server on
|
|
204
|
+
* localhost for itself to query).
|
|
205
|
+
*/
|
|
206
|
+
allowLocalBinding?: boolean
|
|
207
|
+
/** macOS only. Mach service names the task may look up. */
|
|
208
|
+
allowMachLookup?: string[]
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export interface ExecConfig {
|
|
212
|
+
/** Shell command to run, from the project's directory. */
|
|
213
|
+
command: string
|
|
214
|
+
/** Environment exposed to the child process. */
|
|
215
|
+
env?: ExecEnv
|
|
216
|
+
/**
|
|
217
|
+
* Upper bound (ms) before vx SIGTERMs the child. A normal task that
|
|
218
|
+
* runs longer is killed and reported `failed` (timed out) — never
|
|
219
|
+
* cached. Omitted → no limit.
|
|
220
|
+
*
|
|
221
|
+
* For a `persistent` task this bounds the READINESS wait instead: if
|
|
222
|
+
* `readyWhen` hasn't matched within the window the child is SIGTERMed
|
|
223
|
+
* and the task fails — without it a server that prints an unexpected
|
|
224
|
+
* banner would hang the run forever. A persistent task that's ready
|
|
225
|
+
* on spawn (no `readyWhen`) becomes ready before the timer can fire,
|
|
226
|
+
* so the timeout is a no-op for it.
|
|
227
|
+
*/
|
|
228
|
+
timeout?: number
|
|
229
|
+
/**
|
|
230
|
+
* Max ADDITIONAL attempts after a failed attempt. `retries: 2` means
|
|
231
|
+
* up to 3 executions total. 0 / omitted → no retries (today's
|
|
232
|
+
* behavior: fail on the first non-zero exit). A retry fires after ANY
|
|
233
|
+
* failure, timeout kills included; a Ctrl-C teardown (`aborted`) is
|
|
234
|
+
* never retried. Declared outputs are re-cleaned before each retry so
|
|
235
|
+
* a failed attempt's partial outputs can't leak into the next. The
|
|
236
|
+
* final outcome (and the cached artifact) is the last attempt's.
|
|
237
|
+
* Not allowed with `persistent` — a persistent task has no exit to
|
|
238
|
+
* retry. Explicit config wins over the run-level `--retry <n>`
|
|
239
|
+
* default, including `retries: 0`.
|
|
240
|
+
*/
|
|
241
|
+
retries?: number
|
|
242
|
+
/**
|
|
243
|
+
* Long-running / continuous task (dev server, watcher, daemon).
|
|
244
|
+
* When present, the task is spawned but the runner does NOT wait
|
|
245
|
+
* for it to exit. Instead it considers the task "ready" — either
|
|
246
|
+
* immediately on spawn (no `readyWhen`) or once the readiness
|
|
247
|
+
* pattern matches a line of stdout/stderr. Downstream tasks unblock
|
|
248
|
+
* on ready, not on exit. The orchestrator SIGTERMs every persistent
|
|
249
|
+
* subprocess once the rest of the graph has finished.
|
|
250
|
+
*
|
|
251
|
+
* `cache` is silently ignored for persistent tasks — there's no
|
|
252
|
+
* exit code to cache and the work continues after vx's run notion
|
|
253
|
+
* of "done".
|
|
254
|
+
*/
|
|
255
|
+
persistent?: PersistentConfig
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
export interface PersistentConfig {
|
|
259
|
+
/**
|
|
260
|
+
* Regex pattern (string) matched against stdout/stderr line-by-line.
|
|
261
|
+
* The task is marked "ready" the first time a line matches. Omitted
|
|
262
|
+
* → ready immediately on spawn (fire-and-forget; downstream still
|
|
263
|
+
* runs but doesn't gate on a signal).
|
|
264
|
+
*
|
|
265
|
+
* Example: Vite prints `Local: http://localhost:5173/` when its
|
|
266
|
+
* dev server is up — `readyWhen: 'Local:'` waits for that line.
|
|
267
|
+
*/
|
|
268
|
+
readyWhen?: string
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
export interface ExecEnv {
|
|
272
|
+
/**
|
|
273
|
+
* Names of env vars whose values are taken from the host (parent
|
|
274
|
+
* `process.env`) and forwarded to the child. Their values are NOT
|
|
275
|
+
* folded into the cache key — for things like CI flags, regional
|
|
276
|
+
* vars, secrets that change between machines without affecting output.
|
|
277
|
+
*/
|
|
278
|
+
passThrough?: string[]
|
|
279
|
+
/**
|
|
280
|
+
* Explicit `name: value` pairs. The values are set on the child AND
|
|
281
|
+
* folded into the cache key (they are literal in your config, captured
|
|
282
|
+
* via the task config hash).
|
|
283
|
+
*/
|
|
284
|
+
define?: Record<string, string>
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
export interface CacheConfig {
|
|
288
|
+
/** What participates in the cache key. */
|
|
289
|
+
inputs: CacheInputs
|
|
290
|
+
/** What this task produces. Captured for restore on a cache hit. */
|
|
291
|
+
outputs: CacheOutputs
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
export interface CacheOutputs {
|
|
295
|
+
/**
|
|
296
|
+
* Files produced by this task, as project-relative globs. Pass `[]`
|
|
297
|
+
* when the task has no files to capture (e.g. `lint`, `typecheck`).
|
|
298
|
+
* Outputs are not filtered through gitignore — typical artifact dirs
|
|
299
|
+
* like `dist/` and `coverage/` are captured normally even when ignored.
|
|
300
|
+
*/
|
|
301
|
+
files: string[]
|
|
302
|
+
/**
|
|
303
|
+
* Files produced OUTSIDE the project dir, as workspace-root-relative
|
|
304
|
+
* globs (the Turbo `$TURBO_ROOT$` / Nx `{workspaceRoot}` escape
|
|
305
|
+
* hatch). NO project-boundary rule applies: these globs may capture
|
|
306
|
+
* files anywhere in the workspace, including inside other projects'
|
|
307
|
+
* directories. Deliberate escape hatch — prefer project-relative
|
|
308
|
+
* `files` whenever the task can write inside its own dir. Two tasks
|
|
309
|
+
* declaring overlapping workspace outputs is user responsibility;
|
|
310
|
+
* vx does not police it.
|
|
311
|
+
*/
|
|
312
|
+
workspaceFiles?: string[]
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
export interface CacheInputs {
|
|
316
|
+
/**
|
|
317
|
+
* Project-relative globs. Each entry is a positive glob, or a negation
|
|
318
|
+
* prefixed with `!`. Required: pass a recursive glob to mean "all
|
|
319
|
+
* project files" (gitignore-aware), or narrow as needed. An empty
|
|
320
|
+
* array means no file inputs at all.
|
|
321
|
+
*
|
|
322
|
+
* Declared outputs and any nested-project files are excluded
|
|
323
|
+
* automatically — a task cannot invalidate itself, and cannot read
|
|
324
|
+
* across project boundaries.
|
|
325
|
+
*/
|
|
326
|
+
files: string[]
|
|
327
|
+
/**
|
|
328
|
+
* Workspace-root-relative globs (the Turbo `$TURBO_ROOT$` / Nx
|
|
329
|
+
* `{workspaceRoot}` equivalent), for inputs that live outside the
|
|
330
|
+
* project dir — a root tsconfig, shared codegen output, etc. Same
|
|
331
|
+
* syntax as `files` (`!` negation supported), resolved against the
|
|
332
|
+
* workspace-wide git file set (gitignored files are invisible).
|
|
333
|
+
*
|
|
334
|
+
* NO project-boundary rule applies: these globs may match files
|
|
335
|
+
* inside other projects' directories. Deliberate escape hatch —
|
|
336
|
+
* prefer project-relative `files` declarations where possible.
|
|
337
|
+
* Declared `outputs.workspaceFiles` are excluded automatically (a
|
|
338
|
+
* task cannot invalidate itself).
|
|
339
|
+
*/
|
|
340
|
+
workspaceFiles?: string[]
|
|
341
|
+
/**
|
|
342
|
+
* Env var names whose runtime values from parent `process.env` are
|
|
343
|
+
* folded into the cache key. **Independent of `exec.env`** — declaring
|
|
344
|
+
* a name here does not forward it to the child; it only affects cache
|
|
345
|
+
* invalidation. To both forward AND track, list the name in both
|
|
346
|
+
* `exec.env.passThrough` and here.
|
|
347
|
+
*/
|
|
348
|
+
env?: string[]
|
|
349
|
+
/**
|
|
350
|
+
* Which upstream tasks' cache keys participate in this task's key.
|
|
351
|
+
* Uses the same Turbo/Nx micro-syntax as `dependsOn`, plus
|
|
352
|
+
* wildcards and negation for filtering:
|
|
353
|
+
*
|
|
354
|
+
* - `'*'` include every same-project upstream
|
|
355
|
+
* - `'^*'` include every dep-workspace upstream
|
|
356
|
+
* - `'name'` include same-project task `name`
|
|
357
|
+
* - `'^name'` include the `name` task in dep workspaces
|
|
358
|
+
* - `'pkg#name'` include the specific package's `name` task
|
|
359
|
+
* - `'!name'`, `'!^name'`, `'!pkg#name'` — exclude
|
|
360
|
+
*
|
|
361
|
+
* Patterns are applied in order, last write wins, so
|
|
362
|
+
* `['*', '^*', '!^noisy']` reads as "all upstream except deps' noisy".
|
|
363
|
+
*
|
|
364
|
+
* Defaults:
|
|
365
|
+
* - omitted entirely → all upstream contribute (same as `['*', '^*']`).
|
|
366
|
+
* - `[]` → fully decoupled; no upstream contributes.
|
|
367
|
+
*/
|
|
368
|
+
tasks?: readonly string[]
|
|
369
|
+
/**
|
|
370
|
+
* Shell commands run in the PROJECT dir at hash time; their combined,
|
|
371
|
+
* trimmed (stdout + stderr) output is folded into the cache key.
|
|
372
|
+
* Deduped per (projectDir, command) within a run. A non-zero exit
|
|
373
|
+
* fails the run. Modeled on `env`: the COMMANDS are frozen in the
|
|
374
|
+
* lock, the OUTPUT is resolved live every run — correct under
|
|
375
|
+
* `--frozen`. Use for project-specific runtime probes.
|
|
376
|
+
*/
|
|
377
|
+
runtime?: string[]
|
|
378
|
+
/**
|
|
379
|
+
* Like `runtime`, but commands run at the WORKSPACE ROOT and are
|
|
380
|
+
* deduped GLOBALLY per command across the whole run — a `node -v`
|
|
381
|
+
* declared in 500 projects spawns exactly once. The runtime-input
|
|
382
|
+
* analog of `workspaceFiles`: per-task, root-anchored. Use for global
|
|
383
|
+
* tool versions, OS info, etc.
|
|
384
|
+
*/
|
|
385
|
+
workspaceRuntime?: string[]
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* A valid `dependsOn` entry, given the set of sibling task names `K`:
|
|
390
|
+
* - `K` — a same-project task; type-checked against the
|
|
391
|
+
* keys of THIS config's `tasks` (typo → error).
|
|
392
|
+
* - `` `^${string}` `` — a task in workspace deps (e.g. `'^build'`,
|
|
393
|
+
* `'^all'`); the dep's task names aren't known
|
|
394
|
+
* here, so any `^`-prefixed string is allowed.
|
|
395
|
+
* - `` `${string}#${string}` `` — a specific package's task
|
|
396
|
+
* (`'pkg#build'`); cross-project, not key-checked.
|
|
397
|
+
*/
|
|
398
|
+
type DependsOnEntry<K extends string> = K | `^${string}` | `${string}#${string}`
|
|
399
|
+
|
|
400
|
+
/**
|
|
401
|
+
* Identity function — exists only so TypeScript narrows literal types
|
|
402
|
+
* and, crucially, **validates `dependsOn` against this project's own
|
|
403
|
+
* task names**. A bare entry that isn't a declared task key is a compile
|
|
404
|
+
* error; `^name` / `pkg#name` forms reference other projects and stay
|
|
405
|
+
* free strings. Runtime behavior is unchanged (it returns its input).
|
|
406
|
+
*/
|
|
407
|
+
export function defineProject<const T extends ProjectConfig>(
|
|
408
|
+
config: T & {
|
|
409
|
+
tasks?: {
|
|
410
|
+
[K in keyof NonNullable<T['tasks']>]?: {
|
|
411
|
+
dependsOn?: readonly DependsOnEntry<Extract<keyof NonNullable<T['tasks']>, string>>[]
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
},
|
|
415
|
+
): T {
|
|
416
|
+
return config as T
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
export function defineWorkspace<T extends WorkspaceConfig>(config: T): T {
|
|
420
|
+
return config
|
|
421
|
+
}
|
package/src/exec/env.ts
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import path from 'node:path'
|
|
2
|
+
|
|
3
|
+
// Build the environment exposed to a task.
|
|
4
|
+
//
|
|
5
|
+
// Layers, lowest to highest priority:
|
|
6
|
+
// 1. Essentials (hard-coded allowlist for shell tooling).
|
|
7
|
+
// 2. passThrough: parent process.env values for the named vars.
|
|
8
|
+
// 3. define: explicit name=value pairs from the task config.
|
|
9
|
+
// 4. binPaths prepended to PATH (after all the above resolve PATH).
|
|
10
|
+
|
|
11
|
+
const ESSENTIAL_ENV: readonly string[] = [
|
|
12
|
+
'PATH',
|
|
13
|
+
'HOME',
|
|
14
|
+
'SHELL',
|
|
15
|
+
'USER',
|
|
16
|
+
'LOGNAME',
|
|
17
|
+
'TMPDIR',
|
|
18
|
+
'TEMP',
|
|
19
|
+
'TMP',
|
|
20
|
+
'LANG',
|
|
21
|
+
'LC_ALL',
|
|
22
|
+
'LC_CTYPE',
|
|
23
|
+
'TERM',
|
|
24
|
+
'COLORTERM',
|
|
25
|
+
'FORCE_COLOR',
|
|
26
|
+
'NO_COLOR',
|
|
27
|
+
'CI',
|
|
28
|
+
'NODE_OPTIONS',
|
|
29
|
+
'SYSTEMROOT',
|
|
30
|
+
'APPDATA',
|
|
31
|
+
'LOCALAPPDATA',
|
|
32
|
+
'PROGRAMDATA',
|
|
33
|
+
'PROGRAMFILES',
|
|
34
|
+
'PROGRAMFILES(X86)',
|
|
35
|
+
'COMSPEC',
|
|
36
|
+
'PATHEXT',
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
export interface BuildEnvOptions {
|
|
40
|
+
passThrough: readonly string[]
|
|
41
|
+
define: Readonly<Record<string, string>>
|
|
42
|
+
source: NodeJS.ProcessEnv
|
|
43
|
+
/**
|
|
44
|
+
* Directories prepended to the child's PATH (highest priority first).
|
|
45
|
+
* Used to expose the project's own `node_modules/.bin` so user commands
|
|
46
|
+
* (`oxlint`, `tsc`, `vitest`, …) resolve without a PM wrapper. Matches
|
|
47
|
+
* vite-task's behavior — explicit per-project bin, no tree walk, so
|
|
48
|
+
* sibling projects' bins stay invisible per the project-isolation rule.
|
|
49
|
+
*/
|
|
50
|
+
binPaths?: readonly string[]
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function buildIsolatedEnv(opts: BuildEnvOptions): NodeJS.ProcessEnv {
|
|
54
|
+
const out: NodeJS.ProcessEnv = {}
|
|
55
|
+
|
|
56
|
+
for (const name of ESSENTIAL_ENV) {
|
|
57
|
+
const value = opts.source[name]
|
|
58
|
+
if (value !== undefined) out[name] = value
|
|
59
|
+
}
|
|
60
|
+
for (const name of opts.passThrough) {
|
|
61
|
+
const value = opts.source[name]
|
|
62
|
+
if (value !== undefined) out[name] = value
|
|
63
|
+
}
|
|
64
|
+
for (const [name, value] of Object.entries(opts.define)) {
|
|
65
|
+
out[name] = value
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (opts.binPaths && opts.binPaths.length > 0) {
|
|
69
|
+
const prefix = opts.binPaths.join(path.delimiter)
|
|
70
|
+
out['PATH'] = out['PATH'] ? `${prefix}${path.delimiter}${out['PATH']}` : prefix
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return out
|
|
74
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// Module contract. Cross-module imports must come through here; see
|
|
2
|
+
// docs/design/module-isolation-2026-06.md and tests/module-boundaries.test.ts.
|
|
3
|
+
|
|
4
|
+
export { buildIsolatedEnv } from './env.js'
|
|
5
|
+
export { runCommand, runPersistent, signalExitCode } from './runner.js'
|
|
6
|
+
export {
|
|
7
|
+
initSandbox,
|
|
8
|
+
probeSandbox,
|
|
9
|
+
resetSandbox,
|
|
10
|
+
resolveSandboxConfig,
|
|
11
|
+
runSandboxed,
|
|
12
|
+
type SandboxViolation,
|
|
13
|
+
} from './sandbox-runtime.js'
|