belt 0.2.17 → 0.2.18

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4e1813b72986b167b9449f438b18331cc2e9be46cffa58bd059c403f3c35703e
4
- data.tar.gz: 26c3aae3c049bbebff471d6a8a514114a26f8b2d0fdf4671cb154ae9a056798f
3
+ metadata.gz: b5e83d99ec1a6a02931418e3ade22cb51b291028884529e25abbd74f7c23a41f
4
+ data.tar.gz: 938cc1a121a468de4dcb838c04fa44b7a01b78e5165a5da5f805cb330c88d59d
5
5
  SHA512:
6
- metadata.gz: 23bbf9977c0cd765c7a1630e7a74ed480db6d62ee07c22e95e6a491a3a9c04d824b8463b0ef63ae4c4b343e13e96ff26c9edb5c86c8005fce7f0a167b6894c64
7
- data.tar.gz: e7f625fe93dcb125289530e2a3bdb9dee8c37938eeea41bc9154a31aca6b47675a6336e39c986f3a801719f4cf22052bad40968ff6e1b9a82936140f74432f4a
6
+ metadata.gz: d581ff695fda0a3288f809d0c52805091dd9de73a69af5b21f4675a0f65f3d04431f72e18db3f099f6299a3a9f5b0045323218a60a54982d58312021c9bcedbd
7
+ data.tar.gz: 59927af373dde4560edbaede9b9d8f81f43b229804479d0ff874ea67193b1a97bcf67e51afc388f66b3359fc7105df1ac0b2aef61a166d29fbcd43d509881004
data/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.18
4
+
5
+ ### New generator: `belt generate auth`
6
+
7
+ Scaffolds Cognito user pool infrastructure for authentication. Creates the user pool, client, and wires `cognito_user_pool_arns` into the conveyor-belt resource automatically.
8
+
9
+ ```bash
10
+ belt g auth # Single pool: "main"
11
+ belt g auth web mobile # Multiple pools (e.g., web + mobile clients)
12
+ belt g auth web android ios # Three pools
13
+ belt destroy auth # Remove generated files
14
+ ```
15
+
16
+ What it generates:
17
+ - `infrastructure/modules/app/cognito.tf` — User pool + client with sensible defaults (password policy, email verification, deletion protection in prod)
18
+ - `infrastructure/modules/app/cognito_outputs.tf` — Pool ID, ARN, and client ID outputs
19
+ - Patches `main.tf` to pass `cognito_user_pool_arns` to the conveyor-belt resource
20
+
21
+ Multiple pools are supported out of the box — each gets a suffixed name (e.g., `myapp-prod-web`, `myapp-prod-mobile`) and its own set of outputs.
22
+
3
23
  ## 0.2.12
4
24
 
5
25
  ### Rename: `routes.tf.rb` → `routes.rb`, `schema.tf.rb` → `contracts.rb`
