brut 0.19.2 → 0.20.0

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: 765659b6fbd1258a4b5615cfea8e7d273fbebd4336ceb6518367bc5ad329bcb8
4
- data.tar.gz: 35777088ab7cc473a3ef5459087349846586798154e9f699e52b3b5be22c64db
3
+ metadata.gz: afeada7e0616e77f5a11b4e975a94ee7affeaa66664c1f619c4e52de6db2426f
4
+ data.tar.gz: 520765f71bb41275569fed4d481e75ff64b3df28f0a3b3bccf12bf53e3aa223b
5
5
  SHA512:
6
- metadata.gz: 946899aa325e088a414c88e63b6bd58c7278380e90929f886905b9cd100c2a98d672d570a036e0edf66953719dccb4b6bd1dd5525f885fcc2421ea82da142ece
7
- data.tar.gz: 9bfe8cef4bd6e4effbca418ab446796fed352999f21b404d6904ec5ccd048bff9ed18de6f9fd47e8d45ad7045b73bf782375ca7bed13a1022d2af0e1e976bde5
6
+ metadata.gz: d68ff2c363e86ef216bb8c81b88360e51e643496d1b6e602db0bd969a298e8b880a68f26d5e177ba172fc707cbc880fdd998b2dd1caf6ed0ac21f83fb61ef547
7
+ data.tar.gz: f94608a8a377bb58ebcf61e4cc172c5215558c9c374e580ab95d7997c2bdae01e5460299a39a1cbcd10e98b72ee3f0a64f6b2a71bbd75e7ca30dd7fa3508d85f
@@ -51,20 +51,20 @@ class Brut::CLI::Apps::DB < Brut::CLI::Commands::BaseCommand
51
51
  rows = [
52
52
  [
53
53
  "Database Server",
54
- server_up ? theme.success.render("✅ UP") : theme.error.render("❌ DOWN")
54
+ server_up ? theme.success.render("✅ UP") : theme.error.render("❌ DOWN"),
55
55
  ],
56
56
  ]
57
57
  if server_up
58
58
  rows << [
59
59
  "Database #{theme.code.render(database_name)}",
60
- database_exists ? theme.success.render("✅ Exists") : theme.error.render("❌ DOES NOT EXIST")
60
+ database_exists ? theme.success.render("✅ Exists") : theme.error.render("❌ DOES NOT EXIST"),
61
61
  ]
62
62
  end
63
63
  if database_exists
64
64
  if migration_files.empty? && migrations_run.empty?
65
65
  rows << [
66
66
  "Migrations",
67
- "✅ NO MIGRATION FILES TO RUN"
67
+ "✅ NO MIGRATION FILES TO RUN",
68
68
  ]
69
69
  else
70
70
  migration_files.each do |filename|
@@ -19,8 +19,16 @@ class Brut::CLI::Apps::Deploy < Brut::CLI::Commands::BaseCommand
19
19
  def default_rack_env = "development"
20
20
 
21
21
  def run
22
+ options.set_default(:deploy, true)
23
+ puts "Logging in to Heroku Container Registry"
24
+ command = %{heroku container:login}
25
+ system!(command)
22
26
  execute_result = Brut::CLI::ExecuteResult.new do
23
- delegate_to_command(Brut::CLI::Apps::Deploy::Build.new)
27
+ delegate_to_command(
28
+ Brut::CLI::Apps::Deploy::Build.new(
29
+ push: options.deploy? ? "registry.heroku.com/#{Brut.container.app_id}/%{name}": false
30
+ )
31
+ )
24
32
  end
25
33
  if execute_result.failed?
26
34
  puts theme.error.render("Build failed.")
@@ -28,47 +36,17 @@ class Brut::CLI::Apps::Deploy < Brut::CLI::Commands::BaseCommand
28
36
  puts theme.error.render("Error message from build: #{error_message}")
29
37
  end
30
38
  end
31
- options.set_default(:deploy, true)
32
- version = ""
33
- git_guess = %{git rev-parse HEAD}
34
- system!(git_guess) do |output|
35
- version << output
36
- end
37
- version.strip!.chomp!
38
- if version == ""
39
- error "Attempt to use git via command '#{git_guess}' to figure out the version failed"
40
- return 1
41
- end
42
- short_version = version[0..7]
39
+ names = []
43
40
  app_docker_files = AppDockerImages.new(
44
41
  project_root: Brut.container.project_root,
45
42
  organization: Brut.container.app_organization,
46
43
  app_id: Brut.container.app_id,
47
- short_version:
44
+ short_version: "NA"
48
45
  )
