belt 0.0.7 → 0.1.1

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 (66) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/CHANGELOG.md +29 -1
  4. data/README.md +150 -51
  5. data/exe/belt +6 -0
  6. data/lib/belt/action_router.rb +7 -1
  7. data/lib/belt/cli/app_detection.rb +16 -0
  8. data/lib/belt/cli/bucket_security.rb +122 -0
  9. data/lib/belt/cli/env_resolver.rb +15 -0
  10. data/lib/belt/cli/environment_command.rb +77 -0
  11. data/lib/belt/cli/frontend_command.rb +85 -0
  12. data/lib/belt/cli/frontend_deploy_command.rb +125 -0
  13. data/lib/belt/cli/frontend_setup_command.rb +64 -0
  14. data/lib/belt/cli/generate_command.rb +206 -0
  15. data/lib/belt/cli/new_command.rb +126 -0
  16. data/lib/belt/cli/routes_command/route_inference.rb +100 -0
  17. data/lib/belt/cli/routes_command/schema_loader.rb +71 -0
  18. data/lib/belt/cli/routes_command.rb +307 -0
  19. data/lib/belt/cli/setup_command.rb +261 -0
  20. data/lib/belt/cli/tables_command.rb +138 -0
  21. data/lib/belt/cli/tasks_command.rb +110 -0
  22. data/lib/belt/cli/terraform_command.rb +77 -0
  23. data/lib/belt/cli/views_command.rb +134 -0
  24. data/lib/belt/cli.rb +117 -0
  25. data/lib/belt/lambda_handler.rb +16 -0
  26. data/lib/belt/root.rb +26 -0
  27. data/lib/belt/route_dsl.rb +605 -0
  28. data/lib/belt/table_inference.rb +71 -0
  29. data/lib/belt/version.rb +1 -1
  30. data/lib/belt.rb +1 -0
  31. data/lib/templates/environment/backend.tf.erb +8 -0
  32. data/lib/templates/environment/main.tf.erb +42 -0
  33. data/lib/templates/environment/terraform.tfvars.erb +1 -0
  34. data/lib/templates/environment/variables.tf.erb +16 -0
  35. data/lib/templates/frontend/react/index.html.erb +12 -0
  36. data/lib/templates/frontend/react/package.json.erb +20 -0
  37. data/lib/templates/frontend/react/src/App.jsx +14 -0
  38. data/lib/templates/frontend/react/src/index.css +10 -0
  39. data/lib/templates/frontend/react/src/lib/apiClient.js.erb +19 -0
  40. data/lib/templates/frontend/react/src/main.jsx +10 -0
  41. data/lib/templates/frontend/react/src/pages/Home.jsx.erb +10 -0
  42. data/lib/templates/frontend/react/vite.config.js +8 -0
  43. data/lib/templates/frontend_infra/frontend.tf.erb +159 -0
  44. data/lib/templates/generate/controller.rb.erb +59 -0
  45. data/lib/templates/generate/model.rb.erb +20 -0
  46. data/lib/templates/new_app/AGENTS.md.erb +130 -0
  47. data/lib/templates/new_app/Gemfile.erb +5 -0
  48. data/lib/templates/new_app/README.md.erb +25 -0
  49. data/lib/templates/new_app/Rakefile.erb +12 -0
  50. data/lib/templates/new_app/gitignore.erb +14 -0
  51. data/lib/templates/new_app/infrastructure/routes.tf.rb.erb +5 -0
  52. data/lib/templates/new_app/infrastructure/schema.tf.rb.erb +9 -0
  53. data/lib/templates/new_app/lambda/Gemfile.erb +7 -0
  54. data/lib/templates/new_app/lambda/api.rb.erb +22 -0
  55. data/lib/templates/new_app/lambda/controllers/application_controller.rb.erb +6 -0
  56. data/lib/templates/new_app/lambda/lib/routes/routes.rb.erb +11 -0
  57. data/lib/templates/new_app/lambda/models/application_record.rb.erb +6 -0
  58. data/lib/templates/new_app/lambda/models/concerns/timestampable.rb.erb +23 -0
  59. data/lib/templates/views/Edit.jsx.erb +38 -0
  60. data/lib/templates/views/Form.jsx.erb +34 -0
  61. data/lib/templates/views/Index.jsx.erb +39 -0
  62. data/lib/templates/views/New.jsx.erb +26 -0
  63. data/lib/templates/views/Show.jsx.erb +46 -0
  64. data.tar.gz.sig +0 -0
  65. metadata +73 -3
  66. metadata.gz.sig +0 -0
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'activeitem'
4
+
5
+ class ApplicationRecord < ActiveItem::Base
6
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Concerns
4
+ module Timestampable
5
+ def self.included(base)
6
+ base.attr_accessor :created_at, :updated_at
7
+ base.set_callback :create, :before, :set_timestamps
8
+ base.set_callback :update, :before, :set_updated_at
9
+ end
10
+
11
+ private
12
+
13
+ def set_timestamps
14
+ now = Time.now.utc.iso8601
15
+ self.created_at ||= now
16
+ self.updated_at = now
17
+ end
18
+
19
+ def set_updated_at
20
+ self.updated_at = Time.now.utc.iso8601
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,38 @@
1
+ import { useEffect, useState } from 'react'
2
+ import { useParams, useNavigate } from 'react-router-dom'
3
+ import { apiClient } from '../../lib/apiClient'
4
+ import <%= @class_name %>Form from './<%= @class_name %>Form'
5
+
6
+ export default function <%= @class_name %>Edit() {
7
+ const { id } = useParams()
8
+ const navigate = useNavigate()
9
+ const [<%= @singular_name %>, set<%= @class_name %>] = useState(null)
10
+ const [loading, setLoading] = useState(true)
11
+ const [error, setError] = useState(null)
12
+
13
+ useEffect(() => {
14
+ apiClient(`/<%= @resource_name %>/${id}`)
15
+ .then(data => set<%= @class_name %>(data.<%= @singular_name %>))
16
+ .finally(() => setLoading(false))
17
+ }, [id])
18
+
19
+ async function handleSubmit(attrs) {
20
+ try {
21
+ await apiClient(`/<%= @resource_name %>/${id}`, { method: 'PUT', body: attrs })
22
+ navigate(`/<%= @resource_name %>/${id}`)
23
+ } catch (e) {
24
+ setError(e.message)
25
+ }
26
+ }
27
+
28
+ if (loading) return <p>Loading...</p>
29
+ if (!<%= @singular_name %>) return <p><%= @class_name %> not found.</p>
30
+
31
+ return (
32
+ <main style={{ padding: '2rem', maxWidth: '800px', margin: '0 auto' }}>
33
+ <h1>Edit <%= @class_name %></h1>
34
+ {error && <p style={{ color: 'red' }}>{error}</p>}
35
+ <<%= @class_name %>Form initialValues={<%= @singular_name %>} onSubmit={handleSubmit} submitLabel="Update" />
36
+ </main>
37
+ )
38
+ }
@@ -0,0 +1,34 @@
1
+ import { useState } from 'react'
2
+ import { Link } from 'react-router-dom'
3
+
4
+ export default function <%= @class_name %>Form({ initialValues = {}, onSubmit, submitLabel = 'Create' }) {
5
+ <% @fields.each do |field| -%>
6
+ const [<%= field[:name] %>, set<%= field[:name].split('_').map(&:capitalize).join %>] = useState(initialValues.<%= field[:name] %> || '')
7
+ <% end -%>
8
+
9
+ function handleSubmit(e) {
10
+ e.preventDefault()
11
+ onSubmit({ <%= @fields.map { |f| f[:name] }.join(', ') %> })
12
+ }
13
+
14
+ return (
15
+ <form onSubmit={handleSubmit}>
16
+ <% @fields.each do |field| -%>
17
+ <div style={{ marginBottom: '1rem' }}>
18
+ <label style={{ display: 'block', marginBottom: '0.25rem' }}><%= field[:name].split('_').map(&:capitalize).join(' ') %></label>
19
+ <% if field[:type] == 'text' -%>
20
+ <textarea value={<%= field[:name] %>} onChange={e => set<%= field[:name].split('_').map(&:capitalize).join %>(e.target.value)}
21
+ rows={6} style={{ width: '100%' }} />
22
+ <% else -%>
23
+ <input type="text" value={<%= field[:name] %>} onChange={e => set<%= field[:name].split('_').map(&:capitalize).join %>(e.target.value)}
24
+ style={{ width: '100%', padding: '0.5rem' }} />
25
+ <% end -%>
26
+ </div>
27
+ <% end -%>
28
+
29
+ <button type="submit">{submitLabel}</button>
30
+ {' '}
31
+ <Link to="/<%= @resource_name %>">Cancel</Link>
32
+ </form>
33
+ )
34
+ }
@@ -0,0 +1,39 @@
1
+ import { useEffect, useState } from 'react'
2
+ import { Link } from 'react-router-dom'
3
+ import { apiClient } from '../../lib/apiClient'
4
+
5
+ export default function <%= @class_name %>sIndex() {
6
+ const [<%= @resource_name %>, set<%= @class_name %>s] = useState([])
7
+ const [loading, setLoading] = useState(true)
8
+
9
+ useEffect(() => {
10
+ apiClient('/<%= @resource_name %>')
11
+ .then(data => set<%= @class_name %>s(data.<%= @resource_name %> || []))
12
+ .finally(() => setLoading(false))
13
+ }, [])
14
+
15
+ if (loading) return <p>Loading...</p>
16
+
17
+ return (
18
+ <main style={{ padding: '2rem', maxWidth: '800px', margin: '0 auto' }}>
19
+ <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
20
+ <h1><%= @class_name %>s</h1>
21
+ <Link to="/<%= @resource_name %>/new">New <%= @class_name %></Link>
22
+ </div>
23
+
24
+ {<%= @resource_name %>.length === 0 ? (
25
+ <p>No <%= @resource_name %> yet.</p>
26
+ ) : (
27
+ <ul style={{ listStyle: 'none', padding: 0 }}>
28
+ {<%= @resource_name %>.map(<%= @singular_name %> => (
29
+ <li key={<%= @singular_name %>.id} style={{ padding: '0.5rem 0', borderBottom: '1px solid #eee' }}>
30
+ <Link to={`/<%= @resource_name %>/${<%= @singular_name %>.id}`}>
31
+ {<%= @singular_name %>.<%= @fields.first&.dig(:name) || 'id' %>}
32
+ </Link>
33
+ </li>
34
+ ))}
35
+ </ul>
36
+ )}
37
+ </main>
38
+ )
39
+ }
@@ -0,0 +1,26 @@
1
+ import { useState } from 'react'
2
+ import { useNavigate } from 'react-router-dom'
3
+ import { apiClient } from '../../lib/apiClient'
4
+ import <%= @class_name %>Form from './<%= @class_name %>Form'
5
+
6
+ export default function <%= @class_name %>New() {
7
+ const navigate = useNavigate()
8
+ const [error, setError] = useState(null)
9
+
10
+ async function handleSubmit(attrs) {
11
+ try {
12
+ const data = await apiClient('/<%= @resource_name %>', { method: 'POST', body: attrs })
13
+ navigate(`/<%= @resource_name %>/${data.<%= @singular_name %>.id}`)
14
+ } catch (e) {
15
+ setError(e.message)
16
+ }
17
+ }
18
+
19
+ return (
20
+ <main style={{ padding: '2rem', maxWidth: '800px', margin: '0 auto' }}>
21
+ <h1>New <%= @class_name %></h1>
22
+ {error && <p style={{ color: 'red' }}>{error}</p>}
23
+ <<%= @class_name %>Form onSubmit={handleSubmit} />
24
+ </main>
25
+ )
26
+ }
@@ -0,0 +1,46 @@
1
+ import { useEffect, useState } from 'react'
2
+ import { useParams, Link, useNavigate } from 'react-router-dom'
3
+ import { apiClient } from '../../lib/apiClient'
4
+
5
+ export default function <%= @class_name %>Show() {
6
+ const { id } = useParams()
7
+ const navigate = useNavigate()
8
+ const [<%= @singular_name %>, set<%= @class_name %>] = useState(null)
9
+ const [loading, setLoading] = useState(true)
10
+
11
+ useEffect(() => {
12
+ apiClient(`/<%= @resource_name %>/${id}`)
13
+ .then(data => set<%= @class_name %>(data.<%= @singular_name %>))
14
+ .finally(() => setLoading(false))
15
+ }, [id])
16
+
17
+ async function handleDelete() {
18
+ if (!confirm('Are you sure?')) return
19
+ await apiClient(`/<%= @resource_name %>/${id}`, { method: 'DELETE' })
20
+ navigate('/<%= @resource_name %>')
21
+ }
22
+
23
+ if (loading) return <p>Loading...</p>
24
+ if (!<%= @singular_name %>) return <p><%= @class_name %> not found.</p>
25
+
26
+ return (
27
+ <main style={{ padding: '2rem', maxWidth: '800px', margin: '0 auto' }}>
28
+ <Link to="/<%= @resource_name %>">&larr; Back to <%= @resource_name %></Link>
29
+ <% @fields.each do |field| -%>
30
+ <% if field[:type] == 'text' -%>
31
+ <div style={{ margin: '1rem 0', whiteSpace: 'pre-wrap' }}>{<%= @singular_name %>.<%= field[:name] %>}</div>
32
+ <% else -%>
33
+ <p><strong><%= field[:name].split('_').map(&:capitalize).join(' ') %>:</strong> {<%= @singular_name %>.<%= field[:name] %>}</p>
34
+ <% end -%>
35
+ <% end -%>
36
+
37
+ <div style={{ marginTop: '1rem' }}>
38
+ <Link to={`/<%= @resource_name %>/${id}/edit`}>Edit</Link>
39
+ {' | '}
40
+ <button onClick={handleDelete} style={{ background: 'none', border: 'none', color: 'red', cursor: 'pointer' }}>
41
+ Delete
42
+ </button>
43
+ </div>
44
+ </main>
45
+ )
46
+ }
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,11 +1,11 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: belt
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.7
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stowzilla
8
- bindir: bin
8
+ bindir: exe
9
9
  cert_chain:
