@vzn/vx 0.0.596 → 0.0.598
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/launcher.cjs +7 -3
- package/package.json +5 -5
- package/src/bin.ts +9 -5
- package/src/cache/archive.ts +35 -6
- package/src/cache/cache.ts +60 -31
- package/src/cache/git-inputs.ts +23 -0
- package/src/cache/index.ts +2 -0
- package/src/cache/inputs.ts +87 -32
- package/src/cache/key-fold.ts +1 -1
- package/src/cache/layer.ts +2 -1
- package/src/cache/layered-cache.ts +14 -9
- package/src/cache/output-index.ts +12 -7
- package/src/cache/run-history.ts +16 -8
- package/src/cli/cache.ts +2 -2
- package/src/cli/foreign-flags.ts +56 -2
- package/src/cli/info.ts +1 -1
- package/src/cli/init.ts +2 -2
- package/src/cli/last.ts +6 -3
- package/src/cli/lock.ts +5 -3
- package/src/cli/plan-format.ts +26 -20
- package/src/cli/run.ts +42 -15
- package/src/cli/select.ts +19 -5
- package/src/cli/show.ts +21 -27
- package/src/cli/upgrade.ts +1 -1
- package/src/cli/watch-cycle.ts +43 -0
- package/src/cli/watch-fs.ts +11 -3
- package/src/cli/watch.ts +50 -11
- package/src/cli/workspace-config.ts +3 -1
- package/src/config.ts +21 -0
- package/src/exec/executor.ts +1 -1
- package/src/exec/kill-tree.ts +121 -20
- package/src/exec/runner.ts +224 -48
- package/src/exec/sandbox-paths.ts +11 -0
- package/src/exec/sandbox-runtime.ts +187 -54
- package/src/exec/sandbox-violations.ts +59 -6
- package/src/graph/priorities.ts +1 -2
- package/src/graph/scheduler.ts +2 -2
- package/src/graph/task-graph.ts +145 -33
- package/src/index.ts +3 -1
- package/src/orchestrator/affected-tasks.ts +6 -2
- package/src/orchestrator/deferred-outputs.ts +3 -2
- package/src/orchestrator/doctor.ts +5 -3
- package/src/orchestrator/excluded-keys.ts +2 -13
- package/src/orchestrator/execute-task.ts +70 -28
- package/src/orchestrator/failure-mode.ts +38 -29
- package/src/orchestrator/framed-output.ts +5 -5
- package/src/orchestrator/hit-restore.ts +1 -1
- package/src/orchestrator/index.ts +0 -1
- package/src/orchestrator/keyed-projects.ts +6 -3
- package/src/orchestrator/lockfile-claim.ts +14 -7
- package/src/orchestrator/logger.ts +17 -7
- package/src/orchestrator/miss-save.ts +7 -11
- package/src/orchestrator/plan.ts +4 -20
- package/src/orchestrator/plugin-host.ts +7 -2
- package/src/orchestrator/plugin.ts +5 -2
- package/src/orchestrator/prepare.ts +4 -2
- package/src/orchestrator/projects.ts +1 -1
- package/src/orchestrator/run-artifacts.ts +2 -2
- package/src/orchestrator/run-context.ts +1 -1
- package/src/orchestrator/run-lock.ts +35 -25
- package/src/orchestrator/run.ts +2 -1
- package/src/orchestrator/sandbox-request.ts +31 -9
- package/src/orchestrator/signals.ts +22 -4
- package/src/orchestrator/stable-keys.ts +3 -11
- package/src/orchestrator/summary.ts +5 -9
- package/src/orchestrator/task-hash.ts +13 -3
- package/src/orchestrator/upstream.ts +11 -0
- package/src/util/hangup.ts +42 -0
- package/src/util/index.ts +2 -0
- package/src/util/paths.ts +1 -1
- package/src/workspace/affected.ts +79 -42
- package/src/workspace/config-cache.ts +15 -1
- package/src/workspace/config-eval.ts +47 -7
- package/src/workspace/config-imports.ts +8 -55
- package/src/workspace/config-schema.ts +30 -6
- package/src/workspace/lockfile.ts +2 -1
- package/src/workspace/migrate-scripts.ts +1 -1
- package/src/workspace/project-loader.ts +33 -6
- package/src/workspace/workspace.ts +36 -14
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
CONFIG_EXIT,
|
|
10
10
|
evalBudgetMs,
|
|
11
11
|
evaluateConfigFresh,
|
|
12
|
+
thrownValueMessage,
|
|
12
13
|
WATCHED_BUILTIN_NAMES,
|
|
13
14
|
} from './config-eval.js'
|
|
14
15
|
import { hasEsmExport, unprovidedBareImports } from './config-imports.js'
|
|
@@ -86,7 +87,12 @@ function assertDefaultObject(mod: unknown, kind: string, configPath: string): vo
|
|
|
86
87
|
const heldSources = new Map<string, { source: string; uses: number }>()
|
|
87
88
|
let serving = false
|
|
88
89
|
|
|
89
|
-
const HELD_QUERY = /\?vx-held=([0-9a-f]+)$/
|
|
90
|
+
const HELD_QUERY = /\?vx-(?:held|literal)=([0-9a-f]+)$/
|
|
91
|
+
|
|
92
|
+
// Bun's resolver reads `\` as a separator, even in a file: URL, so a
|
|
93
|
+
// config under `a\b` was looked for under `a/b`. Resolving it here keeps
|
|
94
|
+
// the path as written; only the served load can take it.
|
|
95
|
+
const LITERAL_QUERY = /\?vx-literal=[0-9a-f]+$/
|
|
90
96
|
|
|
91
97
|
function serveHeldSources(): void {
|
|
92
98
|
if (serving) return
|
|
@@ -94,6 +100,7 @@ function serveHeldSources(): void {
|
|
|
94
100
|
Bun.plugin({
|
|
95
101
|
name: 'vx-config-bytes',
|
|
96
102
|
setup(build) {
|
|
103
|
+
build.onResolve({ filter: LITERAL_QUERY }, (args) => ({ path: args.path, namespace: 'file' }))
|
|
97
104
|
build.onLoad({ filter: HELD_QUERY }, (args) => ({
|
|
98
105
|
contents: heldSources.get(HELD_QUERY.exec(args.path)![1]!)!.source,
|
|
99
106
|
loader: /\.[cm]?ts\?/.test(args.path) ? 'ts' : 'js',
|
|
@@ -135,7 +142,7 @@ const COMMONJS_HINT =
|
|
|
135
142
|
/\b(?:module|exports|require|this|__dirname|__filename)\b|\\|\bexport\s*=(?!=)/
|
|
136
143
|
|
|
137
144
|
/** vx's module-cache query, which no user wrote: stripped from anything shown to them. */
|
|
138
|
-
const BUST_QUERY = /\?vx-(?:bust|held)=[^'"\s]*/g
|
|
145
|
+
const BUST_QUERY = /\?vx-(?:bust|held|literal)=[^'"\s]*/g
|
|
139
146
|
|
|
140
147
|
// Bun has native TS / ESM execution — no transpiler dep needed. We fold
|
|
141
148
|
// a short content hash into the import URL as a cache-bust key so that:
|
|
@@ -156,10 +163,18 @@ async function loadDefaultExport(
|
|
|
156
163
|
// URL yet, and a repeat load (the one that could replay an evaluation
|
|
157
164
|
// made under earlier env values) re-evaluates in a worker instead
|
|
158
165
|
// (`loadedConfigs`, item 678).
|
|
166
|
+
const literal = configPath.includes('\\')
|
|
159
167
|
const source =
|
|
160
|
-
kind === 'Project'
|
|
168
|
+
kind === 'Project' || literal
|
|
169
|
+
? servableSource(bytes, /\.[cm]?ts$/.test(configPath) ? 'ts' : 'js')
|
|
170
|
+
: null
|
|
171
|
+
if (literal && source === null) {
|
|
172
|
+
throw new UserError(
|
|
173
|
+
`${kind} config ${configPath} sits under a path that holds a backslash, which Bun loads only as an ES module in UTF-8`,
|
|
174
|
+
)
|
|
175
|
+
}
|
|
161
176
|
const hash = xxh3hex(bytes)
|
|
162
|
-
const specifier = `${configPath}?vx-${source !== null ? 'held' : 'bust'}=${hash}`
|
|
177
|
+
const specifier = `${configPath}?vx-${literal ? 'literal' : source !== null ? 'held' : 'bust'}=${hash}`
|
|
163
178
|
if (source !== null) {
|
|
164
179
|
serveHeldSources()
|
|
165
180
|
holdSource(hash, source)
|
|
@@ -191,7 +206,7 @@ async function loadDefaultExport(
|
|
|
191
206
|
if (err instanceof Error && err.stack !== undefined) {
|
|
192
207
|
err.stack = err.stack.replaceAll(specifier, configPath).replace(BUST_QUERY, '')
|
|
193
208
|
}
|
|
194
|
-
throw
|
|
209
|
+
throw loadFailure(err, configPath, kind)
|
|
195
210
|
} finally {
|
|
196
211
|
clearTimeout(deadline)
|
|
197
212
|
unguard()
|
|
@@ -341,6 +356,16 @@ export function configLoadError(err: unknown, configPath: string, kind: string):
|
|
|
341
356
|
return null
|
|
342
357
|
}
|
|
343
358
|
|
|
359
|
+
/** What a failed load throws: Bun's own errors and non-Error throws as user errors, the rest as thrown. */
|
|
360
|
+
function loadFailure(err: unknown, configPath: string, kind: string): unknown {
|
|
361
|
+
const user = configLoadError(err, configPath, kind)
|
|
362
|
+
if (user !== null) return user
|
|
363
|
+
if (err !== null && typeof err === 'object' && typeof (err as Error).message === 'string') {
|
|
364
|
+
return err
|
|
365
|
+
}
|
|
366
|
+
return new UserError(thrownValueMessage(kind, configPath, Bun.inspect(err, { compact: true })))
|
|
367
|
+
}
|
|
368
|
+
|
|
344
369
|
export interface LoadProjectConfigOptions {
|
|
345
370
|
/** Observe the CURRENT environment: no eval cache, even when `evalCache` is passed. */
|
|
346
371
|
fresh?: boolean
|
|
@@ -523,7 +548,7 @@ export async function loadProjectConfigs(
|
|
|
523
548
|
if (repeat) refuseUnprovidedImports(bytes!, configPath, 'Project')
|
|
524
549
|
const mod = repeat
|
|
525
550
|
? await evaluateConfigFresh(configPath).catch((err: unknown) => {
|
|
526
|
-
throw
|
|
551
|
+
throw loadFailure(err, configPath, 'Project')
|
|
527
552
|
})
|
|
528
553
|
: await loadDefaultExport(configPath, 'Project', bytes!)
|
|
529
554
|
// Before anything reads through them: a replaced `Array.prototype.includes`
|
|
@@ -812,6 +837,7 @@ const BUN_MEMBERS_VX_READS: readonly PropertyKey[] = [
|
|
|
812
837
|
'env',
|
|
813
838
|
'file',
|
|
814
839
|
'hash',
|
|
840
|
+
'inspect',
|
|
815
841
|
'main',
|
|
816
842
|
'nanoseconds',
|
|
817
843
|
'plugin',
|
|
@@ -820,6 +846,7 @@ const BUN_MEMBERS_VX_READS: readonly PropertyKey[] = [
|
|
|
820
846
|
'semver',
|
|
821
847
|
'serve',
|
|
822
848
|
'sleep',
|
|
849
|
+
'sleepSync',
|
|
823
850
|
'spawn',
|
|
824
851
|
'spawnSync',
|
|
825
852
|
'stderr',
|
|
@@ -290,13 +290,17 @@ async function readRootManifests(
|
|
|
290
290
|
)
|
|
291
291
|
}
|
|
292
292
|
if (ws === undefined || ws === null) return { globs: ['.'], catalogs }
|
|
293
|
+
// bun's `{ catalog }` and yarn's `{ nohoist }` name no members, and both
|
|
294
|
+
// managers run the root alone; vx refused them as no array (D-151).
|
|
295
|
+
if (typeof ws === 'object' && !Array.isArray(ws) && !('packages' in ws)) {
|
|
296
|
+
return { globs: ['.'], catalogs }
|
|
297
|
+
}
|
|
293
298
|
if (ws && typeof ws === 'object' && !Array.isArray(ws) && 'packages' in ws) {
|
|
294
|
-
const
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
)
|
|
299
|
-
return { globs, catalogs }
|
|
299
|
+
const packages = (ws as { packages?: unknown }).packages
|
|
300
|
+
// Yarn 1 and 4 run the root alone under `packages: null`; read as an
|
|
301
|
+
// empty list, the root was no project and every verb ran nothing (D-150).
|
|
302
|
+
if (packages === null) return { globs: ['.'], catalogs }
|
|
303
|
+
return { globs: assertGlobList(packages, pkgPath, 'workspaces.packages'), catalogs }
|
|
300
304
|
}
|
|
301
305
|
return { globs: assertGlobList(ws, pkgPath, 'workspaces'), catalogs }
|
|
302
306
|
}
|
|
@@ -454,13 +458,27 @@ function extglobRefusal(pattern: string, file: string, field: string): UserError
|
|
|
454
458
|
* to the workspace root.
|
|
455
459
|
*/
|
|
456
460
|
export function resolveCacheDir(root: string, config: WorkspaceConfig | null): string {
|
|
457
|
-
const
|
|
461
|
+
const env = process.env['VX_CACHE_DIR']
|
|
462
|
+
// `cacheDir: ' '` is refused at load (X-22); the variable made the same
|
|
463
|
+
// directory named in spaces at the root (D-160).
|
|
464
|
+
if (config?.cacheDir === undefined && env !== undefined && env !== '' && env.trim() === '') {
|
|
465
|
+
throw new UserError('VX_CACHE_DIR is only whitespace — name a directory, or unset it')
|
|
466
|
+
}
|
|
467
|
+
const rel = config?.cacheDir ?? (env || path.join('.vx', 'cache'))
|
|
468
|
+
const home = process.env['HOME'] || homedir()
|
|
458
469
|
// No shell expands `~` in a config string or a quoted variable, and
|
|
459
470
|
// `'~/.cache/vx'` made a directory named `~` in the workspace.
|
|
460
|
-
|
|
461
|
-
|
|
471
|
+
const dir =
|
|
472
|
+
rel === '~' || rel.startsWith('~/') ? path.join(home, rel.slice(1)) : path.resolve(root, rel)
|
|
473
|
+
// The cache writes a `*` .gitignore beside its index: a bare `~` made a
|
|
474
|
+
// directory named `~`, and expanded it would land in home itself (D-153).
|
|
475
|
+
if (path.resolve(dir) === path.resolve(home)) {
|
|
476
|
+
const source = config?.cacheDir !== undefined ? 'cacheDir' : 'VX_CACHE_DIR'
|
|
477
|
+
throw new UserError(
|
|
478
|
+
`${source} ${JSON.stringify(rel)} is the home directory itself, where the cache's \`*\` .gitignore and index would land — name a directory under it, like '~/.cache/vx'`,
|
|
479
|
+
)
|
|
462
480
|
}
|
|
463
|
-
return
|
|
481
|
+
return dir
|
|
464
482
|
}
|
|
465
483
|
|
|
466
484
|
/**
|
|
@@ -711,12 +729,17 @@ async function readManifest(root: string, dir: string, file: string): Promise<st
|
|
|
711
729
|
)
|
|
712
730
|
if (searchable) unreadable(err, file)
|
|
713
731
|
process.stderr.write(
|
|
714
|
-
`vx: ${
|
|
732
|
+
`vx: ${shownDir(root, dir)} is not readable by this user — skipped, with any project in it\n`,
|
|
715
733
|
)
|
|
716
734
|
return null
|
|
717
735
|
}
|
|
718
736
|
}
|
|
719
737
|
|
|
738
|
+
/** `dir` as a discovery line names it: the root's own path is '' (D-127, D-154). */
|
|
739
|
+
function shownDir(root: string, dir: string): string {
|
|
740
|
+
return relPosix(root, dir) || 'the workspace root'
|
|
741
|
+
}
|
|
742
|
+
|
|
720
743
|
export async function listProjects(workspace: Workspace): Promise<ProjectMeta[]> {
|
|
721
744
|
return discoverProjects(workspace)
|
|
722
745
|
}
|
|
@@ -769,7 +792,7 @@ export async function discoverProjects(
|
|
|
769
792
|
// same flight as the failed read, so naming it costs nothing.
|
|
770
793
|
if (configPath !== null) {
|
|
771
794
|
process.stderr.write(
|
|
772
|
-
`vx: ${
|
|
795
|
+
`vx: ${shownDir(workspace.root, dir)} has a vx config but no package.json — skipped: vx names a project by its package.json "name"\n`,
|
|
773
796
|
)
|
|
774
797
|
}
|
|
775
798
|
return null
|
|
@@ -800,9 +823,8 @@ export async function discoverProjects(
|
|
|
800
823
|
// fine for a dir that declares no tasks; a dir with a vx config was
|
|
801
824
|
// meant to run.
|
|
802
825
|
if (configPath !== null) {
|
|
803
|
-
const rel = relPosix(workspace.root, dir)
|
|
804
826
|
process.stderr.write(
|
|
805
|
-
`vx: ${
|
|
827
|
+
`vx: ${shownDir(workspace.root, dir)} has a vx config but its package.json has no "name" — skipped\n`,
|
|
806
828
|
)
|
|
807
829
|
}
|
|
808
830
|
continue
|