49
- names = []
50
- puts "Logging in to Heroku Container Registry"
51
- command = %{heroku container:login}
52
- system!(command)
53
- app_docker_files.each do |name:, image_name:|
54
- heroku_image_name = "registry.heroku.com/#{Brut.container.app_id}/#{name}"
55
- puts "Tagging '#{image_name}' with '#{heroku_image_name}' for Heroku"
56
- command = %{docker tag #{image_name} #{heroku_image_name}}
57
- system!(command)
58
- begin
59
- puts "Pushing '#{heroku_image_name}'"
60
- command = %{docker push #{heroku_image_name}}
61
- system!(command)
62
- rescue Brut::CLI::SystemExecError => ex
63
- error "Failed to push image '#{heroku_image_name}' to Heroku"
64
- if options.log_level != "debug"
65
- error "Could be you must re-authenticate to Heroku."
66
- error "Try re-running with --log-level=debug to see more details"
67
- end
68
- return 1
69
- end
46
+ app_docker_files.each do |name:, cmd:, dockerfile:|
70
47
  names << name
71
48
  end
49
+
72
50
  deploy_command = "heroku container:release #{names.join(' ')} -a #{Brut.container.app_id}"
73
51
  if options.deploy?
74
52
  puts "Deploying images to Heroku"
@@ -142,7 +120,13 @@ class Brut::CLI::Apps::Deploy < Brut::CLI::Commands::BaseCommand
142
120
  def description = Docker.new.description
143
121
  def opts = Docker.new.opts
144
122
  def default_rack_env = Docker.new.default_rack_env
145
- def run = delegate_to_command(Docker.new)
123
+
124
+ def initialize(push: false)
125
+ @push = push
126
+ end
127
+ def run
128
+ delegate_to_command(Docker.new(push: @push))
129
+ end
146
130
 
147
131
  def commands = []
148
132
 
@@ -155,6 +139,10 @@ class Brut::CLI::Apps::Deploy < Brut::CLI::Commands::BaseCommand
155
139
  ]
156
140
  def default_rack_env = "development"
157
141
 
142
+ def initialize(push: false)
143
+ @push = push
144
+ end
145
+
158
146
  def run
159
147
  if !options.skip_checks?
160
148
  execute_result = Brut::CLI::ExecuteResult.new do
@@ -177,8 +165,6 @@ class Brut::CLI::Apps::Deploy < Brut::CLI::Commands::BaseCommand
177
165
  error "Attempt to use git via command '#{git_guess}' to figure out the version failed"
178
166
  return 1
179
167
  end
180
- short_version = version[0..7]
181
-
182
168
  short_version = version[0..7]
183
169
  app_docker_files = AppDockerImages.new(
184
170
  project_root: Brut.container.project_root,
@@ -219,12 +205,16 @@ class Brut::CLI::Apps::Deploy < Brut::CLI::Commands::BaseCommand
219
205
  puts
220
206
  rows = []
221
207
  items = []
208
+ push_or_load = @push ? "--push" : "--load"
222
209
  app_docker_files.each do |name:, image_name:, dockerfile:|
210
+ if @push && @push.kind_of?(String)
211
+ image_name = @push % { name: name }
212
+ end
223
213
  rows << [ name, theme.code.render(image_name) ]
224
- command = %{docker build --build-arg app_git_sha1=#{version} --file #{Brut.container.project_root}/#{dockerfile} --platform #{options.platform} --tag #{image_name} . 2>&1}
214
+ command = %{docker buildx build --provenance=false --build-arg app_git_sha1=#{version} --file #{Brut.container.project_root}/#{dockerfile} --platform #{options.platform} #{push_or_load} --tag #{image_name} . 2>&1}
225
215
  items << theme.code.render(theme.wrap(command, first_indent: false, indent: 7, newline: " \\\n"))
226
216
  if !options.dry_run?
227
- puts theme.subheader.render("Building '#{name}' image")
217
+ puts theme.subheader.render("Building #{@push ? 'and pushing' : '' } '#{name}' image")
228
218
  system!(command)
229
219
  end
230
220
  end
@@ -53,12 +53,12 @@ class Brut::CLI::Apps::New::App < Brut::CLI::Commands::BaseCommand
53
53
  [
54
54
  "--app-id=ID",
55
55
  Brut::CLI::Apps::New::AppId,
56
- "App identifier, which must be able to be used as a hostname or other Internet identifier. Derived from your app name, if omitted"
56
+ "App identifier, which must be able to be used as a hostname or other Internet identifier. Derived from your app name, if omitted",
57
57
  ],
58
58
  [
59
59
  "--organization=ORG",
60
60
  Brut::CLI::Apps::New::Organization,
61
- "Organization name, e.g. what you'd use for GitHub. Defaults to the app-id value"
61
+ "Organization name, e.g. what you'd use for GitHub. Defaults to the app-id value",
62
62
  ],
63
63
  [
64
64
  "--[no-]interactive",
@@ -67,7 +67,7 @@ class Brut::CLI::Apps::New::App < Brut::CLI::Commands::BaseCommand
67
67
  [
68
68
  "--prefix=PREFIX",
69
69
  Brut::CLI::Apps::New::Prefix,
70
- "Two-character prefix for external IDs and autonomous custom elements. Derived from your app-id, if omitted."
70
+ "Two-character prefix for external IDs and autonomous custom elements. Derived from your app-id, if omitted.",
71
71
  ],
72
72
  [
73
73
  "--segments=SEGMENTS",
@@ -76,11 +76,11 @@ class Brut::CLI::Apps::New::App < Brut::CLI::Commands::BaseCommand
76
76
  ],
77
77
  [
78
78
  "--dry-run",
79
- "Only show what would happen, don't actually do anything"
79
+ "Only show what would happen, don't actually do anything",
80
80
  ],
81
81
  [
82
82
  "--[no-]demo",
83
- "Include, or not, additional files that demonstrate Brut's features (default is true for now)"
83
+ "Include, or not, additional files that demonstrate Brut's features (default is true for now)",
84
84
  ],
85
85
  ]
86
86
 
@@ -272,7 +272,7 @@ class Brut::CLI::Apps::New::App < Brut::CLI::Commands::BaseCommand
272
272
  ],
273
273
  [
274
274
  "--dry-run",
275
- "Only show what would happen, don't actually do anything"
275
+ "Only show what would happen, don't actually do anything",
276
276
  ],
277
277
  ]
278
278
 
@@ -4,6 +4,10 @@ require "brut/cli"
4
4
  class Brut::CLI::Apps::Scaffold < Brut::CLI::Commands::BaseCommand
5
5
  def description = "Create scaffolds of various files to help develop more quckly"
6
6
 
7
+ def commands
8
+ super - [ Brut::CLI::Apps::Scaffold::BaseCommand ]
9
+ end
10
+
7
11
  class BaseCommand < Brut::CLI::Commands::BaseCommand
8
12
  def bootstrap? = false
9
13
  def default_rack_env = "development"
@@ -111,7 +115,7 @@ end}
111
115
  def args_description = "test_name"
112
116
  def name = "e2e_test"
113
117
 
114
- def opts = [
118
+ def opts = super + [
115
119
  ["--path PATH","Path within the e2e tests to create the file"],
116
120
  ]
117
121
 
@@ -193,7 +197,7 @@ end}
193
197
  def args_description = "ComponentName"
194
198
  def detailed_description = "New components go in the `components/` folder of your app, however using --page will create a 'page private' component. To do that, the component name must be an inner class of an existing page, for example HomePage::Welcome. This component goes in a sub-folder inside the `pages/` area of your app"
195
199
 
196
- def opts = [
200
+ def opts = super + [
197
201
  [ "--page","If set, this component is for a specific page and won't go with the other components"],
198
202
  ]
199
203
 
@@ -384,7 +388,7 @@ end}
384
388
  error " The page may not render properly the first time you load it"
385
389
  end
386
390
 
387
- routes_editor = RoutesEditor.new(app_path:,out:)
391
+ routes_editor = RoutesEditor.new(app_path:,stdout: execution_context.stdout)
388
392
  routes_editor.add_route!(route_code:)
389
393
 
390
394
  if !routes_editor.found_routes?
@@ -419,7 +423,7 @@ end}
419
423
  end
