simulci 0.2.0 → 0.3.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/dist/index.js +349 -176
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -28797,7 +28797,7 @@ function verdictOf(gates) {
|
|
|
28797
28797
|
//#endregion
|
|
28798
28798
|
//#region ../engine/src/version.ts
|
|
28799
28799
|
/** The simulci version. release-please updates it with every release. */
|
|
28800
|
-
const VERSION = "0.
|
|
28800
|
+
const VERSION = "0.3.0";
|
|
28801
28801
|
/** The schema version of every JSON document the engine emits or reads. A breaking change bumps it. */
|
|
28802
28802
|
const SCHEMA_VERSIONS = {
|
|
28803
28803
|
scenario: 1,
|
|
@@ -32235,7 +32235,134 @@ function diskSnapshotStore(root) {
|
|
|
32235
32235
|
};
|
|
32236
32236
|
}
|
|
32237
32237
|
//#endregion
|
|
32238
|
+
//#region src/args.ts
|
|
32239
|
+
/**
|
|
32240
|
+
* A command line as citty reads it, with two of its gaps filled. citty 0.2 parses with node:util's parseArgs and no
|
|
32241
|
+
* `multiple`, so a flag given twice keeps only its last value: a `repeated` flag keeps them all. And a string flag
|
|
32242
|
+
* given no value takes the next flag as one: a `bare` flag that stands last, or before another flag, takes its bare
|
|
32243
|
+
* value instead.
|
|
32244
|
+
*/
|
|
32245
|
+
function readArgs(rawArgs, def, opts) {
|
|
32246
|
+
const raw = withBareValues(rawArgs, def, opts.bare ?? {});
|
|
32247
|
+
const repeated = new Set(opts.repeated);
|
|
32248
|
+
const values = {};
|
|
32249
|
+
for (const t of optionTokens(raw, def)) if (repeated.has(t.key)) (values[t.key] ??= []).push(t.value ?? "");
|
|
32250
|
+
return {
|
|
32251
|
+
...parseArgs$2([...raw], def),
|
|
32252
|
+
...values
|
|
32253
|
+
};
|
|
32254
|
+
}
|
|
32255
|
+
/** A bare flag, as `--flag=<its bare value>`. */
|
|
32256
|
+
function withBareValues(raw, def, bare) {
|
|
32257
|
+
const out = [...raw];
|
|
32258
|
+
for (const t of optionTokens(raw, def)) {
|
|
32259
|
+
const value = bare[t.key];
|
|
32260
|
+
if (value !== void 0 && !t.inline && (t.value === void 0 || t.value.startsWith("-"))) out[t.index] = `--${kebab(t.key)}=${value}`;
|
|
32261
|
+
}
|
|
32262
|
+
return out;
|
|
32263
|
+
}
|
|
32264
|
+
/** The flags of a command line, tokenized as citty's parser sees them: without its `--no-` negations. */
|
|
32265
|
+
function optionTokens(raw, def) {
|
|
32266
|
+
const { options, keys } = optionsOf(def);
|
|
32267
|
+
const end = raw.indexOf("--");
|
|
32268
|
+
const kept = raw.flatMap((arg, i) => (end === -1 || i < end) && arg.startsWith("--no-") ? [] : [i]);
|
|
32269
|
+
const { tokens } = parseArgs({
|
|
32270
|
+
args: kept.map((i) => raw[i]),
|
|
32271
|
+
options,
|
|
32272
|
+
strict: false,
|
|
32273
|
+
allowPositionals: true,
|
|
32274
|
+
tokens: true
|
|
32275
|
+
});
|
|
32276
|
+
return tokens.flatMap((t) => {
|
|
32277
|
+
const key = t.kind === "option" ? keys.get(t.name) : void 0;
|
|
32278
|
+
if (t.kind !== "option" || key === void 0) return [];
|
|
32279
|
+
return [{
|
|
32280
|
+
key,
|
|
32281
|
+
index: kept[t.index],
|
|
32282
|
+
value: t.value,
|
|
32283
|
+
inline: t.inlineValue === true
|
|
32284
|
+
}];
|
|
32285
|
+
});
|
|
32286
|
+
}
|
|
32287
|
+
/** The options citty declares for a command's args: each name, its kebab- and camelCase forms and its aliases, typed. */
|
|
32288
|
+
function optionsOf(def) {
|
|
32289
|
+
const options = {};
|
|
32290
|
+
const keys = /* @__PURE__ */ new Map();
|
|
32291
|
+
for (const [key, arg] of Object.entries(def)) {
|
|
32292
|
+
if (arg.type === "positional") continue;
|
|
32293
|
+
const type = arg.type === "boolean" ? "boolean" : "string";
|
|
32294
|
+
const aliases = "alias" in arg ? [arg.alias ?? []].flat() : [];
|
|
32295
|
+
for (const name of /* @__PURE__ */ new Set([
|
|
32296
|
+
key,
|
|
32297
|
+
kebab(key),
|
|
32298
|
+
camel(key),
|
|
32299
|
+
...aliases
|
|
32300
|
+
])) {
|
|
32301
|
+
options[name] = { type };
|
|
32302
|
+
keys.set(name, key);
|
|
32303
|
+
}
|
|
32304
|
+
const short = aliases.find((a) => a.length === 1);
|
|
32305
|
+
if (short !== void 0) options[key] = {
|
|
32306
|
+
type,
|
|
32307
|
+
short
|
|
32308
|
+
};
|
|
32309
|
+
}
|
|
32310
|
+
return {
|
|
32311
|
+
options,
|
|
32312
|
+
keys
|
|
32313
|
+
};
|
|
32314
|
+
}
|
|
32315
|
+
const kebab = (name) => name.replace(/[A-Z]/g, (c) => `-${c.toLowerCase()}`);
|
|
32316
|
+
const camel = (name) => name.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
32317
|
+
/** A flag given a value it does not take: the command prints the message and exits 2. */
|
|
32318
|
+
var FlagError = class extends Error {
|
|
32319
|
+
name = "FlagError";
|
|
32320
|
+
};
|
|
32321
|
+
/** `value` when it is one of `choices`; else a `FlagError` naming what `--<flag>` takes. */
|
|
32322
|
+
function oneOf(flag, value, choices) {
|
|
32323
|
+
if (choices.some((c) => c === value)) return value;
|
|
32324
|
+
throw new FlagError(`--${flag} takes ${spoken(choices)}, not '${String(value)}'`);
|
|
32325
|
+
}
|
|
32326
|
+
/** A list as a sentence says it: `a, b or c`. */
|
|
32327
|
+
const spoken = (items) => items.length > 1 ? `${items.slice(0, -1).join(", ")} or ${items.at(-1)}` : items.join("");
|
|
32328
|
+
/**
|
|
32329
|
+
* The exit code of a command's body; 2 when it throws, with the error's message on stderr (its stack under
|
|
32330
|
+
* `SIMULCI_DEBUG=1`). citty would print the stack and exit 1, which `simulci test` uses for a failed case.
|
|
32331
|
+
*/
|
|
32332
|
+
function exitCodeOf(body, io) {
|
|
32333
|
+
try {
|
|
32334
|
+
return body();
|
|
32335
|
+
} catch (e) {
|
|
32336
|
+
io.stderr(`${errorText(e, io.env.SIMULCI_DEBUG === "1")}\n`);
|
|
32337
|
+
return 2;
|
|
32338
|
+
}
|
|
32339
|
+
}
|
|
32340
|
+
function errorText(e, stack) {
|
|
32341
|
+
if (!(e instanceof Error)) return String(e);
|
|
32342
|
+
return stack && e.stack !== void 0 ? e.stack : e.message;
|
|
32343
|
+
}
|
|
32344
|
+
/** A flag's values as a list: none, one, or every one it was given. */
|
|
32345
|
+
const values = (v) => v === void 0 ? [] : Array.isArray(v) ? v : [v];
|
|
32346
|
+
/** The `--assume` entries and the `--assume-file` list; an `AssumeError` when one does not parse or the file cannot be read. */
|
|
32347
|
+
function assumeOf(assume, file) {
|
|
32348
|
+
return parseAssume(values(assume), file === void 0 ? void 0 : readAssumeFile(file));
|
|
32349
|
+
}
|
|
32350
|
+
function readAssumeFile(file) {
|
|
32351
|
+
try {
|
|
32352
|
+
return readFileSync(resolve(file), "utf8");
|
|
32353
|
+
} catch (e) {
|
|
32354
|
+
throw new AssumeError(`--assume-file ${file}: ${e.code === "ENOENT" ? "no such file" : e.message}`);
|
|
32355
|
+
}
|
|
32356
|
+
}
|
|
32357
|
+
//#endregion
|
|
32238
32358
|
//#region src/plan.ts
|
|
32359
|
+
const FORMATS = [
|
|
32360
|
+
"text",
|
|
32361
|
+
"json",
|
|
32362
|
+
"mermaid"
|
|
32363
|
+
];
|
|
32364
|
+
const DETAILS$1 = ["steps", "legs"];
|
|
32365
|
+
const AS_TEST = ["jobs", "steps"];
|
|
32239
32366
|
/** Builds the scenario from the arguments: `key=value` lists become records, repeated flags become arrays. */
|
|
32240
32367
|
function scenarioFromArgs(args) {
|
|
32241
32368
|
const s = { event: args.event };
|
|
@@ -32251,13 +32378,13 @@ function scenarioFromArgs(args) {
|
|
|
32251
32378
|
const v = args[key];
|
|
32252
32379
|
if (v !== void 0 && v !== "") s[key] = v;
|
|
32253
32380
|
}
|
|
32254
|
-
const changed = list
|
|
32381
|
+
const changed = list(args.changed);
|
|
32255
32382
|
if (changed.length) s.changed = changed;
|
|
32256
|
-
const inputs = pairs(list
|
|
32383
|
+
const inputs = pairs(list(args.input));
|
|
32257
32384
|
if (Object.keys(inputs).length) s.inputs = Object.fromEntries(Object.entries(inputs).map(([k, v]) => [k, typed(v)]));
|
|
32258
|
-
const vars = pairs(list
|
|
32385
|
+
const vars = pairs(list(args.var));
|
|
32259
32386
|
if (Object.keys(vars).length) s.vars = vars;
|
|
32260
|
-
const secrets = list
|
|
32387
|
+
const secrets = list(args.secret);
|
|
32261
32388
|
if (secrets.length) s.secrets = secrets;
|
|
32262
32389
|
if (args.actor !== void 0 || args.fork) {
|
|
32263
32390
|
s.actor = {};
|
|
@@ -32266,7 +32393,7 @@ function scenarioFromArgs(args) {
|
|
|
32266
32393
|
}
|
|
32267
32394
|
return s;
|
|
32268
32395
|
}
|
|
32269
|
-
const list
|
|
32396
|
+
const list = (v) => values(v).flatMap((x) => x.split(",")).map((x) => x.trim()).filter(Boolean);
|
|
32270
32397
|
function pairs(items) {
|
|
32271
32398
|
const out = {};
|
|
32272
32399
|
for (const item of items) {
|
|
@@ -32287,116 +32414,145 @@ function render(plan, format) {
|
|
|
32287
32414
|
if (format === "mermaid") return renderMermaid(plan);
|
|
32288
32415
|
return renderText(plan);
|
|
32289
32416
|
}
|
|
32417
|
+
const PLAN_ARGS = {
|
|
32418
|
+
dir: {
|
|
32419
|
+
type: "string",
|
|
32420
|
+
description: "The repository checkout",
|
|
32421
|
+
default: "."
|
|
32422
|
+
},
|
|
32423
|
+
event: {
|
|
32424
|
+
type: "string",
|
|
32425
|
+
description: "The event (push, pull_request, workflow_dispatch, …)",
|
|
32426
|
+
default: "push"
|
|
32427
|
+
},
|
|
32428
|
+
action: {
|
|
32429
|
+
type: "string",
|
|
32430
|
+
description: "The activity type (opened, labeled, …)"
|
|
32431
|
+
},
|
|
32432
|
+
ref: {
|
|
32433
|
+
type: "string",
|
|
32434
|
+
description: "The git ref (refs/heads/main, or a branch name)"
|
|
32435
|
+
},
|
|
32436
|
+
branch: {
|
|
32437
|
+
type: "string",
|
|
32438
|
+
description: "The branch (refs/heads/<branch>)"
|
|
32439
|
+
},
|
|
32440
|
+
tag: {
|
|
32441
|
+
type: "string",
|
|
32442
|
+
description: "The tag (refs/tags/<tag>)"
|
|
32443
|
+
},
|
|
32444
|
+
base: {
|
|
32445
|
+
type: "string",
|
|
32446
|
+
description: "The base branch of a pull request"
|
|
32447
|
+
},
|
|
32448
|
+
head: {
|
|
32449
|
+
type: "string",
|
|
32450
|
+
description: "The head branch of a pull request"
|
|
32451
|
+
},
|
|
32452
|
+
changed: {
|
|
32453
|
+
type: "string",
|
|
32454
|
+
description: "Changed files, comma-separated or repeated"
|
|
32455
|
+
},
|
|
32456
|
+
input: {
|
|
32457
|
+
type: "string",
|
|
32458
|
+
description: "A workflow_dispatch input, name=value (repeatable)"
|
|
32459
|
+
},
|
|
32460
|
+
var: {
|
|
32461
|
+
type: "string",
|
|
32462
|
+
description: "A variable, NAME=value (repeatable)"
|
|
32463
|
+
},
|
|
32464
|
+
secret: {
|
|
32465
|
+
type: "string",
|
|
32466
|
+
description: "A secret that exists, by name (repeatable)"
|
|
32467
|
+
},
|
|
32468
|
+
actor: {
|
|
32469
|
+
type: "string",
|
|
32470
|
+
description: "The actor login"
|
|
32471
|
+
},
|
|
32472
|
+
fork: {
|
|
32473
|
+
type: "boolean",
|
|
32474
|
+
description: "The pull request comes from a fork",
|
|
32475
|
+
default: false
|
|
32476
|
+
},
|
|
32477
|
+
message: {
|
|
32478
|
+
type: "string",
|
|
32479
|
+
description: "The head commit message"
|
|
32480
|
+
},
|
|
32481
|
+
format: {
|
|
32482
|
+
type: "string",
|
|
32483
|
+
description: spoken(FORMATS),
|
|
32484
|
+
default: "text"
|
|
32485
|
+
},
|
|
32486
|
+
detail: {
|
|
32487
|
+
type: "string",
|
|
32488
|
+
description: spoken(DETAILS$1),
|
|
32489
|
+
default: "steps"
|
|
32490
|
+
},
|
|
32491
|
+
assume: {
|
|
32492
|
+
type: "string",
|
|
32493
|
+
description: "A mock: build.unit=failure, build.unit.outputs.sha=abc (repeatable)"
|
|
32494
|
+
},
|
|
32495
|
+
"assume-file": {
|
|
32496
|
+
type: "string",
|
|
32497
|
+
description: "A YAML list of mocks"
|
|
32498
|
+
},
|
|
32499
|
+
"as-test": {
|
|
32500
|
+
type: "string",
|
|
32501
|
+
description: "Print a test case instead of the plan: jobs (also a bare --as-test) or steps"
|
|
32502
|
+
},
|
|
32503
|
+
workflow: {
|
|
32504
|
+
type: "string",
|
|
32505
|
+
description: "With --as-test: the workflow the case is about"
|
|
32506
|
+
}
|
|
32507
|
+
};
|
|
32290
32508
|
const planCommand = defineCommand({
|
|
32291
32509
|
meta: {
|
|
32292
32510
|
name: "plan",
|
|
32293
32511
|
description: "Which jobs and steps run for a scenario, and why"
|
|
32294
32512
|
},
|
|
32295
|
-
args:
|
|
32296
|
-
|
|
32297
|
-
|
|
32298
|
-
|
|
32299
|
-
|
|
32300
|
-
}
|
|
32301
|
-
|
|
32302
|
-
|
|
32303
|
-
|
|
32304
|
-
|
|
32305
|
-
},
|
|
32306
|
-
action: {
|
|
32307
|
-
type: "string",
|
|
32308
|
-
description: "The activity type (opened, labeled, …)"
|
|
32309
|
-
},
|
|
32310
|
-
ref: {
|
|
32311
|
-
type: "string",
|
|
32312
|
-
description: "The git ref (refs/heads/main, or a branch name)"
|
|
32313
|
-
},
|
|
32314
|
-
branch: {
|
|
32315
|
-
type: "string",
|
|
32316
|
-
description: "The branch (refs/heads/<branch>)"
|
|
32317
|
-
},
|
|
32318
|
-
tag: {
|
|
32319
|
-
type: "string",
|
|
32320
|
-
description: "The tag (refs/tags/<tag>)"
|
|
32321
|
-
},
|
|
32322
|
-
base: {
|
|
32323
|
-
type: "string",
|
|
32324
|
-
description: "The base branch of a pull request"
|
|
32325
|
-
},
|
|
32326
|
-
head: {
|
|
32327
|
-
type: "string",
|
|
32328
|
-
description: "The head branch of a pull request"
|
|
32329
|
-
},
|
|
32330
|
-
changed: {
|
|
32331
|
-
type: "string",
|
|
32332
|
-
description: "Changed files, comma-separated or repeated"
|
|
32333
|
-
},
|
|
32334
|
-
input: {
|
|
32335
|
-
type: "string",
|
|
32336
|
-
description: "A workflow_dispatch input, name=value (repeatable)"
|
|
32337
|
-
},
|
|
32338
|
-
var: {
|
|
32339
|
-
type: "string",
|
|
32340
|
-
description: "A variable, NAME=value (repeatable)"
|
|
32341
|
-
},
|
|
32342
|
-
secret: {
|
|
32343
|
-
type: "string",
|
|
32344
|
-
description: "A secret that exists, by name (repeatable)"
|
|
32345
|
-
},
|
|
32346
|
-
actor: {
|
|
32347
|
-
type: "string",
|
|
32348
|
-
description: "The actor login"
|
|
32349
|
-
},
|
|
32350
|
-
fork: {
|
|
32351
|
-
type: "boolean",
|
|
32352
|
-
description: "The pull request comes from a fork",
|
|
32353
|
-
default: false
|
|
32354
|
-
},
|
|
32355
|
-
message: {
|
|
32356
|
-
type: "string",
|
|
32357
|
-
description: "The head commit message"
|
|
32358
|
-
},
|
|
32359
|
-
format: {
|
|
32360
|
-
type: "string",
|
|
32361
|
-
description: "text, json or mermaid",
|
|
32362
|
-
default: "text"
|
|
32363
|
-
},
|
|
32364
|
-
detail: {
|
|
32365
|
-
type: "string",
|
|
32366
|
-
description: "legs or steps",
|
|
32367
|
-
default: "steps"
|
|
32368
|
-
},
|
|
32369
|
-
assume: {
|
|
32370
|
-
type: "string",
|
|
32371
|
-
description: "A mock: build.unit=failure, build.unit.outputs.sha=abc (repeatable)"
|
|
32372
|
-
},
|
|
32373
|
-
assumeFile: {
|
|
32374
|
-
type: "string",
|
|
32375
|
-
description: "A YAML list of mocks"
|
|
32376
|
-
},
|
|
32377
|
-
asTest: {
|
|
32378
|
-
type: "string",
|
|
32379
|
-
description: "Print a test case instead of the plan: jobs or steps"
|
|
32380
|
-
},
|
|
32381
|
-
workflow: {
|
|
32382
|
-
type: "string",
|
|
32383
|
-
description: "With --as-test: the workflow the case is about"
|
|
32384
|
-
}
|
|
32385
|
-
},
|
|
32386
|
-
run({ args }) {
|
|
32387
|
-
const out = planOutput(args);
|
|
32388
|
-
process.stdout.write(out.text);
|
|
32389
|
-
if (out.code) process.exitCode = out.code;
|
|
32513
|
+
args: PLAN_ARGS,
|
|
32514
|
+
run({ rawArgs }) {
|
|
32515
|
+
const io = {
|
|
32516
|
+
stderr: (t) => process.stderr.write(t),
|
|
32517
|
+
env: process.env
|
|
32518
|
+
};
|
|
32519
|
+
process.exitCode = exitCodeOf(() => {
|
|
32520
|
+
const out = planOutput(planArgs(rawArgs));
|
|
32521
|
+
(out.code ? process.stderr : process.stdout).write(out.text);
|
|
32522
|
+
return out.code;
|
|
32523
|
+
}, io);
|
|
32390
32524
|
}
|
|
32391
32525
|
});
|
|
32392
|
-
/**
|
|
32526
|
+
/**
|
|
32527
|
+
* The arguments of a `simulci plan` command line: every value of a repeated flag, and `jobs` for a bare `--as-test`.
|
|
32528
|
+
* A `FlagError` when `--format`, `--detail` or `--as-test` is given a value it does not take.
|
|
32529
|
+
*/
|
|
32530
|
+
function planArgs(rawArgs) {
|
|
32531
|
+
const a = readArgs(rawArgs, PLAN_ARGS, {
|
|
32532
|
+
repeated: [
|
|
32533
|
+
"changed",
|
|
32534
|
+
"input",
|
|
32535
|
+
"var",
|
|
32536
|
+
"secret",
|
|
32537
|
+
"assume"
|
|
32538
|
+
],
|
|
32539
|
+
bare: { "as-test": "jobs" }
|
|
32540
|
+
});
|
|
32541
|
+
return {
|
|
32542
|
+
...a,
|
|
32543
|
+
format: oneOf("format", a.format, FORMATS),
|
|
32544
|
+
detail: oneOf("detail", a.detail, DETAILS$1),
|
|
32545
|
+
asTest: a.asTest === void 0 || a.asTest === false ? void 0 : oneOf("as-test", a.asTest, AS_TEST)
|
|
32546
|
+
};
|
|
32547
|
+
}
|
|
32548
|
+
/** What `simulci plan` prints, and the exit code: 2, with the message, when it cannot. */
|
|
32393
32549
|
function planOutput(a) {
|
|
32394
32550
|
const files = readWorkflowFiles(a.dir);
|
|
32395
32551
|
if (Object.keys(files).length === 0) return {
|
|
32396
32552
|
text: `no workflows under ${a.dir}/.github/workflows\n`,
|
|
32397
32553
|
code: 2
|
|
32398
32554
|
};
|
|
32399
|
-
const assume =
|
|
32555
|
+
const assume = mocksOf(a);
|
|
32400
32556
|
if (typeof assume === "string") return {
|
|
32401
32557
|
text: assume,
|
|
32402
32558
|
code: 2
|
|
@@ -32406,16 +32562,16 @@ function planOutput(a) {
|
|
|
32406
32562
|
const plan = simulate({
|
|
32407
32563
|
files,
|
|
32408
32564
|
scenario,
|
|
32409
|
-
detail: a.detail
|
|
32565
|
+
detail: a.detail,
|
|
32410
32566
|
mocks: { cli: assume }
|
|
32411
32567
|
});
|
|
32412
|
-
if (a.asTest === void 0
|
|
32568
|
+
if (a.asTest === void 0) return {
|
|
32413
32569
|
text: render(plan, a.format),
|
|
32414
32570
|
code: 0
|
|
32415
32571
|
};
|
|
32416
32572
|
return asTest(plan, a, scenario, assume);
|
|
32417
32573
|
} catch (e) {
|
|
32418
|
-
if (!(e instanceof MocksError)) throw e;
|
|
32574
|
+
if (!(e instanceof MocksError || e instanceof ScenarioError)) throw e;
|
|
32419
32575
|
return {
|
|
32420
32576
|
text: `${e.message}\n`,
|
|
32421
32577
|
code: 2
|
|
@@ -32423,9 +32579,9 @@ function planOutput(a) {
|
|
|
32423
32579
|
}
|
|
32424
32580
|
}
|
|
32425
32581
|
/** The `--assume` entries, or the message to print when they do not parse. */
|
|
32426
|
-
function
|
|
32582
|
+
function mocksOf(a) {
|
|
32427
32583
|
try {
|
|
32428
|
-
return
|
|
32584
|
+
return assumeOf(a.assume, a.assumeFile);
|
|
32429
32585
|
} catch (e) {
|
|
32430
32586
|
if (!(e instanceof AssumeError)) throw e;
|
|
32431
32587
|
return `${e.message}\n`;
|
|
@@ -32439,19 +32595,26 @@ function asTest(plan, a, scenario, assume) {
|
|
|
32439
32595
|
text: "no workflow to write a test for\n",
|
|
32440
32596
|
code: 2
|
|
32441
32597
|
};
|
|
32598
|
+
const path = workflow.startsWith(".github/workflows/") ? workflow : `.github/workflows/${workflow}`;
|
|
32599
|
+
if (!plan.workflows.some((w) => w.depth === 0 && w.path === path)) return {
|
|
32600
|
+
text: `--workflow ${workflow}: no ${path} in the checkout\n`,
|
|
32601
|
+
code: 2
|
|
32602
|
+
};
|
|
32442
32603
|
return {
|
|
32443
32604
|
text: fileFromPlan({
|
|
32444
32605
|
plan,
|
|
32445
32606
|
workflow,
|
|
32446
32607
|
scenario,
|
|
32447
32608
|
assume,
|
|
32448
|
-
detail: a.asTest
|
|
32609
|
+
detail: a.asTest ?? "jobs"
|
|
32449
32610
|
}),
|
|
32450
32611
|
code: 0
|
|
32451
32612
|
};
|
|
32452
32613
|
}
|
|
32453
32614
|
//#endregion
|
|
32454
32615
|
//#region src/test.ts
|
|
32616
|
+
/** `simulci test`: the route tests under `.github/simulci/tests`, or the files named. */
|
|
32617
|
+
const DETAILS = ["steps", "legs"];
|
|
32455
32618
|
/** Runs the command; returns the exit code. */
|
|
32456
32619
|
function runTestCommand(a, io) {
|
|
32457
32620
|
const ready = prepare(a, io);
|
|
@@ -32461,7 +32624,7 @@ function runTestCommand(a, io) {
|
|
|
32461
32624
|
ci: a.ci ?? io.env.CI === "true",
|
|
32462
32625
|
update: a.update ?? false,
|
|
32463
32626
|
strict: a.strict ?? false,
|
|
32464
|
-
snapshotDetail: a.snapshotDetail
|
|
32627
|
+
snapshotDetail: a.snapshotDetail ?? "steps",
|
|
32465
32628
|
snapshots: diskSnapshotStore(root)
|
|
32466
32629
|
};
|
|
32467
32630
|
if (a.filter !== void 0) options.filter = a.filter;
|
|
@@ -32480,7 +32643,7 @@ function runTestCommand(a, io) {
|
|
|
32480
32643
|
/** The checkout, the test files, the project and the cli mocks; or the exit code when one of them is not there. */
|
|
32481
32644
|
function prepare(a, io) {
|
|
32482
32645
|
const root = resolve(a.dir);
|
|
32483
|
-
const files = discoverTests(root,
|
|
32646
|
+
const files = discoverTests(root, values(a.files));
|
|
32484
32647
|
const misplaced = misplacedTests(files);
|
|
32485
32648
|
if (misplaced.length) {
|
|
32486
32649
|
io.stderr(`T000 ${misplaced.join(", ")}: a test file under .github/workflows would be run by GitHub; put it under ${TESTS_DIR}\n`);
|
|
@@ -32492,12 +32655,11 @@ function prepare(a, io) {
|
|
|
32492
32655
|
return 2;
|
|
32493
32656
|
}
|
|
32494
32657
|
try {
|
|
32495
|
-
const fileText = a.assumeFile === void 0 ? void 0 : readFileSync(resolve(a.assumeFile), "utf8");
|
|
32496
32658
|
return {
|
|
32497
32659
|
root,
|
|
32498
32660
|
files,
|
|
32499
32661
|
project: loadFiles(workflows),
|
|
32500
|
-
cli:
|
|
32662
|
+
cli: assumeOf(a.assume, a.assumeFile)
|
|
32501
32663
|
};
|
|
32502
32664
|
} catch (e) {
|
|
32503
32665
|
if (!(e instanceof AssumeError)) throw e;
|
|
@@ -32513,79 +32675,90 @@ function report(result, a, io) {
|
|
|
32513
32675
|
writeFileSync(resolve(a.junit), renderJUnit(result));
|
|
32514
32676
|
}
|
|
32515
32677
|
}
|
|
32516
|
-
const
|
|
32678
|
+
const TEST_ARGS = {
|
|
32679
|
+
files: {
|
|
32680
|
+
type: "positional",
|
|
32681
|
+
description: "Test files (default: every *.test.yml under .github/simulci/tests)",
|
|
32682
|
+
required: false
|
|
32683
|
+
},
|
|
32684
|
+
dir: {
|
|
32685
|
+
type: "string",
|
|
32686
|
+
description: "The repository checkout",
|
|
32687
|
+
default: "."
|
|
32688
|
+
},
|
|
32689
|
+
junit: {
|
|
32690
|
+
type: "string",
|
|
32691
|
+
description: "Write a JUnit report to this file"
|
|
32692
|
+
},
|
|
32693
|
+
update: {
|
|
32694
|
+
type: "boolean",
|
|
32695
|
+
alias: "u",
|
|
32696
|
+
description: "Write every snapshot and prune stale ones",
|
|
32697
|
+
default: false
|
|
32698
|
+
},
|
|
32699
|
+
"snapshot-detail": {
|
|
32700
|
+
type: "string",
|
|
32701
|
+
description: spoken(DETAILS),
|
|
32702
|
+
default: "steps"
|
|
32703
|
+
},
|
|
32704
|
+
filter: {
|
|
32705
|
+
type: "string",
|
|
32706
|
+
alias: "t",
|
|
32707
|
+
description: "Run the cases whose name contains the text"
|
|
32708
|
+
},
|
|
32709
|
+
ci: {
|
|
32710
|
+
type: "boolean",
|
|
32711
|
+
description: "Never write snapshots; a missing one fails; only is an error (default when CI=true)"
|
|
32712
|
+
},
|
|
32713
|
+
strict: {
|
|
32714
|
+
type: "boolean",
|
|
32715
|
+
description: "Ignore allowMaybe; stale snapshots fail",
|
|
32716
|
+
default: false
|
|
32717
|
+
},
|
|
32718
|
+
annotations: {
|
|
32719
|
+
type: "boolean",
|
|
32720
|
+
description: "Print GitHub annotations (default when GITHUB_ACTIONS=true)"
|
|
32721
|
+
},
|
|
32722
|
+
assume: {
|
|
32723
|
+
type: "string",
|
|
32724
|
+
description: "A mock for every case: build.unit=failure, build.unit.outputs.sha=abc (repeatable)"
|
|
32725
|
+
},
|
|
32726
|
+
"assume-file": {
|
|
32727
|
+
type: "string",
|
|
32728
|
+
description: "A YAML list of mocks for every case"
|
|
32729
|
+
}
|
|
32730
|
+
};
|
|
32517
32731
|
const testCommand = defineCommand({
|
|
32518
32732
|
meta: {
|
|
32519
32733
|
name: "test",
|
|
32520
32734
|
description: "Run the route tests under .github/simulci/tests"
|
|
32521
32735
|
},
|
|
32522
|
-
args:
|
|
32523
|
-
|
|
32524
|
-
|
|
32525
|
-
description: "Test files (default: every *.test.yml under .github/simulci/tests)",
|
|
32526
|
-
required: false
|
|
32527
|
-
},
|
|
32528
|
-
dir: {
|
|
32529
|
-
type: "string",
|
|
32530
|
-
description: "The repository checkout",
|
|
32531
|
-
default: "."
|
|
32532
|
-
},
|
|
32533
|
-
junit: {
|
|
32534
|
-
type: "string",
|
|
32535
|
-
description: "Write a JUnit report to this file"
|
|
32536
|
-
},
|
|
32537
|
-
update: {
|
|
32538
|
-
type: "boolean",
|
|
32539
|
-
alias: "u",
|
|
32540
|
-
description: "Write every snapshot and prune stale ones",
|
|
32541
|
-
default: false
|
|
32542
|
-
},
|
|
32543
|
-
snapshotDetail: {
|
|
32544
|
-
type: "string",
|
|
32545
|
-
description: "steps or legs",
|
|
32546
|
-
default: "steps"
|
|
32547
|
-
},
|
|
32548
|
-
filter: {
|
|
32549
|
-
type: "string",
|
|
32550
|
-
alias: "t",
|
|
32551
|
-
description: "Run the cases whose name contains the text"
|
|
32552
|
-
},
|
|
32553
|
-
ci: {
|
|
32554
|
-
type: "boolean",
|
|
32555
|
-
description: "Never write snapshots; a missing one fails; only is an error (default when CI=true)"
|
|
32556
|
-
},
|
|
32557
|
-
strict: {
|
|
32558
|
-
type: "boolean",
|
|
32559
|
-
description: "Ignore allowMaybe; stale snapshots fail",
|
|
32560
|
-
default: false
|
|
32561
|
-
},
|
|
32562
|
-
annotations: {
|
|
32563
|
-
type: "boolean",
|
|
32564
|
-
description: "Print GitHub annotations (default when GITHUB_ACTIONS=true)"
|
|
32565
|
-
},
|
|
32566
|
-
assume: {
|
|
32567
|
-
type: "string",
|
|
32568
|
-
description: "A mock for every case: build.unit=failure, build.unit.outputs.sha=abc (repeatable)"
|
|
32569
|
-
},
|
|
32570
|
-
assumeFile: {
|
|
32571
|
-
type: "string",
|
|
32572
|
-
description: "A YAML list of mocks for every case"
|
|
32573
|
-
}
|
|
32574
|
-
},
|
|
32575
|
-
run({ args }) {
|
|
32576
|
-
const a = args;
|
|
32577
|
-
const files = a._?.length ? a._ : a.files;
|
|
32578
|
-
const code = runTestCommand({
|
|
32579
|
-
...a,
|
|
32580
|
-
...files === void 0 ? {} : { files }
|
|
32581
|
-
}, {
|
|
32736
|
+
args: TEST_ARGS,
|
|
32737
|
+
run({ rawArgs }) {
|
|
32738
|
+
const io = {
|
|
32582
32739
|
stdout: (t) => process.stdout.write(t),
|
|
32583
32740
|
stderr: (t) => process.stderr.write(t),
|
|
32584
32741
|
env: process.env
|
|
32585
|
-
}
|
|
32586
|
-
process.exitCode =
|
|
32742
|
+
};
|
|
32743
|
+
process.exitCode = exitCodeOf(() => runTestCommand(testArgs(rawArgs), io), io);
|
|
32587
32744
|
}
|
|
32588
32745
|
});
|
|
32746
|
+
/**
|
|
32747
|
+
* The arguments of a `simulci test` command line: every test file named, and every `--assume`. A `FlagError` when
|
|
32748
|
+
* `--snapshot-detail` is given a value it does not take.
|
|
32749
|
+
*/
|
|
32750
|
+
function testArgs(rawArgs) {
|
|
32751
|
+
const a = readArgs(rawArgs, TEST_ARGS, { repeated: ["assume"] });
|
|
32752
|
+
const args = {
|
|
32753
|
+
...a,
|
|
32754
|
+
snapshotDetail: oneOf("snapshot-detail", a.snapshotDetail, DETAILS)
|
|
32755
|
+
};
|
|
32756
|
+
const files = a._;
|
|
32757
|
+
return files.length ? {
|
|
32758
|
+
...args,
|
|
32759
|
+
files
|
|
32760
|
+
} : args;
|
|
32761
|
+
}
|
|
32589
32762
|
runMain(defineCommand({
|
|
32590
32763
|
meta: {
|
|
32591
32764
|
name: "simulci",
|