@vzn/vx 0.0.427 → 0.0.429

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.427",
3
+ "version": "0.0.429",
4
4
  "description": "The fastest task runner and build cache for JavaScript monorepos.",
5
5
  "type": "module",
6
6
  "exports": {
@@ -21,10 +21,10 @@
21
21
  "node": ">=18"
22
22
  },
23
23
  "optionalDependencies": {
24
- "@vzn/vx-linux-x64": "0.0.427",
25
- "@vzn/vx-linux-arm64": "0.0.427",
26
- "@vzn/vx-darwin-x64": "0.0.427",
27
- "@vzn/vx-darwin-arm64": "0.0.427"
24
+ "@vzn/vx-linux-x64": "0.0.429",
25
+ "@vzn/vx-linux-arm64": "0.0.429",
26
+ "@vzn/vx-darwin-x64": "0.0.429",
27
+ "@vzn/vx-darwin-arm64": "0.0.429"
28
28
  },
29
29
  "dependencies": {
30
30
  "@anthropic-ai/sandbox-runtime": "^0.0.76"
package/src/cli/init.ts CHANGED
@@ -374,6 +374,9 @@ function namelessNotes(root: string, dirs: readonly string[]): string[] {
374
374
  })
375
375
  .map((dir) => path.relative(root, dir).split(path.sep).join('/'))
376
376
  .sort()
377
+ // The root's own manifest is the empty relative path: a nameless
378
+ // single-package repo read `not mapped: — its package.json…` (M-54).
379
+ .map((rel) => (rel === '' ? 'the root' : rel))
377
380
  if (withScripts.length === 0) return []
378
381
  const shown =
379
382
  withScripts.slice(0, 3).join(', ') +
