brut 0.20.0 → 0.20.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: afeada7e0616e77f5a11b4e975a94ee7affeaa66664c1f619c4e52de6db2426f
4
- data.tar.gz: 520765f71bb41275569fed4d481e75ff64b3df28f0a3b3bccf12bf53e3aa223b
3
+ metadata.gz: de61901270558680112b8b90f787067add7f777204219016020adcc90c15564d
4
+ data.tar.gz: 4437fee31bc132bd93fe419ac72df5bc1b1e7bb67f506a4e36ab126dc7395b85
5
5
  SHA512:
6
- metadata.gz: d68ff2c363e86ef216bb8c81b88360e51e643496d1b6e602db0bd969a298e8b880a68f26d5e177ba172fc707cbc880fdd998b2dd1caf6ed0ac21f83fb61ef547
7
- data.tar.gz: f94608a8a377bb58ebcf61e4cc172c5215558c9c374e580ab95d7997c2bdae01e5460299a39a1cbcd10e98b72ee3f0a64f6b2a71bbd75e7ca30dd7fa3508d85f
6
+ metadata.gz: fcc0ef80b40ea5703298cdbaa71f86f3ec8d22b96f8bc4e9cea336f41b09bbca109b211eea365b202759f8bca4c07ace5c35d2a6f638a7d2231f5d0534090556
7
+ data.tar.gz: c4bb547d99089ca6d567e744c39539fda20ff85b21c45dd2419ab7d0bcfc0e265a9004006973e7bb4f96be35f41d21354efd71502f77e2276140dbd8badf29a3
@@ -91,9 +91,9 @@ This is to ensure that any images your code references will end up in the public
91
91
  def description = "Builds a single CSS file suitable for sending to the browser"
92
92
 
93
93
  def detailed_description = %{
94
- This produces a hashed file in every environment, in order to keep environments consistent and reduce differences. If your CSS file references images, fonts, or other assets via url() or other CSS functions, those files will be hashed and copied into the output directory where CSS is served.
94
+ This produces a hashed file in every environment, in order to keep environments consistent and reduce differences. If your CSS file references images, fonts, or other assets via `url()` or other CSS functions, those files will be hashed and copied into the output directory where CSS is served.
95
95
 
96
- To ensure this happens correctly, your url() or other function must reference the file as a relative file from where your actual source CSS file is located. For example, a font named some-font.ttf would be in app/src/front_end/fonts and to reference this from app/src/front_end/css/index.css you'd use the url "../fonts/some-font.ttf"
96
+ To ensure this happens correctly, your `url()` or other function must reference the file as a relative file from where your actual source CSS file is located. For example, a font named `some-font.ttf` would be in `app/src/front_end/fonts`. To reference this from `app/src/front_end/css/index.css` you'd use `url("../fonts/some-font.ttf")`
97
97
  }
98
98
 
99
99
  def run
@@ -319,7 +319,7 @@ class Brut::CLI::Apps::New::App < Brut::CLI::Commands::BaseCommand
319
319
 
320
320
  puts "Adding #{segment_name} to this app"
321
321
  segment.add!
322
- segment.output_post_add_messaging(stdout:)
322
+ segment.output_post_add_messaging(stdout: execution_context.stdout)
323
323
  0
324
324
  end
325
325
  end
@@ -19,6 +19,11 @@ class Brut::CLI::Apps::New::Ops::InsertCodeInMethod < Brut::CLI::Apps::New::Ops:
19
19
  if !@file.exist? && @ignore_if_file_not_found
20
20
  return
21
21
  end
22
+ if dry_run?
23
+ op = @class_method ? "::" : "#"
24
+ puts "Would add this code to #{@class_name}#{op}#{@method_name} in #{@file}:\n\n#{@code}\n\n"
25
+ return
26
+ end
22
27
  method_node = find_method(class_name: @class_name, method_name: @method_name, class_method: @class_method)
23
28
 
24
29
  insertion_point = if @where == :start
@@ -16,9 +16,6 @@ class Brut::CLI::Apps::New::Segments::Heroku < Brut::CLI::Apps::New::Base
16
16
  end
17
17
  end
18
18
 
