@vzn/vx 0.0.644 → 0.0.645
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/LICENSE +1 -1
- package/README.md +15 -23
- package/package.json +5 -5
- package/schemas/init.json +5 -0
- package/src/cache/archive.ts +12 -76
- package/src/cache/cache.ts +6 -26
- package/src/cache/index.ts +1 -2
- package/src/cache/layer.ts +5 -6
- package/src/cache/layered-cache.ts +106 -42
- package/src/cache/plugin-cache.ts +9 -14
- package/src/cache/remote-lane.ts +47 -18
- package/src/cache/scoped-cache.ts +3 -80
- package/src/cli/help.ts +4 -4
- package/src/cli/init.ts +121 -20
- package/src/cli/plan-format.ts +6 -2
- package/src/cli/plugin-commands.ts +3 -4
- package/src/cli/plugin-templates.ts +1 -1
- package/src/cli/run.ts +0 -1
- package/src/cli/upgrade.ts +1 -1
- package/src/config.ts +2 -4
- package/src/index.ts +1 -1
- package/src/orchestrator/affected-tasks.ts +0 -18
- package/src/orchestrator/index.ts +1 -0
- package/src/orchestrator/options.ts +0 -8
- package/src/orchestrator/plan.ts +7 -1
- package/src/orchestrator/plugin-host.ts +2 -5
- package/src/orchestrator/plugin.ts +11 -7
- package/src/orchestrator/prepare.ts +4 -47
- package/src/orchestrator/run-context.ts +39 -1
- package/src/orchestrator/run.ts +2 -0
- package/src/orchestrator/task-hash.ts +1 -3
- package/src/report/summary.ts +14 -2
- package/src/settings.ts +1 -7
- package/src/workspace/affected.ts +0 -39
- package/src/workspace/index.ts +0 -1
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
<p align="center">
|
|
14
14
|
<a href="https://www.npmjs.com/package/@vzn/vx"><img src="https://img.shields.io/npm/v/@vzn/vx?color=7cb518" alt="npm"></a>
|
|
15
|
-
<a href="https://github.com/vznjs/vx/actions/workflows/ci.yml"><img src="https://github.com/vznjs/vx/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
|
|
15
|
+
<a href="https://github.com/vznjs/vx/actions/workflows/ci.yml?query=branch%3Amain+event%3Apush"><img src="https://github.com/vznjs/vx/actions/workflows/ci.yml/badge.svg?branch=main&event=push" alt="CI"></a>
|
|
16
16
|
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue" alt="MIT license"></a>
|
|
17
17
|
<a href="https://vznjs.github.io/vx/"><img src="https://img.shields.io/badge/docs-vznjs.github.io%2Fvx-c6f84e" alt="Documentation"></a>
|
|
18
18
|
</p>
|
|
@@ -35,13 +35,12 @@ Windows: run vx inside WSL2.
|
|
|
35
35
|
|
|
36
36
|
## Quick start
|
|
37
37
|
|
|
38
|
-
In a git repo
|
|
38
|
+
In a git repo:
|
|
39
39
|
|
|
40
40
|
```sh
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
npx @vzn/vx run build --all # nothing changed: every task up-to-date
|
|
41
|
+
npx @vzn/vx init # installs vx globally and in the repo; beside turbo.json or nx.json: migrates them; else a root vx.config.ts
|
|
42
|
+
vx run build --all # builds in dependency order and stores the results
|
|
43
|
+
vx run build --all # nothing changed: every task up-to-date
|
|
45
44
|
```
|
|
46
45
|
|
|
47
46
|
Bun workspaces: `bun add -d @vzn/vx` and `bunx @vzn/vx` (npm refuses to run in
|
|
@@ -166,17 +165,17 @@ Every gap, and when another tool is the better pick:
|
|
|
166
165
|
|
|
167
166
|
<!-- bench:start — generated by packages/vx-bench/update-site.ts from results.json; do not hand-edit -->
|
|
168
167
|
|
|
169
|
-
| Time added over the ideal run, 1,601 projects, 9,603 tasks | vx
|
|
170
|
-
| ---------------------------------------------------------- |
|
|
171
|
-
| Nothing changed | **
|
|
172
|
-
| Nothing changed, outputs restored | **1.
|
|
173
|
-
| Cold build | **
|
|
174
|
-
| Cold build: CPU the runner burns |
|
|
168
|
+
| Time added over the ideal run, 1,601 projects, 9,603 tasks | vx | Turborepo | Nx | Vite Task |
|
|
169
|
+
| ---------------------------------------------------------- | ---------- | --------- | ---------- | --------- |
|
|
170
|
+
| Nothing changed | **757 ms** | 761 ms | 22.63 s | 12.26 s |
|
|
171
|
+
| Nothing changed, outputs restored | **1.27 s** | 1.76 s | 22.16 s | 11.66 s |
|
|
172
|
+
| Cold build | **6.13 s** | 8.87 s | 3 min 20 s | 7.94 s |
|
|
173
|
+
| Cold build: CPU the runner burns | 30.06 s | 1 min 8 s | 5 min 45 s | 23.91 s |
|
|
175
174
|
|
|
176
175
|
Time each tool adds over the ideal run; lower is better.
|
|
177
176
|
|
|
178
177
|
Benchmark workload: a synthetic monorepo of 1,601 projects and 9,603 tasks in 30 dependency levels, five core libraries a quarter of the projects use; build 300 ms, test and typecheck 150 ms, lint 75 ms, publish 30 ms; real repos with uneven task times will differ.
|
|
179
|
-
Run 2026-10-
|
|
178
|
+
Run 2026-10-20 on linux x64, 4 cores: vx at commit a88652b0b, Turborepo 2.11.9, Nx 23.3.0, Vite Task (vite-plus) 1.3.0.
|
|
180
179
|
|
|
181
180
|
Same graph, commands and concurrency: [how it is measured](https://vznjs.github.io/vx/benchmarks/).
|
|
182
181
|
|
|
@@ -188,17 +187,10 @@ Same graph, commands and concurrency: [how it is measured](https://vznjs.github.
|
|
|
188
187
|
vx. The schema may still change; 1.0's promise:
|
|
189
188
|
[versioning-1.0.md](packages/vx/docs/design/versioning-1.0.md).
|
|
190
189
|
|
|
191
|
-
##
|
|
190
|
+
## Issues
|
|
192
191
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
bun install
|
|
196
|
-
bun packages/vx/src/bin.ts run ci --all # lint, test, docs build: every package
|
|
197
|
-
```
|
|
198
|
-
|
|
199
|
-
Needs Bun ≥ 1.4. Every dev task is a vx task; no `package.json`
|
|
200
|
-
scripts. Start with [CONTRIBUTING.md](CONTRIBUTING.md) and
|
|
201
|
-
[STATUS.md](packages/vx/docs/STATUS.md).
|
|
192
|
+
vx is maintained by VZN and takes issues, not pull requests: report a
|
|
193
|
+
bug or ask for a feature in [issues](https://github.com/vznjs/vx/issues).
|
|
202
194
|
|
|
203
195
|
## License
|
|
204
196
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vzn/vx",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.645",
|
|
4
4
|
"description": "Task orchestration you own: correct, sandboxed, extensible, with a content-addressed build cache.",
|
|
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.645",
|
|
25
|
+
"@vzn/vx-linux-arm64": "0.0.645",
|
|
26
|
+
"@vzn/vx-darwin-x64": "0.0.645",
|
|
27
|
+
"@vzn/vx-darwin-arm64": "0.0.645"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
30
|
"@anthropic-ai/sandbox-runtime": "^0.0.76"
|
package/schemas/init.json
CHANGED
|
@@ -52,6 +52,11 @@
|
|
|
52
52
|
"next": {
|
|
53
53
|
"type": ["string", "null"],
|
|
54
54
|
"description": "the command to type after the real run; null where the plan names none"
|
|
55
|
+
},
|
|
56
|
+
"installs": {
|
|
57
|
+
"type": "array",
|
|
58
|
+
"items": { "type": "string" },
|
|
59
|
+
"description": "core's plan only: the install commands the real run runs (global, then the root devDependency)"
|
|
55
60
|
}
|
|
56
61
|
}
|
|
57
62
|
}
|
package/src/cache/archive.ts
CHANGED
|
@@ -163,8 +163,6 @@ class SumCheck {
|
|
|
163
163
|
add(bytes: Uint8Array): void {
|
|
164
164
|
this.sum.add(bytes)
|
|
165
165
|
}
|
|
166
|
-
/** The checked sum's text, once `verify` passed. */
|
|
167
|
-
checked = ''
|
|
168
166
|
verify(got: string): void {
|
|
169
167
|
if (got !== this.sum.hex()) {
|
|
170
168
|
throw new TarFormatError(
|
|
@@ -172,7 +170,6 @@ class SumCheck {
|
|
|
172
170
|
)
|
|
173
171
|
}
|
|
174
172
|
this.seen = true
|
|
175
|
-
this.checked = got
|
|
176
173
|
}
|
|
177
174
|
done(): void {
|
|
178
175
|
if (!this.seen) throw new TarFormatError('artifact carries no checksum')
|
|
@@ -478,14 +475,11 @@ function streamOf(gen: AsyncGenerator<Uint8Array>): ReadableStream<Uint8Array> {
|
|
|
478
475
|
/**
|
|
479
476
|
* The tree form (`cache.store.decompress`, v47): an artifact's members
|
|
480
477
|
* as plain files under one directory, `outputs/<rel>` and
|
|
481
|
-
* `workspace-outputs/<rel>` as named in the tar, beside its sidecar
|
|
482
|
-
*
|
|
483
|
-
*
|
|
484
|
-
*
|
|
485
|
-
|
|
486
|
-
const TREE_SUM = SUM_ENTRY
|
|
487
|
-
|
|
488
|
-
/**
|
|
478
|
+
* `workspace-outputs/<rel>` as named in the tar, beside its sidecar.
|
|
479
|
+
* stdout lives in the index. A restore copies (or clones) each file out,
|
|
480
|
+
* so a hit decodes nothing; an upload packs the tree back into the
|
|
481
|
+
* artifact a remote expects.
|
|
482
|
+
*
|
|
489
483
|
* Write `plan`'s outputs into the empty `dir` as the tree form, each copied
|
|
490
484
|
* by the kernel (`copy_file_range`, or a clone where the filesystem has one)
|
|
491
485
|
* and given its sidecar mtime, and return what a scan of the plan's tar
|
|
@@ -515,24 +509,14 @@ export async function writeTree(plan: ArtifactPlan, dir: string): Promise<Artifa
|
|
|
515
509
|
}),
|
|
516
510
|
)
|
|
517
511
|
await writeFile(path.join(dir, META_ENTRY), meta)
|
|
518
|
-
await writeFile(path.join(dir, TREE_SUM), await treeSum(dir, stdoutOf(plan)))
|
|
519
512
|
return fold.result()
|
|
520
513
|
}
|
|
521
514
|
|
|
522
|
-
function stdoutOf(plan: ArtifactPlan): string {
|
|
523
|
-
return utf8.decode(plan.inputs.find((i) => i.name === STDOUT_ENTRY)!.body as Uint8Array)
|
|
524
|
-
}
|
|
525
|
-
|
|
526
515
|
/**
|
|
527
|
-
* Write a scanned artifact's sidecar
|
|
528
|
-
*
|
|
516
|
+
* Write a scanned artifact's sidecar into `dir`, whose outputs an extract
|
|
517
|
+
* just wrote: an ingested remote hit kept as a tree.
|
|
529
518
|
*/
|
|
530
|
-
export async function sealTree(
|
|
531
|
-
dir: string,
|
|
532
|
-
scan: ArtifactScan,
|
|
533
|
-
/** The archive's own sidecar text and checked sum (`extractArtifactStream`'s `onScan`). */
|
|
534
|
-
raw?: { meta: string; sum: string },
|
|
535
|
-
): Promise<void> {
|
|
519
|
+
export async function sealTree(dir: string, scan: ArtifactScan): Promise<void> {
|
|
536
520
|
// An artifact with no outputs extracts nothing, so nothing made `dir`.
|
|
537
521
|
await mkdir(dir, { recursive: true })
|
|
538
522
|
const meta: MetaFile = { version: 1, files: {} }
|
|
@@ -543,20 +527,7 @@ export async function sealTree(
|
|
|
543
527
|
if (scan.dirs.length > 0) meta.dirs = scan.dirs
|
|
544
528
|
if (scan.links.length > 0) meta.links = Object.fromEntries(scan.links)
|
|
545
529
|
if (scan.exec !== undefined) meta.exec = scan.exec
|
|
546
|
-
|
|
547
|
-
await writeFile(path.join(dir, META_ENTRY), text)
|
|
548
|
-
// The tree packs back to the archive it came from when the sidecar is
|
|
549
|
-
// the same bytes and the entries came in its order (stdout first, the
|
|
550
|
-
// sidecar last): the sum the extract checked is then the tree's, and the
|
|
551
|
-
// tree is not read back to sum it.
|
|
552
|
-
const names = Object.keys(meta.files)
|
|
553
|
-
const same =
|
|
554
|
-
raw !== undefined &&
|
|
555
|
-
raw.meta === text &&
|
|
556
|
-
scan.entries[0]?.name === STDOUT_ENTRY &&
|
|
557
|
-
scan.entries.length === names.length + 1 &&
|
|
558
|
-
names.every((n, i) => scan.entries[i + 1]!.name === n)
|
|
559
|
-
await writeFile(path.join(dir, TREE_SUM), same ? raw.sum : await treeSum(dir, scan.stdout ?? ''))
|
|
530
|
+
await writeFile(path.join(dir, META_ENTRY), JSON.stringify(meta))
|
|
560
531
|
}
|
|
561
532
|
|
|
562
533
|
/** The plan that packs the tree in `dir` back into the artifact it holds. */
|
|
@@ -579,28 +550,6 @@ export function planOfTree(dir: string, stdout: string): ArtifactPlan {
|
|
|
579
550
|
return { inputs, size: tarSize([...inputs, sumInput('0'.repeat(SUM_SIZE))]) }
|
|
580
551
|
}
|
|
581
552
|
|
|
582
|
-
/** The sum over the tree's members, as `SUM_ENTRY` defines it for the tar it packs to. */
|
|
583
|
-
async function treeSum(dir: string, stdout: string): Promise<string> {
|
|
584
|
-
const sum = new EntrySum()
|
|
585
|
-
for (const i of planOfTree(dir, stdout).inputs) {
|
|
586
|
-
sum.entry(i.name)
|
|
587
|
-
if (i.body instanceof Blob && i.size > 1 << 20)
|
|
588
|
-
for await (const c of i.body.stream()) sum.add(c)
|
|
589
|
-
else if (i.body instanceof Blob) sum.add(await i.body.bytes())
|
|
590
|
-
else sum.add(i.body as Uint8Array)
|
|
591
|
-
}
|
|
592
|
-
return sum.hex()
|
|
593
|
-
}
|
|
594
|
-
|
|
595
|
-
/** Whether the tree's bytes still sum to what was stored (`cache.store.verify: 'hash'`). */
|
|
596
|
-
export async function treeIntact(dir: string, stdout: string): Promise<boolean> {
|
|
597
|
-
try {
|
|
598
|
-
return (await treeSum(dir, stdout)) === readFileSync(path.join(dir, TREE_SUM), 'utf8')
|
|
599
|
-
} catch {
|
|
600
|
-
return false
|
|
601
|
-
}
|
|
602
|
-
}
|
|
603
|
-
|
|
604
553
|
/** What a scan of an artifact reads out of it. */
|
|
605
554
|
interface ArtifactScan {
|
|
606
555
|
entries: ArchiveEntry[]
|
|
@@ -794,12 +743,8 @@ export async function extractArtifactStream(
|
|
|
794
743
|
accept?: (name: string) => boolean,
|
|
795
744
|
/** The workspace root whose `.vx/tmp/` stages the entries; absent, each stages beside its target. */
|
|
796
745
|
stageRoot?: string,
|
|
797
|
-
/**
|
|
798
|
-
|
|
799
|
-
* has checked, with the sidecar's text and the sum when the sidecar came
|
|
800
|
-
* last: what `sealTree` may reuse rather than read the tree back.
|
|
801
|
-
*/
|
|
802
|
-
onScan?: (scan: ArtifactScan, raw: { meta: string; sum: string } | undefined) => void,
|
|
746
|
+
/** Handed what `scanArtifact` would read of the same bytes, once their sum has checked. */
|
|
747
|
+
onScan?: (scan: ArtifactScan) => void,
|
|
803
748
|
): Promise<Set<string>> {
|
|
804
749
|
// No `mkdir -p` of the destination here: `stage` creates each entry's
|
|
805
750
|
// directory chain, the destination included, and the cache restores
|
|
@@ -813,20 +758,15 @@ export async function extractArtifactStream(
|
|
|
813
758
|
let dirs: string[] = []
|
|
814
759
|
let links: Array<[string, string]> = []
|
|
815
760
|
const fold = onScan === undefined ? undefined : new ScanFold()
|
|
816
|
-
let metaText: string | undefined
|
|
817
|
-
let metaLast = false
|
|
818
761
|
try {
|
|
819
762
|
const check = new SumCheck()
|
|
820
763
|
for await (const e of tarEntries(tar)) {
|
|
821
764
|
if (e.type !== '0') continue
|
|
822
765
|
const body = await check.body(e.name, e.body)
|
|
823
766
|
if (body === null) continue
|
|
824
|
-
metaLast = false
|
|
825
767
|
if (e.name === META_ENTRY) {
|
|
826
768
|
const text = await textOf(body)
|
|
827
769
|
fold?.sidecar(text)
|
|
828
|
-
metaText = text
|
|
829
|
-
metaLast = true
|
|
830
770
|
const parsed = JSON.parse(text) as MetaFile
|
|
831
771
|
meta = parsed.files ?? {}
|
|
832
772
|
dirs = dirsOf(parsed)
|
|
@@ -855,11 +795,7 @@ export async function extractArtifactStream(
|
|
|
855
795
|
await x.stage(e.name, dest.base, target, e.size <= SMALL_ENTRY ? await bytesOf(body) : body)
|
|
856
796
|
}
|
|
857
797
|
check.done()
|
|
858
|
-
if (fold !== undefined)
|
|
859
|
-
onScan!(
|
|
860
|
-
fold.result(),
|
|
861
|
-
metaLast && metaText !== undefined ? { meta: metaText, sum: check.checked } : undefined,
|
|
862
|
-
)
|
|
798
|
+
if (fold !== undefined) onScan!(fold.result())
|
|
863
799
|
for (const name of dirs) {
|
|
864
800
|
if (accept !== undefined && !accept(name)) {
|
|
865
801
|
throw new ArchiveSecurityError(
|
package/src/cache/cache.ts
CHANGED
|
@@ -78,7 +78,6 @@ import {
|
|
|
78
78
|
planArtifact,
|
|
79
79
|
planOfTree,
|
|
80
80
|
sealTree,
|
|
81
|
-
treeIntact,
|
|
82
81
|
writeTree,
|
|
83
82
|
} from './archive.js'
|
|
84
83
|
import { readUmask, restoredMode } from './umask.js'
|
|
@@ -814,14 +813,11 @@ export interface StoreOptions {
|
|
|
814
813
|
embeddings: number
|
|
815
814
|
/** A file-backed entry is a `<hash>.tree` of plain files; off, one `<hash>.tar.zst`. */
|
|
816
815
|
decompress: boolean
|
|
817
|
-
/** What a tree restore checks first: each file's size and mtime, every byte, or nothing. */
|
|
818
|
-
verify: 'stat' | 'hash' | 'none'
|
|
819
816
|
}
|
|
820
817
|
|
|
821
818
|
export const DEFAULT_STORE: StoreOptions = {
|
|
822
819
|
embeddings: 32 * 1024,
|
|
823
820
|
decompress: true,
|
|
824
|
-
verify: 'stat',
|
|
825
821
|
}
|
|
826
822
|
|
|
827
823
|
/** What a run's background uploads moved: totals over the ones that landed. */
|
|
@@ -859,6 +855,8 @@ export class Cache implements CacheLayer {
|
|
|
859
855
|
* so the run can report them without a method on the layer seam.
|
|
860
856
|
*/
|
|
861
857
|
readonly uploads: UploadTally = { count: 0, bytes: 0, ms: 0, failed: 0 }
|
|
858
|
+
/** The run's hits pulled from those remotes, and their bytes on the wire. */
|
|
859
|
+
readonly downloads: { count: number; bytes: number } = { count: 0, bytes: 0 }
|
|
862
860
|
/** The same remotes' traffic per key, filled by the same `LayeredCache`. */
|
|
863
861
|
readonly traffic: RemoteTraffic = { inFlight: 0, keys: new Map() }
|
|
864
862
|
|
|
@@ -1924,9 +1922,8 @@ export class Cache implements CacheLayer {
|
|
|
1924
1922
|
}
|
|
1925
1923
|
|
|
1926
1924
|
/**
|
|
1927
|
-
* Restore a tree-form entry: its files
|
|
1928
|
-
*
|
|
1929
|
-
* absent, as on every restore after a clean. Anything else (a tree to
|
|
1925
|
+
* Restore a tree-form entry: its files copied (or cloned) out one by
|
|
1926
|
+
* one where the outputs are absent, as on every restore after a clean. Anything else (a tree to
|
|
1930
1927
|
* restore into, a workspace output, a link) goes through the extractor
|
|
1931
1928
|
* the tar form uses, fed the tree packed back into its tar.
|
|
1932
1929
|
*/
|
|
@@ -1948,21 +1945,6 @@ export class Cache implements CacheLayer {
|
|
|
1948
1945
|
`its outputs hold ${formatBytes(bytes)}, past the ${formatBytes(this.artifactCeiling)} artifact ceiling`,
|
|
1949
1946
|
)
|
|
1950
1947
|
}
|
|
1951
|
-
if (this.store.verify === 'stat') {
|
|
1952
|
-
const changed = await Promise.all(
|
|
1953
|
-
files.map((r) =>
|
|
1954
|
-
stat(path.join(tree, treeName(r.path))).then(
|
|
1955
|
-
(st) => st.size !== r.size || Math.floor(st.mtimeMs) !== r.mtimeMs,
|
|
1956
|
-
() => true,
|
|
1957
|
-
),
|
|
1958
|
-
),
|
|
1959
|
-
)
|
|
1960
|
-
if (changed.includes(true)) {
|
|
1961
|
-
throw new CorruptArtifactError(hash, 'a stored file changed since it was saved')
|
|
1962
|
-
}
|
|
1963
|
-
} else if (this.store.verify === 'hash' && !(await treeIntact(tree, this.stdoutText(hash)))) {
|
|
1964
|
-
throw new CorruptArtifactError(hash, 'the stored files no longer sum to what was saved')
|
|
1965
|
-
}
|
|
1966
1948
|
if (await this.copyTree(tree, rows, files, projectDir, accept)) return
|
|
1967
1949
|
await extractArtifactStream(
|
|
1968
1950
|
packArtifactStream(planOfTree(tree, this.stdoutText(hash))),
|
|
@@ -2699,7 +2681,6 @@ export class Cache implements CacheLayer {
|
|
|
2699
2681
|
const dir = this.tempTreePath(hash)
|
|
2700
2682
|
tree = dir
|
|
2701
2683
|
let scan: Awaited<ReturnType<typeof scanArtifact>> | undefined
|
|
2702
|
-
let raw: { meta: string; sum: string } | undefined
|
|
2703
2684
|
await extractArtifactStream(
|
|
2704
2685
|
await decodedTar(
|
|
2705
2686
|
compressed instanceof Uint8Array ? compressed : Bun.file(tmpPath!),
|
|
@@ -2711,13 +2692,12 @@ export class Cache implements CacheLayer {
|
|
|
2711
2692
|
undefined,
|
|
2712
2693
|
undefined,
|
|
2713
2694
|
undefined,
|
|
2714
|
-
(s
|
|
2695
|
+
(s) => {
|
|
2715
2696
|
scan = s
|
|
2716
|
-
raw = r
|
|
2717
2697
|
},
|
|
2718
2698
|
)
|
|
2719
2699
|
scanned = scan!
|
|
2720
|
-
await sealTree(dir, scanned
|
|
2700
|
+
await sealTree(dir, scanned)
|
|
2721
2701
|
} else if (tar !== undefined) {
|
|
2722
2702
|
// Our own tar, in memory: read in place, on this thread.
|
|
2723
2703
|
scanned = scanTarBytes(tar)
|
package/src/cache/index.ts
CHANGED
|
@@ -82,14 +82,13 @@ export {
|
|
|
82
82
|
type BlobSizeMemo,
|
|
83
83
|
} from './git-inputs.js'
|
|
84
84
|
export {
|
|
85
|
+
type ArtifactInfo,
|
|
85
86
|
LayeredCache,
|
|
86
87
|
type RemoteCacheLayer,
|
|
87
|
-
type RemoteKey,
|
|
88
88
|
type RemoteOpenContext,
|
|
89
89
|
} from './layered-cache.js'
|
|
90
90
|
export { networkConcurrency } from './remote-lane.js'
|
|
91
91
|
export type { ResolvedRemote } from './remote-lane.js'
|
|
92
|
-
export { BranchScope } from './scoped-cache.js'
|
|
93
92
|
export {
|
|
94
93
|
absentPluginCache,
|
|
95
94
|
pluginCache,
|
package/src/cache/layer.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
// reference to `Cache` below is a type (the local handle a layer may wrap).
|
|
6
6
|
|
|
7
7
|
import type { Cache } from './cache.js'
|
|
8
|
+
import type { ArtifactInfo } from './layered-cache.js'
|
|
8
9
|
import { fsClockLag, stampGrainMs } from './fs-clock.js'
|
|
9
10
|
|
|
10
11
|
export interface CacheKeyInput {
|
|
@@ -550,12 +551,12 @@ export interface CacheLayer {
|
|
|
550
551
|
*/
|
|
551
552
|
readonly hasRemote?: boolean
|
|
552
553
|
/**
|
|
553
|
-
* Batch existence probe over the remote — the
|
|
554
|
-
*
|
|
555
|
-
* per-hash". Only meaningful with `hasRemote`; a remote layer that can't
|
|
554
|
+
* Batch existence probe over the remote — the `hashes` stored remotely
|
|
555
|
+
* and what the first remote holding each knows of it (`ArtifactInfo`), in
|
|
556
|
+
* ONE round-trip, or `null` for "no batch info; use per-hash". Only meaningful with `hasRemote`; a remote layer that can't
|
|
556
557
|
* batch omits it and the prefetch pass falls back to per-hash GETs.
|
|
557
558
|
*/
|
|
558
|
-
remoteHasMany?(hashes: readonly string[]): Promise<
|
|
559
|
+
remoteHasMany?(hashes: readonly string[]): Promise<Map<string, ArtifactInfo> | null>
|
|
559
560
|
/**
|
|
560
561
|
* The subset of `hashes` the local store already holds. The prefetch
|
|
561
562
|
* pass probes the remote only for the rest, so a run the local cache
|
|
@@ -581,8 +582,6 @@ export interface CacheLayer {
|
|
|
581
582
|
* about to call absent. Only meaningful with `hasRemote`.
|
|
582
583
|
*/
|
|
583
584
|
probeFirst?(started: Promise<void>): void
|
|
584
|
-
/** `taskId`'s key is `hash`: a scoped run picks each remote key by its task (`BranchScope`). */
|
|
585
|
-
noteKey?(taskId: string, hash: string): void
|
|
586
585
|
/**
|
|
587
586
|
* Resolve once every background write-through upload settles. The
|
|
588
587
|
* orchestrator awaits it before `close()` — an upload reading layer state
|