@vzn/vx 0.0.180 → 0.0.182

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.180",
3
+ "version": "0.0.182",
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.180",
21
- "@vzn/vx-linux-arm64": "0.0.180",
22
- "@vzn/vx-darwin-x64": "0.0.180",
23
- "@vzn/vx-darwin-arm64": "0.0.180"
20
+ "@vzn/vx-linux-x64": "0.0.182",
21
+ "@vzn/vx-linux-arm64": "0.0.182",
22
+ "@vzn/vx-darwin-x64": "0.0.182",
23
+ "@vzn/vx-darwin-arm64": "0.0.182"
24
24
  },
25
25
  "dependencies": {
26
26
  "@anthropic-ai/sandbox-runtime": "^0.0.76"
@@ -132,12 +132,12 @@ interface Pending {
132
132
  }
133
133
 
134
134
  /**
135
- * How long a single config evaluation may take before we treat the worker as
136
- * wedged. Real evaluations are ~10 ms; this exists only so a killed or hung
137
- * worker cannot stall a long-lived process indefinitely. Read per call so a
138
- * 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.
139
139
  */
140
- function workerTimeoutMs(): number {
140
+ export function evalBudgetMs(): number {
141
141
  const raw = process.env['VX_CONFIG_WORKER_TIMEOUT_MS']
142
142
  // Same kind of value as the teardown deadline, so the same treatment: a
143
143
  // BOUND on a worker that may be wedged, with no "no limit" reading. Falls
@@ -266,7 +266,7 @@ export async function evaluateConfigFresh(configPath: string): Promise<unknown>
266
266
  // otherwise take milliseconds. Nothing else bounds it: there is no
267
267
  // run-level timeout. The budget is enormous next to the ~10 ms a real
268
268
  // evaluation costs, so it can only fire on a genuine wedge.
269
- const budget = workerTimeoutMs()
269
+ const budget = evalBudgetMs()
270
270
  const reply = await new Promise<WorkerReply>((resolve, reject) => {
271
271
  pending.set(id, { resolve, reject })
272
272
  timer = setTimeout(() => {
@@ -74,7 +74,14 @@ export function nonJsonPaths(value: unknown): NonJsonValue[] {
74
74
  ancestors.push(v)
75
75
  for (const key of Object.keys(v)) {
76
76
  keys.push(key)
77
- walk((v as Record<string, unknown>)[key], false)
77
+ // Read by descriptor: a getter is code, and each read may answer
78
+ // differently. `vx show` read one three times, so the key, the
79
+ // check and the command could each see another value, and a
80
+ // throwing one surfaced as vx's own stack (D-67). Never called here.
81
+ const d = Object.getOwnPropertyDescriptor(v, key)
82
+ if (d !== undefined && d.get !== undefined) found('a getter')
83
+ else if (d !== undefined && d.set !== undefined) found('a setter')
84
+ else walk(d?.value, false)
78
85
  keys.pop()
79
86
  }
80
87
  ancestors.pop()
@@ -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, CONFIG_EXIT, 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,
@@ -137,8 +137,26 @@ async function loadDefaultExport(
137
137
  }
138
138
  let ns: { default?: unknown }
139
139
  const unguard = guardExit()
140
+ let deadline: ReturnType<typeof setTimeout> | undefined
140
141
  try {
141
- ns = (await import(specifier)) as { default?: unknown }
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 }
142
160
  } catch (err) {
143
161
  // A served module's frames name its specifier; the user wrote the path.
144
162
  if (err instanceof Error && err.stack !== undefined) {
@@ -146,6 +164,7 @@ async function loadDefaultExport(
146
164
  }
147
165
  throw configLoadError(err, configPath, kind) ?? err
148
166
  } finally {
167
+ clearTimeout(deadline)
149
168
  unguard()
150
169
  if (source !== null) releaseSource(hash)
151
170
  }