@@ -0,0 +1,244 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'fileutils'
4
+ require 'erb'
5
+ require_relative 'app_detection'
6
+
7
+ module Belt
8
+ module CLI
9
+ class AuthCommand
10
+ TEMPLATE_DIR = File.expand_path('../../templates/generate/auth', __dir__)
11
+ MODULE_DIR = 'infrastructure/modules/app'
12
+
13
+ include AppDetection
14
+
15
+ def self.run(args)
16
+ if args.include?('--help') || args.include?('-h')
17
+ print_help
18
+ exit 0
19
+ end
20
+
21
+ force = args.delete('--force') || args.delete('-f')
22
+ pools = parse_pools(args)
23
+
24
+ new(pools: pools, force: force).generate
25
+ end
26
+
27
+ def self.destroy(_args)
28
+ new(pools: [], force: false).remove
29
+ end
30
+
31
+ def self.print_help
32
+ puts <<~HELP
33
+ Generate Cognito user pool infrastructure for authentication.
34
+
35
+ Usage: belt generate auth [pool_names...] [options]
36
+
37
+ Options:
38
+ --force, -f Overwrite existing cognito.tf (skip collision check)
39
+
40
+ Arguments:
41
+ pool_names Optional pool names for multiple user pools.
42
+ If omitted, generates a single pool named "main".
43
+
44
+ Examples:
45
+ belt g auth # Single pool: "main"
46
+ belt g auth web # Single pool: "web"
47
+ belt g auth web mobile # Two pools: "web" and "mobile"
48
+ belt g auth web android ios # Three pools: "web", "android", "ios"
49
+ belt g auth --force # Overwrite existing
50
+
51
+ What this generates:
52
+ infrastructure/modules/app/cognito.tf User pool + client resources
53
+ infrastructure/modules/app/cognito_outputs.tf Pool ID, ARN, and client ID outputs
54
+
55
+ It also patches:
56
+ infrastructure/modules/app/main.tf Adds cognito_user_pool_arns to conveyor_belt
57
+
58
+ After running:
59
+ 1. Review the generated Cognito config in cognito.tf
60
+ 2. Customize password policy, MFA, triggers as needed
61
+ 3. Run `belt apply <env>` to deploy
62
+
63
+ To remove:
64
+ belt destroy auth
65
+ HELP
66
+ end
67
+
68
+ # Parse pool names from args. Default to ["main"] if none provided.
69
+ def self.parse_pools(args)
70
+ names = args.reject { |a| a.start_with?('-') }
71
+ names = ['main'] if names.empty?
72
+ names.map(&:downcase).map { |n| n.gsub(/[^a-z0-9_]/, '_') }
73
+ end
74
+
75
+ def initialize(pools:, force: false)
76
+ @pool_names = pools
77
+ @force = force
78
+ @app_name = detect_app_name
79
+ @pools = build_pool_metadata
80
+ end
81
+
82
+ def generate
83
+ check_collision! unless @force
84
+ ensure_module_dir!
85
+
86
+ write_cognito_tf
87
+ write_cognito_outputs_tf
88
+ patch_main_tf
89
+
90
+ puts "\n✓ Auth generated!"
91
+ puts "\nNext steps:"
92
+ puts ' 1. Review infrastructure/modules/app/cognito.tf'
93
+ puts ' 2. Customize password policy, MFA, or Lambda triggers as needed'
94
+ puts ' 3. Run `belt apply <env>` to deploy'
95
+ end
96
+
97
+ def remove
98
+ removed = []
99
+
100
+ cognito_tf = File.join(MODULE_DIR, 'cognito.tf')
101
+ cognito_outputs_tf = File.join(MODULE_DIR, 'cognito_outputs.tf')
102
+
103
+ if File.exist?(cognito_tf)
104
+ FileUtils.rm(cognito_tf)
105
+ removed << cognito_tf
106
+ puts " remove #{cognito_tf}"
107
+ end
108
+
109
+ if File.exist?(cognito_outputs_tf)
110
+ FileUtils.rm(cognito_outputs_tf)
111
+ removed << cognito_outputs_tf
112
+ puts " remove #{cognito_outputs_tf}"
113
+ end
114
+
115
+ unpatch_main_tf
116
+ removed << File.join(MODULE_DIR, 'main.tf') if @main_tf_patched
117
+
118
+ if removed.empty?
119
+ puts ' Nothing to remove — auth was not generated.'
120
+ else
121
+ puts "\n✓ Auth destroyed!"
122
+ end
123
+ end
124
+
125
+ private
126
+
127
+ def build_pool_metadata
128
+ if @pool_names.length == 1 && @pool_names.first == 'main'
129
+ [{ name: 'main', suffix: '', label: '' }]
130
+ else
131
+ @pool_names.map do |name|
132
+ { name: name, suffix: "-#{name}", label: " (#{name})" }
133
+ end
134
+ end
135
+ end
136
+
137
+ def check_collision!
138
+ cognito_tf = File.join(MODULE_DIR, 'cognito.tf')
139
+ return unless File.exist?(cognito_tf)
140
+
141
+ puts "\n✗ Auth already exists at #{cognito_tf}"
142
+ puts "\nTo overwrite, run again with --force:"
143
+ puts " belt g auth #{@pool_names.join(' ')} --force"
144
+ exit 1
145
+ end
146
+
147
+ def ensure_module_dir!
148
+ FileUtils.mkdir_p(MODULE_DIR)
149
+ end
150
+
151
+ def write_cognito_tf
152
+ dest = File.join(MODULE_DIR, 'cognito.tf')
153
+ write_template('cognito.tf.erb', dest)
154
+ puts " create #{dest}"
155
+ end
156
+
157
+ def write_cognito_outputs_tf
158
+ dest = File.join(MODULE_DIR, 'cognito_outputs.tf')
159
+ write_template('cognito_outputs.tf.erb', dest)
160
+ puts " create #{dest}"
161
+ end
162
+
163
+ def patch_main_tf
164
+ main_tf = File.join(MODULE_DIR, 'main.tf')
165
+ return unless File.exist?(main_tf)
166
+
167
+ content = File.read(main_tf)
168
+
169
+ if content.include?('cognito_user_pool_arns')
170
+ puts " skip #{main_tf} (cognito_user_pool_arns already present)"
171
+ return
172
+ end
173
+
174
+ return unless content.match?(/^resource "conveyor_belt"/)
175
+
176
+ arns = @pools.map { |p| "aws_cognito_user_pool.#{p[:name]}.arn" }
177
+ arn_value = build_arn_value(arns)
178
+
179
+ insert_cognito_into_resource(content, main_tf, arn_value)
180
+ end
181
+
182
+ def build_arn_value(arns)
183
+ if arns.length == 1
184
+ "[#{arns.first}]"
185
+ else
186
+ "[\n #{arns.join(",\n ")}\n ]"
187
+ end
188
+ end
189
+
190
+ def insert_cognito_into_resource(content, main_tf, arn_value)
191
+ lines = content.lines
192
+ brace_depth = 0
193
+ insert_index = nil
194
+ in_resource = false
195
+
196
+ lines.each_with_index do |line, idx|
197
+ if line.match?(/^resource "conveyor_belt"/)
198
+ in_resource = true
199
+ brace_depth = 0
200
+ end
201
+
202
+ next unless in_resource
203
+
204
+ brace_depth += line.count('{') - line.count('}')
205
+
206
+ next unless brace_depth <= 0
207
+
208
+ insert_index = idx
209
+ break
210
+ end
211
+
212
+ return unless insert_index
213
+
214
+ cognito_line = "\n cognito_user_pool_arns = #{arn_value}\n"
215
+ lines.insert(insert_index, cognito_line)
216
+ File.write(main_tf, lines.join)
217
+ puts " update #{main_tf} (added cognito_user_pool_arns)"
218
+ end
219
+
220
+ def unpatch_main_tf
221
+ main_tf = File.join(MODULE_DIR, 'main.tf')
222
+ @main_tf_patched = false
223
+ return unless File.exist?(main_tf)
224
+
225
+ content = File.read(main_tf)
226
+ return unless content.include?('cognito_user_pool_arns')
227
+
228
+ # Remove the cognito_user_pool_arns line(s) — could be single or multi-line
229
+ updated = content.gsub(/\n\s*cognito_user_pool_arns\s*=\s*\[[^\]]*\]\n/, "\n")
230
+ return if updated == content
231
+
232
+ File.write(main_tf, updated)
233
+ puts " update #{main_tf} (removed cognito_user_pool_arns)"
234
+ @main_tf_patched = true
235
+ end
236
+
237
+ def write_template(template_name, dest_path)
238
+ template_path = File.join(TEMPLATE_DIR, template_name)
239
+ content = ERB.new(File.read(template_path), trim_mode: '-').result(binding)
240
+ File.write(dest_path, content)
241
+ end
242
+ end
243
+ end
244
+ end
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'fileutils'
4
4
  require_relative 'app_detection'
