belt 0.2.17 → 0.3.14
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 +226 -0
- data/README.md +42 -3
- data/lib/belt/action_router.rb +34 -19
- data/lib/belt/cli/app_detection.rb +30 -3
- data/lib/belt/cli/auth_command.rb +395 -0
- data/lib/belt/cli/console_command.rb +17 -2
- data/lib/belt/cli/deploy_command.rb +51 -17
- data/lib/belt/cli/destroy_command.rb +62 -25
- data/lib/belt/cli/doctor_command.rb +166 -22
- data/lib/belt/cli/environment_command.rb +12 -0
- data/lib/belt/cli/explain_command.rb +112 -0
- data/lib/belt/cli/frontend_command.rb +31 -10
- data/lib/belt/cli/frontend_deploy_command.rb +110 -21
- data/lib/belt/cli/frontend_env_command.rb +56 -19
- data/lib/belt/cli/frontend_env_map.rb +30 -10
- data/lib/belt/cli/frontend_registry.rb +373 -0
- data/lib/belt/cli/frontend_setup_command.rb +71 -5
- data/lib/belt/cli/generate_command.rb +61 -42
- data/lib/belt/cli/index_command.rb +192 -0
- data/lib/belt/cli/lambda_config_command.rb +9 -3
- data/lib/belt/cli/routes_command/request_model_inference.rb +131 -0
- data/lib/belt/cli/routes_command/route_inference.rb +4 -3
- data/lib/belt/cli/routes_command.rb +31 -19
- data/lib/belt/cli/server_command.rb +28 -15
- data/lib/belt/cli/tables_command.rb +3 -1
- data/lib/belt/cli/terraform_command.rb +9 -0
- data/lib/belt/cli/views_command.rb +15 -15
- data/lib/belt/cli/zip_artifact_builder.rb +199 -0
- data/lib/belt/cli.rb +17 -9
- data/lib/belt/docs/backups.md +101 -0
- data/lib/belt/docs/console.md +65 -0
- data/lib/belt/docs/controllers.md +155 -0
- data/lib/belt/docs/deployment.md +166 -0
- data/lib/belt/docs/frontend.md +103 -0
- data/lib/belt/docs/generators.md +137 -0
- data/lib/belt/docs/lambda_handler.md +105 -0
- data/lib/belt/docs/models.md +160 -0
- data/lib/belt/docs/observability.md +94 -0
- data/lib/belt/docs/plugins.md +94 -0
- data/lib/belt/docs/routing.md +138 -0
- data/lib/belt/docs/structure.md +96 -0
- data/lib/belt/inflector.rb +8 -0
- data/lib/belt/route_dsl.rb +318 -100
- data/lib/belt/version.rb +1 -1
- data/lib/templates/frontend/react/env.yml.example +2 -2
- data/lib/templates/generate/auth/cognito.tf.erb +71 -0
- data/lib/templates/generate/auth/cognito_outputs.tf.erb +19 -0
- data/lib/templates/generate/auth/frontend/ConfirmEmail.jsx +55 -0
- data/lib/templates/generate/auth/frontend/Login.jsx +79 -0
- data/lib/templates/generate/auth/frontend/ProtectedRoute.jsx +9 -0
- data/lib/templates/generate/auth/frontend/SignUp.jsx +58 -0
- data/lib/templates/generate/auth/frontend/apiClient.js +34 -0
- data/lib/templates/generate/auth/frontend/auth.css +157 -0
- data/lib/templates/generate/auth/frontend/auth.js +121 -0
- data/lib/templates/generate/model.rb.erb +4 -6
- data/lib/templates/module/frontend.tf.erb +49 -43
- data/lib/templates/module/outputs.tf.erb +1 -1
- data/lib/templates/new_app/AGENTS.md.erb +170 -20
- data/lib/templates/new_app/config/lambda/api.yml.erb +1 -0
- data/lib/templates/new_app/config/routes.rb.erb +8 -3
- data/lib/templates/new_app/lambda/api.rb.erb +1 -1
- metadata +56 -1
|
@@ -1,15 +1,17 @@
|
|
|
1
|
-
# Frontend hosting — S3 + CloudFront
|
|
1
|
+
# Frontend hosting — S3 + CloudFront<% if @include_dns %>
|
|
2
2
|
# Custom domain attached when var.domain is set
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
<% else %>
|
|
4
|
+
# Additional frontend '<%= @frontend.name %>' — CloudFront default domain (no Route53)
|
|
5
|
+
<% end %>
|
|
6
|
+
resource "random_string" "<%= @tf_name %>_suffix" {
|
|
5
7
|
length = 8
|
|
6
8
|
special = false
|
|
7
9
|
upper = false
|
|
8
10
|
}
|
|
9
11
|
|
|
10
12
|
# S3 bucket for frontend static assets
|
|
11
|
-
resource "aws_s3_bucket" "
|
|
12
|
-
bucket = "${var.app_name}
|
|
13
|
+
resource "aws_s3_bucket" "<%= @tf_name %>" {
|
|
14
|
+
bucket = "${replace(var.app_name, "_", "-")}-<%= @bucket_slug %>-${var.environment}-${random_string.<%= @tf_name %>_suffix.result}"
|
|
13
15
|
force_destroy = true
|
|
14
16
|
|
|
15
17
|
lifecycle {
|
|
@@ -17,8 +19,8 @@ resource "aws_s3_bucket" "frontend" {
|
|
|
17
19
|
}
|
|
18
20
|
}
|
|
19
21
|
|
|
20
|
-
resource "aws_s3_bucket_public_access_block" "
|
|
21
|
-
bucket = aws_s3_bucket
|
|
22
|
+
resource "aws_s3_bucket_public_access_block" "<%= @tf_name %>" {
|
|
23
|
+
bucket = aws_s3_bucket.<%= @tf_name %>.id
|
|
22
24
|
|
|
23
25
|
block_public_acls = true
|
|
24
26
|
block_public_policy = true
|
|
@@ -26,8 +28,8 @@ resource "aws_s3_bucket_public_access_block" "frontend" {
|
|
|
26
28
|
restrict_public_buckets = true
|
|
27
29
|
}
|
|
28
30
|
|
|
29
|
-
resource "aws_s3_bucket_server_side_encryption_configuration" "
|
|
30
|
-
bucket = aws_s3_bucket
|
|
31
|
+
resource "aws_s3_bucket_server_side_encryption_configuration" "<%= @tf_name %>" {
|
|
32
|
+
bucket = aws_s3_bucket.<%= @tf_name %>.id
|
|
31
33
|
|
|
32
34
|
rule {
|
|
33
35
|
apply_server_side_encryption_by_default {
|
|
@@ -38,36 +40,36 @@ resource "aws_s3_bucket_server_side_encryption_configuration" "frontend" {
|
|
|
38
40
|
}
|
|
39
41
|
|
|
40
42
|
# CloudFront OAC
|
|
41
|
-
resource "aws_cloudfront_origin_access_control" "
|
|
42
|
-
name = "${var.app_name}-${var.environment}
|
|
43
|
-
description = "OAC for
|
|
43
|
+
resource "aws_cloudfront_origin_access_control" "<%= @tf_name %>" {
|
|
44
|
+
name = "${var.app_name}-${var.environment}-<%= @bucket_slug %>-oac"
|
|
45
|
+
description = "OAC for <%= @bucket_slug %> bucket"
|
|
44
46
|
origin_access_control_origin_type = "s3"
|
|
45
47
|
signing_behavior = "always"
|
|
46
48
|
signing_protocol = "sigv4"
|
|
47
49
|
}
|
|
48
50
|
|
|
49
51
|
# CloudFront distribution
|
|
50
|
-
resource "aws_cloudfront_distribution" "
|
|
52
|
+
resource "aws_cloudfront_distribution" "<%= @tf_name %>" {
|
|
51
53
|
origin {
|
|
52
|
-
domain_name = aws_s3_bucket
|
|
53
|
-
origin_id = "S3-${aws_s3_bucket
|
|
54
|
-
origin_access_control_id = aws_cloudfront_origin_access_control
|
|
54
|
+
domain_name = aws_s3_bucket.<%= @tf_name %>.bucket_regional_domain_name
|
|
55
|
+
origin_id = "S3-${aws_s3_bucket.<%= @tf_name %>.id}"
|
|
56
|
+
origin_access_control_id = aws_cloudfront_origin_access_control.<%= @tf_name %>.id
|
|
55
57
|
}
|
|
56
58
|
|
|
57
59
|
enabled = true
|
|
58
60
|
is_ipv6_enabled = true
|
|
59
61
|
default_root_object = "index.html"
|
|
60
|
-
|
|
62
|
+
<% if @include_dns %>
|
|
61
63
|
# Custom domain aliases (if DNS configured)
|
|
62
64
|
aliases = local.dns_enabled ? compact([
|
|
63
65
|
local.app_domain,
|
|
64
66
|
var.environment == "prod" ? "www.${var.domain}" : ""
|
|
65
67
|
]) : []
|
|
66
|
-
|
|
68
|
+
<% end %>
|
|
67
69
|
default_cache_behavior {
|
|
68
70
|
allowed_methods = ["GET", "HEAD", "OPTIONS"]
|
|
69
71
|
cached_methods = ["GET", "HEAD"]
|
|
70
|
-
target_origin_id = "S3-${aws_s3_bucket
|
|
72
|
+
target_origin_id = "S3-${aws_s3_bucket.<%= @tf_name %>.id}"
|
|
71
73
|
compress = true
|
|
72
74
|
viewer_protocol_policy = "redirect-to-https"
|
|
73
75
|
|
|
@@ -99,22 +101,22 @@ resource "aws_cloudfront_distribution" "frontend" {
|
|
|
99
101
|
}
|
|
100
102
|
|
|
101
103
|
viewer_certificate {
|
|
102
|
-
cloudfront_default_certificate = !local.dns_enabled
|
|
103
|
-
acm_certificate_arn = local.dns_enabled ? aws_acm_certificate_validation.app[0].certificate_arn : null
|
|
104
|
-
ssl_support_method = local.dns_enabled ? "sni-only" : null
|
|
105
|
-
minimum_protocol_version = local.dns_enabled ? "TLSv1.2_2021" : null
|
|
104
|
+
cloudfront_default_certificate = <%= @include_dns ? '!local.dns_enabled' : 'true' %>
|
|
105
|
+
acm_certificate_arn = <%= @include_dns ? 'local.dns_enabled ? aws_acm_certificate_validation.app[0].certificate_arn : null' : 'null' %>
|
|
106
|
+
ssl_support_method = <%= @include_dns ? 'local.dns_enabled ? "sni-only" : null' : 'null' %>
|
|
107
|
+
minimum_protocol_version = <%= @include_dns ? 'local.dns_enabled ? "TLSv1.2_2021" : null' : 'null' %>
|
|
106
108
|
}
|
|
107
109
|
|
|
108
110
|
tags = {
|
|
109
|
-
Name = "${var.app_name}-${var.environment}
|
|
111
|
+
Name = "${var.app_name}-${var.environment}-<%= @bucket_slug %>"
|
|
110
112
|
Environment = var.environment
|
|
111
113
|
ManagedBy = "Terraform"
|
|
112
114
|
}
|
|
113
115
|
}
|
|
114
116
|
|
|
115
117
|
# S3 bucket policy — CloudFront OAC access only
|
|
116
|
-
resource "aws_s3_bucket_policy" "
|
|
117
|
-
bucket = aws_s3_bucket
|
|
118
|
+
resource "aws_s3_bucket_policy" "<%= @tf_name %>" {
|
|
119
|
+
bucket = aws_s3_bucket.<%= @tf_name %>.id
|
|
118
120
|
|
|
119
121
|
policy = jsonencode({
|
|
120
122
|
Version = "2012-10-17"
|
|
@@ -126,55 +128,59 @@ resource "aws_s3_bucket_policy" "frontend" {
|
|
|
126
128
|
Service = "cloudfront.amazonaws.com"
|
|
127
129
|
}
|
|
128
130
|
Action = "s3:GetObject"
|
|
129
|
-
Resource = "${aws_s3_bucket
|
|
131
|
+
Resource = "${aws_s3_bucket.<%= @tf_name %>.arn}/*"
|
|
130
132
|
Condition = {
|
|
131
133
|
StringEquals = {
|
|
132
|
-
"AWS:SourceArn" = aws_cloudfront_distribution
|
|
134
|
+
"AWS:SourceArn" = aws_cloudfront_distribution.<%= @tf_name %>.arn
|
|
133
135
|
}
|
|
134
136
|
}
|
|
135
137
|
}
|
|
136
138
|
]
|
|
137
139
|
})
|
|
138
140
|
|
|
139
|
-
depends_on = [aws_s3_bucket_public_access_block
|
|
141
|
+
depends_on = [aws_s3_bucket_public_access_block.<%= @tf_name %>]
|
|
140
142
|
}
|
|
141
|
-
|
|
143
|
+
<% if @include_dns %>
|
|
142
144
|
# Route53 record for frontend (app domain → CloudFront)
|
|
143
|
-
resource "aws_route53_record" "
|
|
145
|
+
resource "aws_route53_record" "<%= @tf_name %>" {
|
|
144
146
|
count = local.dns_enabled ? 1 : 0
|
|
145
147
|
zone_id = aws_route53_zone.app[0].zone_id
|
|
146
148
|
name = local.app_domain
|
|
147
149
|
type = "A"
|
|
148
150
|
|
|
149
151
|
alias {
|
|
150
|
-
name = aws_cloudfront_distribution
|
|
151
|
-
zone_id = aws_cloudfront_distribution
|
|
152
|
+
name = aws_cloudfront_distribution.<%= @tf_name %>.domain_name
|
|
153
|
+
zone_id = aws_cloudfront_distribution.<%= @tf_name %>.hosted_zone_id
|
|
152
154
|
evaluate_target_health = false
|
|
153
155
|
}
|
|
154
156
|
}
|
|
155
157
|
|
|
156
158
|
# www record for prod only
|
|
157
|
-
resource "aws_route53_record" "
|
|
159
|
+
resource "aws_route53_record" "<%= @tf_name %>_www" {
|
|
158
160
|
count = local.dns_enabled && var.environment == "prod" ? 1 : 0
|
|
159
161
|
zone_id = aws_route53_zone.app[0].zone_id
|
|
160
162
|
name = "www.${var.domain}"
|
|
161
163
|
type = "A"
|
|
162
164
|
|
|
163
165
|
alias {
|
|
164
|
-
name = aws_cloudfront_distribution
|
|
165
|
-
zone_id = aws_cloudfront_distribution
|
|
166
|
+
name = aws_cloudfront_distribution.<%= @tf_name %>.domain_name
|
|
167
|
+
zone_id = aws_cloudfront_distribution.<%= @tf_name %>.hosted_zone_id
|
|
166
168
|
evaluate_target_health = false
|
|
167
169
|
}
|
|
168
170
|
}
|
|
169
|
-
|
|
170
|
-
output "
|
|
171
|
-
value = aws_s3_bucket
|
|
171
|
+
<% end %>
|
|
172
|
+
output "<%= @output_prefix %>_bucket_name" {
|
|
173
|
+
value = aws_s3_bucket.<%= @tf_name %>.id
|
|
172
174
|
}
|
|
173
175
|
|
|
174
|
-
output "
|
|
175
|
-
value = aws_cloudfront_distribution
|
|
176
|
+
output "<%= @output_prefix %>_distribution_id" {
|
|
177
|
+
value = aws_cloudfront_distribution.<%= @tf_name %>.id
|
|
176
178
|
}
|
|
177
179
|
|
|
178
|
-
output "
|
|
179
|
-
|
|
180
|
+
output "<%= @output_prefix %>_url" {
|
|
181
|
+
<% if @include_dns -%>
|
|
182
|
+
value = local.dns_enabled ? "https://${local.app_domain}" : "https://${aws_cloudfront_distribution.<%= @tf_name %>.domain_name}"
|
|
183
|
+
<% else -%>
|
|
184
|
+
value = "https://${aws_cloudfront_distribution.<%= @tf_name %>.domain_name}"
|
|
185
|
+
<% end -%>
|
|
180
186
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
output "api_url" {
|
|
2
2
|
description = "API Gateway base URL (custom domain if configured, otherwise default)"
|
|
3
|
-
value = var.domain != "" ? "https://${local.api_domain}" : values(conveyor_belt.main.api_gateway_urls)[0]
|
|
3
|
+
value = var.domain != "" ? "https://${local.api_domain}/${keys(conveyor_belt.main.api_gateway_urls)[0]}" : values(conveyor_belt.main.api_gateway_urls)[0]
|
|
4
4
|
}
|
|
5
5
|
|
|
6
6
|
output "api_urls" {
|
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
This file explains the project structure, tooling, and conventions for AI agents working in this codebase.
|
|
4
4
|
|
|
5
|
+
## Quick Reference
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
belt explain <topic> # Get documentation on any Belt concept
|
|
9
|
+
belt explain routing # How routes work
|
|
10
|
+
belt explain controllers # Controller patterns and response helpers
|
|
11
|
+
belt explain models # ActiveItem (DynamoDB ORM)
|
|
12
|
+
belt explain deployment # Deploy lifecycle
|
|
13
|
+
belt explain generators # Scaffolding new resources
|
|
14
|
+
belt explain --help # List all available topics
|
|
15
|
+
```
|
|
16
|
+
|
|
5
17
|
## Stack
|
|
6
18
|
|
|
7
19
|
- **Belt** — CLI and runtime framework (like Rails for serverless). Provides Lambda handler, action router, controller base class, and CLI tooling.
|
|
@@ -33,6 +45,79 @@ This file explains the project structure, tooling, and conventions for AI agents
|
|
|
33
45
|
└── AGENTS.md # This file
|
|
34
46
|
```
|
|
35
47
|
|
|
48
|
+
## Common Tasks (Copy-Paste Recipes)
|
|
49
|
+
|
|
50
|
+
### Add a new REST resource
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
belt generate resource order item_id:string quantity:number total:number status:string
|
|
54
|
+
belt setup tables <env>
|
|
55
|
+
belt apply <env>
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
This creates: model, controller (with all CRUD actions), route entry, and DynamoDB schema.
|
|
59
|
+
|
|
60
|
+
### Add a model only (no API endpoint)
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
belt generate model audit_log user_id:string action:string resource:string
|
|
64
|
+
belt setup tables <env>
|
|
65
|
+
belt apply <env>
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Add a controller only (route already exists)
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
belt generate controller admin/reports
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Add a new route to an existing resource
|
|
75
|
+
|
|
76
|
+
Edit `config/routes.rb`:
|
|
77
|
+
```ruby
|
|
78
|
+
# Inside the gateway block, add:
|
|
79
|
+
get "posts/search", controller: :posts, action: :search
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Then add the `search` action to `lambda/controllers/<%= @app_name %>/posts_controller.rb`.
|
|
83
|
+
|
|
84
|
+
### Deploy changes
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
belt deploy <env> # full deploy (init → plan → apply)
|
|
88
|
+
belt deploy <env> --auto # skip confirmation
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Inspect current routes
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
belt routes # human-readable table
|
|
95
|
+
belt routes -f json # machine-readable
|
|
96
|
+
belt routes -g posts # filter by pattern
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Open a console
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
belt c <env> # interactive Ruby session with app loaded
|
|
103
|
+
belt c <env> --run "Post.count" # one-liner
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Check system health
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
belt doctor # verify AWS creds, Terraform, Ruby, etc.
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Add a new environment
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
belt generate environment staging
|
|
116
|
+
belt setup state # if no state bucket exists
|
|
117
|
+
belt setup tables staging
|
|
118
|
+
belt deploy staging
|
|
119
|
+
```
|
|
120
|
+
|
|
36
121
|
## Belt CLI Commands
|
|
37
122
|
|
|
38
123
|
```bash
|
|
@@ -43,12 +128,17 @@ belt generate controller <name> # Generate controller onl
|
|
|
43
128
|
belt generate environment <env_name> # Generate Terraform environment directory
|
|
44
129
|
belt setup state # Create/select S3 state bucket
|
|
45
130
|
belt setup tables <env> # Generate DynamoDB table definitions from schema
|
|
131
|
+
belt explain <topic> # Show documentation for a concept
|
|
46
132
|
belt init <env> # terraform init
|
|
47
133
|
belt plan <env> # terraform plan
|
|
48
134
|
belt apply <env> # terraform apply
|
|
49
135
|
belt destroy <env> # terraform destroy
|
|
50
|
-
belt destroy environment <env> # same (mirrors generate environment)
|
|
51
136
|
belt output <env> # terraform output
|
|
137
|
+
belt routes [-g PATTERN] [-f json] # Show route definitions
|
|
138
|
+
belt contracts [-g PATTERN] [-f json] # Show API contracts
|
|
139
|
+
belt console <env> # Interactive Ruby console
|
|
140
|
+
belt logs <lambda> [-f] [-s 5m] [-e env] # View Lambda logs
|
|
141
|
+
belt doctor # Check dependencies
|
|
52
142
|
```
|
|
53
143
|
|
|
54
144
|
## How Routing Works
|
|
@@ -56,21 +146,50 @@ belt output <env> # terraform output
|
|
|
56
146
|
1. `config/routes.rb` defines routes using a DSL:
|
|
57
147
|
```ruby
|
|
58
148
|
Belt.application.routes.draw do
|
|
59
|
-
|
|
149
|
+
gateway :<%= @app_name %> do
|
|
60
150
|
resources :things, tables: [:things]
|
|
61
151
|
end
|
|
62
152
|
end
|
|
63
153
|
```
|
|
64
154
|
|
|
65
|
-
2. Conveyor Belt creates
|
|
155
|
+
2. Conveyor Belt creates API Gateway routes. URLs look like:
|
|
66
156
|
```
|
|
67
157
|
https://api.<env>.example.com/<%= @app_name %>/things
|
|
68
158
|
```
|
|
69
159
|
|
|
70
|
-
3. The Lambda entry point (`lambda/<%= @app_name %>.rb`) uses `Belt::ActionRouter` to dispatch requests to controllers based on the route manifest
|
|
160
|
+
3. The Lambda entry point (`lambda/<%= @app_name %>.rb`) uses `Belt::ActionRouter` to dispatch requests to controllers based on the route manifest.
|
|
71
161
|
|
|
72
162
|
4. `resources :things` generates: `GET /things`, `POST /things`, `GET /things/:id`, `PUT /things/:id`, `DELETE /things/:id`. **PUT, not PATCH.**
|
|
73
163
|
|
|
164
|
+
### Request Model Wiring (Validation)
|
|
165
|
+
|
|
166
|
+
Belt auto-wires request validation from `config/contracts.rb` using conventions:
|
|
167
|
+
|
|
168
|
+
- `POST /items` → looks for `:create_item` (or `:create_<gateway>_item`)
|
|
169
|
+
- `PUT /items/:id` → looks for `:update_item` (or `:update_<gateway>_item`)
|
|
170
|
+
|
|
171
|
+
**No `request_model:` declaration needed** if your contract names follow this pattern. Belt infers it.
|
|
172
|
+
|
|
173
|
+
### Response Model Wiring (Documentation)
|
|
174
|
+
|
|
175
|
+
Belt auto-wires response models the same way:
|
|
176
|
+
|
|
177
|
+
- `resources :items` → looks for `model :item` in contracts → auto-wires `response_model: :item`
|
|
178
|
+
|
|
179
|
+
**No `response_model:` declaration needed** if your model name matches the singular resource name.
|
|
180
|
+
|
|
181
|
+
### Overrides
|
|
182
|
+
|
|
183
|
+
For per-action overrides when conventions don't fit:
|
|
184
|
+
```ruby
|
|
185
|
+
resources :items, request_model: { create: :create_item, update: :update_item }
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
For non-CRUD custom actions, use explicit wiring:
|
|
189
|
+
```ruby
|
|
190
|
+
post '/schedule', request_model: :schedule_pickup
|
|
191
|
+
```
|
|
192
|
+
|
|
74
193
|
## How Models Work
|
|
75
194
|
|
|
76
195
|
Models use ActiveItem (DynamoDB ORM):
|
|
@@ -83,7 +202,13 @@ end
|
|
|
83
202
|
|
|
84
203
|
Table names resolve as `{APP_NAME}-{ENVIRONMENT}-{pluralized_model}` (e.g., `<%= @app_name %>-wups-posts`).
|
|
85
204
|
|
|
86
|
-
Key
|
|
205
|
+
Key methods: `create!`, `find`, `find_by`, `where`, `update`, `destroy`, `all`, `count`, `exists?`.
|
|
206
|
+
|
|
207
|
+
Querying with indexes:
|
|
208
|
+
```ruby
|
|
209
|
+
Post.where(user_id: "u-123", index: "UserIndex")
|
|
210
|
+
Post.find_by(status: "active", index: "StatusIndex")
|
|
211
|
+
```
|
|
87
212
|
|
|
88
213
|
## How Controllers Work
|
|
89
214
|
|
|
@@ -93,24 +218,35 @@ Controllers inherit from `BeltController::Base`:
|
|
|
93
218
|
module <%= @module_name %>Controllers
|
|
94
219
|
class ThingsController < ApplicationController
|
|
95
220
|
def index
|
|
96
|
-
|
|
97
|
-
success_response(things: items.map(&:to_h))
|
|
221
|
+
@things = Thing.all # implicit JSON: { "things": [...] }
|
|
98
222
|
end
|
|
99
223
|
|
|
100
224
|
def show
|
|
101
|
-
|
|
102
|
-
success_response(thing: item.to_h)
|
|
225
|
+
@thing = Thing.find(params[:id]) # implicit JSON: { "thing": {...} }
|
|
103
226
|
end
|
|
104
227
|
|
|
105
228
|
def create
|
|
106
|
-
|
|
107
|
-
|
|
229
|
+
attrs = params.require(:thing).permit(:title, :content).to_h
|
|
230
|
+
@thing = Thing.create!(attrs)
|
|
231
|
+
response_status :created # 201 + implicit JSON
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
def destroy
|
|
235
|
+
Thing.find(params[:id]).destroy
|
|
236
|
+
head :no_content # 204
|
|
108
237
|
end
|
|
109
238
|
end
|
|
110
239
|
end
|
|
111
240
|
```
|
|
112
241
|
|
|
113
|
-
|
|
242
|
+
**Key patterns:**
|
|
243
|
+
- `params` = merged path parameters + parsed JSON body
|
|
244
|
+
- Instance variables auto-serialize to JSON response
|
|
245
|
+
- `response_status :created` sets status without explicit response
|
|
246
|
+
- `head :no_content` for empty responses
|
|
247
|
+
- `success_response(data, status)` for explicit control
|
|
248
|
+
- `error_response(message, status)` for error responses
|
|
249
|
+
- `rescue_from` for exception handling
|
|
114
250
|
|
|
115
251
|
## Deployment Flow
|
|
116
252
|
|
|
@@ -119,16 +255,30 @@ export AWS_PROFILE=<your_profile>
|
|
|
119
255
|
belt setup state # One-time: create S3 state bucket
|
|
120
256
|
belt generate environment <env> # One-time: scaffold Terraform configs
|
|
121
257
|
belt setup tables <env> # Generate DynamoDB table resources
|
|
122
|
-
belt
|
|
123
|
-
belt apply <env> # Deploy everything
|
|
258
|
+
belt deploy <env> # Deploy everything (init → plan → apply)
|
|
124
259
|
```
|
|
125
260
|
|
|
126
|
-
##
|
|
261
|
+
## Key Differences from Rails
|
|
262
|
+
|
|
263
|
+
| Rails | Belt |
|
|
264
|
+
|-------|------|
|
|
265
|
+
| PostgreSQL / MySQL | DynamoDB (NoSQL) |
|
|
266
|
+
| ActiveRecord | ActiveItem |
|
|
267
|
+
| `PATCH` for updates | `PUT` for updates |
|
|
268
|
+
| `rails server` | Deployed to AWS Lambda |
|
|
269
|
+
| `rails routes` | `belt routes` |
|
|
270
|
+
| `rails console` | `belt console <env>` |
|
|
271
|
+
| `config/routes.rb` → URL paths | `config/routes.rb` → URL paths + infrastructure |
|
|
272
|
+
| Migrations | `belt setup tables <env>` |
|
|
273
|
+
| `has_many` / `belongs_to` | Manual (no joins in DynamoDB) |
|
|
274
|
+
| Template rendering | JSON responses (default) |
|
|
275
|
+
|
|
276
|
+
## Troubleshooting
|
|
127
277
|
|
|
128
278
|
```bash
|
|
129
|
-
belt
|
|
130
|
-
belt
|
|
131
|
-
belt
|
|
279
|
+
belt doctor # Check all dependencies
|
|
280
|
+
belt explain <topic> # Documentation for any concept
|
|
281
|
+
belt routes -f json # Verify route configuration
|
|
282
|
+
belt logs <lambda> -e <env> -s 5m # Check recent Lambda logs
|
|
283
|
+
belt c <env> --run "Post.count" # Verify data access
|
|
132
284
|
```
|
|
133
|
-
|
|
134
|
-
This creates the model, controller, updates routes + schema, generates the DynamoDB table definition, and deploys.
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
# timeout Lambda timeout in seconds (default: 30)
|
|
6
6
|
# memory_size Lambda memory in MB (default: 256)
|
|
7
7
|
# env_vars Environment variables (static values or ref() for Terraform values)
|
|
8
|
+
# iam_policy_arns Additional IAM policy ARNs to attach (supports ref())
|
|
8
9
|
# dynamodb_tables DynamoDB table access (by name — ARNs built by convention)
|
|
9
10
|
# s3_buckets S3 bucket access declarations
|
|
10
11
|
# sns_triggers SNS topic triggers
|
|
@@ -1,7 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
Belt.application.routes.draw do
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
4
|
+
# Creates an API Gateway named "api" with a default Lambda function of the same name.
|
|
5
|
+
# Use `function :other_name do ... end` inside to route specific paths to additional Lambdas.
|
|
6
|
+
gateway :api do
|
|
7
|
+
# Public stack-check / welcome (HTML for browsers, JSON for the SPA shell).
|
|
8
|
+
# This route is auto-injected by Belt if omitted, but keeping it explicit is clearer.
|
|
9
|
+
root to: 'welcome#show'
|
|
5
10
|
# resources :posts
|
|
6
11
|
end
|
|
7
12
|
end
|
|
@@ -9,7 +9,7 @@ require_relative 'lib/routes/api_routes'
|
|
|
9
9
|
require_relative 'controllers/<%= @app_name %>/<%= r %>_controller'
|
|
10
10
|
<% end -%>
|
|
11
11
|
|
|
12
|
-
ROUTER = Belt::ActionRouter.new(routes: Routes::API,
|
|
12
|
+
ROUTER = Belt::ActionRouter.new(routes: Routes::API, gateway: 'api')
|
|
13
13
|
|
|
14
14
|
def execute(path:, body:, event:)
|
|
15
15
|
ROUTER.route(event: event, body: body)
|
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.
|
|
4
|
+
version: 0.3.14
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Stowzilla
|
|
@@ -43,6 +43,20 @@ dependencies:
|
|
|
43
43
|
- - ">="
|
|
44
44
|
- !ruby/object:Gem::Version
|
|
45
45
|
version: '7.0'
|
|
46
|
+
- !ruby/object:Gem::Dependency
|
|
47
|
+
name: irb
|
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
|
49
|
+
requirements:
|
|
50
|
+
- - ">="
|
|
51
|
+
- !ruby/object:Gem::Version
|
|
52
|
+
version: '1.0'
|
|
53
|
+
type: :runtime
|
|
54
|
+
prerelease: false
|
|
55
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
56
|
+
requirements:
|
|
57
|
+
- - ">="
|
|
58
|
+
- !ruby/object:Gem::Version
|
|
59
|
+
version: '1.0'
|
|
46
60
|
- !ruby/object:Gem::Dependency
|
|
47
61
|
name: lambda_loadout
|
|
48
62
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -57,6 +71,20 @@ dependencies:
|
|
|
57
71
|
- - "~>"
|
|
58
72
|
- !ruby/object:Gem::Version
|
|
59
73
|
version: '0.0'
|
|
74
|
+
- !ruby/object:Gem::Dependency
|
|
75
|
+
name: rdoc
|
|
76
|
+
requirement: !ruby/object:Gem::Requirement
|
|
77
|
+
requirements:
|
|
78
|
+
- - ">="
|
|
79
|
+
- !ruby/object:Gem::Version
|
|
80
|
+
version: '6.0'
|
|
81
|
+
type: :runtime
|
|
82
|
+
prerelease: false
|
|
83
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
84
|
+
requirements:
|
|
85
|
+
- - ">="
|
|
86
|
+
- !ruby/object:Gem::Version
|
|
87
|
+
version: '6.0'
|
|
60
88
|
description: Belt provides a collection of lightweight utilities for Ruby applications.
|
|
61
89
|
email:
|
|
62
90
|
- andy@stowzilla.com
|
|
@@ -76,6 +104,7 @@ files:
|
|
|
76
104
|
- lib/belt/assets/welcome.css
|
|
77
105
|
- lib/belt/cli.rb
|
|
78
106
|
- lib/belt/cli/app_detection.rb
|
|
107
|
+
- lib/belt/cli/auth_command.rb
|
|
79
108
|
- lib/belt/cli/backup_config.rb
|
|
80
109
|
- lib/belt/cli/backup_runner.rb
|
|
81
110
|
- lib/belt/cli/bucket_security.rb
|
|
@@ -87,19 +116,23 @@ files:
|
|
|
87
116
|
- lib/belt/cli/env_resolver.rb
|
|
88
117
|
- lib/belt/cli/environment_command.rb
|
|
89
118
|
- lib/belt/cli/environment_config.rb
|
|
119
|
+
- lib/belt/cli/explain_command.rb
|
|
90
120
|
- lib/belt/cli/frontend_command.rb
|
|
91
121
|
- lib/belt/cli/frontend_deploy_command.rb
|
|
92
122
|
- lib/belt/cli/frontend_env_command.rb
|
|
93
123
|
- lib/belt/cli/frontend_env_map.rb
|
|
124
|
+
- lib/belt/cli/frontend_registry.rb
|
|
94
125
|
- lib/belt/cli/frontend_setup_command.rb
|
|
95
126
|
- lib/belt/cli/generate_command.rb
|
|
96
127
|
- lib/belt/cli/generator_registry.rb
|
|
128
|
+
- lib/belt/cli/index_command.rb
|
|
97
129
|
- lib/belt/cli/lambda_config_command.rb
|
|
98
130
|
- lib/belt/cli/logs_command.rb
|
|
99
131
|
- lib/belt/cli/new_command.rb
|
|
100
132
|
- lib/belt/cli/path_gem_materializer.rb
|
|
101
133
|
- lib/belt/cli/plugin_command.rb
|
|
102
134
|
- lib/belt/cli/routes_command.rb
|
|
135
|
+
- lib/belt/cli/routes_command/request_model_inference.rb
|
|
103
136
|
- lib/belt/cli/routes_command/route_inference.rb
|
|
104
137
|
- lib/belt/cli/routes_command/schema_loader.rb
|
|
105
138
|
- lib/belt/cli/server_command.rb
|
|
@@ -108,8 +141,21 @@ files:
|
|
|
108
141
|
- lib/belt/cli/tasks_command.rb
|
|
109
142
|
- lib/belt/cli/terraform_command.rb
|
|
110
143
|
- lib/belt/cli/views_command.rb
|
|
144
|
+
- lib/belt/cli/zip_artifact_builder.rb
|
|
111
145
|
- lib/belt/configuration.rb
|
|
112
146
|
- lib/belt/controllers/welcome_controller.rb
|
|
147
|
+
- lib/belt/docs/backups.md
|
|
148
|
+
- lib/belt/docs/console.md
|
|
149
|
+
- lib/belt/docs/controllers.md
|
|
150
|
+
- lib/belt/docs/deployment.md
|
|
151
|
+
- lib/belt/docs/frontend.md
|
|
152
|
+
- lib/belt/docs/generators.md
|
|
153
|
+
- lib/belt/docs/lambda_handler.md
|
|
154
|
+
- lib/belt/docs/models.md
|
|
155
|
+
- lib/belt/docs/observability.md
|
|
156
|
+
- lib/belt/docs/plugins.md
|
|
157
|
+
- lib/belt/docs/routing.md
|
|
158
|
+
- lib/belt/docs/structure.md
|
|
113
159
|
- lib/belt/helpers/cors_origin.rb
|
|
114
160
|
- lib/belt/helpers/error_logging.rb
|
|
115
161
|
- lib/belt/helpers/response.rb
|
|
@@ -141,6 +187,15 @@ files:
|
|
|
141
187
|
- lib/templates/frontend/react/src/pages/Home.jsx.erb
|
|
142
188
|
- lib/templates/frontend/react/vite.config.js
|
|
143
189
|
- lib/templates/frontend_infra/frontend.tf.erb
|
|
190
|
+
- lib/templates/generate/auth/cognito.tf.erb
|
|
191
|
+
- lib/templates/generate/auth/cognito_outputs.tf.erb
|
|
192
|
+
- lib/templates/generate/auth/frontend/ConfirmEmail.jsx
|
|
193
|
+
- lib/templates/generate/auth/frontend/Login.jsx
|
|
194
|
+
- lib/templates/generate/auth/frontend/ProtectedRoute.jsx
|
|
195
|
+
- lib/templates/generate/auth/frontend/SignUp.jsx
|
|
196
|
+
- lib/templates/generate/auth/frontend/apiClient.js
|
|
197
|
+
- lib/templates/generate/auth/frontend/auth.css
|
|
198
|
+
- lib/templates/generate/auth/frontend/auth.js
|
|
144
199
|
- lib/templates/generate/controller.rb.erb
|
|
145
200
|
- lib/templates/generate/model.rb.erb
|
|
146
201
|
- lib/templates/module/dns.tf.erb
|