belt 0.2.9 → 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/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
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4a550043d0d16c901e656de4a9d89a0f473d77778121e2b27fb8791f3edf74d4
|
|
4
|
+
data.tar.gz: f9404b6e69d725650ee612af06b96917886d2856fca14955bb99cb9555811dcd
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e719bed32a829bf952c22885d63a1415ea292c3b1770a7f7bdb398ef874a5182a1b8208654e2672d6d7fcccbb4b2e9f24a82186708ed3982d9ca7ec94d9bedbf
|
|
7
|
+
data.tar.gz: 32fa436e2a97b5d3597af8d2afcfb8657c5de91f12a01b551d8368be97a80fdfd544e5b6b711a930ff741c1e8228d393088893700f13c719b454b59e01c3d6a0
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,92 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.10
|
|
4
|
+
|
|
5
|
+
### Quiet `belt new` (with optional verbose)
|
|
6
|
+
|
|
7
|
+
`belt new` prints a short phase summary by default (skeleton, environments, frontend,
|
|
8
|
+
bundle, state bucket) instead of every path plus AWS/npm noise.
|
|
9
|
+
|
|
10
|
+
Use `-v` / `--verbose` for Rails-style per-file `create` lines when you want the
|
|
11
|
+
inventory. Nested generators skip their own next-steps banners; the final success
|
|
12
|
+
block still owns next steps. State-bucket setup stays non-interactive either way.
|
|
13
|
+
|
|
14
|
+
### State bucket naming (global uniqueness)
|
|
15
|
+
|
|
16
|
+
S3 bucket names are a **global** namespace across all AWS accounts. The previous default
|
|
17
|
+
`belt-terraform-state` only works for the first account that creates it — everyone else
|
|
18
|
+
hits "owned by a different AWS account".
|
|
19
|
+
|
|
20
|
+
**Fix:** shared bucket is still one-per-account (all apps share it), but the name is now:
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
belt-terraform-state-<account_id>
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
- Still one bucket for all belt apps in an account (state key: `<app>/<env>/terraform.tfstate`)
|
|
27
|
+
- `--bucket` still overrides when you want a custom name
|
|
28
|
+
- `belt setup state` rewrites `backend.tf` with the resolved name
|
|
29
|
+
- Existing installs that already own `belt-terraform-state` can keep using `--bucket belt-terraform-state`
|
|
30
|
+
|
|
31
|
+
### Implicit responses (Rails-style assigns)
|
|
32
|
+
|
|
33
|
+
Controllers can set instance variables and skip the explicit `success_response` call:
|
|
34
|
+
|
|
35
|
+
```ruby
|
|
36
|
+
def index
|
|
37
|
+
@posts = Post.all
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def show
|
|
41
|
+
@post = Post.find(params[:id])
|
|
42
|
+
end
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Belt auto-builds `success_response({ posts: [...] })` / `{ post: {...} }` from assigns set
|
|
46
|
+
during the action. Serialization:
|
|
47
|
+
|
|
48
|
+
- Models → `to_h` (ActiveItem already defines this)
|
|
49
|
+
- `ActiveItem::Relation` / Enumerable → map each record via `to_h`
|
|
50
|
+
- Nested hashes/arrays recurse
|
|
51
|
+
|
|
52
|
+
Explicit `success_response` / `error_response` / `html_response` / `render` / `head` still win
|
|
53
|
+
when returned. Rescue handlers still use `error_response`.
|
|
54
|
+
|
|
55
|
+
### Default format (`:json` / `:html`)
|
|
56
|
+
|
|
57
|
+
```ruby
|
|
58
|
+
# App-wide (lambda/config/environment.rb)
|
|
59
|
+
Belt.configure do |c|
|
|
60
|
+
c.default_format = :json # default — API first
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Per-controller override (inherits down the chain)
|
|
64
|
+
class PagesController < ApplicationController
|
|
65
|
+
self.default_format = :html
|
|
66
|
+
end
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
| `default_format` | Implicit response (no explicit helper) |
|
|
70
|
+
|---|---|
|
|
71
|
+
| `:json` (default) | `success_response` from assigns |
|
|
72
|
+
| `:html` | `render` template `views/<controller>/<action>.html.erb` (missing → `TemplateNotFound`) |
|
|
73
|
+
|
|
74
|
+
### Symbol status codes, `head`, `response_status`
|
|
75
|
+
|
|
76
|
+
```ruby
|
|
77
|
+
success_response({ post: post.to_h }, :created) # 201
|
|
78
|
+
error_response("Nope", :unprocessable_entity) # 422
|
|
79
|
+
head :no_content # 204 empty body
|
|
80
|
+
head :created # 201 empty body
|
|
81
|
+
|
|
82
|
+
def create
|
|
83
|
+
@post = Post.create!(...)
|
|
84
|
+
response_status :created # 201 + implicit assigns body
|
|
85
|
+
end
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Bare integers still work. Symbols match the Rack/Rails names (`:created`, `:not_found`, …).
|
|
89
|
+
|
|
3
90
|
## 0.3.0
|
|
4
91
|
|
|
5
92
|
### Pre-Deploy Backups
|
data/README.md
CHANGED
|
@@ -71,24 +71,31 @@ require "belt"
|
|
|
71
71
|
class PostsController < BeltController::Base
|
|
72
72
|
before_action :authenticate!
|
|
73
73
|
|
|
74
|
+
# Implicit response: assigns become the JSON body
|
|
75
|
+
# → { "posts": [ { "id": "...", "title": "..." }, ... ] }
|
|
74
76
|
def index
|
|
75
|
-
posts = Post.where(user_id: current_user_id, index: "UserIndex")
|
|
76
|
-
success_response(posts.map(&:attributes))
|
|
77
|
+
@posts = Post.where(user_id: current_user_id, index: "UserIndex")
|
|
77
78
|
end
|
|
78
79
|
|
|
79
80
|
def show
|
|
80
|
-
post = Post.find(params["id"])
|
|
81
|
-
success_response(post.attributes)
|
|
81
|
+
@post = Post.find(params["id"])
|
|
82
82
|
end
|
|
83
83
|
|
|
84
|
+
# Implicit body + non-200 status
|
|
84
85
|
def create
|
|
85
86
|
attrs = params.require(:post).permit(:title, :body).to_h
|
|
86
|
-
post = Post.create!(attrs.merge(user_id: current_user_id))
|
|
87
|
-
|
|
87
|
+
@post = Post.create!(attrs.merge(user_id: current_user_id))
|
|
88
|
+
response_status :created
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def destroy
|
|
92
|
+
Post.find(params["id"]).destroy
|
|
93
|
+
head :no_content
|
|
88
94
|
end
|
|
89
95
|
end
|
|
90
96
|
```
|
|
91
97
|
|
|
98
|
+
|
|
92
99
|
### 4. Lambda entry point
|
|
93
100
|
|
|
94
101
|
Use `Belt::LambdaHandler` to get automatic observability, CORS preflight handling, and error wrapping:
|
|
@@ -199,9 +206,42 @@ end
|
|
|
199
206
|
|
|
200
207
|
```ruby
|
|
201
208
|
success_response({ id: "123", name: "Example" }) # 200 JSON with CORS
|
|
202
|
-
success_response({ id: "123" },
|
|
203
|
-
error_response("Not found",
|
|
209
|
+
success_response({ id: "123" }, :created) # 201 Created (symbol or int)
|
|
210
|
+
error_response("Not found", :not_found) # 404 JSON error
|
|
211
|
+
error_response("Nope", :unprocessable_entity) # 422
|
|
204
212
|
html_response("<h1>Hello</h1>") # 200 HTML with CORS
|
|
213
|
+
head :no_content # 204 empty body
|
|
214
|
+
head :created # 201 empty body
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### Default format (API vs HTML)
|
|
218
|
+
|
|
219
|
+
```ruby
|
|
220
|
+
# App-wide — typically in lambda/config/environment.rb
|
|
221
|
+
Belt.configure do |config|
|
|
222
|
+
config.default_format = :json # default
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
# Per-controller override
|
|
226
|
+
class PagesController < ApplicationController
|
|
227
|
+
self.default_format = :html
|
|
228
|
+
end
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
- **`:json`** (default): action assigns → `success_response({ posts: [...] })`
|
|
232
|
+
- **`:html`**: action assigns stay on the controller; Belt implicitly `render`s
|
|
233
|
+
`views/<controller>/<action>.html.erb`. Missing template raises `Belt::TemplateNotFound`
|
|
234
|
+
(no silent JSON fallback).
|
|
235
|
+
- Explicit helpers always win: `success_response`, `error_response`, `html_response`,
|
|
236
|
+
`render`, `head`.
|
|
237
|
+
|
|
238
|
+
### Non-200 with implicit assigns
|
|
239
|
+
|
|
240
|
+
```ruby
|
|
241
|
+
def create
|
|
242
|
+
@post = Post.create!(...)
|
|
243
|
+
response_status :created # → 201 + { post: {...} }
|
|
244
|
+
end
|
|
205
245
|
```
|
|
206
246
|
|
|
207
247
|
## Controller Discovery
|
data/lib/belt/action_router.rb
CHANGED
|
@@ -165,7 +165,7 @@ module Belt
|
|
|
165
165
|
def error_response(message, status_code, event = nil)
|
|
166
166
|
origin = Belt::Helpers::CorsOrigin.resolve_origin(Belt::Helpers::CorsOrigin.origin_from_event(event))
|
|
167
167
|
headers = {
|
|
168
|
-
'Access-Control-Allow-Headers' => 'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token',
|
|
168
|
+
'Access-Control-Allow-Headers' => 'Content-Type,Accept,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token',
|
|
169
169
|
'Access-Control-Allow-Methods' => 'GET,POST,PUT,DELETE,PATCH,OPTIONS',
|
|
170
170
|
'Content-Type' => 'application/json'
|
|
171
171
|
}
|
data/lib/belt/assets/welcome.css
CHANGED
|
@@ -1,82 +1,136 @@
|
|
|
1
1
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
2
|
+
html, body {
|
|
3
|
+
height: 100%;
|
|
4
|
+
}
|
|
2
5
|
body {
|
|
3
6
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
4
|
-
background: #
|
|
7
|
+
background: #0b1220;
|
|
5
8
|
color: #2d3748;
|
|
6
9
|
min-height: 100vh;
|
|
10
|
+
overflow: hidden;
|
|
7
11
|
}
|
|
8
12
|
|
|
9
|
-
/* Hero
|
|
13
|
+
/* Hero fills the viewport — all content lives in the overlay (no scroll) */
|
|
10
14
|
.hero {
|
|
11
15
|
position: relative;
|
|
12
16
|
width: 100%;
|
|
17
|
+
height: 100vh;
|
|
13
18
|
overflow: hidden;
|
|
14
19
|
}
|
|
15
20
|
.hero-bg {
|
|
16
21
|
width: 100%;
|
|
17
|
-
height:
|
|
22
|
+
height: 100%;
|
|
23
|
+
object-fit: cover;
|
|
24
|
+
/* Pin the logo / sky — crop hills at the bottom if the viewport is short */
|
|
25
|
+
object-position: top center;
|
|
18
26
|
display: block;
|
|
19
27
|
}
|
|
20
28
|
.overlay {
|
|
21
29
|
position: absolute;
|
|
22
|
-
top:
|
|
30
|
+
top: 50%;
|
|
23
31
|
left: 50%;
|
|
24
32
|
transform: translate(-50%, -50%);
|
|
25
|
-
padding:
|
|
26
|
-
background: rgba(255, 255, 255, 0.
|
|
27
|
-
backdrop-filter: blur(
|
|
28
|
-
-webkit-backdrop-filter: blur(
|
|
29
|
-
border-radius:
|
|
33
|
+
padding: 1.35rem 1.6rem 1.25rem;
|
|
34
|
+
background: rgba(255, 255, 255, 0.55);
|
|
35
|
+
backdrop-filter: blur(8px);
|
|
36
|
+
-webkit-backdrop-filter: blur(8px);
|
|
37
|
+
border-radius: 14px;
|
|
30
38
|
text-align: center;
|
|
31
|
-
max-width:
|
|
32
|
-
width:
|
|
33
|
-
|
|
39
|
+
max-width: 560px;
|
|
40
|
+
width: min(92vw, 560px);
|
|
41
|
+
max-height: min(92vh, 620px);
|
|
42
|
+
overflow: auto;
|
|
43
|
+
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.18);
|
|
34
44
|
}
|
|
35
45
|
.overlay h1 {
|
|
36
|
-
font-size:
|
|
46
|
+
font-size: 1.85rem;
|
|
37
47
|
font-weight: 700;
|
|
38
48
|
color: #2d3748;
|
|
39
|
-
margin-bottom: 0.
|
|
49
|
+
margin-bottom: 0.3rem;
|
|
50
|
+
line-height: 1.2;
|
|
40
51
|
}
|
|
41
52
|
.overlay .subtitle {
|
|
42
|
-
font-size:
|
|
53
|
+
font-size: 0.95rem;
|
|
43
54
|
color: #4a5568;
|
|
55
|
+
line-height: 1.4;
|
|
44
56
|
}
|
|
45
57
|
|
|
46
|
-
/*
|
|
47
|
-
.container {
|
|
48
|
-
max-width: 700px;
|
|
49
|
-
margin: 0 auto;
|
|
50
|
-
padding: 2rem;
|
|
51
|
-
}
|
|
58
|
+
/* Stack + next steps sit inside the white card */
|
|
52
59
|
.stack-check {
|
|
53
60
|
display: flex;
|
|
54
61
|
align-items: center;
|
|
55
62
|
justify-content: center;
|
|
56
|
-
gap: 0.
|
|
57
|
-
margin
|
|
63
|
+
gap: 0.4rem;
|
|
64
|
+
margin: 1rem 0 0.85rem;
|
|
58
65
|
flex-wrap: wrap;
|
|
59
66
|
}
|
|
60
67
|
.check-item {
|
|
61
68
|
display: flex;
|
|
62
69
|
align-items: center;
|
|
63
|
-
gap: 0.
|
|
64
|
-
padding: 0.
|
|
70
|
+
gap: 0.35rem;
|
|
71
|
+
padding: 0.35rem 0.7rem;
|
|
65
72
|
border-radius: 8px;
|
|
66
73
|
font-weight: 500;
|
|
74
|
+
font-size: 0.88rem;
|
|
67
75
|
}
|
|
68
76
|
.check-pass {
|
|
69
|
-
background: rgba(39, 174, 96, 0.
|
|
70
|
-
border: 1px solid rgba(39, 174, 96, 0.
|
|
77
|
+
background: rgba(39, 174, 96, 0.12);
|
|
78
|
+
border: 1px solid rgba(39, 174, 96, 0.35);
|
|
71
79
|
color: #1e8449;
|
|
72
80
|
}
|
|
81
|
+
/* Optional / empty DynamoDB — not an error, just not in use yet */
|
|
82
|
+
.check-neutral {
|
|
83
|
+
background: rgba(113, 128, 150, 0.1);
|
|
84
|
+
border: 1px solid rgba(113, 128, 150, 0.28);
|
|
85
|
+
color: #4a5568;
|
|
86
|
+
}
|
|
73
87
|
.check-warn {
|
|
74
88
|
background: rgba(243, 156, 18, 0.1);
|
|
75
89
|
border: 1px solid rgba(243, 156, 18, 0.3);
|
|
76
90
|
color: #d68910;
|
|
77
91
|
}
|
|
78
|
-
.icon { font-size:
|
|
79
|
-
.arrow { color: #718096; font-size: 1.
|
|
92
|
+
.icon { font-size: 1rem; }
|
|
93
|
+
.arrow { color: #718096; font-size: 1.05rem; }
|
|
94
|
+
|
|
95
|
+
.next-steps {
|
|
96
|
+
text-align: left;
|
|
97
|
+
border-top: 1px solid rgba(45, 55, 72, 0.12);
|
|
98
|
+
padding-top: 0.75rem;
|
|
99
|
+
margin-top: 0.15rem;
|
|
100
|
+
}
|
|
101
|
+
.next-steps h2 {
|
|
102
|
+
font-size: 0.92rem;
|
|
103
|
+
margin-bottom: 0.45rem;
|
|
104
|
+
color: #2d3748;
|
|
105
|
+
text-align: left;
|
|
106
|
+
}
|
|
107
|
+
.next-steps ol {
|
|
108
|
+
padding-left: 1.2rem;
|
|
109
|
+
margin: 0;
|
|
110
|
+
}
|
|
111
|
+
.next-steps li {
|
|
112
|
+
margin-bottom: 0.4rem;
|
|
113
|
+
color: #4a5568;
|
|
114
|
+
line-height: 1.4;
|
|
115
|
+
font-size: 0.88rem;
|
|
116
|
+
}
|
|
117
|
+
.next-steps li:last-child { margin-bottom: 0; }
|
|
118
|
+
.next-steps code,
|
|
119
|
+
.info code {
|
|
120
|
+
background: rgba(99, 45, 145, 0.1);
|
|
121
|
+
padding: 0.12rem 0.35rem;
|
|
122
|
+
border-radius: 4px;
|
|
123
|
+
color: #632d91;
|
|
124
|
+
font-size: 0.78rem;
|
|
125
|
+
word-break: break-word;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/* Legacy blocks (local server welcome, older layouts) */
|
|
129
|
+
.container {
|
|
130
|
+
max-width: 700px;
|
|
131
|
+
margin: 0 auto;
|
|
132
|
+
padding: 2rem;
|
|
133
|
+
}
|
|
80
134
|
.info {
|
|
81
135
|
background: #ffffff;
|
|
82
136
|
border: 1px solid #e2e8f0;
|
|
@@ -87,26 +141,44 @@ body {
|
|
|
87
141
|
.info p { margin-bottom: 0.5rem; color: #4a5568; }
|
|
88
142
|
.info p:last-child { margin-bottom: 0; }
|
|
89
143
|
.info strong { color: #2d3748; }
|
|
90
|
-
.
|
|
91
|
-
|
|
92
|
-
|
|
144
|
+
.dynamodb-hint {
|
|
145
|
+
text-align: center;
|
|
146
|
+
margin: 0.5rem 0;
|
|
147
|
+
color: #4a5568;
|
|
148
|
+
font-size: 0.9rem;
|
|
149
|
+
line-height: 1.45;
|
|
150
|
+
}
|
|
151
|
+
.dynamodb-hint code {
|
|
152
|
+
background: rgba(99, 45, 145, 0.1);
|
|
153
|
+
padding: 0.12rem 0.35rem;
|
|
93
154
|
border-radius: 4px;
|
|
94
155
|
color: #632d91;
|
|
95
|
-
font-size: 0.
|
|
156
|
+
font-size: 0.8rem;
|
|
157
|
+
word-break: break-word;
|
|
96
158
|
}
|
|
97
|
-
.
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
border-
|
|
101
|
-
|
|
159
|
+
.footer-note {
|
|
160
|
+
margin-top: 0.75rem;
|
|
161
|
+
padding-top: 0.65rem;
|
|
162
|
+
border-top: 1px solid rgba(45, 55, 72, 0.1);
|
|
163
|
+
font-size: 0.78rem;
|
|
164
|
+
color: #718096;
|
|
165
|
+
text-align: center;
|
|
102
166
|
}
|
|
103
|
-
.
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
167
|
+
.footer-note code {
|
|
168
|
+
background: rgba(45, 55, 72, 0.06);
|
|
169
|
+
padding: 0.08rem 0.3rem;
|
|
170
|
+
border-radius: 3px;
|
|
171
|
+
color: #4a5568;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/* Tiny viewports: shrink type a bit so everything still fits */
|
|
175
|
+
@media (max-height: 640px) {
|
|
176
|
+
.overlay {
|
|
177
|
+
padding: 1rem 1.15rem;
|
|
178
|
+
}
|
|
179
|
+
.overlay h1 { font-size: 1.5rem; }
|
|
180
|
+
.overlay .subtitle { font-size: 0.88rem; }
|
|
181
|
+
.stack-check { margin: 0.7rem 0 0.55rem; }
|
|
182
|
+
.check-item { padding: 0.28rem 0.55rem; font-size: 0.8rem; }
|
|
183
|
+
.next-steps li { font-size: 0.82rem; }
|
|
112
184
|
}
|
|
@@ -71,20 +71,26 @@ module Belt
|
|
|
71
71
|
def harden_bucket(bucket, audit)
|
|
72
72
|
unless audit[:versioning]
|
|
73
73
|
enable_versioning(bucket)
|
|
74
|
-
|
|
74
|
+
say_security ' enable versioning'
|
|
75
75
|
end
|
|
76
76
|
unless audit[:encryption]
|
|
77
77
|
enable_encryption(bucket)
|
|
78
|
-
|
|
78
|
+
say_security ' enable AES-256 encryption'
|
|
79
79
|
end
|
|
80
80
|
unless audit[:public_access_block]
|
|
81
81
|
block_public_access(bucket)
|
|
82
|
-
|
|
82
|
+
say_security ' enable public access block'
|
|
83
83
|
end
|
|
84
84
|
return if audit[:tls_policy]
|
|
85
85
|
|
|
86
86
|
apply_tls_policy(bucket)
|
|
87
|
-
|
|
87
|
+
say_security ' enable TLS-only bucket policy'
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def say_security(message)
|
|
91
|
+
return if respond_to?(:say, true) && @quiet
|
|
92
|
+
|
|
93
|
+
puts message
|
|
88
94
|
end
|
|
89
95
|
|
|
90
96
|
private
|
|
@@ -9,6 +9,7 @@ require_relative 'terraform_command'
|
|
|
9
9
|
require_relative 'backup_config'
|
|
10
10
|
require_relative 'backup_runner'
|
|
11
11
|
require_relative 'environment_config'
|
|
12
|
+
require_relative 'path_gem_materializer'
|
|
12
13
|
|
|
13
14
|
module Belt
|
|
14
15
|
module CLI
|
|
@@ -150,6 +151,7 @@ module Belt
|
|
|
150
151
|
puts "belt → deploying #{@env} (in #{env_dir}/)\n\n"
|
|
151
152
|
|
|
152
153
|
ensure_lockfile_consistent!
|
|
154
|
+
warn_active_path_gems!
|
|
153
155
|
generate_routes_if_needed
|
|
154
156
|
run_backups unless @skip_backup
|
|
155
157
|
|
|
@@ -457,9 +459,55 @@ module Belt
|
|
|
457
459
|
FileUtils.cp(gemfile, build_dir) if File.exist?(gemfile)
|
|
458
460
|
FileUtils.cp(lockfile, build_dir) if File.exist?(lockfile)
|
|
459
461
|
|
|
462
|
+
copy_vendor_cache(build_dir)
|
|
463
|
+
materialize_path_gems!(build_dir)
|
|
464
|
+
|
|
460
465
|
puts ' 📁 Copied handlers, controllers, models, lib, config'
|
|
461
466
|
end
|
|
462
467
|
|
|
468
|
+
# Stage prebuilt .gem files so Docker `bundle install` can install unreleased
|
|
469
|
+
# gems as normal package installs (with specifications/), not path/git layouts.
|
|
470
|
+
# Prefer project-root vendor/cache (next to Gemfile — Bundler's natural path);
|
|
471
|
+
# fall back to lambda/vendor/cache for older layouts.
|
|
472
|
+
def copy_vendor_cache(build_dir)
|
|
473
|
+
cache_dir = [
|
|
474
|
+
File.join(@project_root, 'vendor', 'cache'),
|
|
475
|
+
File.join(@project_root, 'lambda', 'vendor', 'cache')
|
|
476
|
+
].find { |dir| Dir.exist?(dir) && !Dir.empty?(dir) }
|
|
477
|
+
return unless cache_dir
|
|
478
|
+
|
|
479
|
+
dest = File.join(build_dir, 'vendor', 'cache')
|
|
480
|
+
FileUtils.mkdir_p(dest)
|
|
481
|
+
FileUtils.cp_r(Dir.glob(File.join(cache_dir, '*')), dest)
|
|
482
|
+
gem_count = Dir.glob(File.join(dest, '*.gem')).size
|
|
483
|
+
puts " 📦 Copied vendor/cache (#{gem_count} local gem(s))"
|
|
484
|
+
end
|
|
485
|
+
|
|
486
|
+
# path: gems install under bundler/gems/ with no specifications/ — Lambda's
|
|
487
|
+
# bare `require 'belt'` can't see them. Build real .gem files into the
|
|
488
|
+
# package's vendor/cache and pin versions in the *build* Gemfile/lock only.
|
|
489
|
+
def materialize_path_gems!(build_dir)
|
|
490
|
+
gems = PathGemMaterializer.materialize!(build_dir, project_root: @project_root)
|
|
491
|
+
return if gems.empty?
|
|
492
|
+
|
|
493
|
+
puts " 🔧 Materialized path gem(s) → vendor/cache: #{gems.join(', ')}"
|
|
494
|
+
end
|
|
495
|
+
|
|
496
|
+
# path: gems need host-side materialize before Docker install. --rebuild
|
|
497
|
+
# always does it. Full terraform apply needs a conveyor-belt build that
|
|
498
|
+
# includes path-gem materialize (discord-vendor-cache-gemfile-parent+).
|
|
499
|
+
def warn_active_path_gems!
|
|
500
|
+
lockfile = File.join(@project_root, 'Gemfile.lock')
|
|
501
|
+
return unless File.exist?(lockfile)
|
|
502
|
+
return unless File.read(lockfile).match?(/^PATH\n/)
|
|
503
|
+
|
|
504
|
+
puts ' ⚠ Gemfile.lock has PATH gems (path: in Gemfile).'
|
|
505
|
+
puts " Safe now: belt deploy #{@env} --rebuild (always materializes)."
|
|
506
|
+
puts ' Full terraform apply needs conveyor-belt with path-gem materialize.'
|
|
507
|
+
puts ' Or pin a version + drop a built .gem in vendor/cache/'
|
|
508
|
+
puts ''
|
|
509
|
+
end
|
|
510
|
+
|
|
463
511
|
def build_gems(build_dir)
|
|
464
512
|
puts ' 🐳 Building gems in Docker (Lambda-compatible)...'
|
|
465
513
|
|