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,129 @@
1
+ #!/usr/bin/env bash
2
+
3
+ set -e
4
+ SCRIPT_DIR=$( cd -- "$( dirname -- "${0}" )" > /dev/null 2>&1 && pwd )
5
+ ROOT_DIR=$(realpath "$SCRIPT_DIR/..")
6
+
7
+ main() {
8
+ detect_gem_version
9
+ detect_docker_platform
10
+ release_gem
11
+ build_and_maybe_push
12
+ }
13
+
14
+ log() {
15
+ echo "[ mkbrut/bin/publish ] ${*}"
16
+ }
17
+
18
+ usage() {
19
+ echo "Usage: $0"
20
+ echo
21
+ echo " Publish Ruby Gem and Docker image"
22
+ echo
23
+ }
24
+
25
+ release_gem() {
26
+ log "Releasing Gem"
27
+ rake release
28
+ wait_for_rubygems
29
+ }
30
+
31
+ wait_for_rubygems() {
32
+ timeout=30
33
+ interval=2
34
+ elapsed=0
35
+
36
+ log "Waiting ${timeout} seconds for mkbrut $GEM_VERSION to be available on RubyGems"
37
+ while (( elapsed < timeout )); do
38
+ if gem list -r "mkbrut" -a | grep -q "$GEM_VERSION"; then
39
+ log "mkbrut $GEM_VERSION is available on RubyGems"
40
+ return 0
41
+ fi
42
+ sleep $interval
43
+ (( elapsed += interval ))
44
+ done
45
+
46
+ log "mkbrut $GEM_VERSION is not available on RubyGems after $timeout seconds"
47
+ log "Something may have gone wrong. Or, it could just be taking a while"
48
+ log "You can re-run this with -G to bypassing pushing to RubyGems"
49
+ echo
50
+ log " bin/deploy -G"
51
+ exit 1
52
+ }
53
+
54
+ setup_docker_buildx() {
55
+ local builder_name=mkbrut-builder
56
+
57
+ if ! docker buildx ls | grep -q "^$builder_name"; then
58
+ log "Creating Docker buildx builder: $builder_name"
59
+ docker buildx create --name "$builder_name" --use
60
+ docker buildx inspect "$builder_name" --bootstrap
61
+ else
62
+ log "Docker buildx builder $builder_name already exists"
63
+ docker buildx use "$builder_name"
64
+ fi
65
+ }
66
+
67
+ detect_gem_version() {
68
+
69
+ GEM_VERSION=$( rake -T | grep "rake install " | sed 's/^.*mkbrut-//g' | sed 's/\.gem.*$//g')
70
+ if [[ "$GEM_VERSION" =~ ^[0-9]+\.[0-9]\.[0-9]+$ ]]; then
71
+ log "Releasing version ${GEM_VERSION}"
72
+ else
73
+ log "Version '${GEM_VERSION}' does not look like a version: X.Y.Z"
74
+ exit 1
75
+ fi
76
+ }
77
+
78
+ detect_docker_platform() {
79
+
80
+ KERNEL_NAME_FOR_ARCHITECTURE=$(docker info --format '{{.Architecture}}')
81
+ OSTYPE=$(docker info --format '{{.OSType}}')
82
+
83
+ case "$KERNEL_NAME_FOR_ARCHITECTURE" in
84
+ "aarch64")
85
+ DOCKER_NAME_FOR_ARCHITECTURE="arm64"
86
+ ;;
87
+ "x86_64")
88
+ DOCKER_NAME_FOR_ARCHITECTURE="amd64"
89
+ ;;
90
+ *)
91
+ log "Docker reported architecture is $KERNEL_NAME_FOR_ARCHITECTURE - hoping this works"
92
+ ;;
93
+ esac
94
+ HOST_PLATFORM=${OSTYPE}/${DOCKER_NAME_FOR_ARCHITECTURE}
95
+ }
96
+
97
+ build_and_maybe_push() {
98
+
99
+ # See bin/setup for another use of this page
100
+ local docker_credentials_source="${SCRIPT_DIR}/../dx/credentials/dockerhub.credentials"
101
+ setup_docker_buildx
102
+
103
+ . "${docker_credentials_source}"
104
+ log "Loging into DockerHub with credentials from ${docker_credentials_source}"
105
+ docker login --username "${DOCKERHUB_USERNAME}" --password "${DOCKERHUB_PAT}"
106
+
107
+ DOCKER_BUILD_PUSH_OR_LOAD=--push
108
+ DOCKER_PLATFORMS="linux/amd64,linux/arm64"
109
+
110
+ log "Building Docker image and pushing to DockerHub"
111
+
112
+ docker buildx build \
113
+ --platform ${DOCKER_PLATFORMS} \
114
+ ${DOCKER_BUILD_PUSH_OR_LOAD} \
115
+ --no-cache \
116
+ --build-arg brut_version="${GEM_VERSION}" \
117
+ --tag thirdtank/mkbrut:"${GEM_VERSION}" \
118
+ --tag thirdtank/mkbrut:latest \
119
+ deploy
120
+ }
121
+
122
+ for arg in "${@}"; do
123
+ if [ "${arg}" = "-h" ] || [ "${arg}" = "--help" ] || [ "${arg}" = "help" ]; then
124
+ usage
125
+ exit 0
126
+ fi
127
+ done
128
+
129
+ main
data/mkbrut/bin/rake ADDED
@@ -0,0 +1,16 @@
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
+ require "rubygems"
14
+ require "bundler/setup"
15
+
16
+ load Gem.bin_path("rake", "rake")
data/mkbrut/bin/setup ADDED
@@ -0,0 +1,30 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require_relative "../../bin/bin_kit"
4
+ require "optparse"
5
+ require "pathname"
6
+ require "fileutils"
7
+
8
+ def setup(update_gems:)
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
+ end
19
+
20
+
21
+ options = {
22
+ update_gems: false,
23
+ }
24
+ OptionParser.new do |opts|
25
+ opts.on("--update-gems","Update gems get the latest versions consistent with Gemfile / gemspec.") do
26
+ options[:update_gems] = true
27
+ end
28
+ end.parse!
29
+
30
+ setup(**options)
Binary file
@@ -0,0 +1,2 @@
1
+ # Nothing in this repo needs to be put into the Docker image
2
+ *
@@ -0,0 +1,25 @@
1
+ FROM ruby:3.4
2
+
3
+ SHELL [ "/bin/bash", "-o", "pipefail", "-c" ]
4
+
5
+ ENV DEBIAN_FRONTEND=noninteractive
6
+ RUN apt-get -y update
7
+
8
+ # Update RubyGems and Bundler
9
+ RUN echo "gem: --no-document" >> ~/.gemrc && \
10
+ gem update --system
11
+
12
+ ARG brut_version
13
+ RUN gem install --version ${brut_version} mkbrut
14
+
15
+ ARG user_uid=10001
16
+ ARG user_gid=10002
17
+
18
+ # Create the user's group ID if it does not exist
19
+ RUN getent group ${user_gid} || groupadd --gid ${user_gid} appgroup
20
+ RUN useradd --uid ${user_uid} --gid ${user_gid} --create-home --home-dir /home/appuser appuser
21
+
22
+ # NOT including the group here as that will place the user's environment
23
+ # ONLY in that group and not in all the groups in which they are a part.
24
+ USER appuser
25
+
data/mkbrut/exe/mkbrut ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "mkbrut"
4
+
5
+ exit MKBrut::CLI.new(args: ARGV).run
@@ -0,0 +1,79 @@
1
+ require "fileutils"
2
+ require "erb"
3
+ require "ostruct"
4
+
5
+ module MKBrut
6
+ class App
7
+ def initialize(current_dir:, app_options:, out:, err:)
8
+ @out = out
9
+ @app_options = app_options
10
+
11
+ @out.puts "Creating app with these options:\n"
12
+ @out.puts "App name: #{app_options.app_name}"
13
+ @out.puts "App ID: #{app_options.app_id}"
14
+ @out.puts "Prefix: #{app_options.prefix}"
15
+ @out.puts "Organization: #{app_options.organization}"
16
+ @out.puts "Include demo? #{app_options.demo}\n"
17
+
18
+ if app_options.dry_run?
19
+ @out.puts "Dry Run"
20
+ MKBrut::Ops::BaseOp.dry_run = true
21
+ end
22
+
23
+ templates_dir = Pathname(
24
+ Gem::Specification.find_by_name("mkbrut").gem_dir
25
+ ) / "templates"
26
+
27
+ @base = MKBrut::Base.new(
28
+ app_options:,
29
+ current_dir:,
30
+ templates_dir:
31
+ )
32
+ @segments = [
33
+ MKBrut::Segments::BareBones.new(
34
+ app_options:,
35
+ current_dir:,
36
+ templates_dir:,
37
+ )
38
+ ]
39
+ if app_options.demo?
40
+ @segments << MKBrut::Segments::Demo.new(
41
+ app_options:,
42
+ current_dir:,
43
+ templates_dir:
44
+ )
45
+ end
46
+ app_options.segments.each do |segment|
47
+ case segment
48
+ when "heroku"
49
+ @segments << MKBrut::Segments::Heroku.new(
50
+ app_options:,
51
+ current_dir:,
52
+ templates_dir:
53
+ )
54
+ else
55
+ raise "Segment #{segment} is not supported"
56
+ end
57
+ end
58
+ end
59
+
60
+ def create!
61
+ @out.puts "Creating Base app"
62
+ @base.create!
63
+ @segments.each do |segment|
64
+ @out.puts "Creating segment: #{segment.class.friendly_name}"
65
+ segment.add!
66
+ end
67
+ @out.puts "#{@app_options.app_name} was created\n\n"
68
+ @out.puts "Time to get building:"
69
+ @out.puts "1. cd #{@app_options.app_name}"
70
+ @out.puts "2. dx/build"
71
+ @out.puts "3. dx/start"
72
+ @out.puts "4. [ in another terminal ] dx/exec bash"
73
+ @out.puts "5. [ inside the Docker container ] bin/setup"
74
+ @out.puts "6. [ inside the Docker container ] bin/dev"
75
+ @out.puts "7. Visit http://localhost:6502 in your browser"
76
+ @out.puts "8. [ inside the Docker container ] bin/setup help # to see more commands"
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,8 @@
1
+ class MKBrut::AppId < MKBrut::InternetIdentifier
2
+ def self.from_app_name(app_name)
3
+ self.new(app_name.to_s.gsub(/[^a-zA-Z0-9\-]/, "-").downcase)
4
+ end
5
+ def initialize(value)
6
+ super(:app_id, value)
7
+ end
8
+ end
@@ -0,0 +1,29 @@
1
+ module MKBrut
2
+ class AppName
3
+ def initialize(value)
4
+ identifier = value.to_s
5
+ if identifier.empty?
6
+ raise MKBrut::InvalidIdentifier, "app-name is required"
7
+ end
8
+
9
+ if identifier.length > 63
10
+ raise MKBrut::InvalidIdentifier, "app-name cannot be longer than 63 characters"
11
+ end
12
+
13
+ if identifier.start_with?("-") || identifier.end_with?("-")
14
+ raise MKBrut::InvalidIdentifier, "app-name cannot start or end with a hyphen"
15
+ end
16
+
17
+ if identifier.match?(/[^a-zA-Z\-_]/)
18
+ raise MKBrut::InvalidIdentifier, "app-name can only contain letters, hyphens, and underscores"
19
+ end
20
+ if identifier.match?(/__/) || identifier.match?(/--/)
21
+ raise MKBrut::InvalidIdentifier, "app-name can not have repeating underscores or hyphens"
22
+ end
23
+ @identifier = identifier.to_s.gsub(/_/,"-")
24
+ end
25
+
26
+ def to_s = @identifier
27
+ alias to_str to_s
28
+ end
29
+ end
@@ -0,0 +1,36 @@
1
+ class MKBrut::AppOptions
2
+ attr_reader :app_name, :app_id, :prefix, :organization, :demo, :versions, :segments
3
+
4
+ def initialize(
5
+ app_name:,
6
+ app_id: nil,
7
+ prefix: nil,
8
+ dry_run: nil,
9
+ organization: nil,
10
+ demo: false,
11
+ versions: nil,
12
+ **rest
13
+ )
14
+ if app_name.nil?
15
+ raise ArgumentError, "app_name is required"
16
+ end
17
+
18
+ @app_name = app_name
19
+ @app_id = app_id || MKBrut::AppId.from_app_name(@app_name)
20
+ @prefix = prefix || MKBrut::Prefix.from_app_id(@app_id)
21
+ @organization = organization || @app_id
22
+ @dry_run = !!dry_run
23
+ @demo = !!demo
24
+ @versions = versions
25
+ @segments = rest.map { |key,value|
26
+ if key =~ /^segment-(.+)$/ && value
27
+ $1
28
+ else
29
+ nil
30
+ end
31
+ }.compact
32
+ end
33
+
34
+ def dry_run? = @dry_run
35
+ def demo? = @demo
36
+ end
@@ -0,0 +1,57 @@
1
+ require "pathname"
2
+ require "securerandom"
3
+ # Constructs the base of any Brut app.
4
+ class MKBrut::Base
5
+ include MKBrut
6
+
7
+ class ErbBinding < MKBrut::ErbBindingDelegate
8
+ def session_secret = SecureRandom.hex(64)
9
+ end
10
+
11
+ def initialize(app_options:, current_dir:, templates_dir:)
12
+ @project_root = current_dir / app_options.app_name
13
+ @templates_dir = templates_dir / "Base"
14
+ @erb_binding = ErbBinding.new(app_options)
15
+ end
16
+
17
+ def create!
18
+ if @project_root.exist?
19
+ raise "Project root #{@project_root} already exists"
20
+ end
21
+ operations = [ Ops::Mkdir.new(@project_root) ] +
22
+ copy_files(@templates_dir, @project_root)
23
+
24
+ operations.each do |operation|
25
+ operation.call
26
+ end
27
+ end
28
+
29
+ private
30
+
31
+ def filenames_to_always_skip = [ "README.md", "mkicons.sh" ]
32
+
33
+ def copy_files(source_dir, destination_root)
34
+ operations = []
35
+ Dir.glob("#{source_dir}/*", flags: File::FNM_DOTMATCH).each do |template_file|
36
+ template_file = Pathname(template_file)
37
+ if [ ".", ".." ].include?(template_file.basename.to_s)
38
+ next
39
+ end
40
+ if template_file.directory?
41
+ operations << Ops::Mkdir.new(destination_root / template_file.basename)
42
+ operations += copy_files(template_file, destination_root / template_file.basename)
43
+ elsif template_file.extname == ".erb"
44
+ operations << Ops::RenderTemplate.new(
45
+ template_file,
46
+ destination_root:,
47
+ erb_binding: @erb_binding
48
+ )
49
+ elsif filenames_to_always_skip.include?(template_file.basename.to_s)
50
+ operations << Ops::SkipFile.new(template_file)
51
+ else
52
+ operations << Ops::CopyFile.new(template_file, destination_root:)
53
+ end
54
+ end
55
+ operations
56
+ end
57
+ end
@@ -0,0 +1,107 @@
1
+ require "optparse"
2
+ require "pathname"
3
+
4
+ module MKBrut
5
+ class CLI
6
+ def initialize(args:, out: $stdout, err: $stderr)
7
+ @args = args
8
+ @out = out
9
+ @err = err
10
+ end
11
+
12
+ def run
13
+
14
+ app_options = parse_options(@args, MKBrut::Versions.new)
15
+ new_app = MKBrut::App.new(
16
+ current_dir: Pathname.pwd.expand_path,
17
+ app_options:,
18
+ out: PrefixedIO.new(@out, "mkbrut"),
19
+ err: @err
20
+ )
21
+ new_app.create!
22
+ 0
23
+ rescue => e
24
+ @err.puts "Error: #{e.message}"
25
+ if ENV["BRUT_CLI_RAISE_ON_ERROR"] == "true"
26
+ raise
27
+ end
28
+ 1
29
+ end
30
+
31
+ def show_help(versions)
32
+ @out.puts @option_parser
33
+ @out.puts
34
+ @out.puts "ARGUMENTS"
35
+ @out.puts
36
+ @out.puts " app-name - name for your app, which will be the folder where your app's files are created"
37
+ @out.puts
38
+ @out.puts "ENVIRONMENT VARIABLES"
39
+ @out.puts
40
+ @out.puts " BRUT_CLI_RAISE_ON_ERROR - if set to 'true', any error will raise an exception instead of printing to stderr"
41
+ @out.puts
42
+ end
43
+
44
+ private
45
+
46
+
47
+ def parse_options(args, versions)
48
+ options = {}
49
+ @option_parser = OptionParser.new do |opts|
50
+ opts.accept(MKBrut::Prefix) do |prefix|
51
+ MKBrut::Prefix.new(prefix)
52
+ end
53
+ opts.accept(MKBrut::AppId) do |prefix|
54
+ MKBrut::AppId.new(prefix)
55
+ end
56
+ opts.accept(MKBrut::Organization) do |prefix|
57
+ MKBrut::Organization.new(prefix)
58
+ end
59
+ opts.banner = [
60
+ "Usage: mkbrut [options] app-name",
61
+ "",
62
+ " Creates a new Brut-powered app",
63
+ "",
64
+ "VERSION",
65
+ "",
66
+ " #{MKBrut::VERSION}",
67
+ "",
68
+ "OPTIONS",
69
+ "",
70
+ ].join("\n")
71
+
72
+ opts.on("-a", "--app-id=ID", MKBrut::AppId,
73
+ "App identifier, which must be able to be used as a hostname or other Internet identifier. Derived from your app name, if omitted")
74
+
75
+ opts.on("-o", "--organization=ORG",MKBrut::Organization,
76
+ "Organization name, e.g. what you'd use for GitHub. Defaults to the app-id value")
77
+
78
+ opts.on("-e", "--prefix=PREFIX", MKBrut::Prefix,
79
+ "Two-character prefix for external IDs and autonomous custom elements. Derived from your app-id, if omitted.")
80
+
81
+ opts.on("--dry-run", "Only show what would happen, don't actually do anything")
82
+ opts.on("--[no-]demo", "Include, or not, additional files that demonstrate Brut's features (default is true for now")
83
+ {
84
+ "sidekiq" => "Use Sidekiq for background jobs",
85
+ "heroku" => "Use Heroku for container-based deployment",
86
+ }.each do |segment,description|
87
+ opts.on("--segment-#{segment}", description)
88
+ end
89
+ opts.on("-h", "--help", "Show this help message") do
90
+ show_help(versions)
91
+ exit
92
+ end
93
+ end
94
+
95
+ @option_parser.parse!(args, into: options)
96
+ if !options.key?(:demo)
97
+ options[:demo] = true
98
+ end
99
+
100
+ options[:app_name] = MKBrut::AppName.new(args.first)
101
+ options[:app_id] = options[:'app-id']
102
+ options[:dry_run] = !!options[:'dry-run']
103
+ MKBrut::AppOptions.new(**options.merge(versions:))
104
+ end
105
+
106
+ end
107
+ end
@@ -0,0 +1,20 @@
1
+ # This exists because ERB can't working with a SimpleDelegator or
2
+ # Delegate.
3
+ class MKBrut::ErbBindingDelegate
4
+ def initialize(app_options)
5
+ @app_options = app_options
6
+ end
7
+
8
+ # Not using Delegate because it won't work with ERB binding
9
+ def method_missing(syn,*args,&block)
10
+ if args.empty? && @app_options.respond_to?(syn)
11
+ @app_options.send(syn)
12
+ else
13
+ super
14
+ end
15
+ end
16
+
17
+ def respond_to_missing?(syn,include_all)
18
+ @app_options.respond_to?(syn)
19
+ end
20
+ end
@@ -0,0 +1,32 @@
1
+ module MKBrut
2
+ class InternetIdentifier
3
+ def initialize(name, value)
4
+ @name = name
5
+ @identifier = value.to_s
6
+ validate_identifier
7
+ end
8
+
9
+ def to_s = @identifier
10
+ alias to_str to_s
11
+
12
+ private
13
+
14
+ def validate_identifier
15
+ if @identifier.empty?
16
+ raise MKBrut::InvalidIdentifier, "#{@name} cannot be empty"
17
+ end
18
+
19
+ if @identifier.length > 63
20
+ raise MKBrut::InvalidIdentifier, "#{@name} cannot be longer than 63 characters"
21
+ end
22
+
23
+ if @identifier.start_with?("-") || @identifier.end_with?("-")
24
+ raise MKBrut::InvalidIdentifier, "#{@name} cannot start or end with a hyphen"
25
+ end
26
+
27
+ if @identifier.match?(/[^a-zA-Z0-9-]/)
28
+ raise MKBrut::InvalidIdentifier, "#{@name} can only contain letters, numbers, and hyphens"
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,4 @@
1
+ module MKBrut
2
+ class InvalidIdentifier < ArgumentError
3
+ end
4
+ end
@@ -0,0 +1,42 @@
1
+ class MKBrut::Ops::AddCSSImport < MKBrut::Ops::BaseOp
2
+ def initialize(project_root:, import:)
3
+ @file = project_root / "app" / "src" / "front_end" / "css" / "index.css"
4
+ @import = import
5
+ end
6
+
7
+ def call
8
+ if dry_run?
9
+ puts "Would add import '#{@import}'; to '#{@file}'"
10
+ return
11
+ end
12
+
13
+ contents = File.read(@file).split(/\n/)
14
+
15
+ inserted_import = false
16
+ previous_line_was_import = false
17
+ new_contents = []
18
+ contents.each do |line|
19
+ if line =~ /^\s*@import\s+["']/
20
+ previous_line_was_import = true
21
+ new_contents << line
22
+ else
23
+ if previous_line_was_import && !inserted_import
24
+ new_contents << "@import '#{@import}';"
25
+ inserted_import = true
26
+ end
27
+ previous_line_was_import = false
28
+ new_contents << line
29
+ end
30
+ end
31
+ if !inserted_import && previous_line_was_import
32
+ new_contents << "@import \"#{@import}\";"
33
+ inserted_import = true
34
+ end
35
+ if !inserted_import
36
+ raise "Did not find any other @imports in '#{@file}' - was expecting at least one to exist"
37
+ end
38
+ File.open(@file, "w") do |file|
39
+ file.puts new_contents.join("\n")
40
+ end
41
+ end
42
+ end