brut 0.0.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 (131) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +7 -0
  3. data/CODE_OF_CONDUCT.txt +99 -0
  4. data/Dockerfile.dx +32 -0
  5. data/Gemfile +6 -0
  6. data/Gemfile.lock +133 -0
  7. data/LICENSE.txt +370 -0
  8. data/README.md +21 -0
  9. data/Rakefile +1 -0
  10. data/bin/bin_kit.rb +39 -0
  11. data/bin/rake +27 -0
  12. data/bin/setup +145 -0
  13. data/brut.gemspec +60 -0
  14. data/docker-compose.dx.yml +16 -0
  15. data/dx/build +26 -0
  16. data/dx/docker-compose.env +22 -0
  17. data/dx/dx.sh.lib +24 -0
  18. data/dx/exec +58 -0
  19. data/dx/prune +19 -0
  20. data/dx/setupkit.sh.lib +144 -0
  21. data/dx/show-help-in-app-container-then-wait.sh +38 -0
  22. data/dx/start +30 -0
  23. data/dx/stop +23 -0
  24. data/lib/brut/back_end/action.rb +3 -0
  25. data/lib/brut/back_end/result.rb +46 -0
  26. data/lib/brut/back_end/seed_data.rb +24 -0
  27. data/lib/brut/back_end/validator.rb +3 -0
  28. data/lib/brut/back_end/validators/form_validator.rb +37 -0
  29. data/lib/brut/cli/app.rb +130 -0
  30. data/lib/brut/cli/app_runner.rb +219 -0
  31. data/lib/brut/cli/apps/build_assets.rb +123 -0
  32. data/lib/brut/cli/apps/db.rb +279 -0
  33. data/lib/brut/cli/apps/scaffold.rb +256 -0
  34. data/lib/brut/cli/apps/test.rb +200 -0
  35. data/lib/brut/cli/command.rb +130 -0
  36. data/lib/brut/cli/error.rb +12 -0
  37. data/lib/brut/cli/execution_results.rb +81 -0
  38. data/lib/brut/cli/executor.rb +37 -0
  39. data/lib/brut/cli/options.rb +46 -0
  40. data/lib/brut/cli/output.rb +30 -0
  41. data/lib/brut/cli.rb +24 -0
  42. data/lib/brut/factory_bot.rb +20 -0
  43. data/lib/brut/framework/app.rb +55 -0
  44. data/lib/brut/framework/config.rb +415 -0
  45. data/lib/brut/framework/container.rb +190 -0
  46. data/lib/brut/framework/errors/abstract_method.rb +9 -0
  47. data/lib/brut/framework/errors/bug.rb +14 -0
  48. data/lib/brut/framework/errors/not_found.rb +10 -0
  49. data/lib/brut/framework/errors.rb +14 -0
  50. data/lib/brut/framework/fussy_type_enforcement.rb +50 -0
  51. data/lib/brut/framework/mcp.rb +215 -0
  52. data/lib/brut/framework/project_environment.rb +18 -0
  53. data/lib/brut/framework.rb +13 -0
  54. data/lib/brut/front_end/asset_metadata.rb +76 -0
  55. data/lib/brut/front_end/component.rb +213 -0
  56. data/lib/brut/front_end/components/form_tag.rb +71 -0
  57. data/lib/brut/front_end/components/i18n_translations.rb +36 -0
  58. data/lib/brut/front_end/components/input.rb +13 -0
  59. data/lib/brut/front_end/components/inputs/csrf_token.rb +8 -0
  60. data/lib/brut/front_end/components/inputs/select.rb +100 -0
  61. data/lib/brut/front_end/components/inputs/text_field.rb +63 -0
  62. data/lib/brut/front_end/components/inputs/textarea.rb +51 -0
  63. data/lib/brut/front_end/components/locale_detection.rb +25 -0
  64. data/lib/brut/front_end/components/page_identifier.rb +13 -0
  65. data/lib/brut/front_end/components/timestamp.rb +33 -0
  66. data/lib/brut/front_end/download.rb +23 -0
  67. data/lib/brut/front_end/flash.rb +57 -0
  68. data/lib/brut/front_end/form.rb +171 -0
  69. data/lib/brut/front_end/forms/constraint_violation.rb +39 -0
  70. data/lib/brut/front_end/forms/input.rb +119 -0
  71. data/lib/brut/front_end/forms/input_definition.rb +100 -0
  72. data/lib/brut/front_end/forms/validity_state.rb +36 -0
  73. data/lib/brut/front_end/handler.rb +48 -0
  74. data/lib/brut/front_end/handlers/csp_reporting_handler.rb +11 -0
  75. data/lib/brut/front_end/handlers/locale_detection_handler.rb +22 -0
  76. data/lib/brut/front_end/handling_results.rb +14 -0
  77. data/lib/brut/front_end/http_method.rb +33 -0
  78. data/lib/brut/front_end/http_status.rb +16 -0
  79. data/lib/brut/front_end/middleware.rb +7 -0
  80. data/lib/brut/front_end/middlewares/reload_app.rb +31 -0
  81. data/lib/brut/front_end/page.rb +47 -0
  82. data/lib/brut/front_end/request_context.rb +82 -0
  83. data/lib/brut/front_end/route_hook.rb +15 -0
  84. data/lib/brut/front_end/route_hooks/age_flash.rb +8 -0
  85. data/lib/brut/front_end/route_hooks/csp_no_inline_scripts.rb +17 -0
  86. data/lib/brut/front_end/route_hooks/csp_no_inline_styles_or_scripts.rb +46 -0
  87. data/lib/brut/front_end/route_hooks/locale_detection.rb +24 -0
  88. data/lib/brut/front_end/route_hooks/setup_request_context.rb +11 -0
  89. data/lib/brut/front_end/routing.rb +236 -0
  90. data/lib/brut/front_end/session.rb +56 -0
  91. data/lib/brut/front_end/template.rb +32 -0
  92. data/lib/brut/front_end/templates/block_filter.rb +60 -0
  93. data/lib/brut/front_end/templates/erb_engine.rb +26 -0
  94. data/lib/brut/front_end/templates/erb_parser.rb +84 -0
  95. data/lib/brut/front_end/templates/escapable_filter.rb +18 -0
  96. data/lib/brut/front_end/templates/html_safe_string.rb +40 -0
  97. data/lib/brut/i18n/base_methods.rb +168 -0
  98. data/lib/brut/i18n/for_cli.rb +4 -0
  99. data/lib/brut/i18n/for_html.rb +4 -0
  100. data/lib/brut/i18n/http_accept_language.rb +68 -0
  101. data/lib/brut/i18n.rb +6 -0
  102. data/lib/brut/instrumentation/basic.rb +66 -0
  103. data/lib/brut/instrumentation/event.rb +19 -0
  104. data/lib/brut/instrumentation/http_event.rb +5 -0
  105. data/lib/brut/instrumentation/subscriber.rb +41 -0
  106. data/lib/brut/instrumentation.rb +11 -0
  107. data/lib/brut/junk_drawer.rb +88 -0
  108. data/lib/brut/sinatra_helpers.rb +183 -0
  109. data/lib/brut/spec_support/component_support.rb +49 -0
  110. data/lib/brut/spec_support/flash_support.rb +7 -0
  111. data/lib/brut/spec_support/general_support.rb +18 -0
  112. data/lib/brut/spec_support/handler_support.rb +7 -0
  113. data/lib/brut/spec_support/matcher.rb +9 -0
  114. data/lib/brut/spec_support/matchers/be_a_bug.rb +14 -0
  115. data/lib/brut/spec_support/matchers/be_page_for.rb +14 -0
  116. data/lib/brut/spec_support/matchers/be_routing_for.rb +11 -0
  117. data/lib/brut/spec_support/matchers/have_constraint_violation.rb +56 -0
  118. data/lib/brut/spec_support/matchers/have_html_attribute.rb +69 -0
  119. data/lib/brut/spec_support/matchers/have_rendered.rb +20 -0
  120. data/lib/brut/spec_support/matchers/have_returned_http_status.rb +27 -0
  121. data/lib/brut/spec_support/session_support.rb +3 -0
  122. data/lib/brut/spec_support.rb +12 -0
  123. data/lib/brut/version.rb +3 -0
  124. data/lib/brut.rb +38 -0
  125. data/lib/sequel/extensions/brut_instrumentation.rb +37 -0
  126. data/lib/sequel/extensions/brut_migrations.rb +98 -0
  127. data/lib/sequel/plugins/created_at.rb +14 -0
  128. data/lib/sequel/plugins/external_id.rb +45 -0
  129. data/lib/sequel/plugins/find_bang.rb +13 -0
  130. data/lib/sequel/plugins.rb +3 -0
  131. metadata +484 -0
