@step-forge/step-forge 0.0.19 → 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.
Files changed (40) hide show
  1. package/README.md +5 -2
  2. package/RUNTIME.md +212 -0
  3. package/dist/{analyzer-QH03uejK.js → analyzer-DYfdoSIS.js} +55 -16
  4. package/dist/analyzer-DYfdoSIS.js.map +1 -0
  5. package/dist/analyzer-cli.js +1 -1
  6. package/dist/analyzer-cli.js.map +1 -1
  7. package/dist/analyzer.d.ts +14 -0
  8. package/dist/analyzer.js +1 -1
  9. package/dist/cli.cjs +470 -0
  10. package/dist/cli.cjs.map +1 -0
  11. package/dist/cli.d.cts +1 -0
  12. package/dist/cli.d.ts +1 -0
  13. package/dist/cli.js +471 -0
  14. package/dist/cli.js.map +1 -0
  15. package/dist/engine-DPRxs6eC.js +181 -0
  16. package/dist/engine-DPRxs6eC.js.map +1 -0
  17. package/dist/engine-DWAIlwWp.cjs +204 -0
  18. package/dist/engine-DWAIlwWp.cjs.map +1 -0
  19. package/dist/gherkinParser-CHpkEwii.cjs +243 -0
  20. package/dist/gherkinParser-CHpkEwii.cjs.map +1 -0
  21. package/dist/gherkinParser-CKARHgt4.js +188 -0
  22. package/dist/gherkinParser-CKARHgt4.js.map +1 -0
  23. package/dist/hooks-CGYzwDOv.cjs +117 -0
  24. package/dist/hooks-CGYzwDOv.cjs.map +1 -0
  25. package/dist/hooks-CywugMQQ.js +82 -0
  26. package/dist/hooks-CywugMQQ.js.map +1 -0
  27. package/dist/hooks-Dar49TtT.d.cts +189 -0
  28. package/dist/hooks-Dar49TtT.d.ts +189 -0
  29. package/dist/runtime.cjs +13 -0
  30. package/dist/runtime.d.cts +142 -0
  31. package/dist/runtime.d.ts +142 -0
  32. package/dist/runtime.js +3 -0
  33. package/dist/step-forge.cjs +185 -306
  34. package/dist/step-forge.cjs.map +1 -1
  35. package/dist/step-forge.d.cts +193 -733
  36. package/dist/step-forge.d.ts +193 -733
  37. package/dist/step-forge.js +174 -276
  38. package/dist/step-forge.js.map +1 -1
  39. package/package.json +20 -9
  40. package/dist/analyzer-QH03uejK.js.map +0 -1
@@ -1,34 +1,41 @@
1
- import { Given, Then, When } from "@cucumber/cucumber";
1
+ import { o as globalRegistry, r as globalHookRegistry } from "./hooks-CywugMQQ.js";
2
+ import { i as BasicWorld, n as parseFeatureFiles, r as globFiles, t as parseFeatureContent } from "./gherkinParser-CKARHgt4.js";
2
3
  import _ from "lodash";
3
- import { glob } from "node:fs/promises";
4
- import * as path from "node:path";
5
4
  import ts from "typescript";
6
- import * as fs from "node:fs";
7
- import { AstBuilder, GherkinClassicTokenMatcher, Parser } from "@cucumber/gherkin";
8
- import * as messages from "@cucumber/messages";
9
5
  //#region src/builderTypeUtils.ts
10
6
  const isString = (statement) => typeof statement === "string";
11
7
  //#endregion
12
8
  //#region src/parsers.ts