19
- def output_post_add_messaging(stdout:)
20
- end
21
-
22
19
  def <=>(other)
23
20
  if self.class == other.class
24
21
  0
@@ -75,6 +75,10 @@ class Brut::CLI::Apps::New::Segments::Sidekiq < Brut::CLI::Apps::New::Base
75
75
  file: @project_root / "Gemfile",
76
76
  content: "# Sidekiq is used for background jobs\ngem \"sidekiq\"\n"
77
77
  ),
78
+ Brut::CLI::Apps::New::Ops::AppendToFile.new(
79
+ file: @project_root / "Gemfile",
80
+ content: "# Sets up OTel middelware for Sidekiq \ngem \"opentelemetry-instrumentation-sidekiq\"\n"
81
+ ),
78
82
  Brut::CLI::Apps::New::Ops::AppendToFile.new(
79
83
  file: @project_root / ".env.development",
80
84
  content: %{
@@ -110,7 +114,7 @@ SIDEKIQ_BASIC_AUTH_PASSWORD=password
110
114
  Brut::CLI::Apps::New::Ops::InsertIntoFile.new(
111
115
  file: @project_root / "specs" / "spec_helper.rb",
112
116
  before_line: "require \"brut/spec_support\"",
113
- content: "require \"sidekiq/testing\""
117
+ content: "Sidekiq.testing!(:fake)"
114
118
  ),
115
119
  Brut::CLI::Apps::New::Ops::InsertIntoFile.new(
116
120
  file: @project_root / "config.ru",
@@ -142,10 +146,9 @@ SIDEKIQ_BASIC_AUTH_PASSWORD=password
142
146
  code: "@sidekiq_segment.boot!"
143
147
  ),
144
148
  Brut::CLI::Apps::New::Ops::InsertCodeInMethod.new(
145
- file: project_root / "deploy" / "heroku_config.rb",
149
+ file: project_root / "deploy" / "docker_config.rb",
146
150
  class_name: "HerokuConfig",
147
151
  method_name: "additional_images",
148
- class_method: true,
149
152
  ignore_if_file_not_found: true,
150
153
  code: %{
151
154
  {
@@ -637,7 +637,8 @@ describe("#{description}", () => {
637
637
 
638
638
  def run
639
639
  if argv.length == 0
640
- return abort_execution("You must provide a model name")
640
+ puts "You must provide one or more model names"
641
+ return 1
641
642
  end
642
643
  db_module = ModuleName.from_string("DB")
643
644
  actions = argv.map { |arg|
@@ -90,6 +90,7 @@ Runs all non end-to-end tests for the app, or runs a subset of non-end-to-end te
90
90
  [ "E2E_RECORD_VIDEOS","If set to 'true', videos of each test run are saved in `./tmp/e2e-videos`" ],
91
91
  [ "E2E_SLOW_MO","If set to, will attempt to slow operations down by this many milliseconds" ],
92
92
  [ "E2E_TIMEOUT_MS","ms to wait for any browser activity before failing the test. And here you didn't think you'd get away without using sleep in browse-based tests?" ],
93
+ [ "E2E_STARTUP_TIMEOUT_SEC","seconds to wait for the test server to start before assuming something went wrong" ],
93
94
  ]
94
95
 
95
96
  def rspec_cli_args = "--tag e2e"
@@ -102,11 +103,17 @@ Runs all end-to-end tests for the app, or runs a subset of end-to-end tests usin
102
103
  private
103
104
 
104
105
  def run_tests
105
- require "brut/spec_support/e2e_test_server"
106
- Brut::SpecSupport::E2ETestServer.instance.start
107
- super
108
- ensure
109
- Brut::SpecSupport::E2ETestServer.instance.stop
106
+ test_server = Brut::SpecSupport::E2ETestServer.new(
107
+ bin_dir: Brut.container.project_root / "bin",
108
+ start_timeout_seconds: ENV["E2E_STARTUP_TIMEOUT_SEC"]
109
+ )
110
+ begin
111
+ require "brut/spec_support/e2e_test_server"
112
+ test_server.start
113
+ super
114
+ ensure
115
+ test_server.stop
116
+ end
110
117
  end
111
118
  end
112
119
  class Js < Brut::CLI::Commands::BaseCommand
@@ -0,0 +1,109 @@
1
+ class Brut::CLI::Commands::HelpInMarkdown < Brut::CLI::Commands::BaseCommand
2
+ def description = "Get help for the app or a command, in Markdown"
3
+ attr_accessor :option_parser
4
+
5
+ def initialize(command,option_parser)
6
+ @command = command
7
+ @option_parser = option_parser
8
+ end
9
+
10
+ def commands = []
11
+
12
+ def run
13
+ if env["BRUT_HELP_IN_MARKDOWN_COMMANDS_ONLY"]
14
+ @command.commands.sort_by(&:name).each do |command|
15
+ puts command.name
16
+ end
17
+ return 0
18
+ end
19
+ cli = [@command.name ]
20
+ cmd = @command
21
+ while cmd.parent_command
22
+ cmd = cmd.parent_command
23
+ cli.unshift cmd.name
24
+ end
25
+ invocation = cli.join(" ")
26
+ puts "# `#{invocation}`"
27
+ puts
28
+ puts @command.description
29
+ puts
30
+
31
+ usage = invocation
32
+
33
+ options = @option_parser.top.list
34
+ if options.size > 0
35
+ usage << theme.weak.render(" [options]")
36
+ end
37
+ if @command.commands.any?
38
+ usage << theme.code.render(" command")
39
+ end
40
+ if @command.args_description
41
+ usage << " #{@command.args_description}"
42
+ end
43
+ puts
44
+ puts "## USAGE"
45
+ puts
46
+ puts " " + usage
47
+ puts
48
+ if @command.detailed_description
49
+ puts
50
+ puts "## DESCRIPTION"
51
+ puts
52
+ puts @command.detailed_description.gsub(/ +/," ").strip
53
+ puts
54
+ end
55
+ if options.size > 0
56
+ puts
57
+ puts "## OPTIONS"
58
+ puts
59
+
60
+ options.each do |option|
61
+ switches = option.long.map { |switch|
62
+ if option.arg
63
+ if option.arg[0] == "="
64
+ "#{switch.strip}#{theme.weak.render(option.arg.strip)}"
65
+ else
66
+ "#{switch.strip}=#{theme.weak.render(option.arg.strip)}"
67
+ end
68
+ else
69
+ switch
70
+ end
71
+ } + option.short.map { |switch|
72
+ if option.arg
73
+ "#{switch} #{theme.weak.render(option.arg)}"
74
+ else
75
+ switch
76
+ end
77
+ }
78
+ puts "* `#{switches.join(", ")}` - #{option.desc.join(" ")}"
79
+ end
80
+ end
81
+ if @command.env_vars.any?
82
+ puts
83
+ puts "## ENVIRONMENT VARIABLES"
84
+ puts
85
+ @command.env_vars.sort_by(&:first).each do |env_var|
86
+ puts "* `#{env_var[0]}` - #{env_var[1]}"
87
+ end
88
+ end
89
+ if @command.commands.any?
90
+ commands_subpath = env["BRUT_HELP_IN_MARKDOWN_COMMAND_PATH"] || "commands"
91
+ puts
92
+ puts "## COMMANDS"
93
+ puts
94
+ @command.commands.sort_by(&:name).each do |command|
95
+ puts "### [`#{command.name}`](./#{commands_subpath}/#{command.name})"
96
+ puts
97
+ puts "#{command.description}"
98
+ if command.detailed_description
99
+ puts
100
+ puts command.detailed_description.gsub(/ +/," ").strip
101
+ end
102
+ end
103
+ end
104
+ 0
105
+ end
106
+
107
+ def bootstrap? = false
108
+ def default_rack_env = nil
109
+ end
@@ -2,6 +2,7 @@ module Brut::CLI::Commands
2
2
  autoload(:BaseCommand, "brut/cli/commands/base_command")
3
3
  autoload(:CompoundCommand, "brut/cli/commands/compound_command")
4
4
  autoload(:Help, "brut/cli/commands/help")
5
+ autoload(:HelpInMarkdown, "brut/cli/commands/help_in_markdown")
5
6
  autoload(:OutputError, "brut/cli/commands/output_error")
6
7
  autoload(:RaiseError, "brut/cli/commands/raise_error")
7
8
  autoload(:ExecutionContext, "brut/cli/commands/execution_context")
@@ -30,6 +30,11 @@ class Brut::CLI::ParsedCommandLine
30
30
  # This should always succeed, however depending on the contents of the parameters, the value
31
31
  # for `#command` may be a command that outputs an error.
32
32
  def initialize(app_command:, argv:, env:)
33
+ help_command_class = if env["BRUT_HELP_IN_MARKDOWN"] == "true"
34
+ Brut::CLI::Commands::HelpInMarkdown
35
+ else
36
+ Brut::CLI::Commands::Help
37
+ end
33
38
  brut_provided_help_requested = false
34
39
  app_option_parser = new_option_parser(app_command.name) do |opts|
35
40
  opts.banner = app_command.description
@@ -51,7 +56,7 @@ class Brut::CLI::ParsedCommandLine
51
56
  end
52
57
 
53
58
  help_command = if brut_provided_help_requested
54
- Brut::CLI::Commands::Help.new(app_command,app_option_parser)
59
+ help_command_class.new(app_command,app_option_parser)
55
60
  end
56
61
 
57
62
  command = app_command
@@ -83,7 +88,7 @@ class Brut::CLI::ParsedCommandLine
83
88
  end
84
89
  remaining_argv = command_option_parser.parse!(remaining_argv, into: options)
85
90
  if brut_provided_help_requested
86
- help_command = Brut::CLI::Commands::Help.new(command,command_option_parser)
91
+ help_command = help_command_class.new(command,command_option_parser)
87
92
  elsif help_command
88
93
  help_command.option_parser = command_option_parser
89
94
  end
@@ -147,8 +152,7 @@ private
147
152
  "Project environment, e.g. test, development, production. Default depends on the command")
148
153
  opts.on("--log-level=LOG_LEVEL", [ "debug", "info", "warn", "error", "fatal" ],
149
154
  "Log level, which should be debug, info, warn, error, or fatal. Defaults to error")
150
- opts.on("--verbose", "Set log level to debug, and show log messages on stdout")
151
- opts.on("--debug", "Set log level to debug, and show log messages on stdout")
155
+ opts.on("--debug", "--verbose", "Set log level to debug, and show log messages on stdout")
152
156
  opts.on("--quiet", "Set log level to error")
153
157
  opts.on("--log-file=FILE",
154
158
  "Path to a file where log messages are written. Defaults to $XDG_CACHE_HOME/brut/logs/#{app_name}.log")
@@ -439,6 +439,7 @@ private
439
439
  if defined?(OpenTelemetry::Instrumentation::Sidekiq)
440
440
  c.use 'OpenTelemetry::Instrumentation::Sidekiq', {
441
441
  span_naming: :job_class,
442
+ propagation_style: :child, # XXX: Configurable?
442
443
  }
443
444
  else
444
445
  SemanticLogger[self.class].info "OpenTelemetry::Instrumentation::Sidekiq is not loaded, so Sidekiq traces will not be captured"
@@ -4,7 +4,6 @@
4
4
  class Brut::FrontEnd::AssetMetadata
5
5
 
6
6
  # @param [String] asset_metadata_file to the asset metadata file
7
- # @param [IO] out IO on which to write messaging
8
7
  def initialize(asset_metadata_file:,logger: :use_default)
9
8
  @asset_metadata_file = asset_metadata_file
10
9
  @logger = if logger == :use_default
@@ -1,4 +1,16 @@
1
- # Extended by {Brut::FrontEnd::Form} to allow declaring inputs. Do not use this module directly. Instead, call {#input} or {#select}
1
+ # Extended by {Brut::FrontEnd::Form} to allow declaring inputs. This module creates methods per input on the form passed to your handlers. For example, if you have an `input :book_title`, then `form.book_title` will be available to access the value of the "book_title" input.
2
+ #
3
+ # There are two methods that could be created, per input. Examples below use
4
+ # `book_title` as the attribute name
5
+ #
6
+ # * `#book_title` - returns {Brut::FrontEnd::Forms::Input#value}, which is always a string.
7
+ # * `#book_title_coerced` - returns {Brut::FrontEnd::Forms::Input#typed_value}, which is always the correct type for the input **or `nil` if type coercion failed**. Only call this once you have checked for constraint violations
8
+ #
9
+ # For indexed parameters, the above methods require the index to be passed,
10
+ # e.g. `form.book_title_coerced(4)`. For non-indexed parameters, the index may
11
+ # not be passed.
12
+ #
13
+ # Do not use this module directly. Instead, call {#input} or {#select}
2
14
  # from within your form's class definition.
3
15
  module Brut::FrontEnd::Forms::InputDeclarations
4
16
  # Declares an input for this form, to be modeled via an HTML `<INPUT>` tag.
@@ -59,11 +71,22 @@ module Brut::FrontEnd::Forms::InputDeclarations
59
71
  end
60
72
  self.input(input_definition.name, index:).value
61
73
  end
74
+ define_method "#{input_definition.name}_coerced" do |index=nil|
75
+ if index.nil?
76
+ raise ArgumentError,"#{input_definition.name} is an array - you must provide an index to access one of its values"
77
+ end
78
+ self.input(input_definition.name, index:).typed_value
79
+ end
62
80
  define_method "#{input_definition.name}_each" do |&block|
63
81
  self.inputs(input_definition.name).each_with_index do |input,i|
64
82
  block.(input.value,i)
65
83
  end
66
84
  end
85
+ define_method "#{input_definition.name}_each_coerced" do |&block|
86
+ self.inputs(input_definition.name).each_with_index do |input,i|
87
+ block.(input.typed_value,i)
88
+ end
89
+ end
67
90
  else
68
91
  define_method input_definition.name do |index_that_should_be_omitted=nil|
69
92
  if !index_that_should_be_omitted.nil?
@@ -71,6 +94,12 @@ module Brut::FrontEnd::Forms::InputDeclarations
71
94
  end
72
95
  self.input(input_definition.name, index: 0).value
73
96
  end
97
+ define_method "#{input_definition.name}_coerced" do |index_that_should_be_omitted=nil|
98
+ if !index_that_should_be_omitted.nil?
99
+ raise ArgumentError,"#{input_definition.name} is not an array - do not provide an index when accessing its value"
100
+ end
101
+ self.input(input_definition.name, index: 0).typed_value
102
+ end
74
103
  end
75
104
  end
76
105
 
@@ -15,9 +15,10 @@ class Brut::SpecSupport::E2ETestServer
15
15
  # from the given bin dir
16
16
  #
17
17
  # @param [Pathname] bin_dir path to where the app's Brut-provide CLI apps are installed
18
- def initialize(bin_dir:)
19
- @bin_dir = bin_dir
20
- @pid = nil
18
+ def initialize(bin_dir:, start_timeout_seconds: nil)
19
+ @bin_dir = bin_dir
20
+ @pid = nil
21
+ @start_timeout_seconds = start_timeout_seconds || 5
21
22
  end
22
23
 
23
24
  # Starts the server. Returns when the server has started
@@ -66,7 +67,7 @@ private
66
67
 
67
68
  def is_port_open?(ip, port)
68
69
  begin
69
- Timeout::timeout(5) do
70
+ Timeout::timeout(@start_timeout_seconds) do
70
71
  loop do
71
72
  begin
72
73
  logger.debug "Attemping to conenct to '#{ip}' on port '#{port}'"
@@ -112,7 +112,11 @@ private
112
112
  errors.each do
113
113
  $stderr.puts("FATAL Exception: #{it.exception.class}: #{it.exception.message}\n #{it.exception.backtrace.join("\n ")}")
114
114
  end
115
- exit 1
115
+ if errors.any?
116
+ exit 1
117
+ else
118
+ exit 0
119
+ end
116
120
  else
117
121
  errors.each { @queue.unshift(Brut::TUI::Events::Exception.new(it)) }
118
122
  end
data/lib/brut/version.rb CHANGED
@@ -1,4 +1,4 @@
1
1
  module Brut
2
2
  # @!visibility private
3
- VERSION = "0.20.0"
3
+ VERSION = "0.20.1"
4
4
  end
@@ -16,7 +16,7 @@ if ARGV[0] == "help"
16
16
  exit
17
17
  end
18
18
 
19
- ENV["RACK_ENV"] = "development"
19
+ ENV["RACK_ENV"] ||= "development"
20
20
  if ENV["LOG_LEVEL"].to_s == ""
21
21
  ENV["LOG_LEVEL"] = "warn"
22
22
  end
@@ -19,7 +19,7 @@ echo "[ bin/release ] started"
19
19
  echo "[ bin/release ] Creating DB if needed"
20
20
  BRUT_CLI_RAISE_ON_ERROR=true bundle exec brut db create --env=production
21
21
  echo "[ bin/release ] Migrating DB if needed"
22
- BRUT_CLI_RAISE_ON_ERROR=true bundle exec burt db migrate --env=production
22
+ BRUT_CLI_RAISE_ON_ERROR=true bundle exec brut db migrate --env=production
23
23
 
24
24
  # Add additional commands here as needed
25
25
 
@@ -48,7 +48,7 @@ setup_playright_build_args() {
48
48
  require_command "grep"
49
49
  require_command "sed"
50
50
 
51
- if [ ! -e "${SCRIPT_DIR}"/Gemfile.lock ]; then
51
+ if [ ! -e "${SCRIPT_DIR}"/../Gemfile.lock ]; then
52
52
  log "Could not find Gemfile.lock, which is needed to determine the playwright-ruby-client version"
53
53
  log "Assuming your app is brand-new, this should be OK"
54
54
  echo "# When this file was created, there was no Gemfile.lock, so" >> "${SCRIPT_DIR}"/build.args
@@ -57,7 +57,7 @@ setup_playright_build_args() {
57
57
  echo "# encouraged to re-run \`dx/build\` to address this issue." >> "${SCRIPT_DIR}"/build.args
58
58
  echo PLAYWRIGHT_VERSION=latest >> "${SCRIPT_DIR}"/build.args
59
59
  else
60
- PLAYWRIGHT_VERSION=$(grep playwright-ruby-client Gemfile.lock | grep '(' | sed 's/^.*(//' | sed 's/).*$//' | grep -v ^=)
60
+ PLAYWRIGHT_VERSION=$(grep playwright-ruby-client $SCRIPT_DIR/../Gemfile.lock | grep '(' | sed 's/^.*(//' | sed 's/).*$//' | grep -v ^=)
61
61
  if [ -z "${PLAYWRIGHT_VERSION}" ]; then
62
62
  log "Could not find precise version of playwright-ruby-client from Gemfile.lock"
63
63
  log "This means that your playwright-ruby-client version and playwright NPM modules may be out of sync and may not work"
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.20.0
4
+ version: 0.20.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Bryant Copeland
@@ -475,16 +475,16 @@ dependencies:
475
475
  name: yard
476
476
  requirement: !ruby/object:Gem::Requirement
477
477
  requirements:
478
- - - ">="
478
+ - - '='
479
479
  - !ruby/object:Gem::Version
480
- version: '0'
480
+ version: 0.9.37
481
481
  type: :development
482
482
  prerelease: false
483
483
  version_requirements: !ruby/object:Gem::Requirement
484
484
  requirements:
485
- - - ">="
485
+ - - '='
486
486
  - !ruby/object:Gem::Version
487
- version: '0'
487
+ version: 0.9.37
488
488
  description: An opinionated web framework build on web standards
489
489
  email:
490
490
  - davec@thirdtank.com
@@ -548,6 +548,7 @@ files:
548
548
  - lib/brut/cli/commands/compound_command.rb
549
549
  - lib/brut/cli/commands/execution_context.rb
550
550
  - lib/brut/cli/commands/help.rb
551
+ - lib/brut/cli/commands/help_in_markdown.rb
551
552
  - lib/brut/cli/commands/output_error.rb
552
553
  - lib/brut/cli/commands/raise_error.rb
553
554
  - lib/brut/cli/error.rb