belt 0.2.11 → 0.2.13

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 (35) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +42 -1
  3. data/lib/belt/cli/app_detection.rb +19 -5
  4. data/lib/belt/cli/contracts_command.rb +142 -0
  5. data/lib/belt/cli/destroy_command.rb +74 -0
  6. data/lib/belt/cli/frontend_command.rb +23 -0
  7. data/lib/belt/cli/generate_command.rb +11 -9
  8. data/lib/belt/cli/logs_command.rb +634 -0
  9. data/lib/belt/cli/new_command.rb +2 -2
  10. data/lib/belt/cli/routes_command/schema_loader.rb +17 -7
  11. data/lib/belt/cli/routes_command.rb +7 -3
  12. data/lib/belt/cli/setup_command.rb +1 -1
  13. data/lib/belt/cli/views_command.rb +96 -11
  14. data/lib/belt/cli.rb +6 -0
  15. data/lib/belt/root.rb +12 -3
  16. data/lib/belt/route_dsl.rb +9 -6
  17. data/lib/belt/version.rb +1 -1
  18. data/lib/belt.rb +1 -0
  19. data/lib/templates/frontend_infra/frontend.tf.erb +2 -1
  20. data/lib/templates/generate/controller.rb.erb +13 -18
  21. data/lib/templates/generate/model.rb.erb +1 -14
  22. data/lib/templates/module/frontend.tf.erb +2 -1
  23. data/lib/templates/module/main.tf.erb +1 -1
  24. data/lib/templates/new_app/AGENTS.md.erb +4 -4
  25. data/lib/templates/new_app/Gemfile.erb +0 -2
  26. data/lib/templates/new_app/README.md.erb +2 -2
  27. data/lib/templates/new_app/gitignore.erb +3 -0
  28. data/lib/templates/new_app/lambda/api.rb.erb +0 -6
  29. data/lib/templates/new_app/lambda/config/environment.rb.erb +0 -6
  30. data/lib/templates/new_app/lambda/models/application_record.rb.erb +0 -2
  31. data/lib/templates/plugin/AGENTS.md.erb +1 -1
  32. data/lib/templates/plugin/README.md.erb +1 -1
  33. metadata +11 -3
  34. /data/lib/templates/new_app/config/{schema.tf.rb.erb → contracts.rb.erb} +0 -0
  35. /data/lib/templates/new_app/config/{routes.tf.rb.erb → routes.rb.erb} +0 -0
@@ -27,7 +27,7 @@ module Belt
27
27
  routes_file = find_routes_file
28
28
  unless routes_file
29
29
  abort 'Error: No routes file found. ' \
30
- 'Expected config/routes.tf.rb (or infrastructure/routes.tf.rb)'
30
+ 'Expected config/routes.rb (or config/routes.tf.rb, infrastructure/routes.tf.rb)'
31
31
  end
32
32
 
33
33
  dsl = load_routes(routes_file)
@@ -71,7 +71,7 @@ module Belt
71
71
  @options[:output_dir] = dir
72
72
  end
73
73
 
74
- opts.on('--schema FILE', 'Path to schema.tf.rb for model definitions') do |file|
74
+ opts.on('--schema FILE', 'Path to contracts.rb for model definitions') do |file|
75
75
  @options[:schema_file] = file
76
76
  end
77
77
 
@@ -87,7 +87,11 @@ module Belt
87
87
  end
88
88
 
89
89
  def find_routes_file
90
- candidates = ['config/routes.tf.rb', 'infrastructure/routes.tf.rb']
90
+ candidates = [
91
+ 'config/routes.rb',
92
+ 'config/routes.tf.rb',
93
+ 'infrastructure/routes.tf.rb'
94
+ ]
91
95
  candidates.find { |f| File.exist?(f) }
92
96
  end
93
97
 
@@ -32,7 +32,7 @@ module Belt
32
32
  puts 'Usage: belt setup <state|tables|frontend> [options]'
