@step-forge/step-forge 0.0.21 → 0.0.22
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/RUNTIME.md +42 -12
- package/dist/{analyzer-DYfdoSIS.js → analyzer-byS8yRrY.js} +52 -24
- package/dist/analyzer-byS8yRrY.js.map +1 -0
- package/dist/analyzer-cli.js +1 -1
- package/dist/analyzer.d.ts +2 -0
- package/dist/analyzer.js +1 -1
- package/dist/cli.cjs +129 -74
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +129 -74
- package/dist/cli.js.map +1 -1
- package/dist/{hooks-Dar49TtT.d.ts → config-C7PCYgYy.d.cts} +65 -16
- package/dist/{hooks-Dar49TtT.d.cts → config-C7PCYgYy.d.ts} +65 -16
- package/dist/{engine-DPRxs6eC.js → engine-DPVLEHBi.js} +8 -26
- package/dist/engine-DPVLEHBi.js.map +1 -0
- package/dist/{engine-DWAIlwWp.cjs → engine-vqA-eL_T.cjs} +8 -26
- package/dist/engine-vqA-eL_T.cjs.map +1 -0
- package/dist/{gherkinParser-CHpkEwii.cjs → gherkinParser-BT40q_i3.cjs} +97 -2
- package/dist/gherkinParser-BT40q_i3.cjs.map +1 -0
- package/dist/{gherkinParser-CKARHgt4.js → gherkinParser-NcttZgN4.js} +74 -3
- package/dist/gherkinParser-NcttZgN4.js.map +1 -0
- package/dist/hooks-BDCMKeNq.js +71 -0
- package/dist/{hooks-CywugMQQ.js.map → hooks-BDCMKeNq.js.map} +1 -1
- package/dist/{hooks-CGYzwDOv.cjs → hooks-Be0cjULN.cjs} +20 -31
- package/dist/{hooks-CGYzwDOv.cjs.map → hooks-Be0cjULN.cjs.map} +1 -1
- package/dist/runtime.cjs +3 -3
- package/dist/runtime.d.cts +11 -48
- package/dist/runtime.d.ts +11 -48
- package/dist/runtime.js +3 -3
- package/dist/step-forge.cjs +65 -28
- package/dist/step-forge.cjs.map +1 -1
- package/dist/step-forge.d.cts +13 -4
- package/dist/step-forge.d.ts +13 -4
- package/dist/step-forge.js +65 -28
- package/dist/step-forge.js.map +1 -1
- package/package.json +2 -2
- package/dist/analyzer-DYfdoSIS.js.map +0 -1
- package/dist/engine-DPRxs6eC.js.map +0 -1
- package/dist/engine-DWAIlwWp.cjs.map +0 -1
- package/dist/gherkinParser-CHpkEwii.cjs.map +0 -1
- package/dist/gherkinParser-CKARHgt4.js.map +0 -1
- package/dist/hooks-CywugMQQ.js +0 -82
package/dist/cli.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
|
-
import { i as
|
|
3
|
-
import { i as BasicWorld, n as parseFeatureFiles, r as globFiles } from "./gherkinParser-
|
|
4
|
-
import { i as runScenario, r as compileRegistry } from "./engine-
|
|
5
|
-
import { access } from "node:fs/promises";
|
|
2
|
+
import { i as runHooksParallel, n as globalHookRegistry, o as globalRegistry, r as runHooks } from "./hooks-BDCMKeNq.js";
|
|
3
|
+
import { c as userFrames, i as BasicWorld, n as parseFeatureFiles, o as relativeFrame, r as globFiles, s as relativeLocation } from "./gherkinParser-NcttZgN4.js";
|
|
4
|
+
import { i as runScenario, r as compileRegistry } from "./engine-DPVLEHBi.js";
|
|
6
5
|
import * as path from "node:path";
|
|
7
6
|
import { relative } from "node:path";
|
|
8
|
-
import { parseArgs } from "node:util";
|
|
9
7
|
import { pathToFileURL } from "node:url";
|
|
8
|
+
import { access } from "node:fs/promises";
|
|
9
|
+
import { parseArgs } from "node:util";
|
|
10
10
|
//#region src/runtime/config.ts
|
|
11
11
|
const DEFAULT_FEATURES = "**/*.feature";
|
|
12
12
|
const DEFAULT_STEPS = "**/*.steps.ts";
|
|
@@ -58,6 +58,7 @@ function resolveConfig(cwd, file, cli) {
|
|
|
58
58
|
world: pick("world"),
|
|
59
59
|
concurrency: pick("concurrency") ?? 1,
|
|
60
60
|
reporter: pick("reporter") ?? "pretty",
|
|
61
|
+
verbose: pick("verbose") ?? false,
|
|
61
62
|
name: pick("name"),
|
|
62
63
|
tags: pick("tags")
|
|
63
64
|
};
|
|
@@ -169,7 +170,6 @@ const c = {
|
|
|
169
170
|
green: wrap(32, 39),
|
|
170
171
|
red: wrap(31, 39),
|
|
171
172
|
yellow: wrap(33, 39),
|
|
172
|
-
cyan: wrap(36, 39),
|
|
173
173
|
dim: wrap(2, 22),
|
|
174
174
|
bold: wrap(1, 22)
|
|
175
175
|
};
|
|
@@ -178,37 +178,69 @@ const STATUS_MARK = {
|
|
|
178
178
|
failed: c.red("✗"),
|
|
179
179
|
skipped: c.yellow("-")
|
|
180
180
|
};
|
|
181
|
-
/**
|
|
181
|
+
/** ✓ / ✗ / - for a whole scenario (skipped = every step skipped). */
|
|
182
|
+
function scenarioMark(result) {
|
|
183
|
+
if (result.status === "failed") return c.red("✗");
|
|
184
|
+
if (result.steps.every((s) => s.status === "skipped")) return c.yellow("-");
|
|
185
|
+
return c.green("✓");
|
|
186
|
+
}
|
|
187
|
+
/** Dot per scenario for the live heartbeat: `.` pass / `F` fail / `-` skip. */
|
|
188
|
+
function scenarioDot(result) {
|
|
189
|
+
if (result.status === "failed") return c.red("F");
|
|
190
|
+
if (result.steps.every((s) => s.status === "skipped")) return c.yellow("-");
|
|
191
|
+
return c.green(".");
|
|
192
|
+
}
|
|
193
|
+
/** Indent every non-empty line of a block by `pad` spaces. */
|
|
182
194
|
function indent(text, pad) {
|
|
183
195
|
const prefix = " ".repeat(pad);
|
|
184
196
|
return text.split("\n").map((line) => line ? prefix + line : line).join("\n");
|
|
185
197
|
}
|
|
186
|
-
/**
|
|
187
|
-
|
|
188
|
-
|
|
198
|
+
/**
|
|
199
|
+
* The error, trimmed to user frames: `Name: message` followed by only the
|
|
200
|
+
* caller's own stack frames (library/engine and `node_modules` frames removed),
|
|
201
|
+
* source-mapped by Bun to the original `.ts`. Falls back to just the message
|
|
202
|
+
* when nothing user-owned is left (e.g. an undefined-step error).
|
|
203
|
+
*/
|
|
204
|
+
function renderError(error, cwd) {
|
|
205
|
+
const header = `${error.name}: ${error.message}`;
|
|
206
|
+
const frames = userFrames(error.stack).map((f) => ` at ${relativeFrame(f, cwd)}`);
|
|
207
|
+
return frames.length ? `${header}\n${frames.join("\n")}` : header;
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* The detail block shown beneath a failing step: the `.feature` line, the step
|
|
211
|
+
* definition location, and the trimmed error — the two coordinates that make a
|
|
212
|
+
* failure easy to chase (where in the feature, which step definition).
|
|
213
|
+
*/
|
|
214
|
+
function failureDetail(stepResult, result, cwd) {
|
|
215
|
+
const lines = [`feature: ${relative(cwd, result.scenario.file)}:${stepResult.step.line}`];
|
|
216
|
+
if (stepResult.source) lines.push(`defined: ${relativeLocation(stepResult.source, cwd)}`);
|
|
217
|
+
if (stepResult.error) lines.push(renderError(stepResult.error, cwd));
|
|
218
|
+
return indent(c.red(lines.join("\n")), 6);
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* One scenario as a Cucumber-style block: a marked header with its `.feature`
|
|
222
|
+
* location, then each step with its mark (and, in `verbose`, the step
|
|
223
|
+
* definition location as a trailing comment). A failing step is followed by its
|
|
224
|
+
* failure detail; a scenario-level error (a failing hook, no step to blame) is
|
|
225
|
+
* appended at the end.
|
|
226
|
+
*/
|
|
227
|
+
function renderScenario(result, cwd, opts) {
|
|
189
228
|
const label = result.scenario.outline ? `${result.scenario.outline.name} › ${result.scenario.name}` : result.scenario.name;
|
|
190
|
-
lines
|
|
229
|
+
const lines = [`${scenarioMark(result)} ${c.bold(label)}`];
|
|
230
|
+
const loc = result.scenario.line ? `${relative(cwd, result.scenario.file)}:${result.scenario.line}` : relative(cwd, result.scenario.file);
|
|
231
|
+
lines.push(indent(c.dim(loc), 4));
|
|
232
|
+
lines.push("");
|
|
191
233
|
for (const step of result.scenario.steps) {
|
|
192
|
-
const
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
lines.push(indent(c.red(stack), 6));
|
|
234
|
+
const sr = result.steps.find((s) => s.step === step);
|
|
235
|
+
const status = sr?.status ?? "skipped";
|
|
236
|
+
const comment = opts.stepSource && sr?.source ? c.dim(` # ${relativeLocation(sr.source, cwd)}`) : "";
|
|
237
|
+
lines.push(` ${STATUS_MARK[status]} ${c.dim(step.effectiveKeyword)} ${step.text}${comment}`);
|
|
238
|
+
if (sr?.status === "failed") lines.push(failureDetail(sr, result, cwd));
|
|
198
239
|
}
|
|
240
|
+
const stepFailed = result.steps.some((s) => s.status === "failed");
|
|
241
|
+
if (result.error && !stepFailed) lines.push(indent(c.red(renderError(result.error, cwd)), 4));
|
|
199
242
|
return lines.join("\n");
|
|
200
243
|
}
|
|
201
|
-
/** Group results by feature file, preserving first-seen order. */
|
|
202
|
-
function groupByFeature(results) {
|
|
203
|
-
const groups = /* @__PURE__ */ new Map();
|
|
204
|
-
for (const result of results) {
|
|
205
|
-
const key = result.scenario.file;
|
|
206
|
-
const bucket = groups.get(key);
|
|
207
|
-
if (bucket) bucket.push(result);
|
|
208
|
-
else groups.set(key, [result]);
|
|
209
|
-
}
|
|
210
|
-
return groups;
|
|
211
|
-
}
|
|
212
244
|
function tally(results) {
|
|
213
245
|
const totals = {
|
|
214
246
|
scenarios: {
|
|
@@ -230,71 +262,84 @@ function tally(results) {
|
|
|
230
262
|
}
|
|
231
263
|
return totals;
|
|
232
264
|
}
|
|
233
|
-
/** The shared end-of-run summary block: counts + duration. */
|
|
234
265
|
function renderSummary(results, durationMs) {
|
|
235
266
|
const t = tally(results);
|
|
236
267
|
const scenarioTotal = t.scenarios.passed + t.scenarios.failed + t.scenarios.skipped;
|
|
237
268
|
const stepTotal = t.steps.passed + t.steps.failed + t.steps.skipped;
|
|
238
269
|
const part = (n, label, color) => n > 0 ? color(`${n} ${label}`) : null;
|
|
239
|
-
const
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
part(t.steps.skipped, "skipped", c.yellow)
|
|
248
|
-
].filter((x) => x !== null);
|
|
249
|
-
const seconds = (durationMs / 1e3).toFixed(2);
|
|
270
|
+
const line = (total, noun, counts) => {
|
|
271
|
+
const parts = [
|
|
272
|
+
part(counts.passed, "passed", c.green),
|
|
273
|
+
part(counts.failed, "failed", c.red),
|
|
274
|
+
part(counts.skipped, "skipped", c.yellow)
|
|
275
|
+
].filter((x) => x !== null);
|
|
276
|
+
return `${total} ${noun}${total === 1 ? "" : "s"} (${parts.join(", ")})`;
|
|
277
|
+
};
|
|
250
278
|
return [
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
c.dim(`${
|
|
279
|
+
line(scenarioTotal, "scenario", t.scenarios),
|
|
280
|
+
line(stepTotal, "step", t.steps),
|
|
281
|
+
c.dim(`${(durationMs / 1e3).toFixed(2)}s`)
|
|
254
282
|
].join("\n");
|
|
255
283
|
}
|
|
284
|
+
function write(text) {
|
|
285
|
+
process.stdout.write(text);
|
|
286
|
+
}
|
|
287
|
+
/** Feature-grouped tree of every scenario (used by `--verbose`). */
|
|
288
|
+
function renderFullTree(results, cwd) {
|
|
289
|
+
const groups = /* @__PURE__ */ new Map();
|
|
290
|
+
for (const r of results) {
|
|
291
|
+
const bucket = groups.get(r.scenario.file) ?? [];
|
|
292
|
+
bucket.push(r);
|
|
293
|
+
groups.set(r.scenario.file, bucket);
|
|
294
|
+
}
|
|
295
|
+
const out = [];
|
|
296
|
+
for (const [file, group] of groups) {
|
|
297
|
+
out.push(c.bold(`Feature: ${relative(cwd, file)}`), "");
|
|
298
|
+
for (const r of group) out.push(indent(renderScenario(r, cwd, { stepSource: true }), 2), "");
|
|
299
|
+
}
|
|
300
|
+
return out.join("\n");
|
|
301
|
+
}
|
|
302
|
+
/** The failing scenarios only (used by the default, non-verbose output). */
|
|
303
|
+
function renderFailures(results, cwd) {
|
|
304
|
+
const failed = results.filter((r) => r.status === "failed");
|
|
305
|
+
if (!failed.length) return "";
|
|
306
|
+
return failed.map((r) => renderScenario(r, cwd, { stepSource: false })).join("\n\n") + "\n\n";
|
|
307
|
+
}
|
|
256
308
|
/**
|
|
257
|
-
* Default reporter:
|
|
258
|
-
*
|
|
259
|
-
*
|
|
309
|
+
* Default reporter. Non-verbose: a dot per scenario as a heartbeat, then the
|
|
310
|
+
* failing scenarios in Cucumber-style detail, then the summary. Verbose: the
|
|
311
|
+
* full feature → scenario → step tree instead of just failures.
|
|
260
312
|
*/
|
|
261
|
-
function prettyReporter(
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
313
|
+
function prettyReporter(opts = {}) {
|
|
314
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
315
|
+
const verbose = opts.verbose ?? false;
|
|
316
|
+
return {
|
|
317
|
+
onScenarioEnd(result) {
|
|
318
|
+
if (!verbose) write(scenarioDot(result));
|
|
319
|
+
},
|
|
320
|
+
onComplete(results, durationMs) {
|
|
321
|
+
write(`${verbose ? `\n${renderFullTree(results, cwd)}\n` : `\n\n${renderFailures(results, cwd)}`}${renderSummary(results, durationMs)}\n`);
|
|
269
322
|
}
|
|
270
|
-
|
|
271
|
-
process.stdout.write(out.join("\n") + "\n");
|
|
272
|
-
} };
|
|
323
|
+
};
|
|
273
324
|
}
|
|
274
325
|
/**
|
|
275
|
-
* Compact reporter:
|
|
276
|
-
*
|
|
326
|
+
* Compact reporter: a dot per scenario, then failures in detail and the
|
|
327
|
+
* summary. Always minimal — `verbose` is accepted for interface parity but does
|
|
328
|
+
* not expand the output (use `pretty --verbose` for the full tree).
|
|
277
329
|
*/
|
|
278
|
-
function progressReporter(
|
|
330
|
+
function progressReporter(opts = {}) {
|
|
331
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
279
332
|
return {
|
|
280
333
|
onScenarioEnd(result) {
|
|
281
|
-
|
|
282
|
-
process.stdout.write(mark);
|
|
334
|
+
write(scenarioDot(result));
|
|
283
335
|
},
|
|
284
336
|
onComplete(results, durationMs) {
|
|
285
|
-
|
|
286
|
-
const out = ["", ""];
|
|
287
|
-
if (failures.length) {
|
|
288
|
-
out.push(c.bold("Failures:"), "");
|
|
289
|
-
for (const result of failures) out.push(c.bold(`Feature: ${relative(cwd, result.scenario.file)}`), renderScenario(result), "");
|
|
290
|
-
}
|
|
291
|
-
out.push(renderSummary(results, durationMs));
|
|
292
|
-
process.stdout.write(out.join("\n") + "\n");
|
|
337
|
+
write(`\n\n${renderFailures(results, cwd)}${renderSummary(results, durationMs)}\n`);
|
|
293
338
|
}
|
|
294
339
|
};
|
|
295
340
|
}
|
|
296
|
-
function makeReporter(name,
|
|
297
|
-
return name === "progress" ? progressReporter(
|
|
341
|
+
function makeReporter(name, opts = {}) {
|
|
342
|
+
return name === "progress" ? progressReporter(opts) : prettyReporter(opts);
|
|
298
343
|
}
|
|
299
344
|
//#endregion
|
|
300
345
|
//#region src/runtime/runner.ts
|
|
@@ -349,7 +394,10 @@ async function runPool(items, limit, worker) {
|
|
|
349
394
|
* compiled-once step table with global/feature hooks around the batch. Never
|
|
350
395
|
* throws for test failures — inspect `RunResult.passed`.
|
|
351
396
|
*/
|
|
352
|
-
async function run(config, reporter = makeReporter(config.reporter,
|
|
397
|
+
async function run(config, reporter = makeReporter(config.reporter, {
|
|
398
|
+
cwd: config.cwd,
|
|
399
|
+
verbose: config.verbose
|
|
400
|
+
})) {
|
|
353
401
|
const start = typeof performance !== "undefined" ? performance.now() : Date.now();
|
|
354
402
|
const [featureFiles, stepFiles] = await Promise.all([globFiles(config.features, config.cwd), globFiles(config.steps, config.cwd)]);
|
|
355
403
|
await importSteps(stepFiles);
|
|
@@ -359,7 +407,7 @@ async function run(config, reporter = makeReporter(config.reporter, config.cwd))
|
|
|
359
407
|
name: config.name,
|
|
360
408
|
tags: config.tags
|
|
361
409
|
});
|
|
362
|
-
await
|
|
410
|
+
await runHooksParallel("global", "before", globalHookRegistry);
|
|
363
411
|
await runHooks("feature", "before", globalHookRegistry);
|
|
364
412
|
const results = await runPool(selected, config.concurrency, async (scenario) => {
|
|
365
413
|
const result = scenario.tags.includes("@skip") ? skippedResult(scenario) : await runScenario(scenario, compiled, makeWorld, globalHookRegistry);
|
|
@@ -367,6 +415,7 @@ async function run(config, reporter = makeReporter(config.reporter, config.cwd))
|
|
|
367
415
|
return result;
|
|
368
416
|
});
|
|
369
417
|
await runHooks("feature", "after", globalHookRegistry);
|
|
418
|
+
await runHooksParallel("global", "after", globalHookRegistry);
|
|
370
419
|
const durationMs = (typeof performance !== "undefined" ? performance.now() : Date.now()) - start;
|
|
371
420
|
reporter.onComplete(results, durationMs);
|
|
372
421
|
return {
|
|
@@ -389,6 +438,7 @@ Options:
|
|
|
389
438
|
-w, --world <module> World factory module (default export () => world)
|
|
390
439
|
-c, --concurrency <n> Max scenarios in flight (default: 1, i.e. serial)
|
|
391
440
|
-r, --reporter <name> "pretty" (default) or "progress"
|
|
441
|
+
-v, --verbose Report every scenario, not just failures
|
|
392
442
|
--config <path> Config file directory (default: cwd)
|
|
393
443
|
-h, --help Show this help
|
|
394
444
|
|
|
@@ -425,6 +475,10 @@ function parseCli(argv) {
|
|
|
425
475
|
type: "string",
|
|
426
476
|
short: "r"
|
|
427
477
|
},
|
|
478
|
+
verbose: {
|
|
479
|
+
type: "boolean",
|
|
480
|
+
short: "v"
|
|
481
|
+
},
|
|
428
482
|
config: { type: "string" },
|
|
429
483
|
help: {
|
|
430
484
|
type: "boolean",
|
|
@@ -442,6 +496,7 @@ function parseCli(argv) {
|
|
|
442
496
|
if (values.name) overrides.name = values.name;
|
|
443
497
|
if (values.steps) overrides.steps = values.steps;
|
|
444
498
|
if (values.world) overrides.world = values.world;
|
|
499
|
+
if (values.verbose) overrides.verbose = true;
|
|
445
500
|
if (values.reporter) {
|
|
446
501
|
if (values.reporter !== "pretty" && values.reporter !== "progress") throw new Error(`Unknown reporter: ${values.reporter}`);
|
|
447
502
|
overrides.reporter = values.reporter;
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","names":[],"sources":["../../src/runtime/config.ts","../../src/runtime/filter.ts","../../src/runtime/reporters.ts","../../src/runtime/runner.ts","../../src/runtime/cli.ts"],"sourcesContent":["import * as path from \"node:path\";\nimport { pathToFileURL } from \"node:url\";\nimport { access } from \"node:fs/promises\";\n\n/**\n * User-facing runner configuration. The same shape is accepted from a\n * `step-forge.config.ts` file (default export) and from CLI flags, with flags\n * taking precedence. Everything is optional; {@link resolveConfig} fills in\n * defaults.\n */\nexport interface RunnerOptions {\n /** Feature-file glob(s), relative to `cwd`. Defaults to every `.feature` file. */\n features?: string | string[];\n /** Step-module glob(s), relative to `cwd`. Defaults to every `.steps.ts` file. */\n steps?: string | string[];\n /**\n * Module that default-exports a world factory `() => world`, relative to\n * `cwd`. When omitted each scenario gets a fresh `BasicWorld`.\n */\n world?: string;\n /**\n * Max scenarios in flight at once. Defaults to `1` (serial), matching\n * Cucumber's default execution model — scenarios often share module-level\n * state via hooks, which only holds under serial execution. Raise it to opt\n * into parallelism for isolated, I/O-bound suites.\n */\n concurrency?: number;\n /** Reporter name. Default `pretty`. */\n reporter?: \"pretty\" | \"progress\";\n /** Only run scenarios whose name matches this (string → substring/regex). */\n name?: string;\n /** Cucumber tag expression, e.g. `@smoke and not @wip`. */\n tags?: string;\n}\n\n/** Fully-resolved config: no optionals, globs kept as arrays, paths absolute. */\nexport interface ResolvedConfig {\n cwd: string;\n features: string[];\n steps: string[];\n world?: string;\n concurrency: number;\n reporter: \"pretty\" | \"progress\";\n name?: string;\n tags?: string;\n}\n\nconst DEFAULT_FEATURES = \"**/*.feature\";\nconst DEFAULT_STEPS = \"**/*.steps.ts\";\nconst CONFIG_BASENAMES = [\n \"step-forge.config.ts\",\n \"step-forge.config.mts\",\n \"step-forge.config.js\",\n \"step-forge.config.mjs\",\n];\n\nfunction toArray<T>(value: T | T[] | undefined): T[] {\n if (value === undefined) return [];\n return Array.isArray(value) ? value : [value];\n}\n\nasync function exists(p: string): Promise<boolean> {\n try {\n await access(p);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Locate and import a `step-forge.config.*` file from `cwd`, returning its\n * default export (or `{}` if none is present). Imported by absolute file URL so\n * Bun transpiles the TypeScript config natively.\n */\nexport async function loadConfigFile(cwd: string): Promise<RunnerOptions> {\n for (const basename of CONFIG_BASENAMES) {\n const candidate = path.join(cwd, basename);\n if (!(await exists(candidate))) continue;\n const mod = await import(pathToFileURL(candidate).href);\n return (mod.default ?? mod) as RunnerOptions;\n }\n return {};\n}\n\n/**\n * Merge file config with CLI overrides and apply defaults. CLI overrides win\n * field-by-field; array globs are normalised to arrays and default when neither\n * source provides them.\n */\nexport function resolveConfig(\n cwd: string,\n file: RunnerOptions,\n cli: RunnerOptions\n): ResolvedConfig {\n const pick = <K extends keyof RunnerOptions>(key: K): RunnerOptions[K] =>\n cli[key] ?? file[key];\n\n const features = toArray(pick(\"features\"));\n const steps = toArray(pick(\"steps\"));\n\n return {\n cwd,\n features: features.length ? features : [DEFAULT_FEATURES],\n steps: steps.length ? steps : [DEFAULT_STEPS],\n world: pick(\"world\"),\n concurrency: pick(\"concurrency\") ?? 1,\n reporter: pick(\"reporter\") ?? \"pretty\",\n name: pick(\"name\"),\n tags: pick(\"tags\"),\n };\n}\n","import { ParsedScenario } from \"../analyzer/types\";\n\n/**\n * A predicate over a scenario's tags, compiled from a Cucumber tag expression.\n * Supports `and`, `or`, `not`, parentheses, and bare `@tag` atoms — the common\n * subset of Cucumber's tag-expression language. Kept dependency-free; swap for\n * `@cucumber/tag-expressions` if fuller parity is needed.\n */\nexport type TagPredicate = (tags: readonly string[]) => boolean;\n\n/** Tokenise a tag expression into atoms, operators, and parentheses. */\nfunction tokenize(expr: string): string[] {\n const tokens: string[] = [];\n const re = /\\s*(\\(|\\)|@[^\\s()]+|\\band\\b|\\bor\\b|\\bnot\\b)\\s*/gy;\n let index = 0;\n while (index < expr.length) {\n re.lastIndex = index;\n const m = re.exec(expr);\n if (!m || m.index !== index) {\n throw new Error(`Invalid tag expression near: ${expr.slice(index)}`);\n }\n tokens.push(m[1]);\n index = re.lastIndex;\n }\n return tokens;\n}\n\n/**\n * Compile a tag expression into a predicate via recursive descent\n * (or → and → not → primary). Throws on malformed input so a bad `--tags` flag\n * fails loudly rather than silently matching nothing.\n */\nexport function compileTagExpression(expr: string): TagPredicate {\n const tokens = tokenize(expr);\n let pos = 0;\n\n const peek = () => tokens[pos];\n const next = () => tokens[pos++];\n\n const parseOr = (): TagPredicate => {\n let left = parseAnd();\n while (peek() === \"or\") {\n next();\n const right = parseAnd();\n const l = left;\n left = tags => l(tags) || right(tags);\n }\n return left;\n };\n\n const parseAnd = (): TagPredicate => {\n let left = parseNot();\n while (peek() === \"and\") {\n next();\n const right = parseNot();\n const l = left;\n left = tags => l(tags) && right(tags);\n }\n return left;\n };\n\n const parseNot = (): TagPredicate => {\n if (peek() === \"not\") {\n next();\n const operand = parseNot();\n return tags => !operand(tags);\n }\n return parsePrimary();\n };\n\n const parsePrimary = (): TagPredicate => {\n const token = next();\n if (token === \"(\") {\n const inner = parseOr();\n if (next() !== \")\") throw new Error(\"Unbalanced parentheses in tags\");\n return inner;\n }\n if (token === undefined || !token.startsWith(\"@\")) {\n throw new Error(`Expected a tag, got: ${token ?? \"end of input\"}`);\n }\n return tags => tags.includes(token);\n };\n\n const predicate = parseOr();\n if (pos !== tokens.length) {\n throw new Error(`Unexpected token in tag expression: ${peek()}`);\n }\n return predicate;\n}\n\nexport interface SelectOptions {\n /** Substring or `/regex/flags` match against the scenario (or outline) name. */\n name?: string;\n /** Cucumber tag expression. */\n tags?: string;\n}\n\n/** Parse a `/pattern/flags` string into a RegExp, else treat it as a substring. */\nfunction toNameMatcher(name: string): (candidate: string) => boolean {\n const delim = /^\\/(.*)\\/([a-z]*)$/.exec(name);\n if (delim) {\n const re = new RegExp(delim[1], delim[2]);\n return candidate => re.test(candidate);\n }\n return candidate => candidate.includes(name);\n}\n\n/**\n * Narrow scenarios by name and tags, then apply `@only` focus. `@skip` is *not*\n * applied here — skipped scenarios stay in the set so the runner can report them\n * as skipped rather than silently dropping them. Returns scenarios in input\n * order.\n *\n * `@only` semantics mirror the Vitest plugin: if any surviving scenario is\n * tagged `@only` (and not `@skip`), the run is focused to just those.\n */\nexport function selectScenarios(\n scenarios: ParsedScenario[],\n options: SelectOptions\n): ParsedScenario[] {\n let selected = scenarios;\n\n if (options.name) {\n const matches = toNameMatcher(options.name);\n selected = selected.filter(\n s => matches(s.name) || (s.outline ? matches(s.outline.name) : false)\n );\n }\n\n if (options.tags) {\n const predicate = compileTagExpression(options.tags);\n selected = selected.filter(s => predicate(s.tags));\n }\n\n const focused = selected.filter(\n s => s.tags.includes(\"@only\") && !s.tags.includes(\"@skip\")\n );\n return focused.length ? focused : selected;\n}\n","import { relative } from \"node:path\";\nimport { ScenarioResult, StepResult } from \"./engine\";\n\n/**\n * A reporter observes the run. `onScenarioEnd` fires as each scenario finishes\n * (order is completion order, which under concurrency is non-deterministic);\n * `onComplete` fires once with every result for end-of-run summaries.\n */\nexport interface Reporter {\n onScenarioEnd?(result: ScenarioResult): void;\n onComplete(results: ScenarioResult[], durationMs: number): void;\n}\n\n// --- ANSI colouring -------------------------------------------------------\n// Honour NO_COLOR and non-TTY output; no dependency on a colour library.\nconst useColor =\n !process.env.NO_COLOR && (process.stdout.isTTY ?? false) === true;\n\nconst wrap = (open: number, close: number) => (s: string) =>\n useColor ? `\\x1b[${open}m${s}\\x1b[${close}m` : s;\n\nconst c = {\n green: wrap(32, 39),\n red: wrap(31, 39),\n yellow: wrap(33, 39),\n cyan: wrap(36, 39),\n dim: wrap(2, 22),\n bold: wrap(1, 22),\n};\n\nconst STATUS_MARK: Record<StepResult[\"status\"], string> = {\n passed: c.green(\"✓\"),\n failed: c.red(\"✗\"),\n skipped: c.yellow(\"-\"),\n};\n\n/** Indent every line of a block by `pad` spaces. */\nfunction indent(text: string, pad: number): string {\n const prefix = \" \".repeat(pad);\n return text\n .split(\"\\n\")\n .map(line => (line ? prefix + line : line))\n .join(\"\\n\");\n}\n\n/** Render one scenario as a Cucumber-style tree of steps. */\nfunction renderScenario(result: ScenarioResult): string {\n const lines: string[] = [];\n const label = result.scenario.outline\n ? `${result.scenario.outline.name} › ${result.scenario.name}`\n : result.scenario.name;\n lines.push(` Scenario: ${label}`);\n\n for (const step of result.scenario.steps) {\n const stepResult = result.steps.find(s => s.step === step);\n const status = stepResult?.status ?? \"skipped\";\n lines.push(\n ` ${STATUS_MARK[status]} ${c.dim(step.effectiveKeyword)} ${step.text}`\n );\n }\n\n if (result.error) {\n const stack = result.error.stack ?? String(result.error);\n lines.push(indent(c.red(stack), 6));\n }\n return lines.join(\"\\n\");\n}\n\n/** Group results by feature file, preserving first-seen order. */\nfunction groupByFeature(\n results: ScenarioResult[]\n): Map<string, ScenarioResult[]> {\n const groups = new Map<string, ScenarioResult[]>();\n for (const result of results) {\n const key = result.scenario.file;\n const bucket = groups.get(key);\n if (bucket) bucket.push(result);\n else groups.set(key, [result]);\n }\n return groups;\n}\n\ninterface Totals {\n scenarios: { passed: number; failed: number; skipped: number };\n steps: { passed: number; failed: number; skipped: number };\n}\n\nfunction tally(results: ScenarioResult[]): Totals {\n const totals: Totals = {\n scenarios: { passed: 0, failed: 0, skipped: 0 },\n steps: { passed: 0, failed: 0, skipped: 0 },\n };\n for (const result of results) {\n if (result.status === \"failed\") totals.scenarios.failed++;\n else if (result.steps.every(s => s.status === \"skipped\"))\n totals.scenarios.skipped++;\n else totals.scenarios.passed++;\n for (const step of result.steps) totals.steps[step.status]++;\n }\n return totals;\n}\n\n/** The shared end-of-run summary block: counts + duration. */\nfunction renderSummary(results: ScenarioResult[], durationMs: number): string {\n const t = tally(results);\n const scenarioTotal =\n t.scenarios.passed + t.scenarios.failed + t.scenarios.skipped;\n const stepTotal = t.steps.passed + t.steps.failed + t.steps.skipped;\n\n const part = (\n n: number,\n label: string,\n color: (s: string) => string\n ): string | null => (n > 0 ? color(`${n} ${label}`) : null);\n\n const scenarioParts = [\n part(t.scenarios.passed, \"passed\", c.green),\n part(t.scenarios.failed, \"failed\", c.red),\n part(t.scenarios.skipped, \"skipped\", c.yellow),\n ].filter((x): x is string => x !== null);\n\n const stepParts = [\n part(t.steps.passed, \"passed\", c.green),\n part(t.steps.failed, \"failed\", c.red),\n part(t.steps.skipped, \"skipped\", c.yellow),\n ].filter((x): x is string => x !== null);\n\n const seconds = (durationMs / 1000).toFixed(2);\n return [\n `${scenarioTotal} scenario${scenarioTotal === 1 ? \"\" : \"s\"} (${scenarioParts.join(\", \")})`,\n `${stepTotal} step${stepTotal === 1 ? \"\" : \"s\"} (${stepParts.join(\", \")})`,\n c.dim(`${seconds}s`),\n ].join(\"\\n\");\n}\n\n/**\n * Default reporter: prints the full feature → scenario → step tree grouped by\n * file, then the summary. Buffers to `onComplete` so concurrent scenarios don't\n * interleave mid-tree.\n */\nexport function prettyReporter(cwd: string = process.cwd()): Reporter {\n return {\n onComplete(results, durationMs) {\n const out: string[] = [];\n for (const [file, group] of groupByFeature(results)) {\n const featureName = group[0].scenario.file;\n out.push(c.bold(`Feature: ${relative(cwd, featureName || file)}`));\n for (const result of group) out.push(renderScenario(result));\n out.push(\"\");\n }\n out.push(renderSummary(results, durationMs));\n process.stdout.write(out.join(\"\\n\") + \"\\n\");\n },\n };\n}\n\n/**\n * Compact reporter: one character per scenario as it finishes (`.`/`F`/`-`),\n * then failures in detail and the summary. Best for large suites.\n */\nexport function progressReporter(cwd: string = process.cwd()): Reporter {\n return {\n onScenarioEnd(result) {\n const mark =\n result.status === \"failed\"\n ? c.red(\"F\")\n : result.steps.every(s => s.status === \"skipped\")\n ? c.yellow(\"-\")\n : c.green(\".\");\n process.stdout.write(mark);\n },\n onComplete(results, durationMs) {\n const failures = results.filter(r => r.status === \"failed\");\n const out: string[] = [\"\", \"\"];\n if (failures.length) {\n out.push(c.bold(\"Failures:\"), \"\");\n for (const result of failures) {\n out.push(\n c.bold(`Feature: ${relative(cwd, result.scenario.file)}`),\n renderScenario(result),\n \"\"\n );\n }\n }\n out.push(renderSummary(results, durationMs));\n process.stdout.write(out.join(\"\\n\") + \"\\n\");\n },\n };\n}\n\nexport function makeReporter(\n name: \"pretty\" | \"progress\",\n cwd?: string\n): Reporter {\n return name === \"progress\" ? progressReporter(cwd) : prettyReporter(cwd);\n}\n","/* eslint-disable @typescript-eslint/no-explicit-any */\nimport * as path from \"node:path\";\nimport { pathToFileURL } from \"node:url\";\nimport { ParsedScenario } from \"../analyzer/types\";\nimport { parseFeatureFiles } from \"../analyzer/gherkinParser\";\nimport { globFiles } from \"../globFiles\";\nimport { BasicWorld, MergeableWorld } from \"../world\";\nimport {\n compileRegistry,\n CompiledStep,\n runScenario,\n ScenarioResult,\n} from \"./engine\";\nimport { ensureGlobalHooks, globalHookRegistry, runHooks } from \"./hooks\";\nimport { globalRegistry } from \"./registry\";\nimport { selectScenarios } from \"./filter\";\nimport { makeReporter, Reporter } from \"./reporters\";\nimport { ResolvedConfig } from \"./config\";\n\ntype WorldFactory = () => MergeableWorld<any, any, any>;\n\n/**\n * Import every step-definition module so its `.step(...)` calls self-register\n * into the shared `globalRegistry`. Imported by file URL so Bun transpiles the\n * TypeScript natively.\n */\nasync function importSteps(files: string[]): Promise<void> {\n for (const file of files) {\n await import(pathToFileURL(file).href);\n }\n}\n\n/** Load the configured world factory, or default to a fresh `BasicWorld`. */\nasync function loadWorldFactory(\n world: string | undefined,\n cwd: string\n): Promise<WorldFactory> {\n if (!world) return () => new BasicWorld();\n const resolved = path.resolve(cwd, world);\n const mod = await import(pathToFileURL(resolved).href);\n const factory = mod.default ?? mod;\n if (typeof factory !== \"function\") {\n throw new Error(\n `World module ${world} must default-export a factory function () => world`\n );\n }\n return factory as WorldFactory;\n}\n\n/** A scenario carrying `@skip` never runs: report it with all steps skipped. */\nfunction skippedResult(scenario: ParsedScenario): ScenarioResult {\n return {\n scenario,\n status: \"passed\",\n steps: scenario.steps.map(step => ({ step, status: \"skipped\" as const })),\n };\n}\n\n/**\n * Run `items` through `worker`, at most `limit` in flight. Results come back in\n * input order even though completion order is not deterministic. A minimal\n * dependency-free pool — the whole point of the single-process model.\n */\nasync function runPool<T, R>(\n items: T[],\n limit: number,\n worker: (item: T, index: number) => Promise<R>\n): Promise<R[]> {\n const results = new Array<R>(items.length);\n let cursor = 0;\n const runNext = async (): Promise<void> => {\n while (cursor < items.length) {\n const index = cursor++;\n results[index] = await worker(items[index], index);\n }\n };\n const workers = Array.from(\n { length: Math.min(Math.max(1, limit), items.length) },\n runNext\n );\n await Promise.all(workers);\n return results;\n}\n\nexport interface RunResult {\n results: ScenarioResult[];\n passed: boolean;\n durationMs: number;\n}\n\n/**\n * Execute a whole run end to end: discover and import steps, load the world,\n * parse and select scenarios, then run them concurrently against a\n * compiled-once step table with global/feature hooks around the batch. Never\n * throws for test failures — inspect `RunResult.passed`.\n */\nexport async function run(\n config: ResolvedConfig,\n reporter: Reporter = makeReporter(config.reporter, config.cwd)\n): Promise<RunResult> {\n const start =\n typeof performance !== \"undefined\" ? performance.now() : Date.now();\n\n const [featureFiles, stepFiles] = await Promise.all([\n globFiles(config.features, config.cwd),\n globFiles(config.steps, config.cwd),\n ]);\n\n await importSteps(stepFiles);\n const makeWorld = await loadWorldFactory(config.world, config.cwd);\n\n const compiled: CompiledStep[] = compileRegistry(globalRegistry);\n const allScenarios = parseFeatureFiles(featureFiles);\n const selected = selectScenarios(allScenarios, {\n name: config.name,\n tags: config.tags,\n });\n\n // Global before-hooks once; feature before/after bracket the whole batch.\n // (Hooks aren't file-scoped in the registry, so per-file bracketing would be\n // meaningless under concurrent execution.)\n await ensureGlobalHooks(globalHookRegistry);\n await runHooks(\"feature\", \"before\", globalHookRegistry);\n\n const results = await runPool(\n selected,\n config.concurrency,\n async scenario => {\n const result = scenario.tags.includes(\"@skip\")\n ? skippedResult(scenario)\n : await runScenario(scenario, compiled, makeWorld, globalHookRegistry);\n reporter.onScenarioEnd?.(result);\n return result;\n }\n );\n\n await runHooks(\"feature\", \"after\", globalHookRegistry);\n\n const durationMs =\n (typeof performance !== \"undefined\" ? performance.now() : Date.now()) -\n start;\n reporter.onComplete(results, durationMs);\n\n return {\n results,\n passed: results.every(r => r.status !== \"failed\"),\n durationMs,\n };\n}\n","#!/usr/bin/env bun\nimport { parseArgs } from \"node:util\";\nimport * as path from \"node:path\";\nimport { loadConfigFile, resolveConfig, RunnerOptions } from \"./config\";\nimport { run } from \"./runner\";\n\nconst HELP = `step-forge — native TypeScript runner for Gherkin step definitions\n\nUsage:\n step-forge [options] [feature globs...]\n\nOptions:\n -t, --tags <expr> Tag expression, e.g. \"@smoke and not @wip\"\n -n, --name <pattern> Only scenarios whose name matches (substring or /regex/)\n -s, --steps <glob> Step-definition module glob (repeatable)\n -w, --world <module> World factory module (default export () => world)\n -c, --concurrency <n> Max scenarios in flight (default: 1, i.e. serial)\n -r, --reporter <name> \"pretty\" (default) or \"progress\"\n --config <path> Config file directory (default: cwd)\n -h, --help Show this help\n\nPositional arguments are feature globs and override the configured features.\n`;\n\n/** Parse argv into config overrides. Positional args become feature globs. */\nfunction parseCli(argv: string[]): { cwd: string; overrides: RunnerOptions } {\n const { values, positionals } = parseArgs({\n args: argv,\n allowPositionals: true,\n options: {\n tags: { type: \"string\", short: \"t\" },\n name: { type: \"string\", short: \"n\" },\n steps: { type: \"string\", short: \"s\", multiple: true },\n world: { type: \"string\", short: \"w\" },\n concurrency: { type: \"string\", short: \"c\" },\n reporter: { type: \"string\", short: \"r\" },\n config: { type: \"string\" },\n help: { type: \"boolean\", short: \"h\" },\n },\n });\n\n if (values.help) {\n process.stdout.write(HELP);\n process.exit(0);\n }\n\n const overrides: RunnerOptions = {};\n if (positionals.length) overrides.features = positionals;\n if (values.tags) overrides.tags = values.tags;\n if (values.name) overrides.name = values.name;\n if (values.steps) overrides.steps = values.steps;\n if (values.world) overrides.world = values.world;\n if (values.reporter) {\n if (values.reporter !== \"pretty\" && values.reporter !== \"progress\") {\n throw new Error(`Unknown reporter: ${values.reporter}`);\n }\n overrides.reporter = values.reporter;\n }\n if (values.concurrency !== undefined) {\n const n = Number(values.concurrency);\n if (!Number.isInteger(n) || n < 1) {\n throw new Error(`--concurrency must be a positive integer`);\n }\n overrides.concurrency = n;\n }\n\n const cwd = values.config\n ? path.resolve(process.cwd(), values.config)\n : process.cwd();\n return { cwd, overrides };\n}\n\nasync function main(): Promise<void> {\n const { cwd, overrides } = parseCli(process.argv.slice(2));\n const fileConfig = await loadConfigFile(cwd);\n const config = resolveConfig(cwd, fileConfig, overrides);\n const { passed } = await run(config);\n process.exitCode = passed ? 0 : 1;\n}\n\nmain().catch(err => {\n process.stderr.write(`${err instanceof Error ? err.stack : String(err)}\\n`);\n process.exitCode = 1;\n});\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"cli.js","names":[],"sources":["../../src/runtime/config.ts","../../src/runtime/filter.ts","../../src/runtime/reporters.ts","../../src/runtime/runner.ts","../../src/runtime/cli.ts"],"sourcesContent":["import * as path from \"node:path\";\nimport { pathToFileURL } from \"node:url\";\nimport { access } from \"node:fs/promises\";\n\n/**\n * User-facing runner configuration. The same shape is accepted from a\n * `step-forge.config.ts` file (default export) and from CLI flags, with flags\n * taking precedence. Everything is optional; {@link resolveConfig} fills in\n * defaults.\n */\nexport interface RunnerOptions {\n /** Feature-file glob(s), relative to `cwd`. Defaults to every `.feature` file. */\n features?: string | string[];\n /** Step-module glob(s), relative to `cwd`. Defaults to every `.steps.ts` file. */\n steps?: string | string[];\n /**\n * Module that default-exports a world factory `() => world`, relative to\n * `cwd`. When omitted each scenario gets a fresh `BasicWorld`.\n */\n world?: string;\n /**\n * Max scenarios in flight at once. Defaults to `1` (serial), matching\n * Cucumber's default execution model — scenarios often share module-level\n * state via hooks, which only holds under serial execution. Raise it to opt\n * into parallelism for isolated, I/O-bound suites.\n */\n concurrency?: number;\n /** Reporter name. Default `pretty`. */\n reporter?: \"pretty\" | \"progress\";\n /**\n * Verbose output: report every scenario (pass, fail, skip) instead of only\n * failures. Passed to whichever reporter is active. Default `false`.\n */\n verbose?: boolean;\n /** Only run scenarios whose name matches this (string → substring/regex). */\n name?: string;\n /** Cucumber tag expression, e.g. `@smoke and not @wip`. */\n tags?: string;\n}\n\n/** Fully-resolved config: no optionals, globs kept as arrays, paths absolute. */\nexport interface ResolvedConfig {\n cwd: string;\n features: string[];\n steps: string[];\n world?: string;\n concurrency: number;\n reporter: \"pretty\" | \"progress\";\n verbose: boolean;\n name?: string;\n tags?: string;\n}\n\nconst DEFAULT_FEATURES = \"**/*.feature\";\nconst DEFAULT_STEPS = \"**/*.steps.ts\";\nconst CONFIG_BASENAMES = [\n \"step-forge.config.ts\",\n \"step-forge.config.mts\",\n \"step-forge.config.js\",\n \"step-forge.config.mjs\",\n];\n\nfunction toArray<T>(value: T | T[] | undefined): T[] {\n if (value === undefined) return [];\n return Array.isArray(value) ? value : [value];\n}\n\nasync function exists(p: string): Promise<boolean> {\n try {\n await access(p);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Locate and import a `step-forge.config.*` file from `cwd`, returning its\n * default export (or `{}` if none is present). Imported by absolute file URL so\n * Bun transpiles the TypeScript config natively.\n */\nexport async function loadConfigFile(cwd: string): Promise<RunnerOptions> {\n for (const basename of CONFIG_BASENAMES) {\n const candidate = path.join(cwd, basename);\n if (!(await exists(candidate))) continue;\n const mod = await import(pathToFileURL(candidate).href);\n return (mod.default ?? mod) as RunnerOptions;\n }\n return {};\n}\n\n/**\n * Merge file config with CLI overrides and apply defaults. CLI overrides win\n * field-by-field; array globs are normalised to arrays and default when neither\n * source provides them.\n */\nexport function resolveConfig(\n cwd: string,\n file: RunnerOptions,\n cli: RunnerOptions\n): ResolvedConfig {\n const pick = <K extends keyof RunnerOptions>(key: K): RunnerOptions[K] =>\n cli[key] ?? file[key];\n\n const features = toArray(pick(\"features\"));\n const steps = toArray(pick(\"steps\"));\n\n return {\n cwd,\n features: features.length ? features : [DEFAULT_FEATURES],\n steps: steps.length ? steps : [DEFAULT_STEPS],\n world: pick(\"world\"),\n concurrency: pick(\"concurrency\") ?? 1,\n reporter: pick(\"reporter\") ?? \"pretty\",\n verbose: pick(\"verbose\") ?? false,\n name: pick(\"name\"),\n tags: pick(\"tags\"),\n };\n}\n","import { ParsedScenario } from \"../analyzer/types\";\n\n/**\n * A predicate over a scenario's tags, compiled from a Cucumber tag expression.\n * Supports `and`, `or`, `not`, parentheses, and bare `@tag` atoms — the common\n * subset of Cucumber's tag-expression language. Kept dependency-free; swap for\n * `@cucumber/tag-expressions` if fuller parity is needed.\n */\nexport type TagPredicate = (tags: readonly string[]) => boolean;\n\n/** Tokenise a tag expression into atoms, operators, and parentheses. */\nfunction tokenize(expr: string): string[] {\n const tokens: string[] = [];\n const re = /\\s*(\\(|\\)|@[^\\s()]+|\\band\\b|\\bor\\b|\\bnot\\b)\\s*/gy;\n let index = 0;\n while (index < expr.length) {\n re.lastIndex = index;\n const m = re.exec(expr);\n if (!m || m.index !== index) {\n throw new Error(`Invalid tag expression near: ${expr.slice(index)}`);\n }\n tokens.push(m[1]);\n index = re.lastIndex;\n }\n return tokens;\n}\n\n/**\n * Compile a tag expression into a predicate via recursive descent\n * (or → and → not → primary). Throws on malformed input so a bad `--tags` flag\n * fails loudly rather than silently matching nothing.\n */\nexport function compileTagExpression(expr: string): TagPredicate {\n const tokens = tokenize(expr);\n let pos = 0;\n\n const peek = () => tokens[pos];\n const next = () => tokens[pos++];\n\n const parseOr = (): TagPredicate => {\n let left = parseAnd();\n while (peek() === \"or\") {\n next();\n const right = parseAnd();\n const l = left;\n left = tags => l(tags) || right(tags);\n }\n return left;\n };\n\n const parseAnd = (): TagPredicate => {\n let left = parseNot();\n while (peek() === \"and\") {\n next();\n const right = parseNot();\n const l = left;\n left = tags => l(tags) && right(tags);\n }\n return left;\n };\n\n const parseNot = (): TagPredicate => {\n if (peek() === \"not\") {\n next();\n const operand = parseNot();\n return tags => !operand(tags);\n }\n return parsePrimary();\n };\n\n const parsePrimary = (): TagPredicate => {\n const token = next();\n if (token === \"(\") {\n const inner = parseOr();\n if (next() !== \")\") throw new Error(\"Unbalanced parentheses in tags\");\n return inner;\n }\n if (token === undefined || !token.startsWith(\"@\")) {\n throw new Error(`Expected a tag, got: ${token ?? \"end of input\"}`);\n }\n return tags => tags.includes(token);\n };\n\n const predicate = parseOr();\n if (pos !== tokens.length) {\n throw new Error(`Unexpected token in tag expression: ${peek()}`);\n }\n return predicate;\n}\n\nexport interface SelectOptions {\n /** Substring or `/regex/flags` match against the scenario (or outline) name. */\n name?: string;\n /** Cucumber tag expression. */\n tags?: string;\n}\n\n/** Parse a `/pattern/flags` string into a RegExp, else treat it as a substring. */\nfunction toNameMatcher(name: string): (candidate: string) => boolean {\n const delim = /^\\/(.*)\\/([a-z]*)$/.exec(name);\n if (delim) {\n const re = new RegExp(delim[1], delim[2]);\n return candidate => re.test(candidate);\n }\n return candidate => candidate.includes(name);\n}\n\n/**\n * Narrow scenarios by name and tags, then apply `@only` focus. `@skip` is *not*\n * applied here — skipped scenarios stay in the set so the runner can report them\n * as skipped rather than silently dropping them. Returns scenarios in input\n * order.\n *\n * `@only` semantics mirror the Vitest plugin: if any surviving scenario is\n * tagged `@only` (and not `@skip`), the run is focused to just those.\n */\nexport function selectScenarios(\n scenarios: ParsedScenario[],\n options: SelectOptions\n): ParsedScenario[] {\n let selected = scenarios;\n\n if (options.name) {\n const matches = toNameMatcher(options.name);\n selected = selected.filter(\n s => matches(s.name) || (s.outline ? matches(s.outline.name) : false)\n );\n }\n\n if (options.tags) {\n const predicate = compileTagExpression(options.tags);\n selected = selected.filter(s => predicate(s.tags));\n }\n\n const focused = selected.filter(\n s => s.tags.includes(\"@only\") && !s.tags.includes(\"@skip\")\n );\n return focused.length ? focused : selected;\n}\n","import { relative } from \"node:path\";\nimport { relativeFrame, relativeLocation, userFrames } from \"../sourceLocation\";\nimport { ScenarioResult, StepResult } from \"./engine\";\n\n/**\n * A reporter observes the run. `onScenarioEnd` fires as each scenario finishes\n * (completion order, non-deterministic under concurrency); `onComplete` fires\n * once with every result for the end-of-run output.\n *\n * Every reporter accepts a `verbose` flag: without it a reporter reports only\n * failures (plus a summary); with it, it reports every scenario. A reporter may\n * render the same either way — `verbose` is a request, not a requirement.\n */\nexport interface Reporter {\n onScenarioEnd?(result: ScenarioResult): void;\n onComplete(results: ScenarioResult[], durationMs: number): void;\n}\n\nexport interface ReporterOptions {\n cwd?: string;\n verbose?: boolean;\n}\n\n// --- ANSI colouring -------------------------------------------------------\nconst useColor =\n !process.env.NO_COLOR && (process.stdout.isTTY ?? false) === true;\n\nconst wrap = (open: number, close: number) => (s: string) =>\n useColor ? `\\x1b[${open}m${s}\\x1b[${close}m` : s;\n\nconst c = {\n green: wrap(32, 39),\n red: wrap(31, 39),\n yellow: wrap(33, 39),\n dim: wrap(2, 22),\n bold: wrap(1, 22),\n};\n\nconst STATUS_MARK: Record<StepResult[\"status\"], string> = {\n passed: c.green(\"✓\"),\n failed: c.red(\"✗\"),\n skipped: c.yellow(\"-\"),\n};\n\n/** ✓ / ✗ / - for a whole scenario (skipped = every step skipped). */\nfunction scenarioMark(result: ScenarioResult): string {\n if (result.status === \"failed\") return c.red(\"✗\");\n if (result.steps.every(s => s.status === \"skipped\")) return c.yellow(\"-\");\n return c.green(\"✓\");\n}\n\n/** Dot per scenario for the live heartbeat: `.` pass / `F` fail / `-` skip. */\nfunction scenarioDot(result: ScenarioResult): string {\n if (result.status === \"failed\") return c.red(\"F\");\n if (result.steps.every(s => s.status === \"skipped\")) return c.yellow(\"-\");\n return c.green(\".\");\n}\n\n/** Indent every non-empty line of a block by `pad` spaces. */\nfunction indent(text: string, pad: number): string {\n const prefix = \" \".repeat(pad);\n return text\n .split(\"\\n\")\n .map(line => (line ? prefix + line : line))\n .join(\"\\n\");\n}\n\n/**\n * The error, trimmed to user frames: `Name: message` followed by only the\n * caller's own stack frames (library/engine and `node_modules` frames removed),\n * source-mapped by Bun to the original `.ts`. Falls back to just the message\n * when nothing user-owned is left (e.g. an undefined-step error).\n */\nfunction renderError(error: Error, cwd: string): string {\n const header = `${error.name}: ${error.message}`;\n const frames = userFrames(error.stack).map(\n f => ` at ${relativeFrame(f, cwd)}`\n );\n return frames.length ? `${header}\\n${frames.join(\"\\n\")}` : header;\n}\n\n/**\n * The detail block shown beneath a failing step: the `.feature` line, the step\n * definition location, and the trimmed error — the two coordinates that make a\n * failure easy to chase (where in the feature, which step definition).\n */\nfunction failureDetail(\n stepResult: StepResult,\n result: ScenarioResult,\n cwd: string\n): string {\n const lines = [\n `feature: ${relative(cwd, result.scenario.file)}:${stepResult.step.line}`,\n ];\n if (stepResult.source) {\n lines.push(`defined: ${relativeLocation(stepResult.source, cwd)}`);\n }\n if (stepResult.error) lines.push(renderError(stepResult.error, cwd));\n return indent(c.red(lines.join(\"\\n\")), 6);\n}\n\n/**\n * One scenario as a Cucumber-style block: a marked header with its `.feature`\n * location, then each step with its mark (and, in `verbose`, the step\n * definition location as a trailing comment). A failing step is followed by its\n * failure detail; a scenario-level error (a failing hook, no step to blame) is\n * appended at the end.\n */\nfunction renderScenario(\n result: ScenarioResult,\n cwd: string,\n opts: { stepSource: boolean }\n): string {\n const label = result.scenario.outline\n ? `${result.scenario.outline.name} › ${result.scenario.name}`\n : result.scenario.name;\n const lines: string[] = [`${scenarioMark(result)} ${c.bold(label)}`];\n\n const loc = result.scenario.line\n ? `${relative(cwd, result.scenario.file)}:${result.scenario.line}`\n : relative(cwd, result.scenario.file);\n lines.push(indent(c.dim(loc), 4));\n lines.push(\"\");\n\n for (const step of result.scenario.steps) {\n const sr = result.steps.find(s => s.step === step);\n const status = sr?.status ?? \"skipped\";\n const comment =\n opts.stepSource && sr?.source\n ? c.dim(` # ${relativeLocation(sr.source, cwd)}`)\n : \"\";\n lines.push(\n ` ${STATUS_MARK[status]} ${c.dim(step.effectiveKeyword)} ${step.text}${comment}`\n );\n if (sr?.status === \"failed\") lines.push(failureDetail(sr, result, cwd));\n }\n\n // A hook failure has no failing step; surface its error at scenario level.\n const stepFailed = result.steps.some(s => s.status === \"failed\");\n if (result.error && !stepFailed) {\n lines.push(indent(c.red(renderError(result.error, cwd)), 4));\n }\n\n return lines.join(\"\\n\");\n}\n\n// --- Summary --------------------------------------------------------------\ninterface Totals {\n scenarios: { passed: number; failed: number; skipped: number };\n steps: { passed: number; failed: number; skipped: number };\n}\n\nfunction tally(results: ScenarioResult[]): Totals {\n const totals: Totals = {\n scenarios: { passed: 0, failed: 0, skipped: 0 },\n steps: { passed: 0, failed: 0, skipped: 0 },\n };\n for (const result of results) {\n if (result.status === \"failed\") totals.scenarios.failed++;\n else if (result.steps.every(s => s.status === \"skipped\"))\n totals.scenarios.skipped++;\n else totals.scenarios.passed++;\n for (const step of result.steps) totals.steps[step.status]++;\n }\n return totals;\n}\n\nfunction renderSummary(results: ScenarioResult[], durationMs: number): string {\n const t = tally(results);\n const scenarioTotal =\n t.scenarios.passed + t.scenarios.failed + t.scenarios.skipped;\n const stepTotal = t.steps.passed + t.steps.failed + t.steps.skipped;\n\n const part = (n: number, label: string, color: (s: string) => string) =>\n n > 0 ? color(`${n} ${label}`) : null;\n\n const line = (\n total: number,\n noun: string,\n counts: { passed: number; failed: number; skipped: number }\n ) => {\n const parts = [\n part(counts.passed, \"passed\", c.green),\n part(counts.failed, \"failed\", c.red),\n part(counts.skipped, \"skipped\", c.yellow),\n ].filter((x): x is string => x !== null);\n return `${total} ${noun}${total === 1 ? \"\" : \"s\"} (${parts.join(\", \")})`;\n };\n\n return [\n line(scenarioTotal, \"scenario\", t.scenarios),\n line(stepTotal, \"step\", t.steps),\n c.dim(`${(durationMs / 1000).toFixed(2)}s`),\n ].join(\"\\n\");\n}\n\nfunction write(text: string): void {\n process.stdout.write(text);\n}\n\n/** Feature-grouped tree of every scenario (used by `--verbose`). */\nfunction renderFullTree(results: ScenarioResult[], cwd: string): string {\n const groups = new Map<string, ScenarioResult[]>();\n for (const r of results) {\n const bucket = groups.get(r.scenario.file) ?? [];\n bucket.push(r);\n groups.set(r.scenario.file, bucket);\n }\n const out: string[] = [];\n for (const [file, group] of groups) {\n out.push(c.bold(`Feature: ${relative(cwd, file)}`), \"\");\n for (const r of group)\n out.push(indent(renderScenario(r, cwd, { stepSource: true }), 2), \"\");\n }\n return out.join(\"\\n\");\n}\n\n/** The failing scenarios only (used by the default, non-verbose output). */\nfunction renderFailures(results: ScenarioResult[], cwd: string): string {\n const failed = results.filter(r => r.status === \"failed\");\n if (!failed.length) return \"\";\n return (\n failed\n .map(r => renderScenario(r, cwd, { stepSource: false }))\n .join(\"\\n\\n\") + \"\\n\\n\"\n );\n}\n\n/**\n * Default reporter. Non-verbose: a dot per scenario as a heartbeat, then the\n * failing scenarios in Cucumber-style detail, then the summary. Verbose: the\n * full feature → scenario → step tree instead of just failures.\n */\nexport function prettyReporter(opts: ReporterOptions = {}): Reporter {\n const cwd = opts.cwd ?? process.cwd();\n const verbose = opts.verbose ?? false;\n return {\n onScenarioEnd(result) {\n if (!verbose) write(scenarioDot(result));\n },\n onComplete(results, durationMs) {\n const body = verbose\n ? `\\n${renderFullTree(results, cwd)}\\n`\n : `\\n\\n${renderFailures(results, cwd)}`;\n write(`${body}${renderSummary(results, durationMs)}\\n`);\n },\n };\n}\n\n/**\n * Compact reporter: a dot per scenario, then failures in detail and the\n * summary. Always minimal — `verbose` is accepted for interface parity but does\n * not expand the output (use `pretty --verbose` for the full tree).\n */\nexport function progressReporter(opts: ReporterOptions = {}): Reporter {\n const cwd = opts.cwd ?? process.cwd();\n return {\n onScenarioEnd(result) {\n write(scenarioDot(result));\n },\n onComplete(results, durationMs) {\n write(\n `\\n\\n${renderFailures(results, cwd)}${renderSummary(results, durationMs)}\\n`\n );\n },\n };\n}\n\nexport function makeReporter(\n name: \"pretty\" | \"progress\",\n opts: ReporterOptions = {}\n): Reporter {\n return name === \"progress\" ? progressReporter(opts) : prettyReporter(opts);\n}\n","/* eslint-disable @typescript-eslint/no-explicit-any */\nimport * as path from \"node:path\";\nimport { pathToFileURL } from \"node:url\";\nimport { ParsedScenario } from \"../analyzer/types\";\nimport { parseFeatureFiles } from \"../analyzer/gherkinParser\";\nimport { globFiles } from \"../globFiles\";\nimport { BasicWorld, MergeableWorld } from \"../world\";\nimport {\n compileRegistry,\n CompiledStep,\n runScenario,\n ScenarioResult,\n} from \"./engine\";\nimport { globalHookRegistry, runHooks, runHooksParallel } from \"./hooks\";\nimport { globalRegistry } from \"./registry\";\nimport { selectScenarios } from \"./filter\";\nimport { makeReporter, Reporter } from \"./reporters\";\nimport { ResolvedConfig } from \"./config\";\n\ntype WorldFactory = () => MergeableWorld<any, any, any>;\n\n/**\n * Import every step-definition module so its `.step(...)` calls self-register\n * into the shared `globalRegistry`. Imported by file URL so Bun transpiles the\n * TypeScript natively.\n */\nasync function importSteps(files: string[]): Promise<void> {\n for (const file of files) {\n await import(pathToFileURL(file).href);\n }\n}\n\n/** Load the configured world factory, or default to a fresh `BasicWorld`. */\nasync function loadWorldFactory(\n world: string | undefined,\n cwd: string\n): Promise<WorldFactory> {\n if (!world) return () => new BasicWorld();\n const resolved = path.resolve(cwd, world);\n const mod = await import(pathToFileURL(resolved).href);\n const factory = mod.default ?? mod;\n if (typeof factory !== \"function\") {\n throw new Error(\n `World module ${world} must default-export a factory function () => world`\n );\n }\n return factory as WorldFactory;\n}\n\n/** A scenario carrying `@skip` never runs: report it with all steps skipped. */\nfunction skippedResult(scenario: ParsedScenario): ScenarioResult {\n return {\n scenario,\n status: \"passed\",\n steps: scenario.steps.map(step => ({ step, status: \"skipped\" as const })),\n };\n}\n\n/**\n * Run `items` through `worker`, at most `limit` in flight. Results come back in\n * input order even though completion order is not deterministic. A minimal\n * dependency-free pool — the whole point of the single-process model.\n */\nasync function runPool<T, R>(\n items: T[],\n limit: number,\n worker: (item: T, index: number) => Promise<R>\n): Promise<R[]> {\n const results = new Array<R>(items.length);\n let cursor = 0;\n const runNext = async (): Promise<void> => {\n while (cursor < items.length) {\n const index = cursor++;\n results[index] = await worker(items[index], index);\n }\n };\n const workers = Array.from(\n { length: Math.min(Math.max(1, limit), items.length) },\n runNext\n );\n await Promise.all(workers);\n return results;\n}\n\nexport interface RunResult {\n results: ScenarioResult[];\n passed: boolean;\n durationMs: number;\n}\n\n/**\n * Execute a whole run end to end: discover and import steps, load the world,\n * parse and select scenarios, then run them concurrently against a\n * compiled-once step table with global/feature hooks around the batch. Never\n * throws for test failures — inspect `RunResult.passed`.\n */\nexport async function run(\n config: ResolvedConfig,\n reporter: Reporter = makeReporter(config.reporter, {\n cwd: config.cwd,\n verbose: config.verbose,\n })\n): Promise<RunResult> {\n const start =\n typeof performance !== \"undefined\" ? performance.now() : Date.now();\n\n const [featureFiles, stepFiles] = await Promise.all([\n globFiles(config.features, config.cwd),\n globFiles(config.steps, config.cwd),\n ]);\n\n await importSteps(stepFiles);\n const makeWorld = await loadWorldFactory(config.world, config.cwd);\n\n const compiled: CompiledStep[] = compileRegistry(globalRegistry);\n const allScenarios = parseFeatureFiles(featureFiles);\n const selected = selectScenarios(allScenarios, {\n name: config.name,\n tags: config.tags,\n });\n\n // Global `beforeAll` hooks run once, in parallel, before any scenario starts;\n // feature before/after bracket the whole batch inside them. (Hooks aren't\n // file-scoped in the registry, so per-file bracketing would be meaningless\n // under concurrent execution.)\n await runHooksParallel(\"global\", \"before\", globalHookRegistry);\n await runHooks(\"feature\", \"before\", globalHookRegistry);\n\n const results = await runPool(\n selected,\n config.concurrency,\n async scenario => {\n const result = scenario.tags.includes(\"@skip\")\n ? skippedResult(scenario)\n : await runScenario(scenario, compiled, makeWorld, globalHookRegistry);\n reporter.onScenarioEnd?.(result);\n return result;\n }\n );\n\n await runHooks(\"feature\", \"after\", globalHookRegistry);\n // Global `afterAll` hooks run once, in parallel, after every scenario is done.\n await runHooksParallel(\"global\", \"after\", globalHookRegistry);\n\n const durationMs =\n (typeof performance !== \"undefined\" ? performance.now() : Date.now()) -\n start;\n reporter.onComplete(results, durationMs);\n\n return {\n results,\n passed: results.every(r => r.status !== \"failed\"),\n durationMs,\n };\n}\n","#!/usr/bin/env bun\nimport { parseArgs } from \"node:util\";\nimport * as path from \"node:path\";\nimport { loadConfigFile, resolveConfig, RunnerOptions } from \"./config\";\nimport { run } from \"./runner\";\n\nconst HELP = `step-forge — native TypeScript runner for Gherkin step definitions\n\nUsage:\n step-forge [options] [feature globs...]\n\nOptions:\n -t, --tags <expr> Tag expression, e.g. \"@smoke and not @wip\"\n -n, --name <pattern> Only scenarios whose name matches (substring or /regex/)\n -s, --steps <glob> Step-definition module glob (repeatable)\n -w, --world <module> World factory module (default export () => world)\n -c, --concurrency <n> Max scenarios in flight (default: 1, i.e. serial)\n -r, --reporter <name> \"pretty\" (default) or \"progress\"\n -v, --verbose Report every scenario, not just failures\n --config <path> Config file directory (default: cwd)\n -h, --help Show this help\n\nPositional arguments are feature globs and override the configured features.\n`;\n\n/** Parse argv into config overrides. Positional args become feature globs. */\nfunction parseCli(argv: string[]): { cwd: string; overrides: RunnerOptions } {\n const { values, positionals } = parseArgs({\n args: argv,\n allowPositionals: true,\n options: {\n tags: { type: \"string\", short: \"t\" },\n name: { type: \"string\", short: \"n\" },\n steps: { type: \"string\", short: \"s\", multiple: true },\n world: { type: \"string\", short: \"w\" },\n concurrency: { type: \"string\", short: \"c\" },\n reporter: { type: \"string\", short: \"r\" },\n verbose: { type: \"boolean\", short: \"v\" },\n config: { type: \"string\" },\n help: { type: \"boolean\", short: \"h\" },\n },\n });\n\n if (values.help) {\n process.stdout.write(HELP);\n process.exit(0);\n }\n\n const overrides: RunnerOptions = {};\n if (positionals.length) overrides.features = positionals;\n if (values.tags) overrides.tags = values.tags;\n if (values.name) overrides.name = values.name;\n if (values.steps) overrides.steps = values.steps;\n if (values.world) overrides.world = values.world;\n if (values.verbose) overrides.verbose = true;\n if (values.reporter) {\n if (values.reporter !== \"pretty\" && values.reporter !== \"progress\") {\n throw new Error(`Unknown reporter: ${values.reporter}`);\n }\n overrides.reporter = values.reporter;\n }\n if (values.concurrency !== undefined) {\n const n = Number(values.concurrency);\n if (!Number.isInteger(n) || n < 1) {\n throw new Error(`--concurrency must be a positive integer`);\n }\n overrides.concurrency = n;\n }\n\n const cwd = values.config\n ? path.resolve(process.cwd(), values.config)\n : process.cwd();\n return { cwd, overrides };\n}\n\nasync function main(): Promise<void> {\n const { cwd, overrides } = parseCli(process.argv.slice(2));\n const fileConfig = await loadConfigFile(cwd);\n const config = resolveConfig(cwd, fileConfig, overrides);\n const { passed } = await run(config);\n process.exitCode = passed ? 0 : 1;\n}\n\nmain().catch(err => {\n process.stderr.write(`${err instanceof Error ? err.stack : String(err)}\\n`);\n process.exitCode = 1;\n});\n"],"mappings":"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"}
|
|
@@ -71,6 +71,8 @@ interface StepDefinitionMeta {
|
|
|
71
71
|
interface ParsedScenario {
|
|
72
72
|
name: string;
|
|
73
73
|
file: string;
|
|
74
|
+
/** 1-based line of the `Scenario:`/`Scenario Outline:` keyword in the file. */
|
|
75
|
+
line?: number;
|
|
74
76
|
steps: ParsedStep[];
|
|
75
77
|
/**
|
|
76
78
|
* Gherkin tags in effect for this scenario, each including the leading `@`
|
|
@@ -164,26 +166,73 @@ declare class HookRegistry {
|
|
|
164
166
|
}
|
|
165
167
|
declare const globalHookRegistry: HookRegistry;
|
|
166
168
|
/**
|
|
167
|
-
* Run every registered
|
|
168
|
-
*
|
|
169
|
+
* Run every registered hook of a scope+timing **in registration order** (after
|
|
170
|
+
* hooks reversed by {@link HookRegistry.for}, so teardown unwinds setup). Used
|
|
171
|
+
* for feature hooks, where ordering matters. Throws if a hook throws, so the
|
|
169
172
|
* runner reports it against the enclosing boundary.
|
|
170
173
|
*/
|
|
171
174
|
declare function runHooks(scope: "feature" | "global", timing: HookTiming, registry?: HookRegistry): Promise<void>;
|
|
172
175
|
/**
|
|
173
|
-
* Run
|
|
174
|
-
*
|
|
175
|
-
*
|
|
176
|
-
*
|
|
176
|
+
* Run every registered hook of a scope+timing **concurrently**, resolving once
|
|
177
|
+
* all of them settle. This is how global `beforeAll`/`afterAll` run: independent
|
|
178
|
+
* setup/teardown steps fire in parallel with no ordering between them. A hook
|
|
179
|
+
* with a sequential requirement should sequence that work inside a single hook.
|
|
177
180
|
*
|
|
178
|
-
*
|
|
179
|
-
*
|
|
180
|
-
*
|
|
181
|
-
*
|
|
182
|
-
* in each. Size global setup to be worker-safe (e.g. a server per worker).
|
|
183
|
-
* - Teardown is best-effort: after-hooks start on the worker's `beforeExit` and
|
|
184
|
-
* are not awaited by the runner, so keep them fast/synchronous.
|
|
181
|
+
* The runner calls this exactly once for `beforeAll` (before any scenario
|
|
182
|
+
* starts) and once for `afterAll` (after every scenario is done), so global
|
|
183
|
+
* setup/teardown brackets the whole run deterministically. Rejects if any hook
|
|
184
|
+
* rejects (via `Promise.all`), surfacing the first failure to the caller.
|
|
185
185
|
*/
|
|
186
|
-
declare function
|
|
186
|
+
declare function runHooksParallel(scope: "feature" | "global", timing: HookTiming, registry?: HookRegistry): Promise<void>;
|
|
187
187
|
//#endregion
|
|
188
|
-
|
|
189
|
-
|
|
188
|
+
//#region src/runtime/config.d.ts
|
|
189
|
+
/**
|
|
190
|
+
* User-facing runner configuration. The same shape is accepted from a
|
|
191
|
+
* `step-forge.config.ts` file (default export) and from CLI flags, with flags
|
|
192
|
+
* taking precedence. Everything is optional; {@link resolveConfig} fills in
|
|
193
|
+
* defaults.
|
|
194
|
+
*/
|
|
195
|
+
interface RunnerOptions {
|
|
196
|
+
/** Feature-file glob(s), relative to `cwd`. Defaults to every `.feature` file. */
|
|
197
|
+
features?: string | string[];
|
|
198
|
+
/** Step-module glob(s), relative to `cwd`. Defaults to every `.steps.ts` file. */
|
|
199
|
+
steps?: string | string[];
|
|
200
|
+
/**
|
|
201
|
+
* Module that default-exports a world factory `() => world`, relative to
|
|
202
|
+
* `cwd`. When omitted each scenario gets a fresh `BasicWorld`.
|
|
203
|
+
*/
|
|
204
|
+
world?: string;
|
|
205
|
+
/**
|
|
206
|
+
* Max scenarios in flight at once. Defaults to `1` (serial), matching
|
|
207
|
+
* Cucumber's default execution model — scenarios often share module-level
|
|
208
|
+
* state via hooks, which only holds under serial execution. Raise it to opt
|
|
209
|
+
* into parallelism for isolated, I/O-bound suites.
|
|
210
|
+
*/
|
|
211
|
+
concurrency?: number;
|
|
212
|
+
/** Reporter name. Default `pretty`. */
|
|
213
|
+
reporter?: "pretty" | "progress";
|
|
214
|
+
/**
|
|
215
|
+
* Verbose output: report every scenario (pass, fail, skip) instead of only
|
|
216
|
+
* failures. Passed to whichever reporter is active. Default `false`.
|
|
217
|
+
*/
|
|
218
|
+
verbose?: boolean;
|
|
219
|
+
/** Only run scenarios whose name matches this (string → substring/regex). */
|
|
220
|
+
name?: string;
|
|
221
|
+
/** Cucumber tag expression, e.g. `@smoke and not @wip`. */
|
|
222
|
+
tags?: string;
|
|
223
|
+
}
|
|
224
|
+
/** Fully-resolved config: no optionals, globs kept as arrays, paths absolute. */
|
|
225
|
+
interface ResolvedConfig {
|
|
226
|
+
cwd: string;
|
|
227
|
+
features: string[];
|
|
228
|
+
steps: string[];
|
|
229
|
+
world?: string;
|
|
230
|
+
concurrency: number;
|
|
231
|
+
reporter: "pretty" | "progress";
|
|
232
|
+
verbose: boolean;
|
|
233
|
+
name?: string;
|
|
234
|
+
tags?: string;
|
|
235
|
+
}
|
|
236
|
+
//#endregion
|
|
237
|
+
export { booleanParser as C, stringParser as E, Parser as S, numberParser as T, ParsedScenario as _, HookTiming as a, BasicWorld as b, ScenarioHookFn as c, runHooks as d, runHooksParallel as f, MatchedStep as g, Diagnostic as h, HookScope as i, ScenarioInfo as l, AnalyzerConfig as m, RunnerOptions as n, PlainHookFn as o, AnalysisRule as p, HookRegistry as r, RegisteredHook as s, ResolvedConfig as t, globalHookRegistry as u, ParsedStep as v, intParser as w, MergeableWorld as x, StepDefinitionMeta as y };
|
|
238
|
+
//# sourceMappingURL=config-C7PCYgYy.d.cts.map
|