brut 0.0.27 → 0.0.29

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 (411) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +6 -0
  3. data/.projections.json +10 -0
  4. data/.rspec +3 -0
  5. data/Dockerfile.dx +32 -14
  6. data/Gemfile.lock +1 -1
  7. data/assets/Logo-Square.pxd +0 -0
  8. data/assets/LogoPylon.pxd +0 -0
  9. data/assets/LogoStop.pxd +0 -0
  10. data/assets/LogoTall.pxd +0 -0
  11. data/bin/docs +24 -2
  12. data/bin/rspec +27 -0
  13. data/bin/setup +3 -3
  14. data/brutrb.com/.vitepress/config.mjs +1 -0
  15. data/brutrb.com/.vitepress/theme/custom.css +7 -0
  16. data/brutrb.com/.vitepress/theme/style.css +26 -15
  17. data/brutrb.com/deployment.md +123 -45
  18. data/brutrb.com/dev-environment.md +6 -5
  19. data/brutrb.com/doc-conventions.md +1 -1
  20. data/brutrb.com/getting-started.md +64 -28
  21. data/brutrb.com/images/LogoPylon.png +0 -0
  22. data/brutrb.com/images/LogoSquare.png +0 -0
  23. data/brutrb.com/images/LogoStop.png +0 -0
  24. data/brutrb.com/images/LogoTall.png +0 -0
  25. data/brutrb.com/images/OverviewMetro.graffle +0 -0
  26. data/brutrb.com/images/OverviewMetro.png +0 -0
  27. data/brutrb.com/index.md +4 -3
  28. data/brutrb.com/instrumentation.md +12 -0
  29. data/brutrb.com/layouts.md +130 -0
  30. data/brutrb.com/overview.md +6 -6
  31. data/docker-compose.dx.yml +5 -2
  32. data/docs/404.html +3 -3
  33. data/docs/ai.html +6 -6
  34. data/docs/api/Brut/BackEnd/SeedData.html +1 -1
  35. data/docs/api/Brut/BackEnd/Sidekiq/Middlewares/Server/FlushSpans.html +1 -1
  36. data/docs/api/Brut/BackEnd/Sidekiq/Middlewares/Server.html +1 -1
  37. data/docs/api/Brut/BackEnd/Sidekiq/Middlewares.html +1 -1
  38. data/docs/api/Brut/BackEnd/Sidekiq.html +1 -1
  39. data/docs/api/Brut/BackEnd/Validators/FormValidator.html +1 -1
  40. data/docs/api/Brut/BackEnd/Validators.html +1 -1
  41. data/docs/api/Brut/BackEnd.html +1 -1
  42. data/docs/api/Brut/CLI/App.html +38 -13
  43. data/docs/api/Brut/CLI/AppRunner.html +1 -1
  44. data/docs/api/Brut/CLI/Apps/BuildAssets/All.html +2 -2
  45. data/docs/api/Brut/CLI/Apps/BuildAssets/CSS.html +2 -2
  46. data/docs/api/Brut/CLI/Apps/BuildAssets/Images.html +2 -2
  47. data/docs/api/Brut/CLI/Apps/BuildAssets/JS.html +2 -2
  48. data/docs/api/Brut/CLI/Apps/BuildAssets.html +1 -1
  49. data/docs/api/Brut/CLI/Apps/DB/Create.html +2 -2
  50. data/docs/api/Brut/CLI/Apps/DB/Drop.html +2 -2
  51. data/docs/api/Brut/CLI/Apps/DB/Migrate.html +9 -3
  52. data/docs/api/Brut/CLI/Apps/DB/NewMigration.html +11 -11
  53. data/docs/api/Brut/CLI/Apps/DB/Rebuild.html +2 -2
  54. data/docs/api/Brut/CLI/Apps/DB/Seed.html +2 -2
  55. data/docs/api/Brut/CLI/Apps/DB/Status.html +12 -12
  56. data/docs/api/Brut/CLI/Apps/DB.html +1 -1
  57. data/docs/api/Brut/CLI/Apps/DeployBase/GitChecks.html +270 -0
  58. data/docs/api/Brut/CLI/Apps/DeployBase.html +257 -0
  59. data/docs/api/Brut/CLI/Apps/HerokuContainerBasedDeploy/Deploy.html +585 -0
  60. data/docs/api/Brut/CLI/Apps/HerokuContainerBasedDeploy.html +196 -0
  61. data/docs/api/Brut/CLI/Apps/Scaffold/Action/Route.html +1 -1
  62. data/docs/api/Brut/CLI/Apps/Scaffold/Action.html +2 -2
  63. data/docs/api/Brut/CLI/Apps/Scaffold/Component.html +2 -2
  64. data/docs/api/Brut/CLI/Apps/Scaffold/CustomElementTest.html +2 -2
  65. data/docs/api/Brut/CLI/Apps/Scaffold/E2ETest.html +2 -2
  66. data/docs/api/Brut/CLI/Apps/Scaffold/Form.html +2 -2
  67. data/docs/api/Brut/CLI/Apps/Scaffold/Page/Route.html +1 -1
  68. data/docs/api/Brut/CLI/Apps/Scaffold/Page.html +2 -2
  69. data/docs/api/Brut/CLI/Apps/Scaffold/RoutesEditor.html +1 -1
  70. data/docs/api/Brut/CLI/Apps/Scaffold/Test.html +2 -2
  71. data/docs/api/Brut/CLI/Apps/Scaffold.html +1 -1
  72. data/docs/api/Brut/CLI/Apps/Test/Audit.html +12 -10
  73. data/docs/api/Brut/CLI/Apps/Test/E2e.html +8 -8
  74. data/docs/api/Brut/CLI/Apps/Test/JS.html +9 -9
  75. data/docs/api/Brut/CLI/Apps/Test/Run.html +18 -18
  76. data/docs/api/Brut/CLI/Apps/Test.html +1 -1
  77. data/docs/api/Brut/CLI/Apps.html +2 -2
  78. data/docs/api/Brut/CLI/Command.html +113 -28
  79. data/docs/api/Brut/CLI/Error.html +1 -1
  80. data/docs/api/Brut/CLI/ExecutionResults/Result.html +1 -1
  81. data/docs/api/Brut/CLI/ExecutionResults.html +1 -1
  82. data/docs/api/Brut/CLI/Executor.html +169 -38
  83. data/docs/api/Brut/CLI/InvalidOption.html +1 -1
  84. data/docs/api/Brut/CLI/Options.html +68 -19
  85. data/docs/api/Brut/CLI/Output.html +1 -1
  86. data/docs/api/Brut/CLI/SystemExecError.html +1 -1
  87. data/docs/api/Brut/CLI.html +1 -1
  88. data/docs/api/Brut/FactoryBot.html +1 -1
  89. data/docs/api/Brut/Framework/App.html +1 -1
  90. data/docs/api/Brut/Framework/Config.html +1 -1
  91. data/docs/api/Brut/Framework/Container.html +110 -29
  92. data/docs/api/Brut/Framework/Error.html +1 -1
  93. data/docs/api/Brut/Framework/Errors/AbstractMethod.html +89 -1
  94. data/docs/api/Brut/Framework/Errors/Bug.html +1 -1
  95. data/docs/api/Brut/Framework/Errors/MissingConfiguration.html +1 -1
  96. data/docs/api/Brut/Framework/Errors/MissingParameter.html +1 -1
  97. data/docs/api/Brut/Framework/Errors/NoClassForPath.html +1 -1
  98. data/docs/api/Brut/Framework/Errors/NotFound.html +1 -1
  99. data/docs/api/Brut/Framework/Errors/NotImplemented.html +1 -1
  100. data/docs/api/Brut/Framework/Errors.html +31 -8
  101. data/docs/api/Brut/Framework/FussyTypeEnforcement.html +1 -1
  102. data/docs/api/Brut/Framework/MCP.html +1 -1
  103. data/docs/api/Brut/Framework/ProjectEnvironment.html +1 -1
  104. data/docs/api/Brut/Framework.html +1 -1
  105. data/docs/api/Brut/FrontEnd/AssetPathResolver.html +1 -1
  106. data/docs/api/Brut/FrontEnd/Component/Helpers.html +36 -26
  107. data/docs/api/Brut/FrontEnd/Component.html +7 -7
  108. data/docs/api/Brut/FrontEnd/Components/ConstraintViolations.html +1 -1
  109. data/docs/api/Brut/FrontEnd/Components/FormTag.html +37 -29
  110. data/docs/api/Brut/FrontEnd/Components/I18nTranslations.html +1 -1
  111. data/docs/api/Brut/FrontEnd/Components/Input.html +1 -1
  112. data/docs/api/Brut/FrontEnd/Components/Inputs/CsrfToken.html +1 -1
  113. data/docs/api/Brut/FrontEnd/Components/Inputs/InputTag.html +20 -117
  114. data/docs/api/Brut/FrontEnd/Components/Inputs/RadioButton.html +25 -23
  115. data/docs/api/Brut/FrontEnd/Components/Inputs/SelectTagWithOptions.html +73 -380
  116. data/docs/api/Brut/FrontEnd/Components/Inputs/TextareaTag.html +22 -138
  117. data/docs/api/Brut/FrontEnd/Components/Inputs.html +1 -1
  118. data/docs/api/Brut/FrontEnd/Components/LocaleDetection.html +1 -1
  119. data/docs/api/Brut/FrontEnd/Components/PageIdentifier.html +1 -1
  120. data/docs/api/Brut/FrontEnd/Components/TimeTag.html +1 -1
  121. data/docs/api/Brut/FrontEnd/Components/Traceparent.html +1 -1
  122. data/docs/api/Brut/FrontEnd/Components.html +23 -2
  123. data/docs/api/Brut/FrontEnd/Download.html +1 -1
  124. data/docs/api/Brut/FrontEnd/Flash.html +1 -1
  125. data/docs/api/Brut/FrontEnd/Form.html +1 -1
  126. data/docs/api/Brut/FrontEnd/Forms/ConstraintViolation.html +24 -68
  127. data/docs/api/Brut/FrontEnd/Forms/Input/Color.html +201 -0
  128. data/docs/api/Brut/FrontEnd/Forms/Input/TimeOfDay.html +535 -0
  129. data/docs/api/Brut/FrontEnd/Forms/Input.html +983 -35
  130. data/docs/api/Brut/FrontEnd/Forms/InputDeclarations.html +4 -4
  131. data/docs/api/Brut/FrontEnd/Forms/InputDefinition.html +29 -19
  132. data/docs/api/Brut/FrontEnd/Forms/RadioButtonGroupInput.html +9 -3
  133. data/docs/api/Brut/FrontEnd/Forms/RadioButtonGroupInputDefinition.html +1 -1
  134. data/docs/api/Brut/FrontEnd/Forms/SelectInput.html +9 -3
  135. data/docs/api/Brut/FrontEnd/Forms/SelectInputDefinition.html +1 -1
  136. data/docs/api/Brut/FrontEnd/Forms/ValidityState.html +72 -22
  137. data/docs/api/Brut/FrontEnd/Forms.html +1 -1
  138. data/docs/api/Brut/FrontEnd/GenericResponse.html +1 -1
  139. data/docs/api/Brut/FrontEnd/Handler.html +1 -1
  140. data/docs/api/Brut/FrontEnd/Handlers/CspReportingHandler.html +1 -1
  141. data/docs/api/Brut/FrontEnd/Handlers/InstrumentationHandler/TraceParent.html +1 -1
  142. data/docs/api/Brut/FrontEnd/Handlers/InstrumentationHandler.html +1 -1
  143. data/docs/api/Brut/FrontEnd/Handlers/LocaleDetectionHandler.html +1 -1
  144. data/docs/api/Brut/FrontEnd/Handlers/MissingHandler/Form.html +1 -1
  145. data/docs/api/Brut/FrontEnd/Handlers/MissingHandler.html +1 -1
  146. data/docs/api/Brut/FrontEnd/Handlers.html +1 -1
  147. data/docs/api/Brut/FrontEnd/HandlingResults.html +1 -1
  148. data/docs/api/Brut/FrontEnd/HttpMethod.html +1 -1
  149. data/docs/api/Brut/FrontEnd/HttpStatus.html +1 -1
  150. data/docs/api/Brut/FrontEnd/InlineSvgLocator.html +1 -1
  151. data/docs/api/Brut/FrontEnd/Layout.html +1 -1
  152. data/docs/api/Brut/FrontEnd/Middleware.html +1 -1
  153. data/docs/api/Brut/FrontEnd/Middlewares/AnnotateBrutOwnedPaths.html +1 -1
  154. data/docs/api/Brut/FrontEnd/Middlewares/Favicon.html +1 -1
  155. data/docs/api/Brut/FrontEnd/Middlewares/OpenTelemetrySpan.html +1 -1
  156. data/docs/api/Brut/FrontEnd/Middlewares/ReloadApp.html +6 -2
  157. data/docs/api/Brut/FrontEnd/Middlewares.html +1 -1
  158. data/docs/api/Brut/FrontEnd/Page.html +1 -1
  159. data/docs/api/Brut/FrontEnd/Pages/MissingPage.html +1 -1
  160. data/docs/api/Brut/FrontEnd/Pages.html +1 -1
  161. data/docs/api/Brut/FrontEnd/RequestContext.html +1 -1
  162. data/docs/api/Brut/FrontEnd/RouteHook.html +1 -1
  163. data/docs/api/Brut/FrontEnd/RouteHooks/AgeFlash.html +1 -1
  164. data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineScripts.html +1 -1
  165. data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineStylesOrScripts/ReportOnly.html +1 -1
  166. data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineStylesOrScripts.html +1 -1
  167. data/docs/api/Brut/FrontEnd/RouteHooks/LocaleDetection.html +1 -1
  168. data/docs/api/Brut/FrontEnd/RouteHooks/SetupRequestContext.html +1 -1
  169. data/docs/api/Brut/FrontEnd/RouteHooks.html +1 -1
  170. data/docs/api/Brut/FrontEnd/Routing/FormHandlerRoute.html +1 -1
  171. data/docs/api/Brut/FrontEnd/Routing/FormRoute.html +1 -1
  172. data/docs/api/Brut/FrontEnd/Routing/MissingForm.html +1 -1
  173. data/docs/api/Brut/FrontEnd/Routing/MissingHandler.html +1 -1
  174. data/docs/api/Brut/FrontEnd/Routing/MissingPage.html +1 -1
  175. data/docs/api/Brut/FrontEnd/Routing/MissingPath.html +1 -1
  176. data/docs/api/Brut/FrontEnd/Routing/PageRoute.html +1 -1
  177. data/docs/api/Brut/FrontEnd/Routing/Route.html +1 -1
  178. data/docs/api/Brut/FrontEnd/Routing.html +1 -1
  179. data/docs/api/Brut/FrontEnd/Session.html +1 -1
  180. data/docs/api/Brut/FrontEnd.html +1 -1
  181. data/docs/api/Brut/I18n/BaseMethods.html +1 -1
  182. data/docs/api/Brut/I18n/ForBackEnd.html +1 -1
  183. data/docs/api/Brut/I18n/ForCLI.html +1 -1
  184. data/docs/api/Brut/I18n/ForHTML.html +1 -1
  185. data/docs/api/Brut/I18n/HTTPAcceptLanguage/AlwaysEnglish.html +1 -1
  186. data/docs/api/Brut/I18n/HTTPAcceptLanguage.html +1 -1
  187. data/docs/api/Brut/I18n.html +1 -1
  188. data/docs/api/Brut/Instrumentation/LoggerSpanExporter.html +1 -1
  189. data/docs/api/Brut/Instrumentation/OpenTelemetry/NormalizedAttributes.html +1 -1
  190. data/docs/api/Brut/Instrumentation/OpenTelemetry/Span.html +1 -1
  191. data/docs/api/Brut/Instrumentation/OpenTelemetry.html +1 -1
  192. data/docs/api/Brut/Instrumentation.html +1 -1
  193. data/docs/api/Brut/SinatraHelpers/ClassMethods.html +1 -1
  194. data/docs/api/Brut/SinatraHelpers.html +1 -1
  195. data/docs/api/Brut/SpecSupport/ClockSupport.html +1 -1
  196. data/docs/api/Brut/SpecSupport/ComponentSupport.html +1 -1
  197. data/docs/api/Brut/SpecSupport/E2ETestServer.html +20 -20
  198. data/docs/api/Brut/SpecSupport/E2eSupport.html +1 -1
  199. data/docs/api/Brut/SpecSupport/EnhancedNode.html +1 -1
  200. data/docs/api/Brut/SpecSupport/FlashSupport.html +1 -1
  201. data/docs/api/Brut/SpecSupport/GeneralSupport/ClassMethods.html +1 -1
  202. data/docs/api/Brut/SpecSupport/GeneralSupport.html +1 -1
  203. data/docs/api/Brut/SpecSupport/HandlerSupport.html +2 -2
  204. data/docs/api/Brut/SpecSupport/Matchers/BeABug.html +142 -0
  205. data/docs/api/Brut/SpecSupport/Matchers/BePageFor.html +142 -0
  206. data/docs/api/Brut/SpecSupport/Matchers/BeRoutingFor.html +155 -0
  207. data/docs/api/Brut/SpecSupport/Matchers/HaveConstraintViolation.html +55 -25
  208. data/docs/api/Brut/SpecSupport/Matchers/HaveGenerated.html +149 -0
  209. data/docs/api/Brut/SpecSupport/Matchers/HaveHTMLAttribute.html +46 -19
  210. data/docs/api/Brut/SpecSupport/Matchers/HaveI18nString.html +149 -0
  211. data/docs/api/Brut/SpecSupport/Matchers/HaveLinkTo.html +149 -0
  212. data/docs/api/Brut/SpecSupport/Matchers/HaveRedirectedTo.html +165 -0
  213. data/docs/api/Brut/SpecSupport/Matchers/HaveReturnedHttpStatus.html +158 -0
  214. data/docs/api/Brut/SpecSupport/Matchers/HaveReturnedRackResponse.html +156 -0
  215. data/docs/api/Brut/SpecSupport/Matchers.html +2 -2
  216. data/docs/api/Brut/SpecSupport/RSpecSetup/OptionalSidekiqSupport.html +24 -24
  217. data/docs/api/Brut/SpecSupport/RSpecSetup.html +55 -20
  218. data/docs/api/Brut/SpecSupport/SessionSupport.html +1 -1
  219. data/docs/api/Brut/SpecSupport.html +1 -1
  220. data/docs/api/Brut.html +1 -1
  221. data/docs/api/Clock.html +1 -1
  222. data/docs/api/RichString.html +1 -1
  223. data/docs/api/SemanticLogger/Appender/Async.html +1 -1
  224. data/docs/api/Sequel/Extensions/BrutInstrumentation.html +5 -1
  225. data/docs/api/Sequel/Extensions/BrutMigrations.html +36 -28
  226. data/docs/api/Sequel/Extensions.html +1 -1
  227. data/docs/api/Sequel/Plugins/CreatedAt/InstanceMethods.html +1 -1
  228. data/docs/api/Sequel/Plugins/CreatedAt.html +1 -1
  229. data/docs/api/Sequel/Plugins/ExternalId/ClassMethods.html +1 -1
  230. data/docs/api/Sequel/Plugins/ExternalId/InstanceMethods.html +1 -1
  231. data/docs/api/Sequel/Plugins/ExternalId.html +1 -1
  232. data/docs/api/Sequel/Plugins/FindBang/ClassMethods.html +1 -1
  233. data/docs/api/Sequel/Plugins/FindBang.html +1 -1
  234. data/docs/api/Sequel/Plugins.html +1 -1
  235. data/docs/api/Sequel.html +1 -1
  236. data/docs/api/SpecSupport/Matchers/BeABug.html +143 -0
  237. data/docs/api/_index.html +106 -1
  238. data/docs/api/class_list.html +1 -1
  239. data/docs/api/css/full_list.css +2 -1
  240. data/docs/api/css/style.css +14 -13
  241. data/docs/api/file.README.html +1 -1
  242. data/docs/api/index.html +1 -1
  243. data/docs/api/method_list.html +530 -330
  244. data/docs/api/top-level-namespace.html +1 -1
  245. data/docs/assets/LogoStop.X8x-4riz.png +0 -0
  246. data/docs/assets/OverviewMetro.DUS-5fUZ.png +0 -0
  247. data/docs/assets/{ai.md.tZrjP9im.js → ai.md._6HCDL6d.js} +1 -1
  248. data/docs/assets/ai.md._6HCDL6d.lean.js +1 -0
  249. data/docs/assets/{app.D_yaTITQ.js → app.BhrfSt68.js} +1 -1
  250. data/docs/assets/chunks/@localSearchIndexroot.CeRAdP1K.js +1 -0
  251. data/docs/assets/chunks/{VPLocalSearchBox.B2-ZzyTY.js → VPLocalSearchBox.Dpot_2H4.js} +1 -1
  252. data/docs/assets/chunks/{theme.CfGFVRvE.js → theme.N2SNVLgU.js} +2 -2
  253. data/docs/assets/{components.md.eCttGlN-.js → components.md.CRUMdRoN.js} +1 -1
  254. data/docs/assets/{configuration.md.BRriU0cL.js → configuration.md.LG-zIBww.js} +1 -1
  255. data/docs/assets/deployment.md.BLseERGV.js +48 -0
  256. data/docs/assets/deployment.md.BLseERGV.lean.js +1 -0
  257. data/docs/assets/{dev-environment.md.BNc8AYiK.js → dev-environment.md.GZv6xvi9.js} +1 -1
  258. data/docs/assets/doc-conventions.md.-kN3Xo5C.js +1 -0
  259. data/docs/assets/{doc-conventions.md.DCfRXXi-.lean.js → doc-conventions.md.-kN3Xo5C.lean.js} +1 -1
  260. data/docs/assets/{forms.md.CBTYQ_Cz.js → forms.md.B-koVgyw.js} +23 -23
  261. data/docs/assets/{forms.md.CBTYQ_Cz.lean.js → forms.md.B-koVgyw.lean.js} +1 -1
  262. data/docs/assets/getting-started.md.Dj0qtZI2.js +25 -0
  263. data/docs/assets/getting-started.md.Dj0qtZI2.lean.js +1 -0
  264. data/docs/assets/index.md.CuBB-BdM.js +1 -0
  265. data/docs/assets/index.md.CuBB-BdM.lean.js +1 -0
  266. data/docs/assets/{instrumentation.md.CL6ax7nT.js → instrumentation.md.a9Pjps4P.js} +2 -2
  267. data/docs/assets/{instrumentation.md.CL6ax7nT.lean.js → instrumentation.md.a9Pjps4P.lean.js} +1 -1
  268. data/docs/assets/layouts.md.cPnh3NId.js +51 -0
  269. data/docs/assets/layouts.md.cPnh3NId.lean.js +1 -0
  270. data/docs/assets/lsp.md.Bsu-f6VU.js +1 -0
  271. data/docs/assets/lsp.md.Bsu-f6VU.lean.js +1 -0
  272. data/docs/assets/{overview.md.CDalkuxV.js → overview.md.DVKRM8zl.js} +4 -4
  273. data/docs/assets/overview.md.DVKRM8zl.lean.js +1 -0
  274. data/docs/assets/recipes_authentication.md.CAsXf7hk.js +1 -0
  275. data/docs/assets/recipes_authentication.md.CAsXf7hk.lean.js +1 -0
  276. data/docs/assets/{style.D73IYGCX.css → style.B2o1L9eN.css} +1 -1
  277. data/docs/assets.html +5 -5
  278. data/docs/brut-js/api/AjaxSubmit.html +1 -1
  279. data/docs/brut-js/api/AjaxSubmit.js.html +1 -1
  280. data/docs/brut-js/api/Autosubmit.html +1 -1
  281. data/docs/brut-js/api/Autosubmit.js.html +1 -1
  282. data/docs/brut-js/api/BaseCustomElement.html +1 -1
  283. data/docs/brut-js/api/BaseCustomElement.js.html +1 -1
  284. data/docs/brut-js/api/BrutCustomElements.html +1 -1
  285. data/docs/brut-js/api/BufferedLogger.html +1 -1
  286. data/docs/brut-js/api/ConfirmSubmit.html +1 -1
  287. data/docs/brut-js/api/ConfirmSubmit.js.html +1 -1
  288. data/docs/brut-js/api/ConfirmationDialog.html +1 -1
  289. data/docs/brut-js/api/ConfirmationDialog.js.html +1 -1
  290. data/docs/brut-js/api/ConstraintViolationMessage.html +1 -1
  291. data/docs/brut-js/api/ConstraintViolationMessage.js.html +1 -1
  292. data/docs/brut-js/api/ConstraintViolationMessages.html +1 -1
  293. data/docs/brut-js/api/ConstraintViolationMessages.js.html +1 -1
  294. data/docs/brut-js/api/CopyToClipboard.html +1 -1
  295. data/docs/brut-js/api/CopyToClipboard.js.html +1 -1
  296. data/docs/brut-js/api/Form.html +1 -1
  297. data/docs/brut-js/api/Form.js.html +1 -1
  298. data/docs/brut-js/api/I18nTranslation.html +1 -1
  299. data/docs/brut-js/api/I18nTranslation.js.html +1 -1
  300. data/docs/brut-js/api/LocaleDetection.html +1 -1
  301. data/docs/brut-js/api/LocaleDetection.js.html +1 -1
  302. data/docs/brut-js/api/Logger.html +1 -1
  303. data/docs/brut-js/api/Logger.js.html +1 -1
  304. data/docs/brut-js/api/Message.html +1 -1
  305. data/docs/brut-js/api/Message.js.html +1 -1
  306. data/docs/brut-js/api/PrefixedLogger.html +1 -1
  307. data/docs/brut-js/api/RichString.html +1 -1
  308. data/docs/brut-js/api/RichString.js.html +1 -1
  309. data/docs/brut-js/api/Tabs.html +1 -1
  310. data/docs/brut-js/api/Tabs.js.html +1 -1
  311. data/docs/brut-js/api/Tracing.html +1 -1
  312. data/docs/brut-js/api/Tracing.js.html +1 -1
  313. data/docs/brut-js/api/external-CustomElementRegistry.html +1 -1
  314. data/docs/brut-js/api/external-Performance.html +1 -1
  315. data/docs/brut-js/api/external-Promise.html +1 -1
  316. data/docs/brut-js/api/external-ValidityState.html +1 -1
  317. data/docs/brut-js/api/external-Window.html +1 -1
  318. data/docs/brut-js/api/external-fetch.html +1 -1
  319. data/docs/brut-js/api/global.html +1 -1
  320. data/docs/brut-js/api/index.html +1 -1
  321. data/docs/brut-js/api/index.js.html +1 -1
  322. data/docs/brut-js/api/module-testing.html +1 -1
  323. data/docs/brut-js/api/testing.AssetMetadata.html +1 -1
  324. data/docs/brut-js/api/testing.AssetMetadataLoader.html +1 -1
  325. data/docs/brut-js/api/testing.CustomElementTest.html +1 -1
  326. data/docs/brut-js/api/testing.DOMCreator.html +1 -1
  327. data/docs/brut-js/api/testing_AssetMetadata.js.html +1 -1
  328. data/docs/brut-js/api/testing_AssetMetadataLoader.js.html +1 -1
  329. data/docs/brut-js/api/testing_CustomElementTest.js.html +1 -1
  330. data/docs/brut-js/api/testing_DOMCreator.js.html +1 -1
  331. data/docs/brut-js/api/testing_index.js.html +1 -1
  332. data/docs/brut-js.html +5 -5
  333. data/docs/business-logic.html +5 -5
  334. data/docs/cli.html +5 -5
  335. data/docs/components.html +7 -7
  336. data/docs/configuration.html +6 -6
  337. data/docs/css.html +5 -5
  338. data/docs/custom-element-tests.html +5 -5
  339. data/docs/database-access.html +5 -5
  340. data/docs/database-schema.html +5 -5
  341. data/docs/deployment.html +53 -6
  342. data/docs/dev-environment.html +6 -6
  343. data/docs/doc-conventions.html +6 -6
  344. data/docs/end-to-end-tests.html +5 -5
  345. data/docs/flash-and-session.html +5 -5
  346. data/docs/forms.html +28 -28
  347. data/docs/getting-started.html +30 -7
  348. data/docs/handlers.html +5 -5
  349. data/docs/hashmap.json +1 -1
  350. data/docs/hooks.html +5 -5
  351. data/docs/i18n.html +5 -5
  352. data/docs/index.html +6 -6
  353. data/docs/instrumentation.html +6 -6
  354. data/docs/javascript.html +5 -5
  355. data/docs/jobs.html +5 -5
  356. data/docs/keyword-injection.html +5 -5
  357. data/docs/layouts.html +74 -0
  358. data/docs/lsp.html +24 -0
  359. data/docs/markdown-examples.html +5 -5
  360. data/docs/middleware.html +5 -5
  361. data/docs/not-released.html +5 -5
  362. data/docs/overview.html +9 -9
  363. data/docs/pages.html +6 -6
  364. data/docs/recipes/authentication.html +24 -0
  365. data/docs/routes.html +5 -5
  366. data/docs/security.html +5 -5
  367. data/docs/seed-data.html +5 -5
  368. data/docs/space-time-continuum.html +5 -5
  369. data/docs/tutorial.html +5 -5
  370. data/docs/unit-tests.html +5 -5
  371. data/dx/bash_customizations +7 -0
  372. data/dx/build +13 -2
  373. data/dx/docker-compose.env +1 -1
  374. data/dx/exec +25 -8
  375. data/lib/brut/cli/app.rb +7 -2
  376. data/lib/brut/cli/apps/deploy_base.rb +86 -0
  377. data/lib/brut/cli/apps/heroku_container_based_deploy.rb +194 -0
  378. data/lib/brut/cli/command.rb +7 -13
  379. data/lib/brut/cli/executor.rb +31 -5
  380. data/lib/brut/cli/options.rb +4 -0
  381. data/lib/brut/cli.rb +4 -3
  382. data/lib/brut/framework/container.rb +25 -7
  383. data/lib/brut/framework/errors/abstract_method.rb +7 -0
  384. data/lib/brut/framework/errors.rb +4 -2
  385. data/lib/brut/front_end/forms/input.rb +253 -20
  386. data/lib/brut/front_end/forms/input_definition.rb +15 -12
  387. data/lib/brut/front_end/middlewares/reload_app.rb +2 -0
  388. data/lib/brut/front_end.rb +1 -0
  389. data/lib/brut/spec_support/rspec_setup.rb +42 -2
  390. data/lib/brut/version.rb +1 -1
  391. data/lib/sequel/extensions/brut_instrumentation.rb +4 -0
  392. data/specs/brut/front_end/forms/input.spec.rb +978 -0
  393. data/specs/spec_helper.rb +27 -0
  394. data/specs/support/matchers/have_constraint_violation.rb +23 -0
  395. data/specs/support/matchers.rb +5 -0
  396. data/specs/support.rb +3 -0
  397. metadata +77 -29
  398. data/docs/assets/ai.md.tZrjP9im.lean.js +0 -1
  399. data/docs/assets/chunks/@localSearchIndexroot.BsN5i0Fi.js +0 -1
  400. data/docs/assets/deployment.md.Dbka4OTr.js +0 -1
  401. data/docs/assets/deployment.md.Dbka4OTr.lean.js +0 -1
  402. data/docs/assets/doc-conventions.md.DCfRXXi-.js +0 -1
  403. data/docs/assets/getting-started.md.Bz2s1Vjb.js +0 -2
  404. data/docs/assets/getting-started.md.Bz2s1Vjb.lean.js +0 -1
  405. data/docs/assets/index.md.B28EwVpq.js +0 -1
  406. data/docs/assets/index.md.B28EwVpq.lean.js +0 -1
  407. data/docs/assets/overview.Da81cB9R.png +0 -0
  408. data/docs/assets/overview.md.CDalkuxV.lean.js +0 -1
  409. /data/docs/assets/{components.md.eCttGlN-.lean.js → components.md.CRUMdRoN.lean.js} +0 -0
  410. /data/docs/assets/{configuration.md.BRriU0cL.lean.js → configuration.md.LG-zIBww.lean.js} +0 -0
  411. /data/docs/assets/{dev-environment.md.BNc8AYiK.lean.js → dev-environment.md.GZv6xvi9.lean.js} +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ad36cf0ea0c628d6ab48dafc53dfb9253f19ac5df73669e86532970ea70e759d
