propel_api 0.1.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 +7 -0
- data/CHANGELOG.md +59 -0
- data/LICENSE +21 -0
- data/README.md +320 -0
- data/Rakefile +36 -0
- data/lib/generators/propel_api/USAGE +8 -0
- data/lib/generators/propel_api/controller/controller_generator.rb +208 -0
- data/lib/generators/propel_api/core/base.rb +19 -0
- data/lib/generators/propel_api/core/configuration_methods.rb +187 -0
- data/lib/generators/propel_api/core/named_base.rb +457 -0
- data/lib/generators/propel_api/core/path_generation_methods.rb +45 -0
- data/lib/generators/propel_api/core/relationship_inferrer.rb +117 -0
- data/lib/generators/propel_api/install/install_generator.rb +343 -0
- data/lib/generators/propel_api/resource/resource_generator.rb +433 -0
- data/lib/generators/propel_api/templates/config/propel_api.rb.tt +149 -0
- data/lib/generators/propel_api/templates/controllers/api_controller_graphiti.rb +79 -0
- data/lib/generators/propel_api/templates/controllers/api_controller_propel_facets.rb +76 -0
- data/lib/generators/propel_api/templates/controllers/example_controller.rb.tt +96 -0
- data/lib/generators/propel_api/templates/scaffold/facet_controller_template.rb.tt +80 -0
- data/lib/generators/propel_api/templates/scaffold/facet_model_template.rb.tt +141 -0
- data/lib/generators/propel_api/templates/scaffold/graphiti_controller_template.rb.tt +82 -0
- data/lib/generators/propel_api/templates/scaffold/graphiti_model_template.rb.tt +32 -0
- data/lib/generators/propel_api/templates/seeds/seeds_template.rb.tt +493 -0
- data/lib/generators/propel_api/templates/tests/controller_test_template.rb.tt +485 -0
- data/lib/generators/propel_api/templates/tests/fixtures_template.yml.tt +250 -0
- data/lib/generators/propel_api/templates/tests/integration_test_template.rb.tt +487 -0
- data/lib/generators/propel_api/templates/tests/model_test_template.rb.tt +252 -0
- data/lib/generators/propel_api/unpack/unpack_generator.rb +304 -0
- data/lib/propel_api.rb +3 -0
- metadata +95 -0
@@ -0,0 +1,117 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Infers ActiveRecord relationships based on PropelAPI conventions
|
4
|
+
#
|
5
|
+
# Conventions:
|
6
|
+
# - All references default to belongs_to with has_many inverse
|
7
|
+
# - All relationships use dependent: :destroy by default
|
8
|
+
# - Class names match reference names (no STI support)
|
9
|
+
# - Polymorphic relationships use _parent suffix (resource_parent or resourceable_parent)
|
10
|
+
# - Through relationships create join tables
|
11
|
+
#
|
12
|
+
class RelationshipInferrer
|
13
|
+
attr_reader :model_name, :attributes, :options
|
14
|
+
|
15
|
+
def initialize(model_name, attributes, options = {})
|
16
|
+
@model_name = model_name
|
17
|
+
@attributes = attributes
|
18
|
+
@options = options
|
19
|
+
@inverse_relationships = {}
|
20
|
+
end
|
21
|
+
|
22
|
+
# Generate relationships for this model
|
23
|
+
def belongs_to_relationships
|
24
|
+
relationships = []
|
25
|
+
|
26
|
+
reference_attributes.each do |attribute|
|
27
|
+
if polymorphic_reference?(attribute)
|
28
|
+
relationships << polymorphic_belongs_to(attribute)
|
29
|
+
else
|
30
|
+
relationships << standard_belongs_to(attribute)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
relationships.compact
|
35
|
+
end
|
36
|
+
|
37
|
+
# Generate inverse relationships that need to be added to other models
|
38
|
+
def inverse_relationships
|
39
|
+
@inverse_relationships
|
40
|
+
end
|
41
|
+
|
42
|
+
# Check if we should generate associations (can be disabled)
|
43
|
+
def should_generate_associations?
|
44
|
+
!options[:skip_associations]
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def reference_attributes
|
50
|
+
attributes.select { |attr| attr.type == :references }
|
51
|
+
end
|
52
|
+
|
53
|
+
def polymorphic_reference?(attribute)
|
54
|
+
attribute.name.to_s.end_with?('_parent') ||
|
55
|
+
attribute.name.to_s.end_with?('able')
|
56
|
+
end
|
57
|
+
|
58
|
+
|
59
|
+
|
60
|
+
def standard_belongs_to(attribute)
|
61
|
+
reference_name = attribute.name.to_s
|
62
|
+
class_name = reference_name.camelize
|
63
|
+
|
64
|
+
# Add inverse relationship
|
65
|
+
add_inverse_relationship(class_name, standard_has_many)
|
66
|
+
|
67
|
+
# All references except organization are optional by default (PropelAPI convention)
|
68
|
+
if reference_name == 'organization'
|
69
|
+
"belongs_to :#{reference_name}"
|
70
|
+
else
|
71
|
+
"belongs_to :#{reference_name}, optional: true"
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def polymorphic_belongs_to(attribute)
|
76
|
+
# Extract the base name for polymorphic association
|
77
|
+
if attribute.name.to_s.end_with?('_parent')
|
78
|
+
# resource_parent -> resource
|
79
|
+
base_name = attribute.name.to_s.gsub(/_parent$/, '')
|
80
|
+
elsif attribute.name.to_s.end_with?('able')
|
81
|
+
# commentable -> commentable
|
82
|
+
base_name = attribute.name.to_s
|
83
|
+
else
|
84
|
+
base_name = attribute.name.to_s
|
85
|
+
end
|
86
|
+
|
87
|
+
# Polymorphic associations are optional by default (PropelAPI convention)
|
88
|
+
"belongs_to :#{base_name}, polymorphic: true, optional: true"
|
89
|
+
end
|
90
|
+
|
91
|
+
|
92
|
+
|
93
|
+
def standard_has_many
|
94
|
+
"has_many :#{table_name}, dependent: :destroy"
|
95
|
+
end
|
96
|
+
|
97
|
+
|
98
|
+
|
99
|
+
|
100
|
+
|
101
|
+
def add_inverse_relationship(class_name, relationship)
|
102
|
+
@inverse_relationships[class_name] ||= []
|
103
|
+
@inverse_relationships[class_name] << relationship
|
104
|
+
end
|
105
|
+
|
106
|
+
def table_name
|
107
|
+
model_name.tableize
|
108
|
+
end
|
109
|
+
|
110
|
+
def singular_table_name
|
111
|
+
model_name.underscore
|
112
|
+
end
|
113
|
+
|
114
|
+
def class_name
|
115
|
+
model_name.camelize
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,343 @@
|
|
1
|
+
require_relative '../core/base'
|
2
|
+
require_relative '../unpack/unpack_generator'
|
3
|
+
|
4
|
+
##
|
5
|
+
# PropelApi installer that provides API generators for Rails applications
|
6
|
+
#
|
7
|
+
# This creates a COMPLETELY STANDALONE API generation system with no gem dependencies.
|
8
|
+
# Usage:
|
9
|
+
# rails generate propel_api:install # Full standalone system (runtime + generators)
|
10
|
+
#
|
11
|
+
# This generator:
|
12
|
+
# 1. Installs API controller configuration and examples
|
13
|
+
# 2. Automatically extracts generator logic to lib/generators/propel_api/
|
14
|
+
# 3. Creates a system that requires NO gem dependencies for generation
|
15
|
+
#
|
16
|
+
# After installation, you can remove 'propel_api' from your Gemfile completely.
|
17
|
+
# All code is in your application and fully customizable.
|
18
|
+
#
|
19
|
+
module PropelApi
|
20
|
+
class InstallGenerator < PropelApi::Base
|
21
|
+
source_root File.expand_path("../templates", __dir__)
|
22
|
+
|
23
|
+
desc "Install API controllers with configurable serialization backend"
|
24
|
+
|
25
|
+
def copy_api_controller
|
26
|
+
initialize_propel_api_settings
|
27
|
+
|
28
|
+
if behavior == :revoke
|
29
|
+
say "Removing API controller with #{@adapter} adapter", :red
|
30
|
+
say "Using namespace: #{namespace_display} version: #{version_display}", :blue
|
31
|
+
else
|
32
|
+
say "Installing API controller with #{@adapter} adapter", :green
|
33
|
+
say "Using namespace: #{namespace_display} version: #{version_display}", :blue
|
34
|
+
end
|
35
|
+
|
36
|
+
case @adapter
|
37
|
+
when 'graphiti'
|
38
|
+
copy_graphiti_controller
|
39
|
+
generate_graphiti_resources if respond_to?(:generate_graphiti_resources, true)
|
40
|
+
when 'propel_facets'
|
41
|
+
copy_propel_facets_controller
|
42
|
+
else
|
43
|
+
raise "Unknown adapter: #{@adapter}. Supported: 'propel_facets', 'graphiti'"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def create_configuration
|
48
|
+
if behavior == :revoke
|
49
|
+
remove_file "config/initializers/propel_api.rb"
|
50
|
+
# Rails automatically handles file removal, just show appropriate message
|
51
|
+
say "Removed PropelApi configuration", :red
|
52
|
+
else
|
53
|
+
# Create the PropelApi configuration initializer
|
54
|
+
template "config/propel_api.rb.tt", "config/initializers/propel_api.rb"
|
55
|
+
say "Created PropelApi configuration with your selected options", :green
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def create_api_routes
|
60
|
+
if behavior == :revoke
|
61
|
+
# Remove API namespace structure
|
62
|
+
remove_api_namespace
|
63
|
+
else
|
64
|
+
# Add basic API namespace structure (defaults to api/v1)
|
65
|
+
add_api_namespace
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def add_required_gems
|
70
|
+
if behavior == :revoke
|
71
|
+
say "💎 Note: Gems added during installation are still in your Gemfile", :yellow
|
72
|
+
say " Remove manually if no longer needed: faker, pagy, has_scope, ransack#{', graphiti, graphiti-rails, vandal_ui' if @adapter == 'graphiti'}", :yellow
|
73
|
+
else
|
74
|
+
case @adapter
|
75
|
+
when 'graphiti'
|
76
|
+
add_graphiti_gems
|
77
|
+
when 'propel_facets'
|
78
|
+
add_propel_facets_gems
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def show_setup_instructions
|
84
|
+
say_setup_instructions
|
85
|
+
end
|
86
|
+
|
87
|
+
def extract_generator_for_customization
|
88
|
+
generator_path = "lib/generators/propel_api"
|
89
|
+
|
90
|
+
if File.exist?(generator_path)
|
91
|
+
say ""
|
92
|
+
say "📦 Generator logic already extracted at #{generator_path}/", :blue
|
93
|
+
say "💡 Skipping extraction to preserve your customizations", :cyan
|
94
|
+
else
|
95
|
+
say ""
|
96
|
+
say "📦 Extracting generator logic for full customization...", :blue
|
97
|
+
|
98
|
+
# Automatically run the unpack generator to extract generator logic
|
99
|
+
invoke PropelApi::UnpackGenerator, [], { force: false }
|
100
|
+
|
101
|
+
say ""
|
102
|
+
say "✅ Generator logic extracted to lib/generators/propel_api/", :green
|
103
|
+
say "💡 Your application is now completely standalone - no gem dependency needed for generators!", :cyan
|
104
|
+
say "🗑️ You can now remove 'propel_api' from your Gemfile", :yellow
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
private
|
109
|
+
|
110
|
+
def copy_propel_facets_controller
|
111
|
+
template "controllers/api_controller_propel_facets.rb", api_controller_path
|
112
|
+
copy_example_controller
|
113
|
+
|
114
|
+
# Copy propel_facets specific dependencies
|
115
|
+
copy_propel_facets_concerns if respond_to?(:copy_propel_facets_concerns, true)
|
116
|
+
end
|
117
|
+
|
118
|
+
def copy_graphiti_controller
|
119
|
+
template "controllers/api_controller_graphiti.rb", api_controller_path
|
120
|
+
copy_example_controller
|
121
|
+
|
122
|
+
# Graphiti doesn't need additional concerns - it's self-contained
|
123
|
+
say "Remember to create Graphiti resources for your models:", :cyan
|
124
|
+
say " rails generate graphiti:resource YourModel", :yellow
|
125
|
+
end
|
126
|
+
|
127
|
+
def copy_example_controller
|
128
|
+
if behavior == :revoke
|
129
|
+
# Rails automatically handles file removal, just show appropriate message
|
130
|
+
say "📝 Removed example controller at doc/api_controller_example.rb", :red
|
131
|
+
else
|
132
|
+
# Create example controller showing usage patterns
|
133
|
+
@engine = @adapter
|
134
|
+
@resource_name = "Post" # Use a generic example name
|
135
|
+
|
136
|
+
template "controllers/example_controller.rb.tt", "doc/api_controller_example.rb"
|
137
|
+
say "📝 Created example controller at doc/api_controller_example.rb", :blue
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def add_propel_facets_gems
|
142
|
+
add_gem_if_missing 'faker', '~> 3.5', 'Realistic seed data generation'
|
143
|
+
add_gem_if_missing 'pagy', '~> 9.0', 'Pagination for Propel Facets API'
|
144
|
+
add_gem_if_missing 'has_scope', '~> 0.8.0', 'Scope filtering for Propel Facets API'
|
145
|
+
add_gem_if_missing 'ransack', '~> 4.0', 'Advanced search for Propel Facets API'
|
146
|
+
|
147
|
+
say "Don't forget to run: bundle install", :yellow
|
148
|
+
end
|
149
|
+
|
150
|
+
def add_graphiti_gems
|
151
|
+
add_gem_if_missing 'faker', '~> 3.5', 'Realistic seed data generation'
|
152
|
+
add_gem_if_missing 'graphiti', '~> 1.4', 'JSON:API resource framework'
|
153
|
+
add_gem_if_missing 'graphiti-rails', '~> 0.2', 'Rails integration for Graphiti'
|
154
|
+
add_gem_if_missing 'vandal_ui', '~> 0.4', 'Interactive API documentation for Graphiti'
|
155
|
+
|
156
|
+
say "Don't forget to run: bundle install", :yellow
|
157
|
+
end
|
158
|
+
|
159
|
+
def add_gem_if_missing(gem_name, version, comment)
|
160
|
+
gemfile_content = File.read(File.join(destination_root, 'Gemfile'))
|
161
|
+
|
162
|
+
# Check if gem is already present
|
163
|
+
if gemfile_content.match(/gem\s+['"]#{gem_name}['"]/)
|
164
|
+
say "#{gem_name} is already in Gemfile, skipping...", :blue
|
165
|
+
else
|
166
|
+
gem gem_name, version, comment: comment
|
167
|
+
say "Added #{gem_name} #{version} to Gemfile", :green
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
def say_setup_instructions
|
172
|
+
if behavior == :revoke
|
173
|
+
say "\n" + "="*60, :red
|
174
|
+
say "API Controller Removed Successfully!", :red
|
175
|
+
say "="*60, :red
|
176
|
+
say "\n🔧 PropelApi configuration removed from config/initializers/propel_api.rb", :cyan
|
177
|
+
say "📋 Removed: --adapter=#{@adapter} --namespace=#{@api_namespace || 'none'} --version=#{@api_version || 'none'}", :cyan
|
178
|
+
say "\n💡 To reinstall: rails generate propel_api:install", :blue
|
179
|
+
say "="*60, :red
|
180
|
+
else
|
181
|
+
say "\n" + "="*60, :green
|
182
|
+
say "API Controller Installed Successfully!", :green
|
183
|
+
say "="*60, :green
|
184
|
+
|
185
|
+
say "\n📦 System Status: Completely standalone - no gem dependency!", :green
|
186
|
+
say "💡 Optional: Remove 'propel_api' from Gemfile (system is fully extracted)", :cyan
|
187
|
+
|
188
|
+
say "\n🔧 Configuration saved to config/initializers/propel_api.rb", :cyan
|
189
|
+
say "📋 Your defaults: --adapter=#{@adapter} --namespace=#{@api_namespace || 'none'} --version=#{@api_version || 'none'}", :cyan
|
190
|
+
|
191
|
+
case @adapter
|
192
|
+
when 'propel_facets'
|
193
|
+
say_propel_facets_instructions
|
194
|
+
when 'graphiti'
|
195
|
+
say_graphiti_instructions
|
196
|
+
end
|
197
|
+
|
198
|
+
say "\n🎨 Customization:", :bold
|
199
|
+
say "• Generator logic: lib/generators/propel_api/propel_api_generator.rb", :blue
|
200
|
+
say "• Templates: lib/generators/propel_api/templates/", :blue
|
201
|
+
say "• Modify any part of the system - it's all yours now!", :cyan
|
202
|
+
|
203
|
+
say "="*60, :green
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
def say_propel_facets_instructions
|
208
|
+
say "\n📋 Propel Facets Setup Instructions:", :cyan
|
209
|
+
say "\n1. Install the propel_facets generator if not already done:"
|
210
|
+
say " rails generate propel_facets", :yellow
|
211
|
+
say "\n2. Your API controller uses the propel_facets serialization system"
|
212
|
+
say "3. Add facets to your models using:"
|
213
|
+
say " json_facet :summary, fields: [:title, :excerpt]", :yellow
|
214
|
+
say "\n4. Create controllers that inherit from #{api_controller_class_name}:"
|
215
|
+
say " class #{api_controller_class_name.gsub('ApiController', 'UsersController')} < #{api_controller_class_name}", :yellow
|
216
|
+
say " permitted_params :name, :email", :yellow
|
217
|
+
say " connect_facet :short, actions: [:index]", :yellow
|
218
|
+
say " connect_facet :details, actions: [:show]", :yellow
|
219
|
+
say " end", :yellow
|
220
|
+
say "\n5. See doc/api_controller_example.rb for complete usage examples"
|
221
|
+
say "6. Your API endpoints will be available at: #{api_route_prefix}/..."
|
222
|
+
say "7. Pagination and filtering are built-in via Pagy and HasScope"
|
223
|
+
say "\n8. Generate resources using:"
|
224
|
+
say " rails generate propel_api Resource field1:type field2:type", :yellow
|
225
|
+
end
|
226
|
+
|
227
|
+
def say_graphiti_instructions
|
228
|
+
say "\n🎯 Graphiti Setup Instructions:", :cyan
|
229
|
+
say "\n1. Create resource classes for your models:"
|
230
|
+
say " rails generate graphiti:resource User", :yellow
|
231
|
+
say "\n2. Create controllers that inherit from #{api_controller_class_name}:"
|
232
|
+
say " class #{api_controller_class_name.gsub('ApiController', 'UsersController')} < #{api_controller_class_name}", :yellow
|
233
|
+
say " self.resource = UserResource", :yellow
|
234
|
+
say " end", :yellow
|
235
|
+
say "\n3. Add to your application.rb or initializer:"
|
236
|
+
say " Graphiti.configure { |c| c.base_url = Rails.application.routes.url_helpers.root_url }", :yellow
|
237
|
+
say "\n4. See doc/api_controller_example.rb for complete usage examples"
|
238
|
+
say "5. Visit /vandal for interactive API documentation"
|
239
|
+
say "6. Your API follows JSON:API specification automatically"
|
240
|
+
say "7. Use the Spraypaint.js client for frontend integration"
|
241
|
+
say "8. API endpoints: #{api_route_prefix}/... with JSON:API format"
|
242
|
+
say "\n9. Generate resources using:"
|
243
|
+
say " rails generate propel_api Resource field1:type field2:type", :yellow
|
244
|
+
end
|
245
|
+
|
246
|
+
# Propel orchestration methods - only active when called by Propel installer
|
247
|
+
def propel_orchestrated_adapter
|
248
|
+
return nil unless called_by_propel_installer?
|
249
|
+
|
250
|
+
begin
|
251
|
+
if defined?(Propel) && Propel.configuration.respond_to?(:api_adapter)
|
252
|
+
return Propel.configuration.api_adapter
|
253
|
+
end
|
254
|
+
rescue => e
|
255
|
+
# Propel configuration not available
|
256
|
+
end
|
257
|
+
|
258
|
+
nil
|
259
|
+
end
|
260
|
+
|
261
|
+
def propel_orchestrated_namespace
|
262
|
+
return nil unless called_by_propel_installer?
|
263
|
+
|
264
|
+
begin
|
265
|
+
if defined?(Propel) && Propel.configuration.respond_to?(:api_namespace)
|
266
|
+
return Propel.configuration.api_namespace
|
267
|
+
end
|
268
|
+
rescue => e
|
269
|
+
# Propel configuration not available
|
270
|
+
end
|
271
|
+
|
272
|
+
nil
|
273
|
+
end
|
274
|
+
|
275
|
+
def propel_orchestrated_version
|
276
|
+
return nil unless called_by_propel_installer?
|
277
|
+
|
278
|
+
begin
|
279
|
+
if defined?(Propel) && Propel.configuration.respond_to?(:api_version)
|
280
|
+
return Propel.configuration.api_version
|
281
|
+
end
|
282
|
+
rescue => e
|
283
|
+
# Propel configuration not available
|
284
|
+
end
|
285
|
+
|
286
|
+
nil
|
287
|
+
end
|
288
|
+
|
289
|
+
def called_by_propel_installer?
|
290
|
+
# Check if we're being called by Propel installer by examining the call stack
|
291
|
+
caller.any? { |line| line.include?('propel/install_generator') }
|
292
|
+
end
|
293
|
+
|
294
|
+
def add_api_namespace
|
295
|
+
initialize_propel_api_settings
|
296
|
+
|
297
|
+
# Use defaults: api/v1 (these come from the PropelApi configuration)
|
298
|
+
namespace = @api_namespace || 'api'
|
299
|
+
version = @api_version || 'v1'
|
300
|
+
|
301
|
+
# Create the namespace structure (always use nested structure for consistency)
|
302
|
+
route_content = "namespace :#{namespace} do\n namespace :#{version} do\n # Add your API routes here\n # Example: resources :users\n end\n end"
|
303
|
+
|
304
|
+
# Check if namespace already exists
|
305
|
+
routes_file = File.join(destination_root, "config/routes.rb")
|
306
|
+
if File.exist?(routes_file)
|
307
|
+
routes_content = File.read(routes_file)
|
308
|
+
|
309
|
+
# Check if API namespace already exists
|
310
|
+
if routes_content.match?(/namespace\s+:#{namespace}/)
|
311
|
+
say "API namespace already exists in routes", :blue
|
312
|
+
return
|
313
|
+
end
|
314
|
+
end
|
315
|
+
|
316
|
+
route route_content
|
317
|
+
say "Added API namespace to routes (#{namespace}/#{version})", :green
|
318
|
+
end
|
319
|
+
|
320
|
+
def remove_api_namespace
|
321
|
+
initialize_propel_api_settings
|
322
|
+
|
323
|
+
namespace = @api_namespace || 'api'
|
324
|
+
version = @api_version || 'v1'
|
325
|
+
|
326
|
+
routes_file = File.join(destination_root, "config/routes.rb")
|
327
|
+
return unless File.exist?(routes_file)
|
328
|
+
|
329
|
+
routes_content = File.read(routes_file)
|
330
|
+
|
331
|
+
# Remove the nested namespace block (api/v1 structure)
|
332
|
+
pattern = /namespace\s+:#{namespace}\s+do.*?namespace\s+:#{version}\s+do.*?end.*?end/m
|
333
|
+
|
334
|
+
if routes_content.match?(pattern)
|
335
|
+
updated_content = routes_content.gsub(pattern, '')
|
336
|
+
# Clean up extra newlines
|
337
|
+
updated_content = updated_content.gsub(/\n\n+/, "\n\n")
|
338
|
+
File.write(routes_file, updated_content)
|
339
|
+
say "Removed API namespace from routes (#{namespace}/#{version})", :red
|
340
|
+
end
|
341
|
+
end
|
342
|
+
end
|
343
|
+
end
|