@finbheara/archive-client 0.40.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 +48 -0
- package/dist/chunks/{index-bs939h4v.js → index-0xdcttqf.js} +694 -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 +2 -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";
|
|
@@ -503,6 +552,18 @@ var FEATURES2 = [
|
|
|
503
552
|
since: 345,
|
|
504
553
|
calls: "the same on a row of results.stream and results.entries (scope majors)"
|
|
505
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
|
+
},
|
|
506
567
|
{
|
|
507
568
|
feature: "majors.tabs",
|
|
508
569
|
needs: ["majors:tabs.address", "majors:tabs.true_round"],
|
|
@@ -1414,7 +1475,7 @@ function streamSql(q, shape = {}) {
|
|
|
1414
1475
|
if (q.primary === true)
|
|
1415
1476
|
where.push("(r.primary_source IS NULL OR r.primary_source = '')");
|
|
1416
1477
|
return {
|
|
1417
|
-
sql: `SELECT ${COLUMNS}${shape.competition === true ? COMPETITION_COLUMNS : ""}${shape.competitorsPrinted === true ? ", c.competitors_printed AS competitors_printed" : ""} 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`,
|
|
1418
1479
|
args
|
|
1419
1480
|
};
|
|
1420
1481
|
}
|
|
@@ -1495,7 +1556,7 @@ var COLUMNS2 = `r.address AS address, r.competition AS competition, c.event AS e
|
|
|
1495
1556
|
r.place_printed AS place_printed, c.competitors AS competitors, r.name_printed AS name_printed,
|
|
1496
1557
|
r.dancer_id AS dancer_id, r.school_printed AS school_printed, r.school_id AS school_id,
|
|
1497
1558
|
r.yob AS yob, r.qualifier AS qualifier`;
|
|
1498
|
-
function queryResults(rel, db, q) {
|
|
1559
|
+
function queryResults(rel, db, q, basis = false) {
|
|
1499
1560
|
const where = [];
|
|
1500
1561
|
const args = [];
|
|
1501
1562
|
if (q.dancer_id !== undefined) {
|
|
@@ -1521,7 +1582,7 @@ function queryResults(rel, db, q) {
|
|
|
1521
1582
|
args.push(q.year);
|
|
1522
1583
|
}
|
|
1523
1584
|
const asked = q.limit ?? 500;
|
|
1524
|
-
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
|
|
1525
1586
|
WHERE ${where.join(" AND ")}
|
|
1526
1587
|
ORDER BY c.year, c.event, r.competition, r.place IS NULL, r.place, r.address`).all(...args);
|
|
1527
1588
|
return {
|
|
@@ -1544,7 +1605,8 @@ class ResultFinder2 {
|
|
|
1544
1605
|
async query(q) {
|
|
1545
1606
|
await this.rel.fetch("results");
|
|
1546
1607
|
await this.rel.require("results.level");
|
|
1547
|
-
|
|
1608
|
+
const basis = (await this.rel.capability("results.attach_basis")).state === "full";
|
|
1609
|
+
return queryResults(this.rel, "results", q, basis);
|
|
1548
1610
|
}
|
|
1549
1611
|
async dancerLevel(q) {
|
|
1550
1612
|
await this.rel.fetch("results");
|
|
@@ -1557,7 +1619,8 @@ class ResultFinder2 {
|
|
|
1557
1619
|
await this.rel.require(db === "majors" ? "majors.stream" : "results.stream");
|
|
1558
1620
|
const competition = (await this.rel.capability(`${db}.stream.competition`)).state === "full";
|
|
1559
1621
|
const competitorsPrinted = (await this.rel.capability(`${db}.stream.competitors_printed`)).state === "full";
|
|
1560
|
-
|
|
1622
|
+
const attachBasis = (await this.rel.capability(`${db}.attach_basis`)).state === "full";
|
|
1623
|
+
return { db: this.rel.db(db), shape: { competition, competitorsPrinted, attachBasis } };
|
|
1561
1624
|
}
|
|
1562
1625
|
async* stream(q) {
|
|
1563
1626
|
const { db, shape } = await this.streamDb(q);
|
|
@@ -1621,7 +1684,8 @@ class MajorsFinder2 {
|
|
|
1621
1684
|
async results(q) {
|
|
1622
1685
|
await this.db();
|
|
1623
1686
|
await this.rel.require("results.level");
|
|
1624
|
-
|
|
1687
|
+
const basis = (await this.rel.capability("majors.attach_basis")).state === "full";
|
|
1688
|
+
return queryResults(this.rel, "majors", q, basis);
|
|
1625
1689
|
}
|
|
1626
1690
|
}
|
|
1627
1691
|
|
|
@@ -3306,8 +3370,8 @@ class CompResolver2 {
|
|
|
3306
3370
|
|
|
3307
3371
|
import { Database } from "bun:sqlite";
|
|
3308
3372
|
import { createHash as createHash2 } from "crypto";
|
|
3309
|
-
import { existsSync, mkdirSync, readdirSync, renameSync, rmSync, statSync } from "fs";
|
|
3310
|
-
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";
|
|
3311
3375
|
|
|
3312
3376
|
import {
|
|
3313
3377
|
createIndex as createIndex3,
|
|
@@ -3547,6 +3611,34 @@ class StaffFinder2 {
|
|
|
3547
3611
|
}
|
|
3548
3612
|
}
|
|
3549
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
|
+
|
|
3550
3642
|
import { parquetMetadata, parquetReadObjects, parquetSchema } from "hyparquet";
|
|
3551
3643
|
var COMPRESSORS = {
|
|
3552
3644
|
ZSTD: (input) => new Uint8Array(Bun.zstdDecompressSync(input))
|
|
@@ -3793,8 +3885,8 @@ class EventFinder2 {
|
|
|
3793
3885
|
function defaultBuilders2() {
|
|
3794
3886
|
return [schoolsIndex, feisesIndex, compsIndex, dancersIndex, staffIndex, teamsIndex, eventsIndex];
|
|
3795
3887
|
}
|
|
3796
|
-
var indexDir = (rel) =>
|
|
3797
|
-
var indexPath2 = (rel, name) =>
|
|
3888
|
+
var indexDir = (rel) => join2(rel.dir, "index");
|
|
3889
|
+
var indexPath2 = (rel, name) => join2(indexDir(rel), `${name}.sqlite`);
|
|
3798
3890
|
function indexKey2(rel, b) {
|
|
3799
3891
|
const files = rel.receipt().files;
|
|
3800
3892
|
const parts = [b.name, b.version, ...b.keyParts?.(rel) ?? []];
|
|
@@ -3805,7 +3897,7 @@ function indexKey2(rel, b) {
|
|
|
3805
3897
|
return createHash2("sha256").update(parts.join("\x00")).digest("hex");
|
|
3806
3898
|
}
|
|
3807
3899
|
function keyOf(path) {
|
|
3808
|
-
if (!
|
|
3900
|
+
if (!existsSync2(path))
|
|
3809
3901
|
return null;
|
|
3810
3902
|
try {
|
|
3811
3903
|
const db = new Database(path, { readonly: true });
|
|
@@ -3830,8 +3922,9 @@ function buildIndex2(rel, b, o) {
|
|
|
3830
3922
|
if (b.load !== undefined && o.loaded === undefined)
|
|
3831
3923
|
throw new Error(`archive-client: index "${b.name}" reads its inputs first: build it with ensureIndex (or rel.index)`);
|
|
3832
3924
|
mkdirSync(indexDir(rel), { recursive: true });
|
|
3925
|
+
sweepDead(indexDir(rel), BUILDING_FILE);
|
|
3833
3926
|
const tmp = `${out}.building.${process.pid}`;
|
|
3834
|
-
|
|
3927
|
+
rmSync2(tmp, { force: true });
|
|
3835
3928
|
const t0 = performance.now();
|
|
3836
3929
|
const db = new Database(tmp, { create: true });
|
|
3837
3930
|
try {
|
|
@@ -3847,7 +3940,7 @@ function buildIndex2(rel, b, o) {
|
|
|
3847
3940
|
db.run("VACUUM");
|
|
3848
3941
|
} catch (e) {
|
|
3849
3942
|
db.close();
|
|
3850
|
-
|
|
3943
|
+
rmSync2(tmp, { force: true });
|
|
3851
3944
|
throw e;
|
|
3852
3945
|
}
|
|
3853
3946
|
db.close();
|
|
@@ -3858,16 +3951,42 @@ function buildIndex2(rel, b, o) {
|
|
|
3858
3951
|
async function ensureIndex2(rel, b, o) {
|
|
3859
3952
|
if (!o.force && isCurrent2(rel, b))
|
|
3860
3953
|
return indexPath2(rel, b.name);
|
|
3861
|
-
const
|
|
3862
|
-
|
|
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
|
+
}
|
|
3863
3982
|
}
|
|
3864
3983
|
function listIndexes2(rel) {
|
|
3865
3984
|
const dir = indexDir(rel);
|
|
3866
|
-
if (!
|
|
3985
|
+
if (!existsSync2(dir))
|
|
3867
3986
|
return [];
|
|
3868
|
-
return
|
|
3987
|
+
return readdirSync2(dir).filter((f) => f.endsWith(".sqlite")).sort().map((f) => {
|
|
3869
3988
|
const name = f.slice(0, -".sqlite".length);
|
|
3870
|
-
const path =
|
|
3989
|
+
const path = join2(dir, f);
|
|
3871
3990
|
const b = rel.builders().find((x) => x.name === name);
|
|
3872
3991
|
return {
|
|
3873
3992
|
name,
|
|
@@ -3881,35 +4000,35 @@ function listIndexes2(rel) {
|
|
|
3881
4000
|
import { createHash as createHash3 } from "crypto";
|
|
3882
4001
|
import {
|
|
3883
4002
|
copyFileSync as copyFileSync2,
|
|
3884
|
-
existsSync as
|
|
4003
|
+
existsSync as existsSync7,
|
|
3885
4004
|
linkSync as linkSync2,
|
|
3886
4005
|
mkdirSync as mkdirSync5,
|
|
3887
4006
|
readFileSync as readFileSync5,
|
|
3888
4007
|
readdirSync as readdirSync4,
|
|
3889
4008
|
renameSync as renameSync3,
|
|
3890
|
-
rmSync as
|
|
4009
|
+
rmSync as rmSync6,
|
|
3891
4010
|
statSync as statSync4
|
|
3892
4011
|
} from "fs";
|
|
3893
|
-
import { join as
|
|
4012
|
+
import { join as join6 } from "path";
|
|
3894
4013
|
|
|
3895
4014
|
import {
|
|
3896
4015
|
copyFileSync,
|
|
3897
|
-
existsSync as
|
|
4016
|
+
existsSync as existsSync6,
|
|
3898
4017
|
linkSync,
|
|
3899
4018
|
mkdirSync as mkdirSync4,
|
|
3900
4019
|
readFileSync as readFileSync4,
|
|
3901
4020
|
readdirSync as readdirSync3,
|
|
3902
|
-
rmSync as
|
|
4021
|
+
rmSync as rmSync5,
|
|
3903
4022
|
statSync as statSync3
|
|
3904
4023
|
} from "fs";
|
|
3905
|
-
import { isAbsolute, join as
|
|
4024
|
+
import { isAbsolute, join as join5, relative, resolve, sep } from "path";
|
|
3906
4025
|
|
|
3907
|
-
import { existsSync as
|
|
3908
|
-
import { join as
|
|
3909
|
-
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");
|
|
3910
4029
|
function readPin2(root) {
|
|
3911
4030
|
const p = pinPath(root);
|
|
3912
|
-
if (!
|
|
4031
|
+
if (!existsSync3(p))
|
|
3913
4032
|
return null;
|
|
3914
4033
|
try {
|
|
3915
4034
|
const v = JSON.parse(readFileSync2(p, "utf8"));
|
|
@@ -3925,23 +4044,24 @@ function writePin(root, pin) {
|
|
|
3925
4044
|
}
|
|
3926
4045
|
function clearPin(root) {
|
|
3927
4046
|
const p = pinPath(root);
|
|
3928
|
-
if (!
|
|
4047
|
+
if (!existsSync3(p))
|
|
3929
4048
|
return false;
|
|
3930
|
-
|
|
4049
|
+
rmSync3(p);
|
|
3931
4050
|
return true;
|
|
3932
4051
|
}
|
|
3933
4052
|
|
|
3934
4053
|
import { Database as Database2 } from "bun:sqlite";
|
|
4054
|
+
import { randomBytes } from "crypto";
|
|
3935
4055
|
import {
|
|
3936
|
-
|
|
4056
|
+
createReadStream as createReadStream2,
|
|
4057
|
+
existsSync as existsSync5,
|
|
3937
4058
|
mkdirSync as mkdirSync3,
|
|
3938
4059
|
readFileSync as readFileSync3,
|
|
3939
|
-
readdirSync as readdirSync2,
|
|
3940
4060
|
renameSync as renameSync2,
|
|
3941
|
-
rmSync as
|
|
4061
|
+
rmSync as rmSync4,
|
|
3942
4062
|
statSync as statSync2
|
|
3943
4063
|
} from "fs";
|
|
3944
|
-
import { join as
|
|
4064
|
+
import { basename, dirname as dirname2, join as join4 } from "path";
|
|
3945
4065
|
|
|
3946
4066
|
var CALENDAR_STAGES2 = [
|
|
3947
4067
|
"announced",
|
|
@@ -4277,9 +4397,9 @@ class RegistrationResolver2 {
|
|
|
4277
4397
|
}
|
|
4278
4398
|
|
|
4279
4399
|
import { spawn } from "child_process";
|
|
4280
|
-
import { createWriteStream as createWriteStream2, existsSync as
|
|
4400
|
+
import { createWriteStream as createWriteStream2, existsSync as existsSync4, mkdirSync as mkdirSync2 } from "fs";
|
|
4281
4401
|
import { dirname } from "path";
|
|
4282
|
-
import { Readable } from "stream";
|
|
4402
|
+
import { Readable as Readable2 } from "stream";
|
|
4283
4403
|
import { pipeline as pipeline2 } from "stream/promises";
|
|
4284
4404
|
function run(cmd, args) {
|
|
4285
4405
|
return new Promise((resolve) => {
|
|
@@ -4345,8 +4465,59 @@ var ghRemote2 = {
|
|
|
4345
4465
|
dest,
|
|
4346
4466
|
"--clobber"
|
|
4347
4467
|
]);
|
|
4348
|
-
if (r.status !== 0 || !
|
|
4468
|
+
if (r.status !== 0 || !existsSync4(dest))
|
|
4349
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
|
+
}();
|
|
4350
4521
|
}
|
|
4351
4522
|
};
|
|
4352
4523
|
var API = "https://api.github.com";
|
|
@@ -4399,7 +4570,19 @@ function httpsRemote2(env = process.env, fetchImpl = fetch) {
|
|
|
4399
4570
|
if (!res.ok || res.body === null)
|
|
4400
4571
|
throw new Error(`GET ${a.url}: ${res.status} ${res.statusText}`);
|
|
4401
4572
|
mkdirSync2(dirname(dest), { recursive: true });
|
|
4402
|
-
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));
|
|
4403
4586
|
}
|
|
4404
4587
|
};
|
|
4405
4588
|
}
|
|
@@ -4419,7 +4602,10 @@ function fallbackRemote2(first, second) {
|
|
|
4419
4602
|
name: `${first.name}+${second.name}`,
|
|
4420
4603
|
tags: both((r) => r.tags.bind(r)),
|
|
4421
4604
|
assets: both((r) => r.assets.bind(r)),
|
|
4422
|
-
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
|
+
} : {}
|
|
4423
4609
|
};
|
|
4424
4610
|
}
|
|
4425
4611
|
var installed = null;
|
|
@@ -4658,12 +4844,12 @@ class UpcomingFinder2 {
|
|
|
4658
4844
|
function tagDir(root, tag) {
|
|
4659
4845
|
if (parseArchiveTag2(tag) === null)
|
|
4660
4846
|
throw new Error(`archive-client: "${tag}" is not a release tag (archive-YYYY.MM.DD[-N[.k]])`);
|
|
4661
|
-
return
|
|
4847
|
+
return join4(root, tag);
|
|
4662
4848
|
}
|
|
4663
|
-
var receiptPath = (dir) =>
|
|
4849
|
+
var receiptPath = (dir) => join4(dir, ".client.json");
|
|
4664
4850
|
function readReceipt(dir) {
|
|
4665
4851
|
const p = receiptPath(dir);
|
|
4666
|
-
if (!
|
|
4852
|
+
if (!existsSync5(p))
|
|
4667
4853
|
return null;
|
|
4668
4854
|
try {
|
|
4669
4855
|
return JSON.parse(readFileSync3(p, "utf8"));
|
|
@@ -4680,7 +4866,7 @@ async function resolveTag2(spec, opts = {}) {
|
|
|
4680
4866
|
const pin = readPin2(root);
|
|
4681
4867
|
return resolveTag2(pin === null ? { kind: "latest" } : { kind: "tag", tag: pin.tag }, opts);
|
|
4682
4868
|
}
|
|
4683
|
-
if (s.kind === "tag" &&
|
|
4869
|
+
if (s.kind === "tag" && existsSync5(join4(tagDir(root, s.tag), "MANIFEST.json")))
|
|
4684
4870
|
return s.tag;
|
|
4685
4871
|
const repo = requireRepo2(opts);
|
|
4686
4872
|
const tags = await (opts.remote ?? defaultRemote()).tags(repo);
|
|
@@ -4689,9 +4875,30 @@ async function resolveTag2(spec, opts = {}) {
|
|
|
4689
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"}`);
|
|
4690
4876
|
return tag;
|
|
4691
4877
|
}
|
|
4692
|
-
|
|
4693
|
-
|
|
4694
|
-
|
|
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
|
+
}
|
|
4695
4902
|
}
|
|
4696
4903
|
|
|
4697
4904
|
class Release2 {
|
|
@@ -4718,7 +4925,7 @@ class Release2 {
|
|
|
4718
4925
|
feisPanels;
|
|
4719
4926
|
opts;
|
|
4720
4927
|
handles = new Map;
|
|
4721
|
-
|
|
4928
|
+
listing = null;
|
|
4722
4929
|
constructor(tag, root, manifest, opts) {
|
|
4723
4930
|
const t = parseArchiveTag2(tag);
|
|
4724
4931
|
this.tag = tag;
|
|
@@ -4751,19 +4958,65 @@ class Release2 {
|
|
|
4751
4958
|
return planAsset2(asset, this.manifest);
|
|
4752
4959
|
}
|
|
4753
4960
|
has(asset) {
|
|
4754
|
-
return
|
|
4961
|
+
return existsSync5(join4(this.dir, this.plan(asset).local));
|
|
4755
4962
|
}
|
|
4756
4963
|
path(asset) {
|
|
4757
|
-
const p =
|
|
4758
|
-
if (!
|
|
4964
|
+
const p = join4(this.dir, this.plan(asset).local);
|
|
4965
|
+
if (!existsSync5(p))
|
|
4759
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}\`)`);
|
|
4760
4967
|
return p;
|
|
4761
4968
|
}
|
|
4762
4969
|
async fetch(...assets) {
|
|
4763
|
-
|
|
4764
|
-
|
|
4765
|
-
|
|
4766
|
-
|
|
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
|
+
};
|
|
4767
5020
|
}
|
|
4768
5021
|
async publishes(asset) {
|
|
4769
5022
|
const plan = this.plan(asset);
|
|
@@ -4819,25 +5072,29 @@ class Release2 {
|
|
|
4819
5072
|
if (c.state === "missing")
|
|
4820
5073
|
throw needsRelease2(this.tag, c);
|
|
4821
5074
|
}
|
|
4822
|
-
|
|
4823
|
-
|
|
4824
|
-
|
|
4825
|
-
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)) {
|
|
4826
5079
|
const r = this.receipt();
|
|
4827
|
-
r.published =
|
|
5080
|
+
r.published = assets.map((a) => a.name).sort();
|
|
4828
5081
|
writeJsonAtomic2(receiptPath(this.dir), r);
|
|
4829
5082
|
}
|
|
4830
|
-
|
|
4831
|
-
|
|
5083
|
+
return assets;
|
|
5084
|
+
})();
|
|
5085
|
+
this.listing.catch(() => {
|
|
5086
|
+
this.listing = null;
|
|
5087
|
+
});
|
|
5088
|
+
return this.listing;
|
|
4832
5089
|
}
|
|
4833
|
-
async fetchOne(asset) {
|
|
5090
|
+
async fetchOne(asset, signal) {
|
|
4834
5091
|
const plan = this.plan(asset);
|
|
4835
|
-
const dest =
|
|
5092
|
+
const dest = join4(this.dir, plan.local);
|
|
4836
5093
|
const have = this.receipt().files[plan.local];
|
|
4837
|
-
if (
|
|
5094
|
+
if (existsSync5(dest) && have !== undefined)
|
|
4838
5095
|
return have;
|
|
4839
5096
|
const log = this.opts.log ?? (() => {});
|
|
4840
|
-
if (
|
|
5097
|
+
if (existsSync5(dest) && plan.contentSha256 !== null) {
|
|
4841
5098
|
const sha = await sha256File2(dest);
|
|
4842
5099
|
if (sha === plan.contentSha256) {
|
|
4843
5100
|
const adopted = {
|
|
@@ -4846,7 +5103,8 @@ class Release2 {
|
|
|
4846
5103
|
bytes: statSync2(dest).size,
|
|
4847
5104
|
verified: ["manifest"]
|
|
4848
5105
|
};
|
|
4849
|
-
|
|
5106
|
+
if (this.opts.progress === undefined)
|
|
5107
|
+
log(`adopt ${this.tag} ${plan.local} (the manifest's sha256)`);
|
|
4850
5108
|
this.record(plan.local, adopted);
|
|
4851
5109
|
return adopted;
|
|
4852
5110
|
}
|
|
@@ -4855,14 +5113,24 @@ class Release2 {
|
|
|
4855
5113
|
const pub = plan.published.map((n) => listed.find((a) => a.name === n)).find((a) => a !== undefined);
|
|
4856
5114
|
if (pub === undefined)
|
|
4857
5115
|
throw new Error(`archive-client: ${this.tag} publishes no ${plan.published.join(" or ")} (asked: ${asset})`);
|
|
4858
|
-
|
|
4859
|
-
|
|
4860
|
-
|
|
4861
|
-
|
|
4862
|
-
|
|
4863
|
-
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
|
+
});
|
|
4864
5133
|
this.record(plan.local, landed);
|
|
4865
|
-
dropIfEmpty(part);
|
|
4866
5134
|
return landed;
|
|
4867
5135
|
}
|
|
4868
5136
|
record(local, f) {
|
|
@@ -4891,7 +5159,11 @@ class Release2 {
|
|
|
4891
5159
|
if (FEATURES2.some((f) => f.feature === b.name))
|
|
4892
5160
|
await this.require(b.name);
|
|
4893
5161
|
await this.fetch(...b.inputs);
|
|
4894
|
-
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
|
+
});
|
|
4895
5167
|
}
|
|
4896
5168
|
async indexDb(name) {
|
|
4897
5169
|
const key = `index:${name}`;
|
|
@@ -4911,69 +5183,172 @@ class Release2 {
|
|
|
4911
5183
|
this.close();
|
|
4912
5184
|
}
|
|
4913
5185
|
}
|
|
4914
|
-
|
|
5186
|
+
function verifyLanded(got, pub, plan, strict) {
|
|
4915
5187
|
const verified = [];
|
|
4916
5188
|
const host = sha256OfDigest(pub.digest);
|
|
4917
5189
|
if (host !== null) {
|
|
4918
|
-
|
|
4919
|
-
|
|
4920
|
-
rmSync3(raw, { force: true });
|
|
4921
|
-
throw new Error(`archive-client: ${pub.name} does not match the host's digest (sha256 ${got}, the host says ${host})`);
|
|
4922
|
-
}
|
|
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})`);
|
|
4923
5192
|
verified.push("host-digest");
|
|
4924
5193
|
}
|
|
4925
|
-
let landedAt = raw;
|
|
4926
|
-
let sha;
|
|
4927
|
-
if (plan.decompress && pub.name.endsWith(".zst")) {
|
|
4928
|
-
landedAt = `${raw.slice(0, -".zst".length)}.plain`;
|
|
4929
|
-
log(`decompress ${pub.name}`);
|
|
4930
|
-
sha = await unzstdTo2(raw, landedAt);
|
|
4931
|
-
} else
|
|
4932
|
-
sha = await sha256File2(raw);
|
|
4933
5194
|
if (plan.contentSha256 !== null) {
|
|
4934
|
-
if (
|
|
4935
|
-
|
|
4936
|
-
throw new Error(`archive-client: ${plan.local} does not match MANIFEST.json (sha256 ${sha}, the manifest says ${plan.contentSha256})`);
|
|
4937
|
-
}
|
|
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})`);
|
|
4938
5197
|
verified.push("manifest");
|
|
4939
5198
|
}
|
|
4940
|
-
if (verified.length === 0 &&
|
|
4941
|
-
rmSync3(landedAt, { force: true });
|
|
5199
|
+
if (verified.length === 0 && strict)
|
|
4942
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;
|
|
4943
5312
|
}
|
|
4944
5313
|
renameSync2(landedAt, dest);
|
|
4945
5314
|
if (landedAt !== raw) {
|
|
4946
5315
|
if (opts.keepCompressed === true)
|
|
4947
5316
|
renameSync2(raw, `${dest}.zst`);
|
|
4948
5317
|
else
|
|
4949
|
-
|
|
5318
|
+
rmSync4(raw, { force: true });
|
|
4950
5319
|
}
|
|
4951
|
-
return { from: pub.name, sha256:
|
|
5320
|
+
return { from: pub.name, sha256: got.sha256, bytes: statSync2(dest).size, verified };
|
|
4952
5321
|
}
|
|
4953
5322
|
async function openRelease2(spec = "pinned", opts = {}) {
|
|
4954
5323
|
const root = cacheRoot2(opts);
|
|
4955
5324
|
const tag = await resolveTag2(spec, opts);
|
|
4956
5325
|
const dir = tagDir(root, tag);
|
|
4957
|
-
const manifestPath =
|
|
4958
|
-
if (!
|
|
5326
|
+
const manifestPath = join4(dir, "MANIFEST.json");
|
|
5327
|
+
if (!existsSync5(manifestPath)) {
|
|
4959
5328
|
const remote = opts.remote ?? defaultRemote();
|
|
4960
5329
|
const repo = requireRepo2(opts);
|
|
4961
5330
|
const listed = await remote.assets(repo, tag);
|
|
4962
5331
|
const pub = listed.find((a) => a.name === "MANIFEST.json");
|
|
4963
5332
|
if (pub === undefined)
|
|
4964
5333
|
throw new Error(`archive-client: ${tag} publishes no MANIFEST.json`);
|
|
4965
|
-
|
|
4966
|
-
|
|
4967
|
-
|
|
4968
|
-
|
|
4969
|
-
|
|
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
|
+
});
|
|
4970
5346
|
writeJsonAtomic2(receiptPath(dir), {
|
|
4971
5347
|
tag,
|
|
4972
5348
|
source: repo,
|
|
4973
5349
|
files: { "MANIFEST.json": landed },
|
|
4974
5350
|
published: listed.map((a) => a.name).sort()
|
|
4975
5351
|
});
|
|
4976
|
-
dropIfEmpty(part);
|
|
4977
5352
|
}
|
|
4978
5353
|
const manifest = JSON.parse(readFileSync3(manifestPath, "utf8"));
|
|
4979
5354
|
const rel = new Release2(tag, root, manifest, opts);
|
|
@@ -4982,8 +5357,8 @@ async function openRelease2(spec = "pinned", opts = {}) {
|
|
|
4982
5357
|
}
|
|
4983
5358
|
function openCached2(tag, opts = {}) {
|
|
4984
5359
|
const root = cacheRoot2(opts);
|
|
4985
|
-
const p =
|
|
4986
|
-
if (!
|
|
5360
|
+
const p = join4(tagDir(root, tag), "MANIFEST.json");
|
|
5361
|
+
if (!existsSync5(p))
|
|
4987
5362
|
return null;
|
|
4988
5363
|
return new Release2(tag, root, JSON.parse(readFileSync3(p, "utf8")), opts);
|
|
4989
5364
|
}
|
|
@@ -4995,14 +5370,14 @@ function checkKeep(keep) {
|
|
|
4995
5370
|
return keep;
|
|
4996
5371
|
}
|
|
4997
5372
|
function duBytes(p) {
|
|
4998
|
-
if (!
|
|
5373
|
+
if (!existsSync6(p))
|
|
4999
5374
|
return 0;
|
|
5000
5375
|
const s = statSync3(p);
|
|
5001
5376
|
if (!s.isDirectory())
|
|
5002
5377
|
return s.size;
|
|
5003
5378
|
let n = 0;
|
|
5004
5379
|
for (const e of readdirSync3(p))
|
|
5005
|
-
n += duBytes(
|
|
5380
|
+
n += duBytes(join5(p, e));
|
|
5006
5381
|
return n;
|
|
5007
5382
|
}
|
|
5008
5383
|
|
|
@@ -5014,9 +5389,9 @@ class ReleaseManager2 {
|
|
|
5014
5389
|
this.root = cacheRoot2(opts);
|
|
5015
5390
|
}
|
|
5016
5391
|
cachedTags() {
|
|
5017
|
-
if (!
|
|
5392
|
+
if (!existsSync6(this.root))
|
|
5018
5393
|
return [];
|
|
5019
|
-
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);
|
|
5020
5395
|
}
|
|
5021
5396
|
cached(tag) {
|
|
5022
5397
|
const r = openCached2(tag, this.opts);
|
|
@@ -5056,10 +5431,14 @@ class ReleaseManager2 {
|
|
|
5056
5431
|
async use(spec, o = {}) {
|
|
5057
5432
|
const keep = o.prune === false ? null : this.keepCount(o.keep);
|
|
5058
5433
|
const was = readPin2(this.root)?.tag ?? null;
|
|
5059
|
-
const rel = await
|
|
5434
|
+
const rel = await openRelease2(spec, { ...this.opts, assets: [] });
|
|
5060
5435
|
const tag = rel.tag;
|
|
5061
|
-
|
|
5062
|
-
|
|
5436
|
+
let built;
|
|
5437
|
+
try {
|
|
5438
|
+
built = await this.buildAsLanded(rel, [], o.assets ?? ["results"], false);
|
|
5439
|
+
} finally {
|
|
5440
|
+
rel.close();
|
|
5441
|
+
}
|
|
5063
5442
|
const pin = await this.pin(tag);
|
|
5064
5443
|
if (keep === null)
|
|
5065
5444
|
return { tag, was, pin, built, pruned: null };
|
|
@@ -5093,22 +5472,22 @@ class ReleaseManager2 {
|
|
|
5093
5472
|
async add(dir, tag) {
|
|
5094
5473
|
if (parseArchiveTag2(tag) === null)
|
|
5095
5474
|
throw new Error(`add: "${tag}" is not a release tag`);
|
|
5096
|
-
const mf =
|
|
5097
|
-
if (!
|
|
5475
|
+
const mf = join5(dir, "MANIFEST.json");
|
|
5476
|
+
if (!existsSync6(mf))
|
|
5098
5477
|
throw new Error(`add: ${dir} holds no MANIFEST.json`);
|
|
5099
5478
|
const dest = tagDir(this.root, tag);
|
|
5100
|
-
if (
|
|
5479
|
+
if (existsSync6(join5(dest, "MANIFEST.json")))
|
|
5101
5480
|
throw new Error(`add: ${tag} is already in the cache (${dest}); \`rm ${tag}\` first`);
|
|
5102
5481
|
const manifest = JSON.parse(readFileSync4(mf, "utf8"));
|
|
5103
|
-
const part =
|
|
5482
|
+
const part = join5(dest, ".part");
|
|
5104
5483
|
mkdirSync4(part, { recursive: true });
|
|
5105
5484
|
const receipt = { tag, source: `local:${dir}`, files: {} };
|
|
5106
5485
|
const log = this.opts.log ?? (() => {});
|
|
5107
5486
|
for (const name of readdirSync3(dir).sort()) {
|
|
5108
|
-
const src =
|
|
5487
|
+
const src = join5(dir, name);
|
|
5109
5488
|
if (!statSync3(src).isFile())
|
|
5110
5489
|
continue;
|
|
5111
|
-
const raw =
|
|
5490
|
+
const raw = join5(part, name);
|
|
5112
5491
|
try {
|
|
5113
5492
|
linkSync(src, raw);
|
|
5114
5493
|
} catch {
|
|
@@ -5118,10 +5497,10 @@ class ReleaseManager2 {
|
|
|
5118
5497
|
const side = Object.entries(manifest.sideFiles ?? {}).find(([k, f]) => (f.file ?? `${k}.zst`) === name)?.[0];
|
|
5119
5498
|
const asked = base !== name && isDatabase(base) ? base : side ?? name;
|
|
5120
5499
|
const plan = planAsset2(asked, manifest);
|
|
5121
|
-
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);
|
|
5122
5501
|
receipt.files[plan.local] = landed;
|
|
5123
5502
|
}
|
|
5124
|
-
|
|
5503
|
+
rmSync5(part, { recursive: true, force: true });
|
|
5125
5504
|
writeJsonAtomic2(receiptPath(dest), receipt);
|
|
5126
5505
|
const rel = openCached2(tag, this.opts);
|
|
5127
5506
|
if (rel === null)
|
|
@@ -5130,20 +5509,51 @@ class ReleaseManager2 {
|
|
|
5130
5509
|
}
|
|
5131
5510
|
async index(spec, names = [], o = {}) {
|
|
5132
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) {
|
|
5133
5519
|
const want = names.length > 0 ? names : rel.builders().map((b) => b.name);
|
|
5134
|
-
const
|
|
5135
|
-
for (const name of want) {
|
|
5520
|
+
const builders = want.map((name) => {
|
|
5136
5521
|
const b = rel.builders().find((x) => x.name === name);
|
|
5137
5522
|
if (b === undefined)
|
|
5138
5523
|
throw new Error(`index: no index "${name}"`);
|
|
5139
|
-
|
|
5140
|
-
|
|
5141
|
-
|
|
5142
|
-
|
|
5143
|
-
|
|
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
|
+
}
|
|
5144
5554
|
}
|
|
5145
|
-
|
|
5146
|
-
return {
|
|
5555
|
+
await f.all;
|
|
5556
|
+
return want.map((name) => ({ name, path: paths.get(name) }));
|
|
5147
5557
|
}
|
|
5148
5558
|
plan(dryRun) {
|
|
5149
5559
|
return { dryRun, remove: [], keep: [], bytes: 0 };
|
|
@@ -5158,7 +5568,7 @@ class ReleaseManager2 {
|
|
|
5158
5568
|
p.bytes = p.remove.reduce((n, r) => n + r.bytes, 0);
|
|
5159
5569
|
if (!p.dryRun)
|
|
5160
5570
|
for (const r of p.remove)
|
|
5161
|
-
|
|
5571
|
+
rmSync5(r.path, { recursive: true, force: true });
|
|
5162
5572
|
return p;
|
|
5163
5573
|
}
|
|
5164
5574
|
prune(o = {}) {
|
|
@@ -5180,7 +5590,7 @@ class ReleaseManager2 {
|
|
|
5180
5590
|
}
|
|
5181
5591
|
remove(tag, o = {}) {
|
|
5182
5592
|
const path = tagDir(this.root, tag);
|
|
5183
|
-
if (!
|
|
5593
|
+
if (!existsSync6(path))
|
|
5184
5594
|
throw new Error(`${tag} is not in the cache`);
|
|
5185
5595
|
if (readPin2(this.root)?.tag === tag)
|
|
5186
5596
|
throw new Error(`${tag} is pinned: \`unpin\` first`);
|
|
@@ -5192,7 +5602,7 @@ class ReleaseManager2 {
|
|
|
5192
5602
|
const p = this.plan(o.dryRun ?? false);
|
|
5193
5603
|
for (const t of tag === undefined ? this.cachedTags() : [tag]) {
|
|
5194
5604
|
const path = indexDir({ dir: tagDir(this.root, t) });
|
|
5195
|
-
if (
|
|
5605
|
+
if (existsSync6(path))
|
|
5196
5606
|
this.planRemove(p, path, `${t}'s indexes`);
|
|
5197
5607
|
}
|
|
5198
5608
|
return this.apply(p);
|
|
@@ -5201,8 +5611,8 @@ class ReleaseManager2 {
|
|
|
5201
5611
|
const rel = this.cached(tag);
|
|
5202
5612
|
const bad = [];
|
|
5203
5613
|
for (const [name, f] of Object.entries(rel.receipt().files)) {
|
|
5204
|
-
const p =
|
|
5205
|
-
const got =
|
|
5614
|
+
const p = join5(rel.dir, name);
|
|
5615
|
+
const got = existsSync6(p) ? await sha256File2(p) : "missing";
|
|
5206
5616
|
if (got !== f.sha256)
|
|
5207
5617
|
bad.push({ file: name, want: f.sha256, got });
|
|
5208
5618
|
}
|
|
@@ -5214,11 +5624,11 @@ function releases2(opts = {}) {
|
|
|
5214
5624
|
}
|
|
5215
5625
|
|
|
5216
5626
|
var LOCAL_DIR2 = "local";
|
|
5217
|
-
var localRoot2 = (root) =>
|
|
5627
|
+
var localRoot2 = (root) => join6(root, LOCAL_DIR2);
|
|
5218
5628
|
var newerThan2 = (a, b) => a.cut > b.cut || a.cut === b.cut && a.fraction > b.fraction;
|
|
5219
5629
|
function readManifest(dir) {
|
|
5220
|
-
const p =
|
|
5221
|
-
if (!
|
|
5630
|
+
const p = join6(dir, "MANIFEST.json");
|
|
5631
|
+
if (!existsSync7(p))
|
|
5222
5632
|
return null;
|
|
5223
5633
|
try {
|
|
5224
5634
|
const v = JSON.parse(readFileSync5(p, "utf8"));
|
|
@@ -5240,8 +5650,8 @@ function incomplete2(dir) {
|
|
|
5240
5650
|
const d = dbs[name];
|
|
5241
5651
|
if (d === undefined)
|
|
5242
5652
|
continue;
|
|
5243
|
-
const p =
|
|
5244
|
-
if (!
|
|
5653
|
+
const p = join6(dir, d.file ?? `${name}.sqlite`);
|
|
5654
|
+
if (!existsSync7(p))
|
|
5245
5655
|
return `no ${d.file ?? `${name}.sqlite`}`;
|
|
5246
5656
|
if (d.bytes !== undefined && statSync4(p).size !== d.bytes)
|
|
5247
5657
|
return `${d.file ?? `${name}.sqlite`} is ${statSync4(p).size} bytes, the manifest says ${d.bytes}`;
|
|
@@ -5274,18 +5684,18 @@ function pickBuild2(builds, base) {
|
|
|
5274
5684
|
return best;
|
|
5275
5685
|
}
|
|
5276
5686
|
function buildTag2(p) {
|
|
5277
|
-
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;
|
|
5278
5688
|
const date = at.toISOString().slice(0, 10).replaceAll("-", ".");
|
|
5279
5689
|
return renderArchiveTag2({ date, cut: p.version.cut, fraction: p.version.fraction });
|
|
5280
5690
|
}
|
|
5281
5691
|
function buildFiles(dir) {
|
|
5282
|
-
return readdirSync4(dir).filter((n) => statSync4(
|
|
5692
|
+
return readdirSync4(dir).filter((n) => statSync4(join6(dir, n)).isFile()).filter((n) => {
|
|
5283
5693
|
const m = /^(.*)\.sqlite(\.zst)?$/.exec(n);
|
|
5284
5694
|
return m === null || isDatabase(m[1]);
|
|
5285
5695
|
}).sort();
|
|
5286
5696
|
}
|
|
5287
5697
|
function lacks(b, manifest, inputs) {
|
|
5288
|
-
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))));
|
|
5289
5699
|
}
|
|
5290
5700
|
function buildRemote(label) {
|
|
5291
5701
|
const refuse = async () => {
|
|
@@ -5302,7 +5712,7 @@ async function openBuild2(root, p, opts) {
|
|
|
5302
5712
|
const o = buildOptions(root, p.build.label, opts);
|
|
5303
5713
|
const local = localRoot2(root);
|
|
5304
5714
|
const tag = buildTag2(p);
|
|
5305
|
-
const want = sha256Of(
|
|
5715
|
+
const want = sha256Of(join6(p.build.dir, "MANIFEST.json"));
|
|
5306
5716
|
const current = () => {
|
|
5307
5717
|
const rel = openCached2(tag, o);
|
|
5308
5718
|
return rel !== null && rel.receipt().files["MANIFEST.json"]?.sha256 === want ? rel : null;
|
|
@@ -5311,33 +5721,33 @@ async function openBuild2(root, p, opts) {
|
|
|
5311
5721
|
if (have !== null)
|
|
5312
5722
|
return have;
|
|
5313
5723
|
mkdirSync5(local, { recursive: true });
|
|
5314
|
-
const work =
|
|
5724
|
+
const work = join6(local, scratchName("add"));
|
|
5315
5725
|
try {
|
|
5316
|
-
const stage =
|
|
5726
|
+
const stage = join6(work, "stage");
|
|
5317
5727
|
mkdirSync5(stage, { recursive: true });
|
|
5318
5728
|
for (const f of buildFiles(p.build.dir)) {
|
|
5319
5729
|
try {
|
|
5320
|
-
linkSync2(
|
|
5730
|
+
linkSync2(join6(p.build.dir, f), join6(stage, f));
|
|
5321
5731
|
} catch {
|
|
5322
|
-
copyFileSync2(
|
|
5732
|
+
copyFileSync2(join6(p.build.dir, f), join6(stage, f));
|
|
5323
5733
|
}
|
|
5324
5734
|
}
|
|
5325
|
-
const added = await new ReleaseManager2({ ...o, cache:
|
|
5735
|
+
const added = await new ReleaseManager2({ ...o, cache: join6(work, "cache") }).add(stage, tag);
|
|
5326
5736
|
const receipt = added.receipt();
|
|
5327
5737
|
receipt.source = `local:${p.build.dir}`;
|
|
5328
5738
|
writeJsonAtomic2(receiptPath(added.dir), receipt);
|
|
5329
5739
|
added.close();
|
|
5330
|
-
const dest =
|
|
5331
|
-
if (
|
|
5740
|
+
const dest = join6(local, tag);
|
|
5741
|
+
if (existsSync7(dest) && current() === null) {
|
|
5332
5742
|
try {
|
|
5333
|
-
renameSync3(dest,
|
|
5743
|
+
renameSync3(dest, join6(local, scratchName("old")));
|
|
5334
5744
|
} catch {}
|
|
5335
5745
|
}
|
|
5336
5746
|
try {
|
|
5337
5747
|
renameSync3(added.dir, dest);
|
|
5338
5748
|
} catch {}
|
|
5339
5749
|
} finally {
|
|
5340
|
-
|
|
5750
|
+
rmSync6(work, { recursive: true, force: true });
|
|
5341
5751
|
}
|
|
5342
5752
|
const rel = current();
|
|
5343
5753
|
if (rel === null)
|
|
@@ -5354,7 +5764,7 @@ function alive(pid) {
|
|
|
5354
5764
|
}
|
|
5355
5765
|
function sweepBuilds2(root, keep) {
|
|
5356
5766
|
const local = localRoot2(root);
|
|
5357
|
-
if (!
|
|
5767
|
+
if (!existsSync7(local))
|
|
5358
5768
|
return [];
|
|
5359
5769
|
const gone = [];
|
|
5360
5770
|
for (const name of readdirSync4(local).sort()) {
|
|
@@ -5365,23 +5775,23 @@ function sweepBuilds2(root, keep) {
|
|
|
5365
5775
|
continue;
|
|
5366
5776
|
if (m === null && parseArchiveTag2(name) === null)
|
|
5367
5777
|
continue;
|
|
5368
|
-
const path =
|
|
5778
|
+
const path = join6(local, name);
|
|
5369
5779
|
try {
|
|
5370
|
-
|
|
5780
|
+
rmSync6(path, { recursive: true, force: true });
|
|
5371
5781
|
gone.push(path);
|
|
5372
5782
|
} catch {}
|
|
5373
5783
|
}
|
|
5374
5784
|
return gone;
|
|
5375
5785
|
}
|
|
5376
5786
|
|
|
5377
|
-
import { existsSync as
|
|
5378
|
-
import { join as
|
|
5787
|
+
import { existsSync as existsSync8, readFileSync as readFileSync6 } from "fs";
|
|
5788
|
+
import { join as join7 } from "path";
|
|
5379
5789
|
var LATEST_CHECK_MS2 = 60 * 60 * 1000;
|
|
5380
5790
|
var LATEST_CHECK_BUDGET_MS2 = 1500;
|
|
5381
|
-
var latestCheckPath2 = (root) =>
|
|
5791
|
+
var latestCheckPath2 = (root) => join7(root, "LATEST.json");
|
|
5382
5792
|
function readLatestCheck2(root) {
|
|
5383
5793
|
const p = latestCheckPath2(root);
|
|
5384
|
-
if (!
|
|
5794
|
+
if (!existsSync8(p))
|
|
5385
5795
|
return null;
|
|
5386
5796
|
try {
|
|
5387
5797
|
const v = JSON.parse(readFileSync6(p, "utf8"));
|
|
@@ -5463,4 +5873,126 @@ function staleWarning2(answered, latest, floor, tags = [], command = "idq") {
|
|
|
5463
5873
|
return `answered from ${answered}. ${floorTag ?? `cut ${floor.cut}`} is required${why}: run \`${command} use ${floor.cut}\` to get it`;
|
|
5464
5874
|
}
|
|
5465
5875
|
|
|
5466
|
-
|
|
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 };
|