13
- /** Matches a quoted string (`{string}`) and passes the unquoted contents through. */
9
+ /** Matches a quoted string (`{string}`) and strips the surrounding quotes. */
14
10
  const stringParser = {
15
- parse: (value) => String(value),
16
- gherkin: "{string}"
11
+ name: "string",
12
+ regexp: [/"([^"\\]*(\\.[^"\\]*)*)"/, /'([^'\\]*(\\.[^'\\]*)*)'/],
13
+ parse: (value) => {
14
+ const match = /^"([\s\S]*)"$/.exec(value) ?? /^'([\s\S]*)'$/.exec(value);
15
+ return match ? match[1].replace(/\\(["'])/g, "$1") : value;
16
+ }
17
17
  };
18
18
  /** Matches an unquoted integer (`{int}`). */
19
19
  const intParser = {
20
- parse: (value) => typeof value === "number" ? value : parseInt(String(value), 10),
21
- gherkin: "{int}"
20
+ name: "int",
21
+ regexp: /-?\d+/,
22
+ parse: (value) => parseInt(value, 10)
22
23
  };
23
24
  /** Matches an unquoted floating point number (`{float}`). */
24
25
  const numberParser = {
25
- parse: (value) => typeof value === "number" ? value : parseFloat(String(value)),
26
- gherkin: "{float}"
26
+ name: "float",
27
+ regexp: /-?\d*\.?\d+/,
28
+ parse: (value) => parseFloat(value)
27
29
  };
28
- /** Matches an unquoted `true`/`false` word (`{word}`) and parses it to a boolean. */
30
+ /**
31
+ * Matches an unquoted `true`/`false` word (`{boolean}`) and parses it to a
32
+ * boolean. Unlike the others this is a genuine custom parameter type — cucumber
33
+ * has no built-in `boolean` — so its `regexp`/`parse` are what the matcher uses.
34
+ */
29
35
  const booleanParser = {
30
- parse: (value) => value === true || value === "true",
31
- gherkin: "{word}"
36
+ name: "boolean",
37
+ regexp: /true|false/,
38
+ parse: (value) => value === "true"
32
39
  };
33
40
  //#endregion
34
41
  //#region src/utils.ts
@@ -67,187 +74,123 @@ const requireFromThen = (keys, world) => {
67
74
  };
68
75
  //#endregion
69
76
  //#region src/common.ts
70
- const cucFunctionMap = {
71
- given: Given,
72
- when: When,
73
- then: Then
74
- };
77
+ /**
78
+ * The runtime core of the builder chain. `addStep` is deliberately phase-agnostic:
79
+ * the calling builder (given/when/then) has already computed the exact type of the
80
+ * step function via its two type parameters:
81
+ *
82
+ * - `StepFnInput` — the `{ variables, given, when, then }` object the step receives,
83
+ * with each phase already narrowed to its declared dependencies.
84
+ * - `StepFnOutput` — the phase-appropriate return type (`Partial<State>`, or `void`).
85
+ *
86
+ * Everything else is plain runtime data (a statement function, the step type, the
87
+ * dependency map, the parsers), so `addStep` carries no generics for them.
88
+ */
75
89
  const addStep = (statement, stepType, dependencies = {
76
90
  given: {},
77
91
  when: {},
78
92
  then: {}
79
93
  }, declaredParsers) => (stepFunction) => {
80
- const statementFunction = statement;
81
94
  const { given: givenDependencies, when: whenDependencies, then: thenDependencies } = dependencies;
82
- const argCount = statementFunction.length;
95
+ const argCount = statement.length;
83
96
  const parsers = declaredParsers ?? Array.from({ length: argCount }, () => stringParser);
84
- const expression = statementFunction(...parsers.map((parser) => parser.gherkin));
97
+ const expression = statement(...parsers.map((parser) => `{${parser.name}}`));
98
+ const execute = async (world, capturedArgs) => {
99
+ const requiredKeys = (deps) => Object.entries(deps).filter(([, value]) => value === "required").map(([key]) => key);
100
+ const result = await stepFunction({
101
+ variables: capturedArgs,
102
+ given: {
103
+ ..._.pick(world.given, Object.keys(givenDependencies)),
104
+ ...requireFromGiven(requiredKeys(givenDependencies), world)
105
+ },
106
+ when: {
107
+ ..._.pick(world.when, Object.keys(whenDependencies)),
108
+ ...requireFromWhen(requiredKeys(whenDependencies), world)
109
+ },
110
+ then: {
111
+ ..._.pick(world.then, Object.keys(thenDependencies)),
112
+ ...requireFromThen(requiredKeys(thenDependencies), world)
113
+ }
114
+ });
115
+ world[stepType].merge({ ...result });
116
+ };
117
+ globalRegistry.add({
118
+ stepType,
119
+ expression,
120
+ parsers,
121
+ execute
122
+ });
85
123
  return {
86
124
  statement,
87
125
  expression,
88
126
  dependencies,
89
127
  stepType,
90
- stepFunction,
91
- register: () => {
92
- const cucStepFunction = Object.defineProperty(async function(...args) {
93
- const coercedArgs = parsers.map((parser, index) => parser.parse(args[index]));
94
- const ensuredGivenValues = requireFromGiven(Object.entries(givenDependencies ?? {}).filter(([, value]) => value === "required").map(([key]) => key), this);
95
- const narrowedGiven = {
96
- ..._.pick(this.given, Object.keys(givenDependencies ?? {})),
97
- ...ensuredGivenValues
98
- };
99
- const ensuredWhenValues = requireFromWhen(Object.entries(whenDependencies ?? {}).filter(([, value]) => value === "required").map(([key]) => key), this);
100
- const narrowedWhen = {
101
- ..._.pick(this.when, Object.keys(whenDependencies ?? {})),
102
- ...ensuredWhenValues
103
- };
104
- const ensuredThenValues = requireFromThen(Object.entries(thenDependencies ?? {}).filter(([, value]) => value === "required").map(([key]) => key), this);
105
- const result = await stepFunction({
106
- variables: coercedArgs,
107
- given: narrowedGiven,
108
- when: narrowedWhen,
109
- then: {
110
- ..._.pick(this.then, Object.keys(thenDependencies ?? {})),
111
- ...ensuredThenValues
112
- }
113
- });
114
- this[stepType].merge({ ...result });
115
- }, "length", {
116
- value: argCount,
117
- configurable: true
118
- });
119
- const cucStep = cucFunctionMap[stepType];
120
- cucStep(expression, cucStepFunction);
121
- return {
122
- stepType,
123
- expression,
124
- dependencies,
125
- statement: statementFunction,
126
- stepFunction
127
- };
128
- }
128
+ stepFunction
129
129
  };
130
130
  };
131
131
  //#endregion
132
132
  //#region src/given.ts
133
- const givenDependencies = (statement, stepType, parsers) => (dependencies) => {
134
- return { step: addStep(statement, stepType, {
135
- ...dependencies,
136
- when: {},
137
- then: {}
138
- }, parsers) };
139
- };
140
- const givenParsers = (statement, stepType) => (parsers) => {
141
- return {
142
- dependencies: givenDependencies(statement, stepType, parsers),
143
- step: addStep(statement, stepType, void 0, parsers)
144
- };
145
- };
146
- const givenStatement = (stepType) => (statement) => {
147
- let normalizedStatement;
148
- if (isString(statement)) normalizedStatement = (() => statement);
149
- else normalizedStatement = statement;
133
+ const GIVEN = "given";
134
+ const givenDependencies = (statement, parsers) => (dependencies) => ({ step: addStep(statement, GIVEN, {
135
+ ...dependencies,
136
+ when: {},
137
+ then: {}
138
+ }, parsers) });
139
+ const givenParsers = (statement) => (parsers) => ({
140
+ dependencies: givenDependencies(statement, parsers),
141
+ step: addStep(statement, GIVEN, void 0, parsers)
142
+ });
143
+ const givenStatement = () => (statement) => {
144
+ const normalizedStatement = isString(statement) ? () => statement : statement;
150
145
  return {
151
- dependencies: givenDependencies(normalizedStatement, stepType),
152
- parsers: givenParsers(normalizedStatement, stepType),
153
- step: addStep(normalizedStatement, stepType)
146
+ dependencies: givenDependencies(normalizedStatement),
147
+ parsers: givenParsers(normalizedStatement),
148
+ step: addStep(normalizedStatement, GIVEN)
154
149
  };
155
150
  };
156
- const givenBuilder = () => {
157
- return { statement: givenStatement("given") };
158
- };
151
+ const givenBuilder = () => ({ statement: givenStatement() });
159
152
  //#endregion
160
153
  //#region src/when.ts
161
- const whenDependencies = (statement, stepType, parsers) => (dependencies) => {
162
- return { step: addStep(statement, stepType, {
163
- given: dependencies.given ?? {},
164
- when: dependencies.when ?? {},
165
- then: {}
166
- }, parsers) };
167
- };
168
- const whenParsers = (statement, stepType) => (parsers) => {
169
- return {
170
- dependencies: whenDependencies(statement, stepType, parsers),
171
- step: addStep(statement, stepType, void 0, parsers)
172
- };
173
- };
174
- const whenStatement = (stepType) => (statement) => {
175
- let normalizedStatement;
176
- if (isString(statement)) normalizedStatement = (() => statement);
177
- else normalizedStatement = statement;
154
+ const WHEN = "when";
155
+ const whenDependencies = (statement, parsers) => (dependencies) => ({ step: addStep(statement, WHEN, {
156
+ given: dependencies.given ?? {},
157
+ when: dependencies.when ?? {},
158
+ then: {}
159
+ }, parsers) });
160
+ const whenParsers = (statement) => (parsers) => ({
161
+ dependencies: whenDependencies(statement, parsers),
162
+ step: addStep(statement, WHEN, void 0, parsers)
163
+ });
164
+ const whenStatement = () => (statement) => {
165
+ const normalizedStatement = isString(statement) ? () => statement : statement;
178
166
  return {
179
- dependencies: whenDependencies(normalizedStatement, stepType),
180
- parsers: whenParsers(normalizedStatement, stepType),
181
- step: addStep(normalizedStatement, stepType)
167
+ dependencies: whenDependencies(normalizedStatement),
168
+ parsers: whenParsers(normalizedStatement),
169
+ step: addStep(normalizedStatement, WHEN)
182
170
  };
183
171
  };
184
- const whenBuilder = () => {
185
- return { statement: whenStatement("when") };
186
- };
172
+ const whenBuilder = () => ({ statement: whenStatement() });
187
173
  //#endregion
188
174
  //#region src/then.ts
189
- const thenDependencies = (statement, stepType, parsers) => (dependencies) => {
190
- return { step: addStep(statement, stepType, {
191
- given: dependencies.given ?? {},
192
- when: dependencies.when ?? {},
193
- then: dependencies.then ?? {}
194
- }, parsers) };
195
- };
196
- const thenParsers = (statement, stepType) => (parsers) => {
197
- return {
198
- dependencies: thenDependencies(statement, stepType, parsers),
199
- step: addStep(statement, stepType, void 0, parsers)
200
- };
201
- };
202
- const thenStatement = (stepType) => (statement) => {
203
- let normalizedStatement;
204
- if (isString(statement)) normalizedStatement = (() => statement);
205
- else normalizedStatement = statement;
175
+ const THEN = "then";
176
+ const thenDependencies = (statement, parsers) => (dependencies) => ({ step: addStep(statement, THEN, {
177
+ given: dependencies.given ?? {},
178
+ when: dependencies.when ?? {},
179
+ then: dependencies.then ?? {}
180
+ }, parsers) });
181
+ const thenParsers = (statement) => (parsers) => ({
182
+ dependencies: thenDependencies(statement, parsers),
183
+ step: addStep(statement, THEN, void 0, parsers)
184
+ });
185
+ const thenStatement = () => (statement) => {
186
+ const normalizedStatement = isString(statement) ? () => statement : statement;
206
187
  return {
207
- dependencies: thenDependencies(normalizedStatement, stepType),
208
- parsers: thenParsers(normalizedStatement, stepType),
209
- step: addStep(normalizedStatement, stepType)
188
+ dependencies: thenDependencies(normalizedStatement),
189
+ parsers: thenParsers(normalizedStatement),
190
+ step: addStep(normalizedStatement, THEN)
210
191
  };
211
192
  };
212
- const thenBuilder = () => {
213
- return { statement: thenStatement("then") };
214
- };
215
- //#endregion
216
- //#region src/world.ts
217
- function mergeCustomizer(objValue, srcValue) {
218
- if (_.isArray(objValue)) return objValue.concat(srcValue);
219
- else if (objValue && !_.isPlainObject(objValue) && objValue !== srcValue) throw new Error(`Merge would have destroyed previous value ${objValue} with ${srcValue}`);
220
- return objValue;
221
- }
222
- var BasicWorld = class {
223
- givenState = {};
224
- whenState = {};
225
- thenState = {};
226
- get given() {
227
- return {
228
- ...this.givenState,
229
- merge: (newState) => {
230
- this.givenState = _.merge({ ...this.givenState }, newState, mergeCustomizer);
231
- }
232
- };
233
- }
234
- get when() {
235
- return {
236
- ...this.whenState,
237
- merge: (newState) => {
238
- this.whenState = _.merge({ ...this.whenState }, newState, mergeCustomizer);
239
- }
240
- };
241
- }
242
- get then() {
243
- return {
244
- ...this.thenState,
245
- merge: (newState) => {
246
- this.thenState = _.merge({ ...this.thenState }, newState, mergeCustomizer);
247
- }
248
- };
249
- }
250
- };
193
+ const thenBuilder = () => ({ statement: thenStatement() });
251
194
  //#endregion
252
195
  //#region src/analyzer/stepExtractor.ts
253
196
  const BUILDER_NAMES = {
@@ -283,7 +226,7 @@ function extractStepDefinitions(filePaths, tsConfigPath) {
283
226
  function extractFromSourceFile(sourceFile, checker) {
284
227
  const results = [];
285
228
  function visit(node) {
286
- if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression) && node.expression.name.text === "register") {
229
+ if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression) && node.expression.name.text === "step") {
287
230
  const meta = extractFromRegisterCall(node, sourceFile, checker);
288
231
  if (meta) results.push(meta);
289
232
  }
@@ -466,105 +409,6 @@ function resolveReExportedCall(chain, checker) {
466
409
  return null;
467
410
  }
468
411
  //#endregion
469
- //#region src/analyzer/gherkinParser.ts
470
- function parseFeatureFiles(filePaths) {
471
- const scenarios = [];
472
- for (const filePath of filePaths) {
473
- const parsed = parseFeatureContent(fs.readFileSync(filePath, "utf-8"), filePath);
474
- scenarios.push(...parsed);
475
- }
476
- return scenarios;
477
- }
478
- function parseFeatureContent(content, filePath) {
479
- const feature = new Parser(new AstBuilder(messages.IdGenerator.uuid()), new GherkinClassicTokenMatcher()).parse(content).feature;
480
- if (!feature) return [];
481
- const featureBackground = [];
482
- const scenarios = [];
483
- for (const child of feature.children) {
484
- if (child.background) featureBackground.push(...child.background.steps);
485
- if (child.scenario) scenarios.push(...expandScenario(child.scenario, featureBackground, filePath));
486
- if (child.rule) {
487
- const ruleBackground = [...featureBackground];
488
- for (const ruleChild of child.rule.children) {
489
- if (ruleChild.background) ruleBackground.push(...ruleChild.background.steps);
490
- if (ruleChild.scenario) scenarios.push(...expandScenario(ruleChild.scenario, ruleBackground, filePath));
491
- }
492
- }
493
- }
494
- return scenarios;
495
- }
496
- function expandScenario(scenario, backgroundSteps, filePath) {
497
- if (!(scenario.examples.length > 0 && scenario.examples.some((e) => e.tableBody.length > 0))) {
498
- const bgParsed = convertSteps(backgroundSteps);
499
- const scenarioParsed = convertSteps(scenario.steps);
500
- const allSteps = resolveEffectiveKeywords([...bgParsed, ...scenarioParsed]);
501
- return [{
502
- name: scenario.name,
503
- file: filePath,
504
- steps: allSteps
505
- }];
506
- }
507
- const results = [];
508
- for (const example of scenario.examples) {
509
- if (!example.tableHeader || example.tableBody.length === 0) continue;
510
- const headers = example.tableHeader.cells.map((c) => c.value);
511
- for (const row of example.tableBody) {
512
- const values = row.cells.map((c) => c.value);
513
- const substitution = {};
514
- headers.forEach((h, i) => {
515
- substitution[h] = values[i];
516
- });
517
- const bgParsed = convertSteps(backgroundSteps);
518
- const scenarioSteps = convertSteps(scenario.steps).map((step) => ({
519
- ...step,
520
- text: substituteExampleValues(step.text, substitution)
521
- }));
522
- const allSteps = resolveEffectiveKeywords([...bgParsed, ...scenarioSteps]);
523
- results.push({
524
- name: `${scenario.name} (${values.join(", ")})`,
525
- file: filePath,
526
- steps: allSteps
527
- });
528
- }
529
- }
530
- return results;
531
- }
532
- function convertSteps(steps) {
533
- return steps.map((step) => ({
534
- keyword: normalizeKeyword(step.keyword),
535
- text: step.text,
536
- line: step.location.line,
537
- column: (step.location.column ?? 1) + step.keyword.length
538
- }));
539
- }
540
- function normalizeKeyword(keyword) {
541
- const trimmed = keyword.trim();
542
- if (trimmed === "Given") return "Given";
543
- if (trimmed === "When") return "When";
544
- if (trimmed === "Then") return "Then";
545
- if (trimmed === "And") return "And";
546
- if (trimmed === "But") return "But";
547
- return "Given";
548
- }
549
- function resolveEffectiveKeywords(steps) {
550
- let lastEffective = "Given";
551
- return steps.map((step) => {
552
- let effectiveKeyword;
553
- if (step.keyword === "And" || step.keyword === "But") effectiveKeyword = lastEffective;
554
- else effectiveKeyword = step.keyword;
555
- lastEffective = effectiveKeyword;
556
- return {
557
- ...step,
558
- effectiveKeyword
559
- };
560
- });
561
- }
562
- function substituteExampleValues(text, substitution) {
563
- let result = text;
564
- for (const [key, value] of Object.entries(substitution)) result = result.replace(new RegExp(`<${key}>`, "g"), value);
565
- return result;
566
- }
567
- //#endregion
568
412
  //#region src/analyzer/stepMatcher.ts
569
413
  function compileDefinitions(definitions) {
570
414
  const compiled = [];
@@ -699,8 +543,8 @@ function runRules(rules, scenario, matchedSteps) {
699
543
  //#region src/analyzer/index.ts
700
544
  async function analyze(config, options) {
701
545
  const rules = options?.rules ?? defaultRules;
702
- const stepFilePaths = await resolveGlobs(config.stepFiles);
703
- const featureFilePaths = await resolveGlobs(config.featureFiles);
546
+ const stepFilePaths = await globFiles(config.stepFiles);
547
+ const featureFilePaths = await globFiles(config.featureFiles);
704
548
  if (stepFilePaths.length === 0) return [];
705
549
  if (featureFilePaths.length === 0) return [];
706
550
  const stepDefinitions = extractStepDefinitions(stepFilePaths, config.tsConfigPath);
@@ -712,11 +556,6 @@ async function analyze(config, options) {
712
556
  }
713
557
  return diagnostics;
714
558
  }
715
- async function resolveGlobs(patterns) {
716
- const files = [];
717
- for (const pattern of patterns) for await (const file of glob(pattern)) files.push(path.resolve(file));
718
- return [...new Set(files)];
719
- }
720
559
  //#endregion
721
560
  //#region src/init.ts
722
561
  const createBuilders = () => {
@@ -727,6 +566,65 @@ const createBuilders = () => {
727
566
  };
728
567
  };
729
568
  //#endregion
569
+ //#region src/hooks.ts
570
+ /**
571
+ * Run before every scenario, with access to that scenario's fresh world. For
572
+ * side effects only (reset a mock, seed an external system) — return values are
573
+ * ignored; seed test *state* with given steps so the dependency graph stays the
574
+ * single source of truth.
575
+ */
576
+ function beforeScenario(fn) {
577
+ globalHookRegistry.add({
578
+ scope: "scenario",
579
+ timing: "before",
580
+ fn
581
+ });
582
+ }
583
+ /**
584
+ * Run after every scenario (even when a step failed), with access to that
585
+ * scenario's world. Runs in reverse registration order so teardown unwinds
586
+ * setup. For cleanup side effects only.
587
+ */
588
+ function afterScenario(fn) {
589
+ globalHookRegistry.add({
590
+ scope: "scenario",
591
+ timing: "after",
592
+ fn
593
+ });
594
+ }
595
+ /** Run once at the start of each feature file. No world exists at this boundary. */
596
+ function beforeFeature(fn) {
597
+ globalHookRegistry.add({
598
+ scope: "feature",
599
+ timing: "before",
600
+ fn
601
+ });
602
+ }
603
+ /** Run once at the end of each feature file (reverse registration order). */
604
+ function afterFeature(fn) {
605
+ globalHookRegistry.add({
606
+ scope: "feature",
607
+ timing: "after",
608
+ fn
609
+ });
610
+ }
611
+ /** Run once before the entire test run, across all feature files. */
612
+ function beforeAll(fn) {
613
+ globalHookRegistry.add({
614
+ scope: "global",
615
+ timing: "before",
616
+ fn
617
+ });
618
+ }
619
+ /** Run once after the entire test run (reverse registration order). */
620
+ function afterAll(fn) {
621
+ globalHookRegistry.add({
622
+ scope: "global",
623
+ timing: "after",
624
+ fn
625
+ });
626
+ }
627
+ //#endregion
730
628
  //#region src/index.ts
731
629
  const analyzer = {
732
630
  analyze,
@@ -738,6 +636,6 @@ const analyzer = {
738
636
  defaultRules
739
637
  };
740
638
  //#endregion
741
- export { BasicWorld, analyzer, booleanParser, createBuilders, givenBuilder, intParser, numberParser, stringParser, thenBuilder, whenBuilder };
639
+ export { BasicWorld, afterAll, afterFeature, afterScenario, analyzer, beforeAll, beforeFeature, beforeScenario, booleanParser, createBuilders, givenBuilder, intParser, numberParser, stringParser, thenBuilder, whenBuilder };
742
640
 
743
641
  //# sourceMappingURL=step-forge.js.map