420
424
  def description = "Create a handler for an action"
421
425
  def args_description = "action_route"
422
- def opts = [
426
+ def opts = super + [
423
427
  [ "--http-method=METHOD", "If present, the action will be a path available on the given route and this HTTP method. If omitted, this will create an action available via POST" ],
424
428
  ]
425
429
 
@@ -528,7 +532,7 @@ end}
528
532
  execution_context.stdout.printf printf_string,handler_class_name, handler_source_path.relative_path_from(Brut.container.project_root)
529
533
  execution_context.stdout.printf printf_string,"Spec", handler_spec_path.relative_path_from(Brut.container.project_root)
530
534
 
531
- routes_editor = RoutesEditor.new(app_path:,out:)
535
+ routes_editor = RoutesEditor.new(app_path:,stdout: execution_context.stdout)
532
536
  routes_editor.add_route!(route_code:)
533
537
 
534
538
  if form
@@ -703,9 +707,9 @@ end
703
707
  end
704
708
 
705
709
  class RoutesEditor
706
- def initialize(app_path:,out:)
710
+ def initialize(app_path:,stdout:)
707
711
  @app_path = app_path
708
- @out = out
712
+ @stdout = stdout
709
713
  @found_routes = false
710
714
  @routes_existed = false
711
715
  end