@@ -128,8 +128,12 @@ self.onmessage = async (e) => {
128
128
  const envBefore = watched ? { ...live } : null
129
129
  const cwdBefore = watched ? globalThis.process.cwd() : null
130
130
  // The umask is the process's, not the worker's: a config's umask(0o777)
131
- // here left every file vx wrote after it 000 (D-125). Put back below.
132
- const umaskIn = globalThis.process.umask()
131
+ // here left every file vx wrote after it 000 (D-125). Read only when
132
+ // blaming, the one evaluation in flight: Bun reads it by setting 0 and
133
+ // back, so this read beside the main thread's left the process at 0
134
+ // and blamed an innocent config. An ordinary load's change is the
135
+ // loader's to see, on the main thread, once the round is done.
136
+ const umaskIn = watched ? globalThis.process.umask() : null
133
137
  const same = (a, b) =>
134
138
  a !== undefined && b !== undefined && Object.is(a.value, b.value) && a.get === b.get && a.set === b.set &&
135
139
  a.writable === b.writable && a.enumerable === b.enumerable && a.configurable === b.configurable
@@ -148,9 +152,7 @@ self.onmessage = async (e) => {
148
152
  return out
149
153
  }
150
154
  const umaskBack = () => {
151
- if (globalThis.process.umask() === umaskIn) return false
152
- globalThis.process.umask(umaskIn)
153
- return true
155
+ if (umaskIn !== null && globalThis.process.umask() !== umaskIn) globalThis.process.umask(umaskIn)
154
156
  }
155
157
  try {
156
158
  const ns = await import(path)
@@ -160,24 +162,29 @@ self.onmessage = async (e) => {
160
162
  const mod = await ns?.default
161
163
  const isObject = mod !== null && typeof mod === 'object'
162
164
  const nonJson = isObject ? nonJsonPaths(mod) : []
165
+ // Seen, then put back, BEFORE the reply: the caller terminates a
166
+ // blaming worker as soon as it answers, and a restore after the reply
167
+ // lost that race and left the process at the config's mask.
168
+ const report = changed()
169
+ umaskBack()
163
170
  postMessage({
164
171
  id,
165
172
  ok: true,
166
173
  nonJson,
167
174
  json: isObject && nonJson.length === 0 ? JSON.stringify(mod) : null,
168
175
  fn: typeof mod === 'function',
169
- changed: changed(),
170
- umask: umaskBack(),
176
+ changed: report,
171
177
  })
172
178
  } catch (err) {
179
+ const report = changed()
180
+ umaskBack()
173
181
  postMessage({
174
182
  id,
175
183
  ok: false,
176
184
  name: err?.name ?? 'Error',
177
185
  message: err?.message ?? String(err),
178
186
  stack: err?.stack ?? null,
179
- changed: changed(),
180
- umask: umaskBack(),
187
+ changed: report,
181
188
  position:
182
189
  err?.position && typeof err.position === 'object'
183
190
  ? { file: err.position.file, line: err.position.line, column: err.position.column }
@@ -189,9 +196,6 @@ self.onmessage = async (e) => {
189
196
 
190
197
  const FUNCTION_EXPORT = (): void => {}
191
198
 
192
- /** A repeat load moved the process's umask; the loader names the config (D-125). */
193
- export class UmaskChanged extends Error {}
194
-
195
199
  const WORKER_URL = `data:text/javascript,${encodeURIComponent(WORKER_SRC)}`
196
200
 
197
201
  interface WorkerReply {
@@ -200,8 +204,6 @@ interface WorkerReply {
200
204
  json: string | null
201
205
  /** The default export is a function, which JSON cannot carry back. */
202
206
  fn?: boolean
203
- /** The evaluation moved the process's umask; the worker put it back. */
204
- umask?: boolean
205
207
  nonJson: NonJsonValue[]
206
208
  name: string
207
209
  message: string
@@ -363,7 +365,6 @@ export async function evaluateConfigFresh(configPath: string): Promise<unknown>
363
365
  timer.unref?.()
364
366
  w.postMessage({ id, path: abs, env: { ...process.env } })
365
367
  })
366
- if (reply.umask === true) throw new UmaskChanged()
367
368
  const [nonJson] = reply.nonJson
368
369
  if (nonJson !== undefined) throw new UserError(nonJsonMessage(configPath, nonJson))
369
370
  // A function stands in for the one the config exported, so the caller's
@@ -9,7 +9,6 @@ import {
9
9
  CONFIG_EXIT,
10
10
  evalBudgetMs,
11
11
  evaluateConfigFresh,
12
- UmaskChanged,
13
12
  WATCHED_BUILTIN_NAMES,
14
13
  } from './config-eval.js'
15
14
  import { hasEsmExport, unprovidedBareImports } from './config-imports.js'
@@ -496,7 +495,6 @@ export async function loadProjectConfigs(
496
495
  if (repeat) refuseUnprovidedImports(bytes!, configPath, 'Project')
497
496
  const mod = repeat
498
497
  ? await evaluateConfigFresh(configPath).catch((err: unknown) => {
499
- if (err instanceof UmaskChanged) throw builtinsChanged(['process.umask'], configPath)
500
498
  throw configLoadError(err, configPath, 'Project') ?? err
501
499
  })
502
500
  : await loadDefaultExport(configPath, 'Project', bytes!)
@@ -508,7 +506,10 @@ export async function loadProjectConfigs(
508
506
  ...restoreBuiltins(builtins, overlapping),
509
507
  ...restoreEnv(env),
510
508
  ...restoreCwd(cwd),
511
- ...restoreUmask(umask),
509
+ // Alone in the round: nothing else reads the umask now. Beside
510
+ // other loads the round reads it once they are done (see
511
+ // `currentUmask`).
512
+ ...(overlapping ? [] : restoreUmask(umask)),
512
513
  ]
513
514
  // Loads overlap, so another config's change can surface after this
514
515
  // one: named here, the refusal blamed the wrong file (D-119). The round
@@ -604,12 +605,22 @@ export async function loadProjectConfigs(
604
605
  for (const i of misses) {
605
606
  const configPath = prepared[i]!.configPath
606
607
  const own = await builtinsChangedBy(configPath)
608
+ // The blaming worker is gone, so this thread reads alone; and a
609
+ // worker that outlived its budget was ended before it could put
610
+ // the umask back.
611
+ restoreUmask(umask)
607
612
  if (own.length > 0) throw builtinsChanged(own, configPath)
608
613
  }
609
614
  throw builtinsChanged(changedInRound)
610
615
  }
611
616
  if (first !== undefined) throw first.failed
612
- if (changed.length > 0) throw builtinsChanged(changed)
617
+ // One load in the round, in the worker (a repeat load), is the one
618
+ // that moved it: the worker no longer reads the umask to say so.
619
+ if (changed.length > 0)
620
+ throw builtinsChanged(
621
+ changed,
622
+ misses.length === 1 ? prepared[misses[0]!]!.configPath : undefined,
623
+ )
613
624
  return results.map((r) => (r as Loaded).config)
614
625
  } finally {
615
626
  endRound()
@@ -906,8 +917,9 @@ function currentUmask(): number {
906
917
  // Through globalThis, as restoreCwd: the playground's shim has no umask.
907
918
  const proc = globalThis.process as { umask?: (mask?: number) => number }
908
919
  if (typeof proc.umask !== 'function') return -1
909
- // No argument reads it: setting 0 and back would open a window where a
910
- // file written by an overlapping write landed world-writable.
920
+ // Bun reads it by setting 0 and putting it back (probed on 1.4.2: four
921
+ // workers reading at once left the process at 0), so it is read on this
922
+ // thread only, and never while the config worker evaluates.
911
923
  return proc.umask()
912
924
  }
913
925