@finbheara/archive-client 0.40.0 → 0.43.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.
@@ -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"],
@@ -547,6 +608,21 @@ var FEATURES2 = [
547
608
  since: 245,
548
609
  calls: "dancers.years"
549
610
  },
611
+ {
612
+ feature: "dancers.records.birth_consistency",
613
+ needs: ["results:dancers.birth_consistency", "results:dancers.birth_consistency_rows"],
614
+ since: 345,
615
+ calls: "the `birth_consistency` and `birth_consistency_rows` a record of dancers.records carries (the percent of its readable rows that fit its birth window, and how many were readable): absent, never refused, on a release without them"
616
+ },
617
+ {
618
+ feature: "dancers.years.birth_consistency",
619
+ needs: [
620
+ "results:dancer_years.birth_consistency",
621
+ "results:dancer_years.birth_consistency_rows"
622
+ ],
623
+ since: 345,
624
+ calls: "the `birth_consistency` and `birth_consistency_rows` a dancer-year of dancers.years carries: absent, never refused, on a release without them"
625
+ },
550
626
  {
551
627
  feature: "dancers.years.printed",
552
628
  needs: ["results:dancer_years.printed_regions"],
@@ -1414,7 +1490,7 @@ function streamSql(q, shape = {}) {
1414
1490
  if (q.primary === true)
1415
1491
  where.push("(r.primary_source IS NULL OR r.primary_source = '')");
1416
1492
  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`,
1493
+ 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
1494
  args
1419
1495
  };
1420
1496
  }
@@ -1495,7 +1571,7 @@ var COLUMNS2 = `r.address AS address, r.competition AS competition, c.event AS e
1495
1571
  r.place_printed AS place_printed, c.competitors AS competitors, r.name_printed AS name_printed,
1496
1572
  r.dancer_id AS dancer_id, r.school_printed AS school_printed, r.school_id AS school_id,
1497
1573
  r.yob AS yob, r.qualifier AS qualifier`;
1498
- function queryResults(rel, db, q) {
1574
+ function queryResults(rel, db, q, basis = false) {
1499
1575
  const where = [];
1500
1576
  const args = [];
1501
1577
  if (q.dancer_id !== undefined) {
@@ -1521,7 +1597,7 @@ function queryResults(rel, db, q) {
1521
1597
  args.push(q.year);
1522
1598
  }
1523
1599
  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
1600
+ 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
1601
  WHERE ${where.join(" AND ")}
1526
1602
  ORDER BY c.year, c.event, r.competition, r.place IS NULL, r.place, r.address`).all(...args);
1527
1603
  return {
@@ -1544,7 +1620,8 @@ class ResultFinder2 {
1544
1620
  async query(q) {
1545
1621
  await this.rel.fetch("results");
1546
1622
  await this.rel.require("results.level");
1547
- return queryResults(this.rel, "results", q);
1623
+ const basis = (await this.rel.capability("results.attach_basis")).state === "full";
1624
+ return queryResults(this.rel, "results", q, basis);
1548
1625
  }
1549
1626
  async dancerLevel(q) {
1550
1627
  await this.rel.fetch("results");
@@ -1557,7 +1634,8 @@ class ResultFinder2 {
1557
1634
  await this.rel.require(db === "majors" ? "majors.stream" : "results.stream");
1558
1635
  const competition = (await this.rel.capability(`${db}.stream.competition`)).state === "full";
1559
1636
  const competitorsPrinted = (await this.rel.capability(`${db}.stream.competitors_printed`)).state === "full";
1560
- return { db: this.rel.db(db), shape: { competition, competitorsPrinted } };
1637
+ const attachBasis = (await this.rel.capability(`${db}.attach_basis`)).state === "full";
1638
+ return { db: this.rel.db(db), shape: { competition, competitorsPrinted, attachBasis } };
1561
1639
  }
1562
1640
  async* stream(q) {
1563
1641
  const { db, shape } = await this.streamDb(q);
@@ -1621,7 +1699,8 @@ class MajorsFinder2 {
1621
1699
  async results(q) {
1622
1700
  await this.db();
1623
1701
  await this.rel.require("results.level");
1624
- return queryResults(this.rel, "majors", q);
1702
+ const basis = (await this.rel.capability("majors.attach_basis")).state === "full";
1703
+ return queryResults(this.rel, "majors", q, basis);
1625
1704
  }
1626
1705
  }
1627
1706
 
@@ -2222,7 +2301,8 @@ class DancerResolver2 {
2222
2301
  await this.rel.require("dancers.records");
2223
2302
  const db = this.rel.db("results");
2224
2303
  const names = (await this.rel.capability("dancers.records.names")).state === "full";
2225
- const sql = `SELECT dancer_id, display, appearances, birth_lo, birth_hi, birth_source, birth_conflict, genders${names ? ", names_printed" : ""} FROM dancers`;
2304
+ const fit = (await this.rel.capability("dancers.records.birth_consistency")).state === "full";
2305
+ const sql = `SELECT dancer_id, display, appearances, birth_lo, birth_hi, birth_source, birth_conflict, genders${names ? ", names_printed" : ""}${fit ? ", birth_consistency, birth_consistency_rows" : ""} FROM dancers`;
2226
2306
  const rows = q.ids === undefined ? db.query(`${sql} ORDER BY dancer_id`).all() : idChunks(q.ids).flatMap((c) => db.query(`${sql} WHERE dancer_id IN (${c.map(() => "?").join(",")})`).all(...c));
2227
2307
  rows.sort((a, b) => a.dancer_id - b.dancer_id);
2228
2308
  return {
@@ -2241,7 +2321,8 @@ class DancerResolver2 {
2241
2321
  await this.rel.require("dancers.years");
2242
2322
  const db = this.rel.db("results");
2243
2323
  const printed = (await this.rel.capability("dancers.years.printed")).state === "full";
2244
- const sql = `SELECT dancer_id, year, region, region_source, nationals, nationals_source, geohash${printed ? ", printed_regions" : ""} FROM dancer_years`;
2324
+ const fit = (await this.rel.capability("dancers.years.birth_consistency")).state === "full";
2325
+ const sql = `SELECT dancer_id, year, region, region_source, nationals, nationals_source, geohash${printed ? ", printed_regions" : ""}${fit ? ", birth_consistency, birth_consistency_rows" : ""} FROM dancer_years`;
2245
2326
  const yearSql = q.year === undefined ? "" : " AND year = ?";
2246
2327
  const yearArgs = q.year === undefined ? [] : [q.year];
2247
2328
  const rows = q.ids === undefined ? db.query(`${sql} WHERE 1${yearSql} ORDER BY dancer_id, year`).all(...yearArgs) : idChunks(q.ids).flatMap((c) => db.query(`${sql} WHERE dancer_id IN (${c.map(() => "?").join(",")})${yearSql}`).all(...c, ...yearArgs));
@@ -3306,8 +3387,8 @@ class CompResolver2 {
3306
3387
 
3307
3388
  import { Database } from "bun:sqlite";
3308
3389
  import { createHash as createHash2 } from "crypto";
3309
- import { existsSync, mkdirSync, readdirSync, renameSync, rmSync, statSync } from "fs";
3310
- import { join } from "path";
3390
+ import { existsSync as existsSync2, mkdirSync, readdirSync as readdirSync2, renameSync, rmSync as rmSync2, statSync } from "fs";
3391
+ import { join as join2 } from "path";
3311
3392
 
3312
3393
  import {
3313
3394
  createIndex as createIndex3,
@@ -3547,6 +3628,34 @@ class StaffFinder2 {
3547
3628
  }
3548
3629
  }
3549
3630
 
3631
+ import { existsSync, readdirSync, rmSync } from "fs";
3632
+ import { join } from "path";
3633
+ function pidAlive(pid) {
3634
+ if (pid === process.pid)
3635
+ return true;
3636
+ try {
3637
+ process.kill(pid, 0);
3638
+ return true;
3639
+ } catch (e) {
3640
+ return e.code === "EPERM";
3641
+ }
3642
+ }
3643
+ var PART_FILE = /^\..+\.(\d+)-[0-9a-f]{8}\.part(?:\.zst|\.dl)?$/;
3644
+ var BUILDING_FILE = /^.+\.sqlite\.building\.(\d+)$/;
3645
+ function sweepDead(dir, re) {
3646
+ if (!existsSync(dir))
3647
+ return [];
3648
+ const gone = [];
3649
+ for (const f of readdirSync(dir)) {
3650
+ const m = re.exec(f);
3651
+ if (m === null || pidAlive(Number(m[1])))
3652
+ continue;
3653
+ rmSync(join(dir, f), { force: true });
3654
+ gone.push(f);
3655
+ }
3656
+ return gone.sort();
3657
+ }
3658
+
3550
3659
  import { parquetMetadata, parquetReadObjects, parquetSchema } from "hyparquet";
3551
3660
  var COMPRESSORS = {
3552
3661
  ZSTD: (input) => new Uint8Array(Bun.zstdDecompressSync(input))
@@ -3793,8 +3902,8 @@ class EventFinder2 {
3793
3902
  function defaultBuilders2() {
3794
3903
  return [schoolsIndex, feisesIndex, compsIndex, dancersIndex, staffIndex, teamsIndex, eventsIndex];
3795
3904
  }
3796
- var indexDir = (rel) => join(rel.dir, "index");
3797
- var indexPath2 = (rel, name) => join(indexDir(rel), `${name}.sqlite`);
3905
+ var indexDir = (rel) => join2(rel.dir, "index");
3906
+ var indexPath2 = (rel, name) => join2(indexDir(rel), `${name}.sqlite`);
3798
3907
  function indexKey2(rel, b) {
3799
3908
  const files = rel.receipt().files;
3800
3909
  const parts = [b.name, b.version, ...b.keyParts?.(rel) ?? []];
@@ -3805,7 +3914,7 @@ function indexKey2(rel, b) {
3805
3914
  return createHash2("sha256").update(parts.join("\x00")).digest("hex");
3806
3915
  }
3807
3916
  function keyOf(path) {
3808
- if (!existsSync(path))
3917
+ if (!existsSync2(path))
3809
3918
  return null;
3810
3919
  try {
3811
3920
  const db = new Database(path, { readonly: true });
@@ -3830,8 +3939,9 @@ function buildIndex2(rel, b, o) {
3830
3939
  if (b.load !== undefined && o.loaded === undefined)
3831
3940
  throw new Error(`archive-client: index "${b.name}" reads its inputs first: build it with ensureIndex (or rel.index)`);
3832
3941
  mkdirSync(indexDir(rel), { recursive: true });
3942
+ sweepDead(indexDir(rel), BUILDING_FILE);
3833
3943
  const tmp = `${out}.building.${process.pid}`;
3834
- rmSync(tmp, { force: true });
3944
+ rmSync2(tmp, { force: true });
3835
3945
  const t0 = performance.now();
3836
3946
  const db = new Database(tmp, { create: true });
3837
3947
  try {
@@ -3847,7 +3957,7 @@ function buildIndex2(rel, b, o) {
3847
3957
  db.run("VACUUM");
3848
3958
  } catch (e) {
3849
3959
  db.close();
3850
- rmSync(tmp, { force: true });
3960
+ rmSync2(tmp, { force: true });
3851
3961
  throw e;
3852
3962
  }
3853
3963
  db.close();
@@ -3858,16 +3968,42 @@ function buildIndex2(rel, b, o) {
3858
3968
  async function ensureIndex2(rel, b, o) {
3859
3969
  if (!o.force && isCurrent2(rel, b))
3860
3970
  return indexPath2(rel, b.name);
3861
- const loaded = b.load === undefined ? undefined : await b.load(rel);
3862
- return buildIndex2(rel, b, { ...o, loaded });
3971
+ const t0 = performance.now();
3972
+ const tell = (state, error) => o.progress?.({
3973
+ tag: rel.tag,
3974
+ kind: "index",
3975
+ item: b.name,
3976
+ state,
3977
+ bytes: 0,
3978
+ total: null,
3979
+ unpacked: 0,
3980
+ ms: performance.now() - t0,
3981
+ ...error !== undefined ? { error } : {}
3982
+ });
3983
+ tell("indexing");
3984
+ try {
3985
+ const loaded = b.load === undefined ? undefined : await b.load(rel);
3986
+ if (o.progress !== undefined)
3987
+ await new Promise((r) => setTimeout(r, 0));
3988
+ const path = buildIndex2(rel, b, {
3989
+ force: o.force,
3990
+ loaded,
3991
+ ...o.progress === undefined && o.log !== undefined ? { log: o.log } : {}
3992
+ });
3993
+ tell("done");
3994
+ return path;
3995
+ } catch (e) {
3996
+ tell("failed", e.message);
3997
+ throw e;
3998
+ }
3863
3999
  }
3864
4000
  function listIndexes2(rel) {
3865
4001
  const dir = indexDir(rel);
3866
- if (!existsSync(dir))
4002
+ if (!existsSync2(dir))
3867
4003
  return [];
3868
- return readdirSync(dir).filter((f) => f.endsWith(".sqlite")).sort().map((f) => {
4004
+ return readdirSync2(dir).filter((f) => f.endsWith(".sqlite")).sort().map((f) => {
3869
4005
  const name = f.slice(0, -".sqlite".length);
3870
- const path = join(dir, f);
4006
+ const path = join2(dir, f);
3871
4007
  const b = rel.builders().find((x) => x.name === name);
3872
4008
  return {
3873
4009
  name,
@@ -3881,35 +4017,35 @@ function listIndexes2(rel) {
3881
4017
  import { createHash as createHash3 } from "crypto";
3882
4018
  import {
3883
4019
  copyFileSync as copyFileSync2,
3884
- existsSync as existsSync6,
4020
+ existsSync as existsSync7,
3885
4021
  linkSync as linkSync2,
3886
4022
  mkdirSync as mkdirSync5,
3887
4023
  readFileSync as readFileSync5,
3888
4024
  readdirSync as readdirSync4,
3889
4025
  renameSync as renameSync3,
3890
- rmSync as rmSync5,
4026
+ rmSync as rmSync6,
3891
4027
  statSync as statSync4
3892
4028
  } from "fs";
3893
- import { join as join5 } from "path";
4029
+ import { join as join6 } from "path";
3894
4030
 
3895
4031
  import {
3896
4032
  copyFileSync,
3897
- existsSync as existsSync5,
4033
+ existsSync as existsSync6,
3898
4034
  linkSync,
3899
4035
  mkdirSync as mkdirSync4,
3900
4036
  readFileSync as readFileSync4,
3901
4037
  readdirSync as readdirSync3,
3902
- rmSync as rmSync4,
4038
+ rmSync as rmSync5,
3903
4039
  statSync as statSync3
3904
4040
  } from "fs";
3905
- import { isAbsolute, join as join4, relative, resolve, sep } from "path";
4041
+ import { isAbsolute, join as join5, relative, resolve, sep } from "path";
3906
4042
 
3907
- import { existsSync as existsSync2, readFileSync as readFileSync2, rmSync as rmSync2 } from "fs";
3908
- import { join as join2 } from "path";
3909
- var pinPath = (root) => join2(root, "PINNED.json");
4043
+ import { existsSync as existsSync3, readFileSync as readFileSync2, rmSync as rmSync3 } from "fs";
4044
+ import { join as join3 } from "path";
4045
+ var pinPath = (root) => join3(root, "PINNED.json");
3910
4046
  function readPin2(root) {
3911
4047
  const p = pinPath(root);
3912
- if (!existsSync2(p))
4048
+ if (!existsSync3(p))
3913
4049
  return null;
3914
4050
  try {
3915
4051
  const v = JSON.parse(readFileSync2(p, "utf8"));
@@ -3925,23 +4061,24 @@ function writePin(root, pin) {
3925
4061
  }
3926
4062
  function clearPin(root) {
3927
4063
  const p = pinPath(root);
3928
- if (!existsSync2(p))
4064
+ if (!existsSync3(p))
3929
4065
  return false;
3930
- rmSync2(p);
4066
+ rmSync3(p);
3931
4067
  return true;
3932
4068
  }
3933
4069
 
3934
4070
  import { Database as Database2 } from "bun:sqlite";
4071
+ import { randomBytes } from "crypto";
3935
4072
  import {
3936
- existsSync as existsSync4,
4073
+ createReadStream as createReadStream2,
4074
+ existsSync as existsSync5,
3937
4075
  mkdirSync as mkdirSync3,
3938
4076
  readFileSync as readFileSync3,
3939
- readdirSync as readdirSync2,
3940
4077
  renameSync as renameSync2,
3941
- rmSync as rmSync3,
4078
+ rmSync as rmSync4,
3942
4079
  statSync as statSync2
3943
4080
  } from "fs";
3944
- import { join as join3 } from "path";
4081
+ import { basename, dirname as dirname2, join as join4 } from "path";
3945
4082
 
3946
4083
  var CALENDAR_STAGES2 = [
3947
4084
  "announced",
@@ -4277,9 +4414,9 @@ class RegistrationResolver2 {
4277
4414
  }
4278
4415
 
4279
4416
  import { spawn } from "child_process";
4280
- import { createWriteStream as createWriteStream2, existsSync as existsSync3, mkdirSync as mkdirSync2 } from "fs";
4417
+ import { createWriteStream as createWriteStream2, existsSync as existsSync4, mkdirSync as mkdirSync2 } from "fs";
4281
4418
  import { dirname } from "path";
4282
- import { Readable } from "stream";
4419
+ import { Readable as Readable2 } from "stream";
4283
4420
  import { pipeline as pipeline2 } from "stream/promises";
4284
4421
  function run(cmd, args) {
4285
4422
  return new Promise((resolve) => {
@@ -4345,8 +4482,59 @@ var ghRemote2 = {
4345
4482
  dest,
4346
4483
  "--clobber"
4347
4484
  ]);
4348
- if (r.status !== 0 || !existsSync3(dest))
4485
+ if (r.status !== 0 || !existsSync4(dest))
4349
4486
  throw new Error(`gh release download ${tag} -p ${name}: ${firstLine(r.stderr)}`);
4487
+ },
4488
+ async bytes(repo, tag, name, signal) {
4489
+ const what = `gh release download ${tag} -p ${name}`;
4490
+ let child;
4491
+ try {
4492
+ child = spawn("gh", ["release", "download", tag, "-R", repo, "-p", name, "-O", "-"], {
4493
+ stdio: ["ignore", "pipe", "pipe"]
4494
+ });
4495
+ } catch (e) {
4496
+ throw new Error(`${what}: ${e.message}`);
4497
+ }
4498
+ let stderr = "";
4499
+ child.stderr?.on("data", (d) => {
4500
+ stderr += String(d);
4501
+ });
4502
+ const closed = new Promise((resolve) => {
4503
+ child.on("error", (e) => {
4504
+ stderr += e.message;
4505
+ resolve(null);
4506
+ });
4507
+ child.on("close", (status) => resolve(status));
4508
+ });
4509
+ const kill = () => {
4510
+ child.kill("SIGTERM");
4511
+ };
4512
+ signal?.addEventListener("abort", kill, { once: true });
4513
+ const out = child.stdout;
4514
+ if (out === null || out === undefined)
4515
+ throw new Error(`${what}: no stdout`);
4516
+ const it = out[Symbol.asyncIterator]();
4517
+ const failed = async () => {
4518
+ const status = await closed;
4519
+ signal?.removeEventListener("abort", kill);
4520
+ if (status !== 0)
4521
+ throw new Error(`${what}: ${firstLine(stderr) || `exit ${status}`}`);
4522
+ };
4523
+ const head = await it.next();
4524
+ if (head.done === true) {
4525
+ await failed();
4526
+ return async function* () {}();
4527
+ }
4528
+ return async function* () {
4529
+ yield head.value;
4530
+ for (;; ) {
4531
+ const n = await it.next();
4532
+ if (n.done === true)
4533
+ break;
4534
+ yield n.value;
4535
+ }
4536
+ await failed();
4537
+ }();
4350
4538
  }
4351
4539
  };
4352
4540
  var API = "https://api.github.com";
@@ -4399,7 +4587,19 @@ function httpsRemote2(env = process.env, fetchImpl = fetch) {
4399
4587
  if (!res.ok || res.body === null)
4400
4588
  throw new Error(`GET ${a.url}: ${res.status} ${res.statusText}`);
4401
4589
  mkdirSync2(dirname(dest), { recursive: true });
4402
- await pipeline2(Readable.fromWeb(nodeWebStream(res.body)), createWriteStream2(dest));
4590
+ await pipeline2(Readable2.fromWeb(nodeWebStream(res.body)), createWriteStream2(dest));
4591
+ },
4592
+ async bytes(repo, tag, name, signal) {
4593
+ const a = (await release(repo, tag)).assets.find((x) => x.name === name);
4594
+ if (a === undefined)
4595
+ throw new Error(`${tag} has no asset ${name}`);
4596
+ const res = await fetchImpl(a.url, {
4597
+ headers: headers(env, "application/octet-stream"),
4598
+ ...signal !== undefined ? { signal } : {}
4599
+ });
4600
+ if (!res.ok || res.body === null)
4601
+ throw new Error(`GET ${a.url}: ${res.status} ${res.statusText}`);
4602
+ return Readable2.fromWeb(nodeWebStream(res.body));
4403
4603
  }
4404
4604
  };
4405
4605
  }
@@ -4419,7 +4619,10 @@ function fallbackRemote2(first, second) {
4419
4619
  name: `${first.name}+${second.name}`,
4420
4620
  tags: both((r) => r.tags.bind(r)),
4421
4621
  assets: both((r) => r.assets.bind(r)),
4422
- download: both((r) => r.download.bind(r))
4622
+ download: both((r) => r.download.bind(r)),
4623
+ ...first.bytes !== undefined && second.bytes !== undefined ? {
4624
+ bytes: both((r) => r.bytes.bind(r))
4625
+ } : {}
4423
4626
  };
4424
4627
  }
4425
4628
  var installed = null;
@@ -4658,12 +4861,12 @@ class UpcomingFinder2 {
4658
4861
  function tagDir(root, tag) {
4659
4862
  if (parseArchiveTag2(tag) === null)
4660
4863
  throw new Error(`archive-client: "${tag}" is not a release tag (archive-YYYY.MM.DD[-N[.k]])`);
4661
- return join3(root, tag);
4864
+ return join4(root, tag);
4662
4865
  }
4663
- var receiptPath = (dir) => join3(dir, ".client.json");
4866
+ var receiptPath = (dir) => join4(dir, ".client.json");
4664
4867
  function readReceipt(dir) {
4665
4868
  const p = receiptPath(dir);
4666
- if (!existsSync4(p))
4869
+ if (!existsSync5(p))
4667
4870
  return null;
4668
4871
  try {
4669
4872
  return JSON.parse(readFileSync3(p, "utf8"));
@@ -4680,7 +4883,7 @@ async function resolveTag2(spec, opts = {}) {
4680
4883
  const pin = readPin2(root);
4681
4884
  return resolveTag2(pin === null ? { kind: "latest" } : { kind: "tag", tag: pin.tag }, opts);
4682
4885
  }
4683
- if (s.kind === "tag" && existsSync4(join3(tagDir(root, s.tag), "MANIFEST.json")))
4886
+ if (s.kind === "tag" && existsSync5(join4(tagDir(root, s.tag), "MANIFEST.json")))
4684
4887
  return s.tag;
4685
4888
  const repo = requireRepo2(opts);
4686
4889
  const tags = await (opts.remote ?? defaultRemote()).tags(repo);
@@ -4689,9 +4892,30 @@ async function resolveTag2(spec, opts = {}) {
4689
4892
  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
4893
  return tag;
4691
4894
  }
4692
- function dropIfEmpty(dir) {
4693
- if (existsSync4(dir) && readdirSync2(dir).length === 0)
4694
- rmSync3(dir, { recursive: true });
4895
+ var DEFAULT_CONCURRENCY2 = 4;
4896
+ function partPath(dest) {
4897
+ return join4(dirname2(dest), `.${basename(dest)}.${process.pid}-${randomBytes(4).toString("hex")}.part`);
4898
+ }
4899
+
4900
+ class Slots {
4901
+ n;
4902
+ running = 0;
4903
+ waiting = [];
4904
+ constructor(n) {
4905
+ this.n = n;
4906
+ }
4907
+ async run(job, signal) {
4908
+ if (this.running >= this.n)
4909
+ await new Promise((r) => this.waiting.push(r));
4910
+ this.running++;
4911
+ try {
4912
+ signal.throwIfAborted();
4913
+ return await job();
4914
+ } finally {
4915
+ this.running--;
4916
+ this.waiting.shift()?.();
4917
+ }
4918
+ }
4695
4919
  }
4696
4920
 
4697
4921
  class Release2 {
@@ -4718,7 +4942,7 @@ class Release2 {
4718
4942
  feisPanels;
4719
4943
  opts;
4720
4944
  handles = new Map;
4721
- remoteAssets = null;
4945
+ listing = null;
4722
4946
  constructor(tag, root, manifest, opts) {
4723
4947
  const t = parseArchiveTag2(tag);
4724
4948
  this.tag = tag;
@@ -4751,19 +4975,65 @@ class Release2 {
4751
4975
  return planAsset2(asset, this.manifest);
4752
4976
  }
4753
4977
  has(asset) {
4754
- return existsSync4(join3(this.dir, this.plan(asset).local));
4978
+ return existsSync5(join4(this.dir, this.plan(asset).local));
4755
4979
  }
4756
4980
  path(asset) {
4757
- const p = join3(this.dir, this.plan(asset).local);
4758
- if (!existsSync4(p))
4981
+ const p = join4(this.dir, this.plan(asset).local);
4982
+ if (!existsSync5(p))
4759
4983
  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
4984
  return p;
4761
4985
  }
4762
4986
  async fetch(...assets) {
4763
- const out = [];
4764
- for (const a of assets)
4765
- out.push(await this.fetchOne(a));
4766
- return out;
4987
+ return this.fetching(assets).all;
4988
+ }
4989
+ inflight = new Map;
4990
+ slots = null;
4991
+ swept = false;
4992
+ fetching(assets) {
4993
+ this.slots ??= new Slots(Math.max(1, this.opts.concurrency ?? DEFAULT_CONCURRENCY2));
4994
+ const slots = this.slots;
4995
+ const ac = new AbortController;
4996
+ const mine = new Map;
4997
+ let first = null;
4998
+ for (const a of assets) {
4999
+ if (mine.has(a))
5000
+ continue;
5001
+ const local = this.plan(a).local;
5002
+ let p = this.inflight.get(local);
5003
+ if (p === undefined) {
5004
+ p = slots.run(() => this.fetchOne(a, ac.signal), ac.signal);
5005
+ this.inflight.set(local, p);
5006
+ const started = p;
5007
+ started.then(() => this.inflight.delete(local), () => this.inflight.delete(local));
5008
+ }
5009
+ p.catch((e) => {
5010
+ first ??= { e };
5011
+ ac.abort();
5012
+ });
5013
+ mine.set(a, p);
5014
+ }
5015
+ const all = (async () => {
5016
+ const settled = await Promise.allSettled([...mine.values()]);
5017
+ if (first !== null)
5018
+ throw first.e;
5019
+ return assets.map((a) => {
5020
+ const s = settled[[...mine.keys()].indexOf(a)];
5021
+ return s.value;
5022
+ });
5023
+ })();
5024
+ return {
5025
+ each: (asset) => {
5026
+ const p = mine.get(asset);
5027
+ if (p === undefined)
5028
+ throw new Error(`archive-client: ${asset} was not asked of this fetch`);
5029
+ return p;
5030
+ },
5031
+ all,
5032
+ cancel: () => {
5033
+ first ??= { e: new Error("archive-client: the fetch was cancelled") };
5034
+ ac.abort();
5035
+ }
5036
+ };
4767
5037
  }
4768
5038
  async publishes(asset) {
4769
5039
  const plan = this.plan(asset);
@@ -4819,25 +5089,29 @@ class Release2 {
4819
5089
  if (c.state === "missing")
4820
5090
  throw needsRelease2(this.tag, c);
4821
5091
  }
4822
- async listed() {
4823
- if (this.remoteAssets === null) {
4824
- this.remoteAssets = await (this.opts.remote ?? defaultRemote()).assets(requireRepo2(this.opts), this.tag);
4825
- if (existsSync4(this.dir)) {
5092
+ listed() {
5093
+ this.listing ??= (async () => {
5094
+ const assets = await (this.opts.remote ?? defaultRemote()).assets(requireRepo2(this.opts), this.tag);
5095
+ if (existsSync5(this.dir)) {
4826
5096
  const r = this.receipt();
4827
- r.published = this.remoteAssets.map((a) => a.name).sort();
5097
+ r.published = assets.map((a) => a.name).sort();
4828
5098
  writeJsonAtomic2(receiptPath(this.dir), r);
4829
5099
  }
4830
- }
4831
- return this.remoteAssets;
5100
+ return assets;
5101
+ })();
5102
+ this.listing.catch(() => {
5103
+ this.listing = null;
5104
+ });
5105
+ return this.listing;
4832
5106
  }
4833
- async fetchOne(asset) {
5107
+ async fetchOne(asset, signal) {
4834
5108
  const plan = this.plan(asset);
4835
- const dest = join3(this.dir, plan.local);
5109
+ const dest = join4(this.dir, plan.local);
4836
5110
  const have = this.receipt().files[plan.local];
4837
- if (existsSync4(dest) && have !== undefined)
5111
+ if (existsSync5(dest) && have !== undefined)
4838
5112
  return have;
4839
5113
  const log = this.opts.log ?? (() => {});
4840
- if (existsSync4(dest) && plan.contentSha256 !== null) {
5114
+ if (existsSync5(dest) && plan.contentSha256 !== null) {
4841
5115
  const sha = await sha256File2(dest);
4842
5116
  if (sha === plan.contentSha256) {
4843
5117
  const adopted = {
@@ -4846,7 +5120,8 @@ class Release2 {
4846
5120
  bytes: statSync2(dest).size,
4847
5121
  verified: ["manifest"]
4848
5122
  };
4849
- log(`adopt ${this.tag} ${plan.local} (the manifest's sha256)`);
5123
+ if (this.opts.progress === undefined)
5124
+ log(`adopt ${this.tag} ${plan.local} (the manifest's sha256)`);
4850
5125
  this.record(plan.local, adopted);
4851
5126
  return adopted;
4852
5127
  }
@@ -4855,14 +5130,24 @@ class Release2 {
4855
5130
  const pub = plan.published.map((n) => listed.find((a) => a.name === n)).find((a) => a !== undefined);
4856
5131
  if (pub === undefined)
4857
5132
  throw new Error(`archive-client: ${this.tag} publishes no ${plan.published.join(" or ")} (asked: ${asset})`);
4858
- const part = join3(this.dir, ".part");
4859
- mkdirSync3(part, { recursive: true });
4860
- const raw = join3(part, pub.name);
4861
- log(`fetch ${this.tag} ${pub.name} (${pub.size} bytes)`);
4862
- await (this.opts.remote ?? defaultRemote()).download(requireRepo2(this.opts), this.tag, pub.name, raw);
4863
- const landed = await land(raw, pub, plan, dest, this.opts, log);
5133
+ mkdirSync3(this.dir, { recursive: true });
5134
+ if (!this.swept) {
5135
+ sweepDead(this.dir, PART_FILE);
5136
+ this.swept = true;
5137
+ }
5138
+ const landed = await fetchAsset({
5139
+ remote: this.opts.remote ?? defaultRemote(),
5140
+ repo: requireRepo2(this.opts),
5141
+ tag: this.tag,
5142
+ pub,
5143
+ plan,
5144
+ dest,
5145
+ opts: this.opts,
5146
+ log,
5147
+ ...this.opts.progress !== undefined ? { progress: this.opts.progress } : {},
5148
+ ...signal !== undefined ? { signal } : {}
5149
+ });
4864
5150
  this.record(plan.local, landed);
4865
- dropIfEmpty(part);
4866
5151
  return landed;
4867
5152
  }
4868
5153
  record(local, f) {
@@ -4891,7 +5176,11 @@ class Release2 {
4891
5176
  if (FEATURES2.some((f) => f.feature === b.name))
4892
5177
  await this.require(b.name);
4893
5178
  await this.fetch(...b.inputs);
4894
- return ensureIndex2(this, b, { force: o.force ?? false, log: this.opts.log });
5179
+ return ensureIndex2(this, b, {
5180
+ force: o.force ?? false,
5181
+ log: this.opts.log,
5182
+ ...this.opts.progress !== undefined ? { progress: this.opts.progress } : {}
5183
+ });
4895
5184
  }
4896
5185
  async indexDb(name) {
4897
5186
  const key = `index:${name}`;
@@ -4911,69 +5200,172 @@ class Release2 {
4911
5200
  this.close();
4912
5201
  }
4913
5202
  }
4914
- async function land(raw, pub, plan, dest, opts, log) {
5203
+ function verifyLanded(got, pub, plan, strict) {
4915
5204
  const verified = [];
4916
5205
  const host = sha256OfDigest(pub.digest);
4917
5206
  if (host !== null) {
4918
- const got = await sha256File2(raw);
4919
- if (got !== host) {
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
- }
5207
+ if (got.rawSha256 !== host)
5208
+ throw new Error(`archive-client: ${pub.name} does not match the host's digest (sha256 ${got.rawSha256}, the host says ${host})`);
4923
5209
  verified.push("host-digest");
4924
5210
  }
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
5211
  if (plan.contentSha256 !== null) {
4934
- if (sha !== plan.contentSha256) {
4935
- rmSync3(landedAt, { force: true });
4936
- throw new Error(`archive-client: ${plan.local} does not match MANIFEST.json (sha256 ${sha}, the manifest says ${plan.contentSha256})`);
4937
- }
5212
+ if (got.sha256 !== plan.contentSha256)
5213
+ throw new Error(`archive-client: ${plan.local} does not match MANIFEST.json (sha256 ${got.sha256}, the manifest says ${plan.contentSha256})`);
4938
5214
  verified.push("manifest");
4939
5215
  }
4940
- if (verified.length === 0 && opts.strict === true) {
4941
- rmSync3(landedAt, { force: true });
5216
+ if (verified.length === 0 && strict)
4942
5217
  throw new Error(`archive-client: nothing verifies ${pub.name} (no host digest, no manifest hash) and strict is set`);
5218
+ return verified;
5219
+ }
5220
+ async function fetchAsset(j) {
5221
+ const t0 = performance.now();
5222
+ const { pub, plan, tag } = j;
5223
+ const decompress = plan.decompress && pub.name.endsWith(".zst");
5224
+ const tmp = partPath(j.dest);
5225
+ const raw = decompress && j.opts.keepCompressed === true ? `${tmp}.zst` : undefined;
5226
+ let dl = null;
5227
+ let bytes = 0;
5228
+ let unpacked = 0;
5229
+ let state = "downloading";
5230
+ const total = Number.isFinite(pub.size) ? pub.size : null;
5231
+ const tell = (s, error) => j.progress?.({
5232
+ tag,
5233
+ kind: "asset",
5234
+ item: pub.name,
5235
+ state: s,
5236
+ bytes,
5237
+ total,
5238
+ unpacked,
5239
+ ms: performance.now() - t0,
5240
+ ...error !== undefined ? { error } : {}
5241
+ });
5242
+ const onBytes = (received, written) => {
5243
+ if (state === "downloading")
5244
+ bytes = received;
5245
+ unpacked = decompress ? written : 0;
5246
+ tell(state);
5247
+ };
5248
+ if (j.progress === undefined)
5249
+ j.log(`fetch ${tag} ${pub.name} (${pub.size} bytes)`);
5250
+ tell("downloading");
5251
+ try {
5252
+ let got;
5253
+ if (j.remote.bytes !== undefined) {
5254
+ const body = await j.remote.bytes(j.repo, tag, pub.name, j.signal);
5255
+ if (decompress && j.progress === undefined)
5256
+ j.log(`decompress ${pub.name}`);
5257
+ got = await streamTo2(body, tmp, {
5258
+ decompress,
5259
+ ...raw !== undefined ? { raw } : {},
5260
+ onBytes,
5261
+ ...j.signal !== undefined ? { signal: j.signal } : {}
5262
+ });
5263
+ } else {
5264
+ dl = `${tmp}.dl`;
5265
+ await j.remote.download(j.repo, tag, pub.name, dl);
5266
+ j.signal?.throwIfAborted();
5267
+ bytes = statSync2(dl).size;
5268
+ if (decompress) {
5269
+ if (j.progress === undefined)
5270
+ j.log(`decompress ${pub.name}`);
5271
+ state = "unpacking";
5272
+ tell(state);
5273
+ got = await streamTo2(createReadStream2(dl), tmp, {
5274
+ decompress,
5275
+ onBytes,
5276
+ ...j.signal !== undefined ? { signal: j.signal } : {}
5277
+ });
5278
+ if (raw !== undefined)
5279
+ renameSync2(dl, raw);
5280
+ else
5281
+ rmSync4(dl, { force: true });
5282
+ } else {
5283
+ state = "verifying";
5284
+ tell(state);
5285
+ const sha = await sha256File2(dl);
5286
+ got = { rawSha256: sha, rawBytes: bytes, sha256: sha, bytes };
5287
+ renameSync2(dl, tmp);
5288
+ }
5289
+ dl = null;
5290
+ }
5291
+ state = "verifying";
5292
+ tell(state);
5293
+ const verified = verifyLanded(got, pub, plan, j.opts.strict === true);
5294
+ renameSync2(tmp, j.dest);
5295
+ if (raw !== undefined)
5296
+ renameSync2(raw, `${j.dest}.zst`);
5297
+ tell("done");
5298
+ return { from: pub.name, sha256: got.sha256, bytes: got.bytes, verified };
5299
+ } catch (e) {
5300
+ for (const f of [tmp, raw, dl])
5301
+ if (f !== undefined && f !== null)
5302
+ rmSync4(f, { force: true });
5303
+ const cancelled = j.signal?.aborted === true;
5304
+ tell(cancelled ? "cancelled" : "failed", cancelled ? undefined : e.message);
5305
+ throw e;
5306
+ }
5307
+ }
5308
+ async function land(raw, pub, plan, dest, opts, log) {
5309
+ const decompress = plan.decompress && pub.name.endsWith(".zst");
5310
+ let got;
5311
+ let landedAt = raw;
5312
+ if (decompress) {
5313
+ landedAt = partPath(dest);
5314
+ log(`decompress ${pub.name}`);
5315
+ got = await streamTo2(createReadStream2(raw), landedAt, { decompress: true });
5316
+ } else {
5317
+ const sha = await sha256File2(raw);
5318
+ const n = statSync2(raw).size;
5319
+ got = { rawSha256: sha, rawBytes: n, sha256: sha, bytes: n };
5320
+ }
5321
+ let verified;
5322
+ try {
5323
+ verified = verifyLanded(got, pub, plan, opts.strict === true);
5324
+ } catch (e) {
5325
+ rmSync4(landedAt, { force: true });
5326
+ if (landedAt !== raw)
5327
+ rmSync4(raw, { force: true });
5328
+ throw e;
4943
5329
  }
4944
5330
  renameSync2(landedAt, dest);
4945
5331
  if (landedAt !== raw) {
4946
5332
  if (opts.keepCompressed === true)
4947
5333
  renameSync2(raw, `${dest}.zst`);
4948
5334
  else
4949
- rmSync3(raw, { force: true });
5335
+ rmSync4(raw, { force: true });
4950
5336
  }
4951
- return { from: pub.name, sha256: sha, bytes: statSync2(dest).size, verified };
5337
+ return { from: pub.name, sha256: got.sha256, bytes: statSync2(dest).size, verified };
4952
5338
  }
4953
5339
  async function openRelease2(spec = "pinned", opts = {}) {
4954
5340
  const root = cacheRoot2(opts);
4955
5341
  const tag = await resolveTag2(spec, opts);
4956
5342
  const dir = tagDir(root, tag);
4957
- const manifestPath = join3(dir, "MANIFEST.json");
4958
- if (!existsSync4(manifestPath)) {
5343
+ const manifestPath = join4(dir, "MANIFEST.json");
5344
+ if (!existsSync5(manifestPath)) {
4959
5345
  const remote = opts.remote ?? defaultRemote();
4960
5346
  const repo = requireRepo2(opts);
4961
5347
  const listed = await remote.assets(repo, tag);
4962
5348
  const pub = listed.find((a) => a.name === "MANIFEST.json");
4963
5349
  if (pub === undefined)
4964
5350
  throw new Error(`archive-client: ${tag} publishes no MANIFEST.json`);
4965
- const part = join3(dir, ".part");
4966
- mkdirSync3(part, { recursive: true });
4967
- const raw = join3(part, "MANIFEST.json");
4968
- await remote.download(repo, tag, "MANIFEST.json", raw);
4969
- const landed = await land(raw, pub, planAsset2("MANIFEST.json", null), manifestPath, opts, () => {});
5351
+ mkdirSync3(dir, { recursive: true });
5352
+ const landed = await fetchAsset({
5353
+ remote,
5354
+ repo,
5355
+ tag,
5356
+ pub,
5357
+ plan: planAsset2("MANIFEST.json", null),
5358
+ dest: manifestPath,
5359
+ opts,
5360
+ log: () => {},
5361
+ ...opts.progress !== undefined ? { progress: opts.progress } : {}
5362
+ });
4970
5363
  writeJsonAtomic2(receiptPath(dir), {
4971
5364
  tag,
4972
5365
  source: repo,
4973
5366
  files: { "MANIFEST.json": landed },
4974
5367
  published: listed.map((a) => a.name).sort()
4975
5368
  });
4976
- dropIfEmpty(part);
4977
5369
  }
4978
5370
  const manifest = JSON.parse(readFileSync3(manifestPath, "utf8"));
4979
5371
  const rel = new Release2(tag, root, manifest, opts);
@@ -4982,8 +5374,8 @@ async function openRelease2(spec = "pinned", opts = {}) {
4982
5374
  }
4983
5375
  function openCached2(tag, opts = {}) {
4984
5376
  const root = cacheRoot2(opts);
4985
- const p = join3(tagDir(root, tag), "MANIFEST.json");
4986
- if (!existsSync4(p))
5377
+ const p = join4(tagDir(root, tag), "MANIFEST.json");
5378
+ if (!existsSync5(p))
4987
5379
  return null;
4988
5380
  return new Release2(tag, root, JSON.parse(readFileSync3(p, "utf8")), opts);
4989
5381
  }
@@ -4995,14 +5387,14 @@ function checkKeep(keep) {
4995
5387
  return keep;
4996
5388
  }
4997
5389
  function duBytes(p) {
4998
- if (!existsSync5(p))
5390
+ if (!existsSync6(p))
4999
5391
  return 0;
5000
5392
  const s = statSync3(p);
5001
5393
  if (!s.isDirectory())
5002
5394
  return s.size;
5003
5395
  let n = 0;
5004
5396
  for (const e of readdirSync3(p))
5005
- n += duBytes(join4(p, e));
5397
+ n += duBytes(join5(p, e));
5006
5398
  return n;
5007
5399
  }
5008
5400
 
@@ -5014,9 +5406,9 @@ class ReleaseManager2 {
5014
5406
  this.root = cacheRoot2(opts);
5015
5407
  }
5016
5408
  cachedTags() {
5017
- if (!existsSync5(this.root))
5409
+ if (!existsSync6(this.root))
5018
5410
  return [];
5019
- return readdirSync3(this.root).filter((t) => parseArchiveTag2(t) !== null && existsSync5(join4(this.root, t, "MANIFEST.json"))).sort(compareTagsNewestFirst2);
5411
+ return readdirSync3(this.root).filter((t) => parseArchiveTag2(t) !== null && existsSync6(join5(this.root, t, "MANIFEST.json"))).sort(compareTagsNewestFirst2);
5020
5412
  }
5021
5413
  cached(tag) {
5022
5414
  const r = openCached2(tag, this.opts);
@@ -5056,10 +5448,14 @@ class ReleaseManager2 {
5056
5448
  async use(spec, o = {}) {
5057
5449
  const keep = o.prune === false ? null : this.keepCount(o.keep);
5058
5450
  const was = readPin2(this.root)?.tag ?? null;
5059
- const rel = await this.fetch(spec, o);
5451
+ const rel = await openRelease2(spec, { ...this.opts, assets: [] });
5060
5452
  const tag = rel.tag;
5061
- rel.close();
5062
- const { built } = await this.index(tag);
5453
+ let built;
5454
+ try {
5455
+ built = await this.buildAsLanded(rel, [], o.assets ?? ["results"], false);
5456
+ } finally {
5457
+ rel.close();
5458
+ }
5063
5459
  const pin = await this.pin(tag);
5064
5460
  if (keep === null)
5065
5461
  return { tag, was, pin, built, pruned: null };
@@ -5093,22 +5489,22 @@ class ReleaseManager2 {
5093
5489
  async add(dir, tag) {
5094
5490
  if (parseArchiveTag2(tag) === null)
5095
5491
  throw new Error(`add: "${tag}" is not a release tag`);
5096
- const mf = join4(dir, "MANIFEST.json");
5097
- if (!existsSync5(mf))
5492
+ const mf = join5(dir, "MANIFEST.json");
5493
+ if (!existsSync6(mf))
5098
5494
  throw new Error(`add: ${dir} holds no MANIFEST.json`);
5099
5495
  const dest = tagDir(this.root, tag);
5100
- if (existsSync5(join4(dest, "MANIFEST.json")))
5496
+ if (existsSync6(join5(dest, "MANIFEST.json")))
5101
5497
  throw new Error(`add: ${tag} is already in the cache (${dest}); \`rm ${tag}\` first`);
5102
5498
  const manifest = JSON.parse(readFileSync4(mf, "utf8"));
5103
- const part = join4(dest, ".part");
5499
+ const part = join5(dest, ".part");
5104
5500
  mkdirSync4(part, { recursive: true });
5105
5501
  const receipt = { tag, source: `local:${dir}`, files: {} };
5106
5502
  const log = this.opts.log ?? (() => {});
5107
5503
  for (const name of readdirSync3(dir).sort()) {
5108
- const src = join4(dir, name);
5504
+ const src = join5(dir, name);
5109
5505
  if (!statSync3(src).isFile())
5110
5506
  continue;
5111
- const raw = join4(part, name);
5507
+ const raw = join5(part, name);
5112
5508
  try {
5113
5509
  linkSync(src, raw);
5114
5510
  } catch {
@@ -5118,10 +5514,10 @@ class ReleaseManager2 {
5118
5514
  const side = Object.entries(manifest.sideFiles ?? {}).find(([k, f]) => (f.file ?? `${k}.zst`) === name)?.[0];
5119
5515
  const asked = base !== name && isDatabase(base) ? base : side ?? name;
5120
5516
  const plan = planAsset2(asked, manifest);
5121
- const landed = await land(raw, { name, digest: null }, plan, join4(dest, plan.local), this.opts, log);
5517
+ const landed = await land(raw, { name, digest: null }, plan, join5(dest, plan.local), this.opts, log);
5122
5518
  receipt.files[plan.local] = landed;
5123
5519
  }
5124
- rmSync4(part, { recursive: true, force: true });
5520
+ rmSync5(part, { recursive: true, force: true });
5125
5521
  writeJsonAtomic2(receiptPath(dest), receipt);
5126
5522
  const rel = openCached2(tag, this.opts);
5127
5523
  if (rel === null)
@@ -5130,20 +5526,51 @@ class ReleaseManager2 {
5130
5526
  }
5131
5527
  async index(spec, names = [], o = {}) {
5132
5528
  const rel = await openRelease2(spec, { ...this.opts, assets: [] });
5529
+ try {
5530
+ return { tag: rel.tag, built: await this.buildAsLanded(rel, names, [], o.force ?? false) };
5531
+ } finally {
5532
+ rel.close();
5533
+ }
5534
+ }
5535
+ async buildAsLanded(rel, names, extra, force) {
5133
5536
  const want = names.length > 0 ? names : rel.builders().map((b) => b.name);
5134
- const built = [];
5135
- for (const name of want) {
5537
+ const builders = want.map((name) => {
5136
5538
  const b = rel.builders().find((x) => x.name === name);
5137
5539
  if (b === undefined)
5138
5540
  throw new Error(`index: no index "${name}"`);
5139
- await rel.fetch(...b.inputs);
5140
- built.push({
5141
- name,
5142
- path: await ensureIndex2(rel, b, { force: o.force ?? false, log: this.opts.log })
5143
- });
5541
+ return b;
5542
+ });
5543
+ const assets = [...new Set([...extra, ...builders.flatMap((b) => b.inputs)])];
5544
+ const f = rel.fetching(assets);
5545
+ f.all.catch(() => {});
5546
+ const waiting = new Map(builders.map((b) => [b.name, Promise.all(b.inputs.map((a) => f.each(a))).then(() => b.name)]));
5547
+ for (const p of waiting.values())
5548
+ p.catch(() => {});
5549
+ const paths = new Map;
5550
+ while (waiting.size > 0) {
5551
+ let name;
5552
+ try {
5553
+ name = await Promise.race(waiting.values());
5554
+ } catch {
5555
+ await f.all;
5556
+ throw new Error("archive-client: a fetch failed and said nothing");
5557
+ }
5558
+ waiting.delete(name);
5559
+ const b = builders.find((x) => x.name === name);
5560
+ try {
5561
+ paths.set(name, await ensureIndex2(rel, b, {
5562
+ force,
5563
+ ...this.opts.log !== undefined ? { log: this.opts.log } : {},
5564
+ ...this.opts.progress !== undefined ? { progress: this.opts.progress } : {}
5565
+ }));
5566
+ } catch (e) {
5567
+ f.cancel();
5568
+ await f.all.catch(() => {});
5569
+ throw e;
5570
+ }
5144
5571
  }
5145
- rel.close();
5146
- return { tag: rel.tag, built };
5572
+ await f.all;
5573
+ return want.map((name) => ({ name, path: paths.get(name) }));
5147
5574
  }
5148
5575
  plan(dryRun) {
5149
5576
  return { dryRun, remove: [], keep: [], bytes: 0 };
@@ -5158,7 +5585,7 @@ class ReleaseManager2 {
5158
5585
  p.bytes = p.remove.reduce((n, r) => n + r.bytes, 0);
5159
5586
  if (!p.dryRun)
5160
5587
  for (const r of p.remove)
5161
- rmSync4(r.path, { recursive: true, force: true });
5588
+ rmSync5(r.path, { recursive: true, force: true });
5162
5589
  return p;
5163
5590
  }
5164
5591
  prune(o = {}) {
@@ -5180,7 +5607,7 @@ class ReleaseManager2 {
5180
5607
  }
5181
5608
  remove(tag, o = {}) {
5182
5609
  const path = tagDir(this.root, tag);
5183
- if (!existsSync5(path))
5610
+ if (!existsSync6(path))
5184
5611
  throw new Error(`${tag} is not in the cache`);
5185
5612
  if (readPin2(this.root)?.tag === tag)
5186
5613
  throw new Error(`${tag} is pinned: \`unpin\` first`);
@@ -5192,7 +5619,7 @@ class ReleaseManager2 {
5192
5619
  const p = this.plan(o.dryRun ?? false);
5193
5620
  for (const t of tag === undefined ? this.cachedTags() : [tag]) {
5194
5621
  const path = indexDir({ dir: tagDir(this.root, t) });
5195
- if (existsSync5(path))
5622
+ if (existsSync6(path))
5196
5623
  this.planRemove(p, path, `${t}'s indexes`);
5197
5624
  }
5198
5625
  return this.apply(p);
@@ -5201,8 +5628,8 @@ class ReleaseManager2 {
5201
5628
  const rel = this.cached(tag);
5202
5629
  const bad = [];
5203
5630
  for (const [name, f] of Object.entries(rel.receipt().files)) {
5204
- const p = join4(rel.dir, name);
5205
- const got = existsSync5(p) ? await sha256File2(p) : "missing";
5631
+ const p = join5(rel.dir, name);
5632
+ const got = existsSync6(p) ? await sha256File2(p) : "missing";
5206
5633
  if (got !== f.sha256)
5207
5634
  bad.push({ file: name, want: f.sha256, got });
5208
5635
  }
@@ -5214,11 +5641,11 @@ function releases2(opts = {}) {
5214
5641
  }
5215
5642
 
5216
5643
  var LOCAL_DIR2 = "local";
5217
- var localRoot2 = (root) => join5(root, LOCAL_DIR2);
5644
+ var localRoot2 = (root) => join6(root, LOCAL_DIR2);
5218
5645
  var newerThan2 = (a, b) => a.cut > b.cut || a.cut === b.cut && a.fraction > b.fraction;
5219
5646
  function readManifest(dir) {
5220
- const p = join5(dir, "MANIFEST.json");
5221
- if (!existsSync6(p))
5647
+ const p = join6(dir, "MANIFEST.json");
5648
+ if (!existsSync7(p))
5222
5649
  return null;
5223
5650
  try {
5224
5651
  const v = JSON.parse(readFileSync5(p, "utf8"));
@@ -5240,8 +5667,8 @@ function incomplete2(dir) {
5240
5667
  const d = dbs[name];
5241
5668
  if (d === undefined)
5242
5669
  continue;
5243
- const p = join5(dir, d.file ?? `${name}.sqlite`);
5244
- if (!existsSync6(p))
5670
+ const p = join6(dir, d.file ?? `${name}.sqlite`);
5671
+ if (!existsSync7(p))
5245
5672
  return `no ${d.file ?? `${name}.sqlite`}`;
5246
5673
  if (d.bytes !== undefined && statSync4(p).size !== d.bytes)
5247
5674
  return `${d.file ?? `${name}.sqlite`} is ${statSync4(p).size} bytes, the manifest says ${d.bytes}`;
@@ -5274,18 +5701,18 @@ function pickBuild2(builds, base) {
5274
5701
  return best;
5275
5702
  }
5276
5703
  function buildTag2(p) {
5277
- const at = typeof p.manifest.emittedAt === "string" && !Number.isNaN(Date.parse(p.manifest.emittedAt)) ? new Date(p.manifest.emittedAt) : statSync4(join5(p.build.dir, "MANIFEST.json")).mtime;
5704
+ 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
5705
  const date = at.toISOString().slice(0, 10).replaceAll("-", ".");
5279
5706
  return renderArchiveTag2({ date, cut: p.version.cut, fraction: p.version.fraction });
5280
5707
  }
5281
5708
  function buildFiles(dir) {
5282
- return readdirSync4(dir).filter((n) => statSync4(join5(dir, n)).isFile()).filter((n) => {
5709
+ return readdirSync4(dir).filter((n) => statSync4(join6(dir, n)).isFile()).filter((n) => {
5283
5710
  const m = /^(.*)\.sqlite(\.zst)?$/.exec(n);
5284
5711
  return m === null || isDatabase(m[1]);
5285
5712
  }).sort();
5286
5713
  }
5287
5714
  function lacks(b, manifest, inputs) {
5288
- return inputs.filter((a) => !planAsset2(a, manifest).published.some((n) => existsSync6(join5(b.dir, n))));
5715
+ return inputs.filter((a) => !planAsset2(a, manifest).published.some((n) => existsSync7(join6(b.dir, n))));
5289
5716
  }
5290
5717
  function buildRemote(label) {
5291
5718
  const refuse = async () => {
@@ -5302,7 +5729,7 @@ async function openBuild2(root, p, opts) {
5302
5729
  const o = buildOptions(root, p.build.label, opts);
5303
5730
  const local = localRoot2(root);
5304
5731
  const tag = buildTag2(p);
5305
- const want = sha256Of(join5(p.build.dir, "MANIFEST.json"));
5732
+ const want = sha256Of(join6(p.build.dir, "MANIFEST.json"));
5306
5733
  const current = () => {
5307
5734
  const rel = openCached2(tag, o);
5308
5735
  return rel !== null && rel.receipt().files["MANIFEST.json"]?.sha256 === want ? rel : null;
@@ -5311,33 +5738,33 @@ async function openBuild2(root, p, opts) {
5311
5738
  if (have !== null)
5312
5739
  return have;
5313
5740
  mkdirSync5(local, { recursive: true });
5314
- const work = join5(local, scratchName("add"));
5741
+ const work = join6(local, scratchName("add"));
5315
5742
  try {
5316
- const stage = join5(work, "stage");
5743
+ const stage = join6(work, "stage");
5317
5744
  mkdirSync5(stage, { recursive: true });
5318
5745
  for (const f of buildFiles(p.build.dir)) {
5319
5746
  try {
5320
- linkSync2(join5(p.build.dir, f), join5(stage, f));
5747
+ linkSync2(join6(p.build.dir, f), join6(stage, f));
5321
5748
  } catch {
5322
- copyFileSync2(join5(p.build.dir, f), join5(stage, f));
5749
+ copyFileSync2(join6(p.build.dir, f), join6(stage, f));
5323
5750
  }
5324
5751
  }
5325
- const added = await new ReleaseManager2({ ...o, cache: join5(work, "cache") }).add(stage, tag);
5752
+ const added = await new ReleaseManager2({ ...o, cache: join6(work, "cache") }).add(stage, tag);
5326
5753
  const receipt = added.receipt();
5327
5754
  receipt.source = `local:${p.build.dir}`;
5328
5755
  writeJsonAtomic2(receiptPath(added.dir), receipt);
5329
5756
  added.close();
5330
- const dest = join5(local, tag);
5331
- if (existsSync6(dest) && current() === null) {
5757
+ const dest = join6(local, tag);
5758
+ if (existsSync7(dest) && current() === null) {
5332
5759
  try {
5333
- renameSync3(dest, join5(local, scratchName("old")));
5760
+ renameSync3(dest, join6(local, scratchName("old")));
5334
5761
  } catch {}
5335
5762
  }
5336
5763
  try {
5337
5764
  renameSync3(added.dir, dest);
5338
5765
  } catch {}
5339
5766
  } finally {
5340
- rmSync5(work, { recursive: true, force: true });
5767
+ rmSync6(work, { recursive: true, force: true });
5341
5768
  }
5342
5769
  const rel = current();
5343
5770
  if (rel === null)
@@ -5354,7 +5781,7 @@ function alive(pid) {
5354
5781
  }
5355
5782
  function sweepBuilds2(root, keep) {
5356
5783
  const local = localRoot2(root);
5357
- if (!existsSync6(local))
5784
+ if (!existsSync7(local))
5358
5785
  return [];
5359
5786
  const gone = [];
5360
5787
  for (const name of readdirSync4(local).sort()) {
@@ -5365,23 +5792,23 @@ function sweepBuilds2(root, keep) {
5365
5792
  continue;
5366
5793
  if (m === null && parseArchiveTag2(name) === null)
5367
5794
  continue;
5368
- const path = join5(local, name);
5795
+ const path = join6(local, name);
5369
5796
  try {
5370
- rmSync5(path, { recursive: true, force: true });
5797
+ rmSync6(path, { recursive: true, force: true });
5371
5798
  gone.push(path);
5372
5799
  } catch {}
5373
5800
  }
5374
5801
  return gone;
5375
5802
  }
5376
5803
 
5377
- import { existsSync as existsSync7, readFileSync as readFileSync6 } from "fs";
5378
- import { join as join6 } from "path";
5804
+ import { existsSync as existsSync8, readFileSync as readFileSync6 } from "fs";
5805
+ import { join as join7 } from "path";
5379
5806
  var LATEST_CHECK_MS2 = 60 * 60 * 1000;
5380
5807
  var LATEST_CHECK_BUDGET_MS2 = 1500;
5381
- var latestCheckPath2 = (root) => join6(root, "LATEST.json");
5808
+ var latestCheckPath2 = (root) => join7(root, "LATEST.json");
5382
5809
  function readLatestCheck2(root) {
5383
5810
  const p = latestCheckPath2(root);
5384
- if (!existsSync7(p))
5811
+ if (!existsSync8(p))
5385
5812
  return null;
5386
5813
  try {
5387
5814
  const v = JSON.parse(readFileSync6(p, "utf8"));
@@ -5463,4 +5890,126 @@ function staleWarning2(answered, latest, floor, tags = [], command = "idq") {
5463
5890
  return `answered from ${answered}. ${floorTag ?? `cut ${floor.cut}`} is required${why}: run \`${command} use ${floor.cut}\` to get it`;
5464
5891
  }
5465
5892
 
5466
- export { DATABASES2, planAsset2, sha256File2, unzstdTo2, 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, 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 };
5893
+ var PROGRESS_MODES2 = ["tty", "plain", "none"];
5894
+ function progressMode2(flag, env, isTTY) {
5895
+ for (const [v, from] of [
5896
+ [flag, "--progress"],
5897
+ [env.IDQ_PROGRESS, "IDQ_PROGRESS"]
5898
+ ]) {
5899
+ if (v === undefined || v === "")
5900
+ continue;
5901
+ if (PROGRESS_MODES2.includes(v))
5902
+ return v;
5903
+ throw new Error(`${from} ${v}: expected ${PROGRESS_MODES2.join(", ")}`);
5904
+ }
5905
+ return isTTY ? "tty" : "plain";
5906
+ }
5907
+ var ENDS = new Set(["done", "failed", "cancelled"]);
5908
+ function bytesText(n) {
5909
+ if (n >= 1e9)
5910
+ return `${(n / 1e9).toFixed(2)} GB`;
5911
+ if (n >= 1e6)
5912
+ return `${(n / 1e6).toFixed(1)} MB`;
5913
+ if (n >= 1000)
5914
+ return `${(n / 1000).toFixed(1)} kB`;
5915
+ return `${n} B`;
5916
+ }
5917
+ var secs = (ms) => `${(ms / 1000).toFixed(1)} s`;
5918
+ function plainLine(e, started) {
5919
+ const what = e.kind === "index" ? `${e.tag} index ${e.item}` : `${e.tag} ${e.item}`;
5920
+ if (e.state === "done") {
5921
+ if (e.kind === "index")
5922
+ return `done ${what} in ${secs(e.ms)}`;
5923
+ const size = e.unpacked > 0 ? `${bytesText(e.bytes)} -> ${bytesText(e.unpacked)}` : bytesText(e.bytes);
5924
+ return `done ${what} ${size} in ${secs(e.ms)}`;
5925
+ }
5926
+ if (e.state === "failed")
5927
+ return `failed ${what} after ${secs(e.ms)}: ${e.error ?? "failed"}`;
5928
+ if (e.state === "cancelled")
5929
+ return `cancelled ${what} after ${secs(e.ms)}`;
5930
+ if (started)
5931
+ return null;
5932
+ return `start ${what}${e.total !== null ? ` (${bytesText(e.total)})` : ""}`;
5933
+ }
5934
+ function ttyLine(e, width) {
5935
+ const name = e.kind === "index" ? `index ${e.item}` : e.item;
5936
+ let amount = "";
5937
+ if (e.kind === "asset") {
5938
+ amount = e.total !== null ? `${bytesText(e.bytes)} / ${bytesText(e.total)}` : bytesText(e.bytes);
5939
+ if (e.unpacked > 0)
5940
+ amount += ` unpacked ${bytesText(e.unpacked)}`;
5941
+ if (!ENDS.has(e.state) && e.ms > 0)
5942
+ amount += ` ${bytesText(e.bytes * 1000 / e.ms)}/s`;
5943
+ }
5944
+ const state = e.state === "failed" ? `failed: ${e.error ?? ""}` : ENDS.has(e.state) ? `${e.state} in ${secs(e.ms)}` : e.state;
5945
+ const line = `${name.padEnd(34)} ${amount.padEnd(44)} ${state}`;
5946
+ return line.length > width ? line.slice(0, Math.max(0, width - 1)) : line;
5947
+ }
5948
+ function progressRenderer2(mode, io) {
5949
+ if (mode === "none")
5950
+ return { mode, sink: () => {}, close: () => {} };
5951
+ if (mode === "plain") {
5952
+ const started = new Set;
5953
+ return {
5954
+ mode,
5955
+ sink: (e) => {
5956
+ const key = `${e.tag}\x00${e.kind}\x00${e.item}`;
5957
+ const l = plainLine(e, started.has(key));
5958
+ started.add(key);
5959
+ if (ENDS.has(e.state))
5960
+ started.delete(key);
5961
+ if (l !== null)
5962
+ io.line(l);
5963
+ },
5964
+ close: () => {}
5965
+ };
5966
+ }
5967
+ const write = io.write ?? ((s) => process.stderr.write(s));
5968
+ const columns = io.columns ?? (() => process.stderr.columns ?? 0);
5969
+ const interval = io.intervalMs ?? 100;
5970
+ const items = new Map;
5971
+ let drawn = 0;
5972
+ let timer = null;
5973
+ let dirty = false;
5974
+ const draw = () => {
5975
+ timer = null;
5976
+ if (!dirty)
5977
+ return;
5978
+ dirty = false;
5979
+ const width = Math.max(40, columns() || 120);
5980
+ let out = drawn > 0 ? `\x1B[${drawn}A` : "";
5981
+ for (const e of items.values())
5982
+ out += `\r\x1B[2K${ttyLine(e, width)}
5983
+ `;
5984
+ drawn = items.size;
5985
+ write(out);
5986
+ if ([...items.values()].every((e) => ENDS.has(e.state))) {
5987
+ items.clear();
5988
+ drawn = 0;
5989
+ }
5990
+ };
5991
+ return {
5992
+ mode,
5993
+ sink: (e) => {
5994
+ const key = `${e.tag}\x00${e.kind}\x00${e.item}`;
5995
+ const moved = items.get(key)?.state !== e.state;
5996
+ items.set(key, e);
5997
+ dirty = true;
5998
+ if (moved) {
5999
+ if (timer !== null)
6000
+ clearTimeout(timer);
6001
+ draw();
6002
+ } else if (timer === null) {
6003
+ timer = setTimeout(draw, interval);
6004
+ timer.unref?.();
6005
+ }
6006
+ },
6007
+ close: () => {
6008
+ if (timer !== null)
6009
+ clearTimeout(timer);
6010
+ draw();
6011
+ }
6012
+ };
6013
+ }
6014
+
6015
+ 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 };