data/bin/bin_kit.rb ADDED
@@ -0,0 +1,39 @@
1
+ require "pathname"
2
+ require "fileutils"
3
+ require "open3"
4
+
5
+ # We don't want the setup method to have to do all this error
6
+ # checking, and we also want to explicitly log what we are
7
+ # executing. Thus, we use this method instead of Kernel#system
8
+ def system!(*args)
9
+ if ENV["BRUT_BIN_KIT_DEBUG"] == "true"
10
+ log "Executing #{args}"
11
+ out,err,status = Open3.capture3(*args)
12
+ if status.success?
13
+ log "#{args} succeeded"
14
+ else
15
+ log "#{args} failed"
16
+ log "STDOUT:"
17
+ $stdout.puts out
18
+ log "STDERR:"
19
+ $stderr.puts err
20
+ abort
21
+ end
22
+ else
23
+ log "Executing #{args}"
24
+ if system(*args)
25
+ log "#{args} succeeded"
26
+ else
27
+ log "#{args} failed"
28
+ abort
29
+ end
30
+ end
31
+ end
32
+
33
+ # It's helpful to know what messages came from this
34
+ # script, so we'll use log instead of `puts`
35
+ def log(message)
36
+ puts "[ #{$0} ] #{message}"
37
+ end
38
+
39
+ ROOT_DIR = ((Pathname(__dir__) / ".." ).expand_path)
data/bin/rake ADDED
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # This file was generated by Bundler.
6
+ #
7
+ # The application 'rake' is installed as part of a gem, and
8
+ # this file is here to facilitate running it.
9
+ #
10
+
11
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
12
+
13
+ bundle_binstub = File.expand_path("bundle", __dir__)
14
+
15
+ if File.file?(bundle_binstub)
16
+ if File.read(bundle_binstub, 300).include?("This file was generated by Bundler")
17
+ load(bundle_binstub)
18
+ else
19
+ abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
20
+ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
21
+ end
22
+ end
23
+
24
+ require "rubygems"
25
+ require "bundler/setup"
26
+
27
+ load Gem.bin_path("rake", "rake")
data/bin/setup ADDED
@@ -0,0 +1,145 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require_relative "bin_kit"
4
+ require "optparse"
5
+ require "pathname"
6
+ require "fileutils"
7
+
8
+ def setup(update_gems:,setup_credentials:)
9
+ if update_gems
10
+ log "Updating gems"
11
+ system! "bundle update"
12
+ else
13
+ log "Installing gems"
14
+ # Only do bundle install if the much-faster
15
+ # bundle check indicates we need to
16
+ system! "bundle check --no-color || bundle install --no-color --quiet"
17
+ end
18
+
19
+ if setup_credentials
20
+ project_root = Pathname($0).dirname / ".."
21
+ credentials_dir = project_root / "dx" / "credentials"
22
+ if credentials_dir.exist?
23
+ if credentials_dir.directory?
24
+ log "#{credentials_dir} exists"
25
+ else
26
+ log "#{credentials_dir} is not a directory - please delete it or move it elsewhere and re-run this script"
27
+ exit 1
28
+ end
29
+ else
30
+ log "#{credentials_dir} doesn't exist - creating"
31
+ FileUtils.mkdir_p credentials_dir
32
+ end
33
+
34
+ # https://docs.github.com/en/authentication/connecting-to-github-with-ssh/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent?platform=linux
35
+ key_file = credentials_dir / "id_ed25519"
36
+
37
+ if key_file.exist?
38
+ log "#{key_file} exists already"
39
+ else
40
+ git_config_command = "git config --get user.email"
41
+ email = `#{git_config_command}`.chomp
42
+ if !$?.success?
43
+ log "Could not determine your email via #{git_config_command} - is your git set up properly?"
44
+ exit 1
45
+ end
46
+ log "Creating your key in #{key_file} using email #{email}."
47
+ log "You will be prompted for a passphrase, which you are encouraged to provide"
48
+ system! "ssh-keygen -t ed25519 -C #{email} -f \"#{key_file}\""
49
+ log ""
50
+ log "You must now add this to your GitHub profile in order to perform Git commands"
51
+ log ""
52
+ log "The key you just generated has a public key that should be available on your computer at"
53
+ log ""
54
+ log " #{key_file.relative_path_from(project_root)}.pub"
55
+ log ""
56
+ log "Copy its contents and head to:"
57
+ log ""
58
+ log " https://github.com/settings/keys"
59
+ log ""
60
+ log "Click 'New SSH key' and paste your key in, giving it a name like 'Brut Dev Env'"
61
+ log ""
62
+ log "(if this doesn't look right, check https://docs.github.com/en/authentication/connecting-to-github-with-ssh/adding-a-new-ssh-key-to-your-github-account)"
63
+ log ""
64
+ log "Hit return when done"
65
+ x = gets
66
+ end
67
+
68
+ log "Adding your SSH key to ssh-agent - you must provide your passphrase"
69
+ system! "ssh-add #{key_file}"
70
+
71
+ known_hosts_dest = Pathname("/") / "root" / ".ssh" / "known_hosts"
72
+ if known_hosts_dest.exist?
73
+ log "#{known_hosts_dest} exists, your ssh key should work with GitHub"
74
+ else
75
+ log "#{known_hosts_dest} does not exist"
76
+ known_hosts_source = credentials_dir / "known_hosts"
77
+ if known_hosts_source.exist?
78
+ log "#{known_hosts_source} exists - copying it to #{known_hosts_dest}"
79
+ FileUtils.mkdir_p known_hosts_dest.dirname
80
+ FileUtils.chmod(0700,known_hosts_dest.dirname)
81
+ FileUtils.cp known_hosts_source, known_hosts_dest
82
+ FileUtils.chmod(0600,known_hosts_dest)
83
+ else
84
+ log "#{known_hosts_source} also does not exist. To create it, we'll connect to github.com"
85
+ log "NOTE: it will show you a fingerprint to verify authenticity. You should check it against:"
86
+ log ""
87
+ log " https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/githubs-ssh-key-fingerprints"
88
+ log ""
89
+ log " and proceed ONLY if the values match"
90
+ log ""
91
+ system("ssh -T git@github.com") # NOT system! because this may exit nonzero but still have succeeded
92
+ if $?.exitstatus == 255
93
+ log "SOMETHING MAY HAVE GONE WRONG!"
94
+ end
95
+ if known_hosts_dest.exist?
96
+ log "Copying #{known_hosts_dest} back to #{known_hosts_source} to use in the future"
97
+ FileUtils.cp known_hosts_dest,known_hosts_source
98
+ else
99
+ log "For some reason #{known_hosts_dest} was not created. Future ssh commands may ask you to verify GitHub's fingerprint"
100
+ end
101
+ end
102
+ end
103
+ log "Your ssh key looks good"
104
+
105
+ gem_credentials_dest = Pathname("/") / "root" / ".gem" / "credentials"
106
+ if gem_credentials_dest.exist?
107
+ log "Gem credentials look good"
108
+ else
109
+ log "#{gem_credentials_dest} doesn't exist - creating"
110
+ FileUtils.mkdir_p gem_credentials_dest.dirname
111
+ gem_credentials_source = credentials_dir / "rubygems.credentials"
112
+ if gem_credentials_source.exist?
113
+ log "#{gem_credentials_source} exists - copying it to #{gem_credentials_dest}"
114
+ FileUtils.cp gem_credentials_source,gem_credentials_dest
115
+ else
116
+ log "#{gem_credentials_source} must contain a RubyGems credentials file"
117
+ log ""
118
+ log "Follow the instructions here:"
119
+ log ""
120
+ log " https://guides.rubygems.org/api-key-scopes/#creating-from-gem-cli"
121
+ log ""
122
+ log "Then copy ~/.gem/credentials into #{gem_credentials_source} and re-run this script"
123
+ exit 1
124
+ end
125
+ end
126
+ else
127
+ log "Not setting up GitHub or RubyGems credentials. You won't be able to push the gem"
128
+ end
129
+ end
130
+
131
+
132
+ options = {
133
+ update_gems: false,
134
+ setup_credentials: true,
135
+ }
136
+ OptionParser.new do |opts|
137
+ opts.on("--update-gems","Update gems get the latest versions consistent with Gemfile / gemspec.") do
138
+ options[:update_gems] = true
139
+ end
140
+ opts.on("--no-credentials","If set, no GitHub or RubyGems credentials are required, but you can't push gems") do
141
+ options[:setup_credentials] = false
142
+ end
143
+ end.parse!
144
+
145
+ setup(**options)
data/brut.gemspec ADDED
@@ -0,0 +1,60 @@
1
+ lib = File.expand_path("../lib", __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require "brut/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "brut"
7
+ spec.version = Brut::VERSION
8
+ spec.authors = ["David Bryant Copeland"]
9
+ spec.email = ["davec@thirdtank.com"]
10
+
11
+ spec.summary = %q{NOT YET RELEASED - Web Framework Built around Ruby, Web Standards, Simplicity, and Object-Orientation}
12
+ spec.description = %q{NOT YET RELEASED - An opinionated web framework build on web standards}
13
+ spec.homepage = "https://naildrivin5.com"
14
+
15
+ # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
16
+ # to allow pushing to a single host or delete this section to allow pushing to any host.
17
+ if spec.respond_to?(:metadata)
18
+ spec.metadata["allowed_push_host"] = "https://rubygems.org"
19
+
20
+ spec.metadata["homepage_uri"] = spec.homepage
21
+ spec.metadata["source_code_uri"] = "https://naildrivin5.com"
22
+ spec.metadata["changelog_uri"] = "https://naildrivin5.com"
23
+ else
24
+ raise "RubyGems 2.0 or newer is required to protect against " \
25
+ "public gem pushes."
26
+ end
27
+
28
+ # Specify which files should be added to the gem when it is released.
29
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
30
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
31
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
32
+ end
33
+ spec.bindir = "exe"
34
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
35
+ spec.require_paths = ["lib"]
36
+
37
+ spec.add_runtime_dependency "dotenv"
38
+ spec.add_runtime_dependency "ostruct" # squelch some warning - this is not used
39
+ spec.add_runtime_dependency "factory_bot"
40
+ spec.add_runtime_dependency "faker"
41
+ spec.add_runtime_dependency "i18n"
42
+ spec.add_runtime_dependency "nokogiri"
43
+ spec.add_runtime_dependency "prism"
44
+ spec.add_runtime_dependency "rack-protection"
45
+ spec.add_runtime_dependency "rackup"
46
+ spec.add_runtime_dependency "rexml"
47
+ spec.add_runtime_dependency "semantic_logger"
48
+ spec.add_runtime_dependency "sequel"
49
+ spec.add_runtime_dependency "sinatra"
50
+ spec.add_runtime_dependency "temple"
51
+ spec.add_runtime_dependency "tilt"
52
+ spec.add_runtime_dependency "tzinfo"
53
+ spec.add_runtime_dependency "tzinfo-data"
54
+ spec.add_runtime_dependency "zeitwerk"
55
+
56
+ spec.add_development_dependency "activesupport"
57
+ spec.add_development_dependency "rspec", "~> 3.0"
58
+ spec.add_development_dependency "bundler"
59
+ spec.add_development_dependency "rake"
60
+ end
@@ -0,0 +1,16 @@
1
+ services:
2
+ app:
3
+ image: ${IMAGE}
4
+ pull_policy: "missing"
5
+ init: true
6
+ volumes:
7
+ - type: bind
8
+ source: "./"
9
+ target: "/root/work"
10
+ consistency: "consistent"
11
+ - type: bind
12
+ source: ${GIT_CONFIG}
13
+ target: "/root/.gitconfig"
14
+ entrypoint: /root/show-help-in-app-container-then-wait.sh
15
+ working_dir: /root/work
16
+
data/dx/build ADDED
@@ -0,0 +1,26 @@
1
+ #!/bin/bash
2
+
3
+ set -e
4
+
5
+ SCRIPT_DIR=$( cd -- "$( dirname -- "${0}" )" > /dev/null 2>&1 && pwd )
6
+
7
+ . "${SCRIPT_DIR}/dx.sh.lib"
8
+
9
+ require_command "docker"
10
+ load_docker_compose_env
11
+
12
+ usage_on_help "Builds the Docker image based on the Dockerfile" "" "build.pre" "build.post" "${@}"
13
+
14
+ exec_hook_if_exists "build.pre" Dockerfile.dx "${IMAGE}"
15
+
16
+ docker build \
17
+ --file Dockerfile.dx \
18
+ --tag "${IMAGE}" \
19
+ ./
20
+
21
+ exec_hook_if_exists "build.post" Dockerfile.dx "${IMAGE}"
22
+
23
+ log "🌈" "Your Docker image has been built tagged '${IMAGE}'"
24
+ log "🔄" "You can now run dx/start to start it up, though you may need to stop it first with Ctrl-C"
25
+
26
+ # vim: ft=bash
@@ -0,0 +1,22 @@
1
+ # IMAGE is the name of the image to be built for running
2
+ # your app. The recommended format is ORG/REPO:TAG
3
+ #
4
+ # ORG - your org on GitHub or DockerHub
5
+ # REPO - the name of the repo on GitHub or the app name
6
+ # TAG - a version identifier. Recommend you avoid latest as this is confusing
7
+ IMAGE=thirdtank/brut-dev:ruby-3.3
8
+
9
+ # This is used to tell docker compose what the name
10
+ # of your project is for the purpose of naming
11
+ # containers. It can be anything and is mostly
12
+ # used for pruning containers via bin/prune
13
+ PROJECT_NAME=brut-dev
14
+
15
+ # Use this to override the service name for your
16
+ # app in docker-compose.dx.yml
17
+ DEFAULT_SERVICE=app
18
+
19
+ # Path to the git configuration to use
20
+ GIT_CONFIG=~/.gitconfig
21
+
22
+ # vim: ft=bash
data/dx/dx.sh.lib ADDED
@@ -0,0 +1,24 @@
1
+ # shellcheck shell=bash
2
+
3
+ . "${SCRIPT_DIR}/setupkit.sh.lib"
4
+
5
+ require_command "realpath"
6
+ require_command "cat"
7
+
8
+ ENV_FILE=$(realpath "${SCRIPT_DIR}")/docker-compose.env
9
+
10
+ load_docker_compose_env() {
11
+ . "${ENV_FILE}"
12
+ }
13
+
14
+ exec_hook_if_exists() {
15
+ script_name=$1
16
+ shift
17
+ if [ -x "${SCRIPT_DIR}"/"${script_name}" ]; then
18
+ log "đŸĒ" "${script_name} exists - executing"
19
+ "${SCRIPT_DIR}"/"${script_name}" "${@}"
20
+ else
21
+ debug "${script_name} does not exist"
22
+ fi
23
+ }
24
+ # vim: ft=bash
data/dx/exec ADDED
@@ -0,0 +1,58 @@
1
+ #!/bin/bash
2
+
3
+ set -e
4
+
5
+ SCRIPT_DIR=$( cd -- "$( dirname -- "${0}" )" > /dev/null 2>&1 && pwd )
6
+
7
+ . "${SCRIPT_DIR}/dx.sh.lib"
8
+
9
+ require_command "docker"
10
+ load_docker_compose_env
11
+
12
+ usage_description="Execute a command inside the app's container with ssh-agent active."
13
+ usage_args="[-s service] [-A] command"
14
+ usage_pre="exec.pre"
15
+ usage_on_help "${usage_description}" "${usage_args}" "${usage_pre}" "" "${@}"
16
+
17
+ SERVICE="${SERVICE_NAME:-${DEFAULT_SERVICE}}"
18
+ SSH_AGENT="ssh-agent "
19
+ while getopts "s:A" opt "${@}"; do
20
+ case ${opt} in
21
+ s )
22
+ SERVICE="${OPTARG}"
23
+ ;;
24
+ A )
25
+ SSH_AGENT=""
26
+ ;;
27
+ \? )
28
+ log "🛑" "Unknown option: ${opt}"
29
+ usage "${description}" "${usage_args}" "${usage_pre}"
30
+ ;;
31
+ : )
32
+ log "🛑" "Invalid option: ${opt} requires an argument"
33
+ usage "${description}" "${usage_args}" "${usage_pre}"
34
+ ;;
35
+ esac
36
+ done
37
+ shift $((OPTIND -1))
38
+
39
+ if [ $# -eq 0 ]; then
40
+ log "🛑" "You must provide a command e.g. bash or ls -l"
41
+ usage "${description}" "${usage_args}" "${usage_pre}"
42
+ fi
43
+
44
+
45
+ exec_hook_if_exists "exec.pre"
46
+
47
+ log "🚂" "Running '${*}' inside container with service name '${SERVICE}'"
48
+
49
+ docker \
50
+ compose \
51
+ --file docker-compose.dx.yaml \
52
+ --project-name "${PROJECT_NAME}" \
53
+ --env-file "${ENV_FILE}" \
54
+ exec \
55
+ "${SERVICE}" \
56
+ ${SSH_AGENT} "${@}"
57
+
58
+ # vim: ft=bash
data/dx/prune ADDED
@@ -0,0 +1,19 @@
1
+ #!/bin/bash
2
+
3
+ set -e
4
+
5
+ SCRIPT_DIR=$( cd -- "$( dirname -- "${0}" )" > /dev/null 2>&1 && pwd )
6
+
7
+ . "${SCRIPT_DIR}/dx.sh.lib"
8
+ require_command "docker"
9
+ load_docker_compose_env
10
+
11
+ usage_on_help "Prune containers for this repo" "" "" "" "${@}"
12
+
13
+ for container_id in $(docker container ls -a -f "name=^${PROJECT_NAME}-.*-1$" --format="{{.ID}}"); do
14
+ log "🗑" "Removing container with id '${container_id}'"
15
+ docker container rm "${container_id}"
16
+ done
17
+ echo "đŸ§ŧ" "Containers removed"
18
+
19
+ # vim: ft=bash
@@ -0,0 +1,144 @@
1
+ # shellcheck shell=bash
2
+
3
+ fatal() {
4
+ remainder=${*:2}
5
+ if [ -z "$remainder" ]; then
6
+ log "🛑" "${@}"
7
+ else
8
+ log "${@}"
9
+ fi
10
+ exit 1
11
+ }
12
+
13
+ log() {
14
+ emoji=$1
15
+ remainder=${*:2}
16
+ if [ -z "${NO_EMOJI}" ]; then
17
+ echo "[ ${0} ] ${*}"
18
+ else
19
+ # if remainder is empty that means no emoji was passed
20
+ if [ -z "$remainder" ]; then
21
+ echo "[ ${0} ] ${*}"
22
+ else # emoji was passed, but we ignore it
23
+ echo "[ ${0} ] ${remainder}"
24
+ fi
25
+ fi
26
+ }
27
+
28
+ debug() {
29
+ message=$1
30
+ if [ -z "${DOCKBOX_DEBUG}" ]; then
31
+ return
32
+ fi
33
+ log "🐛" "${message}"
34
+ }
35
+
36
+ usage() {
37
+ description=$1
38
+ arg_names=$2
39
+ pre_hook=$3
40
+ post_hook=$4
41
+ echo "usage: ${0} [-h] ${arg_names}"
42
+ if [ -n "${description}" ]; then
43
+ echo
44
+ echo "DESCRIPTION"
45
+ echo " ${description}"
46
+ fi
47
+ if [ -n "${pre_hook}" ] || [ -n "${post_hook}" ]; then
48
+ echo
49
+ echo "HOOKS"
50
+ if [ -n "${pre_hook}" ]; then
51
+ echo " ${pre_hook} - if present, called before the main action"
52
+ fi
53
+ if [ -n "${post_hook}" ]; then
54
+ echo " ${post_hook} - if present, called after the main action"
55
+ fi
56
+ fi
57
+ exit 0
58
+ }
59
+
60
+ usage_on_help() {
61
+ description=$1
62
+ arg_names=$2
63
+ pre_hook=$3
64
+ post_hook=$4
65
+ # These are the args passed to the invocation so this
66
+ # function can determine if the user requested help
67
+ cli_args=( "${@:5}" )
68
+
69
+ for arg in "${cli_args[@]}"; do
70
+ if [ "${arg}" = "-h" ] || [ "${arg}" = "--help" ]; then
71
+ usage "${description}" "${arg_names}" "${pre_hook}" "${post_hook}"
72
+ fi
73
+ done
74
+ }
75
+
76
+ # Read user input into the variable 'INPUT'
77
+ #
78
+ # Args:
79
+ #
80
+ # [1] - an emoji to use for messages
81
+ # [2] - the message explaining what input is being requested
82
+ # [3] - a default value to use if no value is provided
83
+ #
84
+ # Respects NO_EMOJI when outputing messages to the user
85
+ user_input() {
86
+ emoji=$1
87
+ message=$2
88
+ default=$3
89
+ prompt=$4
90
+
91
+ if [ -z "$message" ]; then
92
+ echo "user_input requires a message"
93
+ exit 1
94
+ fi
95
+
96
+ INPUT=
97
+
98
+ if [ -z "${prompt}" ]; then
99
+ prompt=$(log "${emoji}" "Value: ")
100
+ if [ -n "${default}" ]; then
101
+ prompt=$(log "${emoji}" "Value (or hit return to use '${default}'): ")
102
+ fi
103
+ fi
104
+
105
+ while [ -z "${INPUT}" ]; do
106
+
107
+ log "$emoji" "$message"
108
+ read -r -p "${prompt}" INPUT
109
+ if [ -z "$INPUT" ]; then
110
+ INPUT=$default
111
+ fi
112
+ if [ -z "$INPUT" ]; then
113
+ log "đŸ˜ļ", "You must provide a value"
114
+ fi
115
+ done
116
+ }
117
+
118
+ user_confirm() {
119
+ user_input "$1" "$2" "$3" "y/n> "
120
+ }
121
+
122
+ require_not_exist() {
123
+ file=$1
124
+ message=$2
125
+ if [ -e "${file}" ]; then
126
+ fatal "$message"
127
+ fi
128
+ }
129
+ require_exist() {
130
+ file=$1
131
+ message=$2
132
+ if [ ! -e "${file}" ]; then
133
+ fatal "$message"
134
+ fi
135
+ }
136
+
137
+ require_command() {
138
+ command_name=$1
139
+ if ! command -v "${command_name}" >/dev/null 2>&1; then
140
+ fatal "Command '${command_name}' not found - it is required for this script to run"
141
+ fi
142
+ }
143
+
144
+ # vim: ft=bash
@@ -0,0 +1,38 @@
1
+ #!/bin/bash
2
+
3
+ set -e
4
+
5
+ # Ideally, the message below is shown after everything starts up. We can't
6
+ # achieve this using healtchecks because the interval for a healtcheck is
7
+ # also an initial delay, and we don't really want to do healthchecks on
8
+ # our DB or Redis every 2 seconds. So, we sleep just a bit to let
9
+ # the other containers start up and vomit out their output first.
10
+ sleep 2
11
+ # Output some helpful messaging when invoking `dx/start` (which itself is
12
+ # a convenience script for `docker compose up`.
13
+ #
14
+ # Adding this to work around the mild inconvenience of the `app` container's
15
+ # entrypoint generating no output.
16
+ #
17
+ cat <<-'PROMPT'
18
+
19
+
20
+
21
+ 🎉 Dev Environment Initialized! 🎉
22
+
23
+ â„šī¸ To use this environment, open a new terminal and run
24
+
25
+ dx/exec bash
26
+
27
+ 🕹 Use `ctrl-c` to exit.
28
+
29
+
30
+
31
+ PROMPT
32
+
33
+ # Using `sleep infinity` instead of `tail -f /dev/null`. This may be a
34
+ # performance improvement based on the conversation on a semi-related
35
+ # StackOverflow page.
36
+ #
37
+ # @see https://stackoverflow.com/a/41655546
38
+ sleep infinity
data/dx/start ADDED
@@ -0,0 +1,30 @@
1
+ #!/bin/bash
2
+
3
+ set -e
4
+
5
+ SCRIPT_DIR=$( cd -- "$( dirname -- "${0}" )" > /dev/null 2>&1 && pwd )
6
+
7
+ . "${SCRIPT_DIR}/dx.sh.lib"
8
+ require_command "docker"
9
+ load_docker_compose_env
10
+
11
+ usage_on_help "Starts all services, including a container in which to run your app" "" "" "" "${@}"
12
+
13
+ log "🚀" "Starting docker-compose.dx.yml"
14
+
15
+ BUILD=--build
16
+ if [ "${1}" == "--no-build" ]; then
17
+ BUILD=
18
+ fi
19
+
20
+ docker \
21
+ compose \
22
+ --file docker-compose.dx.yml \
23
+ --project-name "${PROJECT_NAME}" \
24
+ --env-file "${ENV_FILE}" \
25
+ up \
26
+ "${BUILD}" \
27
+ --timestamps \
28
+ --force-recreate
29
+
30
+ # vim: ft=bash
data/dx/stop ADDED
@@ -0,0 +1,23 @@
1
+ #!/bin/bash
2
+
3
+ set -e
4
+
5
+ SCRIPT_DIR=$( cd -- "$( dirname -- "${0}" )" > /dev/null 2>&1 && pwd )
6
+
7
+ . "${SCRIPT_DIR}/dx.sh.lib"
8
+ require_command "docker"
9
+ load_docker_compose_env
10
+
11
+ usage_on_help "Stops all services, the container in which to run your app and removes any volumes" "" "" "" "${@}"
12
+
13
+ log "🚀" "Stopping docker-compose.dx.yml"
14
+
15
+ docker \
16
+ compose \
17
+ --file docker-compose.dx.yml \
18
+ --project-name "${PROJECT_NAME}" \
19
+ --env-file "${ENV_FILE}" \
20
+ down \
21
+ --volumes
22
+
23
+ # vim: ft=bash
@@ -0,0 +1,3 @@
1
+ class Brut::BackEnd::Action
2
+ include SemanticLogger::Loggable
3
+ end