belt 0.2.8 → 0.2.10
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 +87 -0
- data/README.md +48 -8
- data/lib/belt/action_router.rb +1 -1
- data/lib/belt/assets/welcome.css +119 -47
- data/lib/belt/cli/backup_runner.rb +37 -54
- data/lib/belt/cli/bucket_security.rb +10 -4
- data/lib/belt/cli/deploy_command.rb +48 -0
- data/lib/belt/cli/doctor_command.rb +208 -0
- data/lib/belt/cli/environment_command.rb +6 -6
- data/lib/belt/cli/frontend_command.rb +25 -12
- data/lib/belt/cli/frontend_setup_command.rb +41 -0
- data/lib/belt/cli/new_command.rb +106 -38
- data/lib/belt/cli/path_gem_materializer.rb +141 -0
- data/lib/belt/cli/setup_command.rb +35 -10
- data/lib/belt/cli.rb +4 -0
- data/lib/belt/configuration.rb +30 -0
- data/lib/belt/controllers/welcome_controller.rb +69 -2
- data/lib/belt/helpers/response.rb +35 -4
- data/lib/belt/http_status.rb +89 -0
- data/lib/belt/rendering.rb +1 -0
- data/lib/belt/version.rb +1 -1
- data/lib/belt/views/welcome/show.html.erb +31 -31
- data/lib/belt.rb +16 -0
- data/lib/belt_controller/base.rb +24 -1
- data/lib/belt_controller/implicit_response.rb +104 -0
- data/lib/templates/environment/backend.tf.erb +1 -0
- data/lib/templates/frontend/react/src/index.css +4 -4
- data/lib/templates/frontend/react/src/lib/apiClient.js.erb +20 -3
- data/lib/templates/frontend/react/src/pages/Home.jsx.erb +283 -4
- data/lib/templates/module/main.tf.erb +13 -3
- data/lib/templates/new_app/config/routes.tf.rb.erb +2 -1
- metadata +20 -1
|
@@ -1,9 +1,288 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react'
|
|
2
|
+
import { apiClient } from '../lib/apiClient'
|
|
3
|
+
|
|
4
|
+
// Mirrors lib/belt/assets/welcome.css so SPA and Lambda HTML welcome match.
|
|
5
|
+
// All content lives in the hero overlay — no scroll under the poster.
|
|
6
|
+
const welcomeStyles = `
|
|
7
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
8
|
+
html, body, #root { height: 100%; }
|
|
9
|
+
.belt-welcome {
|
|
10
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
11
|
+
background: #0b1220;
|
|
12
|
+
color: #2d3748;
|
|
13
|
+
min-height: 100vh;
|
|
14
|
+
height: 100%;
|
|
15
|
+
overflow: hidden;
|
|
16
|
+
}
|
|
17
|
+
.belt-welcome .hero {
|
|
18
|
+
position: relative;
|
|
19
|
+
width: 100%;
|
|
20
|
+
height: 100vh;
|
|
21
|
+
overflow: hidden;
|
|
22
|
+
}
|
|
23
|
+
.belt-welcome .hero-bg {
|
|
24
|
+
width: 100%;
|
|
25
|
+
height: 100%;
|
|
26
|
+
object-fit: cover;
|
|
27
|
+
/* Pin the logo / sky — crop hills at the bottom if the viewport is short */
|
|
28
|
+
object-position: top center;
|
|
29
|
+
display: block;
|
|
30
|
+
}
|
|
31
|
+
.belt-welcome .overlay {
|
|
32
|
+
position: absolute;
|
|
33
|
+
top: 50%;
|
|
34
|
+
left: 50%;
|
|
35
|
+
transform: translate(-50%, -50%);
|
|
36
|
+
padding: 1.35rem 1.6rem 1.25rem;
|
|
37
|
+
background: rgba(255, 255, 255, 0.55);
|
|
38
|
+
backdrop-filter: blur(8px);
|
|
39
|
+
-webkit-backdrop-filter: blur(8px);
|
|
40
|
+
border-radius: 14px;
|
|
41
|
+
text-align: center;
|
|
42
|
+
max-width: 560px;
|
|
43
|
+
width: min(92vw, 560px);
|
|
44
|
+
max-height: min(92vh, 620px);
|
|
45
|
+
overflow: auto;
|
|
46
|
+
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.18);
|
|
47
|
+
}
|
|
48
|
+
.belt-welcome .overlay h1 {
|
|
49
|
+
font-size: 1.85rem;
|
|
50
|
+
font-weight: 700;
|
|
51
|
+
color: #2d3748;
|
|
52
|
+
margin-bottom: 0.3rem;
|
|
53
|
+
line-height: 1.2;
|
|
54
|
+
}
|
|
55
|
+
.belt-welcome .overlay .subtitle {
|
|
56
|
+
font-size: 0.95rem;
|
|
57
|
+
color: #4a5568;
|
|
58
|
+
line-height: 1.4;
|
|
59
|
+
}
|
|
60
|
+
.belt-welcome .stack-check {
|
|
61
|
+
display: flex;
|
|
62
|
+
align-items: center;
|
|
63
|
+
justify-content: center;
|
|
64
|
+
gap: 0.4rem;
|
|
65
|
+
margin: 1rem 0 0.85rem;
|
|
66
|
+
flex-wrap: wrap;
|
|
67
|
+
}
|
|
68
|
+
.belt-welcome .check-item {
|
|
69
|
+
display: flex;
|
|
70
|
+
align-items: center;
|
|
71
|
+
gap: 0.35rem;
|
|
72
|
+
padding: 0.35rem 0.7rem;
|
|
73
|
+
border-radius: 8px;
|
|
74
|
+
font-weight: 500;
|
|
75
|
+
font-size: 0.88rem;
|
|
76
|
+
}
|
|
77
|
+
.belt-welcome .check-pass {
|
|
78
|
+
background: rgba(39, 174, 96, 0.12);
|
|
79
|
+
border: 1px solid rgba(39, 174, 96, 0.35);
|
|
80
|
+
color: #1e8449;
|
|
81
|
+
}
|
|
82
|
+
.belt-welcome .check-neutral {
|
|
83
|
+
background: rgba(113, 128, 150, 0.1);
|
|
84
|
+
border: 1px solid rgba(113, 128, 150, 0.28);
|
|
85
|
+
color: #4a5568;
|
|
86
|
+
}
|
|
87
|
+
.belt-welcome .icon { font-size: 1rem; }
|
|
88
|
+
.belt-welcome .arrow { color: #718096; font-size: 1.05rem; }
|
|
89
|
+
.belt-welcome .next-steps {
|
|
90
|
+
text-align: left;
|
|
91
|
+
border-top: 1px solid rgba(45, 55, 72, 0.12);
|
|
92
|
+
padding-top: 0.75rem;
|
|
93
|
+
margin-top: 0.15rem;
|
|
94
|
+
}
|
|
95
|
+
.belt-welcome .next-steps h2 {
|
|
96
|
+
font-size: 0.92rem;
|
|
97
|
+
margin-bottom: 0.45rem;
|
|
98
|
+
color: #2d3748;
|
|
99
|
+
text-align: left;
|
|
100
|
+
}
|
|
101
|
+
.belt-welcome .next-steps ol {
|
|
102
|
+
padding-left: 1.2rem;
|
|
103
|
+
margin: 0;
|
|
104
|
+
}
|
|
105
|
+
.belt-welcome .next-steps li {
|
|
106
|
+
margin-bottom: 0.4rem;
|
|
107
|
+
color: #4a5568;
|
|
108
|
+
line-height: 1.4;
|
|
109
|
+
font-size: 0.88rem;
|
|
110
|
+
}
|
|
111
|
+
.belt-welcome .next-steps li:last-child { margin-bottom: 0; }
|
|
112
|
+
.belt-welcome .next-steps code {
|
|
113
|
+
background: rgba(99, 45, 145, 0.1);
|
|
114
|
+
padding: 0.12rem 0.35rem;
|
|
115
|
+
border-radius: 4px;
|
|
116
|
+
color: #632d91;
|
|
117
|
+
font-size: 0.78rem;
|
|
118
|
+
word-break: break-word;
|
|
119
|
+
}
|
|
120
|
+
.belt-welcome .footer-note {
|
|
121
|
+
margin-top: 0.75rem;
|
|
122
|
+
padding-top: 0.65rem;
|
|
123
|
+
border-top: 1px solid rgba(45, 55, 72, 0.1);
|
|
124
|
+
font-size: 0.78rem;
|
|
125
|
+
color: #718096;
|
|
126
|
+
text-align: center;
|
|
127
|
+
}
|
|
128
|
+
.belt-welcome .footer-note code {
|
|
129
|
+
background: rgba(45, 55, 72, 0.06);
|
|
130
|
+
padding: 0.08rem 0.3rem;
|
|
131
|
+
border-radius: 3px;
|
|
132
|
+
color: #4a5568;
|
|
133
|
+
}
|
|
134
|
+
.belt-welcome .status-main {
|
|
135
|
+
padding: 3rem 2rem;
|
|
136
|
+
text-align: center;
|
|
137
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
138
|
+
color: #4a5568;
|
|
139
|
+
min-height: 100vh;
|
|
140
|
+
display: flex;
|
|
141
|
+
flex-direction: column;
|
|
142
|
+
align-items: center;
|
|
143
|
+
justify-content: center;
|
|
144
|
+
}
|
|
145
|
+
.belt-welcome .status-main h1 {
|
|
146
|
+
font-size: 1.75rem;
|
|
147
|
+
color: #2d3748;
|
|
148
|
+
margin-bottom: 0.75rem;
|
|
149
|
+
}
|
|
150
|
+
.belt-welcome .error { color: #b91c1c; margin-top: 0.75rem; }
|
|
151
|
+
.belt-welcome .muted { color: #6b7280; margin-top: 0.5rem; font-size: 0.95rem; }
|
|
152
|
+
@media (max-height: 640px) {
|
|
153
|
+
.belt-welcome .overlay { padding: 1rem 1.15rem; }
|
|
154
|
+
.belt-welcome .overlay h1 { font-size: 1.5rem; }
|
|
155
|
+
.belt-welcome .overlay .subtitle { font-size: 0.88rem; }
|
|
156
|
+
.belt-welcome .stack-check { margin: 0.7rem 0 0.55rem; }
|
|
157
|
+
.belt-welcome .check-item { padding: 0.28rem 0.55rem; font-size: 0.8rem; }
|
|
158
|
+
.belt-welcome .next-steps li { font-size: 0.82rem; }
|
|
159
|
+
}
|
|
160
|
+
`
|
|
161
|
+
|
|
162
|
+
function StackItem({ ok, label }) {
|
|
163
|
+
// DynamoDB false is expected (no tables yet) — neutral, not a warning.
|
|
164
|
+
const className = ok ? 'check-item check-pass' : 'check-item check-neutral'
|
|
165
|
+
return (
|
|
166
|
+
<div className={className}>
|
|
167
|
+
<span className="icon" aria-hidden="true">{ok ? '✓' : '○'}</span>
|
|
168
|
+
<span>{label}</span>
|
|
169
|
+
</div>
|
|
170
|
+
)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Turn "Label: belt some command" into mixed text + <code> when possible.
|
|
174
|
+
function StepLine({ step }) {
|
|
175
|
+
const match = typeof step === 'string' ? step.match(/^(.*?):\s+(belt\s.+)$/) : null
|
|
176
|
+
if (match) {
|
|
177
|
+
return (
|
|
178
|
+
<>
|
|
179
|
+
{match[1]}: <code>{match[2]}</code>
|
|
180
|
+
</>
|
|
181
|
+
)
|
|
182
|
+
}
|
|
183
|
+
return step
|
|
184
|
+
}
|
|
185
|
+
|
|
1
186
|
function Home() {
|
|
187
|
+
const [welcome, setWelcome] = useState(null)
|
|
188
|
+
const [error, setError] = useState(null)
|
|
189
|
+
const [loading, setLoading] = useState(true)
|
|
190
|
+
|
|
191
|
+
useEffect(() => {
|
|
192
|
+
let cancelled = false
|
|
193
|
+
|
|
194
|
+
apiClient('/')
|
|
195
|
+
.then((data) => {
|
|
196
|
+
if (!cancelled) {
|
|
197
|
+
setWelcome(data)
|
|
198
|
+
setLoading(false)
|
|
199
|
+
}
|
|
200
|
+
})
|
|
201
|
+
.catch((err) => {
|
|
202
|
+
if (!cancelled) {
|
|
203
|
+
setError(err.message || 'Failed to reach API')
|
|
204
|
+
setLoading(false)
|
|
205
|
+
}
|
|
206
|
+
})
|
|
207
|
+
|
|
208
|
+
return () => { cancelled = true }
|
|
209
|
+
}, [])
|
|
210
|
+
|
|
211
|
+
if (loading) {
|
|
212
|
+
return (
|
|
213
|
+
<div className="belt-welcome">
|
|
214
|
+
<style>{welcomeStyles}</style>
|
|
215
|
+
<main className="status-main">
|
|
216
|
+
<p className="muted">Connecting to API…</p>
|
|
217
|
+
</main>
|
|
218
|
+
</div>
|
|
219
|
+
)
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (error) {
|
|
223
|
+
return (
|
|
224
|
+
<div className="belt-welcome">
|
|
225
|
+
<style>{welcomeStyles}</style>
|
|
226
|
+
<main className="status-main">
|
|
227
|
+
<h1><%= @module_name %></h1>
|
|
228
|
+
<p className="error">Could not load welcome data from the API.</p>
|
|
229
|
+
<p className="muted">{error}</p>
|
|
230
|
+
<p className="muted" style={{ marginTop: '1rem' }}>
|
|
231
|
+
Check <code>VITE_API_URL</code> (run <code>belt frontend env dev</code>) and that CORS allows this origin.
|
|
232
|
+
</p>
|
|
233
|
+
</main>
|
|
234
|
+
</div>
|
|
235
|
+
)
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const stack = welcome.stack || {}
|
|
239
|
+
const dynamoOk = !!stack.dynamodb
|
|
240
|
+
const title = welcome.title || '<%= @module_name %>'
|
|
241
|
+
const subtitle = welcome.subtitle || 'Your Belt app is running.'
|
|
242
|
+
|
|
2
243
|
return (
|
|
3
|
-
<
|
|
4
|
-
<
|
|
5
|
-
|
|
6
|
-
|
|
244
|
+
<div className="belt-welcome">
|
|
245
|
+
<style>{welcomeStyles}</style>
|
|
246
|
+
|
|
247
|
+
<div className="hero">
|
|
248
|
+
{welcome.background_image ? (
|
|
249
|
+
<img
|
|
250
|
+
src={welcome.background_image}
|
|
251
|
+
alt="Belt"
|
|
252
|
+
className="hero-bg"
|
|
253
|
+
/>
|
|
254
|
+
) : (
|
|
255
|
+
<div className="hero-bg" style={{ minHeight: '100vh', background: '#1a202c' }} />
|
|
256
|
+
)}
|
|
257
|
+
<div className="overlay">
|
|
258
|
+
<h1>{title}</h1>
|
|
259
|
+
<p className="subtitle">{subtitle}</p>
|
|
260
|
+
|
|
261
|
+
<div className="stack-check">
|
|
262
|
+
<StackItem ok={!!stack.api_gateway} label="API Gateway" />
|
|
263
|
+
<span className="arrow">→</span>
|
|
264
|
+
<StackItem ok={!!stack.lambda} label="Lambda" />
|
|
265
|
+
<span className="arrow">→</span>
|
|
266
|
+
<StackItem ok={dynamoOk} label="DynamoDB" />
|
|
267
|
+
</div>
|
|
268
|
+
|
|
269
|
+
{Array.isArray(welcome.next_steps) && welcome.next_steps.length > 0 && (
|
|
270
|
+
<div className="next-steps">
|
|
271
|
+
<h2>Next Steps</h2>
|
|
272
|
+
<ol>
|
|
273
|
+
{welcome.next_steps.map((step, i) => (
|
|
274
|
+
<li key={i}><StepLine step={step} /></li>
|
|
275
|
+
))}
|
|
276
|
+
</ol>
|
|
277
|
+
</div>
|
|
278
|
+
)}
|
|
279
|
+
|
|
280
|
+
<p className="footer-note">
|
|
281
|
+
Edit <code>src/pages/Home.jsx</code> to replace this page.
|
|
282
|
+
</p>
|
|
283
|
+
</div>
|
|
284
|
+
</div>
|
|
285
|
+
</div>
|
|
7
286
|
)
|
|
8
287
|
}
|
|
9
288
|
|
|
@@ -21,17 +21,27 @@ terraform {
|
|
|
21
21
|
resource "conveyor_belt" "main" {
|
|
22
22
|
provider = conveyor-belt
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
# path.module is infrastructure/modules/app — climb three levels to project root
|
|
25
|
+
source = "${path.module}/../../../config/routes.tf.rb"
|
|
25
26
|
app_name = var.app_name
|
|
26
27
|
lambda_source_dir = "${path.module}/../../../lambda"
|
|
27
28
|
lambda_shared_dirs = ["controllers", "helpers", "lib", "models", "views"]
|
|
28
|
-
|
|
29
|
+
<% if @frontend -%>
|
|
30
|
+
# CloudFront first so SPA→API CORS works out of the box (tutorial footgun fixed).
|
|
31
|
+
# localhost stays via env frontend_urls for local `belt server` dev.
|
|
32
|
+
frontend_urls = concat(
|
|
33
|
+
["https://${aws_cloudfront_distribution.frontend.domain_name}"],
|
|
34
|
+
var.frontend_urls
|
|
35
|
+
)
|
|
36
|
+
<% else -%>
|
|
37
|
+
frontend_urls = var.frontend_urls
|
|
38
|
+
<% end -%>
|
|
29
39
|
friendly_errors = var.environment != "prod"
|
|
30
40
|
|
|
31
41
|
custom_domain_name = local.dns_enabled ? local.api_domain : ""
|
|
32
42
|
|
|
33
43
|
# Per-lambda configuration from config/lambda/*.yml (database.yml style)
|
|
34
|
-
lambda_config_dir = "${path.module}
|
|
44
|
+
lambda_config_dir = "${path.module}/../../../config/lambda"
|
|
35
45
|
|
|
36
46
|
# Dynamic value references — YAML env_vars use ref(name) to pull these in
|
|
37
47
|
lambda_env_refs = var.lambda_env_refs
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
Belt.application.routes.draw do
|
|
2
2
|
namespace :api do
|
|
3
|
-
|
|
3
|
+
# Public stack-check / welcome (HTML for browsers, JSON for the SPA shell)
|
|
4
|
+
get "/", action: :show, controller: :welcome, auth: :none
|
|
4
5
|
# resources :posts
|
|
5
6
|
end
|
|
6
7
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: belt
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.2.
|
|
4
|
+
version: 0.2.10
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Stowzilla
|
|
@@ -76,6 +76,7 @@ files:
|
|
|
76
76
|
- lib/belt/cli/console_command.rb
|
|
77
77
|
- lib/belt/cli/deploy_command.rb
|
|
78
78
|
- lib/belt/cli/destroy_command.rb
|
|
79
|
+
- lib/belt/cli/doctor_command.rb
|
|
79
80
|
- lib/belt/cli/env_resolver.rb
|
|
80
81
|
- lib/belt/cli/environment_command.rb
|
|
81
82
|
- lib/belt/cli/environment_config.rb
|
|
@@ -88,6 +89,7 @@ files:
|
|
|
88
89
|
- lib/belt/cli/generator_registry.rb
|
|
89
90
|
- lib/belt/cli/lambda_config_command.rb
|
|
90
91
|
- lib/belt/cli/new_command.rb
|
|
92
|
+
- lib/belt/cli/path_gem_materializer.rb
|
|
91
93
|
- lib/belt/cli/routes_command.rb
|
|
92
94
|
- lib/belt/cli/routes_command/route_inference.rb
|
|
93
95
|
- lib/belt/cli/routes_command/schema_loader.rb
|
|
@@ -97,10 +99,12 @@ files:
|
|
|
97
99
|
- lib/belt/cli/tasks_command.rb
|
|
98
100
|
- lib/belt/cli/terraform_command.rb
|
|
99
101
|
- lib/belt/cli/views_command.rb
|
|
102
|
+
- lib/belt/configuration.rb
|
|
100
103
|
- lib/belt/controllers/welcome_controller.rb
|
|
101
104
|
- lib/belt/helpers/cors_origin.rb
|
|
102
105
|
- lib/belt/helpers/error_logging.rb
|
|
103
106
|
- lib/belt/helpers/response.rb
|
|
107
|
+
- lib/belt/http_status.rb
|
|
104
108
|
- lib/belt/inflector.rb
|
|
105
109
|
- lib/belt/lambda_handler.rb
|
|
106
110
|
- lib/belt/observability.rb
|
|
@@ -112,6 +116,7 @@ files:
|
|
|
112
116
|
- lib/belt/version.rb
|
|
113
117
|
- lib/belt/views/welcome/show.html.erb
|
|
114
118
|
- lib/belt_controller/base.rb
|
|
119
|
+
- lib/belt_controller/implicit_response.rb
|
|
115
120
|
- lib/templates/environment/backend.tf.erb
|
|
116
121
|
- lib/templates/environment/main.tf.erb
|
|
117
122
|
- lib/templates/environment/outputs.tf.erb
|
|
@@ -160,6 +165,19 @@ metadata:
|
|
|
160
165
|
source_code_uri: https://github.com/stowzilla/belt
|
|
161
166
|
changelog_uri: https://github.com/stowzilla/belt/blob/master/CHANGELOG.md
|
|
162
167
|
rubygems_mfa_required: 'true'
|
|
168
|
+
post_install_message: |2+
|
|
169
|
+
|
|
170
|
+
╭────────────────────────────────────────────╮
|
|
171
|
+
│ Belt installed successfully! │
|
|
172
|
+
│ │
|
|
173
|
+
│ Run `belt doctor` to verify your setup: │
|
|
174
|
+
│ • AWS CLI │
|
|
175
|
+
│ • Terraform │
|
|
176
|
+
│ • AWS credentials & authentication │
|
|
177
|
+
│ │
|
|
178
|
+
│ Then: belt new <app_name> │
|
|
179
|
+
╰────────────────────────────────────────────╯
|
|
180
|
+
|
|
163
181
|
rdoc_options: []
|
|
164
182
|
require_paths:
|
|
165
183
|
- lib
|
|
@@ -178,3 +196,4 @@ rubygems_version: 3.6.9
|
|
|
178
196
|
specification_version: 4
|
|
179
197
|
summary: Belt - a utility toolkit for Ruby applications
|
|
180
198
|
test_files: []
|
|
199
|
+
...
|