belt 0.1.5 → 0.1.7
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/lib/belt/assets/belt-default.jpg +0 -0
- data/lib/belt/assets/welcome.css +1 -1
- data/lib/belt/cli/app_detection.rb +23 -1
- data/lib/belt/cli/deploy_command.rb +334 -6
- data/lib/belt/cli/destroy_command.rb +287 -0
- data/lib/belt/cli/environment_command.rb +11 -0
- data/lib/belt/cli/frontend_command.rb +35 -3
- data/lib/belt/cli/frontend_setup_command.rb +5 -2
- data/lib/belt/cli/generate_command.rb +49 -19
- data/lib/belt/cli/new_command.rb +5 -5
- data/lib/belt/cli/routes_command/route_inference.rb +4 -0
- data/lib/belt/cli/server_command.rb +3 -3
- data/lib/belt/cli.rb +24 -3
- data/lib/belt/controllers/welcome_controller.rb +9 -65
- data/lib/belt/rendering.rb +136 -0
- data/lib/belt/version.rb +1 -1
- data/lib/belt/views/welcome/show.html.erb +51 -0
- data/lib/belt.rb +1 -0
- data/lib/belt_controller/base.rb +2 -0
- data/lib/templates/environment/main.tf.erb +16 -3
- data/lib/templates/environment/outputs.tf.erb +5 -0
- data/lib/templates/frontend_infra/frontend.tf.erb +0 -4
- data/lib/templates/new_app/infrastructure/routes.tf.rb.erb +1 -1
- data/lib/templates/new_app/lambda/api.rb.erb +2 -2
- data/lib/templates/new_app/lambda/controllers/application_controller.rb.erb +1 -1
- data/lib/templates/new_app/lambda/lib/routes/routes.rb.erb +1 -1
- metadata +5 -2
- data/lib/belt/assets/belt-default.png +0 -0
|
@@ -12,69 +12,17 @@ module Belt
|
|
|
12
12
|
ASSETS_DIR = File.expand_path('../assets', __dir__)
|
|
13
13
|
|
|
14
14
|
def show
|
|
15
|
-
title = ENV.fetch('WELCOME_TITLE', 'Welcome to Belt')
|
|
16
|
-
subtitle = ENV.fetch('WELCOME_SUBTITLE', 'API Gateway → Lambda → DynamoDB — all connected.')
|
|
17
|
-
|
|
15
|
+
@title = ENV.fetch('WELCOME_TITLE', 'Welcome to Belt')
|
|
16
|
+
@subtitle = ENV.fetch('WELCOME_SUBTITLE', 'API Gateway → Lambda → DynamoDB — all connected.')
|
|
17
|
+
@css = welcome_css
|
|
18
|
+
@background_image = background_image_base64
|
|
19
|
+
@dynamodb_connected = dynamodb_connected?
|
|
18
20
|
|
|
19
|
-
|
|
21
|
+
render
|
|
20
22
|
end
|
|
21
23
|
|
|
22
24
|
private
|
|
23
25
|
|
|
24
|
-
def render_welcome_page(title:, subtitle:, app_name:)
|
|
25
|
-
<<~HTML
|
|
26
|
-
<!DOCTYPE html>
|
|
27
|
-
<html lang="en">
|
|
28
|
-
<head>
|
|
29
|
-
<meta charset="UTF-8">
|
|
30
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
31
|
-
<title>#{escape_html(title)}</title>
|
|
32
|
-
<style>#{welcome_css}</style>
|
|
33
|
-
</head>
|
|
34
|
-
<body>
|
|
35
|
-
<div class="hero">
|
|
36
|
-
<img src="data:image/png;base64,#{background_image_base64}" alt="Belt" class="hero-bg" />
|
|
37
|
-
<div class="overlay">
|
|
38
|
-
<h1>#{escape_html(title)}</h1>
|
|
39
|
-
<p class="subtitle">#{escape_html(subtitle)}</p>
|
|
40
|
-
</div>
|
|
41
|
-
</div>
|
|
42
|
-
<div class="container">
|
|
43
|
-
<div class="stack-check">
|
|
44
|
-
<div class="check-item check-pass">
|
|
45
|
-
<span class="icon">✓</span>
|
|
46
|
-
<span>API Gateway</span>
|
|
47
|
-
</div>
|
|
48
|
-
<div class="arrow">→</div>
|
|
49
|
-
<div class="check-item check-pass">
|
|
50
|
-
<span class="icon">✓</span>
|
|
51
|
-
<span>Lambda</span>
|
|
52
|
-
</div>
|
|
53
|
-
<div class="arrow">→</div>
|
|
54
|
-
#{dynamodb_check_html}
|
|
55
|
-
</div>
|
|
56
|
-
<div class="next-steps">
|
|
57
|
-
<h2>Next Steps</h2>
|
|
58
|
-
<ol>
|
|
59
|
-
<li>Generate a resource: <code>belt g resource post title:string body:text</code></li>
|
|
60
|
-
<li>Deploy: <code>belt deploy</code></li>
|
|
61
|
-
<li>This page will be replaced once you define your own root route.</li>
|
|
62
|
-
</ol>
|
|
63
|
-
</div>
|
|
64
|
-
</div>
|
|
65
|
-
</body>
|
|
66
|
-
</html>
|
|
67
|
-
HTML
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
def dynamodb_check_html
|
|
71
|
-
if dynamodb_connected?
|
|
72
|
-
'<div class="check-item check-pass"><span class="icon">✓</span><span>DynamoDB</span></div>'
|
|
73
|
-
else
|
|
74
|
-
'<div class="check-item check-warn"><span class="icon">⚠</span><span>DynamoDB</span></div>'
|
|
75
|
-
end
|
|
76
|
-
end
|
|
77
|
-
|
|
78
26
|
def dynamodb_connected?
|
|
79
27
|
return false unless defined?(Aws::DynamoDB::Client)
|
|
80
28
|
|
|
@@ -85,17 +33,13 @@ module Belt
|
|
|
85
33
|
false
|
|
86
34
|
end
|
|
87
35
|
|
|
88
|
-
def escape_html(text)
|
|
89
|
-
text.to_s.gsub('&', '&').gsub('<', '<').gsub('>', '>').gsub('"', '"')
|
|
90
|
-
end
|
|
91
|
-
|
|
92
36
|
def welcome_css
|
|
93
|
-
@
|
|
37
|
+
@welcome_css_content ||= File.read(File.join(ASSETS_DIR, 'welcome.css'))
|
|
94
38
|
end
|
|
95
39
|
|
|
96
40
|
def background_image_base64
|
|
97
|
-
@
|
|
98
|
-
File.binread(File.join(ASSETS_DIR, 'belt-default.
|
|
41
|
+
@background_image_content ||= Base64.strict_encode64(
|
|
42
|
+
File.binread(File.join(ASSETS_DIR, 'belt-default.jpg'))
|
|
99
43
|
)
|
|
100
44
|
end
|
|
101
45
|
end
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'erb'
|
|
4
|
+
require 'cgi'
|
|
5
|
+
|
|
6
|
+
module Belt
|
|
7
|
+
# Provides Rails-like ERB rendering for BeltController.
|
|
8
|
+
#
|
|
9
|
+
# Convention: templates live at `views/<controller_name>/<action>.html.erb`
|
|
10
|
+
# relative to the controller's gem or app directory.
|
|
11
|
+
#
|
|
12
|
+
# Usage:
|
|
13
|
+
# class WelcomeController < BeltController::Base
|
|
14
|
+
# def show
|
|
15
|
+
# @title = "Hello"
|
|
16
|
+
# render # auto-resolves to views/welcome/show.html.erb
|
|
17
|
+
# end
|
|
18
|
+
# end
|
|
19
|
+
#
|
|
20
|
+
# Or explicit:
|
|
21
|
+
# render template: "welcome/show"
|
|
22
|
+
# render inline: "<h1><%= @title %></h1>"
|
|
23
|
+
#
|
|
24
|
+
module Rendering
|
|
25
|
+
def self.included(base)
|
|
26
|
+
base.extend(ClassMethods)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
module ClassMethods
|
|
30
|
+
# Override in subclasses to set a custom view path.
|
|
31
|
+
# Defaults to `views/` relative to the file that defines the controller.
|
|
32
|
+
def view_paths
|
|
33
|
+
@view_paths ||= []
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def prepend_view_path(path)
|
|
37
|
+
view_paths.unshift(path)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def append_view_path(path)
|
|
41
|
+
view_paths << path
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
private
|
|
46
|
+
|
|
47
|
+
# Render an ERB template and return an html_response.
|
|
48
|
+
#
|
|
49
|
+
# Options:
|
|
50
|
+
# render — auto-resolve from controller/action
|
|
51
|
+
# render template: "ctrl/action" — explicit template path (no extension)
|
|
52
|
+
# render inline: "<%= @x %>" — render a string as ERB
|
|
53
|
+
# render status: 201 — custom status code
|
|
54
|
+
# render layout: false — skip layout (default: no layout)
|
|
55
|
+
#
|
|
56
|
+
def render(options = {})
|
|
57
|
+
status = options.fetch(:status, 200)
|
|
58
|
+
|
|
59
|
+
html = if options.key?(:inline)
|
|
60
|
+
render_erb_string(options[:inline])
|
|
61
|
+
else
|
|
62
|
+
template_path = resolve_template_path(options[:template])
|
|
63
|
+
render_erb_file(template_path)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
html_response(html, status)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def render_erb_string(source)
|
|
70
|
+
erb = ERB.new(source, trim_mode: '-')
|
|
71
|
+
erb.result(binding)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def render_erb_file(path)
|
|
75
|
+
raise Belt::TemplateNotFound, "Template not found: #{path}" unless File.exist?(path)
|
|
76
|
+
|
|
77
|
+
source = File.read(path)
|
|
78
|
+
erb = ERB.new(source, trim_mode: '-')
|
|
79
|
+
erb.filename = path
|
|
80
|
+
erb.result(binding)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# HTML-escape helper, available in templates as `h(value)`
|
|
84
|
+
def h(text)
|
|
85
|
+
CGI.escapeHTML(text.to_s)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def resolve_template_path(explicit_template = nil)
|
|
89
|
+
template_name = explicit_template || default_template_name
|
|
90
|
+
search_paths = build_view_search_paths
|
|
91
|
+
|
|
92
|
+
search_paths.each do |base|
|
|
93
|
+
full_path = File.join(base, "#{template_name}.html.erb")
|
|
94
|
+
return full_path if File.exist?(full_path)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# If nothing found, return the first candidate so the error message is useful
|
|
98
|
+
File.join(search_paths.first || '.', "#{template_name}.html.erb")
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def default_template_name
|
|
102
|
+
# PostsController → posts, Belt::WelcomeController → welcome
|
|
103
|
+
controller = self.class.name.split('::').last
|
|
104
|
+
.sub(/Controller$/, '')
|
|
105
|
+
.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
|
|
106
|
+
.gsub(/([a-z\d])([A-Z])/, '\1_\2')
|
|
107
|
+
.downcase
|
|
108
|
+
"#{controller}/#{action_name}"
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def build_view_search_paths
|
|
112
|
+
paths = []
|
|
113
|
+
|
|
114
|
+
# 1. Class-level view paths (set by the controller or gem)
|
|
115
|
+
paths.concat(self.class.view_paths) if self.class.respond_to?(:view_paths)
|
|
116
|
+
|
|
117
|
+
# 2. Walk up the class hierarchy for inherited view paths
|
|
118
|
+
klass = self.class.superclass
|
|
119
|
+
while klass.respond_to?(:view_paths)
|
|
120
|
+
paths.concat(klass.view_paths)
|
|
121
|
+
klass = klass.superclass
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# 3. App-level views (convention: lambda/views/ from Belt.root)
|
|
125
|
+
if defined?(Belt.root) && Belt.root
|
|
126
|
+
paths << File.join(Belt.root, 'lambda', 'views')
|
|
127
|
+
paths << File.join(Belt.root, 'views')
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# 4. Gem-level fallback (views/ relative to this file — belt gem's own views)
|
|
131
|
+
paths << File.expand_path('views', __dir__)
|
|
132
|
+
|
|
133
|
+
paths.uniq
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
data/lib/belt/version.rb
CHANGED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title><%= h(@title) %></title>
|
|
7
|
+
<style><%= @css %></style>
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<div class="hero">
|
|
11
|
+
<img src="data:image/jpeg;base64,<%= @background_image %>" alt="Belt" class="hero-bg" />
|
|
12
|
+
<div class="overlay">
|
|
13
|
+
<h1><%= h(@title) %></h1>
|
|
14
|
+
<p class="subtitle"><%= h(@subtitle) %></p>
|
|
15
|
+
</div>
|
|
16
|
+
</div>
|
|
17
|
+
<div class="container">
|
|
18
|
+
<div class="stack-check">
|
|
19
|
+
<div class="check-item check-pass">
|
|
20
|
+
<span class="icon">✓</span>
|
|
21
|
+
<span>API Gateway</span>
|
|
22
|
+
</div>
|
|
23
|
+
<div class="arrow">→</div>
|
|
24
|
+
<div class="check-item check-pass">
|
|
25
|
+
<span class="icon">✓</span>
|
|
26
|
+
<span>Lambda</span>
|
|
27
|
+
</div>
|
|
28
|
+
<div class="arrow">→</div>
|
|
29
|
+
<%- if @dynamodb_connected -%>
|
|
30
|
+
<div class="check-item check-pass">
|
|
31
|
+
<span class="icon">✓</span>
|
|
32
|
+
<span>DynamoDB</span>
|
|
33
|
+
</div>
|
|
34
|
+
<%- else -%>
|
|
35
|
+
<div class="check-item check-warn">
|
|
36
|
+
<span class="icon">⚠</span>
|
|
37
|
+
<span>DynamoDB</span>
|
|
38
|
+
</div>
|
|
39
|
+
<%- end -%>
|
|
40
|
+
</div>
|
|
41
|
+
<div class="next-steps">
|
|
42
|
+
<h2>Next Steps</h2>
|
|
43
|
+
<ol>
|
|
44
|
+
<li>Generate a resource: <code>belt g resource post title:string body:text</code></li>
|
|
45
|
+
<li>Deploy: <code>belt deploy</code></li>
|
|
46
|
+
<li>This page will be replaced once you define your own root route.</li>
|
|
47
|
+
</ol>
|
|
48
|
+
</div>
|
|
49
|
+
</div>
|
|
50
|
+
</body>
|
|
51
|
+
</html>
|
data/lib/belt.rb
CHANGED
data/lib/belt_controller/base.rb
CHANGED
|
@@ -6,10 +6,12 @@ require_relative '../belt/parameters'
|
|
|
6
6
|
require_relative '../belt/helpers/response'
|
|
7
7
|
require_relative '../belt/helpers/error_logging'
|
|
8
8
|
require_relative '../belt/helpers/cors_origin'
|
|
9
|
+
require_relative '../belt/rendering'
|
|
9
10
|
|
|
10
11
|
module BeltController
|
|
11
12
|
class Base
|
|
12
13
|
include Belt::Helpers::Response
|
|
14
|
+
include Belt::Rendering
|
|
13
15
|
|
|
14
16
|
attr_reader :event, :body
|
|
15
17
|
|
|
@@ -20,7 +20,7 @@ provider "aws" {
|
|
|
20
20
|
|
|
21
21
|
default_tags {
|
|
22
22
|
tags = {
|
|
23
|
-
Project =
|
|
23
|
+
Project = var.app_name
|
|
24
24
|
Environment = var.environment
|
|
25
25
|
ManagedBy = "Terraform"
|
|
26
26
|
}
|
|
@@ -37,7 +37,20 @@ resource "conveyor_belt" "main" {
|
|
|
37
37
|
source = "${path.module}/../routes.tf.rb"
|
|
38
38
|
app_name = var.app_name
|
|
39
39
|
lambda_source_dir = "${path.module}/../../lambda"
|
|
40
|
-
lambda_shared_dirs = ["controllers", "helpers", "lib", "models", "
|
|
40
|
+
lambda_shared_dirs = ["controllers", "helpers", "lib", "models", "views"]
|
|
41
41
|
frontend_urls = ["http://localhost:3000"]
|
|
42
|
-
friendly_errors =
|
|
42
|
+
friendly_errors = var.environment != "prod"
|
|
43
|
+
|
|
44
|
+
# Per-lambda configuration: env vars, timeouts, memory
|
|
45
|
+
lambda_config = {
|
|
46
|
+
api = {
|
|
47
|
+
# timeout = 30
|
|
48
|
+
# memory_size = 256
|
|
49
|
+
|
|
50
|
+
env_vars = {
|
|
51
|
+
WELCOME_TITLE = "Welcome to ${var.app_name}"
|
|
52
|
+
WELCOME_SUBTITLE = "API Gateway → Lambda → DynamoDB — all connected."
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
43
56
|
}
|
|
@@ -10,12 +10,12 @@ ActiveItem.configure do |config|
|
|
|
10
10
|
config.environment = ENV['ENVIRONMENT']
|
|
11
11
|
end
|
|
12
12
|
|
|
13
|
-
require_relative 'lib/routes
|
|
13
|
+
require_relative 'lib/routes/api_routes'
|
|
14
14
|
<% @resources&.each do |r| -%>
|
|
15
15
|
require_relative 'controllers/<%= @app_name %>/<%= r %>_controller'
|
|
16
16
|
<% end -%>
|
|
17
17
|
|
|
18
|
-
ROUTER = Belt::ActionRouter.new(routes: Routes
|
|
18
|
+
ROUTER = Belt::ActionRouter.new(routes: Routes::API, namespace: 'api')
|
|
19
19
|
|
|
20
20
|
def execute(path:, body:, event:)
|
|
21
21
|
ROUTER.route(event: event, body: body)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Routes
|
|
4
|
-
|
|
4
|
+
API = [
|
|
5
5
|
{ verb: 'GET', path: '/', controller: 'welcome', action: 'show' },
|
|
6
6
|
# { verb: 'GET', path: '/posts', controller: 'posts', action: 'index' },
|
|
7
7
|
# { verb: 'POST', path: '/posts', controller: 'posts', action: 'create' },
|
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.1.
|
|
4
|
+
version: 0.1.7
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Stowzilla
|
|
@@ -52,13 +52,14 @@ files:
|
|
|
52
52
|
- exe/belt
|
|
53
53
|
- lib/belt.rb
|
|
54
54
|
- lib/belt/action_router.rb
|
|
55
|
-
- lib/belt/assets/belt-default.
|
|
55
|
+
- lib/belt/assets/belt-default.jpg
|
|
56
56
|
- lib/belt/assets/welcome.css
|
|
57
57
|
- lib/belt/cli.rb
|
|
58
58
|
- lib/belt/cli/app_detection.rb
|
|
59
59
|
- lib/belt/cli/bucket_security.rb
|
|
60
60
|
- lib/belt/cli/console_command.rb
|
|
61
61
|
- lib/belt/cli/deploy_command.rb
|
|
62
|
+
- lib/belt/cli/destroy_command.rb
|
|
62
63
|
- lib/belt/cli/env_resolver.rb
|
|
63
64
|
- lib/belt/cli/environment_command.rb
|
|
64
65
|
- lib/belt/cli/frontend_command.rb
|
|
@@ -82,10 +83,12 @@ files:
|
|
|
82
83
|
- lib/belt/lambda_handler.rb
|
|
83
84
|
- lib/belt/observability.rb
|
|
84
85
|
- lib/belt/parameters.rb
|
|
86
|
+
- lib/belt/rendering.rb
|
|
85
87
|
- lib/belt/root.rb
|
|
86
88
|
- lib/belt/route_dsl.rb
|
|
87
89
|
- lib/belt/table_inference.rb
|
|
88
90
|
- lib/belt/version.rb
|
|
91
|
+
- lib/belt/views/welcome/show.html.erb
|
|
89
92
|
- lib/belt_controller/base.rb
|
|
90
93
|
- lib/templates/environment/backend.tf.erb
|
|
91
94
|
- lib/templates/environment/main.tf.erb
|
|
Binary file
|