4
- data.tar.gz: fa4dcb2b80e205fb2de2372fe8cfdfd441b6634ab69458287e41f5e681cc507f
3
+ metadata.gz: 3b77e4a2a30122b1805ecf5cb52a5313bb137c188364a6baac62270918cd1640
4
+ data.tar.gz: 393ad694666d889d5672fb592e7f188e19f78f519f19fbd4a5edb1af32ef5d21
5
5
  SHA512:
6
- metadata.gz: a357cccfd1224fcd00d7909670d026a532c1d8cdc0bdd2549a92fad1621370bbdcde707b8fdab0c003ddbe71728018aef2c45201194614025f1b97520775847e
7
- data.tar.gz: 042fc9088726c05c0f12b573f54eeb2d7ace79a4552a5756170fb776eaddf5ad895d2a702f1b2e5f05d94f5ff07b7ca4b560e9c3729153441458aa6c3c8e2340
6
+ metadata.gz: 769c54918d2cb9a3e40e007d954c70ef6ec4033bb1a159d616cc4680aaf5b5d650e0335d729a5acc10b0d9f96574c4dfe5ec5f563bbd82828c6878ea4a532338
7
+ data.tar.gz: 510b4299c42f476f61110382afdb26c1c71ae8f6aecf16f95bebcf30bb5ac14b0817b08bb0787e3b9b541be59add9e35d4d60df78debbe0627b371aa4639ce00
data/.gitignore CHANGED
@@ -32,3 +32,9 @@
32
32
  /brut-css/dist
