spec_forge 0.6.0 → 0.7.0

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 (83) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +112 -2
  3. data/README.md +133 -8
  4. data/flake.lock +3 -3
  5. data/flake.nix +3 -3
  6. data/lib/spec_forge/attribute/factory.rb +1 -1
  7. data/lib/spec_forge/callbacks.rb +9 -0
  8. data/lib/spec_forge/cli/docs/generate.rb +72 -0
  9. data/lib/spec_forge/cli/docs.rb +92 -0
  10. data/lib/spec_forge/cli/init.rb +39 -7
  11. data/lib/spec_forge/cli/new.rb +13 -3
  12. data/lib/spec_forge/cli/run.rb +12 -4
  13. data/lib/spec_forge/cli/serve.rb +155 -0
  14. data/lib/spec_forge/cli.rb +14 -6
  15. data/lib/spec_forge/configuration.rb +2 -2
  16. data/lib/spec_forge/context/store.rb +23 -40
  17. data/lib/spec_forge/core_ext/array.rb +27 -0
  18. data/lib/spec_forge/documentation/builder.rb +383 -0
  19. data/lib/spec_forge/documentation/document/operation.rb +47 -0
  20. data/lib/spec_forge/documentation/document/parameter.rb +22 -0
  21. data/lib/spec_forge/documentation/document/request_body.rb +24 -0
  22. data/lib/spec_forge/documentation/document/response.rb +39 -0
  23. data/lib/spec_forge/documentation/document/response_body.rb +27 -0
  24. data/lib/spec_forge/documentation/document.rb +48 -0
  25. data/lib/spec_forge/documentation/generators/base.rb +81 -0
  26. data/lib/spec_forge/documentation/generators/openapi/base.rb +100 -0
  27. data/lib/spec_forge/documentation/generators/openapi/error_formatter.rb +149 -0
  28. data/lib/spec_forge/documentation/generators/openapi/v3_0.rb +65 -0
  29. data/lib/spec_forge/documentation/generators/openapi.rb +59 -0
  30. data/lib/spec_forge/documentation/generators.rb +17 -0
  31. data/lib/spec_forge/documentation/loader/cache.rb +138 -0
  32. data/lib/spec_forge/documentation/loader.rb +159 -0
  33. data/lib/spec_forge/documentation/openapi/base.rb +33 -0
  34. data/lib/spec_forge/documentation/openapi/v3_0/example.rb +44 -0
  35. data/lib/spec_forge/documentation/openapi/v3_0/media_type.rb +42 -0
  36. data/lib/spec_forge/documentation/openapi/v3_0/operation.rb +175 -0
  37. data/lib/spec_forge/documentation/openapi/v3_0/response.rb +65 -0
  38. data/lib/spec_forge/documentation/openapi/v3_0/schema.rb +80 -0
  39. data/lib/spec_forge/documentation/openapi/v3_0/tag.rb +71 -0
  40. data/lib/spec_forge/documentation/openapi.rb +23 -0
  41. data/lib/spec_forge/documentation.rb +27 -0
  42. data/lib/spec_forge/error.rb +17 -0
  43. data/lib/spec_forge/factory.rb +2 -2
  44. data/lib/spec_forge/filter.rb +3 -4
  45. data/lib/spec_forge/forge.rb +5 -4
  46. data/lib/spec_forge/http/backend.rb +2 -0
  47. data/lib/spec_forge/http/request.rb +14 -3
  48. data/lib/spec_forge/loader.rb +14 -24
  49. data/lib/spec_forge/normalizer/default.rb +51 -0
  50. data/lib/spec_forge/normalizer/definition.rb +248 -0
  51. data/lib/spec_forge/normalizer/validators.rb +99 -0
  52. data/lib/spec_forge/normalizer.rb +356 -199
  53. data/lib/spec_forge/normalizers/_shared.yml +74 -0
  54. data/lib/spec_forge/normalizers/configuration.yml +23 -0
  55. data/lib/spec_forge/normalizers/constraint.yml +8 -0
  56. data/lib/spec_forge/normalizers/expectation.yml +47 -0
  57. data/lib/spec_forge/normalizers/factory.yml +12 -0
  58. data/lib/spec_forge/normalizers/factory_reference.yml +15 -0
  59. data/lib/spec_forge/normalizers/global_context.yml +28 -0
  60. data/lib/spec_forge/normalizers/spec.yml +50 -0
  61. data/lib/spec_forge/runner/adapter.rb +183 -0
  62. data/lib/spec_forge/runner/debug_proxy.rb +3 -3
  63. data/lib/spec_forge/runner/state.rb +4 -5
  64. data/lib/spec_forge/runner.rb +40 -124
  65. data/lib/spec_forge/spec/expectation/constraint.rb +13 -5
  66. data/lib/spec_forge/spec/expectation.rb +7 -3
  67. data/lib/spec_forge/spec.rb +13 -58
  68. data/lib/spec_forge/version.rb +1 -1
  69. data/lib/spec_forge.rb +30 -23
  70. data/lib/templates/openapi.yml.tt +22 -0
  71. data/lib/templates/redoc.html.tt +28 -0
  72. data/lib/templates/swagger.html.tt +59 -0
  73. metadata +92 -14
  74. data/lib/spec_forge/normalizer/configuration.rb +0 -90
  75. data/lib/spec_forge/normalizer/constraint.rb +0 -60
  76. data/lib/spec_forge/normalizer/expectation.rb +0 -105
  77. data/lib/spec_forge/normalizer/factory.rb +0 -78
  78. data/lib/spec_forge/normalizer/factory_reference.rb +0 -85
  79. data/lib/spec_forge/normalizer/global_context.rb +0 -88
  80. data/lib/spec_forge/normalizer/spec.rb +0 -97
  81. /data/lib/templates/{forge_helper.tt → forge_helper.rb.tt} +0 -0
  82. /data/lib/templates/{new_factory.tt → new_factory.yml.tt} +0 -0
  83. /data/lib/templates/{new_spec.tt → new_spec.yml.tt} +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6b2d5790d4797a63b8fbad4dd296b9c9beb657b30a98bda5fab8ca7c635abd5a