33
33
  puts "\nSubcommands:"
34
34
  puts ' state Set up S3 bucket for Terraform state'
35
- puts ' tables Generate DynamoDB table definitions from schema.tf.rb'
35
+ puts ' tables Generate DynamoDB table definitions from contracts.rb'
36
36
  puts ' frontend Generate S3 + CloudFront infrastructure for frontend hosting'
37
37
  exit 1
38
38
  end
@@ -10,10 +10,14 @@ module Belt
10
10
  TEMPLATE_DIR = File.expand_path('../../templates/views', __dir__)
11
11
 
12
12
  def self.run(args)
13
+ force = args.delete('--force') || args.delete('-f')
14
+
13
15
  name = args.shift
14
16
  if name.nil? || name.empty?
15
- puts 'Usage: belt generate views <resource> [field:type ...]'
17
+ puts 'Usage: belt generate views <resource> [field:type ...] [options]'
16
18
  puts "\nGenerates React pages for all REST actions (index, show, new, edit)."
19
+ puts "\nOptions:"
20
+ puts ' --force, -f Overwrite existing files without prompting'
17
21
  puts "\nExamples:"
18
22
  puts ' belt generate views post title:string content:text status:string'
19
23
  puts ' belt generate views comment body:text author:string'
@@ -25,14 +29,19 @@ module Belt
25
29
  { name: n, type: t || 'string' }
26
30
  end
27
31
 
28
- # If no fields provided, try to read from schema.tf.rb
32
+ # If no fields provided, try to read from contracts.rb
29
33
  fields = read_schema_fields(name) if fields.empty?
30
34
 
31
- new(name, fields).generate
35
+ new(name, fields, force: force).generate
32
36
  end
33
37
 
34
38
  def self.read_schema_fields(name)
35
- schema_file = ['config/schema.tf.rb', 'infrastructure/schema.tf.rb'].find { |f| File.exist?(f) }
39
+ schema_file = [
40
+ 'config/contracts.rb',
41
+ 'config/contracts.tf.rb',
42
+ 'config/schema.tf.rb',
43
+ 'infrastructure/schema.tf.rb'
44
+ ].find { |f| File.exist?(f) }
36
45
  return [] unless schema_file
37
46
 
38
47
  content = File.read(schema_file)
@@ -40,20 +49,37 @@ module Belt
40
49
 
41
50
  # Extract fields from model block
42
51
  if content =~ /model :#{singular} do\n(.*?)\n\s*end/m
