brut 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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