@step-forge/step-forge 0.0.11 → 0.0.13

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 (68) hide show
  1. package/dist/analyzer-QH03uejK.js +478 -0
  2. package/dist/analyzer-QH03uejK.js.map +1 -0
  3. package/dist/analyzer-cli.d.ts +1 -0
  4. package/dist/analyzer-cli.js +69 -0
  5. package/dist/analyzer-cli.js.map +1 -0
  6. package/dist/analyzer.d.ts +73 -0
  7. package/dist/analyzer.js +2 -0
  8. package/dist/step-forge.cjs +727 -0
  9. package/dist/step-forge.cjs.map +1 -0
  10. package/dist/step-forge.d.cts +326 -0
  11. package/dist/step-forge.d.ts +326 -0
  12. package/dist/step-forge.js +695 -0
  13. package/dist/step-forge.js.map +1 -0
  14. package/package.json +3 -2
  15. package/.claude/settings.local.json +0 -18
  16. package/.eslintignore +0 -6
  17. package/.eslintrc +0 -18
  18. package/.prettierignore +0 -6
  19. package/.prettierrc +0 -15
  20. package/CLAUDE.md +0 -115
  21. package/cucumber.mjs +0 -39
  22. package/docs/assets/state_deps.gif +0 -0
  23. package/features/TESTING.md +0 -100
  24. package/features/analyzer/analyzer.feature +0 -110
  25. package/features/analyzer/fixtures/ambiguous-step.feature +0 -5
  26. package/features/analyzer/fixtures/missing-given-dep.feature +0 -5
  27. package/features/analyzer/fixtures/missing-multiple-deps.feature +0 -6
  28. package/features/analyzer/fixtures/missing-when-dep.feature +0 -5
  29. package/features/analyzer/fixtures/steps.ts +0 -113
  30. package/features/analyzer/fixtures/undefined-step.feature +0 -5
  31. package/features/analyzer/fixtures/undefined-with-missing-dep.feature +0 -5
  32. package/features/analyzer/fixtures/valid-and-but-keywords.feature +0 -8
  33. package/features/analyzer/fixtures/valid-deps.feature +0 -6
  34. package/features/analyzer/fixtures/valid-no-deps.feature +0 -6
  35. package/features/analyzer/fixtures/valid-optional-deps.feature +0 -6
  36. package/features/analyzer/fixtures/valid-variables.feature +0 -6
  37. package/features/basic.feature +0 -21
  38. package/features/exported.feature +0 -6
  39. package/features/steps/analyzerSteps.ts +0 -67
  40. package/features/steps/commonSteps.ts +0 -93
  41. package/features/steps/exportedSteps.ts +0 -42
  42. package/features/steps/world.ts +0 -29
  43. package/src/analyzer/cli.ts +0 -96
  44. package/src/analyzer/gherkinParser.ts +0 -179
  45. package/src/analyzer/index.ts +0 -89
  46. package/src/analyzer/rules/ambiguousStepRule.ts +0 -36
  47. package/src/analyzer/rules/dependencyRule.ts +0 -71
  48. package/src/analyzer/rules/index.ts +0 -23
  49. package/src/analyzer/rules/undefinedStepRule.ts +0 -31
  50. package/src/analyzer/stepExtractor.ts +0 -432
  51. package/src/analyzer/stepMatcher.ts +0 -85
  52. package/src/analyzer/types.ts +0 -55
  53. package/src/builderTypeUtils.ts +0 -27
  54. package/src/common.ts +0 -138
  55. package/src/given.ts +0 -102
  56. package/src/index.ts +0 -46
  57. package/src/then.ts +0 -128
  58. package/src/utils.ts +0 -74
  59. package/src/when.ts +0 -118
  60. package/src/world.ts +0 -90
  61. package/test/givenCompilationTests.ts +0 -130
  62. package/test/testUtils.ts +0 -30
  63. package/test/thenCompilationTests.ts +0 -207
  64. package/test/whenCompilationTests.ts +0 -157
  65. package/ts-node-esm-register.js +0 -8
  66. package/tsconfig.cucumber.json +0 -14
  67. package/tsconfig.json +0 -29
  68. package/tsdown.config.ts +0 -35