33
33
  # This is where the CSS lives temporarily while docs are being built
34
34
  /brut-css/src/docs/brut.css
35
+
36
+ # Per-developer Bash customizations that could contain secrets
37
+ /dx/bash_customizations.local
38
+
39
+ # Gems are installed here for LSP convienience
40
+ /local-gems
data/.projections.json ADDED
@@ -0,0 +1,10 @@
1
+ {
2
+ "lib/*.rb": {
3
+ "alternate": "specs/{}.spec.rb",
4
+ "type": "source"
5
+ },
6
+ "specs/*.spec.rb": {
7
+ "alternate": "lib/{}.rb",
8
+ "type": "spec"
9
+ }
10
+ }
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ -I specs
2
+ --require spec_helper
3
+ -P "specs/**/*.spec.rb"
data/Dockerfile.dx CHANGED
@@ -10,28 +10,46 @@ RUN apt-get -y clean && \
10
10
  apt-get -y update && \
11
11
  apt-get install --quiet --yes ca-certificates curl gnupg rsync
12
12
 
13
- # Install NODE per https://github.com/nodesource/distributions?tab=readme-ov-file#using-debian-as-root-nodejs-22
14
- RUN curl -fsSL https://deb.nodesource.com/setup_22.x -o /tmp/nodesource_setup.sh && \
15
- bash /tmp/nodesource_setup.sh && \
16
- apt-get install -y nodejs
17
-
18
- # Install Postgres client per https://www.postgresql.org/download/linux/debian/ - note that the
19
- # automated configuration doesn't work, I think because it assumes non-root.
20
- RUN apt-get -y install lsb-release && \
21
- sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list' && \
22
- wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add - && \
23
- apt-get update && \
24
- apt-get -y install postgresql-client-16
25
-
26
13
  RUN echo "gem: --no-document" >> ~/.gemrc && \
