brut 0.5.0 → 0.8.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.
Files changed (265) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +4 -0
  3. data/CHANGELOG.md +7 -0
  4. data/Dockerfile.dx +19 -0
  5. data/Gemfile.lock +1 -1
  6. data/README.md +19 -0
  7. data/assets/YouTubeThumb.pxd +0 -0
  8. data/bin/build +86 -0
  9. data/bin/ci +36 -0
  10. data/bin/docs +39 -9
  11. data/bin/publish +61 -0
  12. data/bin/setup +6 -0
  13. data/brut-css/bin/build +19 -0
  14. data/brut-css/bin/ci +19 -0
  15. data/brut-css/bin/docs +19 -0
  16. data/brut-css/bin/publish +21 -0
  17. data/brut-css/bin/setup +1 -0
  18. data/brut-css/package-lock.json +2 -2
  19. data/brut-css/package.json +1 -1
  20. data/brut-js/bin/build +15 -6
  21. data/brut-js/bin/docs +25 -0
  22. data/brut-js/bin/publish +21 -0
  23. data/brut-js/bin/setup +1 -0
  24. data/brut-js/dx +1 -0
  25. data/brut-js/package-lock.json +2 -2
  26. data/brut-js/package.json +1 -1
  27. data/brut.gemspec +2 -2
  28. data/brutrb.com/bin/setup +1 -0
  29. data/brutrb.com/getting-started.md +3 -0
  30. data/brutrb.com/overview.md +6 -0
  31. data/brutrb.com/tutorial.md +7 -3
  32. data/docs/404.html +2 -2
  33. data/docs/adrs.html +3 -3
  34. data/docs/ai.html +3 -3
  35. data/docs/assets/{app.D6BuVHo9.js → app.DyQLb4Ot.js} +1 -1
  36. data/docs/assets/chunks/@localSearchIndexroot.CmtZyrFA.js +1 -0
  37. data/docs/assets/chunks/{VPLocalSearchBox.BpvHMbx6.js → VPLocalSearchBox.T1iA-eJx.js} +1 -1
  38. data/docs/assets/chunks/{theme.wlAOvi2f.js → theme.ChwsbWjK.js} +2 -2
  39. data/docs/assets/{components.md.iLiv2E9X.js → components.md.DHh-NwKs.js} +3 -3
  40. data/docs/assets/{configuration.md.DmuAdsli.js → configuration.md.D8Wz3oJU.js} +1 -1
  41. data/docs/assets/{forms.md.D8aa_qI-.js → forms.md.BRE85eju.js} +1 -1
  42. data/docs/assets/{getting-started.md.DLplsDUd.js → getting-started.md.2ioiTe-B.js} +6 -3
  43. data/docs/assets/{getting-started.md.DLplsDUd.lean.js → getting-started.md.2ioiTe-B.lean.js} +1 -1
  44. data/docs/assets/overview.md.DlKiRRG_.js +1 -0
  45. data/docs/assets/overview.md.DlKiRRG_.lean.js +1 -0
  46. data/docs/assets/tutorial.md.BIb7XT6j.js +1 -0
  47. data/docs/assets/tutorial.md.BIb7XT6j.lean.js +1 -0
  48. data/docs/assets.html +3 -3
  49. data/docs/brut-js.html +3 -3
  50. data/docs/business-logic.html +3 -3
  51. data/docs/cli.html +3 -3
  52. data/docs/components.html +7 -7
  53. data/docs/configuration.html +5 -5
  54. data/docs/css.html +3 -3
  55. data/docs/custom-element-tests.html +3 -3
  56. data/docs/database-access.html +3 -3
  57. data/docs/database-schema.html +3 -3
  58. data/docs/deployment.html +3 -3
  59. data/docs/dev-environment.html +3 -3
  60. data/docs/dir-structure.html +3 -3
  61. data/docs/doc-conventions.html +3 -3
  62. data/docs/end-to-end-tests.html +3 -3
  63. data/docs/features.html +3 -3
  64. data/docs/flash-and-session.html +3 -3
  65. data/docs/form-constraints.html +3 -3
  66. data/docs/forms.html +5 -5
  67. data/docs/getting-started.html +9 -6
  68. data/docs/handlers.html +3 -3
  69. data/docs/hashmap.json +1 -1
  70. data/docs/hooks.html +3 -3
  71. data/docs/i18n.html +3 -3
  72. data/docs/index.html +3 -3
  73. data/docs/instrumentation.html +3 -3
  74. data/docs/javascript.html +3 -3
  75. data/docs/jobs.html +3 -3
  76. data/docs/keyword-injection.html +3 -3
  77. data/docs/layouts.html +3 -3
  78. data/docs/lsp.html +3 -3
  79. data/docs/markdown-examples.html +3 -3
  80. data/docs/middleware.html +3 -3
  81. data/docs/overview.html +5 -5
  82. data/docs/pages.html +3 -3
  83. data/docs/recipes/alternate-layouts.html +3 -3
  84. data/docs/recipes/authentication.html +3 -3
  85. data/docs/recipes/blank-layouts.html +3 -3
  86. data/docs/recipes/custom-flash.html +3 -3
  87. data/docs/recipes/indexed-forms.html +3 -3
  88. data/docs/recipes/migrations.html +3 -3
  89. data/docs/recipes/text-field-component.html +3 -3
  90. data/docs/roadmap.html +3 -3
  91. data/docs/routes.html +3 -3
  92. data/docs/security.html +3 -3
  93. data/docs/seed-data.html +3 -3
  94. data/docs/space-time-continuum.html +3 -3
  95. data/docs/tutorial.html +5 -5
  96. data/docs/unit-tests.html +3 -3
  97. data/docs/why.html +3 -3
  98. data/lib/brut/framework/mcp.rb +1 -1
  99. data/lib/brut/front_end/components/form_tag.rb +2 -2
  100. data/lib/brut/version.rb +1 -1
  101. data/mkbrut/.gitignore +16 -0
  102. data/mkbrut/CODE_OF_CONDUCT.txt +100 -0
  103. data/mkbrut/Gemfile +3 -0
  104. data/mkbrut/Gemfile.lock +19 -0
  105. data/mkbrut/LICENSE.txt +370 -0
  106. data/mkbrut/README.md +145 -0
  107. data/mkbrut/Rakefile +2 -0
  108. data/mkbrut/bin/build +36 -0
  109. data/mkbrut/bin/ci +19 -0
  110. data/mkbrut/bin/docs +19 -0
  111. data/mkbrut/bin/publish +129 -0
  112. data/mkbrut/bin/rake +16 -0
  113. data/mkbrut/bin/setup +30 -0
  114. data/mkbrut/brut-welcome.png +0 -0
  115. data/mkbrut/deploy/.dockerignore +2 -0
  116. data/mkbrut/deploy/Dockerfile +25 -0
  117. data/mkbrut/exe/mkbrut +5 -0
  118. data/mkbrut/lib/mkbrut/app.rb +79 -0
  119. data/mkbrut/lib/mkbrut/app_id.rb +8 -0
  120. data/mkbrut/lib/mkbrut/app_name.rb +29 -0
  121. data/mkbrut/lib/mkbrut/app_options.rb +36 -0
  122. data/mkbrut/lib/mkbrut/base.rb +57 -0
  123. data/mkbrut/lib/mkbrut/cli.rb +107 -0
  124. data/mkbrut/lib/mkbrut/erb_binding_delegate.rb +20 -0
  125. data/mkbrut/lib/mkbrut/internet_identifier.rb +32 -0
  126. data/mkbrut/lib/mkbrut/invalid_identifier.rb +4 -0
  127. data/mkbrut/lib/mkbrut/ops/add_css_import.rb +42 -0
  128. data/mkbrut/lib/mkbrut/ops/add_i18n_message.rb +74 -0
  129. data/mkbrut/lib/mkbrut/ops/add_method.rb +48 -0
  130. data/mkbrut/lib/mkbrut/ops/append_to_file.rb +20 -0
  131. data/mkbrut/lib/mkbrut/ops/base_op.rb +21 -0
  132. data/mkbrut/lib/mkbrut/ops/copy_file.rb +12 -0
  133. data/mkbrut/lib/mkbrut/ops/insert_code_in_method.rb +58 -0
  134. data/mkbrut/lib/mkbrut/ops/insert_route.rb +52 -0
  135. data/mkbrut/lib/mkbrut/ops/mkdir.rb +13 -0
  136. data/mkbrut/lib/mkbrut/ops/prism_parsing_op.rb +70 -0
  137. data/mkbrut/lib/mkbrut/ops/render_template.rb +26 -0
  138. data/mkbrut/lib/mkbrut/ops/skip_file.rb +10 -0
  139. data/mkbrut/lib/mkbrut/ops.rb +16 -0
  140. data/mkbrut/lib/mkbrut/organization.rb +5 -0
  141. data/mkbrut/lib/mkbrut/prefix.rb +26 -0
  142. data/mkbrut/lib/mkbrut/prefixed_io.rb +16 -0
  143. data/mkbrut/lib/mkbrut/segments/bare_bones.rb +185 -0
  144. data/mkbrut/lib/mkbrut/segments/demo.rb +121 -0
  145. data/mkbrut/lib/mkbrut/segments/heroku.rb +30 -0
  146. data/mkbrut/lib/mkbrut/segments/sidekiq.rb +3 -0
  147. data/mkbrut/lib/mkbrut/segments.rb +8 -0
  148. data/mkbrut/lib/mkbrut/version.rb +3 -0
  149. data/mkbrut/lib/mkbrut/versions.rb +13 -0
  150. data/mkbrut/lib/mkbrut.rb +18 -0
  151. data/mkbrut/mkbrut.gemspec +32 -0
  152. data/mkbrut/templates/Base/.dockerignore +25 -0
  153. data/mkbrut/templates/Base/.env.development.erb +60 -0
  154. data/mkbrut/templates/Base/.env.test.erb +8 -0
  155. data/mkbrut/templates/Base/.gitignore +31 -0
  156. data/mkbrut/templates/Base/.projections.json +59 -0
  157. data/mkbrut/templates/Base/Dockerfile.dx +205 -0
  158. data/mkbrut/templates/Base/Gemfile.erb +53 -0
  159. data/mkbrut/templates/Base/Procfile.development +5 -0
  160. data/mkbrut/templates/Base/Procfile.test +1 -0
  161. data/mkbrut/templates/Base/README.md +4 -0
  162. data/mkbrut/templates/Base/README.md.erb +40 -0
  163. data/mkbrut/templates/Base/app/bootstrap.rb +61 -0
  164. data/mkbrut/templates/Base/app/config/i18n/en/1_defaults.rb +128 -0
  165. data/mkbrut/templates/Base/app/config/i18n/en/2_app.rb +24 -0
  166. data/mkbrut/templates/Base/app/public/static/manifest.json.erb +33 -0
  167. data/mkbrut/templates/Base/app/src/app.rb.erb +37 -0
  168. data/mkbrut/templates/Base/app/src/back_end/data_models/app_data_model.rb +5 -0
  169. data/mkbrut/templates/Base/app/src/back_end/data_models/db.rb +19 -0
  170. data/mkbrut/templates/Base/app/src/back_end/data_models/migrations/20240101130000_citext.rb +6 -0
  171. data/mkbrut/templates/Base/app/src/back_end/data_models/seed/seed_data.rb +9 -0
  172. data/mkbrut/templates/Base/app/src/front_end/components/app_component.rb +8 -0
  173. data/mkbrut/templates/Base/app/src/front_end/components/custom_element_registration.rb.erb +7 -0
  174. data/mkbrut/templates/Base/app/src/front_end/css/index.css +2 -0
  175. data/mkbrut/templates/Base/app/src/front_end/css/svgs.css +12 -0
  176. data/mkbrut/templates/Base/app/src/front_end/forms/app_form.rb +4 -0
  177. data/mkbrut/templates/Base/app/src/front_end/handlers/app_handler.rb +4 -0
  178. data/mkbrut/templates/Base/app/src/front_end/images/LogoPylon.png +0 -0
  179. data/mkbrut/templates/Base/app/src/front_end/images/LogoTransit.png +0 -0
  180. data/mkbrut/templates/Base/app/src/front_end/images/apple-touch-icon-120x120.png +0 -0
  181. data/mkbrut/templates/Base/app/src/front_end/images/apple-touch-icon-152x152.png +0 -0
  182. data/mkbrut/templates/Base/app/src/front_end/images/apple-touch-icon-167x167.png +0 -0
  183. data/mkbrut/templates/Base/app/src/front_end/images/apple-touch-icon-180x180.png +0 -0
  184. data/mkbrut/templates/Base/app/src/front_end/images/favicon.ico +0 -0
  185. data/mkbrut/templates/Base/app/src/front_end/images/icon.png +0 -0
  186. data/mkbrut/templates/Base/app/src/front_end/images/mkicons.sh +6 -0
  187. data/mkbrut/templates/Base/app/src/front_end/js/index.js +6 -0
  188. data/mkbrut/templates/Base/app/src/front_end/layouts/default_layout.rb.erb +73 -0
  189. data/mkbrut/templates/Base/app/src/front_end/pages/app_page.rb +11 -0
  190. data/mkbrut/templates/Base/app/src/front_end/pages/home_page.rb +62 -0
  191. data/mkbrut/templates/Base/app/src/front_end/support/app_session.rb +6 -0
  192. data/mkbrut/templates/Base/app/src/front_end/svgs/README.md +5 -0
  193. data/mkbrut/templates/Base/app/src/front_end/svgs/comment-button.svg +59 -0
  194. data/mkbrut/templates/Base/bin/README.md.erb +5 -0
  195. data/mkbrut/templates/Base/bin/build-assets +7 -0
  196. data/mkbrut/templates/Base/bin/ci +39 -0
  197. data/mkbrut/templates/Base/bin/console +31 -0
  198. data/mkbrut/templates/Base/bin/db +9 -0
  199. data/mkbrut/templates/Base/bin/dbconsole +51 -0
  200. data/mkbrut/templates/Base/bin/dev +25 -0
  201. data/mkbrut/templates/Base/bin/release +26 -0
  202. data/mkbrut/templates/Base/bin/run +86 -0
  203. data/mkbrut/templates/Base/bin/scaffold +9 -0
  204. data/mkbrut/templates/Base/bin/setup +256 -0
  205. data/mkbrut/templates/Base/bin/startup-message +65 -0
  206. data/mkbrut/templates/Base/bin/test +9 -0
  207. data/mkbrut/templates/Base/bin/test-server +29 -0
  208. data/mkbrut/templates/Base/bin/watch-and-build-assets +37 -0
  209. data/mkbrut/templates/Base/config.ru +16 -0
  210. data/mkbrut/templates/Base/docker-compose.dx.yml +92 -0
  211. data/mkbrut/templates/Base/dx/README.md +28 -0
  212. data/mkbrut/templates/Base/dx/bash_customizations +12 -0
  213. data/mkbrut/templates/Base/dx/bash_customizations.local +8 -0
  214. data/mkbrut/templates/Base/dx/build +107 -0
  215. data/mkbrut/templates/Base/dx/docker-compose.env.erb +25 -0
  216. data/mkbrut/templates/Base/dx/dx.sh.lib +137 -0
  217. data/mkbrut/templates/Base/dx/exec +68 -0
  218. data/mkbrut/templates/Base/dx/prune +19 -0
  219. data/mkbrut/templates/Base/dx/show-help-in-app-container-then-wait.sh +38 -0
  220. data/mkbrut/templates/Base/dx/start +30 -0
  221. data/mkbrut/templates/Base/dx/stop +23 -0
  222. data/mkbrut/templates/Base/package.json.erb +37 -0
  223. data/mkbrut/templates/Base/puma.config.rb +53 -0
  224. data/mkbrut/templates/Base/specs/e2e/home_page.spec.rb.erb +23 -0
  225. data/mkbrut/templates/Base/specs/front_end/js/SpecHelper.js +24 -0
  226. data/mkbrut/templates/Base/specs/front_end/pages/home_page.spec.rb +22 -0
  227. data/mkbrut/templates/Base/specs/lint_factories.spec.rb +7 -0
  228. data/mkbrut/templates/Base/specs/spec_helper.rb +78 -0
  229. data/mkbrut/templates/Base/specs/support.rb +2 -0
  230. data/mkbrut/templates/segments/BareBones/app/src/front_end/handlers/trigger_exception_handler.rb +24 -0
  231. data/mkbrut/templates/segments/BareBones/app/src/front_end/js/Example.js.erb +49 -0
  232. data/mkbrut/templates/segments/BareBones/specs/front_end/handlers/trigger_exception_handler.spec.rb +41 -0
  233. data/mkbrut/templates/segments/BareBones/specs/front_end/js/Example.spec.js.erb +38 -0
  234. data/mkbrut/templates/segments/Demo/app/src/back_end/data_models/db/guestbook_message.rb +3 -0
  235. data/mkbrut/templates/segments/Demo/app/src/back_end/data_models/migrations/20250628194124_guestbook.rb +14 -0
  236. data/mkbrut/templates/segments/Demo/app/src/front_end/components/flash_component.rb +36 -0
  237. data/mkbrut/templates/segments/Demo/app/src/front_end/css/constraint-violations.css +18 -0
  238. data/mkbrut/templates/segments/Demo/app/src/front_end/css/fonts.css +19 -0
  239. data/mkbrut/templates/segments/Demo/app/src/front_end/fonts/monaspace-xenon.ttf +0 -0
  240. data/mkbrut/templates/segments/Demo/app/src/front_end/forms/guestbook_message_form.rb +4 -0
  241. data/mkbrut/templates/segments/Demo/app/src/front_end/handlers/guestbook_message_handler.rb +64 -0
  242. data/mkbrut/templates/segments/Demo/app/src/front_end/pages/guestbook_page/message_component.rb +41 -0
  243. data/mkbrut/templates/segments/Demo/app/src/front_end/pages/guestbook_page.rb +43 -0
  244. data/mkbrut/templates/segments/Demo/app/src/front_end/pages/new_guestbook_message_page.rb +64 -0
  245. data/mkbrut/templates/segments/Demo/specs/back_end/data_models/db/guestbook_message.spec.rb +5 -0
  246. data/mkbrut/templates/segments/Demo/specs/e2e/guest_message.spec.rb +54 -0
  247. data/mkbrut/templates/segments/Demo/specs/factories/db/guestbook_message.factory.rb +7 -0
  248. data/mkbrut/templates/segments/Demo/specs/front_end/components/flash_component.spec.rb +5 -0
  249. data/mkbrut/templates/segments/Demo/specs/front_end/handlers/guestbook_message_handler.spec.rb +122 -0
  250. data/mkbrut/templates/segments/Demo/specs/front_end/pages/guestbook_page/message_component.spec.rb +5 -0
  251. data/mkbrut/templates/segments/Demo/specs/front_end/pages/guestbook_page.spec.rb +52 -0
  252. data/mkbrut/templates/segments/Demo/specs/front_end/pages/new_guestbook_message_page.spec.rb +5 -0
  253. data/mkbrut/templates/segments/Heroku/bin/deploy +11 -0
  254. data/mkbrut/templates/segments/Heroku/deploy/Dockerfile +125 -0
  255. data/mkbrut/templates/segments/Heroku/deploy/docker-entrypoint +15 -0
  256. data/mkbrut/templates/segments/Heroku/deploy/heroku_config.rb +26 -0
  257. metadata +185 -21
  258. data/docs/assets/chunks/@localSearchIndexroot.COP2Bcmp.js +0 -1
  259. data/docs/assets/overview.md.iMnwLO4x.js +0 -1
  260. data/docs/assets/overview.md.iMnwLO4x.lean.js +0 -1
  261. data/docs/assets/tutorial.md.BYXj4cOu.js +0 -1
  262. data/docs/assets/tutorial.md.BYXj4cOu.lean.js +0 -1
  263. /data/docs/assets/{components.md.iLiv2E9X.lean.js → components.md.DHh-NwKs.lean.js} +0 -0
  264. /data/docs/assets/{configuration.md.DmuAdsli.lean.js → configuration.md.D8Wz3oJU.lean.js} +0 -0
  265. /data/docs/assets/{forms.md.D8aa_qI-.lean.js → forms.md.BRE85eju.lean.js} +0 -0