43
- ::Regexp.last_match(1).scan(/field :(\w+), type: :(\w+)/).except('created_at', 'updated_at')
44
- .map do |n, t|
45
- {
46
- name: n, type: t
47
- }
52
+ block_content = ::Regexp.last_match(1)
53
+ timestamp_fields = %w[created_at updated_at]
54
+
55
+ # Support both formats:
56
+ # field :name, type: :string (legacy)
57
+ # string :name (current schema DSL)
58
+ dsl_types = %w[string text integer number boolean float date datetime]
59
+ dsl_pattern = /(?:#{dsl_types.join('|')}) :(\w+)/
60
+ fields = block_content.scan(/field :(\w+), type: :(\w+)/)
61
+ fields += block_content.scan(dsl_pattern).map do |match|
62
+ field_name = match[0]
63
+ # Extract type from the DSL method name on that line
64
+ type_match = block_content.match(/(\w+) :#{Regexp.escape(field_name)}/)
65
+ [field_name, type_match ? type_match[1] : 'string']
66
+ end
67
+
68
+ fields.filter_map do |n, t|
69
+ next if timestamp_fields.include?(n)
70
+
71
+ { name: n, type: t }
48
72
  end
49
73
  else
50
74
  []
51
75
  end
52
76
  end
53
77
 
54
- def initialize(name, fields)
78
+ def initialize(name, fields, force: false)
55
79
  @name = name.downcase.gsub(/[^a-z0-9_]/, '_')
56
80
  @fields = fields
81
+ @force = force
82
+ @overwrite_all = false
57
83
  @singular_name = Belt::Inflector.singularize(@name)
58
84
  @resource_name = Belt::Inflector.pluralize(@singular_name)
59
85
  @class_name = Belt::Inflector.classify(@singular_name)
@@ -92,8 +118,61 @@ module Belt
92
118
  def write_template(template_name, dest_path)
93
119
  template_path = File.join(TEMPLATE_DIR, template_name)
94
120
  content = ERB.new(File.read(template_path), trim_mode: '-').result(binding)
121
+ existed = File.exist?(dest_path)
122
+
123
+ if existed && !@force && !@overwrite_all
124
+ action = prompt_overwrite(dest_path)
125
+ case action
126
+ when :yes
127
+ # fall through to write
128
+ when :all
129
+ @overwrite_all = true
130
+ # fall through to write
131
+ when :no
132
+ puts " skip #{dest_path}"
133
+ return
134
+ when :quit
135
+ puts "\nAborted."
136
+ exit 1
137
+ end
138
+ end
139
+
95
140
  File.write(dest_path, content)
96
- puts " create #{dest_path}"
141
+ puts " #{existed ? 'overwrite' : 'create'} #{dest_path}"
142
+ end
143
+
144
+ def prompt_overwrite(path)
145
+ return :yes if @overwrite_all
146
+
147
+ print " conflict #{path}\n"
148
+ print " Overwrite #{path}? (enter \"h\" for help) [Ynaqh] "
149
+ $stdout.flush
150
+
151
+ loop do
152
+ answer = $stdin.gets&.strip&.downcase
153
+ case answer
154
+ when '', 'y', 'yes'
155
+ return :yes
156
+ when 'n', 'no'
157
+ return :no
158
+ when 'a', 'all'
159
+ @overwrite_all = true
160
+ return :all
161
+ when 'q', 'quit'
162
+ return :quit
163
+ when 'h', 'help'
164
+ puts ' Y - yes, overwrite this file'
165
+ puts ' n - no, skip this file'
166
+ puts ' a - all, overwrite this and all remaining files'
167
+ puts ' q - quit, abort the generator'
168
+ puts ' h - help, show this help'
169
+ print " Overwrite #{path}? (enter \"h\" for help) [Ynaqh] "
170
+ $stdout.flush
171
+ else
172
+ print ' Please enter Y, n, a, q, or h: '
173
+ $stdout.flush
174
+ end
175
+ end
97
176
  end
98
177
 
99
178
  def inject_routes
@@ -104,6 +183,12 @@ module Belt
104
183
  pages_dir = @resource_name
105
184
  plural_class = @plural_class_name || Belt::Inflector.camelize(@resource_name)
106
185
 
186
+ # Skip route injection if routes for this resource already exist
187
+ if content.include?("path=\"/#{@resource_name}\"")
188
+ puts " skip #{app_jsx} (routes already exist)"
189
+ return
190
+ end
191
+
107
192
  import_lines = [
108
193
  "import #{plural_class}Index from './pages/#{pages_dir}/#{plural_class}Index'",
109
194
  "import #{@class_name}Show from './pages/#{pages_dir}/#{@class_name}Show'",
data/lib/belt/cli.rb CHANGED
@@ -18,9 +18,11 @@ require_relative 'cli/backup_config'
18
18
  require_relative 'cli/backup_runner'
19
19
  require_relative 'cli/server_command'
20
20
  require_relative 'cli/routes_command'
21
+ require_relative 'cli/contracts_command'
21
22
  require_relative 'cli/lambda_config_command'
22
23
  require_relative 'cli/tasks_command'
23
24
  require_relative 'cli/console_command'
25
+ require_relative 'cli/logs_command'
24
26
  require_relative 'cli/doctor_command'
25
27
  require_relative 'cli/plugin_command'
26
28
 
@@ -31,8 +33,10 @@ module Belt
31
33
  %w[generate g] => Belt::CLI::GenerateCommand,
32
34
  %w[destroy d] => Belt::CLI::DestroyCommand,
33
35
  'routes' => Belt::CLI::RoutesCommand,
36
+ 'contracts' => Belt::CLI::ContractsCommand,
34
37
  'lambda-config' => Belt::CLI::LambdaConfigCommand,
35
38
  %w[console c] => Belt::CLI::ConsoleCommand,
39
+ 'logs' => Belt::CLI::LogsCommand,
36
40
  %w[tasks --tasks -T] => Belt::CLI::TasksCommand,
37
41
  'setup' => Belt::CLI::SetupCommand,
38
42
  'doctor' => Belt::CLI::DoctorCommand,
@@ -109,10 +113,12 @@ module Belt
109
113
  deploy frontend <env> Build and deploy frontend to AWS
110
114
  frontend env <env> Write frontend/.env from terraform outputs
111
115
  routes [-g PATTERN] [-f json] Show route definitions
116
+ contracts [-g PATTERN] [-f json] Show API request/response contracts
112
117
  lambda-config [-e ENV] [-f json|terraform] Show merged lambda configuration
113
118
 
114
119
  console Start an interactive console (IRB)
115
120
  c Alias for console
121
+ logs [lambda] [-f] [-s 5m] [-e env] View Lambda function logs
116
122
  tasks [-g PATTERN] [-a] List available rake tasks
117
123
  -T [-g PATTERN] [-a] Alias for tasks
118
124
  setup state Create/select S3 state bucket
data/lib/belt/root.rb CHANGED
@@ -9,24 +9,32 @@ module Belt
9
9
  @root = path
10
10
  end
11
11
 
12
- # Resolves the path to routes.tf.rb, checking config/ first then infrastructure/ (legacy).
12
+ # Resolves the path to routes.rb, checking config/ first then legacy paths.
13
13
  def self.routes_file
14
14
  candidates = [
15
+ File.join(root, 'config/routes.rb'),
15
16
  File.join(root, 'config/routes.tf.rb'),
16
17
  File.join(root, 'infrastructure/routes.tf.rb')
17
18
  ]
18
19
  candidates.find { |f| File.exist?(f) }
19
20
  end
20
21
 
21
- # Resolves the path to schema.tf.rb, checking config/ first then infrastructure/ (legacy).
22
- def self.schema_file
22
+ # Resolves the path to contracts.rb, checking config/ first then legacy paths.
23
+ def self.contracts_file
23
24
  candidates = [
25
+ File.join(root, 'config/contracts.rb'),
26
+ File.join(root, 'config/contracts.tf.rb'),
24
27
  File.join(root, 'config/schema.tf.rb'),
25
28
  File.join(root, 'infrastructure/schema.tf.rb')
26
29
  ]
27
30
  candidates.find { |f| File.exist?(f) }
28
31
  end
29
32
 
33
+ # Legacy alias for backward compatibility
34
+ def self.schema_file
35
+ contracts_file
36
+ end
37
+
30
38
  # Resolves the lambda config directory.
31
39
  def self.lambda_config_dir
32
40
  File.join(root, 'config/lambda')
@@ -35,6 +43,7 @@ module Belt
35
43
  def self.detect_root
36
44
  dir = Dir.pwd
37
45
  loop do
46
+ return dir if File.exist?(File.join(dir, 'config/routes.rb'))
38
47
  return dir if File.exist?(File.join(dir, 'config/routes.tf.rb'))
39
48
  return dir if File.exist?(File.join(dir, 'infrastructure/routes.tf.rb'))
40
49
 
@@ -5,7 +5,7 @@ require_relative 'inflector'
5
5
  module Belt
6
6
  # DSL for defining API Gateway routes.
7
7
  # Ported from terraform-provider-conveyor-belt/scripts/lib/route_dsl.rb
8
- # so that `belt routes` can parse routes.tf.rb without external dependencies.
8
+ # so that `belt routes` can parse routes.rb without external dependencies.
9
9
 
10
10
  class Route
11
11
  attr_reader :method, :path, :auth, :lambda, :cors, :tables, :route_type,
@@ -518,9 +518,9 @@ module Belt
518
518
  end
519
519
  end
520
520
 
521
- # SchemaBuilder captures request and response model definitions from schema.tf.rb
521
+ # SchemaBuilder captures request and response model definitions from contracts.rb
522
522
  class SchemaBuilder
523
- SUPPORTED_TYPES = %i[string number integer boolean array object map list].freeze
523
+ SUPPORTED_TYPES = %i[string text number integer boolean date datetime array object map list].freeze
524
524
 
525
525
  attr_reader :request_models, :response_models
526
526
 
@@ -557,7 +557,7 @@ module Belt
557
557
  end
558
558
 
559
559
  class RequestModelBuilder
560
- SUPPORTED_TYPES = %i[string number integer boolean array object map list].freeze
560
+ SUPPORTED_TYPES = %i[string text number integer boolean date datetime array object map list].freeze
561
561
 
562
562
  attr_reader :name, :fields
563
563
 
@@ -590,6 +590,7 @@ module Belt
590
590
 
591
591
  def map_type(dsl_type)
592
592
  case dsl_type
593
+ when :text, :date, :datetime then 'string'
593
594
  when :map then 'object'
594
595
  when :list then 'array'
595
596
  else dsl_type.to_s
@@ -598,7 +599,7 @@ module Belt
598
599
  end
599
600
 
600
601
  class ResponseModelBuilder
601
- SUPPORTED_TYPES = %i[string number integer boolean array object map list].freeze
602
+ SUPPORTED_TYPES = %i[string text number integer boolean date datetime array object map list].freeze
602
603
 
603
604
  attr_reader :name, :contexts, :fields
604
605
 
@@ -636,6 +637,7 @@ module Belt
636
637
 
637
638
  def map_type(dsl_type)
638
639
  case dsl_type
640
+ when :text, :date, :datetime then 'string'
639
641
  when :map then 'object'
640
642
  when :list then 'array'
641
643
  else dsl_type.to_s
@@ -644,7 +646,7 @@ module Belt
644
646
  end
645
647
 
646
648
  class ContextBuilder
647
- SUPPORTED_TYPES = %i[string number integer boolean array object map list].freeze
649
+ SUPPORTED_TYPES = %i[string text number integer boolean date datetime array object map list].freeze
648
650
 
649
651
  attr_reader :name, :fields
650
652
 
@@ -673,6 +675,7 @@ module Belt
673
675
 
674
676
  def map_type(dsl_type)
675
677
  case dsl_type
678
+ when :text, :date, :datetime then 'string'
676
679
  when :map then 'object'
677
680
  when :list then 'array'
678
681
  else dsl_type.to_s
data/lib/belt/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Belt
4
- VERSION = '0.2.11'
4
+ VERSION = '0.2.13'
5
5
  end
data/lib/belt.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'activeitem'
3
4
  require_relative 'belt/version'
4
5
  require_relative 'belt/root'
5
6
  require_relative 'belt/configuration'
@@ -9,7 +9,8 @@ resource "random_string" "frontend_suffix" {
9
9
 
10
10
  # S3 bucket for frontend static assets
11
11
  resource "aws_s3_bucket" "frontend" {
12
- bucket = "<%= s3_safe_name(@app_name) %>-frontend-${var.environment}-${random_string.frontend_suffix.result}"
12
+ bucket = "<%= s3_safe_name(@app_name) %>-frontend-${var.environment}-${random_string.frontend_suffix.result}"
13
+ force_destroy = true
13
14
 
14
15
  lifecycle {
15
16
  ignore_changes = [bucket]
@@ -6,53 +6,48 @@ module <%= @module_name %>Controllers
6
6
  class <%= @class_name %>sController < ApplicationController
7
7
  # GET /<%= @resource_name %>
8
8
  def index
9
- <%= @resource_name %> = <%= @class_name %>.all
10
- success_response(<%= @resource_name %>: <%= @resource_name %>.map(&:to_h))
9
+ @<%= @resource_name %> = <%= @class_name %>.all
11
10
  end
12
11
 
13
12
  # POST /<%= @resource_name %>
14
13
  def create
15
- <%= @singular_name %> = <%= @class_name %>.new(<%= @fields.map { |f| "#{f[:name]}: params[:#{f[:name]}]" }.join(', ') %>)
14
+ @<%= @singular_name %> = <%= @class_name %>.new(<%= @fields.map { |f| "#{f[:name]}: params[:#{f[:name]}]" }.join(', ') %>)
16
15
 
17
- if <%= @singular_name %>.save
18
- success_response(<%= @singular_name %>: <%= @singular_name %>.to_h)
16
+ if @<%= @singular_name %>.save
17
+ response_status :created
19
18
  else
20
- error_response(<%= @singular_name %>.errors.full_messages.join(', '), 422)
19
+ error_response(@<%= @singular_name %>.errors.full_messages.join(', '), :unprocessable_entity)
21
20
  end
22
21
  end
23
22
 
24
23
  # GET /<%= @resource_name %>/:<%= @singular_name %>_id
25
24
  def show
26
- <%= @singular_name %> = <%= @class_name %>.find(params[:<%= @singular_name %>_id])
27
- return error_response('<%= @class_name %> not found', 404) unless <%= @singular_name %>
28
-
29
- success_response(<%= @singular_name %>: <%= @singular_name %>.to_h)
25
+ @<%= @singular_name %> = <%= @class_name %>.find(params[:<%= @singular_name %>_id])
26
+ return error_response('<%= @class_name %> not found', :not_found) unless @<%= @singular_name %>
30
27
  end
31
28
 
32
29
  # PUT /<%= @resource_name %>/:<%= @singular_name %>_id
33
30
  def update
34
- <%= @singular_name %> = <%= @class_name %>.find(params[:<%= @singular_name %>_id])
35
- return error_response('<%= @class_name %> not found', 404) unless <%= @singular_name %>
31
+ @<%= @singular_name %> = <%= @class_name %>.find(params[:<%= @singular_name %>_id])
32
+ return error_response('<%= @class_name %> not found', :not_found) unless @<%= @singular_name %>
36
33
 
37
34
  attrs = {}
38
35
  <% @fields.each do |field| -%>
39
36
  attrs[:<%= field[:name] %>] = params[:<%= field[:name] %>] if params.key?(:<%= field[:name] %>)
40
37
  <% end -%>
41
38
 
42
- if <%= @singular_name %>.update(attrs)
43
- success_response(<%= @singular_name %>: <%= @singular_name %>.to_h)
44
- else
45
- error_response(<%= @singular_name %>.errors.full_messages.join(', '), 422)
39
+ unless @<%= @singular_name %>.update(attrs)
40
+ error_response(@<%= @singular_name %>.errors.full_messages.join(', '), :unprocessable_entity)
46
41
  end
47
42
  end
48
43
 
49
44
  # DELETE /<%= @resource_name %>/:<%= @singular_name %>_id
50
45
  def destroy
51
46
  <%= @singular_name %> = <%= @class_name %>.find(params[:<%= @singular_name %>_id])
52
- return error_response('<%= @class_name %> not found', 404) unless <%= @singular_name %>
47
+ return error_response('<%= @class_name %> not found', :not_found) unless <%= @singular_name %>
53
48
 
54
49
  <%= @singular_name %>.destroy
55
- success_response(message: '<%= @class_name %> deleted')
50
+ head :no_content
56
51
  end
57
52
  end
58
53
  end
@@ -1,18 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class <%= @class_name %> < ApplicationRecord
4
- <% @fields.each do |field| -%>
5
- attr_accessor :<%= field[:name] %>
6
- <% end -%>
7
-
8
- def to_h
9
- {
10
- id: id,
11
- <% @fields.each do |field| -%>
12
- <%= field[:name] %>: <%= field[:name] %>,
13
- <% end -%>
14
- created_at: created_at,
15
- updated_at: updated_at
16
- }
17
- end
4
+ attr_accessor <%= @fields.map { |f| ":#{f[:name]}" }.join(', ') %>
18
5
  end
@@ -9,7 +9,8 @@ resource "random_string" "frontend_suffix" {
9
9
 
10
10
  # S3 bucket for frontend static assets
11
11
  resource "aws_s3_bucket" "frontend" {
12
- bucket = "${var.app_name}-frontend-${var.environment}-${random_string.frontend_suffix.result}"
12
+ bucket = "${var.app_name}-frontend-${var.environment}-${random_string.frontend_suffix.result}"
13
+ force_destroy = true
13
14
 
14
15
  lifecycle {
15
16
  ignore_changes = [bucket]
@@ -22,7 +22,7 @@ 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
+ source = "${path.module}/../../../config/routes.rb"
26
26
  app_name = var.app_name
27
27
  lambda_source_dir = "${path.module}/../../../lambda"
28
28
  lambda_shared_dirs = ["controllers", "helpers", "lib", "models", "views"]
@@ -6,7 +6,7 @@ This file explains the project structure, tooling, and conventions for AI agents
6
6
 
7
7
  - **Belt** — CLI and runtime framework (like Rails for serverless). Provides Lambda handler, action router, controller base class, and CLI tooling.
8
8
  - **ActiveItem** — ActiveRecord-like ORM for DynamoDB. Models inherit from `ActiveItem::Base`.
9
- - **Conveyor Belt** — Terraform provider that reads a Ruby DSL (`routes.tf.rb`) and creates API Gateway + Lambda + IAM infrastructure.
9
+ - **Conveyor Belt** — Terraform provider that reads a Ruby DSL (`routes.rb`) and creates API Gateway + Lambda + IAM infrastructure.
10
10
  - **Lambda Loadout** — Lambda cold-start optimizer (auto-required by Belt).
11
11
 
12
12
  ## Project Structure
@@ -23,8 +23,8 @@ This file explains the project structure, tooling, and conventions for AI agents
23
23
  │ ├── lib/routes/ # Route manifests (auto-generated)
24
24
  │ └── Gemfile # Lambda-specific dependencies
25
25
  ├── config/
26
- │ ├── routes.tf.rb # API routes (Conveyor Belt DSL)
27
- │ ├── schema.tf.rb # Model schema definitions
26
+ │ ├── routes.rb # API routes (Conveyor Belt DSL)
27
+ │ ├── contracts.rb # API request/response contracts
28
28
  │ └── lambda/ # Per-lambda config (like database.yml)
29
29
  │ └── api.yml # Lambda timeout, memory, env vars
30
30
  ├── infrastructure/
@@ -53,7 +53,7 @@ belt output <env> # terraform output
53
53
 
54
54
  ## How Routing Works
55
55
 
56
- 1. `config/routes.tf.rb` defines routes using a DSL:
56
+ 1. `config/routes.rb` defines routes using a DSL:
57
57
  ```ruby
58
58
  Belt.application.routes.draw do
59
59
  namespace :<%= @app_name %> do
@@ -2,6 +2,4 @@
2
2
 
3
3
  source 'https://rubygems.org'
4
4
 
5
- gem 'activeitem'
6
5
  gem 'belt'
7
- gem 'lambda_loadout'
@@ -6,8 +6,8 @@ A serverless application built with [Belt](https://github.com/stowzilla/belt) an
6
6
 
7
7
  ```
8
8
  ├── config/
9
- │ ├── routes.tf.rb # API route definitions
10
- │ ├── schema.tf.rb # Model schema definitions
9
+ │ ├── routes.rb # API route definitions
10
+ │ ├── contracts.rb # API request/response contracts
11
11
  │ └── lambda/ # Per-lambda config (timeout, memory, env vars)
12
12
  │ └── api.yml
13
13
  ├── infrastructure/ # Terraform environments
@@ -12,3 +12,6 @@
12
12
  # Lambda build artifacts
13
13
  /lambda/vendor/
14
14
  /lambda/.bundle/
15
+
16
+ # Generated route manifests (belt routes --namespace)
17
+ /lambda/lib/routes/
@@ -1,15 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'belt'
4
- require 'activeitem'
5
4
 
6
5
  include Belt::LambdaHandler
7
6
 
8
- ActiveItem.configure do |config|
9
- config.table_prefix = ENV['APP_NAME']
10
- config.environment = ENV['ENVIRONMENT']
11
- end
12
-
13
7
  require_relative 'lib/routes/api_routes'
14
8
  <% @resources&.each do |r| -%>
15
9
  require_relative 'controllers/<%= @app_name %>/<%= r %>_controller'
@@ -3,16 +3,10 @@
3
3
  # Boot the application. Used by `belt console` and Lambda at runtime.
4
4
 
5
5
  require 'belt'
6
- require 'activeitem'
7
6
 
8
7
  ENV['APP_NAME'] ||= '<%= @app_name %>'
9
8
  ENV['AWS_REGION'] ||= 'us-east-1'
10
9
 
11
- ActiveItem.configure do |config|
12
- config.table_prefix = ENV['APP_NAME']
13
- config.environment = ENV['ENVIRONMENT']
14
- end
15
-
16
10
  # Load lib and models
17
11
  Dir[File.join(__dir__, '..', 'lib', '**', '*.rb')].sort.each { |f| require f }
18
12
 
@@ -1,6 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'activeitem'
4
-
5
3
  class ApplicationRecord < ActiveItem::Base
6
4
  end
@@ -67,7 +67,7 @@ When fleshing out the generator, typically install:
67
67
  1. **Terraform module** → `infrastructure/modules/<%= plugin_name %>/` (`main.tf`, `variables.tf`, `outputs.tf`)
68
68
  2. **Lambda config** → `config/lambda/<%= plugin_name %>.yml` (timeout, memory, env, triggers)
69
69
  3. **Lambda entrypoint** → `lambda/<%= plugin_name %>.rb` using `Belt::LambdaHandler`
70
- 4. **Routes / schema** → inject into `config/routes.tf.rb` or schema files when needed
70
+ 4. **Routes / schema** → inject into `config/routes.rb` or `config/contracts.rb` when needed
71
71
  5. **Optional overrides** → `--controllers` (or similar) for app-local subclasses — keep defaults in the gem
72
72
  6. **Destroy path** → `belt destroy <%= plugin_name %>` removes generated artifacts
73
73
  7. **Help** → `.description` plus `--help` / `-h` explaining files and next steps
@@ -42,7 +42,7 @@ Typical plugin generators create some combination of:
42
42
  - **Terraform module** — `infrastructure/modules/<%= plugin_name %>/`
43
43
  - **Lambda entry point** — `lambda/<%= plugin_name %>.rb`
44
44
  - **Lambda config** — `config/lambda/<%= plugin_name %>.yml`
45
- - **Route / schema injection** — updates to `routes.tf.rb` / `schema.tf.rb`
45
+ - **Route / schema injection** — updates to `config/routes.rb` / `config/contracts.rb`
46
46
  - **Optional controller overrides** — `belt g <%= plugin_name %> --controllers`
47
47
 
48
48
  ## Development