@vzn/vx 0.0.179 → 0.0.181
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
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vzn/vx",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.181",
|
|
4
4
|
"description": "A fast, correct task runner and build cache for JavaScript monorepos.",
|
|
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.181",
|
|
21
|
+
"@vzn/vx-linux-arm64": "0.0.181",
|
|
22
|
+
"@vzn/vx-darwin-x64": "0.0.181",
|
|
23
|
+
"@vzn/vx-darwin-arm64": "0.0.181"
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
26
|
"@anthropic-ai/sandbox-runtime": "^0.0.76"
|
|
@@ -39,6 +39,9 @@ import path from 'node:path'
|
|
|
39
39
|
import { MAX_TIMEOUT_MS, UserError } from '../util/index.js'
|
|
40
40
|
import { nonJsonMessage, nonJsonPaths, type NonJsonValue } from './json-data.js'
|
|
41
41
|
|
|
42
|
+
/** Why a config's `process.exit` throws, on both load paths (D-65). */
|
|
43
|
+
export const CONFIG_EXIT = 'a config exports its object; it cannot end the run'
|
|
44
|
+
|
|
42
45
|
const WORKER_SRC = `
|
|
43
46
|
const nonJsonPaths = ${nonJsonPaths.toString()}
|
|
44
47
|
// The parent's stdout is a verb's JSON or vx mcp's JSON-RPC stream, and a
|
|
@@ -60,6 +63,11 @@ const quiet = (async () => {
|
|
|
60
63
|
const ownBunWrite = B.write
|
|
61
64
|
B.write = (dest, ...rest) => ownBunWrite(dest === B.stdout ? B.stderr : dest, ...rest)
|
|
62
65
|
B.stdout.writer = (...args) => B.stderr.writer(...args)
|
|
66
|
+
// An exit here ended the worker unheard, and the load waited out its
|
|
67
|
+
// whole deadline to say the worker did not answer (D-65).
|
|
68
|
+
P.exit = (code) => {
|
|
69
|
+
throw new Error('process.exit(' + (code ?? '') + ') in a config: ' + ${JSON.stringify(CONFIG_EXIT)})
|
|
70
|
+
}
|
|
63
71
|
})()
|
|
64
72
|
self.onmessage = async (e) => {
|
|
65
73
|
await quiet
|
|
@@ -124,12 +132,12 @@ interface Pending {
|
|
|
124
132
|
}
|
|
125
133
|
|
|
126
134
|
/**
|
|
127
|
-
* How long a single config evaluation may take
|
|
128
|
-
*
|
|
129
|
-
* worker cannot stall a long-lived process indefinitely. Read
|
|
130
|
-
* test can drive the deadline instead of waiting it out.
|
|
135
|
+
* How long a single config evaluation may take, in the worker or in process
|
|
136
|
+
* (D-66). Real evaluations are ~10 ms; this exists only so a hung config or a
|
|
137
|
+
* killed worker cannot stall a run or a long-lived process indefinitely. Read
|
|
138
|
+
* per call so a test can drive the deadline instead of waiting it out.
|
|
131
139
|
*/
|
|
132
|
-
function
|
|
140
|
+
export function evalBudgetMs(): number {
|
|
133
141
|
const raw = process.env['VX_CONFIG_WORKER_TIMEOUT_MS']
|
|
134
142
|
// Same kind of value as the teardown deadline, so the same treatment: a
|
|
135
143
|
// BOUND on a worker that may be wedged, with no "no limit" reading. Falls
|
|
@@ -258,7 +266,7 @@ export async function evaluateConfigFresh(configPath: string): Promise<unknown>
|
|
|
258
266
|
// otherwise take milliseconds. Nothing else bounds it: there is no
|
|
259
267
|
// run-level timeout. The budget is enormous next to the ~10 ms a real
|
|
260
268
|
// evaluation costs, so it can only fire on a genuine wedge.
|
|
261
|
-
const budget =
|
|
269
|
+
const budget = evalBudgetMs()
|
|
262
270
|
const reply = await new Promise<WorkerReply>((resolve, reject) => {
|
|
263
271
|
pending.set(id, { resolve, reject })
|
|
264
272
|
timer = setTimeout(() => {
|
|
@@ -3,7 +3,7 @@ import path from 'node:path'
|
|
|
3
3
|
import type { ProjectConfig, WorkspaceConfig } from '../config.js'
|
|
4
4
|
import { UserError, xxh3hex } from '../util/index.js'
|
|
5
5
|
import { validateProjectConfig, validateWorkspace } from './config-schema.js'
|
|
6
|
-
import { beginEvalRound, evaluateConfigFresh } from './config-eval.js'
|
|
6
|
+
import { beginEvalRound, CONFIG_EXIT, evalBudgetMs, evaluateConfigFresh } from './config-eval.js'
|
|
7
7
|
import { hasEsmExport, unprovidedBareImports } from './config-imports.js'
|
|
8
8
|
import {
|
|
9
9
|
configEvalKey,
|
|
@@ -136,8 +136,27 @@ async function loadDefaultExport(
|
|
|
136
136
|
holdSource(hash, source)
|
|
137
137
|
}
|
|
138
138
|
let ns: { default?: unknown }
|
|
139
|
+
const unguard = guardExit()
|
|
140
|
+
let deadline: ReturnType<typeof setTimeout> | undefined
|
|
139
141
|
try {
|
|
140
|
-
|
|
142
|
+
// The worker's budget, here too: a top-level await that never settled
|
|
143
|
+
// while a timer kept the loop alive hung `vx run` for good, silently
|
|
144
|
+
// (D-66).
|
|
145
|
+
const budget = evalBudgetMs()
|
|
146
|
+
ns = (await Promise.race([
|
|
147
|
+
import(specifier),
|
|
148
|
+
new Promise<never>((_, reject) => {
|
|
149
|
+
deadline = setTimeout(
|
|
150
|
+
() =>
|
|
151
|
+
reject(
|
|
152
|
+
new UserError(
|
|
153
|
+
`${kind} config ${configPath} did not finish evaluating within ${budget}ms (VX_CONFIG_WORKER_TIMEOUT_MS)`,
|
|
154
|
+
),
|
|
155
|
+
),
|
|
156
|
+
budget,
|
|
157
|
+
)
|
|
158
|
+
}),
|
|
159
|
+
])) as { default?: unknown }
|
|
141
160
|
} catch (err) {
|
|
142
161
|
// A served module's frames name its specifier; the user wrote the path.
|
|
143
162
|
if (err instanceof Error && err.stack !== undefined) {
|
|
@@ -145,6 +164,8 @@ async function loadDefaultExport(
|
|
|
145
164
|
}
|
|
146
165
|
throw configLoadError(err, configPath, kind) ?? err
|
|
147
166
|
} finally {
|
|
167
|
+
clearTimeout(deadline)
|
|
168
|
+
unguard()
|
|
148
169
|
if (source !== null) releaseSource(hash)
|
|
149
170
|
}
|
|
150
171
|
const mod = ns?.default
|
|
@@ -152,6 +173,28 @@ async function loadDefaultExport(
|
|
|
152
173
|
return mod
|
|
153
174
|
}
|
|
154
175
|
|
|
176
|
+
/**
|
|
177
|
+
* A config's `process.exit` ended vx mid-load: `exit(0)` was a green run
|
|
178
|
+
* that ran nothing and printed nothing (D-65). While any config evaluates
|
|
179
|
+
* in process it throws instead, at the config's line. vx's own exit is
|
|
180
|
+
* signal forwarding's, which a run installs after its load and removes
|
|
181
|
+
* when it ends.
|
|
182
|
+
*/
|
|
183
|
+
let evaluating = 0
|
|
184
|
+
let ownExit: typeof process.exit = process.exit
|
|
185
|
+
function guardExit(): () => void {
|
|
186
|
+
if (evaluating++ === 0) {
|
|
187
|
+
ownExit = process.exit
|
|
188
|
+
process.exit = configExit
|
|
189
|
+
}
|
|
190
|
+
return () => {
|
|
191
|
+
if (--evaluating === 0) process.exit = ownExit
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
const configExit = ((code?: number | string | null) => {
|
|
195
|
+
throw new Error(`process.exit(${code ?? ''}) in a config: ${CONFIG_EXIT}`)
|
|
196
|
+
}) as typeof process.exit
|
|
197
|
+
|
|
155
198
|
/**
|
|
156
199
|
* A bare import nothing above the config provides is refused BEFORE the
|
|
157
200
|
* evaluation: left to Bun, a workspace with no `node_modules` would have
|