5
+ require_relative 'auth_command'
5
6
  require_relative 'generator_registry'
6
7
  require_relative 'tables_command'
7
8
  require_relative '../inflector'
@@ -9,7 +10,7 @@ require_relative '../inflector'
9
10
  module Belt
10
11
  module CLI
11
12
  class DestroyCommand
12
- GENERATORS = %w[scaffold resource model controller environment frontend views].freeze
13
+ GENERATORS = %w[scaffold resource model controller environment frontend views auth].freeze
13
14
 
14
15
  include AppDetection
15
16
 
@@ -51,6 +52,8 @@ module Belt
51
52
  new(generator, name, [], **flags).destroy
52
53
  when 'frontend'
53
54
  new(generator, nil, []).destroy
55
+ when 'auth'
56
+ Belt::CLI::AuthCommand.destroy(args)
54
57
  when 'views'
55
58
  name = args.shift
56
59
  if name.nil? || name.empty?
@@ -98,6 +101,7 @@ module Belt
98
101
  resource Alias for scaffold
99
102
  model Remove an ActiveItem model
100
103
  controller Remove a controller
104
+ auth Remove Cognito user pool infrastructure
101
105
  environment Remove a deployment environment and tear down infrastructure
102
106
  frontend Remove the frontend/ directory
103
107
  views Remove React pages for a resource
@@ -3,6 +3,7 @@
3
3
  require 'fileutils'
4
4
  require 'erb'
5
5
  require_relative 'app_detection'
6
+ require_relative 'auth_command'
6
7
  require_relative 'environment_command'
7
8
  require_relative 'frontend_command'
8
9
  require_relative 'tables_command'
@@ -14,7 +15,7 @@ module Belt
14
15
  module CLI
15
16
  class GenerateCommand
16
17
  TEMPLATE_DIR = File.expand_path('../../templates/generate', __dir__)
17
- GENERATORS = %w[scaffold resource model controller environment frontend views].freeze
18
+ GENERATORS = %w[scaffold resource model controller environment frontend views auth].freeze
18
19
 
19
20
  include AppDetection
20
21
 
@@ -126,6 +127,8 @@ module Belt
126
127
 
127
128
  return Belt::CLI::ViewsCommand.run(args) if generator == 'views'
128
129
 
130
+ return Belt::CLI::AuthCommand.run(args) if generator == 'auth'
131
+
129
132
  name = args.shift
