@step-forge/step-forge 0.0.17 → 0.0.20

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.
@@ -0,0 +1,69 @@
1
+ import { a as RegisteredHook, b as Parser, c as ensureGlobalHooks, g as ParsedStep, h as ParsedScenario, i as PlainHookFn, l as globalHookRegistry, n as HookScope, o as ScenarioHookFn, r as HookTiming, s as ScenarioInfo, t as HookRegistry, u as runHooks, y as MergeableWorld } from "./hooks-Dar49TtT.js";
2
+
3
+ //#region src/runtime/registry.d.ts
4
+ type StepType = "given" | "when" | "then";
5
+ /**
6
+ * A step definition as it exists at *runtime*, independent of any test runner.
7
+ *
8
+ * `execute` is the fully-wired step body: given a world and the raw values
9
+ * captured from a Gherkin step, it applies parsers, validates + narrows
10
+ * dependencies, runs the user's step function, and merges the result back into
11
+ * the world. The engine never needs to know how any of that works — it just
12
+ * matches text to a step and calls `execute`.
13
+ */
14
+ interface RegisteredStep {
15
+ stepType: StepType;
16
+ /** The Cucumber-expression source, e.g. `a user named {string}`. */
17
+ expression: string;
18
+ /** Parsers, one per captured variable, applied after expression capture. */
19
+ parsers: Parser<any>[];
20
+ execute: (world: MergeableWorld<any, any, any>, capturedArgs: unknown[]) => Promise<void>;
21
+ /** Where the step was defined, for ambiguous-match diagnostics. */
22
+ source?: string;
23
+ }
24
+ /**
25
+ * A collection of registered steps. Deliberately a plain instance (not a hidden
26
+ * module global) so tests and the Vitest plugin can create isolated registries.
27
+ * `globalRegistry` is the default sink that `.step()` writes to.
28
+ */
29
+ declare class StepRegistry {
30
+ private steps;
31
+ add(step: RegisteredStep): void;
32
+ all(): readonly RegisteredStep[];
33
+ clear(): void;
34
+ }
35
+ declare const globalRegistry: StepRegistry;
36
+ //#endregion
37
+ //#region src/runtime/engine.d.ts
38
+ declare class UndefinedStepError extends Error {
39
+ readonly step: ParsedStep;
40
+ constructor(step: ParsedStep);
41
+ }
42
+ declare class AmbiguousStepError extends Error {
43
+ readonly step: ParsedStep;
44
+ readonly matches: RegisteredStep[];
45
+ constructor(step: ParsedStep, matches: RegisteredStep[]);
46
+ }
47
+ interface StepResult {
48
+ step: ParsedStep;
49
+ status: "passed" | "failed" | "skipped";
50
+ error?: Error;
51
+ durationMs?: number;
52
+ }
53
+ interface ScenarioResult {
54
+ scenario: ParsedScenario;
55
+ status: "passed" | "failed";
56
+ steps: StepResult[];
57
+ }
58
+ /**
59
+ * Run one scenario against a registry. A fresh world is created per scenario
60
+ * (state never leaks between scenarios). On the first failing step the
61
+ * remaining steps are marked skipped, matching Cucumber's execution semantics.
62
+ *
63
+ * Throws on failure so it maps cleanly onto a test runner's `test()` body, but
64
+ * the returned/attached `ScenarioResult` carries the per-step breakdown.
65
+ */
66
+ declare function runScenario(scenario: ParsedScenario, registry: StepRegistry, makeWorld: () => MergeableWorld<any, any, any>, hooks?: HookRegistry): Promise<ScenarioResult>;
67
+ //#endregion
68
+ export { AmbiguousStepError, HookRegistry, type HookScope, type HookTiming, type PlainHookFn, type RegisteredHook, type RegisteredStep, type ScenarioHookFn, type ScenarioInfo, type ScenarioResult, StepRegistry, type StepResult, type StepType, UndefinedStepError, ensureGlobalHooks, globalHookRegistry, globalRegistry, runHooks, runScenario };
69
+ //# sourceMappingURL=runtime.d.ts.map
@@ -0,0 +1,159 @@
1
+ import { a as StepRegistry, i as runHooks, n as ensureGlobalHooks, o as globalRegistry, r as globalHookRegistry, t as HookRegistry } from "./hooks-CywugMQQ.js";
2
+ import { CucumberExpression, ParameterType, ParameterTypeRegistry } from "@cucumber/cucumber-expressions";
3
+ //#region src/runtime/engine.ts
4
+ const keywordToStepType = {
5
+ Given: "given",
6
+ When: "when",
7
+ Then: "then"
8
+ };
9
+ var UndefinedStepError = class extends Error {
10
+ step;
11
+ constructor(step) {
12
+ super(`Undefined step: ${step.effectiveKeyword} ${step.text}`);
13
+ this.step = step;
14
+ this.name = "UndefinedStepError";
15
+ }
16
+ };
17
+ var AmbiguousStepError = class extends Error {
18
+ step;
19
+ matches;
20
+ constructor(step, matches) {
21
+ super(`Ambiguous step: "${step.text}" matched ${matches.length} definitions:\n` + matches.map((m) => ` - ${m.expression}`).join("\n"));
22
+ this.step = step;
23
+ this.matches = matches;
24
+ this.name = "AmbiguousStepError";
25
+ }
26
+ };
27
+ function compile(registry) {
28
+ return registry.all().map((step) => {
29
+ const paramRegistry = new ParameterTypeRegistry();
30
+ for (const parser of step.parsers) {
31
+ if (paramRegistry.lookupByTypeName(parser.name)) continue;
32
+ const regexps = Array.isArray(parser.regexp) ? parser.regexp : [parser.regexp];
33
+ paramRegistry.defineParameterType(new ParameterType(parser.name, regexps, null, (value) => parser.parse(value)));
34
+ }
35
+ return {
36
+ step,
37
+ expression: new CucumberExpression(step.expression, paramRegistry)
38
+ };
39
+ });
40
+ }
41
+ /**
42
+ * Find the single step definition matching a Gherkin step. Matching is
43
+ * opinionated and strict: the keyword must line up with the step type, exactly
44
+ * one definition must match, and undefined/ambiguous both throw rather than
45
+ * silently skipping (unlike Cucumber's pending/undefined dance).
46
+ */
47
+ function matchStep(step, compiled) {
48
+ const expectedType = keywordToStepType[step.effectiveKeyword];
49
+ const matches = [];
50
+ for (const { step: def, expression } of compiled) {
51
+ if (def.stepType !== expectedType) continue;
52
+ const result = expression.match(step.text);
53
+ if (result) matches.push({
54
+ step: def,
55
+ args: result.map((a) => a.getValue(null))
56
+ });
57
+ }
58
+ if (matches.length === 0) throw new UndefinedStepError(step);
59
+ if (matches.length > 1) throw new AmbiguousStepError(step, matches.map((m) => m.step));
60
+ return matches[0];
61
+ }
62
+ /**
63
+ * Run one scenario against a registry. A fresh world is created per scenario
64
+ * (state never leaks between scenarios). On the first failing step the
65
+ * remaining steps are marked skipped, matching Cucumber's execution semantics.
66
+ *
67
+ * Throws on failure so it maps cleanly onto a test runner's `test()` body, but
68
+ * the returned/attached `ScenarioResult` carries the per-step breakdown.
69
+ */
70
+ async function runScenario(scenario, registry, makeWorld, hooks = globalHookRegistry) {
71
+ const compiled = compile(registry);
72
+ const world = makeWorld();
73
+ const scenarioInfo = {
74
+ name: scenario.name,
75
+ file: scenario.file
76
+ };
77
+ const steps = [];
78
+ let failed = false;
79
+ let firstError;
80
+ const fail = (err) => {
81
+ if (failed) return;
82
+ failed = true;
83
+ firstError = err instanceof Error ? err : new Error(String(err));
84
+ };
85
+ try {
86
+ for (const hook of hooks.for("scenario", "before")) await hook.fn({
87
+ world,
88
+ scenario: scenarioInfo
89
+ });
90
+ } catch (err) {
91
+ fail(err);
92
+ }
93
+ for (const step of scenario.steps) {
94
+ if (failed) {
95
+ steps.push({
96
+ step,
97
+ status: "skipped"
98
+ });
99
+ continue;
100
+ }
101
+ try {
102
+ const { step: def, args } = matchStep(step, compiled);
103
+ await def.execute(world, args);
104
+ steps.push({
105
+ step,
106
+ status: "passed"
107
+ });
108
+ } catch (err) {
109
+ const error = err instanceof Error ? err : new Error(String(err));
110
+ steps.push({
111
+ step,
112
+ status: "failed",
113
+ error
114
+ });
115
+ fail(error);
116
+ }
117
+ }
118
+ for (const hook of hooks.for("scenario", "after")) try {
119
+ await hook.fn({
120
+ world,
121
+ scenario: scenarioInfo
122
+ });
123
+ } catch (err) {
124
+ fail(err);
125
+ }
126
+ const result = {
127
+ scenario,
128
+ status: failed ? "failed" : "passed",
129
+ steps
130
+ };
131
+ if (failed && firstError) {
132
+ const failing = steps.find((s) => s.status === "failed");
133
+ if (failing) attachFeatureFrame(firstError, scenario.file, failing.step);
134
+ throw firstError;
135
+ }
136
+ return result;
137
+ }
138
+ /**
139
+ * Prepend a synthetic stack frame pointing at the failing Gherkin step. Because
140
+ * the frame's file is the real `.feature` on disk, the test runner treats it as
141
+ * a source location and shows a code frame at the step — instead of us jamming
142
+ * `file:line` into the error message. The frame goes *above* the real stack, so
143
+ * the step-definition frames (the actual throw site) are preserved below it.
144
+ */
145
+ function attachFeatureFrame(error, file, step) {
146
+ const frame = ` at ${`${step.effectiveKeyword} ${step.text}`} (${file}:${step.line}:${step.column})`;
147
+ const stack = error.stack;
148
+ if (!stack) {
149
+ error.stack = `${error.name}: ${error.message}\n${frame}`;
150
+ return;
151
+ }
152
+ const firstFrame = stack.indexOf("\n at ");
153
+ if (firstFrame === -1) error.stack = `${stack}\n${frame}`;
154
+ else error.stack = stack.slice(0, firstFrame) + `\n${frame}` + stack.slice(firstFrame);
155
+ }
156
+ //#endregion
157
+ export { AmbiguousStepError, HookRegistry, StepRegistry, UndefinedStepError, ensureGlobalHooks, globalHookRegistry, globalRegistry, runHooks, runScenario };
158
+
159
+ //# sourceMappingURL=runtime.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime.js","names":[],"sources":["../../src/runtime/engine.ts"],"sourcesContent":["/* eslint-disable @typescript-eslint/no-explicit-any */\nimport {\n CucumberExpression,\n ParameterType,\n ParameterTypeRegistry,\n} from \"@cucumber/cucumber-expressions\";\nimport { ParsedScenario, ParsedStep } from \"../analyzer/types\";\nimport { MergeableWorld } from \"../world\";\nimport { globalHookRegistry, HookRegistry, ScenarioHookFn } from \"./hooks\";\nimport { RegisteredStep, StepRegistry, StepType } from \"./registry\";\n\nconst keywordToStepType: Record<ParsedStep[\"effectiveKeyword\"], StepType> = {\n Given: \"given\",\n When: \"when\",\n Then: \"then\",\n};\n\n/** A registered step paired with its compiled Cucumber expression. */\ninterface CompiledStep {\n step: RegisteredStep;\n expression: CucumberExpression;\n}\n\nexport class UndefinedStepError extends Error {\n constructor(public readonly step: ParsedStep) {\n super(`Undefined step: ${step.effectiveKeyword} ${step.text}`);\n this.name = \"UndefinedStepError\";\n }\n}\n\nexport class AmbiguousStepError extends Error {\n constructor(\n public readonly step: ParsedStep,\n public readonly matches: RegisteredStep[]\n ) {\n super(\n `Ambiguous step: \"${step.text}\" matched ${matches.length} definitions:\\n` +\n matches.map(m => ` - ${m.expression}`).join(\"\\n\")\n );\n this.name = \"AmbiguousStepError\";\n }\n}\n\nfunction compile(registry: StepRegistry): CompiledStep[] {\n return registry.all().map(step => {\n // Each step gets its own parameter-type registry (seeded with the\n // built-ins). A parser whose name is already registered — the built-in\n // `{int}`/`{float}`/`{string}`, or a repeat within the same step — reuses\n // that type; a novel name (e.g. `{boolean}`, `{color}`) is registered from\n // the parser's regexp + parse, so matching and coercion happen in one pass.\n const paramRegistry = new ParameterTypeRegistry();\n for (const parser of step.parsers) {\n if (paramRegistry.lookupByTypeName(parser.name)) continue;\n const regexps = Array.isArray(parser.regexp)\n ? parser.regexp\n : [parser.regexp];\n paramRegistry.defineParameterType(\n new ParameterType(parser.name, regexps, null, (value: string) =>\n parser.parse(value)\n )\n );\n }\n return {\n step,\n expression: new CucumberExpression(step.expression, paramRegistry),\n };\n });\n}\n\n/**\n * Find the single step definition matching a Gherkin step. Matching is\n * opinionated and strict: the keyword must line up with the step type, exactly\n * one definition must match, and undefined/ambiguous both throw rather than\n * silently skipping (unlike Cucumber's pending/undefined dance).\n */\nfunction matchStep(\n step: ParsedStep,\n compiled: CompiledStep[]\n): { step: RegisteredStep; args: unknown[] } {\n const expectedType = keywordToStepType[step.effectiveKeyword];\n const matches: { step: RegisteredStep; args: unknown[] }[] = [];\n\n for (const { step: def, expression } of compiled) {\n if (def.stepType !== expectedType) continue;\n const result = expression.match(step.text);\n if (result) {\n // The parsers are registered as the expression's parameter types, so the\n // captured values are already coerced (`{int}` → number, `{color}` → the\n // parser's T). `execute` consumes them as-is.\n matches.push({ step: def, args: result.map(a => a.getValue(null)) });\n }\n }\n\n if (matches.length === 0) throw new UndefinedStepError(step);\n if (matches.length > 1) {\n throw new AmbiguousStepError(\n step,\n matches.map(m => m.step)\n );\n }\n return matches[0];\n}\n\nexport interface StepResult {\n step: ParsedStep;\n status: \"passed\" | \"failed\" | \"skipped\";\n error?: Error;\n durationMs?: number;\n}\n\nexport interface ScenarioResult {\n scenario: ParsedScenario;\n status: \"passed\" | \"failed\";\n steps: StepResult[];\n}\n\n/**\n * Run one scenario against a registry. A fresh world is created per scenario\n * (state never leaks between scenarios). On the first failing step the\n * remaining steps are marked skipped, matching Cucumber's execution semantics.\n *\n * Throws on failure so it maps cleanly onto a test runner's `test()` body, but\n * the returned/attached `ScenarioResult` carries the per-step breakdown.\n */\nexport async function runScenario(\n scenario: ParsedScenario,\n registry: StepRegistry,\n makeWorld: () => MergeableWorld<any, any, any>,\n hooks: HookRegistry = globalHookRegistry\n): Promise<ScenarioResult> {\n const compiled = compile(registry);\n const world = makeWorld();\n const scenarioInfo = { name: scenario.name, file: scenario.file };\n const steps: StepResult[] = [];\n let failed = false;\n let firstError: Error | undefined;\n\n const fail = (err: unknown) => {\n if (failed) return;\n failed = true;\n firstError = err instanceof Error ? err : new Error(String(err));\n };\n\n // before-scenario hooks: a throw here aborts the scenario before any step.\n try {\n for (const hook of hooks.for(\"scenario\", \"before\")) {\n await (hook.fn as ScenarioHookFn)({ world, scenario: scenarioInfo });\n }\n } catch (err) {\n fail(err);\n }\n\n for (const step of scenario.steps) {\n if (failed) {\n steps.push({ step, status: \"skipped\" });\n continue;\n }\n try {\n const { step: def, args } = matchStep(step, compiled);\n await def.execute(world, args);\n steps.push({ step, status: \"passed\" });\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n steps.push({ step, status: \"failed\", error });\n fail(error);\n }\n }\n\n // after-scenario hooks always run (teardown), even on failure. A hook failure\n // only becomes the scenario's error if nothing else failed first.\n for (const hook of hooks.for(\"scenario\", \"after\")) {\n try {\n await (hook.fn as ScenarioHookFn)({ world, scenario: scenarioInfo });\n } catch (err) {\n fail(err);\n }\n }\n\n const result: ScenarioResult = {\n scenario,\n status: failed ? \"failed\" : \"passed\",\n steps,\n };\n\n if (failed && firstError) {\n // Surface the failing Gherkin line as a real stack frame so the runner\n // renders a code frame from the `.feature` file itself. Hook failures have\n // no step to point at, so they surface with their own stack unchanged.\n const failing = steps.find(s => s.status === \"failed\");\n if (failing) attachFeatureFrame(firstError, scenario.file, failing.step);\n throw firstError;\n }\n\n return result;\n}\n\n/**\n * Prepend a synthetic stack frame pointing at the failing Gherkin step. Because\n * the frame's file is the real `.feature` on disk, the test runner treats it as\n * a source location and shows a code frame at the step — instead of us jamming\n * `file:line` into the error message. The frame goes *above* the real stack, so\n * the step-definition frames (the actual throw site) are preserved below it.\n */\nfunction attachFeatureFrame(\n error: Error,\n file: string,\n step: ParsedStep\n): void {\n const label = `${step.effectiveKeyword} ${step.text}`;\n const frame = ` at ${label} (${file}:${step.line}:${step.column})`;\n const stack = error.stack;\n if (!stack) {\n error.stack = `${error.name}: ${error.message}\\n${frame}`;\n return;\n }\n // A message can span multiple lines, so split on the first frame marker\n // rather than the first newline to find where the header ends.\n const firstFrame = stack.indexOf(\"\\n at \");\n if (firstFrame === -1) {\n error.stack = `${stack}\\n${frame}`;\n } else {\n error.stack =\n stack.slice(0, firstFrame) + `\\n${frame}` + stack.slice(firstFrame);\n }\n}\n"],"mappings":"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"}