@vzn/vx 0.0.141 → 0.0.142
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/cache/archive.ts +31 -6
- package/src/cache/cache.ts +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vzn/vx",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.142",
|
|
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.142",
|
|
21
|
+
"@vzn/vx-linux-arm64": "0.0.142",
|
|
22
|
+
"@vzn/vx-darwin-x64": "0.0.142",
|
|
23
|
+
"@vzn/vx-darwin-arm64": "0.0.142"
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
26
|
"@anthropic-ai/sandbox-runtime": "^0.0.76"
|
package/src/cache/archive.ts
CHANGED
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
// otherwise silently loses the claim rather than acting on it.
|
|
39
39
|
|
|
40
40
|
import { chmodSync, renameSync, statSync, utimesSync } from 'node:fs'
|
|
41
|
-
import { mkdir, readlink, realpath, rmdir, stat, unlink, writeFile } from 'node:fs/promises'
|
|
41
|
+
import { lstat, mkdir, readlink, realpath, rmdir, stat, unlink, writeFile } from 'node:fs/promises'
|
|
42
42
|
import path from 'node:path'
|
|
43
43
|
import { UserError } from '../util/index.js'
|
|
44
44
|
import { TarFormatError, type TarInput, tarEntries, tarPack, tarSize } from './tar-stream.js'
|
|
@@ -193,6 +193,13 @@ export interface PackArgs {
|
|
|
193
193
|
outputs: ReadonlyMap<string, string>
|
|
194
194
|
/** The producing execution's usage, when the runner reported it. */
|
|
195
195
|
exec?: ExecUsage | undefined
|
|
196
|
+
/**
|
|
197
|
+
* The project directory a symlinked output must resolve inside. vx reads
|
|
198
|
+
* outputs outside the task's sandbox, so a link a sandboxed task planted
|
|
199
|
+
* to a file it could not read (another project's, the user's home) packed
|
|
200
|
+
* that file into the artifact and the remote (L-23).
|
|
201
|
+
*/
|
|
202
|
+
within?: string
|
|
196
203
|
}
|
|
197
204
|
|
|
198
205
|
/** A packed artifact's plan: its entries with their stats, and the tar's exact size. */
|
|
@@ -206,11 +213,12 @@ export interface ArtifactPlan {
|
|
|
206
213
|
* mtime) and lay out the tar — stdout first, the outputs, the sidecar
|
|
207
214
|
* last — with its exact size, so the caller can choose the one-call pack
|
|
208
215
|
* for a small artifact and the streamed one for a large one before a
|
|
209
|
-
* byte is read. A symlinked output is stored as its target's content,
|
|
210
|
-
*
|
|
216
|
+
* byte is read. A symlinked output is stored as its target's content,
|
|
217
|
+
* when that target is inside `within`.
|
|
211
218
|
*/
|
|
212
219
|
export async function planArtifact(args: PackArgs): Promise<ArtifactPlan> {
|
|
213
220
|
const meta: MetaFile = { version: 1, files: {} }
|
|
221
|
+
let withinReal: Promise<string> | undefined
|
|
214
222
|
if (args.key !== undefined) meta.key = args.key
|
|
215
223
|
const exec = usageOf(args.exec)
|
|
216
224
|
if (exec !== undefined) meta.exec = exec
|
|
@@ -221,14 +229,31 @@ export async function planArtifact(args: PackArgs): Promise<ArtifactPlan> {
|
|
|
221
229
|
// directory, or a dangling one, has no bytes to pack: refuse loudly
|
|
222
230
|
// rather than store an entry that restores to nothing.
|
|
223
231
|
const shown = name.startsWith('outputs/') ? name.slice('outputs/'.length) : name
|
|
224
|
-
const
|
|
232
|
+
const dangling = (err: NodeJS.ErrnoException): never => {
|
|
225
233
|
if (err.code === 'ENOENT') {
|
|
226
234
|
throw new UserError(
|
|
227
235
|
`output ${shown} is a dangling symlink: vx stores regular files only — emit a file there, or narrow cache.outputs.files to the files the task produces, or take it back with a '!' entry`,
|
|
228
236
|
)
|
|
229
237
|
}
|
|
230
238
|
throw err
|
|
231
|
-
}
|
|
239
|
+
}
|
|
240
|
+
// lstat, so a regular file costs no second call; only a link is
|
|
241
|
+
// resolved, and its body is read from the path this check resolved.
|
|
242
|
+
let src = abs
|
|
243
|
+
let st = await lstat(abs)
|
|
244
|
+
if (st.isSymbolicLink()) {
|
|
245
|
+
src = await realpath(abs).catch(dangling)
|
|
246
|
+
if (args.within !== undefined) {
|
|
247
|
+
withinReal ??= realpath(args.within)
|
|
248
|
+
const root = await withinReal
|
|
249
|
+
if (!src.startsWith(root + path.sep)) {
|
|
250
|
+
throw new UserError(
|
|
251
|
+
`output ${shown} is a symlink to ${src}, outside the project: vx packs a symlinked output as its target's bytes, and a project's outputs come from its own directory — emit a file there, or take it back with a '!' entry`,
|
|
252
|
+
)
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
st = await stat(src).catch(dangling)
|
|
256
|
+
}
|
|
232
257
|
if (!st.isFile()) {
|
|
233
258
|
throw new UserError(
|
|
234
259
|
`output ${shown} is not a regular file (a symlink to a directory?): vx stores regular files only — emit a file there, or narrow cache.outputs.files to the files the task produces, or take it back with a '!' entry`,
|
|
@@ -240,7 +265,7 @@ export async function planArtifact(args: PackArgs): Promise<ArtifactPlan> {
|
|
|
240
265
|
meta.files[name] = [mode, Math.floor(st.mtimeMs)]
|
|
241
266
|
return {
|
|
242
267
|
name,
|
|
243
|
-
abs,
|
|
268
|
+
abs: src,
|
|
244
269
|
size: st.size,
|
|
245
270
|
mode,
|
|
246
271
|
// ustar's octal field holds no sign: an mtime before 1970 made the
|
package/src/cache/cache.ts
CHANGED
|
@@ -1304,6 +1304,7 @@ export class Cache implements CacheLayer {
|
|
|
1304
1304
|
stdout: args.entry.stdout ?? '',
|
|
1305
1305
|
outputs: this.outputsOf(args),
|
|
1306
1306
|
exec: usageOfEntry(args.entry),
|
|
1307
|
+
within: args.projectDir,
|
|
1307
1308
|
})
|
|
1308
1309
|
if (plan.size > this.artifactCeiling) {
|
|
1309
1310
|
throw new Error(
|