27
14
  gem update --system && \
28
15
  gem install bundler
29
-
30
16
  # Need vim at all times
31
17
  ENV EDITOR=vim
32
18
  RUN apt-get install -y vim && \
33
19
  echo "set -o vi" >> /root/.bashrc
34
20
 
21
+
22
+ # Setup a non-root user
23
+
24
+ # Their user id, which ideally matches their user id on the host
25
+ ARG user_uid=10001
26
+ # Their group id, which ideally matches their group id on the host
27
+ ARG user_gid=10002
28
+
29
+ # Create the user's group ID if it does not exist
30
+ RUN getent group ${user_gid} || groupadd --gid ${user_gid} appgroup
31
+ # Create the user. Note that we put bash_customizations in both .profile and .bashrc
32
+ # to increase the chances they are used when running bash in various configurations
33
+ RUN useradd --uid ${user_uid} --gid ${user_gid} --create-home --home-dir /home/appuser appuser && \
34
+ echo ". ~/.bash_customizations" >> /home/appuser/.profile && \
35
+ echo ". ~/.bash_customizations.local" >> /home/appuser/.profile && \
36
+ echo ". ~/.bash_customizations" >> /home/appuser/.bashrc && \
37
+ echo ". ~/.bash_customizations.local" >> /home/appuser/.bashrc
38
+
39
+ COPY --chown=appuser:${user_gid} dx/show-help-in-app-container-then-wait.sh /home/appuser
40
+ COPY --chown=appuser:${user_gid} dx/bash_customizations /home/appuser/.bash_customizations
41
+ COPY --chown=appuser:${user_gid} dx/bash_customizations.local /home/appuser/.bash_customizations.local
42
+
43
+ # NOT including the group here as that will place the user's environment
44
+ # ONLY in that group and not in all the groups in which they are a part.
45
+ USER appuser
46
+
47
+ # Install NodeJS, per https://nodejs.org/en/download
48
+ RUN curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | bash && \
49
+ \. "$HOME/.nvm/nvm.sh" && \
50
+ nvm install 22 && \
51
+ node -v && nvm current && npm -v
52
+
35
53
  # Node's colors are hand-crafted to always look bad and render at least some text unreadable