@@ -0,0 +1,65 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "pathname"
4
+ require "yaml"
5
+ require "optparse"
6
+
7
+ option_parser = OptionParser.new do |opts|
8
+ opts.banner = "Usage: bin/startup-message\n\n Outputs a message about where the dev server is running\n\nENVIRONMENT VARIABLES\n\n PORT - the port configured for bin/run"
9
+ end
10
+ option_parser.parse!
11
+
12
+ docker_compose_file = Pathname.new(__FILE__).dirname / ".." / "docker-compose.dx.yml"
13
+
14
+ port_config = nil
15
+ error = nil
16
+
17
+ if docker_compose_file.exist?
18
+ docker_compose = YAML.load_file(docker_compose_file)
19
+
20
+ docker_port = begin
21
+ ENV.fetch("PORT")
22
+ rescue KeyError
23
+ $stderr.puts "ERROR: The PORT environment variable is not set."
24
+ $stderr.puts " Please set it to the port your app is running on."
25
+ exit 1
26
+ end
27
+
28
+ ports_config = docker_compose.dig("services", "app", "ports")
29
+ error = nil
30
+ if ports_config
31
+ port_config = ports_config.detect { |port_mapping|
32
+ host_port, container_port = port_mapping.split(":")
33
+ container_port.to_s == docker_port
34
+ }
35
+ if !port_config
36
+ error = "#{docker_compose_file} does not expose the port #{docker_port} for the 'app' service."
37
+ end
38
+ else
39
+ error = "#{docker_compose_file} does not contain a 'ports' section for the 'app' service."
40
+ end
41
+ else
42
+ error = "#{docker_compose_file} does not exist. This is assumed to be in placefor your dev environment"
43
+ end
44
+ if !error
45
+ sleep 2 # allow all other initial output from bin/dev to happen first
46
+
47
+ host_port = port_config.split(":")[0]
48
+
49
+ url = "http://localhost:#{host_port}"
50
+
51
+ $stdout.puts "Your app is now running at"
52
+ $stdout.puts
53
+ $stdout.puts " #{url}"
54
+ $stdout.puts
55
+ $stdout.flush # ensure this output happens immediately
56
+ else
57
+ $stderr.puts "WARN: #{$0} could not figure out what port the app is exposed on"
58
+ $stderr.puts
59
+ $stderr.puts " #{error}"
60
+ $stderr.puts
61
+ $stderr.puts " This won't stop your app from running, but it does mean"
62
+ $stderr.puts " there is some issue with your dev environment"
63
+ $stderr.flush # ensure this error output happens immediately
64
+ end
65
+ sleep
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler"
4
+ Bundler.require
5
+ require "pathname"
6
+
7
+ require "brut/cli/apps/test"
8
+ exit Brut::CLI.app(Brut::CLI::Apps::Test,project_root: Pathname($0).dirname / "..")
9
+
@@ -0,0 +1,29 @@
1
+ #!/bin/bash
2
+
3
+ set -e
4
+ SCRIPT_DIR=$( cd -- "$( dirname -- "${0}" )" > /dev/null 2>&1 && pwd )
5
+ ROOT_DIR="${SCRIPT_DIR}/.."
6
+
7
+ usage() {
8
+ echo "Usage: $0"
9
+ echo
10
+ echo " Run the app in the test environment, suitable for end-to-end tests"
11
+ echo " This will build all assets first, but not rebuild or reload after that"
12
+ echo
13
+ }
14
+
15
+ for arg in "$@"; do
16
+ if [ "${arg}" = "-h" ] || [ "${arg}" = "--help" ] || [ "${arg}" = "help" ]; then
17
+ usage
18
+ exit 0
19
+ fi
20
+ done
21
+
22
+ RACK_ENV="test"
23
+ export RACK_ENV
24
+ echo "[ bin/test-server ] Building assets"
25
+ "${SCRIPT_DIR}"/build-assets
26
+
27
+ echo "[ bin/test-server ] Starting server"
28
+ PORT=6503 bin/run &
29
+ wait
@@ -0,0 +1,37 @@
1
+ #!/usr/bin/env bash
2
+
3
+ # This script exists to ensure that chokidar actually terminates when foreman stops all the processes.
4
+ # For whatever reason, if Procfile has the npx chokidar… invocation below, these processes are not
5
+ # stopped. Putting them into this script addresses that. Cool.
6
+
7
+ set -e
8
+
9
+ if [ -z "${1}" ] ; then
10
+ echo "[ $0 ] error: asset type required. Must be css, js, or images"
11
+ exit 65
12
+ fi
13
+ usage() {
14
+ echo "usage: $0 asset_type"
15
+ echo
16
+ echo " Sets up a watching/rebuild command for the given asset. Useful in dev only"
17
+ echo
18
+ echo " asset_types:"
19
+ echo
20
+ echo " - css"
21
+ echo " - js"
22
+ echo " - images"
23
+ echo
24
+ }
25
+ for arg in "${@}"; do
26
+ if [ "${arg}" = "-h" ] || [ "${arg}" = "--help" ] || [ "${arg}" = "help" ]; then
27
+ usage
28
+ exit 0
29
+ fi
30
+ done
31
+
32
+ asset_type=$1
33
+ watch_dir="app/src/front_end/${asset_type}/**/*"
34
+ build_command="bin/build-assets ${asset_type}"
35
+
36
+ export SHELL # chokidar needs this for reasons unknown to me
37
+ exec npx chokidar --initial --polling --command "${build_command}" "${watch_dir}"
@@ -0,0 +1,16 @@
1
+ require_relative "app/bootstrap"
2
+ bootstrap = Bootstrap.new.bootstrap!
3
+
4
+ app = Rack::Builder.app do
5
+ use Rack::Session::Cookie,
6
+ key: "rack.session",
7
+ path: "/",
8
+ expire_after: 31_536_000,
9
+ same_site: :lax, # this allows links from other domains to send our cookies to us,
10
+ # but only if such links are direct/obvious to the user.
11
+ secret: ENV.fetch("SESSION_SECRET")
12
+
13
+ run bootstrap.rack_app
14
+ end
15
+ run app
16
+
@@ -0,0 +1,92 @@
1
+ # This file is heavily documented to explain what is going on and why.
2
+ # If you are reading this, however, you now own this file and can
3
+ # change it how you like. Just be sure you understand how it works in the
4
+ # context of Dockerfile.dx and the files in dx/,
5
+ # which are referred to as the Workspace
6
+ #
7
+ # This file is used to run several Docker containers, based on images, together
8
+ # in a shared private network. All containers run by this file will be able to see
9
+ # each other over a network, but the only access your host (computer) will have to
10
+ # these services is via explicitly exposed ports.
11
+
12
+ services:
13
+ # 'app' is the service where all your dev tools will run. The tests will
14
+ # run in here as will the dev server.
15
+ #
16
+ # You interact with this container via `dx/exec`
17
+ app:
18
+ # IMAGE is defined in dx/docker-compose.env and is the name of an image
19
+ # built LOCALLY (never pushed to DockerHub) that has all the tools needed
20
+ # for developing your app installed into it.
21
+ image: ${IMAGE}
22
+ # This is magic that makes the Docker container stop much more quickly when
23
+ # you hit Ctrl-C.
24
+ init: true
25
+ # There are two types of volumes (drives) mounted.
26
+ volumes:
27
+ # This volume is your source code. Its source and target are the same
28
+ # so that stuff like language servers can work.
29
+ - type: bind
30
+ source: ${PWD}
31
+ target: ${PWD}
32
+ consistency: "consistent"
33
+ # This allows docker build commands run inside Docker, referred to
34
+ # as "docker out of docker". Essentially, Docker binaries installed into
35
+ # this container will access your host (you computer)'s Docker install.
36
+ # This is to allow it to build the production images.
37
+ - type: bind
38
+ source: "/var/run/docker.sock"
39
+ target: "/var/run/docker.sock"
40
+ # This is what is started up when you run `dx/start`. It just
41
+ # waits forever keeping the container running.
42
+ command: /home/appuser/show-help-in-app-container-then-wait.sh
43
+ # The working directory for any command, this is the same as your
44
+ # project root.
45
+ working_dir: ${PWD}
46
+ # This sets environment variables
47
+ environment:
48
+ # Because we'll install RubyGems inside our project (mostly to allow
49
+ # language servers to work), configuration in bash_customizations relies
50
+ # on this value being set
51
+ PROJECT_ROOT: ${PWD}
52
+ # This exposes ports from this running container to your localhost.
53
+ # In this case, we expose 6502, where the app runs, and 6503, where
54
+ # the test server runs.
55
+ #
56
+ # To change this, the port before the colon is where the app
57
+ # will be available on your localhost. The port AFTER the colon is where
58
+ # the app runs in the container. So: change the port before the colon, and
59
+ # don't change the one after.
60
+ #
61
+ # * 6502 - dev
62
+ # * 6503 - test server (run by E2E tests)
63
+ ports:
64
+ - "6502:6502"
65
+ - "6503:6503"
66
+ # This "service" runs Postgres. The service name ("postgres") is the host
67
+ # on which the service is available to other containers, namely from the
68
+ # app container. You cannot connect to thise service from your host (computer)
69
+ # directly without additional configuration.
70
+ postgres:
71
+ # This image is the name of the image from DockerHub that will be pulled
72
+ # down to run Postgres.
73
+ image: postgres:16.4
74
+ pull_policy: "missing"
75
+ environment:
76
+ # Postgres will not work without being told the password. The most
77
+ # direct way to do that is via this environment variable.
78
+ POSTGRES_PASSWORD: postgres
79
+ # This runes otel-deskop-viewer, which will receive OpenTelemetry traces
80
+ # from your app. You can connect to this to observe those traces.
81
+ otel-desktop-viewer:
82
+ # The otel-desktop-viewer maintainers do not maintain a Docker
83
+ # image of the app. I have made one available on my DockerHub account.
84
+ # If you want to build your own, see
85
+ # https://github.com/CtrlSpice/otel-desktop-viewer?tab=readme-ov-file#via-docker
86
+ # When you build that image, change the value for image: below to the image
87
+ # name you chose.
88
+ image: davetron5000/otel-desktop-viewer:alpine-3
89
+ # This runs internally on port 8000 but will be available
90
+ # on port 6504 of your machine.
91
+ ports:
92
+ - "6504:8000"
@@ -0,0 +1,28 @@
1
+ # Workspace Commands
2
+
3
+ These commands manage the Workspace, AKA the part of the development environment that
4
+ runs on your computer (sometimes called the *host*).
5
+
6
+ These are all written in Bash as that is the only environment that can be relied upon to exist on any operating system.
7
+
8
+ These are designed to be somewhat agnostic of your app and Brut may update some of
9
+ these files if changes are needed. All app-specific configuration is consolidated
10
+ into just a few files.
11
+
12
+ These files are owned by Brut and you should avoid editing them:
13
+
14
+ * `bash_customizations` - Bash configuration
15
+ * `build` - Builds the development docker image
16
+ * `dx.sh.lib` - Shared bash functions
17
+ * `exec` - Run commands inside a container
18
+ * `prune` - Remove unused containers
19
+ * `README.md` - This file
20
+ * `show-help-in-app-container-then-wait.sh` - Runs inside the app container to keep
21
+ the container up
22
+ * `start` - starts the Workspace/dev environment
23
+ * `stop` - stops the Workspace/dev environment
24
+
25
+ These files contain project-specific information and Brut will not change them:
26
+
27
+ * `docker-compose.env` - Configuration values.
28
+ * `bash_customizations.local` - Per-developer bash configuration, **project specific**, **developer specific**, **do not check into version control**.
@@ -0,0 +1,12 @@
1
+ # This sets up RubyGems and Bundler so they install
2
+ # all gems inside this project's root and not into the system
3
+ # location. This allows LSP servers to work more easily.
4
+ if [ ! -n "$PROJECT_ROOT" ]; then
5
+ echo "[ bash_customizations ] WARNING: PROJECT_ROOT is not set - this will break RubyGems"
6
+ fi
7
+ export GEM_HOME=${PROJECT_ROOT}/local-gems/gem-home
8
+ export PATH=${PATH}:${GEM_HOME}/bin
9
+ # This sets up the Node version so we don't have to do it before every. single.
10
+ # shell. invocation.
11
+ . ~/.nvm/nvm.sh
12
+ nvm use default
@@ -0,0 +1,8 @@
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
+
6
+ # Sets up a multi-line prompt since the working directory
7
+ # may be very deep. Customize or change at your leisure.
8
+ PS1='\[\e[35m\]docker-container\[\e[0m\] - \[\e[37m\]\w\n\[\e[0m\]> '
@@ -0,0 +1,107 @@
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
+ read_custom_build_args() {
10
+ build_args_file="${SCRIPT_DIR}/build.args"
11
+ if [ -e "${build_args_file}" ]; then
12
+ for arg in `grep -v '^#' "${build_args_file}"`; do
13
+ BUILD_ARGS+=(--build-arg ${arg})
14
+ done
15
+ fi
16
+ }
17
+
18
+ setup_local_user_build_args() {
19
+ require_command "id"
20
+ require_command "uname"
21
+ user_uid=$(id -u)
22
+ user_gid=$(id -g)
23
+ docker_gid=
24
+ sadly_user_must_be_added_to_root=
25
+ OS=$(uname)
26
+ if [ "${OS}" == "Darwin" ] ; then
27
+ docker_gid=$(stat -f %g /var/run/docker.sock)
28
+ sadly_user_must_be_added_to_root="0,"
29
+ else
30
+ if [ "${OS}" == "Linux" ] ; then
31
+ docker_gid=$(stat -c %g /var/run/docker.sock)
32
+ else
33
+ log "Could not determine OS, which is needed to know how to invoke stat to figure out the group id of /var/run/docker.sock"
34
+ log "Docker commands will not work"
35
+ fi
36
+ fi
37
+ echo user_uid=${user_uid} >> "${SCRIPT_DIR}"/build.args
38
+ echo user_gid=${user_gid} >> "${SCRIPT_DIR}"/build.args
39
+ echo docker_gid=${docker_gid} >> "${SCRIPT_DIR}"/build.args
40
+ echo sadly_user_must_be_added_to_root=${sadly_user_must_be_added_to_root} >> "${SCRIPT_DIR}"/build.args
41
+ }
42
+
43
+ initialize_build_args() {
44
+ echo "# This is generated - see build.pre" > "${SCRIPT_DIR}"/build.args
45
+ }
46
+
47
+ setup_playright_build_args() {
48
+ require_command "grep"
49
+ require_command "sed"
50
+
51
+ if [ ! -e "${SCRIPT_DIR}"/Gemfile.lock ]; then
52
+ log "Could not find Gemfile.lock, which is needed to determine the playwright-ruby-client version"
53
+ log "Assuming your app is brand-new, this should be OK"
54
+ echo "# When this file was created, there was no Gemfile.lock, so" >> "${SCRIPT_DIR}"/build.args
55
+ echo "# it was not possible to determine which version of Playright was set up." >> "${SCRIPT_DIR}"/build.args
56
+ echo "# Once you've build your app and installed gems, you are " >> "${SCRIPT_DIR}"/build.args
57
+ echo "# encouraged to re-run \`dx/build\` to address this issue." >> "${SCRIPT_DIR}"/build.args
58
+ echo PLAYWRIGHT_VERSION=latest >> "${SCRIPT_DIR}"/build.args
59
+ else
60
+ PLAYWRIGHT_VERSION=$(grep playwright-ruby-client Gemfile.lock | grep '(' | sed 's/^.*(//' | sed 's/).*$//' | grep -v ^=)
61
+ if [ -z "${PLAYWRIGHT_VERSION}" ]; then
62
+ log "Could not find precise version of playwright-ruby-client from Gemfile.lock"
63
+ log "This means that your playwright-ruby-client version and playwright NPM modules may be out of sync and may not work"
64
+ echo "# When this file was created, Gemfile.lock did not" >> "${SCRIPT_DIR}"/build.args
65
+ echo "# contain playwrith-ruby-client. This means it" >> "${SCRIPT_DIR}"/build.args
66
+ echo "# it was not possible to determine which version of" >> "${SCRIPT_DIR}"/build.args
67
+ echo "# Playright was set up. If you aren't using Playwright," >> "${SCRIPT_DIR}"/build.args
68
+ echo "# that's fine, this won't cause issues" >> "${SCRIPT_DIR}"/build.args
69
+ echo "# If you ARE using Playwright, something may be wrong with your setup" >> "${SCRIPT_DIR}"/build.args
70
+ echo PLAYWRIGHT_VERSION=latest >> "${SCRIPT_DIR}"/build.args
71
+ else
72
+ echo PLAYWRIGHT_VERSION=${PLAYWRIGHT_VERSION} >> "${SCRIPT_DIR}"/build.args
73
+ fi
74
+ fi
75
+ }
76
+
77
+ require_command "docker"
78
+ load_docker_compose_env
79
+
80
+ usage_on_help "Builds the Docker image based on the Dockerfile" "" "build.pre" "build.post" "${@}"
81
+
82
+ if ! exec_hook_if_exists "build.pre" Dockerfile.dx "${IMAGE}"; then
83
+ log "build.pre failed"
84
+ exit 1
85
+ fi
86
+ BUILD_ARGS=()
87
+
88
+ initialize_build_args
89
+ setup_local_user_build_args
90
+ setup_playright_build_args
91
+ read_custom_build_args
92
+
93
+ docker build \
94
+ --file Dockerfile.dx \
95
+ --tag "${IMAGE}" \
96
+ ${BUILD_ARGS[@]} \
97
+ ./
98
+
99
+ if ! exec_hook_if_exists "build.post" Dockerfile.dx "${IMAGE}"; then
100
+ log "build.pre failed"
101
+ exit 1
102
+ fi
103
+
104
+ log "🌈" "Your Docker image has been built tagged '${IMAGE}'"
105
+ log "🔄" "You can now run dx/start to start it up, though you may need to stop it first with Ctrl-C"
106
+
107
+ # vim: ft=bash
@@ -0,0 +1,25 @@
1
+ # This file is an input to Docker Compose, and serves
2
+ # as an additional set of environment variables.
3
+
4
+ # IMAGE is the name of the image to be built for running
5
+ # your app. The recommended format is ORG/REPO:TAG
6
+ #
7
+ # The ORG and REPO are based on the values used
8
+ # with mkbrut. TAG is set to mimic the value
9
+ # in the FROM directive of the Dockerfile.
10
+ # This will allow you to upgrade Ruby and run both
11
+ # versions more easily.
12
+ IMAGE=<%= organization %>/<%= app_name %>:ruby-3.4
13
+
14
+ # PROJECT_NAME is used when running Docker compose
15
+ # to help ensure the output includes your project's name.
16
+ PROJECT_NAME=<%= app_name %>
17
+
18
+ # DEFAULT_SERVICE is the name of the service (keys under
19
+ # service: in docker-compose.dx.yml) to use when
20
+ # running dx/exec and no service is specified.
21
+ # The value "app" should match that in docker-compose.dx.yml
22
+ # having the effect of dx/exec running commands inside your app's
23
+ # container.
24
+ DEFAULT_SERVICE=app
25
+ # vim: ft=bash
@@ -0,0 +1,137 @@
1
+ # shellcheck shell=bash
2
+
3
+ # These are various functions needed by all the other scripts in dx/
4
+
5
+ # Log a fatal error and exit nonzero
6
+ fatal() {
7
+ remainder=${*:2}
8
+ if [ -z "$remainder" ]; then
9
+ log "🛑" "${@}"
10
+ else
11
+ log "${@}"
12
+ fi
13
+ exit 1
14
+ }
15
+
16
+ # Log an informational message. This is preferable to simply
17
+ # using `echo` because it prepends the message with the name
18
+ # of the script so you can tell where the message came from.
19
+ log() {
20
+ emoji=$1
21
+ remainder=${*:2}
22
+ if [ -z "${NO_EMOJI}" ]; then
23
+ echo "[ ${0} ] ${*}"
24
+ else
25
+ # if remainder is empty that means no emoji was passed
26
+ if [ -z "$remainder" ]; then
27
+ echo "[ ${0} ] ${*}"
28
+ else # emoji was passed, but we ignore it
29
+ echo "[ ${0} ] ${remainder}"
30
+ fi
31
+ fi
32
+ }
33
+
34
+ # Output a debug message if BRUT_WORKSPACE_DEBUG is set
35
+ debug() {
36
+ message=$1
37
+ if [ -z "${BRUT_WORKSPACE_DEBUG}" ]; then
38
+ return
39
+ fi
40
+ log "🐛" "${message}"
41
+ }
42
+
43
+ # Output general usage information for the command and exit zero.
44
+ #
45
+ # Args:
46
+ #
47
+ # [1] the description of the command
48
+ # [2] The names of the arguments
49
+ # [3] If given, the name of the pre hook that the command responds to
50
+ # [4] If given, the name of the post hook that the command responds to
51
+ usage() {
52
+ description=$1
53
+ arg_names=$2
54
+ pre_hook=$3
55
+ post_hook=$4
56
+ echo "usage: ${0} [-h] ${arg_names}"
57
+ if [ -n "${description}" ]; then
58
+ echo
59
+ echo "DESCRIPTION"
60
+ echo " ${description}"
61
+ fi
62
+ if [ -n "${pre_hook}" ] || [ -n "${post_hook}" ]; then
63
+ echo
64
+ echo "HOOKS"
65
+ if [ -n "${pre_hook}" ]; then
66
+ echo " ${pre_hook} - if present, called before the main action"
67
+ fi
68
+ if [ -n "${post_hook}" ]; then
69
+ echo " ${post_hook} - if present, called after the main action"
70
+ fi
71
+ fi
72
+ exit 0
73
+ }
74
+
75
+ # Show usage if the command line invocation indicated that help was requested.
76
+ # This calls usage, so this will exit 0
77
+ #
78
+ # Args:
79
+ #
80
+ # [1] the description of the command
81
+ # [2] The names of the arguments
82
+ # [3] If given, the name of the pre hook that the command responds to
83
+ # [4] If given, the name of the post hook that the command responds to
84
+ usage_on_help() {
85
+ description=$1
86
+ arg_names=$2
87
+ pre_hook=$3
88
+ post_hook=$4
89
+ # These are the args passed to the invocation so this
90
+ # function can determine if the user requested help
91
+ cli_args=( "${@:5}" )
92
+
93
+ for arg in "${cli_args[@]}"; do
94
+ if [ "${arg}" = "-h" ] || [ "${arg}" = "--help" ]; then
95
+ usage "${description}" "${arg_names}" "${pre_hook}" "${post_hook}"
96
+ fi
97
+ done
98
+ }
99
+
100
+ # Ensure that a command line utility exists, exiting nonzero if not.
101
+ # This is needed to avoid assumptions about what commands a user may have
102
+ # on their computer. It attempts to provide a more useful error message
103
+ # than "command not found".
104
+ require_command() {
105
+ command_name=$1
106
+ if ! command -v "${command_name}" >/dev/null 2>&1; then
107
+ fatal "Command '${command_name}' not found - it is required for this script to run"
108
+ fi
109
+ }
110
+
111
+ # This loads dx/docker-compose.env as variables to the bash script, thus
112
+ # allowing Docker Compose and these scripts to share configuration.
113
+ load_docker_compose_env() {
114
+ . "${ENV_FILE}"
115
+ }
116
+
117
+ # Execute a hook if the file exists. Note that if the hook exits
118
+ # nonzero, the caller will need to check the return value of this function and decide
119
+ # what to do.
120
+ exec_hook_if_exists() {
121
+ script_name=$1
122
+ shift
123
+ if [ -x "${SCRIPT_DIR}"/"${script_name}" ]; then
124
+ log "🪝" "${script_name} exists - executing"
125
+ "${SCRIPT_DIR}"/"${script_name}" "${@}"
126
+ else
127
+ debug "${script_name} does not exist"
128
+ fi
129
+ }
130
+
131
+ require_command "realpath"
132
+ require_command "cat"
133
+
134
+ # Set up the location to the docker-compose.env file.
135
+ ENV_FILE=$(realpath "${SCRIPT_DIR}")/docker-compose.env
136
+
137
+ # vim: ft=bash
@@ -0,0 +1,68 @@
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. Any command other than 'bash' will be run with bash -lc. Use -B to just run the command directly."
13
+ usage_args="[-s service] [-B] command"
14
+ usage_pre="exec.pre"
15
+ usage_on_help "${usage_description}" "${usage_args}" "${usage_pre}" "" "${@}"
16
+
17
+ SERVICE="${SERVICE_NAME:-${DEFAULT_SERVICE}}"
18
+ INCLUDE_PREFIX_FOR_NON_BASH=true
19
+ while getopts "s:B" opt "${@}"; do
20
+ case ${opt} in
21
+ s )
22
+ SERVICE="${OPTARG}"
23
+ ;;
24
+ B )
25
+ INCLUDE_PREFIX_FOR_NON_BASH=false
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
+ if ! exec_hook_if_exists "exec.pre"; then
45
+ log "build.pre failed"
46
+ exit 1
47
+ fi
48
+
49
+ if [ "$#" -eq 1 ] && [ "$1" = "bash" ]; then
50
+ COMMAND=(bash)
51
+ elif [ "$INCLUDE_PREFIX_FOR_NON_BASH" = "true" ]; then
52
+ COMMAND=(bash -lc "$*")
53
+ else
54
+ COMMAND=("$@")
55
+ fi
56
+
57
+ log "🚂" "Running '${COMMAND[@]}' inside container with service name '${SERVICE}'"
58
+
59
+ docker \
60
+ compose \
61
+ --file docker-compose.dx.yaml \
62
+ --project-name "${PROJECT_NAME}" \
63
+ --env-file "${ENV_FILE}" \
64
+ exec \
65
+ "${SERVICE}" \
66
+ "${COMMAND[@]}"
67
+
68
+ # vim: ft=bash
@@ -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