@vzn/vx 0.0.88 → 0.0.90
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +5 -5
- package/src/cli/select.ts +3 -1
- package/src/cli/why.ts +3 -1
- package/src/exec/sandbox-runtime.ts +48 -6
- package/src/workspace/migrate-scripts.ts +12 -6
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vzn/vx",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.90",
|
|
4
4
|
"description": "An open, extensible monorepo task runner.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -17,10 +17,10 @@
|
|
|
17
17
|
"node": ">=18"
|
|
18
18
|
},
|
|
19
19
|
"optionalDependencies": {
|
|
20
|
-
"@vzn/vx-linux-x64": "0.0.
|
|
21
|
-
"@vzn/vx-linux-arm64": "0.0.
|
|
22
|
-
"@vzn/vx-darwin-x64": "0.0.
|
|
23
|
-
"@vzn/vx-darwin-arm64": "0.0.
|
|
20
|
+
"@vzn/vx-linux-x64": "0.0.90",
|
|
21
|
+
"@vzn/vx-linux-arm64": "0.0.90",
|
|
22
|
+
"@vzn/vx-darwin-x64": "0.0.90",
|
|
23
|
+
"@vzn/vx-darwin-arm64": "0.0.90"
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
26
|
"@anthropic-ai/sandbox-runtime": "^0.0.76"
|
package/src/cli/select.ts
CHANGED
|
@@ -353,7 +353,9 @@ export async function pickTask(
|
|
|
353
353
|
}
|
|
354
354
|
}
|
|
355
355
|
if (entries.length === 0) {
|
|
356
|
-
process.stderr.write(
|
|
356
|
+
process.stderr.write(
|
|
357
|
+
'vx run: no tasks declared in any project; declare one under `tasks` in a vx.config, or run `vx init` to write them from package.json scripts\n',
|
|
358
|
+
)
|
|
357
359
|
return null
|
|
358
360
|
}
|
|
359
361
|
const out = io.output ?? process.stdout
|
package/src/cli/why.ts
CHANGED
|
@@ -181,7 +181,9 @@ export async function whyCmd(args: readonly string[]): Promise<number> {
|
|
|
181
181
|
|
|
182
182
|
const why = whyDidThisRerun(db, runId, taskId)
|
|
183
183
|
if (!why.found) {
|
|
184
|
-
throw new UserError(
|
|
184
|
+
throw new UserError(
|
|
185
|
+
`vx why: run ${runId} has no row for ${taskId}; \`vx last --list\` shows the recorded runs, \`vx last <runId>\` what one ran`,
|
|
186
|
+
)
|
|
185
187
|
}
|
|
186
188
|
const diff = cacheKeyDiff(db, runId, taskId)
|
|
187
189
|
|
|
@@ -356,6 +356,17 @@ function trackTaskTmpdir(dir: string): void {
|
|
|
356
356
|
|
|
357
357
|
/** Whether SRT is up in this process — set by `initSandbox`, cleared by `resetSandbox`. */
|
|
358
358
|
let srtUp = false
|
|
359
|
+
/**
|
|
360
|
+
* The run's config while one of its tasks lifts SRT's `socket(AF_UNIX)`
|
|
361
|
+
* block, else undefined. SRT reads the lift from its run-wide config, so
|
|
362
|
+
* one task's `unixSockets` or port list lifted it for every sandboxed task
|
|
363
|
+
* of the run, and a task that asked for neither reached the host's docker
|
|
364
|
+
* or ssh-agent socket (L-6). Such a run sets the lift per task, for the
|
|
365
|
+
* span of its wrap, which is when SRT reads it.
|
|
366
|
+
*/
|
|
367
|
+
let socketRun: Parameters<SrtModule['SandboxManager']['updateConfig']>[0] | undefined
|
|
368
|
+
/** Wraps of a `socketRun`, one at a time: each holds SRT's config for its task. */
|
|
369
|
+
let wrapTurn: Promise<unknown> = Promise.resolve()
|
|
359
370
|
|
|
360
371
|
/**
|
|
361
372
|
* SRT listens on `<tmpdir>/srt-mux-<pid>-<seq>.sock`, seq from 0, and a
|
|
@@ -396,11 +407,11 @@ async function unlinkStaleMuxSockets(): Promise<void> {
|
|
|
396
407
|
export async function initSandbox(opts?: {
|
|
397
408
|
allowedDomains?: readonly string[]
|
|
398
409
|
/**
|
|
399
|
-
*
|
|
400
|
-
*
|
|
401
|
-
*
|
|
402
|
-
*
|
|
403
|
-
*
|
|
410
|
+
* Whether any task of the run lifts SRT's seccomp block on
|
|
411
|
+
* `socket(AF_UNIX)`: one that declares `unixSockets` or a `localBinding`
|
|
412
|
+
* port list (the port bridge is a unix socket the task's side has to
|
|
413
|
+
* create). SRT reads the lift from its run-wide config, so `wrapForTask`
|
|
414
|
+
* sets it for each task's own wrap (L-6).
|
|
404
415
|
*/
|
|
405
416
|
allowAllUnixSockets?: boolean
|
|
406
417
|
}): Promise<void> {
|
|
@@ -428,6 +439,7 @@ export async function initSandbox(opts?: {
|
|
|
428
439
|
true,
|
|
429
440
|
)
|
|
430
441
|
srtUp = true
|
|
442
|
+
socketRun = opts?.allowAllUnixSockets === true ? config : undefined
|
|
431
443
|
// `initialize()` returns early once SRT is up, and on Linux the
|
|
432
444
|
// availability probe brought it up with an EMPTY config before the run's
|
|
433
445
|
// own call — so the run's allowlist and unix-socket allowance never
|
|
@@ -463,6 +475,7 @@ export async function resetSandbox(): Promise<void> {
|
|
|
463
475
|
const { SandboxManager } = await loadSrt()
|
|
464
476
|
await SandboxManager.reset()
|
|
465
477
|
srtUp = false
|
|
478
|
+
socketRun = undefined
|
|
466
479
|
availabilityCache.clear()
|
|
467
480
|
straceAvailableCache = undefined
|
|
468
481
|
})()
|
|
@@ -793,7 +806,12 @@ export async function wrapSandboxedCommand(
|
|
|
793
806
|
: { command: taggedCommand, forwards: false, traced: false }
|
|
794
807
|
const inner =
|
|
795
808
|
ports.length > 0 ? `${portBridgeInner(ports, tag)} ${grouped.command}` : grouped.command
|
|
796
|
-
let wrapped = await
|
|
809
|
+
let wrapped = await wrapForTask(
|
|
810
|
+
SandboxManager,
|
|
811
|
+
inner,
|
|
812
|
+
customConfig,
|
|
813
|
+
ports.length > 0 || asksUnixSockets(args.config),
|
|
814
|
+
)
|
|
797
815
|
if (process.platform === 'darwin') {
|
|
798
816
|
const rules = [
|
|
799
817
|
...macProfileRules(args.config),
|
|
@@ -918,6 +936,30 @@ function ownGroupCommand(
|
|
|
918
936
|
/** The descriptor an in-sandbox strace writes its trace to (`ownGroupCommand`). */
|
|
919
937
|
const TRACE_FD = 5
|
|
920
938
|
|
|
939
|
+
function asksUnixSockets(c: Pick<ResolvedSandboxConfig, 'unixSockets'>): boolean {
|
|
940
|
+
return c.unixSockets === true || (c.unixSockets !== undefined && c.unixSockets.length > 0)
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
/** SRT's wrap, with the socket lift this task asked for or none (L-6). */
|
|
944
|
+
function wrapForTask(
|
|
945
|
+
SandboxManager: SrtModule['SandboxManager'],
|
|
946
|
+
command: string,
|
|
947
|
+
customConfig: Parameters<SrtModule['SandboxManager']['wrapWithSandbox']>[2],
|
|
948
|
+
sockets: boolean,
|
|
949
|
+
): Promise<string> {
|
|
950
|
+
const run = socketRun
|
|
951
|
+
if (run === undefined) return SandboxManager.wrapWithSandbox(command, undefined, customConfig)
|
|
952
|
+
const turn = wrapTurn.then(() => {
|
|
953
|
+
SandboxManager.updateConfig({
|
|
954
|
+
...run,
|
|
955
|
+
network: { ...run.network, allowAllUnixSockets: sockets },
|
|
956
|
+
})
|
|
957
|
+
return SandboxManager.wrapWithSandbox(command, undefined, customConfig)
|
|
958
|
+
})
|
|
959
|
+
wrapTurn = turn.catch(() => undefined)
|
|
960
|
+
return turn
|
|
961
|
+
}
|
|
962
|
+
|
|
921
963
|
/** The ports a list grants, deduped; `true` bridges nothing (the host sees no port on Linux). */
|
|
922
964
|
export function bridgedPorts(c: Pick<ResolvedSandboxConfig, 'localBinding'>): number[] {
|
|
923
965
|
return Array.isArray(c.localBinding) ? [...new Set(c.localBinding)] : []
|
|
@@ -140,13 +140,16 @@ export function migrateScripts(metas: readonly ProjectMeta[]): MigrationPlan {
|
|
|
140
140
|
if (runnable.length === 0) continue
|
|
141
141
|
const hasBuild = names.includes('build')
|
|
142
142
|
const has = (n: string): boolean => names.includes(n)
|
|
143
|
+
// Lifecycle hooks are npm's, not tasks anyone runs by name, and a hook
|
|
144
|
+
// of a script that exists rides inside that script's command.
|
|
145
|
+
const isTask = (n: string): boolean => {
|
|
146
|
+
if (!has(n) || LIFECYCLE.test(n)) return false
|
|
147
|
+
const hookOf = /^(pre|post)(.+)$/.exec(n)
|
|
148
|
+
return hookOf === null || !has(hookOf[2]!) || LIFECYCLE.test(hookOf[2]!)
|
|
149
|
+
}
|
|
143
150
|
const tasks: GeneratedTask[] = []
|
|
144
151
|
for (const name of names) {
|
|
145
|
-
|
|
146
|
-
if (LIFECYCLE.test(name)) continue
|
|
147
|
-
// A hook of a script that exists rides inside that script's command.
|
|
148
|
-
const hookOf = /^(pre|post)(.+)$/.exec(name)
|
|
149
|
-
if (hookOf !== null && has(hookOf[2]!) && !LIFECYCLE.test(hookOf[2]!)) continue
|
|
152
|
+
if (!isTask(name)) continue
|
|
150
153
|
|
|
151
154
|
const todos: string[] = []
|
|
152
155
|
const own = scripts[name] as string
|
|
@@ -155,7 +158,10 @@ export function migrateScripts(metas: readonly ProjectMeta[]): MigrationPlan {
|
|
|
155
158
|
// package manager and never ride inside a task — `pack` stays alone.
|
|
156
159
|
const hook = (h: string): boolean => has(h) && !LIFECYCLE.test(h)
|
|
157
160
|
const hooks = [`pre${name}`, `post${name}`].filter(hook)
|
|
158
|
-
|
|
161
|
+
// A group over a script that is no task (`setup: npm run prepare`)
|
|
162
|
+
// named a task nothing defines, and the run `vx init` suggested
|
|
163
|
+
// refused the config (D-12).
|
|
164
|
+
if (delegate !== null && isTask(delegate) && delegate !== name && hooks.length === 0) {
|
|
159
165
|
// A group: no exec, the graph runs the target. `npm test` calling
|
|
160
166
|
// `vitest` through `npm run test:unit` is two tasks, not a subprocess.
|
|
161
167
|
const task: Record<string, unknown> = { dependsOn: [delegate] }
|