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.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +226 -0
  3. data/README.md +42 -3
  4. data/lib/belt/action_router.rb +34 -19
  5. data/lib/belt/cli/app_detection.rb +30 -3
  6. data/lib/belt/cli/auth_command.rb +395 -0
  7. data/lib/belt/cli/console_command.rb +17 -2
  8. data/lib/belt/cli/deploy_command.rb +51 -17
  9. data/lib/belt/cli/destroy_command.rb +62 -25
  10. data/lib/belt/cli/doctor_command.rb +166 -22
  11. data/lib/belt/cli/environment_command.rb +12 -0
  12. data/lib/belt/cli/explain_command.rb +112 -0
  13. data/lib/belt/cli/frontend_command.rb +31 -10
  14. data/lib/belt/cli/frontend_deploy_command.rb +110 -21
  15. data/lib/belt/cli/frontend_env_command.rb +56 -19
  16. data/lib/belt/cli/frontend_env_map.rb +30 -10
  17. data/lib/belt/cli/frontend_registry.rb +373 -0
  18. data/lib/belt/cli/frontend_setup_command.rb +71 -5
  19. data/lib/belt/cli/generate_command.rb +61 -42
  20. data/lib/belt/cli/index_command.rb +192 -0
  21. data/lib/belt/cli/lambda_config_command.rb +9 -3
  22. data/lib/belt/cli/routes_command/request_model_inference.rb +131 -0
  23. data/lib/belt/cli/routes_command/route_inference.rb +4 -3
  24. data/lib/belt/cli/routes_command.rb +31 -19
  25. data/lib/belt/cli/server_command.rb +28 -15
  26. data/lib/belt/cli/tables_command.rb +3 -1
  27. data/lib/belt/cli/terraform_command.rb +9 -0
  28. data/lib/belt/cli/views_command.rb +15 -15
  29. data/lib/belt/cli/zip_artifact_builder.rb +199 -0
  30. data/lib/belt/cli.rb +17 -9
  31. data/lib/belt/docs/backups.md +101 -0
  32. data/lib/belt/docs/console.md +65 -0
  33. data/lib/belt/docs/controllers.md +155 -0
  34. data/lib/belt/docs/deployment.md +166 -0
  35. data/lib/belt/docs/frontend.md +103 -0
  36. data/lib/belt/docs/generators.md +137 -0
  37. data/lib/belt/docs/lambda_handler.md +105 -0
  38. data/lib/belt/docs/models.md +160 -0
  39. data/lib/belt/docs/observability.md +94 -0
  40. data/lib/belt/docs/plugins.md +94 -0
  41. data/lib/belt/docs/routing.md +138 -0
  42. data/lib/belt/docs/structure.md +96 -0
  43. data/lib/belt/inflector.rb +8 -0
  44. data/lib/belt/route_dsl.rb +318 -100
  45. data/lib/belt/version.rb +1 -1
  46. data/lib/templates/frontend/react/env.yml.example +2 -2
  47. data/lib/templates/generate/auth/cognito.tf.erb +71 -0
  48. data/lib/templates/generate/auth/cognito_outputs.tf.erb +19 -0
  49. data/lib/templates/generate/auth/frontend/ConfirmEmail.jsx +55 -0
  50. data/lib/templates/generate/auth/frontend/Login.jsx +79 -0
  51. data/lib/templates/generate/auth/frontend/ProtectedRoute.jsx +9 -0
  52. data/lib/templates/generate/auth/frontend/SignUp.jsx +58 -0
  53. data/lib/templates/generate/auth/frontend/apiClient.js +34 -0
  54. data/lib/templates/generate/auth/frontend/auth.css +157 -0
  55. data/lib/templates/generate/auth/frontend/auth.js +121 -0
  56. data/lib/templates/generate/model.rb.erb +4 -6
  57. data/lib/templates/module/frontend.tf.erb +49 -43
  58. data/lib/templates/module/outputs.tf.erb +1 -1
  59. data/lib/templates/new_app/AGENTS.md.erb +170 -20
  60. data/lib/templates/new_app/config/lambda/api.yml.erb +1 -0
  61. data/lib/templates/new_app/config/routes.rb.erb +8 -3
  62. data/lib/templates/new_app/lambda/api.rb.erb +1 -1
  63. 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
- resource "random_string" "frontend_suffix" {
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" "frontend" {
12
- bucket = "${var.app_name}-frontend-${var.environment}-${random_string.frontend_suffix.result}"
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" "frontend" {
21
- bucket = aws_s3_bucket.frontend.id
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" "frontend" {
30
- bucket = aws_s3_bucket.frontend.id
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" "frontend" {
42
- name = "${var.app_name}-${var.environment}-frontend-oac"
43
- description = "OAC for frontend bucket"
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" "frontend" {
52
+ resource "aws_cloudfront_distribution" "<%= @tf_name %>" {
51
53
  origin {
52
- domain_name = aws_s3_bucket.frontend.bucket_regional_domain_name
53
- origin_id = "S3-${aws_s3_bucket.frontend.id}"
54
- origin_access_control_id = aws_cloudfront_origin_access_control.frontend.id
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.frontend.id}"
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}-frontend"
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" "frontend" {
117
- bucket = aws_s3_bucket.frontend.id
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.frontend.arn}/*"
131
+ Resource = "${aws_s3_bucket.<%= @tf_name %>.arn}/*"
130
132
  Condition = {
131
133
  StringEquals = {
132
- "AWS:SourceArn" = aws_cloudfront_distribution.frontend.arn
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.frontend]
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" "frontend" {
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.frontend.domain_name
151
- zone_id = aws_cloudfront_distribution.frontend.hosted_zone_id
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" "frontend_www" {
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.frontend.domain_name
165
- zone_id = aws_cloudfront_distribution.frontend.hosted_zone_id
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 "frontend_bucket_name" {
171
- value = aws_s3_bucket.frontend.id
171
+ <% end %>
172
+ output "<%= @output_prefix %>_bucket_name" {
173
+ value = aws_s3_bucket.<%= @tf_name %>.id
172
174
  }
173
175
 
174
- output "frontend_distribution_id" {
175
- value = aws_cloudfront_distribution.frontend.id
176
+ output "<%= @output_prefix %>_distribution_id" {
177
+ value = aws_cloudfront_distribution.<%= @tf_name %>.id
176
178
  }
177
179
 
178
- output "frontend_url" {
179
- value = local.dns_enabled ? "https://${local.app_domain}" : "https://${aws_cloudfront_distribution.frontend.domain_name}"
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
- namespace :<%= @app_name %> do
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 an API Gateway where the namespace becomes a base path mapping. URLs look like:
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 in `lambda/lib/routes/`.
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 ActiveItem methods: `create!`, `find`, `where`, `update`, `destroy`, `all`, `count`, `exists?`.
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
- items = Thing.all
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
- item = Thing.find(params[:id])
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
- item = Thing.create!(params.slice(:title, :content))
107
- success_response(thing: item.to_h, status: 201)
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
- `params` contains merged path parameters and parsed JSON body. Use `success_response` and `error_response` helpers.
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 init <env> # Initialize Terraform
123
- belt apply <env> # Deploy everything
258
+ belt deploy <env> # Deploy everything (init → plan → apply)
124
259
  ```
125
260
 
126
- ## Adding a New Resource
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 generate resource comment body:text author:string post_id:string
130
- belt setup tables <env>
131
- belt apply <env>
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
- namespace :api do
3
- # Public stack-check / welcome (HTML for browsers, JSON for the SPA shell)
4
- get "/", action: :show, controller: :welcome, auth: :none
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, namespace: '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.2.17
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