10
10
  - |
11
11
  -----BEGIN CERTIFICATE-----
@@ -36,6 +36,20 @@ cert_chain:
36
36
  -----END CERTIFICATE-----
37
37
  date: 1980-01-02 00:00:00.000000000 Z
38
38
  dependencies:
39
+ - !ruby/object:Gem::Dependency
40
+ name: activeitem
41
+ requirement: !ruby/object:Gem::Requirement
42
+ requirements:
43
+ - - "~>"
44
+ - !ruby/object:Gem::Version
45
+ version: '0.0'
46
+ type: :runtime
47
+ prerelease: false
48
+ version_requirements: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - "~>"
51
+ - !ruby/object:Gem::Version
52
+ version: '0.0'
39
53
  - !ruby/object:Gem::Dependency
40
54
  name: lambda_loadout
41
55
  requirement: !ruby/object:Gem::Requirement
@@ -54,7 +68,8 @@ description: Belt provides a collection of lightweight utilities for Ruby applic
54
68
  email:
55
69
  - andy@stowzilla.com
56
70
  - adam@stowzilla.com
57
- executables: []
71
+ executables:
72
+ - belt
58
73
  extensions: []
59
74
  extra_rdoc_files: []
60
75
  files:
@@ -62,16 +77,71 @@ files:
62
77
  - LICENSE.txt
