@step-forge/step-forge 0.0.20 → 0.0.21

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/cli.js ADDED
@@ -0,0 +1,471 @@
1
+ #!/usr/bin/env bun
2
+ import { i as runHooks, n as ensureGlobalHooks, o as globalRegistry, r as globalHookRegistry } from "./hooks-CywugMQQ.js";
3
+ import { i as BasicWorld, n as parseFeatureFiles, r as globFiles } from "./gherkinParser-CKARHgt4.js";
4
+ import { i as runScenario, r as compileRegistry } from "./engine-DPRxs6eC.js";
5
+ import { access } from "node:fs/promises";
6
+ import * as path from "node:path";
7
+ import { relative } from "node:path";
8
+ import { parseArgs } from "node:util";
9
+ import { pathToFileURL } from "node:url";
10
+ //#region src/runtime/config.ts
11
+ const DEFAULT_FEATURES = "**/*.feature";
12
+ const DEFAULT_STEPS = "**/*.steps.ts";
13
+ const CONFIG_BASENAMES = [
14
+ "step-forge.config.ts",
15
+ "step-forge.config.mts",
16
+ "step-forge.config.js",
17
+ "step-forge.config.mjs"
18
+ ];
19
+ function toArray(value) {
20
+ if (value === void 0) return [];
21
+ return Array.isArray(value) ? value : [value];
22
+ }
23
+ async function exists(p) {
24
+ try {
25
+ await access(p);
26
+ return true;
27
+ } catch {
28
+ return false;
29
+ }
30
+ }
31
+ /**
32
+ * Locate and import a `step-forge.config.*` file from `cwd`, returning its
33
+ * default export (or `{}` if none is present). Imported by absolute file URL so
34
+ * Bun transpiles the TypeScript config natively.
35
+ */
36
+ async function loadConfigFile(cwd) {
37
+ for (const basename of CONFIG_BASENAMES) {
38
+ const candidate = path.join(cwd, basename);
39
+ if (!await exists(candidate)) continue;
40
+ const mod = await import(pathToFileURL(candidate).href);
41
+ return mod.default ?? mod;
42
+ }
43
+ return {};
44
+ }
45
+ /**
46
+ * Merge file config with CLI overrides and apply defaults. CLI overrides win
47
+ * field-by-field; array globs are normalised to arrays and default when neither
48
+ * source provides them.
49
+ */
50
+ function resolveConfig(cwd, file, cli) {
51
+ const pick = (key) => cli[key] ?? file[key];
52
+ const features = toArray(pick("features"));
53
+ const steps = toArray(pick("steps"));
54
+ return {
55
+ cwd,
56
+ features: features.length ? features : [DEFAULT_FEATURES],
57
+ steps: steps.length ? steps : [DEFAULT_STEPS],
58
+ world: pick("world"),
59
+ concurrency: pick("concurrency") ?? 1,
60
+ reporter: pick("reporter") ?? "pretty",
61
+ name: pick("name"),
62
+ tags: pick("tags")
63
+ };
64
+ }
65
+ //#endregion
66
+ //#region src/runtime/filter.ts
67
+ /** Tokenise a tag expression into atoms, operators, and parentheses. */
68
+ function tokenize(expr) {
69
+ const tokens = [];
70
+ const re = /\s*(\(|\)|@[^\s()]+|\band\b|\bor\b|\bnot\b)\s*/gy;
71
+ let index = 0;
72
+ while (index < expr.length) {
73
+ re.lastIndex = index;
74
+ const m = re.exec(expr);
75
+ if (!m || m.index !== index) throw new Error(`Invalid tag expression near: ${expr.slice(index)}`);
76
+ tokens.push(m[1]);
77
+ index = re.lastIndex;
78
+ }
79
+ return tokens;
80
+ }
81
+ /**
82
+ * Compile a tag expression into a predicate via recursive descent
83
+ * (or → and → not → primary). Throws on malformed input so a bad `--tags` flag
84
+ * fails loudly rather than silently matching nothing.
85
+ */
86
+ function compileTagExpression(expr) {
87
+ const tokens = tokenize(expr);
88
+ let pos = 0;
89
+ const peek = () => tokens[pos];
90
+ const next = () => tokens[pos++];
91
+ const parseOr = () => {
92
+ let left = parseAnd();
93
+ while (peek() === "or") {
94
+ next();
95
+ const right = parseAnd();
96
+ const l = left;
97
+ left = (tags) => l(tags) || right(tags);
98
+ }
99
+ return left;
100
+ };
101
+ const parseAnd = () => {
102
+ let left = parseNot();
103
+ while (peek() === "and") {
104
+ next();
105
+ const right = parseNot();
106
+ const l = left;
107
+ left = (tags) => l(tags) && right(tags);
108
+ }
109
+ return left;
110
+ };
111
+ const parseNot = () => {
112
+ if (peek() === "not") {
113
+ next();
114
+ const operand = parseNot();
115
+ return (tags) => !operand(tags);
116
+ }
117
+ return parsePrimary();
118
+ };
119
+ const parsePrimary = () => {
120
+ const token = next();
121
+ if (token === "(") {
122
+ const inner = parseOr();
123
+ if (next() !== ")") throw new Error("Unbalanced parentheses in tags");
124
+ return inner;
125
+ }
126
+ if (token === void 0 || !token.startsWith("@")) throw new Error(`Expected a tag, got: ${token ?? "end of input"}`);
127
+ return (tags) => tags.includes(token);
128
+ };
129
+ const predicate = parseOr();
130
+ if (pos !== tokens.length) throw new Error(`Unexpected token in tag expression: ${peek()}`);
131
+ return predicate;
132
+ }
133
+ /** Parse a `/pattern/flags` string into a RegExp, else treat it as a substring. */
134
+ function toNameMatcher(name) {
135
+ const delim = /^\/(.*)\/([a-z]*)$/.exec(name);
136
+ if (delim) {
137
+ const re = new RegExp(delim[1], delim[2]);
138
+ return (candidate) => re.test(candidate);
139
+ }
140
+ return (candidate) => candidate.includes(name);
141
+ }
142
+ /**
143
+ * Narrow scenarios by name and tags, then apply `@only` focus. `@skip` is *not*
144
+ * applied here — skipped scenarios stay in the set so the runner can report them
145
+ * as skipped rather than silently dropping them. Returns scenarios in input
146
+ * order.
147
+ *
148
+ * `@only` semantics mirror the Vitest plugin: if any surviving scenario is
149
+ * tagged `@only` (and not `@skip`), the run is focused to just those.
150
+ */
151
+ function selectScenarios(scenarios, options) {
152
+ let selected = scenarios;
153
+ if (options.name) {
154
+ const matches = toNameMatcher(options.name);
155
+ selected = selected.filter((s) => matches(s.name) || (s.outline ? matches(s.outline.name) : false));
156
+ }
157
+ if (options.tags) {
158
+ const predicate = compileTagExpression(options.tags);
159
+ selected = selected.filter((s) => predicate(s.tags));
160
+ }
161
+ const focused = selected.filter((s) => s.tags.includes("@only") && !s.tags.includes("@skip"));
162
+ return focused.length ? focused : selected;
163
+ }
164
+ //#endregion
165
+ //#region src/runtime/reporters.ts
166
+ const useColor = !process.env.NO_COLOR && (process.stdout.isTTY ?? false) === true;
167
+ const wrap = (open, close) => (s) => useColor ? `\x1b[${open}m${s}\x1b[${close}m` : s;
168
+ const c = {
169
+ green: wrap(32, 39),
170
+ red: wrap(31, 39),
171
+ yellow: wrap(33, 39),
172
+ cyan: wrap(36, 39),
173
+ dim: wrap(2, 22),
174
+ bold: wrap(1, 22)
175
+ };
176
+ const STATUS_MARK = {
177
+ passed: c.green("✓"),
178
+ failed: c.red("✗"),
179
+ skipped: c.yellow("-")
180
+ };
181
+ /** Indent every line of a block by `pad` spaces. */
182
+ function indent(text, pad) {
183
+ const prefix = " ".repeat(pad);
184
+ return text.split("\n").map((line) => line ? prefix + line : line).join("\n");
185
+ }
186
+ /** Render one scenario as a Cucumber-style tree of steps. */
187
+ function renderScenario(result) {
188
+ const lines = [];
189
+ const label = result.scenario.outline ? `${result.scenario.outline.name} › ${result.scenario.name}` : result.scenario.name;
190
+ lines.push(` Scenario: ${label}`);
191
+ for (const step of result.scenario.steps) {
192
+ const status = result.steps.find((s) => s.step === step)?.status ?? "skipped";
193
+ lines.push(` ${STATUS_MARK[status]} ${c.dim(step.effectiveKeyword)} ${step.text}`);
194
+ }
195
+ if (result.error) {
196
+ const stack = result.error.stack ?? String(result.error);
197
+ lines.push(indent(c.red(stack), 6));
198
+ }
199
+ return lines.join("\n");
200
+ }
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
+ function tally(results) {
213
+ const totals = {
214
+ scenarios: {
215
+ passed: 0,
216
+ failed: 0,
217
+ skipped: 0
218
+ },
219
+ steps: {
220
+ passed: 0,
221
+ failed: 0,
222
+ skipped: 0
223
+ }
224
+ };
225
+ for (const result of results) {
226
+ if (result.status === "failed") totals.scenarios.failed++;
227
+ else if (result.steps.every((s) => s.status === "skipped")) totals.scenarios.skipped++;
228
+ else totals.scenarios.passed++;
229
+ for (const step of result.steps) totals.steps[step.status]++;
230
+ }
231
+ return totals;
232
+ }
233
+ /** The shared end-of-run summary block: counts + duration. */
234
+ function renderSummary(results, durationMs) {
235
+ const t = tally(results);
236
+ const scenarioTotal = t.scenarios.passed + t.scenarios.failed + t.scenarios.skipped;
237
+ const stepTotal = t.steps.passed + t.steps.failed + t.steps.skipped;
238
+ const part = (n, label, color) => n > 0 ? color(`${n} ${label}`) : null;
239
+ const scenarioParts = [
240
+ part(t.scenarios.passed, "passed", c.green),
241
+ part(t.scenarios.failed, "failed", c.red),
242
+ part(t.scenarios.skipped, "skipped", c.yellow)
243
+ ].filter((x) => x !== null);
244
+ const stepParts = [
245
+ part(t.steps.passed, "passed", c.green),
246
+ part(t.steps.failed, "failed", c.red),
247
+ part(t.steps.skipped, "skipped", c.yellow)
248
+ ].filter((x) => x !== null);
249
+ const seconds = (durationMs / 1e3).toFixed(2);
250
+ return [
251
+ `${scenarioTotal} scenario${scenarioTotal === 1 ? "" : "s"} (${scenarioParts.join(", ")})`,
252
+ `${stepTotal} step${stepTotal === 1 ? "" : "s"} (${stepParts.join(", ")})`,
253
+ c.dim(`${seconds}s`)
254
+ ].join("\n");
255
+ }
256
+ /**
257
+ * Default reporter: prints the full feature → scenario → step tree grouped by
258
+ * file, then the summary. Buffers to `onComplete` so concurrent scenarios don't
259
+ * interleave mid-tree.
260
+ */
261
+ function prettyReporter(cwd = process.cwd()) {
262
+ return { onComplete(results, durationMs) {
263
+ const out = [];
264
+ for (const [file, group] of groupByFeature(results)) {
265
+ const featureName = group[0].scenario.file;
266
+ out.push(c.bold(`Feature: ${relative(cwd, featureName || file)}`));
267
+ for (const result of group) out.push(renderScenario(result));
268
+ out.push("");
269
+ }
270
+ out.push(renderSummary(results, durationMs));
271
+ process.stdout.write(out.join("\n") + "\n");
272
+ } };
273
+ }
274
+ /**
275
+ * Compact reporter: one character per scenario as it finishes (`.`/`F`/`-`),
276
+ * then failures in detail and the summary. Best for large suites.
277
+ */
278
+ function progressReporter(cwd = process.cwd()) {
279
+ return {
280
+ onScenarioEnd(result) {
281
+ const mark = result.status === "failed" ? c.red("F") : result.steps.every((s) => s.status === "skipped") ? c.yellow("-") : c.green(".");
282
+ process.stdout.write(mark);
283
+ },
284
+ onComplete(results, durationMs) {
285
+ const failures = results.filter((r) => r.status === "failed");
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");
293
+ }
294
+ };
295
+ }
296
+ function makeReporter(name, cwd) {
297
+ return name === "progress" ? progressReporter(cwd) : prettyReporter(cwd);
298
+ }
299
+ //#endregion
300
+ //#region src/runtime/runner.ts
301
+ /**
302
+ * Import every step-definition module so its `.step(...)` calls self-register
303
+ * into the shared `globalRegistry`. Imported by file URL so Bun transpiles the
304
+ * TypeScript natively.
305
+ */
306
+ async function importSteps(files) {
307
+ for (const file of files) await import(pathToFileURL(file).href);
308
+ }
309
+ /** Load the configured world factory, or default to a fresh `BasicWorld`. */
310
+ async function loadWorldFactory(world, cwd) {
311
+ if (!world) return () => new BasicWorld();
312
+ const mod = await import(pathToFileURL(path.resolve(cwd, world)).href);
313
+ const factory = mod.default ?? mod;
314
+ if (typeof factory !== "function") throw new Error(`World module ${world} must default-export a factory function () => world`);
315
+ return factory;
316
+ }
317
+ /** A scenario carrying `@skip` never runs: report it with all steps skipped. */
318
+ function skippedResult(scenario) {
319
+ return {
320
+ scenario,
321
+ status: "passed",
322
+ steps: scenario.steps.map((step) => ({
323
+ step,
324
+ status: "skipped"
325
+ }))
326
+ };
327
+ }
328
+ /**
329
+ * Run `items` through `worker`, at most `limit` in flight. Results come back in
330
+ * input order even though completion order is not deterministic. A minimal
331
+ * dependency-free pool — the whole point of the single-process model.
332
+ */
333
+ async function runPool(items, limit, worker) {
334
+ const results = new Array(items.length);
335
+ let cursor = 0;
336
+ const runNext = async () => {
337
+ while (cursor < items.length) {
338
+ const index = cursor++;
339
+ results[index] = await worker(items[index], index);
340
+ }
341
+ };
342
+ const workers = Array.from({ length: Math.min(Math.max(1, limit), items.length) }, runNext);
343
+ await Promise.all(workers);
344
+ return results;
345
+ }
346
+ /**
347
+ * Execute a whole run end to end: discover and import steps, load the world,
348
+ * parse and select scenarios, then run them concurrently against a
349
+ * compiled-once step table with global/feature hooks around the batch. Never
350
+ * throws for test failures — inspect `RunResult.passed`.
351
+ */
352
+ async function run(config, reporter = makeReporter(config.reporter, config.cwd)) {
353
+ const start = typeof performance !== "undefined" ? performance.now() : Date.now();
354
+ const [featureFiles, stepFiles] = await Promise.all([globFiles(config.features, config.cwd), globFiles(config.steps, config.cwd)]);
355
+ await importSteps(stepFiles);
356
+ const makeWorld = await loadWorldFactory(config.world, config.cwd);
357
+ const compiled = compileRegistry(globalRegistry);
358
+ const selected = selectScenarios(parseFeatureFiles(featureFiles), {
359
+ name: config.name,
360
+ tags: config.tags
361
+ });
362
+ await ensureGlobalHooks(globalHookRegistry);
363
+ await runHooks("feature", "before", globalHookRegistry);
364
+ const results = await runPool(selected, config.concurrency, async (scenario) => {
365
+ const result = scenario.tags.includes("@skip") ? skippedResult(scenario) : await runScenario(scenario, compiled, makeWorld, globalHookRegistry);
366
+ reporter.onScenarioEnd?.(result);
367
+ return result;
368
+ });
369
+ await runHooks("feature", "after", globalHookRegistry);
370
+ const durationMs = (typeof performance !== "undefined" ? performance.now() : Date.now()) - start;
371
+ reporter.onComplete(results, durationMs);
372
+ return {
373
+ results,
374
+ passed: results.every((r) => r.status !== "failed"),
375
+ durationMs
376
+ };
377
+ }
378
+ //#endregion
379
+ //#region src/runtime/cli.ts
380
+ const HELP = `step-forge — native TypeScript runner for Gherkin step definitions
381
+
382
+ Usage:
383
+ step-forge [options] [feature globs...]
384
+
385
+ Options:
386
+ -t, --tags <expr> Tag expression, e.g. "@smoke and not @wip"
387
+ -n, --name <pattern> Only scenarios whose name matches (substring or /regex/)
388
+ -s, --steps <glob> Step-definition module glob (repeatable)
389
+ -w, --world <module> World factory module (default export () => world)
390
+ -c, --concurrency <n> Max scenarios in flight (default: 1, i.e. serial)
391
+ -r, --reporter <name> "pretty" (default) or "progress"
392
+ --config <path> Config file directory (default: cwd)
393
+ -h, --help Show this help
394
+
395
+ Positional arguments are feature globs and override the configured features.
396
+ `;
397
+ /** Parse argv into config overrides. Positional args become feature globs. */
398
+ function parseCli(argv) {
399
+ const { values, positionals } = parseArgs({
400
+ args: argv,
401
+ allowPositionals: true,
402
+ options: {
403
+ tags: {
404
+ type: "string",
405
+ short: "t"
406
+ },
407
+ name: {
408
+ type: "string",
409
+ short: "n"
410
+ },
411
+ steps: {
412
+ type: "string",
413
+ short: "s",
414
+ multiple: true
415
+ },
416
+ world: {
417
+ type: "string",
418
+ short: "w"
419
+ },
420
+ concurrency: {
421
+ type: "string",
422
+ short: "c"
423
+ },
424
+ reporter: {
425
+ type: "string",
426
+ short: "r"
427
+ },
428
+ config: { type: "string" },
429
+ help: {
430
+ type: "boolean",
431
+ short: "h"
432
+ }
433
+ }
434
+ });
435
+ if (values.help) {
436
+ process.stdout.write(HELP);
437
+ process.exit(0);
438
+ }
439
+ const overrides = {};
440
+ if (positionals.length) overrides.features = positionals;
441
+ if (values.tags) overrides.tags = values.tags;
442
+ if (values.name) overrides.name = values.name;
443
+ if (values.steps) overrides.steps = values.steps;
444
+ if (values.world) overrides.world = values.world;
445
+ if (values.reporter) {
446
+ if (values.reporter !== "pretty" && values.reporter !== "progress") throw new Error(`Unknown reporter: ${values.reporter}`);
447
+ overrides.reporter = values.reporter;
448
+ }
449
+ if (values.concurrency !== void 0) {
450
+ const n = Number(values.concurrency);
451
+ if (!Number.isInteger(n) || n < 1) throw new Error(`--concurrency must be a positive integer`);
452
+ overrides.concurrency = n;
453
+ }
454
+ return {
455
+ cwd: values.config ? path.resolve(process.cwd(), values.config) : process.cwd(),
456
+ overrides
457
+ };
458
+ }
459
+ async function main() {
460
+ const { cwd, overrides } = parseCli(process.argv.slice(2));
461
+ const { passed } = await run(resolveConfig(cwd, await loadConfigFile(cwd), overrides));
462
+ process.exitCode = passed ? 0 : 1;
463
+ }
464
+ main().catch((err) => {
465
+ process.stderr.write(`${err instanceof Error ? err.stack : String(err)}\n`);
466
+ process.exitCode = 1;
467
+ });
468
+ //#endregion
469
+ export {};
470
+
471
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +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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"}