@finbheara/archive-client 0.39.0 → 0.42.0
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/README.md +52 -1
- package/dist/chunks/{index-hdr8qvwf.js → index-0xdcttqf.js} +707 -162
- package/dist/cli.js +17 -4
- package/dist/index.js +87 -2
- package/dist/types/attach-basis.d.ts +23 -0
- package/dist/types/bytes.d.ts +12 -0
- package/dist/types/cli-spec.d.ts +1 -1
- package/dist/types/cli.d.ts +3 -0
- package/dist/types/index.d.ts +4 -2
- package/dist/types/indexes.d.ts +2 -0
- package/dist/types/manager.d.ts +1 -0
- package/dist/types/progress.d.ts +30 -0
- package/dist/types/release.d.ts +34 -2
- package/dist/types/remote.d.ts +1 -0
- package/dist/types/results.d.ts +2 -1
- package/dist/types/strays.d.ts +4 -0
- package/dist/types/stream.d.ts +4 -0
- package/package.json +1 -1
|
@@ -51,7 +51,7 @@ function planAsset2(name, manifest) {
|
|
|
51
51
|
|
|
52
52
|
import { createHash } from "crypto";
|
|
53
53
|
import { createReadStream, createWriteStream } from "fs";
|
|
54
|
-
import { Transform } from "stream";
|
|
54
|
+
import { Readable, Transform } from "stream";
|
|
55
55
|
import { pipeline } from "stream/promises";
|
|
56
56
|
import { createZstdDecompress } from "zlib";
|
|
57
57
|
async function sha256File2(path) {
|
|
@@ -79,6 +79,55 @@ function sha256OfDigest(digest) {
|
|
|
79
79
|
const m = /^sha256:([0-9a-f]{64})$/i.exec(digest.trim());
|
|
80
80
|
return m === null ? null : m[1].toLowerCase();
|
|
81
81
|
}
|
|
82
|
+
async function streamTo2(source, dest, o) {
|
|
83
|
+
const rawHash = createHash("sha256");
|
|
84
|
+
const outHash = o.decompress ? createHash("sha256") : rawHash;
|
|
85
|
+
let received = 0;
|
|
86
|
+
let written = 0;
|
|
87
|
+
const keep = o.raw === undefined ? null : createWriteStream(o.raw);
|
|
88
|
+
const tell = () => o.onBytes?.(received, o.decompress ? written : received);
|
|
89
|
+
const inTap = new Transform({
|
|
90
|
+
transform(chunk, _enc, cb) {
|
|
91
|
+
rawHash.update(chunk);
|
|
92
|
+
received += chunk.length;
|
|
93
|
+
if (!o.decompress)
|
|
94
|
+
tell();
|
|
95
|
+
if (keep !== null && !keep.write(chunk))
|
|
96
|
+
keep.once("drain", () => cb(null, chunk));
|
|
97
|
+
else
|
|
98
|
+
cb(null, chunk);
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
const outTap = new Transform({
|
|
102
|
+
transform(chunk, _enc, cb) {
|
|
103
|
+
outHash.update(chunk);
|
|
104
|
+
written += chunk.length;
|
|
105
|
+
tell();
|
|
106
|
+
cb(null, chunk);
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
const chain = [
|
|
110
|
+
Readable.from(source),
|
|
111
|
+
inTap,
|
|
112
|
+
...o.decompress ? [createZstdDecompress(), outTap] : [],
|
|
113
|
+
createWriteStream(dest)
|
|
114
|
+
];
|
|
115
|
+
try {
|
|
116
|
+
await pipeline(chain, o.signal !== undefined ? { signal: o.signal } : {});
|
|
117
|
+
} catch (e) {
|
|
118
|
+
keep?.destroy();
|
|
119
|
+
throw e;
|
|
120
|
+
}
|
|
121
|
+
if (keep !== null)
|
|
122
|
+
await new Promise((res, rej) => keep.end((e) => e ? rej(e) : res()));
|
|
123
|
+
const rawSha256 = rawHash.digest("hex");
|
|
124
|
+
return {
|
|
125
|
+
rawSha256,
|
|
126
|
+
rawBytes: received,
|
|
127
|
+
sha256: o.decompress ? outHash.digest("hex") : rawSha256,
|
|
128
|
+
bytes: o.decompress ? written : received
|
|
129
|
+
};
|
|
130
|
+
}
|
|
82
131
|
|
|
83
132
|
import { Age, DANCE_UP_YEARS, birthYearsMeet } from "@finbheara/domain/age";
|
|
84
133
|
import { createIndex as createIndex2, indexSchema as indexSchema2, personEncoder } from "@finbheara/domain/noun-index";
|
|
@@ -491,6 +540,30 @@ var FEATURES2 = [
|
|
|
491
540
|
since: 270,
|
|
492
541
|
calls: "the same competition fields on a row of results.stream and results.entries (scope majors): absent, never refused, on a release without them"
|
|
493
542
|
},
|
|
543
|
+
{
|
|
544
|
+
feature: "results.stream.competitors_printed",
|
|
545
|
+
needs: ["results:competitions.competitors_printed"],
|
|
546
|
+
since: 345,
|
|
547
|
+
calls: "the `competitors_printed` a row of results.stream and results.entries carries (the competed count the page printed): absent, never refused, on a release without it"
|
|
548
|
+
},
|
|
549
|
+
{
|
|
550
|
+
feature: "majors.stream.competitors_printed",
|
|
551
|
+
needs: ["majors:competitions.competitors_printed"],
|
|
552
|
+
since: 345,
|
|
553
|
+
calls: "the same on a row of results.stream and results.entries (scope majors)"
|
|
554
|
+
},
|
|
555
|
+
{
|
|
556
|
+
feature: "results.attach_basis",
|
|
557
|
+
needs: ["results:results.attach_basis"],
|
|
558
|
+
since: 345,
|
|
559
|
+
calls: "the `attach_basis` a row of results.query, results.stream and results.entries carries (how its dancer_id was answered, or why not): absent, never refused, on a release without it"
|
|
560
|
+
},
|
|
561
|
+
{
|
|
562
|
+
feature: "majors.attach_basis",
|
|
563
|
+
needs: ["majors:results.attach_basis"],
|
|
564
|
+
since: 345,
|
|
565
|
+
calls: "the same on a row of majors.results, and of results.stream and results.entries (scope majors)"
|
|
566
|
+
},
|
|
494
567
|
{
|
|
495
568
|
feature: "majors.tabs",
|
|
496
569
|
needs: ["majors:tabs.address", "majors:tabs.true_round"],
|
|
@@ -1402,7 +1475,7 @@ function streamSql(q, shape = {}) {
|
|
|
1402
1475
|
if (q.primary === true)
|
|
1403
1476
|
where.push("(r.primary_source IS NULL OR r.primary_source = '')");
|
|
1404
1477
|
return {
|
|
1405
|
-
sql: `SELECT ${COLUMNS}${shape.competition === true ? COMPETITION_COLUMNS : ""} FROM results r JOIN competitions c ON c.file = r.file AND c.line = r.competition_line${where.length === 0 ? "" : ` WHERE ${where.join(" AND ")}`} ORDER BY r.file, r.line`,
|
|
1478
|
+
sql: `SELECT ${COLUMNS}${shape.competition === true ? COMPETITION_COLUMNS : ""}${shape.competitorsPrinted === true ? ", c.competitors_printed AS competitors_printed" : ""}${shape.attachBasis === true ? ", r.attach_basis AS attach_basis" : ""} FROM results r JOIN competitions c ON c.file = r.file AND c.line = r.competition_line${where.length === 0 ? "" : ` WHERE ${where.join(" AND ")}`} ORDER BY r.file, r.line`,
|
|
1406
1479
|
args
|
|
1407
1480
|
};
|
|
1408
1481
|
}
|
|
@@ -1483,7 +1556,7 @@ var COLUMNS2 = `r.address AS address, r.competition AS competition, c.event AS e
|
|
|
1483
1556
|
r.place_printed AS place_printed, c.competitors AS competitors, r.name_printed AS name_printed,
|
|
1484
1557
|
r.dancer_id AS dancer_id, r.school_printed AS school_printed, r.school_id AS school_id,
|
|
1485
1558
|
r.yob AS yob, r.qualifier AS qualifier`;
|
|
1486
|
-
function queryResults(rel, db, q) {
|
|
1559
|
+
function queryResults(rel, db, q, basis = false) {
|
|
1487
1560
|
const where = [];
|
|
1488
1561
|
const args = [];
|
|
1489
1562
|
if (q.dancer_id !== undefined) {
|
|
@@ -1509,7 +1582,7 @@ function queryResults(rel, db, q) {
|
|
|
1509
1582
|
args.push(q.year);
|
|
1510
1583
|
}
|
|
1511
1584
|
const asked = q.limit ?? 500;
|
|
1512
|
-
const all = rel.db(db).query(`SELECT ${COLUMNS2} FROM results r JOIN competitions c ON c.file = r.file AND c.line = r.competition_line
|
|
1585
|
+
const all = rel.db(db).query(`SELECT ${COLUMNS2}${basis ? ", r.attach_basis AS attach_basis" : ""} FROM results r JOIN competitions c ON c.file = r.file AND c.line = r.competition_line
|
|
1513
1586
|
WHERE ${where.join(" AND ")}
|
|
1514
1587
|
ORDER BY c.year, c.event, r.competition, r.place IS NULL, r.place, r.address`).all(...args);
|
|
1515
1588
|
return {
|
|
@@ -1532,7 +1605,8 @@ class ResultFinder2 {
|
|
|
1532
1605
|
async query(q) {
|
|
1533
1606
|
await this.rel.fetch("results");
|
|
1534
1607
|
await this.rel.require("results.level");
|
|
1535
|
-
|
|
1608
|
+
const basis = (await this.rel.capability("results.attach_basis")).state === "full";
|
|
1609
|
+
return queryResults(this.rel, "results", q, basis);
|
|
1536
1610
|
}
|
|
1537
1611
|
async dancerLevel(q) {
|
|
1538
1612
|
await this.rel.fetch("results");
|
|
@@ -1544,7 +1618,9 @@ class ResultFinder2 {
|
|
|
1544
1618
|
await this.rel.fetch(db);
|
|
1545
1619
|
await this.rel.require(db === "majors" ? "majors.stream" : "results.stream");
|
|
1546
1620
|
const competition = (await this.rel.capability(`${db}.stream.competition`)).state === "full";
|
|
1547
|
-
|
|
1621
|
+
const competitorsPrinted = (await this.rel.capability(`${db}.stream.competitors_printed`)).state === "full";
|
|
1622
|
+
const attachBasis = (await this.rel.capability(`${db}.attach_basis`)).state === "full";
|
|
1623
|
+
return { db: this.rel.db(db), shape: { competition, competitorsPrinted, attachBasis } };
|
|
1548
1624
|
}
|
|
1549
1625
|
async* stream(q) {
|
|
1550
1626
|
const { db, shape } = await this.streamDb(q);
|
|
@@ -1608,7 +1684,8 @@ class MajorsFinder2 {
|
|
|
1608
1684
|
async results(q) {
|
|
1609
1685
|
await this.db();
|
|
1610
1686
|
await this.rel.require("results.level");
|
|
1611
|
-
|
|
1687
|
+
const basis = (await this.rel.capability("majors.attach_basis")).state === "full";
|
|
1688
|
+
return queryResults(this.rel, "majors", q, basis);
|
|
1612
1689
|
}
|
|
1613
1690
|
}
|
|
1614
1691
|
|
|
@@ -3293,8 +3370,8 @@ class CompResolver2 {
|
|
|
3293
3370
|
|
|
3294
3371
|
import { Database } from "bun:sqlite";
|
|
3295
3372
|
import { createHash as createHash2 } from "crypto";
|
|
3296
|
-
import { existsSync, mkdirSync, readdirSync, renameSync, rmSync, statSync } from "fs";
|
|
3297
|
-
import { join } from "path";
|
|
3373
|
+
import { existsSync as existsSync2, mkdirSync, readdirSync as readdirSync2, renameSync, rmSync as rmSync2, statSync } from "fs";
|
|
3374
|
+
import { join as join2 } from "path";
|
|
3298
3375
|
|
|
3299
3376
|
import {
|
|
3300
3377
|
createIndex as createIndex3,
|
|
@@ -3534,6 +3611,34 @@ class StaffFinder2 {
|
|
|
3534
3611
|
}
|
|
3535
3612
|
}
|
|
3536
3613
|
|
|
3614
|
+
import { existsSync, readdirSync, rmSync } from "fs";
|
|
3615
|
+
import { join } from "path";
|
|
3616
|
+
function pidAlive(pid) {
|
|
3617
|
+
if (pid === process.pid)
|
|
3618
|
+
return true;
|
|
3619
|
+
try {
|
|
3620
|
+
process.kill(pid, 0);
|
|
3621
|
+
return true;
|
|
3622
|
+
} catch (e) {
|
|
3623
|
+
return e.code === "EPERM";
|
|
3624
|
+
}
|
|
3625
|
+
}
|
|
3626
|
+
var PART_FILE = /^\..+\.(\d+)-[0-9a-f]{8}\.part(?:\.zst|\.dl)?$/;
|
|
3627
|
+
var BUILDING_FILE = /^.+\.sqlite\.building\.(\d+)$/;
|
|
3628
|
+
function sweepDead(dir, re) {
|
|
3629
|
+
if (!existsSync(dir))
|
|
3630
|
+
return [];
|
|
3631
|
+
const gone = [];
|
|
3632
|
+
for (const f of readdirSync(dir)) {
|
|
3633
|
+
const m = re.exec(f);
|
|
3634
|
+
if (m === null || pidAlive(Number(m[1])))
|
|
3635
|
+
continue;
|
|
3636
|
+
rmSync(join(dir, f), { force: true });
|
|
3637
|
+
gone.push(f);
|
|
3638
|
+
}
|
|
3639
|
+
return gone.sort();
|
|
3640
|
+
}
|
|
3641
|
+
|
|
3537
3642
|
import { parquetMetadata, parquetReadObjects, parquetSchema } from "hyparquet";
|
|
3538
3643
|
var COMPRESSORS = {
|
|
3539
3644
|
ZSTD: (input) => new Uint8Array(Bun.zstdDecompressSync(input))
|
|
@@ -3780,8 +3885,8 @@ class EventFinder2 {
|
|
|
3780
3885
|
function defaultBuilders2() {
|
|
3781
3886
|
return [schoolsIndex, feisesIndex, compsIndex, dancersIndex, staffIndex, teamsIndex, eventsIndex];
|
|
3782
3887
|
}
|
|
3783
|
-
var indexDir = (rel) =>
|
|
3784
|
-
var indexPath2 = (rel, name) =>
|
|
3888
|
+
var indexDir = (rel) => join2(rel.dir, "index");
|
|
3889
|
+
var indexPath2 = (rel, name) => join2(indexDir(rel), `${name}.sqlite`);
|
|
3785
3890
|
function indexKey2(rel, b) {
|
|
3786
3891
|
const files = rel.receipt().files;
|
|
3787
3892
|
const parts = [b.name, b.version, ...b.keyParts?.(rel) ?? []];
|
|
@@ -3792,7 +3897,7 @@ function indexKey2(rel, b) {
|
|
|
3792
3897
|
return createHash2("sha256").update(parts.join("\x00")).digest("hex");
|
|
3793
3898
|
}
|
|
3794
3899
|
function keyOf(path) {
|
|
3795
|
-
if (!
|
|
3900
|
+
if (!existsSync2(path))
|
|
3796
3901
|
return null;
|
|
3797
3902
|
try {
|
|
3798
3903
|
const db = new Database(path, { readonly: true });
|
|
@@ -3817,8 +3922,9 @@ function buildIndex2(rel, b, o) {
|
|
|
3817
3922
|
if (b.load !== undefined && o.loaded === undefined)
|
|
3818
3923
|
throw new Error(`archive-client: index "${b.name}" reads its inputs first: build it with ensureIndex (or rel.index)`);
|
|
3819
3924
|
mkdirSync(indexDir(rel), { recursive: true });
|
|
3925
|
+
sweepDead(indexDir(rel), BUILDING_FILE);
|
|
3820
3926
|
const tmp = `${out}.building.${process.pid}`;
|
|
3821
|
-
|
|
3927
|
+
rmSync2(tmp, { force: true });
|
|
3822
3928
|
const t0 = performance.now();
|
|
3823
3929
|
const db = new Database(tmp, { create: true });
|
|
3824
3930
|
try {
|
|
@@ -3834,7 +3940,7 @@ function buildIndex2(rel, b, o) {
|
|
|
3834
3940
|
db.run("VACUUM");
|
|
3835
3941
|
} catch (e) {
|
|
3836
3942
|
db.close();
|
|
3837
|
-
|
|
3943
|
+
rmSync2(tmp, { force: true });
|
|
3838
3944
|
throw e;
|
|
3839
3945
|
}
|
|
3840
3946
|
db.close();
|
|
@@ -3845,16 +3951,42 @@ function buildIndex2(rel, b, o) {
|
|
|
3845
3951
|
async function ensureIndex2(rel, b, o) {
|
|
3846
3952
|
if (!o.force && isCurrent2(rel, b))
|
|
3847
3953
|
return indexPath2(rel, b.name);
|
|
3848
|
-
const
|
|
3849
|
-
|
|
3954
|
+
const t0 = performance.now();
|
|
3955
|
+
const tell = (state, error) => o.progress?.({
|
|
3956
|
+
tag: rel.tag,
|
|
3957
|
+
kind: "index",
|
|
3958
|
+
item: b.name,
|
|
3959
|
+
state,
|
|
3960
|
+
bytes: 0,
|
|
3961
|
+
total: null,
|
|
3962
|
+
unpacked: 0,
|
|
3963
|
+
ms: performance.now() - t0,
|
|
3964
|
+
...error !== undefined ? { error } : {}
|
|
3965
|
+
});
|
|
3966
|
+
tell("indexing");
|
|
3967
|
+
try {
|
|
3968
|
+
const loaded = b.load === undefined ? undefined : await b.load(rel);
|
|
3969
|
+
if (o.progress !== undefined)
|
|
3970
|
+
await new Promise((r) => setTimeout(r, 0));
|
|
3971
|
+
const path = buildIndex2(rel, b, {
|
|
3972
|
+
force: o.force,
|
|
3973
|
+
loaded,
|
|
3974
|
+
...o.progress === undefined && o.log !== undefined ? { log: o.log } : {}
|
|
3975
|
+
});
|
|
3976
|
+
tell("done");
|
|
3977
|
+
return path;
|
|
3978
|
+
} catch (e) {
|
|
3979
|
+
tell("failed", e.message);
|
|
3980
|
+
throw e;
|
|
3981
|
+
}
|
|
3850
3982
|
}
|
|
3851
3983
|
function listIndexes2(rel) {
|
|
3852
3984
|
const dir = indexDir(rel);
|
|
3853
|
-
if (!
|
|
3985
|
+
if (!existsSync2(dir))
|
|
3854
3986
|
return [];
|
|
3855
|
-
return
|
|
3987
|
+
return readdirSync2(dir).filter((f) => f.endsWith(".sqlite")).sort().map((f) => {
|
|
3856
3988
|
const name = f.slice(0, -".sqlite".length);
|
|
3857
|
-
const path =
|
|
3989
|
+
const path = join2(dir, f);
|
|
3858
3990
|
const b = rel.builders().find((x) => x.name === name);
|
|
3859
3991
|
return {
|
|
3860
3992
|
name,
|
|
@@ -3868,35 +4000,35 @@ function listIndexes2(rel) {
|
|
|
3868
4000
|
import { createHash as createHash3 } from "crypto";
|
|
3869
4001
|
import {
|
|
3870
4002
|
copyFileSync as copyFileSync2,
|
|
3871
|
-
existsSync as
|
|
4003
|
+
existsSync as existsSync7,
|
|
3872
4004
|
linkSync as linkSync2,
|
|
3873
4005
|
mkdirSync as mkdirSync5,
|
|
3874
4006
|
readFileSync as readFileSync5,
|
|
3875
4007
|
readdirSync as readdirSync4,
|
|
3876
4008
|
renameSync as renameSync3,
|
|
3877
|
-
rmSync as
|
|
4009
|
+
rmSync as rmSync6,
|
|
3878
4010
|
statSync as statSync4
|
|
3879
4011
|
} from "fs";
|
|
3880
|
-
import { join as
|
|
4012
|
+
import { join as join6 } from "path";
|
|
3881
4013
|
|
|
3882
4014
|
import {
|
|
3883
4015
|
copyFileSync,
|
|
3884
|
-
existsSync as
|
|
4016
|
+
existsSync as existsSync6,
|
|
3885
4017
|
linkSync,
|
|
3886
4018
|
mkdirSync as mkdirSync4,
|
|
3887
4019
|
readFileSync as readFileSync4,
|
|
3888
4020
|
readdirSync as readdirSync3,
|
|
3889
|
-
rmSync as
|
|
4021
|
+
rmSync as rmSync5,
|
|
3890
4022
|
statSync as statSync3
|
|
3891
4023
|
} from "fs";
|
|
3892
|
-
import { isAbsolute, join as
|
|
4024
|
+
import { isAbsolute, join as join5, relative, resolve, sep } from "path";
|
|
3893
4025
|
|
|
3894
|
-
import { existsSync as
|
|
3895
|
-
import { join as
|
|
3896
|
-
var pinPath = (root) =>
|
|
4026
|
+
import { existsSync as existsSync3, readFileSync as readFileSync2, rmSync as rmSync3 } from "fs";
|
|
4027
|
+
import { join as join3 } from "path";
|
|
4028
|
+
var pinPath = (root) => join3(root, "PINNED.json");
|
|
3897
4029
|
function readPin2(root) {
|
|
3898
4030
|
const p = pinPath(root);
|
|
3899
|
-
if (!
|
|
4031
|
+
if (!existsSync3(p))
|
|
3900
4032
|
return null;
|
|
3901
4033
|
try {
|
|
3902
4034
|
const v = JSON.parse(readFileSync2(p, "utf8"));
|
|
@@ -3912,23 +4044,24 @@ function writePin(root, pin) {
|
|
|
3912
4044
|
}
|
|
3913
4045
|
function clearPin(root) {
|
|
3914
4046
|
const p = pinPath(root);
|
|
3915
|
-
if (!
|
|
4047
|
+
if (!existsSync3(p))
|
|
3916
4048
|
return false;
|
|
3917
|
-
|
|
4049
|
+
rmSync3(p);
|
|
3918
4050
|
return true;
|
|
3919
4051
|
}
|
|
3920
4052
|
|
|
3921
4053
|
import { Database as Database2 } from "bun:sqlite";
|
|
4054
|
+
import { randomBytes } from "crypto";
|
|
3922
4055
|
import {
|
|
3923
|
-
|
|
4056
|
+
createReadStream as createReadStream2,
|
|
4057
|
+
existsSync as existsSync5,
|
|
3924
4058
|
mkdirSync as mkdirSync3,
|
|
3925
4059
|
readFileSync as readFileSync3,
|
|
3926
|
-
readdirSync as readdirSync2,
|
|
3927
4060
|
renameSync as renameSync2,
|
|
3928
|
-
rmSync as
|
|
4061
|
+
rmSync as rmSync4,
|
|
3929
4062
|
statSync as statSync2
|
|
3930
4063
|
} from "fs";
|
|
3931
|
-
import { join as
|
|
4064
|
+
import { basename, dirname as dirname2, join as join4 } from "path";
|
|
3932
4065
|
|
|
3933
4066
|
var CALENDAR_STAGES2 = [
|
|
3934
4067
|
"announced",
|
|
@@ -4264,9 +4397,9 @@ class RegistrationResolver2 {
|
|
|
4264
4397
|
}
|
|
4265
4398
|
|
|
4266
4399
|
import { spawn } from "child_process";
|
|
4267
|
-
import { createWriteStream as createWriteStream2, existsSync as
|
|
4400
|
+
import { createWriteStream as createWriteStream2, existsSync as existsSync4, mkdirSync as mkdirSync2 } from "fs";
|
|
4268
4401
|
import { dirname } from "path";
|
|
4269
|
-
import { Readable } from "stream";
|
|
4402
|
+
import { Readable as Readable2 } from "stream";
|
|
4270
4403
|
import { pipeline as pipeline2 } from "stream/promises";
|
|
4271
4404
|
function run(cmd, args) {
|
|
4272
4405
|
return new Promise((resolve) => {
|
|
@@ -4332,8 +4465,59 @@ var ghRemote2 = {
|
|
|
4332
4465
|
dest,
|
|
4333
4466
|
"--clobber"
|
|
4334
4467
|
]);
|
|
4335
|
-
if (r.status !== 0 || !
|
|
4468
|
+
if (r.status !== 0 || !existsSync4(dest))
|
|
4336
4469
|
throw new Error(`gh release download ${tag} -p ${name}: ${firstLine(r.stderr)}`);
|
|
4470
|
+
},
|
|
4471
|
+
async bytes(repo, tag, name, signal) {
|
|
4472
|
+
const what = `gh release download ${tag} -p ${name}`;
|
|
4473
|
+
let child;
|
|
4474
|
+
try {
|
|
4475
|
+
child = spawn("gh", ["release", "download", tag, "-R", repo, "-p", name, "-O", "-"], {
|
|
4476
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
4477
|
+
});
|
|
4478
|
+
} catch (e) {
|
|
4479
|
+
throw new Error(`${what}: ${e.message}`);
|
|
4480
|
+
}
|
|
4481
|
+
let stderr = "";
|
|
4482
|
+
child.stderr?.on("data", (d) => {
|
|
4483
|
+
stderr += String(d);
|
|
4484
|
+
});
|
|
4485
|
+
const closed = new Promise((resolve) => {
|
|
4486
|
+
child.on("error", (e) => {
|
|
4487
|
+
stderr += e.message;
|
|
4488
|
+
resolve(null);
|
|
4489
|
+
});
|
|
4490
|
+
child.on("close", (status) => resolve(status));
|
|
4491
|
+
});
|
|
4492
|
+
const kill = () => {
|
|
4493
|
+
child.kill("SIGTERM");
|
|
4494
|
+
};
|
|
4495
|
+
signal?.addEventListener("abort", kill, { once: true });
|
|
4496
|
+
const out = child.stdout;
|
|
4497
|
+
if (out === null || out === undefined)
|
|
4498
|
+
throw new Error(`${what}: no stdout`);
|
|
4499
|
+
const it = out[Symbol.asyncIterator]();
|
|
4500
|
+
const failed = async () => {
|
|
4501
|
+
const status = await closed;
|
|
4502
|
+
signal?.removeEventListener("abort", kill);
|
|
4503
|
+
if (status !== 0)
|
|
4504
|
+
throw new Error(`${what}: ${firstLine(stderr) || `exit ${status}`}`);
|
|
4505
|
+
};
|
|
4506
|
+
const head = await it.next();
|
|
4507
|
+
if (head.done === true) {
|
|
4508
|
+
await failed();
|
|
4509
|
+
return async function* () {}();
|
|
4510
|
+
}
|
|
4511
|
+
return async function* () {
|
|
4512
|
+
yield head.value;
|
|
4513
|
+
for (;; ) {
|
|
4514
|
+
const n = await it.next();
|
|
4515
|
+
if (n.done === true)
|
|
4516
|
+
break;
|
|
4517
|
+
yield n.value;
|
|
4518
|
+
}
|
|
4519
|
+
await failed();
|
|
4520
|
+
}();
|
|
4337
4521
|
}
|
|
4338
4522
|
};
|
|
4339
4523
|
var API = "https://api.github.com";
|
|
@@ -4386,7 +4570,19 @@ function httpsRemote2(env = process.env, fetchImpl = fetch) {
|
|
|
4386
4570
|
if (!res.ok || res.body === null)
|
|
4387
4571
|
throw new Error(`GET ${a.url}: ${res.status} ${res.statusText}`);
|
|
4388
4572
|
mkdirSync2(dirname(dest), { recursive: true });
|
|
4389
|
-
await pipeline2(
|
|
4573
|
+
await pipeline2(Readable2.fromWeb(nodeWebStream(res.body)), createWriteStream2(dest));
|
|
4574
|
+
},
|
|
4575
|
+
async bytes(repo, tag, name, signal) {
|
|
4576
|
+
const a = (await release(repo, tag)).assets.find((x) => x.name === name);
|
|
4577
|
+
if (a === undefined)
|
|
4578
|
+
throw new Error(`${tag} has no asset ${name}`);
|
|
4579
|
+
const res = await fetchImpl(a.url, {
|
|
4580
|
+
headers: headers(env, "application/octet-stream"),
|
|
4581
|
+
...signal !== undefined ? { signal } : {}
|
|
4582
|
+
});
|
|
4583
|
+
if (!res.ok || res.body === null)
|
|
4584
|
+
throw new Error(`GET ${a.url}: ${res.status} ${res.statusText}`);
|
|
4585
|
+
return Readable2.fromWeb(nodeWebStream(res.body));
|
|
4390
4586
|
}
|
|
4391
4587
|
};
|
|
4392
4588
|
}
|
|
@@ -4406,7 +4602,10 @@ function fallbackRemote2(first, second) {
|
|
|
4406
4602
|
name: `${first.name}+${second.name}`,
|
|
4407
4603
|
tags: both((r) => r.tags.bind(r)),
|
|
4408
4604
|
assets: both((r) => r.assets.bind(r)),
|
|
4409
|
-
download: both((r) => r.download.bind(r))
|
|
4605
|
+
download: both((r) => r.download.bind(r)),
|
|
4606
|
+
...first.bytes !== undefined && second.bytes !== undefined ? {
|
|
4607
|
+
bytes: both((r) => r.bytes.bind(r))
|
|
4608
|
+
} : {}
|
|
4410
4609
|
};
|
|
4411
4610
|
}
|
|
4412
4611
|
var installed = null;
|
|
@@ -4645,12 +4844,12 @@ class UpcomingFinder2 {
|
|
|
4645
4844
|
function tagDir(root, tag) {
|
|
4646
4845
|
if (parseArchiveTag2(tag) === null)
|
|
4647
4846
|
throw new Error(`archive-client: "${tag}" is not a release tag (archive-YYYY.MM.DD[-N[.k]])`);
|
|
4648
|
-
return
|
|
4847
|
+
return join4(root, tag);
|
|
4649
4848
|
}
|
|
4650
|
-
var receiptPath = (dir) =>
|
|
4849
|
+
var receiptPath = (dir) => join4(dir, ".client.json");
|
|
4651
4850
|
function readReceipt(dir) {
|
|
4652
4851
|
const p = receiptPath(dir);
|
|
4653
|
-
if (!
|
|
4852
|
+
if (!existsSync5(p))
|
|
4654
4853
|
return null;
|
|
4655
4854
|
try {
|
|
4656
4855
|
return JSON.parse(readFileSync3(p, "utf8"));
|
|
@@ -4667,7 +4866,7 @@ async function resolveTag2(spec, opts = {}) {
|
|
|
4667
4866
|
const pin = readPin2(root);
|
|
4668
4867
|
return resolveTag2(pin === null ? { kind: "latest" } : { kind: "tag", tag: pin.tag }, opts);
|
|
4669
4868
|
}
|
|
4670
|
-
if (s.kind === "tag" &&
|
|
4869
|
+
if (s.kind === "tag" && existsSync5(join4(tagDir(root, s.tag), "MANIFEST.json")))
|
|
4671
4870
|
return s.tag;
|
|
4672
4871
|
const repo = requireRepo2(opts);
|
|
4673
4872
|
const tags = await (opts.remote ?? defaultRemote()).tags(repo);
|
|
@@ -4676,9 +4875,30 @@ async function resolveTag2(spec, opts = {}) {
|
|
|
4676
4875
|
throw new Error(`archive-client: ${repo} publishes no release ${s.kind === "tag" ? s.tag : s.kind === "cut" ? `of cut ${s.cut}${s.fraction > 0 ? `.${s.fraction}` : ""}` : "at all"}`);
|
|
4677
4876
|
return tag;
|
|
4678
4877
|
}
|
|
4679
|
-
|
|
4680
|
-
|
|
4681
|
-
|
|
4878
|
+
var DEFAULT_CONCURRENCY2 = 4;
|
|
4879
|
+
function partPath(dest) {
|
|
4880
|
+
return join4(dirname2(dest), `.${basename(dest)}.${process.pid}-${randomBytes(4).toString("hex")}.part`);
|
|
4881
|
+
}
|
|
4882
|
+
|
|
4883
|
+
class Slots {
|
|
4884
|
+
n;
|
|
4885
|
+
running = 0;
|
|
4886
|
+
waiting = [];
|
|
4887
|
+
constructor(n) {
|
|
4888
|
+
this.n = n;
|
|
4889
|
+
}
|
|
4890
|
+
async run(job, signal) {
|
|
4891
|
+
if (this.running >= this.n)
|
|
4892
|
+
await new Promise((r) => this.waiting.push(r));
|
|
4893
|
+
this.running++;
|
|
4894
|
+
try {
|
|
4895
|
+
signal.throwIfAborted();
|
|
4896
|
+
return await job();
|
|
4897
|
+
} finally {
|
|
4898
|
+
this.running--;
|
|
4899
|
+
this.waiting.shift()?.();
|
|
4900
|
+
}
|
|
4901
|
+
}
|
|
4682
4902
|
}
|
|
4683
4903
|
|
|
4684
4904
|
class Release2 {
|
|
@@ -4705,7 +4925,7 @@ class Release2 {
|
|
|
4705
4925
|
feisPanels;
|
|
4706
4926
|
opts;
|
|
4707
4927
|
handles = new Map;
|
|
4708
|
-
|
|
4928
|
+
listing = null;
|
|
4709
4929
|
constructor(tag, root, manifest, opts) {
|
|
4710
4930
|
const t = parseArchiveTag2(tag);
|
|
4711
4931
|
this.tag = tag;
|
|
@@ -4738,19 +4958,65 @@ class Release2 {
|
|
|
4738
4958
|
return planAsset2(asset, this.manifest);
|
|
4739
4959
|
}
|
|
4740
4960
|
has(asset) {
|
|
4741
|
-
return
|
|
4961
|
+
return existsSync5(join4(this.dir, this.plan(asset).local));
|
|
4742
4962
|
}
|
|
4743
4963
|
path(asset) {
|
|
4744
|
-
const p =
|
|
4745
|
-
if (!
|
|
4964
|
+
const p = join4(this.dir, this.plan(asset).local);
|
|
4965
|
+
if (!existsSync5(p))
|
|
4746
4966
|
throw new Error(`archive-client: ${this.tag} has no ${asset} in the cache: fetch it first (\`await rel.fetch("${asset}")\`, or \`fetch --release ${this.tag} --assets ${asset}\`)`);
|
|
4747
4967
|
return p;
|
|
4748
4968
|
}
|
|
4749
4969
|
async fetch(...assets) {
|
|
4750
|
-
|
|
4751
|
-
|
|
4752
|
-
|
|
4753
|
-
|
|
4970
|
+
return this.fetching(assets).all;
|
|
4971
|
+
}
|
|
4972
|
+
inflight = new Map;
|
|
4973
|
+
slots = null;
|
|
4974
|
+
swept = false;
|
|
4975
|
+
fetching(assets) {
|
|
4976
|
+
this.slots ??= new Slots(Math.max(1, this.opts.concurrency ?? DEFAULT_CONCURRENCY2));
|
|
4977
|
+
const slots = this.slots;
|
|
4978
|
+
const ac = new AbortController;
|
|
4979
|
+
const mine = new Map;
|
|
4980
|
+
let first = null;
|
|
4981
|
+
for (const a of assets) {
|
|
4982
|
+
if (mine.has(a))
|
|
4983
|
+
continue;
|
|
4984
|
+
const local = this.plan(a).local;
|
|
4985
|
+
let p = this.inflight.get(local);
|
|
4986
|
+
if (p === undefined) {
|
|
4987
|
+
p = slots.run(() => this.fetchOne(a, ac.signal), ac.signal);
|
|
4988
|
+
this.inflight.set(local, p);
|
|
4989
|
+
const started = p;
|
|
4990
|
+
started.then(() => this.inflight.delete(local), () => this.inflight.delete(local));
|
|
4991
|
+
}
|
|
4992
|
+
p.catch((e) => {
|
|
4993
|
+
first ??= { e };
|
|
4994
|
+
ac.abort();
|
|
4995
|
+
});
|
|
4996
|
+
mine.set(a, p);
|
|
4997
|
+
}
|
|
4998
|
+
const all = (async () => {
|
|
4999
|
+
const settled = await Promise.allSettled([...mine.values()]);
|
|
5000
|
+
if (first !== null)
|
|
5001
|
+
throw first.e;
|
|
5002
|
+
return assets.map((a) => {
|
|
5003
|
+
const s = settled[[...mine.keys()].indexOf(a)];
|
|
5004
|
+
return s.value;
|
|
5005
|
+
});
|
|
5006
|
+
})();
|
|
5007
|
+
return {
|
|
5008
|
+
each: (asset) => {
|
|
5009
|
+
const p = mine.get(asset);
|
|
5010
|
+
if (p === undefined)
|
|
5011
|
+
throw new Error(`archive-client: ${asset} was not asked of this fetch`);
|
|
5012
|
+
return p;
|
|
5013
|
+
},
|
|
5014
|
+
all,
|
|
5015
|
+
cancel: () => {
|
|
5016
|
+
first ??= { e: new Error("archive-client: the fetch was cancelled") };
|
|
5017
|
+
ac.abort();
|
|
5018
|
+
}
|
|
5019
|
+
};
|
|
4754
5020
|
}
|
|
4755
5021
|
async publishes(asset) {
|
|
4756
5022
|
const plan = this.plan(asset);
|
|
@@ -4806,25 +5072,29 @@ class Release2 {
|
|
|
4806
5072
|
if (c.state === "missing")
|
|
4807
5073
|
throw needsRelease2(this.tag, c);
|
|
4808
5074
|
}
|
|
4809
|
-
|
|
4810
|
-
|
|
4811
|
-
|
|
4812
|
-
if (
|
|
5075
|
+
listed() {
|
|
5076
|
+
this.listing ??= (async () => {
|
|
5077
|
+
const assets = await (this.opts.remote ?? defaultRemote()).assets(requireRepo2(this.opts), this.tag);
|
|
5078
|
+
if (existsSync5(this.dir)) {
|
|
4813
5079
|
const r = this.receipt();
|
|
4814
|
-
r.published =
|
|
5080
|
+
r.published = assets.map((a) => a.name).sort();
|
|
4815
5081
|
writeJsonAtomic2(receiptPath(this.dir), r);
|
|
4816
5082
|
}
|
|
4817
|
-
|
|
4818
|
-
|
|
5083
|
+
return assets;
|
|
5084
|
+
})();
|
|
5085
|
+
this.listing.catch(() => {
|
|
5086
|
+
this.listing = null;
|
|
5087
|
+
});
|
|
5088
|
+
return this.listing;
|
|
4819
5089
|
}
|
|
4820
|
-
async fetchOne(asset) {
|
|
5090
|
+
async fetchOne(asset, signal) {
|
|
4821
5091
|
const plan = this.plan(asset);
|
|
4822
|
-
const dest =
|
|
5092
|
+
const dest = join4(this.dir, plan.local);
|
|
4823
5093
|
const have = this.receipt().files[plan.local];
|
|
4824
|
-
if (
|
|
5094
|
+
if (existsSync5(dest) && have !== undefined)
|
|
4825
5095
|
return have;
|
|
4826
5096
|
const log = this.opts.log ?? (() => {});
|
|
4827
|
-
if (
|
|
5097
|
+
if (existsSync5(dest) && plan.contentSha256 !== null) {
|
|
4828
5098
|
const sha = await sha256File2(dest);
|
|
4829
5099
|
if (sha === plan.contentSha256) {
|
|
4830
5100
|
const adopted = {
|
|
@@ -4833,7 +5103,8 @@ class Release2 {
|
|
|
4833
5103
|
bytes: statSync2(dest).size,
|
|
4834
5104
|
verified: ["manifest"]
|
|
4835
5105
|
};
|
|
4836
|
-
|
|
5106
|
+
if (this.opts.progress === undefined)
|
|
5107
|
+
log(`adopt ${this.tag} ${plan.local} (the manifest's sha256)`);
|
|
4837
5108
|
this.record(plan.local, adopted);
|
|
4838
5109
|
return adopted;
|
|
4839
5110
|
}
|
|
@@ -4842,14 +5113,24 @@ class Release2 {
|
|
|
4842
5113
|
const pub = plan.published.map((n) => listed.find((a) => a.name === n)).find((a) => a !== undefined);
|
|
4843
5114
|
if (pub === undefined)
|
|
4844
5115
|
throw new Error(`archive-client: ${this.tag} publishes no ${plan.published.join(" or ")} (asked: ${asset})`);
|
|
4845
|
-
|
|
4846
|
-
|
|
4847
|
-
|
|
4848
|
-
|
|
4849
|
-
|
|
4850
|
-
const landed = await
|
|
5116
|
+
mkdirSync3(this.dir, { recursive: true });
|
|
5117
|
+
if (!this.swept) {
|
|
5118
|
+
sweepDead(this.dir, PART_FILE);
|
|
5119
|
+
this.swept = true;
|
|
5120
|
+
}
|
|
5121
|
+
const landed = await fetchAsset({
|
|
5122
|
+
remote: this.opts.remote ?? defaultRemote(),
|
|
5123
|
+
repo: requireRepo2(this.opts),
|
|
5124
|
+
tag: this.tag,
|
|
5125
|
+
pub,
|
|
5126
|
+
plan,
|
|
5127
|
+
dest,
|
|
5128
|
+
opts: this.opts,
|
|
5129
|
+
log,
|
|
5130
|
+
...this.opts.progress !== undefined ? { progress: this.opts.progress } : {},
|
|
5131
|
+
...signal !== undefined ? { signal } : {}
|
|
5132
|
+
});
|
|
4851
5133
|
this.record(plan.local, landed);
|
|
4852
|
-
dropIfEmpty(part);
|
|
4853
5134
|
return landed;
|
|
4854
5135
|
}
|
|
4855
5136
|
record(local, f) {
|
|
@@ -4878,7 +5159,11 @@ class Release2 {
|
|
|
4878
5159
|
if (FEATURES2.some((f) => f.feature === b.name))
|
|
4879
5160
|
await this.require(b.name);
|
|
4880
5161
|
await this.fetch(...b.inputs);
|
|
4881
|
-
return ensureIndex2(this, b, {
|
|
5162
|
+
return ensureIndex2(this, b, {
|
|
5163
|
+
force: o.force ?? false,
|
|
5164
|
+
log: this.opts.log,
|
|
5165
|
+
...this.opts.progress !== undefined ? { progress: this.opts.progress } : {}
|
|
5166
|
+
});
|
|
4882
5167
|
}
|
|
4883
5168
|
async indexDb(name) {
|
|
4884
5169
|
const key = `index:${name}`;
|
|
@@ -4898,69 +5183,172 @@ class Release2 {
|
|
|
4898
5183
|
this.close();
|
|
4899
5184
|
}
|
|
4900
5185
|
}
|
|
4901
|
-
|
|
5186
|
+
function verifyLanded(got, pub, plan, strict) {
|
|
4902
5187
|
const verified = [];
|
|
4903
5188
|
const host = sha256OfDigest(pub.digest);
|
|
4904
5189
|
if (host !== null) {
|
|
4905
|
-
|
|
4906
|
-
|
|
4907
|
-
rmSync3(raw, { force: true });
|
|
4908
|
-
throw new Error(`archive-client: ${pub.name} does not match the host's digest (sha256 ${got}, the host says ${host})`);
|
|
4909
|
-
}
|
|
5190
|
+
if (got.rawSha256 !== host)
|
|
5191
|
+
throw new Error(`archive-client: ${pub.name} does not match the host's digest (sha256 ${got.rawSha256}, the host says ${host})`);
|
|
4910
5192
|
verified.push("host-digest");
|
|
4911
5193
|
}
|
|
4912
|
-
let landedAt = raw;
|
|
4913
|
-
let sha;
|
|
4914
|
-
if (plan.decompress && pub.name.endsWith(".zst")) {
|
|
4915
|
-
landedAt = `${raw.slice(0, -".zst".length)}.plain`;
|
|
4916
|
-
log(`decompress ${pub.name}`);
|
|
4917
|
-
sha = await unzstdTo2(raw, landedAt);
|
|
4918
|
-
} else
|
|
4919
|
-
sha = await sha256File2(raw);
|
|
4920
5194
|
if (plan.contentSha256 !== null) {
|
|
4921
|
-
if (
|
|
4922
|
-
|
|
4923
|
-
throw new Error(`archive-client: ${plan.local} does not match MANIFEST.json (sha256 ${sha}, the manifest says ${plan.contentSha256})`);
|
|
4924
|
-
}
|
|
5195
|
+
if (got.sha256 !== plan.contentSha256)
|
|
5196
|
+
throw new Error(`archive-client: ${plan.local} does not match MANIFEST.json (sha256 ${got.sha256}, the manifest says ${plan.contentSha256})`);
|
|
4925
5197
|
verified.push("manifest");
|
|
4926
5198
|
}
|
|
4927
|
-
if (verified.length === 0 &&
|
|
4928
|
-
rmSync3(landedAt, { force: true });
|
|
5199
|
+
if (verified.length === 0 && strict)
|
|
4929
5200
|
throw new Error(`archive-client: nothing verifies ${pub.name} (no host digest, no manifest hash) and strict is set`);
|
|
5201
|
+
return verified;
|
|
5202
|
+
}
|
|
5203
|
+
async function fetchAsset(j) {
|
|
5204
|
+
const t0 = performance.now();
|
|
5205
|
+
const { pub, plan, tag } = j;
|
|
5206
|
+
const decompress = plan.decompress && pub.name.endsWith(".zst");
|
|
5207
|
+
const tmp = partPath(j.dest);
|
|
5208
|
+
const raw = decompress && j.opts.keepCompressed === true ? `${tmp}.zst` : undefined;
|
|
5209
|
+
let dl = null;
|
|
5210
|
+
let bytes = 0;
|
|
5211
|
+
let unpacked = 0;
|
|
5212
|
+
let state = "downloading";
|
|
5213
|
+
const total = Number.isFinite(pub.size) ? pub.size : null;
|
|
5214
|
+
const tell = (s, error) => j.progress?.({
|
|
5215
|
+
tag,
|
|
5216
|
+
kind: "asset",
|
|
5217
|
+
item: pub.name,
|
|
5218
|
+
state: s,
|
|
5219
|
+
bytes,
|
|
5220
|
+
total,
|
|
5221
|
+
unpacked,
|
|
5222
|
+
ms: performance.now() - t0,
|
|
5223
|
+
...error !== undefined ? { error } : {}
|
|
5224
|
+
});
|
|
5225
|
+
const onBytes = (received, written) => {
|
|
5226
|
+
if (state === "downloading")
|
|
5227
|
+
bytes = received;
|
|
5228
|
+
unpacked = decompress ? written : 0;
|
|
5229
|
+
tell(state);
|
|
5230
|
+
};
|
|
5231
|
+
if (j.progress === undefined)
|
|
5232
|
+
j.log(`fetch ${tag} ${pub.name} (${pub.size} bytes)`);
|
|
5233
|
+
tell("downloading");
|
|
5234
|
+
try {
|
|
5235
|
+
let got;
|
|
5236
|
+
if (j.remote.bytes !== undefined) {
|
|
5237
|
+
const body = await j.remote.bytes(j.repo, tag, pub.name, j.signal);
|
|
5238
|
+
if (decompress && j.progress === undefined)
|
|
5239
|
+
j.log(`decompress ${pub.name}`);
|
|
5240
|
+
got = await streamTo2(body, tmp, {
|
|
5241
|
+
decompress,
|
|
5242
|
+
...raw !== undefined ? { raw } : {},
|
|
5243
|
+
onBytes,
|
|
5244
|
+
...j.signal !== undefined ? { signal: j.signal } : {}
|
|
5245
|
+
});
|
|
5246
|
+
} else {
|
|
5247
|
+
dl = `${tmp}.dl`;
|
|
5248
|
+
await j.remote.download(j.repo, tag, pub.name, dl);
|
|
5249
|
+
j.signal?.throwIfAborted();
|
|
5250
|
+
bytes = statSync2(dl).size;
|
|
5251
|
+
if (decompress) {
|
|
5252
|
+
if (j.progress === undefined)
|
|
5253
|
+
j.log(`decompress ${pub.name}`);
|
|
5254
|
+
state = "unpacking";
|
|
5255
|
+
tell(state);
|
|
5256
|
+
got = await streamTo2(createReadStream2(dl), tmp, {
|
|
5257
|
+
decompress,
|
|
5258
|
+
onBytes,
|
|
5259
|
+
...j.signal !== undefined ? { signal: j.signal } : {}
|
|
5260
|
+
});
|
|
5261
|
+
if (raw !== undefined)
|
|
5262
|
+
renameSync2(dl, raw);
|
|
5263
|
+
else
|
|
5264
|
+
rmSync4(dl, { force: true });
|
|
5265
|
+
} else {
|
|
5266
|
+
state = "verifying";
|
|
5267
|
+
tell(state);
|
|
5268
|
+
const sha = await sha256File2(dl);
|
|
5269
|
+
got = { rawSha256: sha, rawBytes: bytes, sha256: sha, bytes };
|
|
5270
|
+
renameSync2(dl, tmp);
|
|
5271
|
+
}
|
|
5272
|
+
dl = null;
|
|
5273
|
+
}
|
|
5274
|
+
state = "verifying";
|
|
5275
|
+
tell(state);
|
|
5276
|
+
const verified = verifyLanded(got, pub, plan, j.opts.strict === true);
|
|
5277
|
+
renameSync2(tmp, j.dest);
|
|
5278
|
+
if (raw !== undefined)
|
|
5279
|
+
renameSync2(raw, `${j.dest}.zst`);
|
|
5280
|
+
tell("done");
|
|
5281
|
+
return { from: pub.name, sha256: got.sha256, bytes: got.bytes, verified };
|
|
5282
|
+
} catch (e) {
|
|
5283
|
+
for (const f of [tmp, raw, dl])
|
|
5284
|
+
if (f !== undefined && f !== null)
|
|
5285
|
+
rmSync4(f, { force: true });
|
|
5286
|
+
const cancelled = j.signal?.aborted === true;
|
|
5287
|
+
tell(cancelled ? "cancelled" : "failed", cancelled ? undefined : e.message);
|
|
5288
|
+
throw e;
|
|
5289
|
+
}
|
|
5290
|
+
}
|
|
5291
|
+
async function land(raw, pub, plan, dest, opts, log) {
|
|
5292
|
+
const decompress = plan.decompress && pub.name.endsWith(".zst");
|
|
5293
|
+
let got;
|
|
5294
|
+
let landedAt = raw;
|
|
5295
|
+
if (decompress) {
|
|
5296
|
+
landedAt = partPath(dest);
|
|
5297
|
+
log(`decompress ${pub.name}`);
|
|
5298
|
+
got = await streamTo2(createReadStream2(raw), landedAt, { decompress: true });
|
|
5299
|
+
} else {
|
|
5300
|
+
const sha = await sha256File2(raw);
|
|
5301
|
+
const n = statSync2(raw).size;
|
|
5302
|
+
got = { rawSha256: sha, rawBytes: n, sha256: sha, bytes: n };
|
|
5303
|
+
}
|
|
5304
|
+
let verified;
|
|
5305
|
+
try {
|
|
5306
|
+
verified = verifyLanded(got, pub, plan, opts.strict === true);
|
|
5307
|
+
} catch (e) {
|
|
5308
|
+
rmSync4(landedAt, { force: true });
|
|
5309
|
+
if (landedAt !== raw)
|
|
5310
|
+
rmSync4(raw, { force: true });
|
|
5311
|
+
throw e;
|
|
4930
5312
|
}
|
|
4931
5313
|
renameSync2(landedAt, dest);
|
|
4932
5314
|
if (landedAt !== raw) {
|
|
4933
5315
|
if (opts.keepCompressed === true)
|
|
4934
5316
|
renameSync2(raw, `${dest}.zst`);
|
|
4935
5317
|
else
|
|
4936
|
-
|
|
5318
|
+
rmSync4(raw, { force: true });
|
|
4937
5319
|
}
|
|
4938
|
-
return { from: pub.name, sha256:
|
|
5320
|
+
return { from: pub.name, sha256: got.sha256, bytes: statSync2(dest).size, verified };
|
|
4939
5321
|
}
|
|
4940
5322
|
async function openRelease2(spec = "pinned", opts = {}) {
|
|
4941
5323
|
const root = cacheRoot2(opts);
|
|
4942
5324
|
const tag = await resolveTag2(spec, opts);
|
|
4943
5325
|
const dir = tagDir(root, tag);
|
|
4944
|
-
const manifestPath =
|
|
4945
|
-
if (!
|
|
5326
|
+
const manifestPath = join4(dir, "MANIFEST.json");
|
|
5327
|
+
if (!existsSync5(manifestPath)) {
|
|
4946
5328
|
const remote = opts.remote ?? defaultRemote();
|
|
4947
5329
|
const repo = requireRepo2(opts);
|
|
4948
5330
|
const listed = await remote.assets(repo, tag);
|
|
4949
5331
|
const pub = listed.find((a) => a.name === "MANIFEST.json");
|
|
4950
5332
|
if (pub === undefined)
|
|
4951
5333
|
throw new Error(`archive-client: ${tag} publishes no MANIFEST.json`);
|
|
4952
|
-
|
|
4953
|
-
|
|
4954
|
-
|
|
4955
|
-
|
|
4956
|
-
|
|
5334
|
+
mkdirSync3(dir, { recursive: true });
|
|
5335
|
+
const landed = await fetchAsset({
|
|
5336
|
+
remote,
|
|
5337
|
+
repo,
|
|
5338
|
+
tag,
|
|
5339
|
+
pub,
|
|
5340
|
+
plan: planAsset2("MANIFEST.json", null),
|
|
5341
|
+
dest: manifestPath,
|
|
5342
|
+
opts,
|
|
5343
|
+
log: () => {},
|
|
5344
|
+
...opts.progress !== undefined ? { progress: opts.progress } : {}
|
|
5345
|
+
});
|
|
4957
5346
|
writeJsonAtomic2(receiptPath(dir), {
|
|
4958
5347
|
tag,
|
|
4959
5348
|
source: repo,
|
|
4960
5349
|
files: { "MANIFEST.json": landed },
|
|
4961
5350
|
published: listed.map((a) => a.name).sort()
|
|
4962
5351
|
});
|
|
4963
|
-
dropIfEmpty(part);
|
|
4964
5352
|
}
|
|
4965
5353
|
const manifest = JSON.parse(readFileSync3(manifestPath, "utf8"));
|
|
4966
5354
|
const rel = new Release2(tag, root, manifest, opts);
|
|
@@ -4969,8 +5357,8 @@ async function openRelease2(spec = "pinned", opts = {}) {
|
|
|
4969
5357
|
}
|
|
4970
5358
|
function openCached2(tag, opts = {}) {
|
|
4971
5359
|
const root = cacheRoot2(opts);
|
|
4972
|
-
const p =
|
|
4973
|
-
if (!
|
|
5360
|
+
const p = join4(tagDir(root, tag), "MANIFEST.json");
|
|
5361
|
+
if (!existsSync5(p))
|
|
4974
5362
|
return null;
|
|
4975
5363
|
return new Release2(tag, root, JSON.parse(readFileSync3(p, "utf8")), opts);
|
|
4976
5364
|
}
|
|
@@ -4982,14 +5370,14 @@ function checkKeep(keep) {
|
|
|
4982
5370
|
return keep;
|
|
4983
5371
|
}
|
|
4984
5372
|
function duBytes(p) {
|
|
4985
|
-
if (!
|
|
5373
|
+
if (!existsSync6(p))
|
|
4986
5374
|
return 0;
|
|
4987
5375
|
const s = statSync3(p);
|
|
4988
5376
|
if (!s.isDirectory())
|
|
4989
5377
|
return s.size;
|
|
4990
5378
|
let n = 0;
|
|
4991
5379
|
for (const e of readdirSync3(p))
|
|
4992
|
-
n += duBytes(
|
|
5380
|
+
n += duBytes(join5(p, e));
|
|
4993
5381
|
return n;
|
|
4994
5382
|
}
|
|
4995
5383
|
|
|
@@ -5001,9 +5389,9 @@ class ReleaseManager2 {
|
|
|
5001
5389
|
this.root = cacheRoot2(opts);
|
|
5002
5390
|
}
|
|
5003
5391
|
cachedTags() {
|
|
5004
|
-
if (!
|
|
5392
|
+
if (!existsSync6(this.root))
|
|
5005
5393
|
return [];
|
|
5006
|
-
return readdirSync3(this.root).filter((t) => parseArchiveTag2(t) !== null &&
|
|
5394
|
+
return readdirSync3(this.root).filter((t) => parseArchiveTag2(t) !== null && existsSync6(join5(this.root, t, "MANIFEST.json"))).sort(compareTagsNewestFirst2);
|
|
5007
5395
|
}
|
|
5008
5396
|
cached(tag) {
|
|
5009
5397
|
const r = openCached2(tag, this.opts);
|
|
@@ -5043,10 +5431,14 @@ class ReleaseManager2 {
|
|
|
5043
5431
|
async use(spec, o = {}) {
|
|
5044
5432
|
const keep = o.prune === false ? null : this.keepCount(o.keep);
|
|
5045
5433
|
const was = readPin2(this.root)?.tag ?? null;
|
|
5046
|
-
const rel = await
|
|
5434
|
+
const rel = await openRelease2(spec, { ...this.opts, assets: [] });
|
|
5047
5435
|
const tag = rel.tag;
|
|
5048
|
-
|
|
5049
|
-
|
|
5436
|
+
let built;
|
|
5437
|
+
try {
|
|
5438
|
+
built = await this.buildAsLanded(rel, [], o.assets ?? ["results"], false);
|
|
5439
|
+
} finally {
|
|
5440
|
+
rel.close();
|
|
5441
|
+
}
|
|
5050
5442
|
const pin = await this.pin(tag);
|
|
5051
5443
|
if (keep === null)
|
|
5052
5444
|
return { tag, was, pin, built, pruned: null };
|
|
@@ -5080,22 +5472,22 @@ class ReleaseManager2 {
|
|
|
5080
5472
|
async add(dir, tag) {
|
|
5081
5473
|
if (parseArchiveTag2(tag) === null)
|
|
5082
5474
|
throw new Error(`add: "${tag}" is not a release tag`);
|
|
5083
|
-
const mf =
|
|
5084
|
-
if (!
|
|
5475
|
+
const mf = join5(dir, "MANIFEST.json");
|
|
5476
|
+
if (!existsSync6(mf))
|
|
5085
5477
|
throw new Error(`add: ${dir} holds no MANIFEST.json`);
|
|
5086
5478
|
const dest = tagDir(this.root, tag);
|
|
5087
|
-
if (
|
|
5479
|
+
if (existsSync6(join5(dest, "MANIFEST.json")))
|
|
5088
5480
|
throw new Error(`add: ${tag} is already in the cache (${dest}); \`rm ${tag}\` first`);
|
|
5089
5481
|
const manifest = JSON.parse(readFileSync4(mf, "utf8"));
|
|
5090
|
-
const part =
|
|
5482
|
+
const part = join5(dest, ".part");
|
|
5091
5483
|
mkdirSync4(part, { recursive: true });
|
|
5092
5484
|
const receipt = { tag, source: `local:${dir}`, files: {} };
|
|
5093
5485
|
const log = this.opts.log ?? (() => {});
|
|
5094
5486
|
for (const name of readdirSync3(dir).sort()) {
|
|
5095
|
-
const src =
|
|
5487
|
+
const src = join5(dir, name);
|
|
5096
5488
|
if (!statSync3(src).isFile())
|
|
5097
5489
|
continue;
|
|
5098
|
-
const raw =
|
|
5490
|
+
const raw = join5(part, name);
|
|
5099
5491
|
try {
|
|
5100
5492
|
linkSync(src, raw);
|
|
5101
5493
|
} catch {
|
|
@@ -5105,10 +5497,10 @@ class ReleaseManager2 {
|
|
|
5105
5497
|
const side = Object.entries(manifest.sideFiles ?? {}).find(([k, f]) => (f.file ?? `${k}.zst`) === name)?.[0];
|
|
5106
5498
|
const asked = base !== name && isDatabase(base) ? base : side ?? name;
|
|
5107
5499
|
const plan = planAsset2(asked, manifest);
|
|
5108
|
-
const landed = await land(raw, { name, digest: null }, plan,
|
|
5500
|
+
const landed = await land(raw, { name, digest: null }, plan, join5(dest, plan.local), this.opts, log);
|
|
5109
5501
|
receipt.files[plan.local] = landed;
|
|
5110
5502
|
}
|
|
5111
|
-
|
|
5503
|
+
rmSync5(part, { recursive: true, force: true });
|
|
5112
5504
|
writeJsonAtomic2(receiptPath(dest), receipt);
|
|
5113
5505
|
const rel = openCached2(tag, this.opts);
|
|
5114
5506
|
if (rel === null)
|
|
@@ -5117,20 +5509,51 @@ class ReleaseManager2 {
|
|
|
5117
5509
|
}
|
|
5118
5510
|
async index(spec, names = [], o = {}) {
|
|
5119
5511
|
const rel = await openRelease2(spec, { ...this.opts, assets: [] });
|
|
5512
|
+
try {
|
|
5513
|
+
return { tag: rel.tag, built: await this.buildAsLanded(rel, names, [], o.force ?? false) };
|
|
5514
|
+
} finally {
|
|
5515
|
+
rel.close();
|
|
5516
|
+
}
|
|
5517
|
+
}
|
|
5518
|
+
async buildAsLanded(rel, names, extra, force) {
|
|
5120
5519
|
const want = names.length > 0 ? names : rel.builders().map((b) => b.name);
|
|
5121
|
-
const
|
|
5122
|
-
for (const name of want) {
|
|
5520
|
+
const builders = want.map((name) => {
|
|
5123
5521
|
const b = rel.builders().find((x) => x.name === name);
|
|
5124
5522
|
if (b === undefined)
|
|
5125
5523
|
throw new Error(`index: no index "${name}"`);
|
|
5126
|
-
|
|
5127
|
-
|
|
5128
|
-
|
|
5129
|
-
|
|
5130
|
-
|
|
5524
|
+
return b;
|
|
5525
|
+
});
|
|
5526
|
+
const assets = [...new Set([...extra, ...builders.flatMap((b) => b.inputs)])];
|
|
5527
|
+
const f = rel.fetching(assets);
|
|
5528
|
+
f.all.catch(() => {});
|
|
5529
|
+
const waiting = new Map(builders.map((b) => [b.name, Promise.all(b.inputs.map((a) => f.each(a))).then(() => b.name)]));
|
|
5530
|
+
for (const p of waiting.values())
|
|
5531
|
+
p.catch(() => {});
|
|
5532
|
+
const paths = new Map;
|
|
5533
|
+
while (waiting.size > 0) {
|
|
5534
|
+
let name;
|
|
5535
|
+
try {
|
|
5536
|
+
name = await Promise.race(waiting.values());
|
|
5537
|
+
} catch {
|
|
5538
|
+
await f.all;
|
|
5539
|
+
throw new Error("archive-client: a fetch failed and said nothing");
|
|
5540
|
+
}
|
|
5541
|
+
waiting.delete(name);
|
|
5542
|
+
const b = builders.find((x) => x.name === name);
|
|
5543
|
+
try {
|
|
5544
|
+
paths.set(name, await ensureIndex2(rel, b, {
|
|
5545
|
+
force,
|
|
5546
|
+
...this.opts.log !== undefined ? { log: this.opts.log } : {},
|
|
5547
|
+
...this.opts.progress !== undefined ? { progress: this.opts.progress } : {}
|
|
5548
|
+
}));
|
|
5549
|
+
} catch (e) {
|
|
5550
|
+
f.cancel();
|
|
5551
|
+
await f.all.catch(() => {});
|
|
5552
|
+
throw e;
|
|
5553
|
+
}
|
|
5131
5554
|
}
|
|
5132
|
-
|
|
5133
|
-
return {
|
|
5555
|
+
await f.all;
|
|
5556
|
+
return want.map((name) => ({ name, path: paths.get(name) }));
|
|
5134
5557
|
}
|
|
5135
5558
|
plan(dryRun) {
|
|
5136
5559
|
return { dryRun, remove: [], keep: [], bytes: 0 };
|
|
@@ -5145,7 +5568,7 @@ class ReleaseManager2 {
|
|
|
5145
5568
|
p.bytes = p.remove.reduce((n, r) => n + r.bytes, 0);
|
|
5146
5569
|
if (!p.dryRun)
|
|
5147
5570
|
for (const r of p.remove)
|
|
5148
|
-
|
|
5571
|
+
rmSync5(r.path, { recursive: true, force: true });
|
|
5149
5572
|
return p;
|
|
5150
5573
|
}
|
|
5151
5574
|
prune(o = {}) {
|
|
@@ -5167,7 +5590,7 @@ class ReleaseManager2 {
|
|
|
5167
5590
|
}
|
|
5168
5591
|
remove(tag, o = {}) {
|
|
5169
5592
|
const path = tagDir(this.root, tag);
|
|
5170
|
-
if (!
|
|
5593
|
+
if (!existsSync6(path))
|
|
5171
5594
|
throw new Error(`${tag} is not in the cache`);
|
|
5172
5595
|
if (readPin2(this.root)?.tag === tag)
|
|
5173
5596
|
throw new Error(`${tag} is pinned: \`unpin\` first`);
|
|
@@ -5179,7 +5602,7 @@ class ReleaseManager2 {
|
|
|
5179
5602
|
const p = this.plan(o.dryRun ?? false);
|
|
5180
5603
|
for (const t of tag === undefined ? this.cachedTags() : [tag]) {
|
|
5181
5604
|
const path = indexDir({ dir: tagDir(this.root, t) });
|
|
5182
|
-
if (
|
|
5605
|
+
if (existsSync6(path))
|
|
5183
5606
|
this.planRemove(p, path, `${t}'s indexes`);
|
|
5184
5607
|
}
|
|
5185
5608
|
return this.apply(p);
|
|
@@ -5188,8 +5611,8 @@ class ReleaseManager2 {
|
|
|
5188
5611
|
const rel = this.cached(tag);
|
|
5189
5612
|
const bad = [];
|
|
5190
5613
|
for (const [name, f] of Object.entries(rel.receipt().files)) {
|
|
5191
|
-
const p =
|
|
5192
|
-
const got =
|
|
5614
|
+
const p = join5(rel.dir, name);
|
|
5615
|
+
const got = existsSync6(p) ? await sha256File2(p) : "missing";
|
|
5193
5616
|
if (got !== f.sha256)
|
|
5194
5617
|
bad.push({ file: name, want: f.sha256, got });
|
|
5195
5618
|
}
|
|
@@ -5201,11 +5624,11 @@ function releases2(opts = {}) {
|
|
|
5201
5624
|
}
|
|
5202
5625
|
|
|
5203
5626
|
var LOCAL_DIR2 = "local";
|
|
5204
|
-
var localRoot2 = (root) =>
|
|
5627
|
+
var localRoot2 = (root) => join6(root, LOCAL_DIR2);
|
|
5205
5628
|
var newerThan2 = (a, b) => a.cut > b.cut || a.cut === b.cut && a.fraction > b.fraction;
|
|
5206
5629
|
function readManifest(dir) {
|
|
5207
|
-
const p =
|
|
5208
|
-
if (!
|
|
5630
|
+
const p = join6(dir, "MANIFEST.json");
|
|
5631
|
+
if (!existsSync7(p))
|
|
5209
5632
|
return null;
|
|
5210
5633
|
try {
|
|
5211
5634
|
const v = JSON.parse(readFileSync5(p, "utf8"));
|
|
@@ -5227,8 +5650,8 @@ function incomplete2(dir) {
|
|
|
5227
5650
|
const d = dbs[name];
|
|
5228
5651
|
if (d === undefined)
|
|
5229
5652
|
continue;
|
|
5230
|
-
const p =
|
|
5231
|
-
if (!
|
|
5653
|
+
const p = join6(dir, d.file ?? `${name}.sqlite`);
|
|
5654
|
+
if (!existsSync7(p))
|
|
5232
5655
|
return `no ${d.file ?? `${name}.sqlite`}`;
|
|
5233
5656
|
if (d.bytes !== undefined && statSync4(p).size !== d.bytes)
|
|
5234
5657
|
return `${d.file ?? `${name}.sqlite`} is ${statSync4(p).size} bytes, the manifest says ${d.bytes}`;
|
|
@@ -5261,18 +5684,18 @@ function pickBuild2(builds, base) {
|
|
|
5261
5684
|
return best;
|
|
5262
5685
|
}
|
|
5263
5686
|
function buildTag2(p) {
|
|
5264
|
-
const at = typeof p.manifest.emittedAt === "string" && !Number.isNaN(Date.parse(p.manifest.emittedAt)) ? new Date(p.manifest.emittedAt) : statSync4(
|
|
5687
|
+
const at = typeof p.manifest.emittedAt === "string" && !Number.isNaN(Date.parse(p.manifest.emittedAt)) ? new Date(p.manifest.emittedAt) : statSync4(join6(p.build.dir, "MANIFEST.json")).mtime;
|
|
5265
5688
|
const date = at.toISOString().slice(0, 10).replaceAll("-", ".");
|
|
5266
5689
|
return renderArchiveTag2({ date, cut: p.version.cut, fraction: p.version.fraction });
|
|
5267
5690
|
}
|
|
5268
5691
|
function buildFiles(dir) {
|
|
5269
|
-
return readdirSync4(dir).filter((n) => statSync4(
|
|
5692
|
+
return readdirSync4(dir).filter((n) => statSync4(join6(dir, n)).isFile()).filter((n) => {
|
|
5270
5693
|
const m = /^(.*)\.sqlite(\.zst)?$/.exec(n);
|
|
5271
5694
|
return m === null || isDatabase(m[1]);
|
|
5272
5695
|
}).sort();
|
|
5273
5696
|
}
|
|
5274
5697
|
function lacks(b, manifest, inputs) {
|
|
5275
|
-
return inputs.filter((a) => !planAsset2(a, manifest).published.some((n) =>
|
|
5698
|
+
return inputs.filter((a) => !planAsset2(a, manifest).published.some((n) => existsSync7(join6(b.dir, n))));
|
|
5276
5699
|
}
|
|
5277
5700
|
function buildRemote(label) {
|
|
5278
5701
|
const refuse = async () => {
|
|
@@ -5289,7 +5712,7 @@ async function openBuild2(root, p, opts) {
|
|
|
5289
5712
|
const o = buildOptions(root, p.build.label, opts);
|
|
5290
5713
|
const local = localRoot2(root);
|
|
5291
5714
|
const tag = buildTag2(p);
|
|
5292
|
-
const want = sha256Of(
|
|
5715
|
+
const want = sha256Of(join6(p.build.dir, "MANIFEST.json"));
|
|
5293
5716
|
const current = () => {
|
|
5294
5717
|
const rel = openCached2(tag, o);
|
|
5295
5718
|
return rel !== null && rel.receipt().files["MANIFEST.json"]?.sha256 === want ? rel : null;
|
|
@@ -5298,33 +5721,33 @@ async function openBuild2(root, p, opts) {
|
|
|
5298
5721
|
if (have !== null)
|
|
5299
5722
|
return have;
|
|
5300
5723
|
mkdirSync5(local, { recursive: true });
|
|
5301
|
-
const work =
|
|
5724
|
+
const work = join6(local, scratchName("add"));
|
|
5302
5725
|
try {
|
|
5303
|
-
const stage =
|
|
5726
|
+
const stage = join6(work, "stage");
|
|
5304
5727
|
mkdirSync5(stage, { recursive: true });
|
|
5305
5728
|
for (const f of buildFiles(p.build.dir)) {
|
|
5306
5729
|
try {
|
|
5307
|
-
linkSync2(
|
|
5730
|
+
linkSync2(join6(p.build.dir, f), join6(stage, f));
|
|
5308
5731
|
} catch {
|
|
5309
|
-
copyFileSync2(
|
|
5732
|
+
copyFileSync2(join6(p.build.dir, f), join6(stage, f));
|
|
5310
5733
|
}
|
|
5311
5734
|
}
|
|
5312
|
-
const added = await new ReleaseManager2({ ...o, cache:
|
|
5735
|
+
const added = await new ReleaseManager2({ ...o, cache: join6(work, "cache") }).add(stage, tag);
|
|
5313
5736
|
const receipt = added.receipt();
|
|
5314
5737
|
receipt.source = `local:${p.build.dir}`;
|
|
5315
5738
|
writeJsonAtomic2(receiptPath(added.dir), receipt);
|
|
5316
5739
|
added.close();
|
|
5317
|
-
const dest =
|
|
5318
|
-
if (
|
|
5740
|
+
const dest = join6(local, tag);
|
|
5741
|
+
if (existsSync7(dest) && current() === null) {
|
|
5319
5742
|
try {
|
|
5320
|
-
renameSync3(dest,
|
|
5743
|
+
renameSync3(dest, join6(local, scratchName("old")));
|
|
5321
5744
|
} catch {}
|
|
5322
5745
|
}
|
|
5323
5746
|
try {
|
|
5324
5747
|
renameSync3(added.dir, dest);
|
|
5325
5748
|
} catch {}
|
|
5326
5749
|
} finally {
|
|
5327
|
-
|
|
5750
|
+
rmSync6(work, { recursive: true, force: true });
|
|
5328
5751
|
}
|
|
5329
5752
|
const rel = current();
|
|
5330
5753
|
if (rel === null)
|
|
@@ -5341,7 +5764,7 @@ function alive(pid) {
|
|
|
5341
5764
|
}
|
|
5342
5765
|
function sweepBuilds2(root, keep) {
|
|
5343
5766
|
const local = localRoot2(root);
|
|
5344
|
-
if (!
|
|
5767
|
+
if (!existsSync7(local))
|
|
5345
5768
|
return [];
|
|
5346
5769
|
const gone = [];
|
|
5347
5770
|
for (const name of readdirSync4(local).sort()) {
|
|
@@ -5352,23 +5775,23 @@ function sweepBuilds2(root, keep) {
|
|
|
5352
5775
|
continue;
|
|
5353
5776
|
if (m === null && parseArchiveTag2(name) === null)
|
|
5354
5777
|
continue;
|
|
5355
|
-
const path =
|
|
5778
|
+
const path = join6(local, name);
|
|
5356
5779
|
try {
|
|
5357
|
-
|
|
5780
|
+
rmSync6(path, { recursive: true, force: true });
|
|
5358
5781
|
gone.push(path);
|
|
5359
5782
|
} catch {}
|
|
5360
5783
|
}
|
|
5361
5784
|
return gone;
|
|
5362
5785
|
}
|
|
5363
5786
|
|
|
5364
|
-
import { existsSync as
|
|
5365
|
-
import { join as
|
|
5787
|
+
import { existsSync as existsSync8, readFileSync as readFileSync6 } from "fs";
|
|
5788
|
+
import { join as join7 } from "path";
|
|
5366
5789
|
var LATEST_CHECK_MS2 = 60 * 60 * 1000;
|
|
5367
5790
|
var LATEST_CHECK_BUDGET_MS2 = 1500;
|
|
5368
|
-
var latestCheckPath2 = (root) =>
|
|
5791
|
+
var latestCheckPath2 = (root) => join7(root, "LATEST.json");
|
|
5369
5792
|
function readLatestCheck2(root) {
|
|
5370
5793
|
const p = latestCheckPath2(root);
|
|
5371
|
-
if (!
|
|
5794
|
+
if (!existsSync8(p))
|
|
5372
5795
|
return null;
|
|
5373
5796
|
try {
|
|
5374
5797
|
const v = JSON.parse(readFileSync6(p, "utf8"));
|
|
@@ -5450,4 +5873,126 @@ function staleWarning2(answered, latest, floor, tags = [], command = "idq") {
|
|
|
5450
5873
|
return `answered from ${answered}. ${floorTag ?? `cut ${floor.cut}`} is required${why}: run \`${command} use ${floor.cut}\` to get it`;
|
|
5451
5874
|
}
|
|
5452
5875
|
|
|
5453
|
-
|
|
5876
|
+
var PROGRESS_MODES2 = ["tty", "plain", "none"];
|
|
5877
|
+
function progressMode2(flag, env, isTTY) {
|
|
5878
|
+
for (const [v, from] of [
|
|
5879
|
+
[flag, "--progress"],
|
|
5880
|
+
[env.IDQ_PROGRESS, "IDQ_PROGRESS"]
|
|
5881
|
+
]) {
|
|
5882
|
+
if (v === undefined || v === "")
|
|
5883
|
+
continue;
|
|
5884
|
+
if (PROGRESS_MODES2.includes(v))
|
|
5885
|
+
return v;
|
|
5886
|
+
throw new Error(`${from} ${v}: expected ${PROGRESS_MODES2.join(", ")}`);
|
|
5887
|
+
}
|
|
5888
|
+
return isTTY ? "tty" : "plain";
|
|
5889
|
+
}
|
|
5890
|
+
var ENDS = new Set(["done", "failed", "cancelled"]);
|
|
5891
|
+
function bytesText(n) {
|
|
5892
|
+
if (n >= 1e9)
|
|
5893
|
+
return `${(n / 1e9).toFixed(2)} GB`;
|
|
5894
|
+
if (n >= 1e6)
|
|
5895
|
+
return `${(n / 1e6).toFixed(1)} MB`;
|
|
5896
|
+
if (n >= 1000)
|
|
5897
|
+
return `${(n / 1000).toFixed(1)} kB`;
|
|
5898
|
+
return `${n} B`;
|
|
5899
|
+
}
|
|
5900
|
+
var secs = (ms) => `${(ms / 1000).toFixed(1)} s`;
|
|
5901
|
+
function plainLine(e, started) {
|
|
5902
|
+
const what = e.kind === "index" ? `${e.tag} index ${e.item}` : `${e.tag} ${e.item}`;
|
|
5903
|
+
if (e.state === "done") {
|
|
5904
|
+
if (e.kind === "index")
|
|
5905
|
+
return `done ${what} in ${secs(e.ms)}`;
|
|
5906
|
+
const size = e.unpacked > 0 ? `${bytesText(e.bytes)} -> ${bytesText(e.unpacked)}` : bytesText(e.bytes);
|
|
5907
|
+
return `done ${what} ${size} in ${secs(e.ms)}`;
|
|
5908
|
+
}
|
|
5909
|
+
if (e.state === "failed")
|
|
5910
|
+
return `failed ${what} after ${secs(e.ms)}: ${e.error ?? "failed"}`;
|
|
5911
|
+
if (e.state === "cancelled")
|
|
5912
|
+
return `cancelled ${what} after ${secs(e.ms)}`;
|
|
5913
|
+
if (started)
|
|
5914
|
+
return null;
|
|
5915
|
+
return `start ${what}${e.total !== null ? ` (${bytesText(e.total)})` : ""}`;
|
|
5916
|
+
}
|
|
5917
|
+
function ttyLine(e, width) {
|
|
5918
|
+
const name = e.kind === "index" ? `index ${e.item}` : e.item;
|
|
5919
|
+
let amount = "";
|
|
5920
|
+
if (e.kind === "asset") {
|
|
5921
|
+
amount = e.total !== null ? `${bytesText(e.bytes)} / ${bytesText(e.total)}` : bytesText(e.bytes);
|
|
5922
|
+
if (e.unpacked > 0)
|
|
5923
|
+
amount += ` unpacked ${bytesText(e.unpacked)}`;
|
|
5924
|
+
if (!ENDS.has(e.state) && e.ms > 0)
|
|
5925
|
+
amount += ` ${bytesText(e.bytes * 1000 / e.ms)}/s`;
|
|
5926
|
+
}
|
|
5927
|
+
const state = e.state === "failed" ? `failed: ${e.error ?? ""}` : ENDS.has(e.state) ? `${e.state} in ${secs(e.ms)}` : e.state;
|
|
5928
|
+
const line = `${name.padEnd(34)} ${amount.padEnd(44)} ${state}`;
|
|
5929
|
+
return line.length > width ? line.slice(0, Math.max(0, width - 1)) : line;
|
|
5930
|
+
}
|
|
5931
|
+
function progressRenderer2(mode, io) {
|
|
5932
|
+
if (mode === "none")
|
|
5933
|
+
return { mode, sink: () => {}, close: () => {} };
|
|
5934
|
+
if (mode === "plain") {
|
|
5935
|
+
const started = new Set;
|
|
5936
|
+
return {
|
|
5937
|
+
mode,
|
|
5938
|
+
sink: (e) => {
|
|
5939
|
+
const key = `${e.tag}\x00${e.kind}\x00${e.item}`;
|
|
5940
|
+
const l = plainLine(e, started.has(key));
|
|
5941
|
+
started.add(key);
|
|
5942
|
+
if (ENDS.has(e.state))
|
|
5943
|
+
started.delete(key);
|
|
5944
|
+
if (l !== null)
|
|
5945
|
+
io.line(l);
|
|
5946
|
+
},
|
|
5947
|
+
close: () => {}
|
|
5948
|
+
};
|
|
5949
|
+
}
|
|
5950
|
+
const write = io.write ?? ((s) => process.stderr.write(s));
|
|
5951
|
+
const columns = io.columns ?? (() => process.stderr.columns ?? 0);
|
|
5952
|
+
const interval = io.intervalMs ?? 100;
|
|
5953
|
+
const items = new Map;
|
|
5954
|
+
let drawn = 0;
|
|
5955
|
+
let timer = null;
|
|
5956
|
+
let dirty = false;
|
|
5957
|
+
const draw = () => {
|
|
5958
|
+
timer = null;
|
|
5959
|
+
if (!dirty)
|
|
5960
|
+
return;
|
|
5961
|
+
dirty = false;
|
|
5962
|
+
const width = Math.max(40, columns() || 120);
|
|
5963
|
+
let out = drawn > 0 ? `\x1B[${drawn}A` : "";
|
|
5964
|
+
for (const e of items.values())
|
|
5965
|
+
out += `\r\x1B[2K${ttyLine(e, width)}
|
|
5966
|
+
`;
|
|
5967
|
+
drawn = items.size;
|
|
5968
|
+
write(out);
|
|
5969
|
+
if ([...items.values()].every((e) => ENDS.has(e.state))) {
|
|
5970
|
+
items.clear();
|
|
5971
|
+
drawn = 0;
|
|
5972
|
+
}
|
|
5973
|
+
};
|
|
5974
|
+
return {
|
|
5975
|
+
mode,
|
|
5976
|
+
sink: (e) => {
|
|
5977
|
+
const key = `${e.tag}\x00${e.kind}\x00${e.item}`;
|
|
5978
|
+
const moved = items.get(key)?.state !== e.state;
|
|
5979
|
+
items.set(key, e);
|
|
5980
|
+
dirty = true;
|
|
5981
|
+
if (moved) {
|
|
5982
|
+
if (timer !== null)
|
|
5983
|
+
clearTimeout(timer);
|
|
5984
|
+
draw();
|
|
5985
|
+
} else if (timer === null) {
|
|
5986
|
+
timer = setTimeout(draw, interval);
|
|
5987
|
+
timer.unref?.();
|
|
5988
|
+
}
|
|
5989
|
+
},
|
|
5990
|
+
close: () => {
|
|
5991
|
+
if (timer !== null)
|
|
5992
|
+
clearTimeout(timer);
|
|
5993
|
+
draw();
|
|
5994
|
+
}
|
|
5995
|
+
};
|
|
5996
|
+
}
|
|
5997
|
+
|
|
5998
|
+
export { DATABASES2, planAsset2, sha256File2, unzstdTo2, streamTo2, NAME_LEVELS2, nameLevel2, TabFinder2, PanelFinder2, FeisPanelFinder2, columnsOf2, FEATURES2, capabilityOf2, needsRelease2, rosterOf2, NO_CALENDAR2, NO_UPCOMING2, FeisFinder2, ADVANCEMENT_DANCES2, entryRank2, FEIS_LOCAL_ID_FLOOR2, ResultFinder2, MajorsFinder2, LINEAGE_FILE2, linksOfLineage2, releaseSchoolGraph2, readsSpanishNames2, SchoolResolver2, initialsOnly2, nameOrderOf, DancerResolver2, readPopulation2, syllabusSourceOf2, canonicalOf2, CompResolver2, StaffFinder2, TeamFinder2, EventFinder2, defaultBuilders2, indexPath2, indexKey2, isCurrent2, buildIndex2, ensureIndex2, listIndexes2, readPin2, CALENDAR_STAGES2, CALENDAR_STAGE_STATES2, CALENDAR_TERMINALS2, CalendarFinder2, youngest2, dancerResolverOf2, RegistrationResolver2, ghRemote2, httpsRemote2, fallbackRemote2, setRemote2, defaultRemote, FIELD_STATES2, DEPARTURE_REASONS2, UpcomingFinder2, resolveTag2, DEFAULT_CONCURRENCY2, Release2, openRelease2, openCached2, DEFAULT_KEEP2, ReleaseManager2, releases2, LOCAL_DIR2, localRoot2, newerThan2, incomplete2, releaseVersion2, pickBuild2, buildTag2, lacks, openBuild2, sweepBuilds2, LATEST_CHECK_MS2, LATEST_CHECK_BUDGET_MS2, latestCheckPath2, readLatestCheck2, latestCheck2, within, staleWarning2, PROGRESS_MODES2, progressMode2, progressRenderer2 };
|