@schwabyio/gta 0.14.3 → 0.14.5

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/dist/SPEC.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # The Gravity file format
2
2
 
3
- Version 0.14.3, the version of Gravity and `gta` that reads it: the two are released
3
+ Version 0.14.5, the version of Gravity and `gta` that reads it: the two are released
4
4
  together.
5
5
 
6
6
  This document specifies the YAML files that **Gravity**, the desktop app, and **`gta`**,
@@ -6922,14 +6922,14 @@ var require_parser = __commonJS({
6922
6922
  case "scalar":
6923
6923
  case "single-quoted-scalar":
6924
6924
  case "double-quoted-scalar": {
6925
- const fs12 = this.flowScalar(this.type);
6925
+ const fs11 = this.flowScalar(this.type);
6926
6926
  if (atNextItem || it.value) {
6927
- map2.items.push({ start, key: fs12, sep: [] });
6927
+ map2.items.push({ start, key: fs11, sep: [] });
6928
6928
  this.onKeyLine = true;
6929
6929
  } else if (it.sep) {
6930
- this.stack.push(fs12);
6930
+ this.stack.push(fs11);
6931
6931
  } else {
6932
- Object.assign(it, { key: fs12, sep: [] });
6932
+ Object.assign(it, { key: fs11, sep: [] });
6933
6933
  this.onKeyLine = true;
6934
6934
  }
6935
6935
  return;
@@ -7057,13 +7057,13 @@ var require_parser = __commonJS({
7057
7057
  case "scalar":
7058
7058
  case "single-quoted-scalar":
7059
7059
  case "double-quoted-scalar": {
7060
- const fs12 = this.flowScalar(this.type);
7060
+ const fs11 = this.flowScalar(this.type);
7061
7061
  if (!it || it.value)
7062
- fc.items.push({ start: [], key: fs12, sep: [] });
7062
+ fc.items.push({ start: [], key: fs11, sep: [] });
7063
7063
  else if (it.sep)
7064
- this.stack.push(fs12);
7064
+ this.stack.push(fs11);
7065
7065
  else
7066
- Object.assign(it, { key: fs12, sep: [] });
7066
+ Object.assign(it, { key: fs11, sep: [] });
7067
7067
  return;
7068
7068
  }
7069
7069
  case "flow-map-end":
@@ -56108,8 +56108,62 @@ var runtime = defaultRuntime();
56108
56108
  var MAX_DIFF_BYTES = 2 * 1024 * 1024;
56109
56109
 
56110
56110
  // ../../packages/core/src/workspace/project.ts
56111
- import fs2 from "node:fs/promises";
56112
56111
  import path from "node:path";
56112
+
56113
+ // ../../packages/core/src/workspace/readFolder.ts
56114
+ import fs2 from "node:fs/promises";
56115
+ var PASSING = /* @__PURE__ */ new Set(["EBUSY", "EPERM", "EACCES", "EAGAIN", "EMFILE", "ENFILE", "UNKNOWN"]);
56116
+ var RETRY_MS = [50, 200, 600];
56117
+ var ABSENT = /* @__PURE__ */ new Set(["ENOENT", "ENOTDIR"]);
56118
+ var UnreadableError = class extends Error {
56119
+ constructor(path13, code, cause) {
56120
+ const said = cause instanceof Error ? cause.message : String(cause);
56121
+ const reason = /^(\w+: [^,]+)/.exec(said)?.[1] ?? code;
56122
+ super(`Could not read ${path13} (${reason})`, { cause });
56123
+ this.path = path13;
56124
+ this.code = code;
56125
+ this.name = "UnreadableError";
56126
+ this.reason = reason;
56127
+ }
56128
+ path;
56129
+ code;
56130
+ /** Why, as a person reads it: `EBUSY: resource busy or locked`. */
56131
+ reason;
56132
+ };
56133
+ var observer = null;
56134
+ var codeOf = (cause) => cause?.code ?? "UNKNOWN";
56135
+ async function tried(target, read) {
56136
+ let code = "";
56137
+ for (let attempt = 0; ; attempt++) {
56138
+ try {
56139
+ const result = await read();
56140
+ if (attempt > 0) observer?.({ path: target, code, tries: attempt + 1, read: true });
56141
+ return result;
56142
+ } catch (cause) {
56143
+ code = codeOf(cause);
56144
+ if (ABSENT.has(code)) throw cause;
56145
+ const wait = RETRY_MS[attempt];
56146
+ if (!PASSING.has(code) || wait === void 0) {
56147
+ if (attempt > 0) observer?.({ path: target, code, tries: attempt + 1, read: false });
56148
+ throw new UnreadableError(target, code, cause);
56149
+ }
56150
+ await new Promise((resolve) => setTimeout(resolve, wait));
56151
+ }
56152
+ }
56153
+ }
56154
+ async function readFolder(directory) {
56155
+ try {
56156
+ return await tried(directory, () => fs2.readdir(directory, { withFileTypes: true }));
56157
+ } catch (cause) {
56158
+ if (ABSENT.has(codeOf(cause))) return [];
56159
+ throw cause;
56160
+ }
56161
+ }
56162
+ function readText(file2) {
56163
+ return tried(file2, () => fs2.readFile(file2, "utf8"));
56164
+ }
56165
+
56166
+ // ../../packages/core/src/workspace/project.ts
56113
56167
  var HOMES = /* @__PURE__ */ new Set([COLLECTIONS_DIR, REQUESTS_DIR, ENDPOINTS_DIR, BASES_DIR]);
56114
56168
  function projectRootOf(collectionFile) {
56115
56169
  const directory = path.dirname(path.resolve(collectionFile));
@@ -56127,14 +56181,14 @@ var isCollectionFile = (name) => name.endsWith(DOC_EXTENSION) && !name.startsWit
56127
56181
  async function discoverProject(root) {
56128
56182
  const collectionsDir = path.join(root, COLLECTIONS_DIR);
56129
56183
  const layout = { root, collectionsDir, files: [], directories: [], problems: [] };
56130
- const entries2 = await readDir(collectionsDir);
56184
+ const entries2 = await readFolder(collectionsDir);
56131
56185
  for (const entry of entries2) {
56132
56186
  if (entry.isFile() && isCollectionFile(entry.name)) {
56133
56187
  layout.files.push(path.join(collectionsDir, entry.name));
56134
56188
  } else if (entry.isDirectory() && !isHidden(entry.name)) {
56135
56189
  layout.directories.push(entry.name);
56136
56190
  const directory = path.join(collectionsDir, entry.name);
56137
- for (const inner of await readDir(directory)) {
56191
+ for (const inner of await readFolder(directory)) {
56138
56192
  if (inner.isFile() && isCollectionFile(inner.name)) {
56139
56193
  layout.files.push(path.join(directory, inner.name));
56140
56194
  } else if (inner.isDirectory() && !isHidden(inner.name)) {
@@ -56165,7 +56219,7 @@ var PROJECT_NAMES = [
56165
56219
  ];
56166
56220
  async function portabilityProblems(root) {
56167
56221
  const problems = [];
56168
- const atRoot = (await readDir(root)).map((entry) => entry.name);
56222
+ const atRoot = (await advisory(root)).map((entry) => entry.name);
56169
56223
  for (const wanted of PROJECT_NAMES) {
56170
56224
  if (atRoot.includes(wanted)) continue;
56171
56225
  const variant = atRoot.find((name) => foldName(name) === foldName(wanted));
@@ -56177,14 +56231,14 @@ async function portabilityProblems(root) {
56177
56231
  }
56178
56232
  }
56179
56233
  for (const home of [COLLECTIONS_DIR, REQUESTS_DIR, ENDPOINTS_DIR, BASES_DIR]) {
56180
- const directories = (await readDir(path.join(root, home))).filter((entry) => entry.isDirectory() && !isHidden(entry.name)).map((entry) => entry.name);
56234
+ const directories = (await advisory(path.join(root, home))).filter((entry) => entry.isDirectory() && !isHidden(entry.name)).map((entry) => entry.name);
56181
56235
  problems.push(...portableNameProblems(home, directories));
56182
56236
  }
56183
56237
  for (const [home, extension] of [
56184
56238
  [ENVIRONMENTS_DIR, DOC_EXTENSION],
56185
56239
  [CHECKS_DIR, ".js"]
56186
56240
  ]) {
56187
- const files = (await readDir(path.join(root, home))).filter((entry) => entry.isFile() && entry.name.endsWith(extension)).map((entry) => entry.name);
56241
+ const files = (await advisory(path.join(root, home))).filter((entry) => entry.isFile() && entry.name.endsWith(extension)).map((entry) => entry.name);
56188
56242
  problems.push(...portableNameProblems(home, files));
56189
56243
  }
56190
56244
  return problems;
@@ -56235,11 +56289,11 @@ async function readProject(root) {
56235
56289
  return info;
56236
56290
  }
56237
56291
  const global = await readProjectFile(target);
56238
- if (global.source === null) {
56292
+ if (global.source === null && !global.problem) {
56239
56293
  problem(`uses: ${uses} \u2014 there is no ${PROJECT_FILE} there, so it is not a project`);
56240
56294
  return info;
56241
56295
  }
56242
- if (global.problem || !global.doc) {
56296
+ if (global.problem || !global.doc || global.source === null) {
56243
56297
  problem(`uses: ${uses} \u2014 its ${PROJECT_FILE} will not read: ${global.problem?.message ?? ""}`);
56244
56298
  return info;
56245
56299
  }
@@ -56253,9 +56307,10 @@ async function readProject(root) {
56253
56307
  async function readProjectFile(root) {
56254
56308
  let source;
56255
56309
  try {
56256
- source = await fs2.readFile(path.join(root, PROJECT_FILE), "utf8");
56257
- } catch {
56258
- return { doc: null, source: null, problem: null };
56310
+ source = await readText(path.join(root, PROJECT_FILE));
56311
+ } catch (cause) {
56312
+ const problem = cause instanceof UnreadableError ? { path: PROJECT_FILE, message: `could not be read (${cause.reason})` } : null;
56313
+ return { doc: null, source: null, problem };
56259
56314
  }
56260
56315
  try {
56261
56316
  return { doc: parseProject(source, PROJECT_FILE).data, source, problem: null };
@@ -56277,28 +56332,22 @@ async function projectEnvironments(root, global) {
56277
56332
  }
56278
56333
  async function readEnvironments(directory, source = "project") {
56279
56334
  const environments = [];
56280
- for (const entry of await readDir(directory)) {
56335
+ for (const entry of await readFolder(directory)) {
56281
56336
  if (!entry.isFile() || !entry.name.endsWith(DOC_EXTENSION)) continue;
56282
56337
  const file2 = path.join(directory, entry.name);
56283
56338
  let name = path.basename(entry.name, DOC_EXTENSION);
56284
56339
  try {
56285
- name = parseEnvironment(await fs2.readFile(file2, "utf8"), file2).data.name ?? name;
56286
- } catch {
56340
+ name = parseEnvironment(await readText(file2), file2).data.name ?? name;
56341
+ } catch (cause) {
56342
+ if (cause instanceof UnreadableError) throw cause;
56287
56343
  }
56288
56344
  environments.push({ name, path: file2, source });
56289
56345
  }
56290
56346
  return environments.sort((a, b) => a.name.localeCompare(b.name));
56291
56347
  }
56292
- async function readDir(directory) {
56293
- try {
56294
- return await fs2.readdir(directory, { withFileTypes: true });
56295
- } catch {
56296
- return [];
56297
- }
56298
- }
56348
+ var advisory = (directory) => readFolder(directory).catch(() => []);
56299
56349
 
56300
56350
  // ../../packages/core/src/workspace/collection.ts
56301
- import fs4 from "node:fs/promises";
56302
56351
  import path4 from "node:path";
56303
56352
 
56304
56353
  // ../../packages/core/src/workspace/ids.ts
@@ -56385,7 +56434,7 @@ async function loadCollection(file2, root) {
56385
56434
  let doc = { steps: [] };
56386
56435
  const problems = [];
56387
56436
  try {
56388
- doc = parseCollection(await fs4.readFile(absolute, "utf8"), relativePath2).data;
56437
+ doc = parseCollection(await readText(absolute), relativePath2).data;
56389
56438
  } catch (cause) {
56390
56439
  problems.push({
56391
56440
  path: relativePath2,
@@ -56430,7 +56479,7 @@ async function loadProjectCollections(root, files) {
56430
56479
  }
56431
56480
 
56432
56481
  // ../../packages/core/src/workspace/projectTls.ts
56433
- import fs5 from "node:fs/promises";
56482
+ import fs4 from "node:fs/promises";
56434
56483
  import path5 from "node:path";
56435
56484
 
56436
56485
  // ../../packages/core/src/http/trust.ts
@@ -56540,7 +56589,7 @@ function entries(doc, root, source) {
56540
56589
  async function readCaFile(file2) {
56541
56590
  let bytes;
56542
56591
  try {
56543
- bytes = await fs5.readFile(file2);
56592
+ bytes = await fs4.readFile(file2);
56544
56593
  } catch (cause) {
56545
56594
  const code = cause.code;
56546
56595
  return {
@@ -56568,17 +56617,17 @@ var import_yaml6 = __toESM(require_dist(), 1);
56568
56617
  var import_yaml5 = __toESM(require_dist(), 1);
56569
56618
 
56570
56619
  // ../../packages/core/src/workspace/library.ts
56571
- import fs6 from "node:fs/promises";
56620
+ import fs5 from "node:fs/promises";
56572
56621
  import path6 from "node:path";
56573
56622
  var GLOBAL_PREFIX = "global:";
56574
56623
  async function libraryFiles(root, home) {
56575
56624
  const directory = path6.join(root, home);
56576
56625
  const files = [];
56577
- for (const entry of await readDir2(directory)) {
56626
+ for (const entry of await readFolder(directory)) {
56578
56627
  if (entry.isFile() && entry.name.endsWith(DOC_EXTENSION)) {
56579
56628
  files.push(path6.join(directory, entry.name));
56580
56629
  } else if (entry.isDirectory() && !entry.name.startsWith(".")) {
56581
- for (const inner of await readDir2(path6.join(directory, entry.name))) {
56630
+ for (const inner of await readFolder(path6.join(directory, entry.name))) {
56582
56631
  if (inner.isFile() && inner.name.endsWith(DOC_EXTENSION)) {
56583
56632
  files.push(path6.join(directory, entry.name, inner.name));
56584
56633
  }
@@ -56600,7 +56649,7 @@ async function filesIn(root, home, source) {
56600
56649
  async function readSet(file2, directory, source) {
56601
56650
  const name = relativePosix(directory, file2).slice(0, -DOC_EXTENSION.length);
56602
56651
  try {
56603
- const doc = parseCollection(await fs6.readFile(file2, "utf8"), file2).data;
56652
+ const doc = parseCollection(await readText(file2), file2).data;
56604
56653
  const problem = idProblem(doc, file2);
56605
56654
  return problem ? { name, path: file2, source, doc, problem } : { name, path: file2, source, doc };
56606
56655
  } catch (cause) {
@@ -56646,10 +56695,10 @@ async function resolveIn(root, global, reference, home, key) {
56646
56695
  }
56647
56696
  async function sharedId(file2, directory) {
56648
56697
  const names = [];
56649
- for (const entry of await readDir2(directory)) {
56698
+ for (const entry of await readFolder(directory)) {
56650
56699
  if (entry.isFile()) names.push(path6.join(directory, entry.name));
56651
56700
  else if (entry.isDirectory() && !entry.name.startsWith(".")) {
56652
- for (const inner of await readDir2(path6.join(directory, entry.name))) {
56701
+ for (const inner of await readFolder(path6.join(directory, entry.name))) {
56653
56702
  if (inner.isFile()) names.push(path6.join(directory, entry.name, inner.name));
56654
56703
  }
56655
56704
  }
@@ -56661,7 +56710,7 @@ async function loadChecks(root, global) {
56661
56710
  const byName = /* @__PURE__ */ new Map();
56662
56711
  for (const place of [...global ? [global.root] : [], root]) {
56663
56712
  const directory = path6.join(place, CHECKS_DIR);
56664
- for (const entry of await readDir2(directory)) {
56713
+ for (const entry of await readFolder(directory)) {
56665
56714
  if (!entry.isFile() || !entry.name.endsWith(".js")) continue;
56666
56715
  const name = entry.name.slice(0, -".js".length);
56667
56716
  if (!/^[A-Za-z_$][\w$]*$/.test(name)) continue;
@@ -56670,7 +56719,7 @@ async function loadChecks(root, global) {
56670
56719
  name,
56671
56720
  filename: relativePosix(root, file2),
56672
56721
  // Windows editors may save a byte order mark, which would hide `export` on line 1.
56673
- code: stripBom(await fs6.readFile(file2, "utf8"))
56722
+ code: stripBom(await readText(file2))
56674
56723
  });
56675
56724
  }
56676
56725
  }
@@ -56741,14 +56790,7 @@ async function loadLibrary(collectionPath) {
56741
56790
  tls: tls2
56742
56791
  };
56743
56792
  }
56744
- async function readDir2(directory) {
56745
- try {
56746
- return await fs6.readdir(directory, { withFileTypes: true });
56747
- } catch {
56748
- return [];
56749
- }
56750
- }
56751
- var exists = (file2) => fs6.stat(file2).then((stat) => stat.isFile()).catch(() => false);
56793
+ var exists = (file2) => fs5.stat(file2).then((stat) => stat.isFile()).catch(() => false);
56752
56794
 
56753
56795
  // ../../packages/core/src/workspace/environmentEdits.ts
56754
56796
  var import_yaml7 = __toESM(require_dist(), 1);
@@ -56782,14 +56824,14 @@ var LF_BLOCK = `${[
56782
56824
 
56783
56825
  // ../../packages/core/src/workspace/yamlMap.ts
56784
56826
  var import_yaml8 = __toESM(require_dist(), 1);
56785
- import fs7 from "node:fs/promises";
56827
+ import fs6 from "node:fs/promises";
56786
56828
  var YamlMapError = class extends Error {
56787
56829
  name = "YamlMapError";
56788
56830
  };
56789
56831
  async function readYamlMap(file2, label, example) {
56790
56832
  let source;
56791
56833
  try {
56792
- source = await fs7.readFile(file2, "utf8");
56834
+ source = await fs6.readFile(file2, "utf8");
56793
56835
  } catch (cause) {
56794
56836
  if (cause.code === "ENOENT") return null;
56795
56837
  throw new YamlMapError(`${label} cannot be read: ${cause.message}`);
@@ -57170,7 +57212,7 @@ function resolveNamed(name, scope, seen) {
57170
57212
  }
57171
57213
 
57172
57214
  // ../../packages/core/src/vars/resolve.ts
57173
- import fs8 from "node:fs/promises";
57215
+ import fs7 from "node:fs/promises";
57174
57216
  import path7 from "node:path";
57175
57217
  async function buildScope(context) {
57176
57218
  const layers = [];
@@ -57222,14 +57264,14 @@ async function firstRow(collectionPath) {
57222
57264
  }
57223
57265
  async function readCollectionExtends(file2) {
57224
57266
  try {
57225
- return parseCollection(await fs8.readFile(file2, "utf8"), file2).data.extends ?? null;
57267
+ return parseCollection(await fs7.readFile(file2, "utf8"), file2).data.extends ?? null;
57226
57268
  } catch {
57227
57269
  return null;
57228
57270
  }
57229
57271
  }
57230
57272
  async function readCollectionVars(file2) {
57231
57273
  try {
57232
- return parseCollection(await fs8.readFile(file2, "utf8"), file2).data.vars ?? null;
57274
+ return parseCollection(await fs7.readFile(file2, "utf8"), file2).data.vars ?? null;
57233
57275
  } catch {
57234
57276
  return null;
57235
57277
  }
@@ -57257,7 +57299,7 @@ async function environmentLayers(context, name) {
57257
57299
  const directory = path7.join(place, ENVIRONMENTS_DIR);
57258
57300
  const file2 = await findEnvironmentFile(directory, name, context.environments);
57259
57301
  if (!file2) continue;
57260
- const doc = edited(file2) ?? parseEnvironment(await fs8.readFile(file2, "utf8"), file2).data;
57302
+ const doc = edited(file2) ?? parseEnvironment(await fs7.readFile(file2, "utf8"), file2).data;
57261
57303
  layers.push(layerOf(relativePosix(root, file2), doc, env, dotEnv));
57262
57304
  }
57263
57305
  if (layers.length === 0) throw new InterpolationError(`Environment "${name}" was not found`);
@@ -57300,7 +57342,7 @@ async function environmentFlags(root, name, environments) {
57300
57342
  const directory = path7.join(place, ENVIRONMENTS_DIR);
57301
57343
  const file2 = await findEnvironmentFile(directory, name, environments);
57302
57344
  if (!file2) continue;
57303
- const doc = edited(file2) ?? parseEnvironment(await fs8.readFile(file2, "utf8"), file2).data;
57345
+ const doc = edited(file2) ?? parseEnvironment(await fs7.readFile(file2, "utf8"), file2).data;
57304
57346
  Object.assign(values, doc.flags?.values ?? {});
57305
57347
  if (doc.flags?.command) command = { command: doc.flags.command, cwd: place };
57306
57348
  }
@@ -57313,7 +57355,7 @@ async function findEnvironmentFile(directory, name, edited) {
57313
57355
  if (renamed) return renamed.path;
57314
57356
  let entries2;
57315
57357
  try {
57316
- entries2 = await fs8.readdir(directory);
57358
+ entries2 = await fs7.readdir(directory);
57317
57359
  } catch {
57318
57360
  return null;
57319
57361
  }
@@ -57324,7 +57366,7 @@ async function findEnvironmentFile(directory, name, edited) {
57324
57366
  if (!entry.endsWith(".yml")) continue;
57325
57367
  const file2 = path7.join(directory, entry);
57326
57368
  try {
57327
- if (parseEnvironment(await fs8.readFile(file2, "utf8"), file2).data.name === name) return file2;
57369
+ if (parseEnvironment(await fs7.readFile(file2, "utf8"), file2).data.name === name) return file2;
57328
57370
  } catch {
57329
57371
  }
57330
57372
  }
@@ -57332,7 +57374,7 @@ async function findEnvironmentFile(directory, name, edited) {
57332
57374
  }
57333
57375
  async function readDotEnv(near) {
57334
57376
  try {
57335
- return parseDotEnv(await fs8.readFile(path7.join(near, ".env"), "utf8"));
57377
+ return parseDotEnv(await fs7.readFile(path7.join(near, ".env"), "utf8"));
57336
57378
  } catch {
57337
57379
  return {};
57338
57380
  }
@@ -57947,7 +57989,7 @@ function toSentRequest(step, collection) {
57947
57989
  }
57948
57990
 
57949
57991
  // ../../packages/core/src/run/prepareRequest.ts
57950
- import fs9 from "node:fs/promises";
57992
+ import fs8 from "node:fs/promises";
57951
57993
  var BodyFileError = class extends Error {
57952
57994
  name = "BodyFileError";
57953
57995
  };
@@ -58028,7 +58070,7 @@ function bodyFiles(body) {
58028
58070
  async function readBodyFile(roots, written, what) {
58029
58071
  const { file: file2, shown } = await findBodyFile(roots, written, what);
58030
58072
  try {
58031
- return { bytes: await fs9.readFile(file2), shown };
58073
+ return { bytes: await fs8.readFile(file2), shown };
58032
58074
  } catch (cause) {
58033
58075
  throw new BodyFileError(`${what}: ${written} \u2014 ${cause.message}`);
58034
58076
  }
@@ -58075,7 +58117,7 @@ async function fileIn(root, relative, written, what) {
58075
58117
  if (misspelled) throw new BodyFileError(`${what}: ${misspelled}`);
58076
58118
  let stat;
58077
58119
  try {
58078
- stat = await fs9.stat(file2);
58120
+ stat = await fs8.stat(file2);
58079
58121
  } catch (cause) {
58080
58122
  const code = cause.code;
58081
58123
  if (code === "ENOENT" || code === "ENOTDIR") return null;
@@ -66608,7 +66650,7 @@ function statusCheckFunctions(files) {
66608
66650
 
66609
66651
  // ../../packages/core/src/rules/lint.ts
66610
66652
  var import_yaml9 = __toESM(require_dist(), 1);
66611
- import fs10 from "node:fs/promises";
66653
+ import fs9 from "node:fs/promises";
66612
66654
  import path11 from "node:path";
66613
66655
  var isBlank = (text) => text === void 0 || text.trim() === "";
66614
66656
  function locator(source) {
@@ -66860,7 +66902,7 @@ async function lintProject(root, loaded, global = null) {
66860
66902
  const relativePath2 = relativePosix(path11.join(root, home), file2);
66861
66903
  let source;
66862
66904
  try {
66863
- source = await fs10.readFile(file2, "utf8");
66905
+ source = await fs9.readFile(file2, "utf8");
66864
66906
  } catch (cause) {
66865
66907
  inHome.push({
66866
66908
  file: `${home}/${relativePath2}`,
@@ -66881,7 +66923,7 @@ async function lintProject(root, loaded, global = null) {
66881
66923
 
66882
66924
  // src/job.ts
66883
66925
  var import_yaml10 = __toESM(require_dist(), 1);
66884
- import fs11 from "node:fs/promises";
66926
+ import fs10 from "node:fs/promises";
66885
66927
  import path12 from "node:path";
66886
66928
  var unattempted = (summary) => summary.total - summary.results.length;
66887
66929
  var allSkipped = (summary) => summary.results.length > 0 && summary.results.every((r) => r.status === "skipped");
@@ -66943,7 +66985,7 @@ async function stepLines(file2) {
66943
66985
  const lines = { setup: [], steps: [], teardown: [] };
66944
66986
  try {
66945
66987
  const counter = new import_yaml10.LineCounter();
66946
- const document = (0, import_yaml10.parseDocument)(await fs11.readFile(file2, "utf8"), { lineCounter: counter });
66988
+ const document = (0, import_yaml10.parseDocument)(await fs10.readFile(file2, "utf8"), { lineCounter: counter });
66947
66989
  const of = (key) => {
66948
66990
  const list2 = document.get(key, true);
66949
66991
  if (!list2 || typeof list2 !== "object" || !("items" in list2)) return [];
@@ -22,7 +22,7 @@ import {
22
22
  resolveFlags,
23
23
  resultName,
24
24
  stepsForTags
25
- } from "./chunk-S563G22M.js";
25
+ } from "./chunk-IL3UPOUK.js";
26
26
 
27
27
  // src/args.ts
28
28
  var UsageError = class extends Error {
@@ -9,7 +9,7 @@ import {
9
9
  loadProject,
10
10
  paint,
11
11
  tallyOf
12
- } from "./chunk-4W4TFJ53.js";
12
+ } from "./chunk-NKT7CXVX.js";
13
13
  import {
14
14
  COLLECTIONS_DIR,
15
15
  canonical,
@@ -20,7 +20,7 @@ import {
20
20
  secretValues,
21
21
  stepLabel,
22
22
  stepLines
23
- } from "./chunk-S563G22M.js";
23
+ } from "./chunk-IL3UPOUK.js";
24
24
 
25
25
  // src/library/project.ts
26
26
  import path from "node:path";
package/dist/gta.js CHANGED
@@ -28,7 +28,7 @@ import {
28
28
  tableWidth,
29
29
  tally,
30
30
  totalsOf
31
- } from "./chunks/chunk-4W4TFJ53.js";
31
+ } from "./chunks/chunk-NKT7CXVX.js";
32
32
  import {
33
33
  COLLECTIONS_DIR,
34
34
  MARK_GLYPH,
@@ -59,7 +59,7 @@ import {
59
59
  sortArraysBy,
60
60
  toJsonLines,
61
61
  unattempted
62
- } from "./chunks/chunk-S563G22M.js";
62
+ } from "./chunks/chunk-IL3UPOUK.js";
63
63
 
64
64
  // src/main.ts
65
65
  import fs from "node:fs/promises";
@@ -367,7 +367,7 @@ function summaryPage(run2) {
367
367
  const rows = run2.collections.map((c, i) => {
368
368
  const t = c.tally;
369
369
  const status = statuses[i];
370
- return `<tr class="s-${status}" data-status="${status}" data-search="${escape(c.id.toLowerCase())}"><td class="r n">${i + 1}</td><td class="name"><a href="${escape(encodeURI(pageOf(c.id)))}">${escape(c.id)}</a></td><td class="r">${duration(c.durationMs)}</td><td class="r gs">${t.steps.total}</td><td class="r">${num(t.steps.passed)}</td><td class="r">${num(t.steps.failed, true)}</td><td class="r">${num(t.steps.errored, true)}</td><td class="r">${num(t.steps.skipped)}</td><td class="r gs">${t.assertions.total}</td><td class="r">${num(t.assertions.passed)}</td><td class="r">${num(t.assertions.failed, true)}</td><td class="gs">${pill(status)}</td></tr>`;
370
+ return `<tr class="s-${status}" data-status="${status}" data-search="${escape(c.id.toLowerCase())}"><td class="r n">${i + 1}</td><td class="name"><a href="${escape(encodeURI(pageOf(c.id)))}" target="_blank" rel="noopener">${breakable(c.id)}</a></td><td class="r">${duration(c.durationMs)}</td><td class="r gs">${t.steps.total}</td><td class="r">${num(t.steps.passed)}</td><td class="r">${num(t.steps.failed, true)}</td><td class="r">${num(t.steps.errored, true)}</td><td class="r">${num(t.steps.skipped)}</td><td class="r gs">${t.assertions.total}</td><td class="r">${num(t.assertions.passed)}</td><td class="r">${num(t.assertions.failed, true)}</td><td class="gs">${pill(status)}</td></tr>`;
371
371
  }).join("\n");
372
372
  const notRun = run2.excluded.length > 0 || run2.untagged > 0 ? card(
373
373
  "Not Run",
@@ -381,7 +381,7 @@ function summaryPage(run2) {
381
381
  return page({
382
382
  title: `${passed ? "PASSED" : "FAILED"} \xB7 ${run2.project} \xB7 gta Summary Results`,
383
383
  run: run2,
384
- body: `${pageHead(passed ? "passed" : "failed", `${escape(run2.project)} <span class="kind">\xB7 Summary Results</span>`, run2.startedAt, run2.durationMs)}
384
+ body: `${pageHead(passed ? "passed" : "failed", `${escape(run2.project)} <span class="kind">\xB7 Summary Results</span>`, run2.startedAt)}
385
385
 
386
386
  ${card("Settings", `<dl class="pairs">
387
387
  ${settings.map(([label, value]) => `<div><dt>${label}</dt><dd>${value}</dd></div>`).join("\n")}
@@ -405,6 +405,7 @@ ${ringStat("Assertions", [
405
405
  ["passed", sum((t) => t.assertions.passed)],
406
406
  ["failed", sum((t) => t.assertions.failed)]
407
407
  ])}
408
+ ${timeStat("Total Run Time", run2.durationMs)}
408
409
  </div>`
409
410
  )}
410
411
 
@@ -418,7 +419,8 @@ ${rows}
418
419
  </tbody></table></div>`,
419
420
  {
420
421
  flush: true,
421
- tools: `${chips({ passed: count("passed"), failed: count("failed"), skipped: count("skipped") })}${searchBox("Search collections")}`
422
+ // As a collection page's Tests: the search by the title, what to show at the far end.
423
+ tools: `${searchBox("Search collections")}<div class="tools-end">${chips({ passed: count("passed"), failed: count("failed"), skipped: count("skipped") })}</div>`
422
424
  }
423
425
  )}
424
426
  ${notRun}`
@@ -438,13 +440,13 @@ function collectionPage(run2, c) {
438
440
  title: `${status.toUpperCase()} \xB7 ${c.id} \xB7 gta Collection Results`,
439
441
  run: run2,
440
442
  body: `<nav class="crumbs"><a href="${SUMMARY_PAGE}">Summary</a><span aria-hidden="true">/</span><span>${escape(c.id)}</span></nav>
441
- ${pageHead(status, escape(c.id), c.startedAt, c.durationMs)}
443
+ ${pageHead(status, escape(c.id), c.startedAt)}
442
444
 
443
445
  ${card(
444
446
  "Results Overview",
445
447
  [
446
448
  c.docs?.trim() ? markdown(c.docs) : "",
447
- data ? `<dl class="pairs"><div><dt>Data File</dt><dd><code>${escape(data.file)}</code></dd></div><div><dt>Iterations</dt><dd>${data.rows}</dd></div></dl>` : "",
449
+ data ? `<dl class="pairs"><div><dt>Data File</dt><dd><code>${escape(data.file)}</code></dd></div><div><dt>Iterations</dt><dd>${thousands(data.rows)}</dd></div></dl>` : "",
448
450
  !c.outcome.ok ? `<section class="sub problem"><h3>Error</h3><pre>${escape(c.outcome.message)}</pre></section>` : "",
449
451
  `<div class="stats">
450
452
  ${ringStat("Tests", [
@@ -457,6 +459,7 @@ ${ringStat("Assertions", [
457
459
  ["passed", t.assertions.passed],
458
460
  ["failed", t.assertions.failed]
459
461
  ])}
462
+ ${timeStat("Total Run Time", c.durationMs)}
460
463
  </div>`
461
464
  ].filter(Boolean).join("\n"),
462
465
  { tools: `<code class="file">${escape(c.file)}</code>` }
@@ -822,7 +825,7 @@ ${options.body}
822
825
  </html>
823
826
  `;
824
827
  }
825
- var pageHead = (status, title, startedAt, ms) => `<header class="head"><div class="title">${pill(status, true)}<h1>${title}</h1></div><div class="meta"><span>${escape(timeOfRun(startedAt))}</span><span>${duration(ms)}</span></div></header>`;
828
+ var pageHead = (status, title, startedAt) => `<header class="head"><div class="title">${pill(status, true)}<h1>${title}</h1></div><div class="meta"><span>${escape(timeOfRun(startedAt))}</span></div></header>`;
826
829
  var card = (title, body, options = {}) => `<section class="card"${options.id ? ` id="${options.id}"` : ""}><div class="card-h"><h2>${title}</h2>${options.tools ?? ""}</div><div class="card-body${options.flush ? " flush" : ""}">
827
830
  ${body}
828
831
  </div></section>`;
@@ -842,8 +845,10 @@ function ringStat(label, parts) {
842
845
  const passed = parts.find(([s]) => s === "passed")?.[1] ?? 0;
843
846
  const ran = total - (parts.find(([s]) => s === "skipped")?.[1] ?? 0);
844
847
  const rate = ran === 0 ? "\u2014" : `${Math.floor(passed / ran * 100)}%`;
845
- return `<div class="stat"><div class="ring" title="${passed} of ${ran} that ran passed">${ring(parts)}<b>${rate}</b></div><div><div class="stat-label">${label}</div><div class="stat-total">${total}</div><div class="legend">${parts.map(([s, v]) => `<span class="${v ? `s-${s}` : "zero"}"><i></i>${v} ${s}</span>`).join("")}</div></div></div>`;
848
+ return `<div class="stat"><div class="ring" title="${thousands(passed)} of ${thousands(ran)} that ran passed">${ring(parts)}<b>${rate}</b></div><div><div class="stat-label">${label}</div><div class="stat-total">${thousands(total)}</div><div class="legend">${parts.map(([s, v]) => `<span class="${v ? `s-${s}` : "zero"}"><i></i>${thousands(v)} ${s}</span>`).join("")}</div></div></div>`;
846
849
  }
850
+ var timeStat = (label, ms) => `<div class="stat"><div class="ring clock">${clock}</div><div><div class="stat-label">${label}</div><div class="stat-time" title="${thousands(Math.round(ms))} ms">${runTime(ms).replace(/(\d) /g, "$1&nbsp;")}</div></div></div>`;
851
+ var clock = `<svg viewBox="0 0 64 64" width="64" height="64" aria-hidden="true"><circle cx="32" cy="32" r="25" fill="none" stroke-width="7" style="stroke:var(--surface-alt)"/><path d="M32 19v13l9 6" fill="none" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" style="stroke:var(--dim)"/></svg>`;
847
852
  function ring(parts) {
848
853
  const R = 25;
849
854
  const C = 2 * Math.PI * R;
@@ -878,7 +883,19 @@ function clip(text) {
878
883
  var userFrames = (stack) => stack.split("\n").filter((line) => !/^\s+at .*(\bnode:|file:\/\/)/.test(line)).join("\n");
879
884
  var capitalise = (text) => text.charAt(0).toUpperCase() + text.slice(1);
880
885
  var plural2 = (n, word) => `${n} ${word}${n === 1 ? "" : "s"}`;
886
+ var thousands = (n) => n.toLocaleString("en-US");
881
887
  var duration = (ms) => ms < 1e3 ? `${Math.round(ms)} ms` : `${(ms / 1e3).toFixed(2)} s`;
888
+ function runTime(ms) {
889
+ const hundredths = Math.round(ms / 10);
890
+ const hours = Math.floor(hundredths / 36e4);
891
+ const minutes = Math.floor(hundredths % 36e4 / 6e3);
892
+ const seconds2 = hundredths % 6e3;
893
+ return [
894
+ hours > 0 ? plural2(hours, "hour") : "",
895
+ minutes > 0 ? plural2(minutes, "minute") : "",
896
+ seconds2 > 0 || hundredths < 6e3 ? `${(seconds2 / 100).toFixed(2)} seconds` : ""
897
+ ].filter(Boolean).join(" ");
898
+ }
882
899
  function timeoutText(ms) {
883
900
  const round = ms >= 36e5 && ms % 36e5 === 0 ? `${ms / 36e5} h` : ms >= 6e4 && ms % 6e4 === 0 ? `${ms / 6e4} min` : ms >= 1e3 && ms % 1e3 === 0 ? `${ms / 1e3} s` : "";
884
901
  return `${ms.toLocaleString("en-US")} ms${round ? ` <span class="dim">\xB7 ${round}</span>` : ""}`;
@@ -892,6 +909,7 @@ function bytes(n) {
892
909
  if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)} KB`;
893
910
  return `${(n / 1024 / 1024).toFixed(1)} MB`;
894
911
  }
912
+ var breakable = (value) => escape(value).replace(/([_/.-])/g, "$1<wbr>");
895
913
  function escape(value) {
896
914
  return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
897
915
  }
@@ -964,7 +982,7 @@ pre{margin:0;white-space:pre-wrap;word-break:break-word}
964
982
  .card{background:var(--surface);border:1px solid var(--border);border-radius:8px;margin-bottom:16px;min-width:0}
965
983
  .card-h{display:flex;flex-wrap:wrap;align-items:center;gap:8px 10px;padding:10px 16px;border-bottom:1px solid var(--border-soft)}
966
984
  .card-h h2{margin:0 6px 0 0;font-size:13.5px;font-weight:650}
967
- .card-h .search,.card-h .file{margin-left:auto}
985
+ .card-h .file{margin-left:auto}
968
986
  .card-h .file{color:var(--dim);overflow-wrap:anywhere}
969
987
  .card-body{padding:16px}
970
988
  .card-body>*+*{margin-top:16px}
@@ -980,6 +998,7 @@ pre{margin:0;white-space:pre-wrap;word-break:break-word}
980
998
  .ring b{position:absolute;inset:0;display:grid;place-items:center;font-size:13.5px;font-variant-numeric:tabular-nums}
981
999
  .stat-label{font-size:12px;color:var(--dim)}
982
1000
  .stat-total{font-size:24px;font-weight:650;font-variant-numeric:tabular-nums;line-height:1;margin-top:3px}
1001
+ .stat-time{font-size:17px;font-weight:650;font-variant-numeric:tabular-nums;line-height:1.25;margin-top:3px}
983
1002
  .legend{display:flex;flex-wrap:wrap;gap:4px 12px;margin-top:6px;font-size:12.5px;font-variant-numeric:tabular-nums}
984
1003
  .legend span{display:inline-flex;align-items:center;gap:5px}
985
1004
  .legend i{width:8px;height:8px;border-radius:2px;background:var(--s)}
@@ -1012,7 +1031,8 @@ pre{margin:0;white-space:pre-wrap;word-break:break-word}
1012
1031
  .overview tbody tr:last-child td{border-bottom:none}
1013
1032
  .overview .r{text-align:right}
1014
1033
  .overview .gs{border-left:1px solid var(--border-soft)}
1015
- .overview td.name{min-width:17rem}
1034
+ .overview td.name{min-width:10rem;overflow-wrap:anywhere}
1035
+ @media (max-width:960px){.overview th,.overview td{padding-left:6px;padding-right:6px}.overview .groups th{padding-left:6px;padding-right:6px}}
1016
1036
  .overview td.n{color:var(--faint)}
1017
1037
  .overview tbody tr:hover td{background:var(--surface-alt)}
1018
1038
  .overview tr.s-failed td:first-child{box-shadow:inset 3px 0 0 var(--failed)}
@@ -1365,7 +1385,7 @@ function escape2(value) {
1365
1385
  var allowedInXml = (code) => code === 9 || code === 10 || code === 13 || code >= 32 && code <= 55295 || code >= 57344 && code <= 65533 || code >= 65536;
1366
1386
 
1367
1387
  // src/main.ts
1368
- var VERSION = true ? "0.14.3" : "0.0.0-dev";
1388
+ var VERSION = true ? "0.14.5" : "0.0.0-dev";
1369
1389
  var JUNIT_DIR = "junit";
1370
1390
  var JUNIT_FILE = "junit.xml";
1371
1391
  var HTML_DIR = "html";
package/dist/index.js CHANGED
@@ -3,9 +3,9 @@ import { createRequire as __gtaCreateRequire } from 'node:module';
3
3
  const require = __gtaCreateRequire(import.meta.url);
4
4
  import {
5
5
  openProject
6
- } from "./chunks/chunk-4JUUUUWB.js";
7
- import "./chunks/chunk-4W4TFJ53.js";
8
- import "./chunks/chunk-S563G22M.js";
6
+ } from "./chunks/chunk-VURKUQTC.js";
7
+ import "./chunks/chunk-NKT7CXVX.js";
8
+ import "./chunks/chunk-IL3UPOUK.js";
9
9
 
10
10
  // src/library/index.ts
11
11
  var openProject2 = openProject;
@@ -3,12 +3,12 @@ import { createRequire as __gtaCreateRequire } from 'node:module';
3
3
  const require = __gtaCreateRequire(import.meta.url);
4
4
  import {
5
5
  openProject
6
- } from "./chunks/chunk-4JUUUUWB.js";
7
- import "./chunks/chunk-4W4TFJ53.js";
6
+ } from "./chunks/chunk-VURKUQTC.js";
7
+ import "./chunks/chunk-NKT7CXVX.js";
8
8
  import {
9
9
  iterationName,
10
10
  resultName
11
- } from "./chunks/chunk-S563G22M.js";
11
+ } from "./chunks/chunk-IL3UPOUK.js";
12
12
 
13
13
  // src/library/playwright.ts
14
14
  import path from "node:path";
package/dist/worker.js CHANGED
@@ -3,7 +3,7 @@ import { createRequire as __gtaCreateRequire } from 'node:module';
3
3
  const require = __gtaCreateRequire(import.meta.url);
4
4
  import {
5
5
  runJob
6
- } from "./chunks/chunk-S563G22M.js";
6
+ } from "./chunks/chunk-IL3UPOUK.js";
7
7
 
8
8
  // src/worker.ts
9
9
  import { parentPort, workerData } from "node:worker_threads";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@schwabyio/gta",
3
- "version": "0.14.3",
3
+ "version": "0.14.5",
4
4
  "type": "module",
5
5
  "description": "gta, the Gravity Test Automation command-line runner: HTTP API tests written as YAML files, run from a terminal or CI.",
6
6
  "keywords": [