@schwabyio/gta 0.9.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -1
- package/dist/SPEC.md +241 -31
- package/dist/chunks/{chunk-VRAFYQNE.js → chunk-OM3ZR56O.js} +1191 -566
- package/dist/gta.js +110 -41
- package/dist/worker.js +1 -1
- package/package.json +1 -1
package/dist/gta.js
CHANGED
|
@@ -20,13 +20,15 @@ import {
|
|
|
20
20
|
parseMarkdown,
|
|
21
21
|
projectEnvironments,
|
|
22
22
|
readProject,
|
|
23
|
+
referenceProblems,
|
|
23
24
|
relativePosix,
|
|
24
25
|
require_dist,
|
|
25
26
|
resolveFlags,
|
|
27
|
+
resultName,
|
|
26
28
|
samePath,
|
|
27
29
|
stepsForTags,
|
|
28
30
|
unattempted
|
|
29
|
-
} from "./chunks/chunk-
|
|
31
|
+
} from "./chunks/chunk-OM3ZR56O.js";
|
|
30
32
|
|
|
31
33
|
// src/args.ts
|
|
32
34
|
var UsageError = class extends Error {
|
|
@@ -194,7 +196,7 @@ function tally(target, outcome, durationMs) {
|
|
|
194
196
|
error: null,
|
|
195
197
|
// Named for the row they ran with, when a data file drove the collection.
|
|
196
198
|
problems: summary2.results.flatMap(
|
|
197
|
-
(r, i) => r.status === "fail" || r.status === "error" ? [{ ...r, item: { ...r.item, name: iterationName(r
|
|
199
|
+
(r, i) => r.status === "fail" || r.status === "error" ? [{ ...r, item: { ...r.item, name: iterationName(resultName(r), outcome.steps[i]) } }] : []
|
|
198
200
|
)
|
|
199
201
|
};
|
|
200
202
|
}
|
|
@@ -491,13 +493,13 @@ ${tests || '<p class="dim">No tests were run.</p>'}
|
|
|
491
493
|
var statusOf = (r) => r.status === "pass" ? "passed" : r.status === "fail" ? "failed" : r.status === "error" ? "errored" : "skipped";
|
|
492
494
|
function testSection(r, index, ref) {
|
|
493
495
|
const status = statusOf(r);
|
|
494
|
-
const named = escape(iterationName(r
|
|
496
|
+
const named = escape(iterationName(resultName(r), ref));
|
|
495
497
|
const name = r.use ? `${named} <span class="dim">(${escape(r.use.set)} ${r.use.child + 1}/${r.use.of})</span>` : named;
|
|
496
498
|
if (r.skipped) {
|
|
497
|
-
return `<details class="acc skipped skipped" data-search="${escape(iterationName(r
|
|
499
|
+
return `<details class="acc skipped skipped" data-search="${escape(iterationName(resultName(r), ref).toLowerCase())}">
|
|
498
500
|
<summary><span>Test ${index + 1}: ${name}</span>${icon("skipped")}</summary>
|
|
499
501
|
<div class="acc-body">
|
|
500
|
-
${card("Skipped", field2("
|
|
502
|
+
${card("Skipped", field2("Reason", `<span class="c-skipped">${escape(r.skipped.reason)} \u2014 this test was not run</span>`, "wide"))}
|
|
501
503
|
</div>
|
|
502
504
|
</details>`;
|
|
503
505
|
}
|
|
@@ -509,7 +511,7 @@ ${card("Skipped", field2("Feature Flag", `<span class="c-skipped">${escape(r.ski
|
|
|
509
511
|
r.logs?.length ? consoleCard(r) : ""
|
|
510
512
|
].filter(Boolean);
|
|
511
513
|
const group = status === "passed" ? "passed" : status === "skipped" ? "skipped" : "failed";
|
|
512
|
-
const search = iterationName(r
|
|
514
|
+
const search = iterationName(resultName(r), ref).toLowerCase();
|
|
513
515
|
return `<details class="acc ${group} ${status}" data-search="${escape(search)}"${status === "passed" ? "" : " open"}>
|
|
514
516
|
<summary><span>Test ${index + 1}: ${name}</span>${icon(status)}</summary>
|
|
515
517
|
<div class="acc-body">
|
|
@@ -950,7 +952,7 @@ function testcase(classname, name, durationMs, body) {
|
|
|
950
952
|
if (body.length === 0) return [`${open}/>`];
|
|
951
953
|
return [`${open}>`, ...body.map((line) => ` ${line}`), " </testcase>"];
|
|
952
954
|
}
|
|
953
|
-
var caseName = (result) => result.use ? `${result
|
|
955
|
+
var caseName = (result) => result.use ? `${resultName(result)} [${result.use.set} ${result.use.child + 1}/${result.use.of}]` : result.item.name;
|
|
954
956
|
var summarize = (a) => a.message ? `${a.name}: ${a.message}` : a.name;
|
|
955
957
|
function describe(a) {
|
|
956
958
|
const lines = [summarize(a)];
|
|
@@ -1070,6 +1072,7 @@ function jsonListing(options) {
|
|
|
1070
1072
|
project: options.project,
|
|
1071
1073
|
environments: options.environments,
|
|
1072
1074
|
environmentType: options.environmentType,
|
|
1075
|
+
settings: { values: options.settings.values, sources: options.settings.sources },
|
|
1073
1076
|
flags: { values: options.flags.values, sources: options.flags.sources },
|
|
1074
1077
|
tags: options.tags,
|
|
1075
1078
|
notTags: options.notTags ?? [],
|
|
@@ -1086,7 +1089,8 @@ function jsonListing(options) {
|
|
|
1086
1089
|
tags: target.collection.doc.tags ?? [],
|
|
1087
1090
|
broken: target.broken
|
|
1088
1091
|
})),
|
|
1089
|
-
leftOut: { excluded: options.excluded, untagged: options.untagged }
|
|
1092
|
+
leftOut: { excluded: options.excluded, untagged: options.untagged },
|
|
1093
|
+
problems: options.problems
|
|
1090
1094
|
};
|
|
1091
1095
|
}
|
|
1092
1096
|
var fileOf = (collection) => `collections/${collection.relativePath}`;
|
|
@@ -1165,13 +1169,11 @@ var SettingsError = class extends Error {
|
|
|
1165
1169
|
name = "SettingsError";
|
|
1166
1170
|
};
|
|
1167
1171
|
var envNameOf = (key) => `GTA_${key.replace(/[A-Z]/g, (c) => `_${c}`).toUpperCase()}`;
|
|
1172
|
+
var DEFAULT_SOURCE = "default";
|
|
1168
1173
|
async function loadSettings(options) {
|
|
1169
|
-
const { root, overrides = {}, env = process.env } = options;
|
|
1170
|
-
const
|
|
1171
|
-
|
|
1172
|
-
try {
|
|
1173
|
-
source = await fs.readFile(file, "utf8");
|
|
1174
|
-
} catch {
|
|
1174
|
+
const { root, global = null, overrides = {}, env = process.env } = options;
|
|
1175
|
+
const own = await readSettingsFile(path.join(root, SETTINGS_FILE), SETTINGS_FILE);
|
|
1176
|
+
if (own === null) {
|
|
1175
1177
|
throw new SettingsError(
|
|
1176
1178
|
`There is no ${SETTINGS_FILE} in ${root}.
|
|
1177
1179
|
gta runs from a project folder: the one holding collections/ and ${SETTINGS_FILE}.
|
|
@@ -1182,23 +1184,20 @@ A minimal ${SETTINGS_FILE}:
|
|
|
1182
1184
|
`
|
|
1183
1185
|
);
|
|
1184
1186
|
}
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
fromFile = (0, import_yaml.parse)(source) ?? {};
|
|
1188
|
-
} catch (cause) {
|
|
1189
|
-
throw new SettingsError(`${SETTINGS_FILE} will not parse: ${cause.message}`);
|
|
1190
|
-
}
|
|
1191
|
-
if (typeof fromFile !== "object" || fromFile === null || Array.isArray(fromFile)) {
|
|
1192
|
-
throw new SettingsError(
|
|
1193
|
-
`${SETTINGS_FILE} must be a map of settings, such as limitConcurrency: 4`
|
|
1194
|
-
);
|
|
1195
|
-
}
|
|
1187
|
+
const sharedLabel = global ? path.posix.join(global.uses, SETTINGS_FILE) : "";
|
|
1188
|
+
const shared = global ? await readSettingsFile(path.join(global.root, SETTINGS_FILE), sharedLabel) : null;
|
|
1196
1189
|
const origin = /* @__PURE__ */ new Map();
|
|
1197
1190
|
const merged = {};
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1191
|
+
const files = [
|
|
1192
|
+
[sharedLabel, shared],
|
|
1193
|
+
[SETTINGS_FILE, own]
|
|
1194
|
+
];
|
|
1195
|
+
for (const [label, fromFile] of files) {
|
|
1196
|
+
for (const [key, value] of Object.entries(fromFile ?? {})) {
|
|
1197
|
+
checkKey(key, label);
|
|
1198
|
+
merged[key] = value;
|
|
1199
|
+
origin.set(key, label);
|
|
1200
|
+
}
|
|
1202
1201
|
}
|
|
1203
1202
|
for (const key of KEYS) {
|
|
1204
1203
|
const value = env[envNameOf(key)];
|
|
@@ -1220,7 +1219,29 @@ A minimal ${SETTINGS_FILE}:
|
|
|
1220
1219
|
throw new SettingsError(`Settings are not valid:
|
|
1221
1220
|
${lines.join("\n")}`);
|
|
1222
1221
|
}
|
|
1223
|
-
|
|
1222
|
+
const sources = Object.fromEntries(
|
|
1223
|
+
KEYS.map((key) => [key, origin.get(key) ?? DEFAULT_SOURCE])
|
|
1224
|
+
);
|
|
1225
|
+
return { settings: result.data, sources };
|
|
1226
|
+
}
|
|
1227
|
+
async function readSettingsFile(file, label) {
|
|
1228
|
+
let source;
|
|
1229
|
+
try {
|
|
1230
|
+
source = await fs.readFile(file, "utf8");
|
|
1231
|
+
} catch (cause) {
|
|
1232
|
+
if (cause.code === "ENOENT") return null;
|
|
1233
|
+
throw new SettingsError(`${label} cannot be read: ${cause.message}`);
|
|
1234
|
+
}
|
|
1235
|
+
let fromFile;
|
|
1236
|
+
try {
|
|
1237
|
+
fromFile = (0, import_yaml.parse)(source) ?? {};
|
|
1238
|
+
} catch (cause) {
|
|
1239
|
+
throw new SettingsError(`${label} will not parse: ${cause.message}`);
|
|
1240
|
+
}
|
|
1241
|
+
if (typeof fromFile !== "object" || fromFile === null || Array.isArray(fromFile)) {
|
|
1242
|
+
throw new SettingsError(`${label} must be a map of settings, such as limitConcurrency: 4`);
|
|
1243
|
+
}
|
|
1244
|
+
return fromFile;
|
|
1224
1245
|
}
|
|
1225
1246
|
function checkKey(key, where) {
|
|
1226
1247
|
if (!Object.hasOwn(SETTING_KEYS, key)) {
|
|
@@ -1248,7 +1269,7 @@ function coerce(key, value, where) {
|
|
|
1248
1269
|
}
|
|
1249
1270
|
|
|
1250
1271
|
// src/main.ts
|
|
1251
|
-
var VERSION = true ? "0.
|
|
1272
|
+
var VERSION = true ? "0.11.0" : "0.0.0-dev";
|
|
1252
1273
|
var JUNIT_DIR = "junit";
|
|
1253
1274
|
var JUNIT_FILE = "junit.xml";
|
|
1254
1275
|
var HTML_DIR = "html";
|
|
@@ -1283,23 +1304,28 @@ async function main(argv, cli) {
|
|
|
1283
1304
|
const command = args.command;
|
|
1284
1305
|
if (command === "g" || command === "get") {
|
|
1285
1306
|
const selection = selectFor(project, { kind: "all" });
|
|
1307
|
+
const problems = await listingProblems(project, selection);
|
|
1308
|
+
const code = problems.length > 0 || selection.targets.some((target) => target.broken) ? EXIT.failed : EXIT.passed;
|
|
1286
1309
|
if (emit) {
|
|
1287
1310
|
emit(
|
|
1288
1311
|
jsonListing({
|
|
1289
1312
|
project: { name: project.name, root: project.root },
|
|
1290
1313
|
environments: project.environments,
|
|
1291
1314
|
environmentType: project.settings.environmentType,
|
|
1315
|
+
settings: { values: project.settings, sources: project.settingSources },
|
|
1292
1316
|
flags: project.flags,
|
|
1293
1317
|
tags: project.settings.tags,
|
|
1294
1318
|
notTags: project.settings.notTags,
|
|
1295
1319
|
targets: selection.targets,
|
|
1296
1320
|
excluded: selection.excluded,
|
|
1297
|
-
untagged: selection.untagged
|
|
1321
|
+
untagged: selection.untagged,
|
|
1322
|
+
problems
|
|
1298
1323
|
})
|
|
1299
1324
|
);
|
|
1300
|
-
return
|
|
1325
|
+
return code;
|
|
1301
1326
|
}
|
|
1302
|
-
|
|
1327
|
+
list(project, selection, problems, ctx, p);
|
|
1328
|
+
return code;
|
|
1303
1329
|
}
|
|
1304
1330
|
const request = command === "a" || command === "all" ? { kind: "all" } : { kind: "list", selectors: command.split(",").filter((s) => s.trim() !== "") };
|
|
1305
1331
|
return await run(project, selectFor(project, request), ctx, p, emit);
|
|
@@ -1321,14 +1347,19 @@ async function openProject(ctx, overrides, flagArgs = []) {
|
|
|
1321
1347
|
gta runs from a project folder: the one holding ${COLLECTIONS_DIR}/ and ${SETTINGS_FILE}.`
|
|
1322
1348
|
);
|
|
1323
1349
|
}
|
|
1324
|
-
const settings = await loadSettings({ root, overrides, env: ctx.env });
|
|
1325
1350
|
const info = await readProject(root);
|
|
1351
|
+
const { settings, sources: settingSources } = await loadSettings({
|
|
1352
|
+
root,
|
|
1353
|
+
global: info.global,
|
|
1354
|
+
overrides,
|
|
1355
|
+
env: ctx.env
|
|
1356
|
+
});
|
|
1326
1357
|
const layout = await discoverProject(root);
|
|
1327
1358
|
const collections = await loadProjectCollections(root, layout.files);
|
|
1328
1359
|
const environments = environmentNames(await projectEnvironments(root, info.global));
|
|
1329
1360
|
if (settings.environmentType && !environments.includes(settings.environmentType)) {
|
|
1330
1361
|
throw new ProjectError(
|
|
1331
|
-
`environmentType: there is no environment called "${settings.environmentType}". ` + (environments.length > 0 ? `This project has: ${environments.join(", ")}` : `This project has no environments/.`)
|
|
1362
|
+
`environmentType (from ${settingSources.environmentType}): there is no environment called "${settings.environmentType}". ` + (environments.length > 0 ? `This project has: ${environments.join(", ")}` : `This project has no environments/.`)
|
|
1332
1363
|
);
|
|
1333
1364
|
}
|
|
1334
1365
|
let flags;
|
|
@@ -1353,6 +1384,7 @@ gta runs from a project folder: the one holding ${COLLECTIONS_DIR}/ and ${SETTIN
|
|
|
1353
1384
|
root,
|
|
1354
1385
|
name: info.doc?.name ?? path2.basename(root),
|
|
1355
1386
|
settings,
|
|
1387
|
+
settingSources,
|
|
1356
1388
|
flags,
|
|
1357
1389
|
collections,
|
|
1358
1390
|
environments,
|
|
@@ -1368,7 +1400,24 @@ var selectFor = (project, request) => selectCollections(project.collections, req
|
|
|
1368
1400
|
tags: project.settings.tags,
|
|
1369
1401
|
notTags: project.settings.notTags
|
|
1370
1402
|
});
|
|
1371
|
-
function
|
|
1403
|
+
async function listingProblems(project, selection) {
|
|
1404
|
+
const listed = new Set(selection.targets.map((target) => target.id));
|
|
1405
|
+
const problems = [];
|
|
1406
|
+
for (const collection of project.collections) {
|
|
1407
|
+
const id = idOf(collection);
|
|
1408
|
+
const leftOut = listed.has(id) ? null : collection.doc.exclude === true ? "excluded" : "tags";
|
|
1409
|
+
if (collection.problems.length > 0) {
|
|
1410
|
+
const message = collection.problems.map((problem) => problem.message).join("; ");
|
|
1411
|
+
if (leftOut) problems.push({ id, leftOut, step: null, message });
|
|
1412
|
+
continue;
|
|
1413
|
+
}
|
|
1414
|
+
for (const problem of await referenceProblems(collection.doc, collection.path)) {
|
|
1415
|
+
problems.push({ id, leftOut, ...problem });
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1418
|
+
return problems;
|
|
1419
|
+
}
|
|
1420
|
+
function list(project, selection, problems, ctx, p) {
|
|
1372
1421
|
const { settings } = project;
|
|
1373
1422
|
ctx.out(field("Project", `${project.name} ${p.dim(project.root)}`));
|
|
1374
1423
|
ctx.out(
|
|
@@ -1379,8 +1428,7 @@ function list(project, selection, ctx, p) {
|
|
|
1379
1428
|
);
|
|
1380
1429
|
ctx.out(field("Flags", flagLine(project.flags, p)));
|
|
1381
1430
|
if (project.caFiles.length > 0) ctx.out(field("CA files", project.caFiles.join(", ")));
|
|
1382
|
-
|
|
1383
|
-
if (settings.notTags.length > 0) ctx.out(field("Not tags", settings.notTags.join(", ")));
|
|
1431
|
+
for (const line of settingLines(project, p)) ctx.out(line);
|
|
1384
1432
|
ctx.out("");
|
|
1385
1433
|
const width = nameWidthFor(selection.targets.map((t) => t.id));
|
|
1386
1434
|
const dirWidth = Math.max(
|
|
@@ -1406,6 +1454,12 @@ function list(project, selection, ctx, p) {
|
|
|
1406
1454
|
for (const target of selection.targets.filter((t) => t.broken)) {
|
|
1407
1455
|
ctx.out(`${p.red("broken")} ${target.id}: ${target.broken}`);
|
|
1408
1456
|
}
|
|
1457
|
+
const leftOut = { excluded: " (excluded)", tags: " (left out by tags)" };
|
|
1458
|
+
for (const problem of problems) {
|
|
1459
|
+
const step = problem.step === null ? "" : `, ${problem.stage ? `${problem.stage} ` : ""}step ${problem.step + 1}`;
|
|
1460
|
+
const where = `${problem.id}${problem.leftOut ? leftOut[problem.leftOut] : ""}${step}`;
|
|
1461
|
+
ctx.out(`${p.red("broken")} ${where}: ${problem.message}`);
|
|
1462
|
+
}
|
|
1409
1463
|
if (selection.excluded.length > 0) {
|
|
1410
1464
|
ctx.out(field("Excluded", `${selection.excluded.join(", ")} ${p.dim("(exclude: true)")}`));
|
|
1411
1465
|
}
|
|
@@ -1414,8 +1468,20 @@ function list(project, selection, ctx, p) {
|
|
|
1414
1468
|
}
|
|
1415
1469
|
const steps = selection.targets.reduce((n, t) => n + stepCountOf(t), 0);
|
|
1416
1470
|
ctx.out(field("Total", `${selection.targets.length} collections, ${steps} steps`));
|
|
1417
|
-
return EXIT.passed;
|
|
1418
1471
|
}
|
|
1472
|
+
function settingLines(project, p) {
|
|
1473
|
+
const rows = Object.keys(SETTING_KEYS).filter((key) => project.settingSources[key] !== DEFAULT_SOURCE).map((key) => ({
|
|
1474
|
+
cell: `${key}: ${shownSetting(project.settings[key])}`,
|
|
1475
|
+
source: project.settingSources[key]
|
|
1476
|
+
}));
|
|
1477
|
+
if (rows.length === 0) return [field("Settings", p.dim("all defaults"))];
|
|
1478
|
+
const width = Math.max(...rows.map((row) => row.cell.length));
|
|
1479
|
+
const indent = " ".repeat(field("Settings", "").length);
|
|
1480
|
+
return rows.map(
|
|
1481
|
+
(row, i) => `${i === 0 ? field("Settings", "") : indent}${row.cell.padEnd(width)} ${p.dim(row.source)}`
|
|
1482
|
+
);
|
|
1483
|
+
}
|
|
1484
|
+
var shownSetting = (value) => Array.isArray(value) ? value.length > 0 ? value.join(", ") : "[]" : String(value ?? "none");
|
|
1419
1485
|
async function run(project, selection, ctx, p, emit = null) {
|
|
1420
1486
|
const { settings } = project;
|
|
1421
1487
|
const width = nameWidthFor(selection.targets.map((t) => t.id));
|
|
@@ -1603,6 +1669,8 @@ function usage(p) {
|
|
|
1603
1669
|
"",
|
|
1604
1670
|
` ${p.bold("Commands:")}`,
|
|
1605
1671
|
" g, get List the collections `gta all` would run, without running them.",
|
|
1672
|
+
" Exits 1 on a broken collection, or a use:, extends: or body",
|
|
1673
|
+
" file that would stop a run, in any collection.",
|
|
1606
1674
|
" a, all Run every collection, less those with exclude: true.",
|
|
1607
1675
|
" <list> Run the collections and directories named, comma separated,",
|
|
1608
1676
|
" in that order: smoke,checkout/sessions,payments",
|
|
@@ -1617,7 +1685,8 @@ function usage(p) {
|
|
|
1617
1685
|
" Override a feature flag for this run; any number of them.",
|
|
1618
1686
|
" GTA_FLAG_<name>=value does the same from the environment.",
|
|
1619
1687
|
"",
|
|
1620
|
-
` ${p.bold("Settings")} (settings.yml, then
|
|
1688
|
+
` ${p.bold("Settings")} (the global project's settings.yml, then this project's, then`,
|
|
1689
|
+
" GTA_* environment variables, then --flags; gta get shows where each came from):",
|
|
1621
1690
|
...settings,
|
|
1622
1691
|
"",
|
|
1623
1692
|
` ${p.bold("Exit code:")} 0 passed, 1 failed, 2 could not run (or write a report).`
|
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-
|
|
6
|
+
} from "./chunks/chunk-OM3ZR56O.js";
|
|
7
7
|
|
|
8
8
|
// src/worker.ts
|
|
9
9
|
import { parentPort, workerData } from "node:worker_threads";
|
package/package.json
CHANGED