@vzn/vx 0.0.397 → 0.0.399
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.399",
|
|
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.
|
|
25
|
-
"@vzn/vx-linux-arm64": "0.0.
|
|
26
|
-
"@vzn/vx-darwin-x64": "0.0.
|
|
27
|
-
"@vzn/vx-darwin-arm64": "0.0.
|
|
24
|
+
"@vzn/vx-linux-x64": "0.0.399",
|
|
25
|
+
"@vzn/vx-linux-arm64": "0.0.399",
|
|
26
|
+
"@vzn/vx-darwin-x64": "0.0.399",
|
|
27
|
+
"@vzn/vx-darwin-arm64": "0.0.399"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
30
|
"@anthropic-ai/sandbox-runtime": "^0.0.76"
|
|
@@ -1141,8 +1141,21 @@ export async function wrapSandboxedCommand(
|
|
|
1141
1141
|
// sandboxed server and all it forked outlived vx (turborepo#9666). Now
|
|
1142
1142
|
// the namespace goes with vx, a `setsid` daemon inside included, a
|
|
1143
1143
|
// traced one-shot task too: its strace runs inside (B-11).
|
|
1144
|
-
|
|
1145
|
-
|
|
1144
|
+
// Every mask and bind is a mount point, and git's discovery stops at
|
|
1145
|
+
// one: a task granted the repository's `.git` still read "not a git
|
|
1146
|
+
// repository … Stopping at filesystem boundary" (2026-10-03). The
|
|
1147
|
+
// boundaries are the sandbox's, so a task that names a `.git` may walk
|
|
1148
|
+
// across them; only such a task, since git-aware tools read the variable
|
|
1149
|
+
// (vx's own repoFacts asks git instead of the disk under it). A value the
|
|
1150
|
+
// task's environment sets wins.
|
|
1151
|
+
if (process.platform === 'linux' && /^\S*bwrap /.test(wrapped)) {
|
|
1152
|
+
const gitGranted = [...args.config.allowRead, ...args.config.allowWrite].some((g) =>
|
|
1153
|
+
g.split(/[\\/]/).includes('.git'),
|
|
1154
|
+
)
|
|
1155
|
+
wrapped =
|
|
1156
|
+
(gitGranted ? 'GIT_DISCOVERY_ACROSS_FILESYSTEM=${GIT_DISCOVERY_ACROSS_FILESYSTEM-1} ' : '') +
|
|
1157
|
+
`exec ${readOnlyMasks(wrapped, scratch)}`
|
|
1158
|
+
}
|
|
1146
1159
|
if (process.platform === 'linux' && !hostHasIpv6())
|
|
1147
1160
|
wrapped = `SOCAT_DEFAULT_LISTEN_IP=4 ${wrapped}`
|
|
1148
1161
|
const held = portsHeld(ports)
|
|
@@ -127,6 +127,9 @@ self.onmessage = async (e) => {
|
|
|
127
127
|
const before = watched?.map(([, o]) => own(o))
|
|
128
128
|
const envBefore = watched ? { ...live } : null
|
|
129
129
|
const cwdBefore = watched ? globalThis.process.cwd() : null
|
|
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()
|
|
130
133
|
const same = (a, b) =>
|
|
131
134
|
a !== undefined && b !== undefined && Object.is(a.value, b.value) && a.get === b.get && a.set === b.set &&
|
|
132
135
|
a.writable === b.writable && a.enumerable === b.enumerable && a.configurable === b.configurable
|
|
@@ -141,8 +144,14 @@ self.onmessage = async (e) => {
|
|
|
141
144
|
for (const k of new Set([...Object.keys(envBefore), ...Object.keys(live)]))
|
|
142
145
|
if (envBefore[k] !== live[k]) out.push('process.env.' + k)
|
|
143
146
|
if (globalThis.process.cwd() !== cwdBefore) out.push('process.cwd (a chdir)')
|
|
147
|
+
if (globalThis.process.umask() !== umaskIn) out.push('process.umask')
|
|
144
148
|
return out
|
|
145
149
|
}
|
|
150
|
+
const umaskBack = () => {
|
|
151
|
+
if (globalThis.process.umask() === umaskIn) return false
|
|
152
|
+
globalThis.process.umask(umaskIn)
|
|
153
|
+
return true
|
|
154
|
+
}
|
|
146
155
|
try {
|
|
147
156
|
const ns = await import(path)
|
|
148
157
|
// Awaited as the in-process load's async return flattens it: a Promise
|
|
@@ -158,6 +167,7 @@ self.onmessage = async (e) => {
|
|
|
158
167
|
json: isObject && nonJson.length === 0 ? JSON.stringify(mod) : null,
|
|
159
168
|
fn: typeof mod === 'function',
|
|
160
169
|
changed: changed(),
|
|
170
|
+
umask: umaskBack(),
|
|
161
171
|
})
|
|
162
172
|
} catch (err) {
|
|
163
173
|
postMessage({
|
|
@@ -167,6 +177,7 @@ self.onmessage = async (e) => {
|
|
|
167
177
|
message: err?.message ?? String(err),
|
|
168
178
|
stack: err?.stack ?? null,
|
|
169
179
|
changed: changed(),
|
|
180
|
+
umask: umaskBack(),
|
|
170
181
|
position:
|
|
171
182
|
err?.position && typeof err.position === 'object'
|
|
172
183
|
? { file: err.position.file, line: err.position.line, column: err.position.column }
|
|
@@ -178,6 +189,9 @@ self.onmessage = async (e) => {
|
|
|
178
189
|
|
|
179
190
|
const FUNCTION_EXPORT = (): void => {}
|
|
180
191
|
|
|
192
|
+
/** A repeat load moved the process's umask; the loader names the config (D-125). */
|
|
193
|
+
export class UmaskChanged extends Error {}
|
|
194
|
+
|
|
181
195
|
const WORKER_URL = `data:text/javascript,${encodeURIComponent(WORKER_SRC)}`
|
|
182
196
|
|
|
183
197
|
interface WorkerReply {
|
|
@@ -186,6 +200,8 @@ interface WorkerReply {
|
|
|
186
200
|
json: string | null
|
|
187
201
|
/** The default export is a function, which JSON cannot carry back. */
|
|
188
202
|
fn?: boolean
|
|
203
|
+
/** The evaluation moved the process's umask; the worker put it back. */
|
|
204
|
+
umask?: boolean
|
|
189
205
|
nonJson: NonJsonValue[]
|
|
190
206
|
name: string
|
|
191
207
|
message: string
|
|
@@ -347,6 +363,7 @@ export async function evaluateConfigFresh(configPath: string): Promise<unknown>
|
|
|
347
363
|
timer.unref?.()
|
|
348
364
|
w.postMessage({ id, path: abs, env: { ...process.env } })
|
|
349
365
|
})
|
|
366
|
+
if (reply.umask === true) throw new UmaskChanged()
|
|
350
367
|
const [nonJson] = reply.nonJson
|
|
351
368
|
if (nonJson !== undefined) throw new UserError(nonJsonMessage(configPath, nonJson))
|
|
352
369
|
// A function stands in for the one the config exported, so the caller's
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
CONFIG_EXIT,
|
|
10
10
|
evalBudgetMs,
|
|
11
11
|
evaluateConfigFresh,
|
|
12
|
+
UmaskChanged,
|
|
12
13
|
WATCHED_BUILTIN_NAMES,
|
|
13
14
|
} from './config-eval.js'
|
|
14
15
|
import { hasEsmExport, unprovidedBareImports } from './config-imports.js'
|
|
@@ -451,6 +452,7 @@ export async function loadProjectConfigs(
|
|
|
451
452
|
let builtins: BuiltinSnapshot | undefined
|
|
452
453
|
let env: Readonly<Record<string, string | undefined>> = {}
|
|
453
454
|
let cwd = ''
|
|
455
|
+
let umask = -1
|
|
454
456
|
// More than one evaluation in flight: a change seen after one load may
|
|
455
457
|
// be another's.
|
|
456
458
|
let overlapping = false
|
|
@@ -489,6 +491,7 @@ export async function loadProjectConfigs(
|
|
|
489
491
|
if (repeat) refuseUnprovidedImports(bytes!, configPath, 'Project')
|
|
490
492
|
const mod = repeat
|
|
491
493
|
? await evaluateConfigFresh(configPath).catch((err: unknown) => {
|
|
494
|
+
if (err instanceof UmaskChanged) throw builtinsChanged(['process.umask'], configPath)
|
|
492
495
|
throw configLoadError(err, configPath, 'Project') ?? err
|
|
493
496
|
})
|
|
494
497
|
: await loadDefaultExport(configPath, 'Project', bytes!)
|
|
@@ -496,7 +499,12 @@ export async function loadProjectConfigs(
|
|
|
496
499
|
// turned the JSON-data walk's own check into "a cyclic reference".
|
|
497
500
|
const changed = repeat
|
|
498
501
|
? []
|
|
499
|
-
: [
|
|
502
|
+
: [
|
|
503
|
+
...restoreBuiltins(builtins),
|
|
504
|
+
...restoreEnv(env),
|
|
505
|
+
...restoreCwd(cwd),
|
|
506
|
+
...restoreUmask(umask),
|
|
507
|
+
]
|
|
500
508
|
// Loads overlap, so another config's change can surface after this
|
|
501
509
|
// one: named here, the refusal blamed the wrong file (D-119). The round
|
|
502
510
|
// finds the one that made it.
|
|
@@ -549,6 +557,7 @@ export async function loadProjectConfigs(
|
|
|
549
557
|
builtins = builtinSnapshot()
|
|
550
558
|
env = { ...process.env }
|
|
551
559
|
cwd = process.cwd()
|
|
560
|
+
umask = currentUmask()
|
|
552
561
|
}
|
|
553
562
|
overlapping = misses.length > 1
|
|
554
563
|
let next = 0
|
|
@@ -569,7 +578,12 @@ export async function loadProjectConfigs(
|
|
|
569
578
|
}
|
|
570
579
|
const changed =
|
|
571
580
|
misses.length > 0
|
|
572
|
-
? [
|
|
581
|
+
? [
|
|
582
|
+
...restoreBuiltins(builtins),
|
|
583
|
+
...restoreEnv(env),
|
|
584
|
+
...restoreCwd(cwd),
|
|
585
|
+
...restoreUmask(umask),
|
|
586
|
+
]
|
|
573
587
|
: []
|
|
574
588
|
if (first?.failed instanceof ChangedInRound) {
|
|
575
589
|
for (const i of misses) {
|
|
@@ -702,6 +716,9 @@ function builtinsChanged(changed: readonly string[], configPath?: string): UserE
|
|
|
702
716
|
(changed.some((c) => c.startsWith('process.cwd'))
|
|
703
717
|
? '; a task runs in its project directory, and `cd <dir> && …` in `exec.command` moves it'
|
|
704
718
|
: '') +
|
|
719
|
+
(changed.some((c) => c.startsWith('process.umask'))
|
|
720
|
+
? '; a task sets its own with `umask <mode> && …` in `exec.command`'
|
|
721
|
+
: '') +
|
|
705
722
|
(changed.some((c) => c.startsWith('globalThis.'))
|
|
706
723
|
? '; a constant configs share goes in a module each one imports'
|
|
707
724
|
: '')
|
|
@@ -785,6 +802,26 @@ function restoreCwd(before: string): string[] {
|
|
|
785
802
|
return ['process.cwd (a chdir)']
|
|
786
803
|
}
|
|
787
804
|
|
|
805
|
+
/**
|
|
806
|
+
* A config's `process.umask()` set the mode of every file vx and its tasks
|
|
807
|
+
* wrote after it: a cache artifact and a task's outputs landed `000`, which
|
|
808
|
+
* a user other than root could not read back (D-125). Put back, and named.
|
|
809
|
+
*/
|
|
810
|
+
function currentUmask(): number {
|
|
811
|
+
// Through globalThis, as restoreCwd: the playground's shim has no umask.
|
|
812
|
+
const proc = globalThis.process as { umask?: (mask?: number) => number }
|
|
813
|
+
if (typeof proc.umask !== 'function') return -1
|
|
814
|
+
// No argument reads it: setting 0 and back would open a window where a
|
|
815
|
+
// file written by an overlapping write landed world-writable.
|
|
816
|
+
return proc.umask()
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
function restoreUmask(before: number): string[] {
|
|
820
|
+
if (before === -1 || currentUmask() === before) return []
|
|
821
|
+
;(globalThis.process as { umask: (mask: number) => number }).umask(before)
|
|
822
|
+
return ['process.umask']
|
|
823
|
+
}
|
|
824
|
+
|
|
788
825
|
function sameDescriptor(p: Primitives, a: PropertyDescriptor, b: PropertyDescriptor): boolean {
|
|
789
826
|
return (
|
|
790
827
|
// `globalThis.NaN` is watched (D-122), and NaN !== NaN.
|