36
54
  # no matter what your setup. Cool.
37
55
  ENV NODE_DISABLE_COLORS=1
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- brut (0.0.27)
4
+ brut (0.0.29)
5
5
  concurrent-ruby
6
6
  i18n
7
7
  irb
Binary file
Binary file
Binary file
Binary file
data/bin/docs CHANGED
@@ -14,14 +14,36 @@ OptionParser.new do |opts|
14
14
  end
15
15
  end.parse!
16
16
 
17
- public_dir = (Pathname(__FILE__).dirname / ".." / "brutrb.com" / "public" ).expand_path.to_s
18
- docs_dir = public_dir + "/api"
17
+ public_dir = (Pathname(__FILE__).dirname / ".." / "brutrb.com" / "public" ).expand_path
18
+ docs_dir = public_dir / "api"
19
19
  brutjs_dir = (Pathname(__FILE__).dirname / ".." / "brut-js" ).expand_path.to_s
20
20
 
21
21
  system(
22
22
  "bundle exec yard doc -o '#{docs_dir}' -m markdown -M rdiscount --backtrace"
23
23
  )
24
24
 
25
+ puts "Hacking CSS"
26
+ [
27
+ docs_dir / "css" / "style.css",
28
+ docs_dir / "css" / "full_list.css"
29
+ ].each do |css_file_to_hack|
30
+ lines = File.read(css_file_to_hack).split(/\n/)
31
+ File.open(css_file_to_hack, "w") do |f|
32
+ lines.each do |line|
33
+ if line =~ /\"Lucida Sans"/
34
+ f.puts line.gsub(/\"Lucida Sans\"/, '"Helvetica Neue", "Lucida Sans"')
35
+ elsif line =~ / Monaco,/
36
+ f.puts line.gsub(/ Monaco,/, '"Courier New", Monaco,')
37
+ elsif line =~ /font-family: monospace;/
38
+ f.puts line.gsub(/font-family: monospace;/, 'font-family: "Courier New", monospace;')
39
+ else
40
+ f.puts line
41
+ end
42
+ end
43
+ f.puts "code { font-family: 'Courier New', monospace; font-weight: 600; }"
44
+ end
45
+ end
46
+
25
47
  FileUtils.chdir brutjs_dir do
26
48
  system("bin/build")
27
49
  end
data/bin/rspec 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 'rspec' 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("rspec-core", "rspec")
data/bin/setup CHANGED
@@ -83,7 +83,7 @@ def setup(update_gems:,setup_credentials:)
83
83
  system! "ssh-add #{key_file}"
84
84
  end
85
85
 
86
- known_hosts_dest = Pathname("/") / "root" / ".ssh" / "known_hosts"
86
+ known_hosts_dest = Pathname("/") / "home" / "appuser" / ".ssh" / "known_hosts"
87
87
  if known_hosts_dest.exist?
88
88
  log "#{known_hosts_dest} exists, your ssh key should work with GitHub"
89
89
  else
@@ -117,7 +117,7 @@ def setup(update_gems:,setup_credentials:)
117
117
  end
118
118
  log "Your ssh key looks good"
119
119
 
120
- gem_credentials_dest = Pathname("/") / "root" / ".gem" / "credentials"
120
+ gem_credentials_dest = Pathname("/") / "home" / "appuser" / ".gem" / "credentials"
121
121
  if gem_credentials_dest.exist?
122
122
  log "Gem credentials look good"
123
123
  else
@@ -139,7 +139,7 @@ def setup(update_gems:,setup_credentials:)
139
139
  end
140
140
  end
141
141
 
142
- npm_credentials_dest = Pathname("/") / "root" / ".npmrc"
142
+ npm_credentials_dest = Pathname("/") / "home" / "appuser" / ".npmrc"
143
143
  if npm_credentials_dest.exist?
144
144
  log "NPM credentials look good"
145
145
  else
@@ -39,6 +39,7 @@ export default defineConfig({
39
39
  items: [
40
40
  { text: "Routes", link: "/routes" },
41
41
  { text: "Pages", link: "/pages" },
42
+ { text: "Layouts", link: "/layouts" },
42
43
  { text: "Forms", link: "/forms" },
43
44
  { text: "Handlers and Actions", link: "/handlers" },
44
45
  { text: "Components", link: "/components" },
@@ -5,3 +5,10 @@
5
5
  .image-bg {
6
6
  display: none;
7
7
  }
8
+ td > code {
9
+ white-space: nowrap;
10
+ }
11
+
12
+ .VPHero img, .VPHome img {
13
+ border-radius: 0.5rem;
14
+ }
@@ -51,13 +51,14 @@
51
51
 
52
52
  --blue-vp-c-brand-1: #0C00A7;
53
53
  --blue-vp-c-brand-2: #6357FF;
54
- --blue-vp-c-brand-3: #1000E2;
55
- --blue-vp-c-brand-soft: #FAF9FF;
54
+ --blue-vp-c-brand-3: #4C0606;
55
+ --blue-vp-c-brand-soft: #FFF9F9;
56
+
57
+ --vp-c-brand-1: #362400;
58
+ --vp-c-brand-2: #F1A100;
59
+ --vp-c-brand-3: #281B00;
60
+ --vp-c-brand-soft: #FFF1D6;
56
61
 
57
- --vp-c-brand-1: #A70C00;
58
- --vp-c-brand-2: #FF6357;
59
- --vp-c-brand-3: #e21000;
60
- --vp-c-brand-soft: #FFE7E5;
61
62
 
62
63
  --xvp-c-brand-1: var(--vp-c-indigo-1);
63
64
  --xvp-c-brand-2: var(--vp-c-indigo-2);
@@ -69,15 +70,25 @@
69
70
  --vp-c-tip-3: var(--vp-c-brand-3);
70
71
  --vp-c-tip-soft: var(--vp-c-brand-soft);
71
72
 
72
- --vp-c-warning-1: var(--vp-c-yellow-1);
73
- --vp-c-warning-2: var(--vp-c-yellow-2);
74
- --vp-c-warning-3: var(--vp-c-yellow-3);
75
- --vp-c-warning-soft: var(--vp-c-yellow-soft);
76
-
77
- --vp-c-danger-1: var(--vp-c-red-1);
78
- --vp-c-danger-2: var(--vp-c-red-2);
79
- --vp-c-danger-3: var(--vp-c-red-3);
80
- --vp-c-danger-soft: var(--vp-c-red-soft);
73
+ --xvp-c-warning-1: var(--vp-c-yellow-1);
74
+ --xvp-c-warning-2: var(--vp-c-yellow-2);
75
+ --xvp-c-warning-3: var(--vp-c-yellow-3);
76
+ --xvp-c-warning-soft: var(--vp-c-yellow-soft);
77
+
78
+ --vp-c-warning-1: #9E5601;
79
+ --vp-c-warning-2: #FDAF53;
80
+ --vp-c-warning-3: #5E3301;
81
+ --vp-c-warning-soft: #FFF3E5;
82
+
83
+ --vp-c-danger-1: #AC0E0E;
84
+ --vp-c-danger-2: #F14E4E;
85
+ --vp-c-danger-3: #4C0606;
86
+ --vp-c-danger-soft: #FFF9F9;
87
+
88
+ --xvp-c-danger-1: var(--vp-c-red-1);
89
+ --xvp-c-danger-2: var(--vp-c-red-2);
90
+ --xvp-c-danger-3: var(--vp-c-red-3);
91
+ --xvp-c-danger-soft: var(--vp-c-red-soft);
81
92
  }
82
93
 
83
94
  /**
@@ -1,66 +1,144 @@
1
1
  # Deployment
2
2
 
3
3
  Brut apps are Rack apps, so they can be deployed in conventional
4
- ways. Brut apps are 12-factor apps and the scripts used for
5
- development are inteded to work for production as well.
4
+ ways.
6
5
 
7
6
  ## Overview
8
7
 
9
- Everyone deploys apps in different ways. Brut can't provide a
10
- simple solution for all deployment setups, so this document will
11
- outline considerations when setting up deployment.
8
+ There are just too many ways to deploy. Brut attempts to address this by adhering to [12-factor principles](https://12factor.net). Brut also tries not to create artifacts like `Procfile` or `Dockerfile` that would conflict with the artifacts you'd need to manage deployment.
12
9
 
13
- The most direct way to understand what needs to happen is to look
14
- at `deploy/Dockerfile`, which is the foundation of a `Dockerfile`
15
- you can use. In particular, it shows you the commands needed to
16
- setup and run the app in production:
10
+ That said, Brut includes first-class support for deploying to Heroku using containers. More options will be included as necessary, either through direct support in code/tooling, or documentation here.
17
11
 
18
- Beyond installing system software to run any Ruby web app, as well
19
- as whatever is needed for NodeJS and Postgres, the Brut-specific
20
- parts look like so:
12
+ ### Heroku Container-based Deployment
21
13
 
22
- 1. Install Ruby Gems with `bundle install`
23
- 2. Install Node modules with `npm clean-install`
24
- 3. Build all assets with `bin/build-assets` (this will bundle all
25
- CSS and Javascript, plus copy over any other [assets](/assets)
26
- to the locations from where the Brut app will serve them)
27
- 4. Run the app with `bin/run`
14
+ When creating your Brut app with `mkbrut`, the Heroku segment can be used to create files and scripts for a [Heroku container-based deployment](https://devcenter.heroku.com/articles/container-registry-and-runtime).
28
15
 
29
- Your Brut app also includes `bin/release` which is a script
30
- intended to run in the production environment after the code has
31
- been deployed, but before the app starts up. By default, it
32
- applies any needed migrations to the database.
16
+ | File | Purpose | Notes |
17
+ |------|---------|-------|
18
+ | `bin/deploy` | Script to use to perform the deployment | This wraps `HerokuContainerBasedDeploy` in `Brut::CLI::Apps` |
19
+ | `deploy/Dockerfile` | Template `Dockerfile` used to create a `Dockerfile` for each process type | Heroku requires each process (web, worker, release, etc.) to have its own `Dockerfile` and own image |
20
+ | `deploy/heroku_config.rb` | Class that exports optional processes | By default, your app has a web and release process. `HerokuConfig` can export others, like Sidekiq |
21
+ | `deploy/docker-entrypoint` | The [`ENTRYPOINT`](https://docs.docker.com/reference/dockerfile/#entrypoint) for production Docker images, which is set up to use jemalloc | You can modify or remove this as needed |
22
+
23
+ How to deploy:
24
+
25
+ 1. Auth to Heroku from inside your dev container:
26
+
27
+ ```
28
+ your-computer> dx/exec bash
29
+ devcontainer> heroku auth:login
30
+ # You will need to copy/paste the URL to log in
31
+ devcontainer> heroku container:login
32
+ ```
33
+
34
+ 2. Create your app using the container stack:
35
+
36
+ ```
37
+ > heroku create --stack container -a «your heroku app name»
38
+ ```
39
+ 3. Ensure your app's source code is all checked in, there are no uncommitted or unadded files, and you have pushed to main.
40
+ 4. `bin/deploy`
41
+
42
+ This will generate a `Dockerfile` for each process (by default, `Dockerfile.web` and `Dockerfile.release`), build images, push those images to Heroku, and ask Heroku to release them.
43
+
44
+ Debugging Tips:
45
+
46
+ * Keep in mind it's hard to make general deployment tools. You are expected to understand your deployment and be capable of deploying an arbitrary Rack app manually. Brut's tooling automates what you need to know.
47
+ * `bin/deploy` runs the `deploy` subcommand, so `bin/deploy help deploy` can provide
48
+ some options for debugging issues:
49
+
50
+ ```
51
+ devcontainer> bin/deploy help deploy
52
+ Usage: bin/deploy [global options] deploy [command options]
53
+
54
+ Build images, push them to Heroku, and deploy them
55
+
56
+ Manages a deploy process based on using Heroku's Container Registry. See
57
+
58
+ https://devcenter.heroku.com/articles/container-registry-and-runtime
59
+
60
+ for details. You are assumed to understand this.
61
+ This command will make the process somewhat easier.
62
+
63
+ This will use deploy/Dockerfile as a template to create
64
+ one Dockerfile for each process you want to run in Heroku.
65
+ deploy/heroku_config.rb is where the processes and their
66
+ commands are configured.
67
+
68
+ The release phase is included automatically, based on bin/release.
69
+
70
+ GLOBAL OPTIONS
71
+
72
+ -h, --help Get help
73
+ --log-level=LEVEL Set log level. Allowed values: debug,
74
+ info, warn, error, fatal. Default 'fatal'
75
+ --verbose Set log level to 'debug', which will produce
76
+ maximum output
77
+
78
+ ENVIRONMENT VARIABLES
79
+
80
+ BRUT_CLI_RAISE_ON_ERROR - if set, shows backtrace on errors
81
+ LOG_LEVEL - log level if --log-level or --verbose is omitted
82
+
83
+
84
+ COMMAND OPTIONS
85
+
86
+ --platform=PLATFORM Override default platform. Can be any Docker
87
+ platform.
88
+ --[no-]dry-run Print the commands that would be run and
89
+ don't actually do anything. Implies --skip-checks
90
+ --[no-]skip-checks Skip checks for code having been
91
+ committed and pushed
92
+ --[no-]deploy After images are pushed, actually deploy them
93
+ --[no-]push After images are created, push them
94
+ to Heroku's registry. If false,
95
+ implies --no-deploy
96
+ ```
97
+ * Try building images first: `bin/deploy deploy --no-push --skip-checks`
98
+ * It's possible to run the images locally. If you are on Apple Silicon, you'll
99
+ need to set --platform:
100
+
101
+ * `bin/deploy deploy --no-push --skip-checks --platform linux/arm64`
102
+ * Create `docker-compose.yml` for your image and any other services e.g. databases
103
+ * Set required environment variables in `docker-compose.yml`
104
+ * Start up Docker compose and poke around
105
+
106
+ You'll need to have a better understanding of Docker to do this, however if you
107
+ are deploying with Docker, this is an understanding you hopefully already have.
108
+
109
+
110
+ ### Other Mechanisms for Deployment
111
+
112
+ As a Rack app, other deployments should be possible. To make the app work, you'll need to make sure a few things are dealt with:
113
+
114
+ * `RACK_ENV` **must** be `"production"`
115
+ * `bin/build-assets` will build all assets by default. This must either be done on production servers or done ahead of time and the results packaged with the app.
116
+ * `bin/build-assets` outputs files in `app/public` and `app/config`. Those files are used at runtime. Brut **will not** initiate the build of any assets.
117
+ * If you are going to build assets on production servers, you *must* included developer tooling. This means NodeJS, all modules in `package.json` and all RubyGems in `Gemfile`.
118
+
119
+ The `deploy/Dockerfile` created by `mkbrut --segment-heroku` is not very Heroku-specific and could serve as a reference.
33
120
 
34
121
  ## Testing
35
122
 
36
- If you are using Docker, you can create the `Dockerfile`s and run
37
- them locally to see how they work. You will need to have local
38
- versions of all infrastructure (database, Redis, etc.), but if
39
- these work locally, there is a high chance they work in
40
- production.
123
+ Testing deployments is a bit out of scope, but in general:
41
124
 
42
- If you are not using Docker, you will need to apply various
43
- techniques that are beyond the scope of this documentation.
125
+ * A container-based deployment can theoretically be run on your computer as a test.
126
+ * Non-production, but production-like environments can be used to validate production configurations.
127
+ * You own the means of production…not Brut.
44
128
 
45
129
  ## Recommended Practices
46
130
 
47
- Brut goes to great lengths to avoid environment-specific code.
48
- Much of Brut's behavior works the same in dev as it does in
49
- production. For example, assets are hashed in all environments.
131
+ * Avoid a lot of code that checks `Brut.container.project_env`. Try to consolidate all prod/test/dev differences in environment variables.
132
+ * Have a way to get a shell into your production environment for debugging.
133
+ * Brut doesn't log much, but if you remove the `OTEL_*` environment variables, Brut will log OTel telemetry to the console, which may be useful.
134
+ * Setting `OTEL_LOG_LEVEL=debug` is advised if the app isn't starting or you aren't seeing any telemetry or logging
50
135
 
51
- Assuming your code does the same thing, there should be a minium
52
- of surprises. That all being said, here are some recommendations:
136
+ ## Technical Notes
53
137
 
54
- * Create a way to interact with external services in a testing
55
- capacity. For example, ensure you have a test user with a known
56
- email address and trigger an email to them. Or a company credit
57
- card you charge and refund.
58
- * Configure observability so you know what your app is doing at
59
- all times.
60
- * Configure a URL that, when accessed, produces an error. This
61
- allows you to check your error reporting system.
62
- * Create a page somewhere that shows the git SHA of your
63
- deployment, or some other unique, unambiguous version number. This
64
- will clarify what version of the code is actually running.
138
+ > [!IMPORTANT]
139
+ > Technical Notes are for deeper understanding and debugging. While we will try to keep them up-to-date with changes to Brut's
140
+ > internals, the source code is always more correct.
65
141
 
142
+ _Last Updated July 3, 2025_
66
143
 
144
+ None at this time.
@@ -137,16 +137,17 @@ that the versions of these command line apps provided when you set up your app a
137
137
  While you are free to set up mise or rbenv or whatever to run all this on your computer, this way of
138
138
  working is currently not supported nor encouraged. For now, Brut will focus on the Docker-based approach.
139
139
 
140
- You are encouraged to understand how it works, especially because the Docker skills you learn in doing so
141
- will help with production deployment and operations.
140
+ The primary reason is that it's a tightly controlled environment that is almost
141
+ entirely scriptable, but does not require devs to abandon their preffered editor.
142
+ Environment manager-based approaches tend to be more fussy and require documentation
143
+ to ensure they are set up.
142
144
 
143
- Beyond this, we recommend that you keep a few things in mind with respect to automation:
145
+ Keep in mind a few things when adding your own automation:
144
146
 
145
147
  * The *Foundational Core* is bootstrapped in a degenerate environment without reliable tools beyond Bash.
146
148
  This is why it's almost entirely written in Bash, since it's available everywhere and relatively stable.
147
149
  * The *Workspace* **can and should** rely on the languages and third party modules that are part of your
148
- app. Just keep in mind that `bin/setup` can only rely on programming language runtimes and not any
149
- particular Ruby gem having been installed.
150
+ app. The only exception is `bin/setup`, since it installs third party modules. As such, it should work entirely based on Ruby and its standard library.
150
151
 
151
152
  ## Technical Notes
152
153
 
@@ -6,7 +6,7 @@ Brut attempts to use existing terminology where possible, particularly where tha
6
6
 
7
7
  When speaking about Ruby, we prefer the term *initializer* over constructor, *parameters* over arguments, and *methods* over messages. We also prefer *tests* over specs, however test files *are* located in `specs/` and named `*.spec.rb` to be consistent with RSpec's nomenclature. We prefer *end-to-end* or *e2e* tests instead of browser tests or request specs.
8
8
 
9
- Further, Brut doesn't render HTML, it *generates* it. The browser renders the HTML for the website's visitor.
9
+ Further, Brut doesn't render HTML, it *generates* it. The browser renders the HTML for the website's visitor. One exception is Phlex, which uses the term *render* to mean "generate HTML to an internal buffer that will be delivered to the client later". Thus, you will need to call Phlex's `render` method from time to time, even though it is not rendering HTML but helping to generate it.
10
10
 
11
11
  Lastly, the documentation tries to talk about the person accessing a website as a "vistor" not a "user". Though the "user" nomenclature is near-ossified in software development, we feel "visitor" is more apt.
12
12