spree_dev_tools 1.0.0 → 1.0.1
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.
- checksums.yaml +4 -4
- data/README.md +36 -0
- data/lib/spree_dev_tools/generators/openapi/openapi_generator.rb +206 -0
- data/lib/spree_dev_tools/openapi.rb +69 -0
- data/lib/spree_dev_tools/rspec/openapi.rb +23 -0
- data/lib/spree_dev_tools/version.rb +1 -1
- data/lib/spree_dev_tools.rb +1 -0
- data/spree_dev_tools.gemspec +1 -0
- metadata +18 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a9f59a38b25829ca79a59e8bbc0268da32d59a249fd5f2539d7f09b93fbf82be
|
|
4
|
+
data.tar.gz: 45c0b758b4bce3ab91e4cf0da4c2835a69bfee006e9b59e93fa73039d2e1215d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a0156248f9e0e46a45ee64a6feca697a11acf39ef7e2e852cf0e56ebe0f6df559862bc82efe7b1da06465054c9d2849c2b4cb4685297a13407f2c452f47d33ca
|
|
7
|
+
data.tar.gz: 94c2522857aef5457f62be18b38b05ac1993b5d23e782902ee60ae10dfb973aa3765a797082e32a89e1bb2556edfa53a0d384b1972042170539109a3aa1c5a89
|
data/README.md
CHANGED
|
@@ -22,6 +22,42 @@ And finally run the generator:
|
|
|
22
22
|
bundle exec rails g spree_dev_tools:install
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
+
## Documenting your Admin API endpoints
|
|
26
|
+
|
|
27
|
+
Spree builds an agent tool (MCP server and dashboard assistant) for every Admin API action it finds documented in an OpenAPI document. Core documents its own endpoints. Your application, and each extension, documents its own in an `openapi/admin.yaml`, and Spree merges that file in.
|
|
28
|
+
|
|
29
|
+
In a Spree application (a new `create-spree-app` project ships both files already):
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
spree generate spree_dev_tools:openapi
|
|
33
|
+
spree task openapi:generate
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
In an extension, which runs against its dummy app:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
bundle exec rails g spree_dev_tools:openapi
|
|
40
|
+
bundle exec rake spree:openapi:generate
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
The generator writes three things:
|
|
44
|
+
|
|
45
|
+
- `spec/swagger_helper.rb`, which configures rswag for your app or engine. That means the document path, the admin security schemes, `admin_operation`, `request_body_for` and `agent_tool`, and reproducible output.
|
|
46
|
+
- `spec/agent_tools_spec.rb`, the contract that fails when an admin action has no documented operation, an opt-out gives no reason, or two actions would derive one tool name.
|
|
47
|
+
- A skeleton rswag spec for every admin action your controllers serve that isn't documented yet, under `spec/integration/admin/`. Each skeleton is skipped until you fill in two things:
|
|
48
|
+
- its description, which agents read to decide whether to call it;
|
|
49
|
+
- the records its response is tested against.
|
|
50
|
+
|
|
51
|
+
Run the generator again after adding endpoints; it only writes what is missing. The task then writes `openapi/admin.yaml`: commit it. An extension's gemspec that ships `git ls-files` includes it already.
|
|
52
|
+
|
|
53
|
+
The app or engine is found from the directory the specs run in. An extension with more than one engine, or a different document title, sets them in `spec/swagger_helper.rb` before the require:
|
|
54
|
+
|
|
55
|
+
```ruby
|
|
56
|
+
SpreeDevTools::Openapi.engine = MyExtension::Engine
|
|
57
|
+
SpreeDevTools::Openapi.title = 'My Extension Admin API'
|
|
58
|
+
require 'spree_dev_tools/rspec/openapi'
|
|
59
|
+
```
|
|
60
|
+
|
|
25
61
|
## Configuring
|
|
26
62
|
|
|
27
63
|
If you want to disable Simplecov set the `SPREE_DEV_TOOLS_DISABLE_SIMPLECOV` ENV variable to any value.
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'rails/generators'
|
|
4
|
+
require 'spree_dev_tools/openapi'
|
|
5
|
+
|
|
6
|
+
module SpreeDevTools
|
|
7
|
+
# Sets an extension, or a Spree application, up to document its Admin API
|
|
8
|
+
# endpoints, which makes each documented action an agent tool. In a Spree
|
|
9
|
+
# project:
|
|
10
|
+
#
|
|
11
|
+
# spree generate spree_dev_tools:openapi
|
|
12
|
+
#
|
|
13
|
+
# Writes spec/swagger_helper.rb and the agent-tool contract spec, then a
|
|
14
|
+
# skeleton rswag spec for every admin action the extension's controllers
|
|
15
|
+
# serve that its document does not describe yet. The skeletons are skipped
|
|
16
|
+
# until filled in: each needs a description, which agents read, and the
|
|
17
|
+
# records its response is tested against. Run it again after adding
|
|
18
|
+
# endpoints; it writes only what is missing.
|
|
19
|
+
class OpenapiGenerator < Rails::Generators::Base
|
|
20
|
+
desc "Sets the extension up to generate openapi/admin.yaml and scaffolds rswag specs for undocumented admin actions"
|
|
21
|
+
|
|
22
|
+
class_option :engine, type: :string, desc: 'The extension engine, e.g. MyExtension::Engine (detected when omitted)'
|
|
23
|
+
|
|
24
|
+
CRUD_SUMMARIES = {
|
|
25
|
+
'index' => 'List %<plural>s',
|
|
26
|
+
'show' => 'Get a %<singular>s',
|
|
27
|
+
'create' => 'Create a %<singular>s',
|
|
28
|
+
'update' => 'Update a %<singular>s',
|
|
29
|
+
'destroy' => 'Delete a %<singular>s'
|
|
30
|
+
}.freeze
|
|
31
|
+
|
|
32
|
+
RESPONSE_CODES = { 'create' => '201', 'destroy' => '204' }.freeze
|
|
33
|
+
|
|
34
|
+
def set_destination
|
|
35
|
+
self.destination_root = engine.root.to_s
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def create_swagger_helper
|
|
39
|
+
create_file 'spec/swagger_helper.rb', <<~RUBY, skip: true
|
|
40
|
+
# frozen_string_literal: true
|
|
41
|
+
|
|
42
|
+
# Integration specs that document the Admin API endpoints added here.
|
|
43
|
+
# `spree task openapi:generate` (in an extension, `bundle exec rake
|
|
44
|
+
# spree:openapi:generate`) writes them into openapi/admin.yaml, which
|
|
45
|
+
# Spree reads to build an agent tool for each documented action.
|
|
46
|
+
require '#{spec_helper}'
|
|
47
|
+
require 'spree_dev_tools/rspec/openapi'
|
|
48
|
+
RUBY
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def create_contract_spec
|
|
52
|
+
create_file 'spec/agent_tools_spec.rb', <<~RUBY, skip: true
|
|
53
|
+
# frozen_string_literal: true
|
|
54
|
+
|
|
55
|
+
require '#{spec_helper}'
|
|
56
|
+
require 'spree/api/testing_support/agent_tools_contract'
|
|
57
|
+
|
|
58
|
+
# Fails when an admin action has no documented operation, an opt-out has
|
|
59
|
+
# no reason, or two actions would derive the same agent tool name.
|
|
60
|
+
RSpec.describe 'agent tools' do
|
|
61
|
+
it_behaves_like 'an engine whose admin writes are agent tools', #{reference}
|
|
62
|
+
end
|
|
63
|
+
RUBY
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def scaffold_undocumented_operations
|
|
67
|
+
missing = undocumented_routes
|
|
68
|
+
if missing.empty?
|
|
69
|
+
say_status :ok, 'every admin action is documented', :green
|
|
70
|
+
return
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
missing.group_by { |route| route[:controller] }.each do |controller, routes|
|
|
74
|
+
# Mirrors the controller's namespace under admin, so two controllers
|
|
75
|
+
# with one name (an order's fulfillments and a seller's) never share
|
|
76
|
+
# a file.
|
|
77
|
+
relative = controller.controller_path.sub(%r{\Aspree/api/v3/admin/}, '')
|
|
78
|
+
path = File.join('spec', 'integration', 'admin', "#{relative}_spec.rb")
|
|
79
|
+
if File.exist?(File.join(destination_root, path))
|
|
80
|
+
say_status :exists, "#{path}: add #{routes.map { |route| "#{route[:verb].upcase} #{route[:path]}" }.join(', ')}", :yellow
|
|
81
|
+
next
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
create_file path, spec_for(controller, routes)
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
private
|
|
89
|
+
|
|
90
|
+
def reference
|
|
91
|
+
options[:engine] || SpreeDevTools::Openapi.reference
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# An application boots Rails from rails_helper; an extension's
|
|
95
|
+
# spec_helper loads its dummy app.
|
|
96
|
+
def spec_helper
|
|
97
|
+
engine.is_a?(Rails::Application) ? 'rails_helper' : 'spec_helper'
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def engine
|
|
101
|
+
@engine ||= if options[:engine]
|
|
102
|
+
options[:engine].constantize
|
|
103
|
+
else
|
|
104
|
+
SpreeDevTools::Openapi.engine
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# The engine's admin routes whose operation no document describes.
|
|
109
|
+
def undocumented_routes
|
|
110
|
+
Spree::Api::AdminDocument.reset!
|
|
111
|
+
paths = Spree::Api::AdminDocument.document['paths'] || {}
|
|
112
|
+
root = File.expand_path(engine.root.to_s)
|
|
113
|
+
|
|
114
|
+
operations = Spree::Api::AgentOperations
|
|
115
|
+
|
|
116
|
+
# What the contract spec requires: the actions that become tools. CRUD
|
|
117
|
+
# reaches agents through the generic tools and needs no spec here.
|
|
118
|
+
operations.admin_routes.select do |route|
|
|
119
|
+
next false unless operations::WRITE_VERBS.include?(route[:verb]) && !operations::CRUD_ACTIONS.include?(route[:action])
|
|
120
|
+
|
|
121
|
+
controller = route[:controller]
|
|
122
|
+
location = controller && Object.const_source_location(controller.name)&.first
|
|
123
|
+
location&.start_with?(root) && paths.dig(route[:path], route[:verb]).nil?
|
|
124
|
+
end.uniq { |route| [route[:verb], route[:path]] }
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def spec_for(controller, routes)
|
|
128
|
+
model = model_for(controller)
|
|
129
|
+
tag = model ? model.model_name.human.pluralize.titleize : controller.controller_name.titleize
|
|
130
|
+
|
|
131
|
+
blocks = routes.group_by { |route| route[:path] }.map do |path, path_routes|
|
|
132
|
+
operations = path_routes.map { |route| operation_for(controller, model, tag, route) }.join("\n\n")
|
|
133
|
+
segments = path.scan(/\{([a-z_]+)\}/).flatten.map do |segment|
|
|
134
|
+
named = segment == 'id' && model ? model.model_name.human : segment.delete_suffix('_id').humanize
|
|
135
|
+
" parameter name: :#{segment}, in: :path, type: :string, required: true, description: '#{named} ID'"
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
[" path '#{path}' do", *segments, '', operations, ' end'].join("\n")
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
<<~RUBY
|
|
142
|
+
# frozen_string_literal: true
|
|
143
|
+
|
|
144
|
+
require 'swagger_helper'
|
|
145
|
+
|
|
146
|
+
RSpec.describe '#{tag} Admin API', type: :request, swagger_doc: 'openapi/admin.yaml' do
|
|
147
|
+
include_context 'API v3 Admin'
|
|
148
|
+
|
|
149
|
+
#{blocks.join("\n\n")}
|
|
150
|
+
end
|
|
151
|
+
RUBY
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def operation_for(controller, model, tag, route)
|
|
155
|
+
action = route[:action]
|
|
156
|
+
read = read_action?(controller, action)
|
|
157
|
+
body = controller.respond_to?(:request_params_schema) && controller.request_params_schema(action)
|
|
158
|
+
segments = route[:path].scan(/\{([a-z_]+)\}/).flatten
|
|
159
|
+
|
|
160
|
+
lines = []
|
|
161
|
+
lines << " # TODO: check the summary; agents see it as the tool's name in words."
|
|
162
|
+
lines << " #{route[:verb]} '#{summary_for(action, model)}' do"
|
|
163
|
+
lines << " tags '#{tag}'"
|
|
164
|
+
lines << " admin_operation :#{read ? 'read' : 'write'}, :#{controller._scoped_resource || 'TODO'}, description: <<~DESC"
|
|
165
|
+
lines << ' TODO: What this does and when to use it, for someone choosing the operation.'
|
|
166
|
+
lines << ' Agents read this to decide whether to call it.'
|
|
167
|
+
lines << ' DESC'
|
|
168
|
+
lines << ' request_body_for' if body
|
|
169
|
+
lines << ''
|
|
170
|
+
lines << " response '#{RESPONSE_CODES.fetch(action, '200')}', 'TODO' do"
|
|
171
|
+
segments.each do |segment|
|
|
172
|
+
lines << " let(:#{segment}) { skip('TODO: the record this operation acts on') }"
|
|
173
|
+
end
|
|
174
|
+
lines << " let(:body) { skip('TODO: a valid request body') }" if body
|
|
175
|
+
# Nothing above defers the request, so skip it outright until the
|
|
176
|
+
# example is written: run_test! would otherwise send it unfinished.
|
|
177
|
+
lines << " before { skip('TODO: the records this operation is tested against') }" if segments.empty? && !body
|
|
178
|
+
lines << ''
|
|
179
|
+
lines << ' run_test!'
|
|
180
|
+
lines << ' end'
|
|
181
|
+
lines << ' end'
|
|
182
|
+
lines.join("\n")
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def summary_for(action, model)
|
|
186
|
+
singular = model ? model.model_name.human.downcase : 'record'
|
|
187
|
+
template = CRUD_SUMMARIES[action]
|
|
188
|
+
return format(template, singular: singular, plural: singular.pluralize) if template
|
|
189
|
+
|
|
190
|
+
"#{action.humanize} a #{singular}"
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def model_for(controller)
|
|
194
|
+
Spree::Api::AgentResourceMap.model_for(controller)
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
# The scope kind the endpoint demands, by the controller's own rule.
|
|
198
|
+
def read_action?(controller, action)
|
|
199
|
+
instance = controller.allocate
|
|
200
|
+
reads = instance.respond_to?(:read_actions, true) ? instance.send(:read_actions) : %w[index show]
|
|
201
|
+
Array(reads).map(&:to_s).include?(action)
|
|
202
|
+
rescue StandardError, NotImplementedError
|
|
203
|
+
false
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
end
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SpreeDevTools
|
|
4
|
+
# Sets an extension's rswag specs up to generate the `openapi/admin.yaml`
|
|
5
|
+
# its gem ships. Spree merges that file into the Admin API document its
|
|
6
|
+
# agent tools are built from, so every action the extension documents is
|
|
7
|
+
# a tool (see Spree's "MCP Server Custom Tools" guide).
|
|
8
|
+
#
|
|
9
|
+
# The same for a Spree application documenting admin endpoints of its own:
|
|
10
|
+
# Spree reads the app's `openapi/admin.yaml` as it reads an extension's.
|
|
11
|
+
#
|
|
12
|
+
# In the extension's (or the app's) `spec/swagger_helper.rb`:
|
|
13
|
+
#
|
|
14
|
+
# require 'spec_helper'
|
|
15
|
+
# require 'spree_dev_tools/rspec/openapi'
|
|
16
|
+
#
|
|
17
|
+
# The extension's engine is found from the directory the specs run in, and
|
|
18
|
+
# in an application, where no engine's root is that directory, it is the
|
|
19
|
+
# application itself. An extension with more than one engine, or a
|
|
20
|
+
# different title, sets them before the require:
|
|
21
|
+
#
|
|
22
|
+
# SpreeDevTools::Openapi.engine = MyExtension::Engine
|
|
23
|
+
# SpreeDevTools::Openapi.title = 'My Extension Admin API'
|
|
24
|
+
module Openapi
|
|
25
|
+
class << self
|
|
26
|
+
attr_writer :engine, :title
|
|
27
|
+
|
|
28
|
+
# @return [Rails::Engine, Rails::Application]
|
|
29
|
+
def engine
|
|
30
|
+
@engine ||= detect_engine || detect_application || raise(
|
|
31
|
+
'spree_dev_tools could not tell which engine these specs document. ' \
|
|
32
|
+
'Set SpreeDevTools::Openapi.engine = MyExtension::Engine before requiring spree_dev_tools/rspec/openapi.'
|
|
33
|
+
)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# @return [String]
|
|
37
|
+
def title
|
|
38
|
+
@title ||= "#{name_of(engine).titleize} Admin API"
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# How generated code refers to the engine: its class, or the
|
|
42
|
+
# application, which is reached as Rails.application.
|
|
43
|
+
#
|
|
44
|
+
# @return [String]
|
|
45
|
+
def reference
|
|
46
|
+
engine.is_a?(Rails::Application) ? 'Rails.application' : engine.name
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
private
|
|
50
|
+
|
|
51
|
+
def name_of(engine)
|
|
52
|
+
engine.is_a?(Rails::Application) ? engine.class.module_parent_name.to_s : engine.engine_name.to_s
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def detect_application
|
|
56
|
+
root = File.expand_path(Dir.pwd)
|
|
57
|
+
Rails.application if Rails.application && File.expand_path(Rails.application.root.to_s) == root
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# The engine whose gem the specs run from.
|
|
61
|
+
def detect_engine
|
|
62
|
+
root = File.expand_path(Dir.pwd)
|
|
63
|
+
Rails::Engine.descendants.reject { |klass| klass <= Rails::Application }.find do |klass|
|
|
64
|
+
klass.root && File.expand_path(klass.root.to_s) == root
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Sets an extension's or an application's rswag specs up to generate the
|
|
4
|
+
# `openapi/admin.yaml` it ships; see SpreeDevTools::Openapi. In
|
|
5
|
+
# `spec/swagger_helper.rb` (an application requires rails_helper instead):
|
|
6
|
+
#
|
|
7
|
+
# require 'spec_helper'
|
|
8
|
+
# require 'spree_dev_tools/rspec/openapi'
|
|
9
|
+
require 'rswag/specs'
|
|
10
|
+
begin
|
|
11
|
+
require 'spree/api/testing_support/openapi'
|
|
12
|
+
rescue LoadError
|
|
13
|
+
raise LoadError, 'spree_dev_tools/rspec/openapi needs a Spree whose spree_api ships ' \
|
|
14
|
+
'Spree::Api::TestingSupport::Openapi (the 6.0 patch release with agent tools from the Admin API document).'
|
|
15
|
+
end
|
|
16
|
+
require 'spree/api/testing_support/deterministic_openapi'
|
|
17
|
+
require 'spree_dev_tools/openapi'
|
|
18
|
+
|
|
19
|
+
RSpec.configure do |config|
|
|
20
|
+
Spree::Api::TestingSupport::Openapi.configure(
|
|
21
|
+
config, engine: SpreeDevTools::Openapi.engine, title: SpreeDevTools::Openapi.title
|
|
22
|
+
)
|
|
23
|
+
end
|
data/lib/spree_dev_tools.rb
CHANGED
data/spree_dev_tools.gemspec
CHANGED
|
@@ -38,6 +38,7 @@ Gem::Specification.new do |s|
|
|
|
38
38
|
s.add_dependency 'rails-controller-testing'
|
|
39
39
|
s.add_dependency 'rspec-activemodel-mocks'
|
|
40
40
|
s.add_dependency 'rspec_junit_formatter'
|
|
41
|
+
s.add_dependency 'rswag-specs'
|
|
41
42
|
s.add_dependency 'rspec-rails'
|
|
42
43
|
s.add_dependency 'rspec-retry'
|
|
43
44
|
s.add_dependency 'ruby-lsp', '>= 0.23.0'
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: spree_dev_tools
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.0.
|
|
4
|
+
version: 1.0.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Vendo Connect Inc., Vendo Sp. z o.o.
|
|
@@ -233,6 +233,20 @@ dependencies:
|
|
|
233
233
|
- - ">="
|
|
234
234
|
- !ruby/object:Gem::Version
|
|
235
235
|
version: '0'
|
|
236
|
+
- !ruby/object:Gem::Dependency
|
|
237
|
+
name: rswag-specs
|
|
238
|
+
requirement: !ruby/object:Gem::Requirement
|
|
239
|
+
requirements:
|
|
240
|
+
- - ">="
|
|
241
|
+
- !ruby/object:Gem::Version
|
|
242
|
+
version: '0'
|
|
243
|
+
type: :runtime
|
|
244
|
+
prerelease: false
|
|
245
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
246
|
+
requirements:
|
|
247
|
+
- - ">="
|
|
248
|
+
- !ruby/object:Gem::Version
|
|
249
|
+
version: '0'
|
|
236
250
|
- !ruby/object:Gem::Dependency
|
|
237
251
|
name: rspec-rails
|
|
238
252
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -335,6 +349,9 @@ files:
|
|
|
335
349
|
- lib/spree_dev_tools/engine.rb
|
|
336
350
|
- lib/spree_dev_tools/generators/install/install_generator.rb
|
|
337
351
|
- lib/spree_dev_tools/generators/install/templates/spree_dev_tools.rb
|
|
352
|
+
- lib/spree_dev_tools/generators/openapi/openapi_generator.rb
|
|
353
|
+
- lib/spree_dev_tools/openapi.rb
|
|
354
|
+
- lib/spree_dev_tools/rspec/openapi.rb
|
|
338
355
|
- lib/spree_dev_tools/rspec/spec_helper.rb
|
|
339
356
|
- lib/spree_dev_tools/rspec/support/active_job.rb
|
|
340
357
|
- lib/spree_dev_tools/rspec/support/database_cleaner.rb
|