@@ -1,113 +0,0 @@
1
- import { givenBuilder } from "../../../src/given";
2
- import { whenBuilder } from "../../../src/when";
3
- import { thenBuilder } from "../../../src/then";
4
-
5
- type GivenState = {
6
- user: { type: string; token: string };
7
- account: { id: string };
8
- };
9
- type WhenState = {
10
- user: { type: string; token: string; saved: boolean };
11
- result: { success: boolean };
12
- };
13
- type ThenState = Record<string, never>;
14
-
15
- // --- No dependency steps --- //
16
-
17
- givenBuilder<GivenState>()
18
- .statement("I started")
19
- .step(() => ({}))
20
- .register();
21
-
22
- whenBuilder<GivenState, WhenState>()
23
- .statement("I got here")
24
- .step(() => ({}))
25
- .register();
26
-
27
- thenBuilder<GivenState, WhenState, ThenState>()
28
- .statement("everything was good")
29
- .step(() => {})
30
- .register();
31
-
32
- // --- Steps that produce state --- //
33
-
34
- givenBuilder<GivenState>()
35
- .statement("a user")
36
- .step(() => {
37
- return {
38
- user: { type: "person", token: "abc" },
39
- };
40
- })
41
- .register();
42
-
43
- givenBuilder<GivenState>()
44
- .statement((name: string) => `a user named ${name}`)
45
- .step(({ variables: [name] }) => {
46
- return {
47
- user: { type: "person", token: name },
48
- };
49
- })
50
- .register();
51
-
52
- givenBuilder<GivenState>()
53
- .statement("an account")
54
- .step(() => {
55
- return {
56
- account: { id: "acct-1" },
57
- };
58
- })
59
- .register();
60
-
61
- // --- Ambiguous steps (same expression, two definitions) --- //
62
-
63
- givenBuilder<GivenState>()
64
- .statement("the system is ready")
65
- .step(() => ({}))
66
- .register();
67
-
68
- givenBuilder<GivenState>()
69
- .statement("the system is ready")
70
- .step(() => ({}))
71
- .register();
72
-
73
- // --- When steps with dependencies --- //
74
-
75
- whenBuilder<GivenState, WhenState>()
76
- .statement("I save the user")
77
- .dependencies({ given: { user: "required" } })
78
- .step(({ given: { user } }) => {
79
- return {
80
- user: { ...user, saved: true },
81
- };
82
- })
83
- .register();
84
-
85
- whenBuilder<GivenState, WhenState>()
86
- .statement("I delete the account")
87
- .dependencies({ given: { account: "required" } })
88
- .step(() => {
89
- return {
90
- result: { success: true },
91
- };
92
- })
93
- .register();
94
-
95
- // --- Then steps with dependencies --- //
96
-
97
- thenBuilder<GivenState, WhenState, ThenState>()
98
- .statement("there is a user")
99
- .dependencies({ when: { user: "required" } })
100
- .step(() => {})
101
- .register();
102
-
103
- thenBuilder<GivenState, WhenState, ThenState>()
104
- .statement((name: string) => `the user's name is ${name}`)
105
- .dependencies({ when: { user: "required" } })
106
- .step(() => {})
107
- .register();
108
-
109
- thenBuilder<GivenState, WhenState, ThenState>()
110
- .statement("the account might exist")
111
- .dependencies({ given: { account: "optional" } })
112
- .step(() => {})
113
- .register();
@@ -1,5 +0,0 @@
1
- Feature: Undefined step
2
- Scenario: Uses an undefined step
3
- Given a user
4
- When I do something that does not exist
5
- Then everything was good
@@ -1,5 +0,0 @@
1
- Feature: Undefined step with missing dependency
2
- Scenario: Has both undefined and missing dependency
3
- When I save the user
4
- When I do something that does not exist
5
- Then everything was good
@@ -1,8 +0,0 @@
1
- Feature: And/But keyword resolution
2
-
3
- Scenario: And and But resolve correctly
4
- Given a user
5
- And an account
6
- When I save the user
7
- But I delete the account
8
- Then there is a user
@@ -1,6 +0,0 @@
1
- Feature: Satisfied dependencies
2
-
3
- Scenario: Full chain with required deps
4
- Given a user
5
- When I save the user
6
- Then there is a user
@@ -1,6 +0,0 @@
1
- Feature: No dependencies
2
-
3
- Scenario: Steps with no dependencies
4
- Given I started
5
- When I got here
6
- Then everything was good
@@ -1,6 +0,0 @@
1
- Feature: Optional dependencies
2
-
3
- Scenario: Optional dep not produced is fine
4
- Given I started
5
- When I got here
6
- Then the account might exist
@@ -1,6 +0,0 @@
1
- Feature: Variables with dependencies
2
-
3
- Scenario: Variables and satisfied deps
4
- Given a user named "John"
5
- When I save the user
6
- Then the user's name is "John"
@@ -1,21 +0,0 @@
1
- Feature: Steps written in gherkin can be matched to Step Forge steps
2
-
3
- Scenario: Basic feature steps with no dependencies or variables can be run as a cucumber scenario
4
- Given I started
5
- When I got here
6
- Then everything was good
7
-
8
- Scenario: Basic feature steps with dependencies can be run as a cucumber scenario
9
- Given a user
10
- When I save the user
11
- Then there is a user
12
-
13
- Scenario: Basic feature steps with variables can be run as a cucumber scenario
14
- Given a user named "John"
15
- When I save the user
16
- Then there is a user
17
-
18
- Scenario: Basic feature steps with dependencies and variables can be run as a cucumber scenario
19
- Given a user
20
- When I name the user "John"
21
- Then the user's name is "John"
@@ -1,6 +0,0 @@
1
- Feature: Builders can be re-exported for ease of use
2
-
3
- Scenario: Exported steps can be used in cucumber scenarios
4
- Given a bank user
5
- When I deposit "1000" "USD"
6
- Then the balance is "1000"
@@ -1,67 +0,0 @@
1
- import * as path from "node:path";
2
- import { fileURLToPath } from "node:url";
3
- import { Given, When, Then } from "@cucumber/cucumber";
4
- import { expect } from "earl";
5
- import { analyze, Diagnostic } from "../../src/analyzer/index";
6
-
7
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
8
- const fixturesDir = path.resolve(__dirname, "../analyzer/fixtures");
9
-
10
- let fixtureStepFile: string;
11
- let fixtureFeatureFile: string;
12
- let diagnostics: Diagnostic[];
13
-
14
- Given("step definitions from {string}", function (fileName: string) {
15
- fixtureStepFile = path.join(fixturesDir, fileName);
16
- });
17
-
18
- Given("a feature file {string}", function (fileName: string) {
19
- fixtureFeatureFile = path.join(fixturesDir, fileName);
20
- });
21
-
22
- When("I analyze the files", async function () {
23
- diagnostics = await analyze({
24
- stepFiles: [fixtureStepFile],
25
- featureFiles: [fixtureFeatureFile],
26
- });
27
- });
28
-
29
- Then("there should be no errors", function () {
30
- const errors = diagnostics.filter((d) => d.severity === "error");
31
- expect(errors).toHaveLength(0);
32
- });
33
-
34
- Then("there should be {int} error/errors", function (count: number) {
35
- const errors = diagnostics.filter((d) => d.severity === "error");
36
- expect(errors).toHaveLength(count);
37
- });
38
-
39
- Then("an error should mention {string}", function (substring: string) {
40
- const errors = diagnostics.filter((d) => d.severity === "error");
41
- const found = errors.some((e) => e.message.includes(substring));
42
- expect(found).toEqual(true);
43
- });
44
-
45
- Then(
46
- "there is/are {int} error/errors for rule {string}",
47
- function (count: number, rule: string) {
48
- const errors = diagnostics.filter(
49
- (d) => d.severity === "error" && d.rule === rule
50
- );
51
- expect(errors).toHaveLength(count);
52
- }
53
- );
54
-
55
- Then(
56
- "the error on line {int} should span columns {int} to {int}",
57
- function (line: number, startCol: number, endCol: number) {
58
- const errors = diagnostics.filter(
59
- (d) => d.severity === "error" && d.range.startLine === line
60
- );
61
- expect(errors.length).toBeGreaterThanOrEqual(1);
62
- const error = errors[0];
63
- expect(error.range.startColumn).toEqual(startCol);
64
- expect(error.range.endColumn).toEqual(endCol);
65
- expect(error.range.endLine).toEqual(line);
66
- }
67
- );
@@ -1,93 +0,0 @@
1
- import { givenBuilder } from "../../src/given";
2
- import { whenBuilder } from "../../src/when";
3
- import { thenBuilder } from "../../src/then";
4
- import { GivenState, ThenState, WhenState } from "./world";
5
- import { expect } from "earl";
6
-
7
- // --- No dependency no variable steps --- //
8
- givenBuilder<GivenState>()
9
- .statement("I started")
10
- .step(() => ({}))
11
- .register();
12
-
13
- whenBuilder<GivenState, WhenState>()
14
- .statement("I got here")
15
- .step(() => ({}))
16
- .register();
17
-
18
- thenBuilder<GivenState, WhenState, ThenState>()
19
- .statement("everything was good")
20
- .step(() => ({}))
21
- .register();
22
-
23
- // --- Dependency only steps --- //
24
- givenBuilder<GivenState>()
25
- .statement("a user")
26
- .step(() => {
27
- return {
28
- user: {
29
- type: "person",
30
- token: "random",
31
- },
32
- };
33
- })
34
- .register();
35
-
36
- whenBuilder<GivenState, WhenState>()
37
- .statement("I save the user")
38
- .dependencies({ given: { user: "required" } })
39
- .step(({ given: { user } }) => {
40
- return {
41
- user: {
42
- ...user,
43
- saved: true,
44
- },
45
- };
46
- })
47
- .register();
48
-
49
- thenBuilder<GivenState, WhenState, ThenState>()
50
- .statement("there is a user")
51
- .dependencies({ when: { user: "required" } })
52
- .step(({ when: { user } }) => {
53
- expect(user.saved).toBeTruthy();
54
- })
55
- .register();
56
-
57
- // --- Variable only steps --- //
58
- givenBuilder<GivenState>()
59
- .statement((userName: string) => `a user named ${userName}`)
60
- .step(({ variables: [userName] }) => {
61
- return {
62
- user: {
63
- type: "person",
64
- token: userName,
65
- },
66
- };
67
- })
68
- .register();
69
-
70
- // --- More complex steps --- //
71
-
72
- whenBuilder<GivenState, WhenState>()
73
- .statement((userName: string) => `I name the user ${userName}`)
74
- .dependencies({ given: { user: "required" } })
75
- .step(({ given: { user }, variables: [userName] }) => {
76
- return {
77
- user: {
78
- ...user,
79
- token: userName,
80
- saved: true,
81
- },
82
- };
83
- })
84
- .register();
85
-
86
- thenBuilder<GivenState, WhenState, ThenState>()
87
- .statement((userName: string) => `the user's name is ${userName}`)
88
- .dependencies({ when: { user: "required" } })
89
- .step(({ when: { user }, variables: [userName] }) => {
90
- const token = user.token;
91
- expect(token).toEqual(userName);
92
- })
93
- .register();
@@ -1,42 +0,0 @@
1
- import { expect } from "earl";
2
- import { givenBuilder } from "../../src/given";
3
- import { thenBuilder } from "../../src/then";
4
- import { whenBuilder } from "../../src/when";
5
- import { GivenState, ThenState, WhenState } from "./world";
6
-
7
- const Given = givenBuilder<GivenState>().statement;
8
- const When = whenBuilder<GivenState, WhenState>().statement;
9
- const Then = thenBuilder<GivenState, WhenState, ThenState>().statement;
10
-
11
- Given("a bank user")
12
- .step(() => {
13
- return {
14
- user: {
15
- type: "customer",
16
- token: "random",
17
- },
18
- };
19
- })
20
- .register();
21
-
22
- When((amount: string, currency: string) => `I deposit ${amount} ${currency}`)
23
- .dependencies({ given: { user: "required" } })
24
- .step(({ variables: [rawAmount, currency], given: { user } }) => {
25
- const amount = parseFloat(rawAmount);
26
- return {
27
- deposit: {
28
- amount,
29
- currency,
30
- user,
31
- },
32
- };
33
- })
34
- .register();
35
-
36
- Then((amount: string) => `the balance is ${amount}`)
37
- .dependencies({ when: { deposit: "required" } })
38
- .step(({ when: { deposit }, variables: [rawAmount] }) => {
39
- const amount = parseFloat(rawAmount);
40
- expect(deposit.amount).toEqual(amount);
41
- })
42
- .register();
@@ -1,29 +0,0 @@
1
- import { setWorldConstructor } from "@cucumber/cucumber";
2
- import { BasicWorld } from "../../src/world";
3
-
4
- export interface GivenState {
5
- user: {
6
- type: string;
7
- token: string;
8
- };
9
- }
10
-
11
- export interface WhenState {
12
- user: {
13
- type: string;
14
- token: string;
15
- saved: boolean;
16
- };
17
- deposit: {
18
- amount: number;
19
- currency: string;
20
- user: {
21
- type: string;
22
- token: string;
23
- };
24
- };
25
- }
26
-
27
- export interface ThenState {}
28
-
29
- setWorldConstructor(BasicWorld<GivenState, WhenState, ThenState>);
@@ -1,96 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import { analyze } from "./index.js";
4
- import type { AnalyzerConfig, Diagnostic } from "./types.js";
5
-
6
- function parseArgs(args: string[]): AnalyzerConfig {
7
- const config: AnalyzerConfig = {
8
- stepFiles: [],
9
- featureFiles: [],
10
- };
11
-
12
- for (let i = 0; i < args.length; i++) {
13
- const arg = args[i];
14
- const next = args[i + 1];
15
-
16
- if ((arg === "--steps" || arg === "-s") && next) {
17
- config.stepFiles.push(next);
18
- i++;
19
- } else if ((arg === "--features" || arg === "-f") && next) {
20
- config.featureFiles.push(next);
21
- i++;
22
- } else if (arg === "--tsconfig" && next) {
23
- config.tsConfigPath = next;
24
- i++;
25
- } else if (arg === "--help" || arg === "-h") {
26
- printUsage();
27
- process.exit(0);
28
- }
29
- }
30
-
31
- return config;
32
- }
33
-
34
- function printUsage() {
35
- console.log(`Usage: step-forge-analyze [options]
36
-
37
- Options:
38
- -s, --steps <glob> Glob pattern for step definition files (repeatable)
39
- -f, --features <glob> Glob pattern for feature files (repeatable)
40
- --tsconfig <path> Path to tsconfig.json (default: auto-detect)
41
- -h, --help Show this help message
42
-
43
- Example:
44
- step-forge-analyze --steps "features/steps/**/*.ts" --features "features/**/*.feature"
45
- `);
46
- }
47
-
48
- function formatDiagnostic(diag: Diagnostic): string {
49
- const location = `${diag.file}:${diag.range.startLine}:${diag.range.startColumn}`;
50
- return `${location} - ${diag.severity}: ${diag.message}`;
51
- }
52
-
53
- async function main() {
54
- const args = process.argv.slice(2);
55
- const config = parseArgs(args);
56
-
57
- if (config.stepFiles.length === 0 || config.featureFiles.length === 0) {
58
- console.error(
59
- "Error: Both --steps and --features are required.\n"
60
- );
61
- printUsage();
62
- process.exit(1);
63
- }
64
-
65
- const diagnostics = await analyze(config);
66
-
67
- if (diagnostics.length === 0) {
68
- console.log("No issues found.");
69
- process.exit(0);
70
- }
71
-
72
- const errors = diagnostics.filter((d) => d.severity === "error");
73
- const warnings = diagnostics.filter((d) => d.severity === "warning");
74
-
75
- for (const diag of diagnostics) {
76
- console.log(formatDiagnostic(diag));
77
- }
78
-
79
- console.log(
80
- `\nFound ${errors.length} error(s) and ${warnings.length} warning(s).`
81
- );
82
- process.exit(errors.length > 0 ? 1 : 0);
83
- }
84
-
85
- // Only run when invoked directly (not when imported by Cucumber or other tools)
86
- const isDirectRun =
87
- import.meta.url === `file://${process.argv[1]}` ||
88
- process.argv[1]?.endsWith("analyzer-cli.js") ||
89
- process.argv[1]?.endsWith("analyzer-cli.ts");
90
-
91
- if (isDirectRun) {
92
- main().catch((err) => {
93
- console.error("Analyzer failed:", err);
94
- process.exit(1);
95
- });
96
- }