63
78
  - README.md
64
79
  - certs/stowzilla.pem
80
+ - exe/belt
65
81
  - lib/belt.rb
66
82
  - lib/belt/action_router.rb
83
+ - lib/belt/cli.rb
84
+ - lib/belt/cli/app_detection.rb
85
+ - lib/belt/cli/bucket_security.rb
86
+ - lib/belt/cli/env_resolver.rb
87
+ - lib/belt/cli/environment_command.rb
88
+ - lib/belt/cli/frontend_command.rb
89
+ - lib/belt/cli/frontend_deploy_command.rb
90
+ - lib/belt/cli/frontend_setup_command.rb
91
+ - lib/belt/cli/generate_command.rb
92
+ - lib/belt/cli/new_command.rb
93
+ - lib/belt/cli/routes_command.rb
94
+ - lib/belt/cli/routes_command/route_inference.rb
95
+ - lib/belt/cli/routes_command/schema_loader.rb
96
+ - lib/belt/cli/setup_command.rb
97
+ - lib/belt/cli/tables_command.rb
98
+ - lib/belt/cli/tasks_command.rb
99
+ - lib/belt/cli/terraform_command.rb
100
+ - lib/belt/cli/views_command.rb
67
101
  - lib/belt/helpers/cors_origin.rb