4
- data.tar.gz: b22a8f664dd676846e703f5312383c9c9b1b9cabab52b2cc3c8a8d28830490c0
3
+ metadata.gz: 7f8c38cb9ff50cb02ee74171a4d571ac411807b1dcce43edab0f28a757269b2f
4
+ data.tar.gz: 1595911a7a84d7712ddcc5bf0e2a08c0c30afce995ac02bb1372c1ae29ca7db0
5
5
  SHA512:
6
- metadata.gz: 858e7dfd4546bc34e7dd8f899d99b5cf03d26cfbe9042f238de44566e2981a82bcd06803a0319f6d937d7c509dc3c02e8a962415c8e6fbb8e3c515c693c5e176
7
- data.tar.gz: 3b246fda2d4bbbc5ce12fa0f683e27e1cb0e2950fe3b7c03b978f6d88662d1b65b0037b00931fe598415ee3d3c8477f7f76fd3f523547e061f86ac000e617d52
6
+ metadata.gz: 731ff8f0cc847010f38caab823c7d30c0d3d7083d4ea53de6ac46c19929c2c31ce698db787303c706ae6ee517dea396ef5ceb220732dd172050967404dd102af
7
+ data.tar.gz: 1a24dd8f84528427e30d105c4e358ca226b699e79a68a9ac2eb5385a974abb2acb27f8c8a3751e8c69a70001ef8d2ef3938ab06f9c81f1f041325b1b038923e1
data/CHANGELOG.md CHANGED
@@ -15,7 +15,115 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
15
15
  ### Removed
16
16
  -->
17
17
 