@@ -726,7 +730,7 @@ end
726
730
  end
727
731
  if in_routes && line =~ /^ end\s*$/
728
732
  if !@routes_existed
729
- @out.puts "Inserted route into #{@app_path.relative_path_from(Brut.container.project_root)}"
733
+ @stdout.puts "Inserted route into #{@app_path.relative_path_from(Brut.container.project_root)}"
730
734
  file.puts " #{route_code}"
731
735
  end
732
736
  @found_routes = true
@@ -1,5 +1,6 @@
1
1
  require "logger"
2
2
  require "fileutils"
3
+ require "pathname"
3
4
  require "delegate"
4
5
 
5
6
  class Brut::CLI::Logger < SimpleDelegator
@@ -58,9 +59,9 @@ class Brut::CLI::Logger < SimpleDelegator
58
59
  end
59
60
 
60
61
  def log_file=(log_file)
61
- @log_file = log_file
62
62
  if log_file
63
- log_dir = log_file.dirname
63
+ @log_file = Pathname(log_file)
64
+ log_dir = @log_file.dirname
64
65
  if !log_dir.exist?
65
66
  FileUtils.mkdir_p(log_dir)
66
67
  end
@@ -69,6 +70,8 @@ class Brut::CLI::Logger < SimpleDelegator
69
70
  if @logger.level == ::Logger::DEBUG
70
71
  @stdout.puts "Logging to file #{@log_file}"
71
72
  end
73
+ else
74
+ @log_file = nil
72
75
  end
73
76
  end
74
77
 
@@ -112,12 +112,16 @@ class Brut::CLI::ParsedCommandLine
112
112
  if !@options[:'log-file']
113
113
  log_file_path = if env["XDG_STATE_HOME"]
114
114
  Pathname(env["XDG_STATE_HOME"]) / "brut"
115
- elsif env["HOME"]
115
+ elsif env["HOME"] && File.writable?(env["HOME"])
116
116
  Pathname("#{env['HOME']}/.local/state/") / "brut"
117
117
  else
118
- Pathname("/tmp/") / "brut"
118
+ nil
119
119
  end
120
- @options[:'log-file'] = log_file_path / (app_command.name + ".log")
120
+ if log_file_path
121
+ @options[:'log-file'] = log_file_path / (app_command.name + ".log")
122
+ end
123
+ else
124
+ @options[:'log-file'] = Pathname(@options[:'log-file'])
121
125
  end
122
126
  if @options[:'log-stdout'].nil?
123
127
  @options[:'log-stdout'] = @options.verbose? || @options.debug?
@@ -23,7 +23,8 @@ module Brut::FrontEnd
23
23
  include Brut::FrontEnd::HandlingResults
24
24
  include Brut::Framework::Errors
25
25
 
26
- # You must implement this to accept whatever parameters you need. See {Brut::FrontEnd::RequestContext} for how that works.
26
+ # You must implement this to perform whatever action your handler must perform. Any information from the request would've been given to your initializer. See {Brut::FrontEnd::RequestContext} for how that works.
27
+ #
27
28
  # The type of the return value determines what will happen:
28
29
  #
29
30
  # * Instance of `URI` - browser will redirect to this URI. Typically, you would do this by calling {Brut::FrontEnd::HandlingResults#redirect_to}.
@@ -39,7 +40,7 @@ module Brut::FrontEnd
39
40
  abstract_method!
40
41
  end
41
42
 
42
- # Override this to performa any checks before {#handle} is called. This should
43
+ # Override this to perform any checks before {#handle} is called. This should
43
44
  # return `nil` if {#handle} should proceed to be called. Generally, you don't need to override
44
45
  # this as {#handle} can include the logic. Where this is useful is to share cross-cutting logic
45
46
  # across other handlers.
@@ -48,8 +49,7 @@ module Brut::FrontEnd
48
49
  # {#handle} for what each return value means.
49
50
  def before_handle = nil
50
51
 
51
- # Called by Brut to handle the request. Do not override this. If your handler responds to `before_handle` that is called with the
52
- # same args as you have defined for {#handle}. If `before_handle` returns anything other than `nil`, that value is returned and
52
+ # Called by Brut to handle the request. Do not override this. If `before_handle` returns anything other than `nil`, that value is returned and
53
53
  # should be one of the values documented in {#handle}. If `before_handle` returns `nil`, {#handle} is called and whatever it
