belt 0.2.19 → 0.3.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/CHANGELOG.md +47 -0
- data/README.md +42 -3
- data/lib/belt/action_router.rb +21 -17
- data/lib/belt/cli/app_detection.rb +4 -2
- data/lib/belt/cli/auth_command.rb +75 -4
- data/lib/belt/cli/console_command.rb +17 -2
- data/lib/belt/cli/deploy_command.rb +10 -2
- data/lib/belt/cli/destroy_command.rb +3 -1
- data/lib/belt/cli/doctor_command.rb +166 -22
- data/lib/belt/cli/generate_command.rb +37 -37
- data/lib/belt/cli/index_command.rb +192 -0
- data/lib/belt/cli/lambda_config_command.rb +2 -1
- data/lib/belt/cli/routes_command/route_inference.rb +4 -3
- data/lib/belt/cli/routes_command.rb +0 -19
- data/lib/belt/cli/tables_command.rb +3 -1
- data/lib/belt/cli/views_command.rb +3 -9
- data/lib/belt/cli.rb +1 -0
- data/lib/belt/inflector.rb +8 -0
- data/lib/belt/route_dsl.rb +196 -85
- data/lib/belt/version.rb +1 -1
- data/lib/templates/generate/auth/cognito.tf.erb +0 -2
- data/lib/templates/generate/auth/frontend/ConfirmEmail.jsx +1 -1
- data/lib/templates/generate/auth/frontend/Login.jsx +2 -4
- data/lib/templates/generate/auth/frontend/SignUp.jsx +1 -1
- data/lib/templates/generate/auth/frontend/auth.css +157 -0
- data/lib/templates/generate/auth/frontend/auth.js +5 -1
- data/lib/templates/generate/model.rb.erb +4 -6
- data/lib/templates/new_app/AGENTS.md.erb +2 -2
- data/lib/templates/new_app/config/lambda/api.yml.erb +1 -0
- data/lib/templates/new_app/config/routes.rb.erb +3 -1
- data/lib/templates/new_app/lambda/api.rb.erb +1 -1
- metadata +31 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: cacb19365ad96e079f7a796dc74390f70089bff236581a65b30fabb6e5719008
|
|
4
|
+
data.tar.gz: c723291b6e114a285b146fead8f5626fe812d798c2230f90999bde34af67806a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8ac85878df33cec2aa990e527530e54368972e6cca18f18f1e080f4154c50b5d539536fdcb55dfa6b2de675c2f271ed60cb578c3e4acedd4c2bc8c39f48cefd3
|
|
7
|
+
data.tar.gz: fe809339def0c18771870b7bc7bd7740c83aab12dfcef9eb7f4a3b07420bf139dce02fe0c8845bf84e994eb9743e2aa635f0fb0d6e2f0ff369f12b15cabdcc6b
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,52 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.3.0
|
|
4
|
+
|
|
5
|
+
Version bump only — no code changes from 0.2.21.
|
|
6
|
+
|
|
7
|
+
**Why 0.3.0 instead of another patch?** The route DSL changes in 0.2.21 (`gateway`/`function`/`namespace`/`scope`) were a minor-version level change (new features, new keywords). RubyGems doesn't allow republishing the same version, so we're bumping to 0.3.0 to correct the semver trajectory.
|
|
8
|
+
|
|
9
|
+
If you're on 0.2.21, you already have the DSL changes. This release is purely for version hygiene.
|
|
10
|
+
|
|
11
|
+
## 0.2.21
|
|
12
|
+
|
|
13
|
+
### Route DSL: `gateway`, `function`, `namespace`, and `scope` keywords
|
|
14
|
+
|
|
15
|
+
The route DSL now uses terminology that matches AWS infrastructure:
|
|
16
|
+
|
|
17
|
+
- **`gateway`** — Creates an API Gateway with a default Lambda function of the same name (replaces top-level `namespace`)
|
|
18
|
+
- **`function`** — Targets enclosed routes to a different Lambda function
|
|
19
|
+
- **`namespace`** — Rails-like path + module prefix (e.g., `/admin/users` with `admin/users` controller). Does NOT change Lambda target.
|
|
20
|
+
- **`scope`** — Flexible grouping with `path:`, `module:`, `auth:`, `tables:` options. Does NOT change Lambda target.
|
|
21
|
+
|
|
22
|
+
```ruby
|
|
23
|
+
Belt.application.routes.draw do
|
|
24
|
+
gateway :api, auth: :cognito do
|
|
25
|
+
resources :posts # → lambda: "api", path: /posts
|
|
26
|
+
|
|
27
|
+
function :onboarding do
|
|
28
|
+
resources :steps # → lambda: "onboarding", path: /steps
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
namespace :admin do
|
|
32
|
+
resources :users # → lambda: "api", path: /admin/users, controller: "admin/users"
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
scope path: 'v2', module: 'v2' do
|
|
36
|
+
resources :widgets # → lambda: "api", path: /v2/widgets, controller: "v2/widgets"
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
**ActionRouter** now accepts `gateway:` keyword (preferred):
|
|
43
|
+
|
|
44
|
+
```ruby
|
|
45
|
+
ROUTER = Belt::ActionRouter.new(routes: Routes::API, gateway: 'api')
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
**100% backward compatible:** `namespace :api do` at top level still works (aliased to `gateway`), and `ActionRouter.new(namespace: 'api')` still works.
|
|
49
|
+
|
|
3
50
|
## 0.2.18
|
|
4
51
|
|
|
5
52
|
### New generator: `belt generate auth`
|
data/README.md
CHANGED
|
@@ -105,7 +105,7 @@ require "belt"
|
|
|
105
105
|
|
|
106
106
|
include Belt::LambdaHandler
|
|
107
107
|
|
|
108
|
-
ROUTER = Belt::ActionRouter.new(routes: Routes::API,
|
|
108
|
+
ROUTER = Belt::ActionRouter.new(routes: Routes::API, gateway: "api")
|
|
109
109
|
|
|
110
110
|
def execute(path:, body:, event:)
|
|
111
111
|
ROUTER.route(event: event, body: body)
|
|
@@ -140,7 +140,7 @@ Define routes in `infrastructure/routes.tf.rb`:
|
|
|
140
140
|
|
|
141
141
|
```ruby
|
|
142
142
|
Belt.application.routes.draw do
|
|
143
|
-
|
|
143
|
+
gateway :api do
|
|
144
144
|
resources :posts, only: [:index, :show, :create]
|
|
145
145
|
end
|
|
146
146
|
end
|
|
@@ -170,6 +170,45 @@ The provider will:
|
|
|
170
170
|
- Generate IAM policies for DynamoDB table access
|
|
171
171
|
- Set up CloudWatch log groups
|
|
172
172
|
|
|
173
|
+
### Route DSL Keywords
|
|
174
|
+
|
|
175
|
+
The routes DSL has four keywords that map to infrastructure and code organization:
|
|
176
|
+
|
|
177
|
+
| Keyword | Purpose | Affects Lambda? |
|
|
178
|
+
|---------|---------|-----------------|
|
|
179
|
+
| `gateway` | Creates an API Gateway + default Lambda | Yes — sets the default Lambda for all routes inside |
|
|
180
|
+
| `function` | Routes to a different Lambda | Yes — overrides the gateway's default |
|
|
181
|
+
| `namespace` | Adds path prefix + controller module | No — Rails-like code organization only |
|
|
182
|
+
| `scope` | Flexible path/module/auth grouping | No — grouping and shared options only |
|
|
183
|
+
|
|
184
|
+
**Example combining all four:**
|
|
185
|
+
|
|
186
|
+
```ruby
|
|
187
|
+
Belt.application.routes.draw do
|
|
188
|
+
gateway :api, auth: :cognito do
|
|
189
|
+
resources :posts # → lambda: api, path: /posts, controller: posts
|
|
190
|
+
|
|
191
|
+
namespace :admin do
|
|
192
|
+
resources :users # → lambda: api, path: /admin/users, controller: admin/users
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
function :worker do
|
|
196
|
+
resources :jobs # → lambda: worker, path: /jobs, controller: jobs
|
|
197
|
+
|
|
198
|
+
namespace :internal do
|
|
199
|
+
resources :tasks # → lambda: worker, path: /internal/tasks, controller: internal/tasks
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
scope path: 'v2', module: 'legacy' do
|
|
204
|
+
resources :widgets # → lambda: api, path: /v2/widgets, controller: legacy/widgets
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
**Key point:** `namespace` and `scope` are purely organizational — they affect URL paths and controller module resolution but never change which Lambda handles the request. Use `function` when you need routes to go to a different Lambda.
|
|
211
|
+
|
|
173
212
|
## BeltController Features
|
|
174
213
|
|
|
175
214
|
### Callbacks
|
|
@@ -423,7 +462,7 @@ The command expects `infrastructure/routes.tf.rb` in the current working directo
|
|
|
423
462
|
|
|
424
463
|
```ruby
|
|
425
464
|
Belt.application.routes.draw do
|
|
426
|
-
|
|
465
|
+
gateway :api do
|
|
427
466
|
resources :posts, only: [:index, :show, :create, :destroy]
|
|
428
467
|
resource :profile, only: [:show, :update]
|
|
429
468
|
get "health", action: :health
|
data/lib/belt/action_router.rb
CHANGED
|
@@ -7,22 +7,26 @@ module Belt
|
|
|
7
7
|
# Routes incoming requests to controllers based on a route manifest.
|
|
8
8
|
#
|
|
9
9
|
# Usage:
|
|
10
|
-
# ROUTER = Belt::ActionRouter.new(routes: MY_ROUTES,
|
|
10
|
+
# ROUTER = Belt::ActionRouter.new(routes: MY_ROUTES, gateway: "api")
|
|
11
11
|
# response = ROUTER.route(event: event, body: body)
|
|
12
12
|
#
|
|
13
13
|
class ActionRouter
|
|
14
14
|
class RouteNotFound < StandardError; end
|
|
15
15
|
|
|
16
|
-
def initialize(routes:, namespace:)
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
def initialize(routes:, gateway: nil, namespace: nil)
|
|
17
|
+
# Accept `gateway:` (preferred) or legacy `namespace:` keyword
|
|
18
|
+
gw = gateway || namespace
|
|
19
|
+
raise ArgumentError, 'Belt::ActionRouter requires a gateway: (or legacy namespace:) argument' unless gw
|
|
20
|
+
|
|
21
|
+
@gateway = gw.to_s
|
|
22
|
+
@gateway_module_name = "#{@gateway.split('_').map(&:capitalize).join}Controllers"
|
|
19
23
|
@routes = build_route_table(routes)
|
|
20
24
|
end
|
|
21
25
|
|
|
22
26
|
def route(event:, body:)
|
|
23
27
|
method = event['httpMethod']
|
|
24
28
|
full_path = event['path']
|
|
25
|
-
match_path =
|
|
29
|
+
match_path = strip_gateway_prefix(full_path)
|
|
26
30
|
|
|
27
31
|
route_info = find_route(method, match_path)
|
|
28
32
|
|
|
@@ -56,10 +60,10 @@ module Belt
|
|
|
56
60
|
|
|
57
61
|
private
|
|
58
62
|
|
|
59
|
-
def
|
|
63
|
+
def strip_gateway_prefix(path)
|
|
60
64
|
return '/' if path.nil?
|
|
61
65
|
|
|
62
|
-
prefix = "/#{@
|
|
66
|
+
prefix = "/#{@gateway}"
|
|
63
67
|
if path.start_with?(prefix)
|
|
64
68
|
stripped = path.sub(prefix, '')
|
|
65
69
|
stripped.empty? ? '/' : stripped
|
|
@@ -127,15 +131,15 @@ module Belt
|
|
|
127
131
|
return false if name.start_with?('/') || name.end_with?('/')
|
|
128
132
|
return false if name.include?('\\')
|
|
129
133
|
|
|
130
|
-
# Allow only lowercase letters, digits, underscores, and
|
|
134
|
+
# Allow only lowercase letters, digits, underscores, and forward slashes for nesting
|
|
131
135
|
name.match?(%r{\A[a-z][a-z0-9_]*(/[a-z][a-z0-9_]*)?\z})
|
|
132
136
|
end
|
|
133
137
|
|
|
134
138
|
def resolve_controller(controller_name)
|
|
135
|
-
# Try
|
|
139
|
+
# Try gateway module first (app's own controllers)
|
|
136
140
|
begin
|
|
137
|
-
|
|
138
|
-
return resolve_from_module(
|
|
141
|
+
gateway_module = Object.const_get(@gateway_module_name)
|
|
142
|
+
return resolve_from_module(gateway_module, controller_name)
|
|
139
143
|
rescue NameError
|
|
140
144
|
# Fall through to controller_paths lookup
|
|
141
145
|
end
|
|
@@ -144,13 +148,13 @@ module Belt
|
|
|
144
148
|
resolve_from_paths(controller_name)
|
|
145
149
|
end
|
|
146
150
|
|
|
147
|
-
def resolve_from_module(
|
|
151
|
+
def resolve_from_module(gateway_module, controller_name)
|
|
148
152
|
if controller_name.include?('/')
|
|
149
153
|
parts = controller_name.split('/')
|
|
150
|
-
parent =
|
|
154
|
+
parent = gateway_module.const_get(parts[0].split('_').map(&:capitalize).join)
|
|
151
155
|
parent.const_get("#{parts[1].split('_').map(&:capitalize).join}Controller")
|
|
152
156
|
else
|
|
153
|
-
|
|
157
|
+
gateway_module.const_get("#{controller_name.split('_').map(&:capitalize).join}Controller")
|
|
154
158
|
end
|
|
155
159
|
end
|
|
156
160
|
|
|
@@ -176,9 +180,9 @@ module Belt
|
|
|
176
180
|
class_name = "#{controller_name.split(%r{[_/]}).map(&:capitalize).join}Controller"
|
|
177
181
|
return Object.const_get(class_name) if Object.const_defined?(class_name)
|
|
178
182
|
|
|
179
|
-
# Try under
|
|
180
|
-
if Object.const_defined?(@
|
|
181
|
-
ns = Object.const_get(@
|
|
183
|
+
# Try under gateway module (e.g., ApiControllers::PostsController)
|
|
184
|
+
if Object.const_defined?(@gateway_module_name)
|
|
185
|
+
ns = Object.const_get(@gateway_module_name)
|
|
182
186
|
return ns.const_get(class_name) if ns.const_defined?(class_name)
|
|
183
187
|
end
|
|
184
188
|
end
|
|
@@ -3,12 +3,14 @@
|
|
|
3
3
|
module Belt
|
|
4
4
|
module CLI
|
|
5
5
|
module AppDetection
|
|
6
|
-
# Detects the primary
|
|
6
|
+
# Detects the primary gateway from the route definitions.
|
|
7
7
|
# Used by generators to determine controller directory and route file naming.
|
|
8
8
|
def detect_namespace
|
|
9
9
|
routes_file = find_routes_file_path
|
|
10
10
|
if routes_file && File.exist?(routes_file)
|
|
11
|
-
|
|
11
|
+
content = File.read(routes_file)
|
|
12
|
+
# Prefer `gateway :name` but fall back to legacy `namespace :name` at top-level
|
|
13
|
+
match = content.match(/^\s*gateway :(\w+)/) || content.match(/^\s*namespace :(\w+)/)
|
|
12
14
|
return match[1] if match
|
|
13
15
|
end
|
|
14
16
|
File.basename(Dir.pwd)
|
|
@@ -101,14 +101,48 @@ module Belt
|
|
|
101
101
|
write_cognito_tf
|
|
102
102
|
write_cognito_outputs_tf
|
|
103
103
|
patch_main_tf
|
|
104
|
+
patch_env_outputs
|
|
104
105
|
generate_frontend_auth if frontend?
|
|
105
106
|
|
|
106
107
|
puts "\n✓ Auth generated!"
|
|
108
|
+
print_next_steps
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def print_next_steps
|
|
107
112
|
puts "\nNext steps:"
|
|
108
|
-
puts ' 1.
|
|
109
|
-
puts '
|
|
110
|
-
puts '
|
|
111
|
-
puts '
|
|
113
|
+
puts ' 1. Add auth: :cognito to your routes namespace:'
|
|
114
|
+
puts ''
|
|
115
|
+
puts ' namespace :api, auth: :cognito do'
|
|
116
|
+
puts ' # your resources...'
|
|
117
|
+
puts ' end'
|
|
118
|
+
puts ''
|
|
119
|
+
if frontend?
|
|
120
|
+
puts ' 2. Wire auth into your frontend/src/App.jsx:'
|
|
121
|
+
puts ''
|
|
122
|
+
puts " import Login from './pages/auth/Login'"
|
|
123
|
+
puts " import ProtectedRoute from './components/ProtectedRoute'"
|
|
124
|
+
puts ''
|
|
125
|
+
puts ' // Add login route:'
|
|
126
|
+
puts ' <Route path="/login" element={<Login onLogin={() => window.location.href = \'/\'} />} />'
|
|
127
|
+
puts ''
|
|
128
|
+
puts ' // Wrap protected routes:'
|
|
129
|
+
puts ' <Route path="/*" element={<ProtectedRoute><YourApp /></ProtectedRoute>} />'
|
|
130
|
+
puts ''
|
|
131
|
+
puts ' 3. Deploy: belt deploy'
|
|
132
|
+
print_create_user_step(4) unless @signup
|
|
133
|
+
else
|
|
134
|
+
puts ' 2. Deploy: belt deploy'
|
|
135
|
+
print_create_user_step(3) unless @signup
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def print_create_user_step(step_num)
|
|
140
|
+
puts " #{step_num}. Create your account:"
|
|
141
|
+
puts ' aws cognito-idp admin-create-user \\'
|
|
142
|
+
puts ' --user-pool-id <pool-id-from-terraform-output> \\'
|
|
143
|
+
puts ' --username your@email.com \\'
|
|
144
|
+
puts ' --temporary-password TempPass123 \\'
|
|
145
|
+
puts ' --message-action SUPPRESS'
|
|
112
146
|
end
|
|
113
147
|
|
|
114
148
|
def remove
|
|
@@ -167,6 +201,7 @@ module Belt
|
|
|
167
201
|
pages_dir = 'frontend/src/pages/auth'
|
|
168
202
|
FileUtils.mkdir_p(pages_dir)
|
|
169
203
|
copy_frontend_file(frontend_template_dir, 'Login.jsx', File.join(pages_dir, 'Login.jsx'))
|
|
204
|
+
copy_frontend_file(frontend_template_dir, 'auth.css', File.join(pages_dir, 'auth.css'))
|
|
170
205
|
if @signup
|
|
171
206
|
# Generate auth pages
|
|
172
207
|
copy_frontend_file(frontend_template_dir, 'SignUp.jsx', File.join(pages_dir, 'SignUp.jsx'))
|
|
@@ -244,6 +279,42 @@ module Belt
|
|
|
244
279
|
insert_cognito_into_resource(content, main_tf, arn_value)
|
|
245
280
|
end
|
|
246
281
|
|
|
282
|
+
def patch_env_outputs
|
|
283
|
+
env_dirs = Dir.glob('infrastructure/*/outputs.tf')
|
|
284
|
+
.reject { |f| f.include?('modules') }
|
|
285
|
+
|
|
286
|
+
env_dirs.each do |outputs_file|
|
|
287
|
+
content = File.read(outputs_file)
|
|
288
|
+
|
|
289
|
+
next if content.include?('cognito_user_pool_id')
|
|
290
|
+
|
|
291
|
+
cognito_outputs = @pools.map do |pool|
|
|
292
|
+
suffix = pool[:suffix]
|
|
293
|
+
<<~HCL
|
|
294
|
+
|
|
295
|
+
output "cognito_user_pool_id#{suffix}" {
|
|
296
|
+
description = "Cognito User Pool ID#{pool[:label]}"
|
|
297
|
+
value = module.app.cognito_user_pool_id#{suffix}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
output "cognito_client_id#{suffix}" {
|
|
301
|
+
description = "Cognito User Pool Client ID#{pool[:label]}"
|
|
302
|
+
value = module.app.cognito_client_id#{suffix}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
output "cognito_region" {
|
|
306
|
+
description = "AWS region for Cognito"
|
|
307
|
+
value = var.aws_region
|
|
308
|
+
}
|
|
309
|
+
HCL
|
|
310
|
+
end.join
|
|
311
|
+
|
|
312
|
+
File.write(outputs_file, content + cognito_outputs)
|
|
313
|
+
env_name = File.basename(File.dirname(outputs_file))
|
|
314
|
+
puts " update #{outputs_file} (added cognito outputs for #{env_name})"
|
|
315
|
+
end
|
|
316
|
+
end
|
|
317
|
+
|
|
247
318
|
def build_arn_value(arns)
|
|
248
319
|
if arns.length == 1
|
|
249
320
|
"[#{arns.first}]"
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'optparse'
|
|
4
|
+
require 'stringio'
|
|
4
5
|
require_relative 'environment_config'
|
|
5
6
|
|
|
6
7
|
module Belt
|
|
@@ -59,7 +60,10 @@ module Belt
|
|
|
59
60
|
boot_app
|
|
60
61
|
puts banner
|
|
61
62
|
ARGV.clear
|
|
62
|
-
|
|
63
|
+
suppress_warnings do
|
|
64
|
+
require 'irb'
|
|
65
|
+
require 'rdoc'
|
|
66
|
+
end
|
|
63
67
|
IRB.start
|
|
64
68
|
end
|
|
65
69
|
|
|
@@ -72,7 +76,7 @@ module Belt
|
|
|
72
76
|
end
|
|
73
77
|
|
|
74
78
|
def boot_app
|
|
75
|
-
require 'bundler/setup'
|
|
79
|
+
suppress_warnings { require 'bundler/setup' }
|
|
76
80
|
|
|
77
81
|
environment_file = File.join(Belt.root, 'lambda', 'config', 'environment.rb')
|
|
78
82
|
if File.exist?(environment_file)
|
|
@@ -132,6 +136,17 @@ module Belt
|
|
|
132
136
|
result.inspect
|
|
133
137
|
end
|
|
134
138
|
end
|
|
139
|
+
|
|
140
|
+
def suppress_warnings
|
|
141
|
+
original_verbose = $VERBOSE
|
|
142
|
+
$VERBOSE = nil
|
|
143
|
+
original_stderr = $stderr
|
|
144
|
+
$stderr = StringIO.new
|
|
145
|
+
yield
|
|
146
|
+
ensure
|
|
147
|
+
$stderr = original_stderr
|
|
148
|
+
$VERBOSE = original_verbose
|
|
149
|
+
end
|
|
135
150
|
end
|
|
136
151
|
end
|
|
137
152
|
end
|
|
@@ -145,9 +145,9 @@ module Belt
|
|
|
145
145
|
|
|
146
146
|
def run
|
|
147
147
|
validate!
|
|
148
|
-
env_dir = File.join(@infra_dir, @env)
|
|
149
|
-
|
|
150
148
|
load_and_apply_env_config!
|
|
149
|
+
run_preflight_checks!
|
|
150
|
+
env_dir = File.join(@infra_dir, @env)
|
|
151
151
|
|
|
152
152
|
puts "belt → deploying #{@env} (in #{env_dir}/)\n\n"
|
|
153
153
|
|
|
@@ -263,6 +263,14 @@ module Belt
|
|
|
263
263
|
"Create it with: belt generate environment #{@env}"
|
|
264
264
|
end
|
|
265
265
|
|
|
266
|
+
def run_preflight_checks!
|
|
267
|
+
require_relative 'doctor_command'
|
|
268
|
+
doctor = DoctorCommand.new(preflight: true)
|
|
269
|
+
return if doctor.run
|
|
270
|
+
|
|
271
|
+
abort 'Deploy blocked. Fix the issues above, then try again.'
|
|
272
|
+
end
|
|
273
|
+
|
|
266
274
|
def validate_aws!
|
|
267
275
|
stdout, status = Open3.capture2('aws', 'sts', 'get-caller-identity', '--output', 'json')
|
|
268
276
|
unless status.success?
|
|
@@ -10,7 +10,7 @@ require_relative '../inflector'
|
|
|
10
10
|
module Belt
|
|
11
11
|
module CLI
|
|
12
12
|
class DestroyCommand
|
|
13
|
-
GENERATORS = %w[scaffold resource model controller environment frontend views auth].freeze
|
|
13
|
+
GENERATORS = %w[scaffold resource model controller environment frontend views index auth].freeze
|
|
14
14
|
|
|
15
15
|
include AppDetection
|
|
16
16
|
|
|
@@ -61,6 +61,8 @@ module Belt
|
|
|
61
61
|
exit 1
|
|
62
62
|
end
|
|
63
63
|
new(generator, name, args.map { |a| parse_field(a) }).destroy
|
|
64
|
+
when 'index'
|
|
65
|
+
Belt::CLI::IndexCommand.run(['remove'] + args)
|
|
64
66
|
else
|
|
65
67
|
name = args.shift
|
|
66
68
|
if name.nil? || name.empty?
|