18
- ## [Unreleased]
18
+ ## [0.7.0] - 12025-06-22
19
+
20
+ ### Added
21
+
22
+ #### 🚀 Documentation-First Architecture
23
+ **The Big Picture**: SpecForge now generates OpenAPI documentation from your tests automatically!
24
+
25
+ - **Primary Documentation Workflow**: New `docs` command (now the default!) generates OpenAPI specs from test execution
26
+ - Smart caching system with `--fresh` flag for forced regeneration
27
+ - Multiple output formats: YAML (default) or JSON via `--format`
28
+ - Custom output paths with `--output` option
29
+ - Built-in OpenAPI 3.0.4 validation with detailed, helpful error messages
30
+ - Optional validation skip with `--skip-validation` for faster iterations
31
+
32
+ - **Live Documentation Server**: `spec_forge serve` command for immediate feedback
33
+ - Local web server with Swagger UI (default) or Redoc (`--ui redoc`)
34
+ - Configurable port with `--port` (defaults to 8080)
35
+ - Auto-generated HTML templates for both UI options
36
+ - Perfect for development and API review workflows
37
+
38
+ - **Flexible Configuration System**:
39
+ - Directory-based config: `config/components/`, `config/paths/`, etc.
40
+ - Template-based initialization with sensible defaults
41
+ - Enhanced YAML merging with `$ref` support
42
+ - Full OpenAPI customization through configuration files
43
+
44
+ #### 🧪 Enhanced Testing Capabilities
45
+
46
+ - **HTTP Header Testing**: Comprehensive header validation
47
+ ```yaml
48
+ headers:
49
+ Content-Type: "application/json"
50
+ X-Request-ID: /^[0-9a-f-]{36}$/
51
+ Cache-Control:
52
+ matcher.and:
53
+ - matcher.include: "max-age="
54
+ - matcher.include: "private"
55
+ ```
56
+
57
+ - **Flexible Store System**: Store anything, access everything
58
+ - OpenStruct-based entries for maximum flexibility
59
+ - Custom data via callbacks (config, metadata, computed values)
60
+ - Same familiar `store.id.attribute` syntax
61
+ - Perfect for complex test scenarios and feature flags
62
+
63
+ - **Documentation Control**: Fine-grained control over what gets documented
64
+ - New `documentation: true/false` attribute for specs and expectations
65
+ - Exclude test-only scenarios from API docs while keeping functionality
66
+
67
+ #### ⚙️ Architecture Improvements
68
+
69
+ - **YAML-Driven Normalizers**: Configuration over code
70
+ - Structure definitions in `lib/spec_forge/normalizers/*.yml`
71
+ - Powerful `reference:` system for reusable components
72
+ - Wildcard support (`*`) for catch-all schemas
73
+ - Centralized validation logic in dedicated module
74
+
75
+ - **Enhanced CLI Experience**:
76
+ - Improved `init` command with `--skip-openapi` and `--skip-factories` flags
77
+ - Better help text and examples throughout
78
+ - Clearer error messages with actionable context
79
+
80
+ - **Developer Utilities**:
81
+ - `Array#to_merged_h` for cleaner hash merging
82
+ - Unified `.normalize!(input, using:)` API across normalizers
83
+ - Separated test preparation (`Runner.prepare`) from execution
84
+
85
+ ### Changed
86
+
87
+ #### 🎯 User Experience Overhaul
88
+
89
+ - **New Default Behavior**: `spec_forge` without arguments now shows help instead of running tests
90
+ - **Breaking Change**: Use `spec_forge docs` for documentation or `spec_forge run` for test-only execution
91
+ - Safer default that guides users to the right command for their needs
92
+
93
+ - **Streamlined Commands**:
94
+ - Better command organization and help text
95
+ - Consistent flag naming across commands
96
+ - Enhanced error handling with helpful suggestions
97
+
98
+ #### 🏗️ Internal Refactoring
99
+
100
+ - **Normalizer Architecture**: YAML-based instead of class-heavy approach
101
+ - Consolidated shared definitions in `_shared.yml`
102
+ - Easier maintenance and extension
103
+ - Better error context with attribute path tracking
104
+
105
+ - **Test Execution Pipeline**:
106
+ - Clean separation between test preparation and execution
107
+ - Enhanced RSpec adapter pattern
108
+ - Better reusability for documentation generation
109
+
110
+ - **HTTP & Store Improvements**:
111
+ - Automatic header value string conversion
112
+ - Simplified store entry structure with OpenStruct flexibility
113
+ - Enhanced request/response handling
114
+
115
+ ### Removed
116
+
117
+ - **Legacy Architecture**: Individual normalizer class files (replaced with YAML config)
118
+
119
+ ---
120
+
121
+ **Migration Notes**:
122
+ - Update any scripts using bare `spec_forge` - now shows help instead of running tests
123
+ - Use `spec_forge docs` for documentation generation or `spec_forge run` for testing
124
+ - Store access patterns remain the same, but internal structure is more flexible
125
+
126
+ ## [0.6.0] - 12025-03-25
19
127
 