54
54
  # returns is returned here.
55
55
  def handle!(**args)
@@ -46,9 +46,9 @@ module Brut
46
46
  "member?" => "include?",
47
47
  },
48
48
  },
49
- "Style/EndlessMethod" => {
50
- "EnforcedStyle" => "allow_single_line",
51
- },
49
+ #"Style/EndlessMethod" => {
50
+ # "EnforcedStyle" => "allow_single_line",
51
+ #},
52
52
  "Style/For" => {
53
53
  "EnforcedStyle" => "each",
54
54
  },
@@ -47,6 +47,9 @@ class Brut::SpecSupport::Matchers::HaveConstraintViolation
47
47
  if !form.kind_of?(Brut::FrontEnd::Form)
48
48
  raise "#{self.class} only works with forms, not #{form.class}"
49
49
  end
50
+ if field.to_s == ""
51
+ raise "field is required"
52
+ end
50
53
  @form = form
51
54
  @field = field.to_s
52
55
  @key = key.to_s
@@ -29,7 +29,7 @@ class Brut::TUI::Script::ExecStep < Brut::TUI::Script::Step
29
29
  command: @command,
30
30
  strip_ansi: false,
31
31
  stdout: @stdout,
32
- stderr: @stderr
32
+ stderr: @stderr,
33
33
  })
34
34
  end
35
35
  def run!
data/lib/brut/version.rb CHANGED
@@ -1,4 +1,4 @@
1
1
  module Brut
2
2
  # @!visibility private
3
- VERSION = "0.19.2"
3
+ VERSION = "0.20.0"
4
4
  end
@@ -0,0 +1,8 @@
1
+ Data Models go here. Create them with `brut scaffold db_model`
2
+
3
+ ```ruby
4
+ # db/widget.rb
5
+ class DB::Widget < AppDataModel
6
+ end
7
+ ```
8
+
@@ -1,8 +1,5 @@
1
1
  # Insert developer-specifilc Bash customizations here.
2
- # This file is not checked into version control, so you
3
- # are safe to export EDITOR=vim and avoid any guff from
4
- # co-workers.
5
-
2
+ # This file is not checked into version control
6
3
  # Sets up a multi-line prompt since the working directory
7
4
  # may be very deep. Customize or change at your leisure.
8
5
  PS1='\[\e[35m\]docker-container\[\e[0m\] - \[\e[37m\]\w\n\[\e[0m\]> '
@@ -1,4 +1,7 @@
1
+ require "dotenv"
1
2
  ENV["RACK_ENV"] = "test"
3
+ Dotenv.load(".env.test.local", ".env.test")
4
+
2
5
  require_relative "../app/bootstrap"
3
6
  Bootstrap.new.bootstrap!
4
7
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: brut
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.19.2
4
+ version: 0.20.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Bryant Copeland
@@ -719,7 +719,6 @@ files:
719
719
  - lib/sequel/plugins/find_bang.rb
720
720
  - templates/Base/.dockerignore
721
721
  - templates/Base/.env.development.erb
722
- - templates/Base/.env.development.local
723
722
  - templates/Base/.env.test.erb
724
723
  - templates/Base/.gitignore
725
724
  - templates/Base/.projections.json
@@ -736,6 +735,7 @@ files:
736
735
  - templates/Base/app/src/app.rb.erb
737
736
  - templates/Base/app/src/back_end/data_models/app_data_model.rb
738
737
  - templates/Base/app/src/back_end/data_models/db.rb
738
+ - templates/Base/app/src/back_end/data_models/db/README.md
739
739
  - templates/Base/app/src/back_end/data_models/migrations/20240101130000_citext.rb
740
740
  - templates/Base/app/src/back_end/data_models/seed/seed_data.rb
741
741
  - templates/Base/app/src/front_end/components/app_component.rb
@@ -851,7 +851,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
851
851
  - !ruby/object:Gem::Version
852
852
  version: '0'
853
853
  requirements: []
854
- rubygems_version: 3.7.2
854
+ rubygems_version: 4.0.8
855
855
  specification_version: 4
856
856
  summary: Web Framework Built around Ruby, Web Standards, Simplicity, and Object-Orientation
857
857
  test_files: []
@@ -1,2 +0,0 @@
1
- # Place developer-specific overrides of .env.development in here,
2
- # e.g. API keys needed for local dev. DO NOT CHECK INTO VERSION CONTROL