130
133
  if name.nil? || name.empty?
131
134
  print_generator_help(generator)
@@ -192,6 +195,7 @@ module Belt
192
195
  scaffold Generate model, controller, routes, schema, and views (full REST resource)
193
196
  model Generate an ActiveItem model
194
197
  controller Generate a controller
198
+ auth Generate Cognito user pool infrastructure
195
199
  environment Create a new deployment environment
196
200
  frontend Scaffold a frontend app (react, vue, svelte)
197
201
  views Generate React pages for a resource
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.17'
4
+ VERSION = '0.2.18'
5
5
  end
@@ -0,0 +1,62 @@
1
+ # Cognito User Pool — authentication for <%= @app_name %>
2
+ # Generated by: belt generate auth
3
+
4
+ <% @pools.each do |pool| -%>
5
+ resource "aws_cognito_user_pool" "<%= pool[:name] %>" {
6
+ name = "${var.app_name}-${var.environment}<%= pool[:suffix] %>"
7
+
8
+ password_policy {
9
+ minimum_length = 8
10
+ require_lowercase = true
11
+ require_numbers = true
12
+ require_symbols = false
13
+ require_uppercase = true
14
+ }
15
+
16
+ auto_verified_attributes = ["email"]
17
+
18
+ account_recovery_setting {
19
+ recovery_mechanism {
20
+ name = "verified_email"
21
+ priority = 1
22
+ }
23
+ }
24
+
25
+ schema {
26
+ name = "email"
27
+ attribute_data_type = "String"
28
+ required = true
29
+ mutable = true
30
+
31
+ string_attribute_constraints {
32
+ min_length = 1
33
+ max_length = 256
34
+ }
35
+ }
36
+
37
+ deletion_protection = var.environment == "prod" ? "ACTIVE" : "INACTIVE"
38
+ }
39
+
40
+ resource "aws_cognito_user_pool_client" "<%= pool[:name] %>" {
41
+ name = "${var.app_name}-${var.environment}<%= pool[:suffix] %>-client"
42
+ user_pool_id = aws_cognito_user_pool.<%= pool[:name] %>.id
43
+
44
+ explicit_auth_flows = [
45
+ "ALLOW_USER_SRP_AUTH",
46
+ "ALLOW_REFRESH_TOKEN_AUTH"
47
+ ]
48
+
49
+ prevent_user_existence_errors = "ENABLED"
50
+
51
+ token_validity_units {
52
+ access_token = "hours"
53
+ id_token = "hours"
54
+ refresh_token = "days"
55
+ }
56
+
57
+ access_token_validity = 1
58
+ id_token_validity = 1
59
+ refresh_token_validity = 30
60
+ }
61
+
62
+ <% end -%>
@@ -0,0 +1,19 @@
1
+ # Cognito outputs — generated by: belt generate auth
2
+
3
+ <% @pools.each do |pool| -%>
4
+ output "cognito_user_pool_id<%= pool[:suffix] %>" {
5
+ description = "Cognito User Pool ID<%= pool[:label] %>"
6
+ value = aws_cognito_user_pool.<%= pool[:name] %>.id
7
+ }
8
+
9
+ output "cognito_user_pool_arn<%= pool[:suffix] %>" {
10
+ description = "Cognito User Pool ARN<%= pool[:label] %>"
11
+ value = aws_cognito_user_pool.<%= pool[:name] %>.arn
12
+ }
13
+
14
+ output "cognito_client_id<%= pool[:suffix] %>" {
15
+ description = "Cognito User Pool Client ID<%= pool[:label] %>"
16
+ value = aws_cognito_user_pool_client.<%= pool[:name] %>.id
17
+ }
18
+
19
+ <% end -%>
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.2.18
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stowzilla
@@ -76,6 +76,7 @@ files:
76
76
  - lib/belt/assets/welcome.css
77
77
  - lib/belt/cli.rb
78
78
  - lib/belt/cli/app_detection.rb
79
+ - lib/belt/cli/auth_command.rb
79
80
  - lib/belt/cli/backup_config.rb
80
81
  - lib/belt/cli/backup_runner.rb
81
82
  - lib/belt/cli/bucket_security.rb
@@ -141,6 +142,8 @@ files:
141
142
  - lib/templates/frontend/react/src/pages/Home.jsx.erb
142
143
  - lib/templates/frontend/react/vite.config.js
143
144
  - lib/templates/frontend_infra/frontend.tf.erb
145
+ - lib/templates/generate/auth/cognito.tf.erb
146
+ - lib/templates/generate/auth/cognito_outputs.tf.erb
144
147
  - lib/templates/generate/controller.rb.erb
145
148
  - lib/templates/generate/model.rb.erb
146
149
  - lib/templates/module/dns.tf.erb