20
128
  ### Added
21
129
 
@@ -231,7 +339,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
231
339
 
232
340
  - Initial commit
233
341
 
234
- [unreleased]: https://github.com/itsthedevman/spec_forge/compare/v0.5.0...HEAD
342
+ [unreleased]: https://github.com/itsthedevman/spec_forge/compare/v0.7.0...HEAD
343
+ [0.7.0]: https://github.com/itsthedevman/spec_forge/compare/v0.6.0...v0.7.0
344
+ [0.6.0]: https://github.com/itsthedevman/spec_forge/compare/v0.5.0...v0.6.0
235
345
  [0.5.0]: https://github.com/itsthedevman/spec_forge/compare/v0.4.0...v0.5.0
236
346
  [0.4.0]: https://github.com/itsthedevman/spec_forge/compare/v0.3.2...v0.4.0
237
347
  [0.3.2]: https://github.com/itsthedevman/spec_forge/compare/v0.3.0...v0.3.2
data/README.md CHANGED
@@ -6,7 +6,7 @@
6
6
 
7
7
  > Note: The code in this repository represents the latest development version with new features and improvements that are being prepared for future releases. For the current stable version, check out [v0.6.0](https://github.com/itsthedevman/spec_forge/releases/tag/v0.6.0) on GitHub releases.
8
8
 
9
- Write API tests in YAML that read like documentation:
9
+ Write API tests in YAML that read like documentation and generate OpenAPI specifications:
10
10
 
11
11
  ```yaml
12
12
  show_user:
@@ -25,22 +25,39 @@ show_user:
25
25
  matcher.and:
26
26
  - kind_of.string
27
27
  - /@/
28
+ headers:
29
+ Content-Type: "application/json"
30
+ X-Request-ID: /^[0-9a-f-]{36}$/
28
31
  ```
29
32
 
30
- That's a complete test. No Ruby code, no configuration files, no HTTP client setup - just a clear description of what you're testing. Under the hood, you get all the power of RSpec's matchers, Faker's data generation, and FactoryBot's test objects.
33
+ That's a complete test that validates your API and creates OpenAPI documentation. No Ruby code, no configuration files, no HTTP client setup - just clear, executable specifications.
31
34
 
32
35
  ## Why SpecForge?
33
36
 
34
- 1. **Living Documentation**: Your tests should serve as clear, readable documentation of your API's behavior.
35
- 2. **Reduce Boilerplate**: Write tests without repetitive setup code and HTTP configuration.
36
- 3. **Quick Setup**: Start testing APIs in minutes instead of spending hours on test infrastructure.
37
- 4. **Gradual Adoption**: Use alongside your existing test suite, introducing it incrementally where it makes sense.
38
- 5. **Developer & QA Collaboration**: Create a testing format that everyone can understand and maintain, regardless of Ruby expertise.
37
+ **For Testing:**
38
+
39
+ - **Reduce Boilerplate**: Write tests without repetitive setup code and HTTP configuration
40
+ - **Quick Setup**: Start testing APIs in minutes instead of spending hours on test infrastructure
41
+ - **Clear Syntax**: Tests that everyone can read and understand, regardless of Ruby expertise
42
+
43
+ **For Documentation:**
44
+
45
+ - **OpenAPI Generation**: Generate OpenAPI specifications from your test structure, with full customization through configuration files
46
+ - **Living Documentation**: Your tests ensure the documentation always matches your actual API behavior
47
+ - **Professional Output**: View your API docs in Swagger UI or Redoc with minimal setup
48
+
49
+ **For Teams:**
50
+
51
+ - **Developer & QA Collaboration**: Create specifications that both developers and QA can maintain
52
+ - **Gradual Adoption**: Use alongside your existing test suite, introducing it incrementally where it makes sense
39
53
 
40
54
  ## Key Features
41
55
 
56
+ - **Automatic Documentation Generation**: Transform tests into OpenAPI specifications with customizable configuration
57
+ - **Live Documentation Server**: Local development server for viewing generated documentation
42
58
  - **YAML-Based Tests**: Write clear, declarative tests that read like documentation
43
59
  - **RSpec Integration**: Leverage all the power of RSpec matchers and expectations
60
+ - **Header Testing**: Comprehensive HTTP header validation with compound matchers
44
61
  - **FactoryBot Integration**: Generate test data with FactoryBot integration
45
62
  - **Faker Integration**: Create realistic test data with Faker
46
63
  - **Variable System**: Define and reference variables for dynamic test data
@@ -49,6 +66,23 @@ That's a complete test. No Ruby code, no configuration files, no HTTP client set
49
66
  - **Global Variables**: Define shared configuration at the file level
50
67
  - **Callback System**: Hook into the test lifecycle using Ruby for setup, teardown, and much more!
51
68
 
69
+ ## Quick Start
70
+
71
+ Get started with SpecForge in 3 commands:
72
+
73
+ ```bash
74
+ # 1. Initialize SpecForge
75
+ spec_forge init
76
+
77
+ # 2. Create your first test
78
+ spec_forge new spec users
79
+
80
+ # 3. View your documentation
81
+ spec_forge serve
82
+ ```
83
+
84
+ Then visit `http://localhost:8080` to see your API documentation!
85
+
52
86
  ## When Not to Use SpecForge
53
87
 
54
88
  Consider alternatives when you need:
@@ -92,12 +126,103 @@ Create your first test:
92
126
  spec_forge new spec users
93
127
  ```
94
128
 
95
- Run your tests:
129
+ Generate documentation (default command):
130
+
131
+ ```bash
132
+ spec_forge
133
+ ```
134
+
135
+ Or start the live documentation server:
136
+
137
+ ```bash
138
+ spec_forge serve
139
+ ```
140
+
141
+ Run tests only (no documentation):
96
142
 
97
143
  ```bash
98
144
  spec_forge run
99
145
  ```
100
146
 
147
+ ## Documentation Workflow
148
+
149
+ SpecForge provides multiple ways to work with your API documentation:
150
+
151
+ ```bash
152
+ # Generate OpenAPI specifications
153
+ spec_forge docs # Smart caching
154
+ spec_forge docs --fresh # Force regeneration
155
+ spec_forge docs --format json # Output as JSON instead of YAML
156
+
157
+ # View documentation in browser
158
+ spec_forge serve # Generate if needed + serve
159
+ spec_forge serve --fresh # Force regeneration + serve
160
+ spec_forge serve --ui redoc # Use Redoc instead
161
+ spec_forge serve --port 3001 # Custom port
162
+
163
+ # Traditional testing
164
+ spec_forge run # Pure testing mode
165
+ spec_forge run users:show_user # Run specific tests
166
+ ```
167
+
168
+ ## Example: Complete User API
169
+
170
+ ```yaml
171
+ # spec_forge/specs/users.yml
172
+ global:
173
+ variables:
174
+ admin_role: "admin"
175
+
176
+ list_users:
177
+ path: /users
178
+ expectations:
179
+ - expect:
180
+ status: 200
181
+ headers:
182
+ Content-Type: "application/json"
183
+ json:
184
+ users:
185
+ matcher.have_size:
186
+ be.greater_than: 0
187
+
188
+ create_user:
189
+ path: /users
190
+ method: POST
191
+ variables:
192
+ username: faker.internet.username
193
+ email: faker.internet.email
194
+ body:
195
+ name: variables.username
196
+ email: variables.email
197
+ role: global.variables.admin_role
198
+ store_as: new_user
199
+ expectations:
200
+ - expect:
201
+ status: 201
202
+ headers:
203
+ Location: /\/users\/\d+/
204
+ json:
205
+ id: kind_of.integer
206
+ name: variables.username
207
+ email: variables.email
208
+ role: global.variables.admin_role
209
+
210
+ show_user:
211
+ path: /users/{id}
212
+ query:
213
+ id: store.new_user.body.id
214
+ expectations:
215
+ - expect:
216
+ status: 200
217
+ json:
218
+ id: store.new_user.body.id
219
+ name: store.new_user.body.name
220
+ email: store.new_user.body.email
221
+ role: global.variables.admin_role
222
+ ```
223
+
224
+ This automatically generates a complete OpenAPI specification with all endpoints, request/response schemas, and examples!
225
+
101
226
  ## Documentation
102
227
 
103
228
  For comprehensive documentation, visit the [SpecForge Wiki](https://github.com/itsthedevman/spec_forge/wiki) which includes:
data/flake.lock CHANGED
@@ -20,11 +20,11 @@
20
20
  },
21
21
  "nixpkgs": {
22
22
  "locked": {
23
- "lastModified": 1742422364,
24
- "narHash": "sha256-mNqIplmEohk5jRkqYqG19GA8MbQ/D4gQSK0Mu4LvfRQ=",
23
+ "lastModified": 1750365781,
24
+ "narHash": "sha256-XE/lFNhz5lsriMm/yjXkvSZz5DfvKJLUjsS6pP8EC50=",
25
25
  "owner": "NixOS",
26
26
  "repo": "nixpkgs",
27
- "rev": "a84ebe20c6bc2ecbcfb000a50776219f48d134cc",
27
+ "rev": "08f22084e6085d19bcfb4be30d1ca76ecb96fe54",
28
28
  "type": "github"
29
29
  },
30
30
  "original": {
data/flake.nix CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- description = "Ruby 3.4.2 development environment";
2
+ description = "Ruby 3.2 development environment";
3
3
 
4
4
  inputs = {
5
5
  nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
@@ -20,8 +20,8 @@
20
20
  {
21
21
  devShells.default = pkgs.mkShell {
22
22
  buildInputs = with pkgs; [
23
- (ruby_3_4.override {
24
- jemallocSupport = true;
23
+ (ruby_3_2.override {
24
+ jemallocSupport = false;
25
25
  docSupport = false;
26
26
  })
27
27
 
@@ -80,7 +80,7 @@ module SpecForge
80
80
  super
81
81
 
82
82
  # Check the arguments before preparing them
83
- arguments[:keyword] = Normalizer.normalize_factory_reference!(arguments[:keyword])
83
+ arguments[:keyword] = Normalizer.normalize!(arguments[:keyword], using: :factory_reference)
84
84
 
85
85
  prepare_arguments!
86
86
  end
@@ -36,6 +36,15 @@ module SpecForge
36
36
  instance[name.to_s] = block
37
37
  end
38
38
 
39
+ #
40
+ # Deregisters a callback
41
+ #
42
+ # @param name [String, Symbol] The name of the callback
43
+ #
44
+ def deregister(name)
45
+ instance.delete(name.to_s)
46
+ end
47
+
39
48
  #
40
49
  # Checks if a callback is registered for the given event
41
50
  #
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SpecForge
4
+ class CLI
5
+ class Docs < Command
6
+ #
7
+ # Shared functionality for generating OpenAPI documentation
8
+ #
9
+ # This module contains the core logic for running tests, extracting endpoint
10
+ # data, and generating OpenAPI specifications. It's used by both the Docs
11
+ # and Serve commands to avoid duplication.
12
+ #
13
+ module Generate
14
+ #
15
+ # Generates OpenAPI documentation and writes it to disk
16
+ #
17
+ # Runs the documentation generation pipeline: executes tests, extracts
18
+ # endpoint data, generates OpenAPI spec, validates it, and writes the
19
+ # output file in the specified format.
20
+ #
21
+ # @return [Pathname] The path to the generated documentation file
22
+ #
23
+ def generate_documentation
24
+ generator = Documentation::Generators::OpenAPI["3.0"]
25
+ output = generator.generate(use_cache: !options.fresh)
26
+
27
+ generator.validate!(output) unless options.skip_validation
28
+
29
+ # Determine output format and path
30
+ file_format = determine_file_format
31
+ file_path = determine_output_path(file_format)
32
+
33
+ content =
34
+ if file_format == "json"
35
+ JSON.pretty_generate(output)
36
+ else
37
+ output.to_yaml(stringify_names: true)
38
+ end
39
+
40
+ ::File.write(file_path, content)
41
+
42
+ file_path
43
+ end
44
+
45
+ private
46
+
47
+ def determine_file_format
48
+ file_format = options.format&.downcase || "yml"
49
+ validate_format!(file_format)
50
+
51
+ file_format
52
+ end
53
+
54
+ def validate_format!(format)
55
+ return if VALID_FORMATS.include?(format)
56
+
57
+ raise ArgumentError,
58
+ "Invalid format #{format.in_quotes}. Valid formats: #{VALID_FORMATS.join_map(", ", &:in_quotes)}"
59
+ end
60
+
61
+ def determine_output_path(format)
62
+ if options.output
63
+ Pathname.new(options.output)
64
+ else
65
+ extension = (format == "json") ? "json" : "yml"
66
+ SpecForge.openapi_path.join("generated", "openapi.#{extension}")
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "docs/generate"
4
+
5
+ module SpecForge
6
+ class CLI
7
+ #
8
+ # Command for generating OpenAPI documentation from SpecForge tests
9
+ #
10
+ # Runs tests and extracts endpoint data to create OpenAPI specifications.
11
+ # Uses intelligent caching to avoid unnecessary test re-execution when
12
+ # specs haven't changed.
13
+ #
14
+ # @example Generate documentation
15
+ # spec_forge docs
16
+ #
17
+ # @example Generate with fresh test run
18
+ # spec_forge docs --fresh
19
+ #
20
+ class Docs < Command
21
+ include Docs::Generate
22
+
23
+ #
24
+ # Valid file formats for documentation output
25
+ #
26
+ # Supported formats include YAML variants (yml, yaml) and JSON.
27
+ # Used for validation when users specify the --format option.
28
+ #
29
+ # @api private
30
+ #
31
+ VALID_FORMATS = %w[yml yaml json].freeze
32
+
33
+ command_name "docs"
34
+ syntax "docs"
35
+ summary "Generate OpenAPI documentation from test results"
36
+
37
+ description <<~DESC
38
+ Generate OpenAPI documentation from test results.
39
+
40
+ Uses caching to avoid re-running tests unless specs
41
+ have changed. Output format can be YAML or JSON.
42
+ DESC
43
+
44
+ example "docs",
45
+ "Generates OpenAPI specifications from your tests using smart caching"
46
+
47
+ example "docs --fresh",
48
+ "Forces test re-execution and regenerates OpenAPI specs ignoring cache"
49
+
50
+ example "docs --format json",
51
+ "Generates OpenAPI specifications in JSON format instead of YAML"
52
+
53
+ example "docs --output ./build/api.yml",
54
+ "Generates OpenAPI specs to a custom file path"
55
+
56
+ example "docs --skip-validation",
57
+ "Generates documentation without validating the OpenAPI specification"
58
+
59
+ option "--fresh", "Re-run all tests ignoring cache"
60
+ option "--format=FORMAT", "Output format: yml/yaml or json (default: yml)"
61
+ option "--output=PATH", "Full file path for generated documentation"
62
+ option "--skip-validation", "Skip OpenAPI specification validation during generation"
63
+
64
+ #
65
+ # Generates OpenAPI documentation from tests
66
+ #
67
+ # Runs all SpecForge tests and creates OpenAPI specifications from the
68
+ # successful test results. This is the main entry point for the docs workflow.
69
+ #
70
+ # @return [void]
71
+ #
72
+ def call
73
+ # spec_forge/openapi/generated
74
+ generated_path = SpecForge.openapi_path.join("generated")
75
+ actions.empty_directory(generated_path, verbose: false)
76
+ actions.empty_directory(generated_path.join(".cache"), verbose: false)
77
+
78
+ file_path = generate_documentation
79
+
80
+ puts <<~STRING
81
+
82
+ ========================================
83
+ 🎉 Success!
84
+ ========================================
85
+
86
+ Your OpenAPI specification is valid and ready to use.
87
+ Output written to: #{file_path.relative_path_from(SpecForge.forge_path)}
88
+ STRING
89
+ end
90
+ end
91
+ end
92
+ end
@@ -11,21 +11,53 @@ module SpecForge
11
11
  class Init < Command
12
12
  command_name "init"
13
13
  syntax "init"
14
- summary "Initializes directory structure and configuration files"
14
+ summary "Set up your SpecForge project (creates folders and config files)"
15
+
16
+ description <<~DESC
17
+ Creates the SpecForge project structure.
18
+
19
+ Sets up:
20
+ • spec_forge/specs/ for test files
21
+ • spec_forge/factories/ for test data (optional)
22
+ • spec_forge/openapi/ for documentation config (optional)
23
+ • forge_helper.rb for configuration
24
+ DESC
25
+
26
+ option "--skip-openapi", "Skip generating the \"openapi\" directory"
27
+ option "--skip-factories", "Skip generating the \"factories\" directory"
15
28
 
16
29
  #
17
30
  # Creates the "spec_forge", "spec_forge/factories", and "spec_forge/specs" directories
18
31
  # Also creates the "spec_forge.rb" initialization file
19
32
  #
20
33
  def call
34
+ initialize_forge
35
+ initialize_openapi unless options.skip_openapi
36
+ end
37
+
38
+ private
39
+
40
+ def initialize_forge
21
41
  base_path = SpecForge.forge_path
22
- actions.empty_directory "#{base_path}/factories"
23
- actions.empty_directory "#{base_path}/specs"
42
+ actions.empty_directory(base_path.join("specs"))
43
+ actions.empty_directory(base_path.join("factories")) unless options.skip_factories
44
+ actions.template("forge_helper.rb.tt", base_path.join("forge_helper.rb"))
45
+ end
46
+
47
+ def initialize_openapi
48
+ # spec_forge/openapi
49
+ openapi_path = SpecForge.openapi_path
50
+ actions.empty_directory(openapi_path)
51
+
52
+ # spec_forge/openapi/config
53
+ config_path = openapi_path.join("config")
54
+
55
+ actions.empty_directory(config_path)
56
+ actions.empty_directory(config_path.join("paths")) # openapi/config/paths
57
+ actions.empty_directory(config_path.join("components")) # openapi/config/components
24
58
 
25
- actions.template(
26
- "forge_helper.tt",
27
- SpecForge.root.join(base_path, "forge_helper.rb")
28
- )
59
+ # openapi/config/openapi.yml
60
+ actions.template("openapi.yml.tt", config_path.join("openapi.yml"))
29
61
  end
30
62
  end
31
63
  end
@@ -13,7 +13,17 @@ module SpecForge
13
13
  #
14
14
  class New < Command
15
15
  command_name "new"
16
- summary "Create a new spec or factory"
16
+ summary "Create new test specs or data factories"
17
+
18
+ description <<~DESC
19
+ Generate new files from templates.
20
+
21
+ Types:
22
+ • spec - Creates YAML test files with common patterns
23
+ • factory - Creates FactoryBot factories for test data
24
+
25
+ Files are created in the appropriate spec_forge/ subdirectory.
26
+ DESC
17
27
 
18
28
  syntax "new <type> <name>"
19
29
 
@@ -51,7 +61,7 @@ module SpecForge
51
61
 
52
62
  def create_new_spec(name)
53
63
  actions.template(
54
- "new_spec.tt",
64
+ "new_spec.yml.tt",
55
65
  SpecForge.forge_path.join("specs", "#{name}.yml"),
56
66
  context: Proxy.new(name).call
57
67
  )
@@ -59,7 +69,7 @@ module SpecForge
59
69
 
60
70
  def create_new_factory(name)
61
71
  actions.template(
62
- "new_factory.tt",
72
+ "new_factory.yml.tt",
63
73
  SpecForge.forge_path.join("factories", "#{name}.yml"),
64
74
  context: Proxy.new(name).call
65
75
  )
@@ -21,8 +21,18 @@ module SpecForge
21
21
  command_name "run"
22
22
  syntax "run [target]"
23
23
 
24
- summary "Runs specs loaded from spec_forge/specs/"
25
- description "Runs specs loaded from spec_forge/specs/. The optional target argument allows running specific files, specs, or expectations."
24
+ summary "Execute your API tests with smart filtering options"
25
+
26
+ description <<~DESC
27
+ Execute API tests with filtering options.
28
+
29
+ Target formats:
30
+ • file_name - Run all specs in a file
31
+ • file:spec - Run specific spec
32
+ • file:spec:"expectation" - Run individual expectation
33
+
34
+ Uses RSpec for execution with detailed error reporting.
35
+ DESC
26
36
 
27
37
  example "spec_forge run",
28
38
  "Run all specs in spec_forge/specs/"
@@ -39,8 +49,6 @@ module SpecForge
39
49
  example "spec_forge run users:create_user:\"POST /users - Create Admin\"",
40
50
  "Run the specific expectation named \"Create Admin\""
41
51
 
42
- # option "-n", "--no-docs", "Do not generate OpenAPI documentation on completion"
43
-
44
52
  #
45
53
  # Loads and runs all specs, or a subset of specs based on the provided arguments
46
54
  #