68
102
  - lib/belt/helpers/error_logging.rb
69
103
  - lib/belt/helpers/response.rb
70
104
  - lib/belt/lambda_handler.rb
71
105
  - lib/belt/observability.rb
72
106
  - lib/belt/parameters.rb
107
+ - lib/belt/root.rb
108
+ - lib/belt/route_dsl.rb
109
+ - lib/belt/table_inference.rb
73
110
  - lib/belt/version.rb
74
111
  - lib/belt_controller/base.rb
112
+ - lib/templates/environment/backend.tf.erb
113
+ - lib/templates/environment/main.tf.erb
114
+ - lib/templates/environment/terraform.tfvars.erb
115
+ - lib/templates/environment/variables.tf.erb
116
+ - lib/templates/frontend/react/index.html.erb
117
+ - lib/templates/frontend/react/package.json.erb
118
+ - lib/templates/frontend/react/src/App.jsx
119
+ - lib/templates/frontend/react/src/index.css
120
+ - lib/templates/frontend/react/src/lib/apiClient.js.erb
121
+ - lib/templates/frontend/react/src/main.jsx
122
+ - lib/templates/frontend/react/src/pages/Home.jsx.erb
123
+ - lib/templates/frontend/react/vite.config.js
124
+ - lib/templates/frontend_infra/frontend.tf.erb
125
+ - lib/templates/generate/controller.rb.erb
126
+ - lib/templates/generate/model.rb.erb
127
+ - lib/templates/new_app/AGENTS.md.erb
128
+ - lib/templates/new_app/Gemfile.erb
129
+ - lib/templates/new_app/README.md.erb
130
+ - lib/templates/new_app/Rakefile.erb
131
+ - lib/templates/new_app/gitignore.erb
132
+ - lib/templates/new_app/infrastructure/routes.tf.rb.erb
133
+ - lib/templates/new_app/infrastructure/schema.tf.rb.erb
134
+ - lib/templates/new_app/lambda/Gemfile.erb
135
+ - lib/templates/new_app/lambda/api.rb.erb
136
+ - lib/templates/new_app/lambda/controllers/application_controller.rb.erb
137
+ - lib/templates/new_app/lambda/lib/routes/routes.rb.erb
138
+ - lib/templates/new_app/lambda/models/application_record.rb.erb
139
+ - lib/templates/new_app/lambda/models/concerns/timestampable.rb.erb
140
+ - lib/templates/views/Edit.jsx.erb
141
+ - lib/templates/views/Form.jsx.erb
142
+ - lib/templates/views/Index.jsx.erb
143
+ - lib/templates/views/New.jsx.erb
144
+ - lib/templates/views/Show.jsx.erb
75
145
  homepage: https://github.com/stowzilla/belt
76
146
  licenses:
77
147
  - MIT
metadata.gz.sig CHANGED
Binary file