brut 0.14.0 → 0.16.0.pre

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 (569) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -0
  3. data/CHANGELOG.md +10 -0
  4. data/Dockerfile.dx +3 -6
  5. data/Gemfile.lock +1 -1
  6. data/brut-css/package-lock.json +2 -2
  7. data/brut-css/package.json +1 -1
  8. data/brut-js/package-lock.json +2 -2
  9. data/brut-js/package.json +1 -1
  10. data/brut-js/specs/Toast.spec.js +34 -0
  11. data/brut-js/src/I18nTranslation.js +3 -0
  12. data/brut-js/src/Message.js +9 -3
  13. data/brut-js/src/RichString.js +4 -1
  14. data/brut-js/src/Toast.js +102 -0
  15. data/brut-js/src/index.js +3 -0
  16. data/brutrb.com/.vitepress/config.mjs +4 -3
  17. data/brutrb.com/brut-js.md +1 -0
  18. data/brutrb.com/deployment.md +23 -9
  19. data/brutrb.com/jobs.md +107 -7
  20. data/brutrb.com/recipes/dev-env-secrets.md +87 -0
  21. data/brutrb.com/roadmap.md +2 -7
  22. data/docs/404.html +3 -3
  23. data/docs/adrs.html +7 -7
  24. data/docs/ai.html +7 -7
  25. data/docs/api/Brut/BackEnd/SeedData.html +1 -1
  26. data/docs/api/Brut/BackEnd/Sidekiq/Middlewares/Server/FlushSpans.html +1 -1
  27. data/docs/api/Brut/BackEnd/Sidekiq/Middlewares/Server.html +1 -1
  28. data/docs/api/Brut/BackEnd/Sidekiq/Middlewares.html +1 -1
  29. data/docs/api/Brut/BackEnd/Sidekiq.html +1 -1
  30. data/docs/api/Brut/BackEnd/Validators/FormValidator.html +1 -1
  31. data/docs/api/Brut/BackEnd/Validators.html +1 -1
  32. data/docs/api/Brut/BackEnd.html +1 -1
  33. data/docs/api/Brut/CLI/App.html +1 -1
  34. data/docs/api/Brut/CLI/AppRunner.html +1 -1
  35. data/docs/api/Brut/CLI/Apps/BuildAssets/All.html +1 -1
  36. data/docs/api/Brut/CLI/Apps/BuildAssets/CSS.html +1 -1
  37. data/docs/api/Brut/CLI/Apps/BuildAssets/Images.html +1 -1
  38. data/docs/api/Brut/CLI/Apps/BuildAssets/JS.html +1 -1
  39. data/docs/api/Brut/CLI/Apps/BuildAssets.html +1 -1
  40. data/docs/api/Brut/CLI/Apps/DB/Create.html +1 -1
  41. data/docs/api/Brut/CLI/Apps/DB/Drop.html +1 -1
  42. data/docs/api/Brut/CLI/Apps/DB/Migrate.html +1 -1
  43. data/docs/api/Brut/CLI/Apps/DB/NewMigration.html +1 -1
  44. data/docs/api/Brut/CLI/Apps/DB/Rebuild.html +1 -1
  45. data/docs/api/Brut/CLI/Apps/DB/Seed.html +1 -1
  46. data/docs/api/Brut/CLI/Apps/DB/Status.html +1 -1
  47. data/docs/api/Brut/CLI/Apps/DB.html +1 -1
  48. data/docs/api/Brut/CLI/Apps/DeployBase/GitChecks.html +1 -1
  49. data/docs/api/Brut/CLI/Apps/DeployBase.html +1 -1
  50. data/docs/api/Brut/CLI/Apps/HerokuContainerBasedDeploy/Deploy.html +1 -1
  51. data/docs/api/Brut/CLI/Apps/HerokuContainerBasedDeploy.html +1 -1
  52. data/docs/api/Brut/CLI/Apps/Scaffold/Action/Route.html +1 -1
  53. data/docs/api/Brut/CLI/Apps/Scaffold/Action.html +1 -1
  54. data/docs/api/Brut/CLI/Apps/Scaffold/Component.html +1 -1
  55. data/docs/api/Brut/CLI/Apps/Scaffold/CustomElementTest.html +1 -1
  56. data/docs/api/Brut/CLI/Apps/Scaffold/DbModel.html +1 -1
  57. data/docs/api/Brut/CLI/Apps/Scaffold/E2ETest.html +1 -1
  58. data/docs/api/Brut/CLI/Apps/Scaffold/Form.html +1 -1
  59. data/docs/api/Brut/CLI/Apps/Scaffold/Page/Route.html +1 -1
  60. data/docs/api/Brut/CLI/Apps/Scaffold/Page.html +1 -1
  61. data/docs/api/Brut/CLI/Apps/Scaffold/RoutesEditor.html +1 -1
  62. data/docs/api/Brut/CLI/Apps/Scaffold/Test.html +1 -1
  63. data/docs/api/Brut/CLI/Apps/Scaffold.html +1 -1
  64. data/docs/api/Brut/CLI/Apps/Test/Audit.html +1 -1
  65. data/docs/api/Brut/CLI/Apps/Test/E2e.html +1 -1
  66. data/docs/api/Brut/CLI/Apps/Test/JS.html +1 -1
  67. data/docs/api/Brut/CLI/Apps/Test/Run.html +1 -1
  68. data/docs/api/Brut/CLI/Apps/Test.html +1 -1
  69. data/docs/api/Brut/CLI/Apps.html +1 -1
  70. data/docs/api/Brut/CLI/Command.html +1 -1
  71. data/docs/api/Brut/CLI/Error.html +1 -1
  72. data/docs/api/Brut/CLI/ExecutionResults/Result.html +1 -1
  73. data/docs/api/Brut/CLI/ExecutionResults.html +1 -1
  74. data/docs/api/Brut/CLI/Executor.html +1 -1
  75. data/docs/api/Brut/CLI/InvalidOption.html +1 -1
  76. data/docs/api/Brut/CLI/Options.html +1 -1
  77. data/docs/api/Brut/CLI/Output.html +1 -1
  78. data/docs/api/Brut/CLI/SystemExecError.html +1 -1
  79. data/docs/api/Brut/CLI.html +1 -1
  80. data/docs/api/Brut/FactoryBot.html +1 -1
  81. data/docs/api/Brut/Framework/App.html +1 -1
  82. data/docs/api/Brut/Framework/Config.html +1 -1
  83. data/docs/api/Brut/Framework/Container.html +1 -1
  84. data/docs/api/Brut/Framework/Error.html +1 -1
  85. data/docs/api/Brut/Framework/Errors/AbstractMethod.html +1 -1
  86. data/docs/api/Brut/Framework/Errors/Bug.html +1 -1
  87. data/docs/api/Brut/Framework/Errors/MissingConfiguration.html +1 -1
  88. data/docs/api/Brut/Framework/Errors/MissingParameter.html +1 -1
  89. data/docs/api/Brut/Framework/Errors/NoClassForPath.html +1 -1
  90. data/docs/api/Brut/Framework/Errors/NotFound.html +1 -1
  91. data/docs/api/Brut/Framework/Errors/NotImplemented.html +1 -1
  92. data/docs/api/Brut/Framework/Errors.html +1 -1
  93. data/docs/api/Brut/Framework/FussyTypeEnforcement.html +1 -1
  94. data/docs/api/Brut/Framework/MCP.html +1 -1
  95. data/docs/api/Brut/Framework/ProjectEnvironment.html +1 -1
  96. data/docs/api/Brut/Framework.html +1 -1
  97. data/docs/api/Brut/FrontEnd/AssetPathResolver.html +1 -1
  98. data/docs/api/Brut/FrontEnd/Component/Helpers.html +1 -1
  99. data/docs/api/Brut/FrontEnd/Component.html +1 -1
  100. data/docs/api/Brut/FrontEnd/Components/ConstraintViolations.html +1 -1
  101. data/docs/api/Brut/FrontEnd/Components/FormTag.html +1 -1
  102. data/docs/api/Brut/FrontEnd/Components/I18nTranslations.html +1 -1
  103. data/docs/api/Brut/FrontEnd/Components/Input.html +1 -1
  104. data/docs/api/Brut/FrontEnd/Components/Inputs/ButtonTag.html +1 -1
  105. data/docs/api/Brut/FrontEnd/Components/Inputs/CsrfToken.html +1 -1
  106. data/docs/api/Brut/FrontEnd/Components/Inputs/InputTag.html +1 -1
  107. data/docs/api/Brut/FrontEnd/Components/Inputs/RadioButton.html +1 -1
  108. data/docs/api/Brut/FrontEnd/Components/Inputs/SelectTagWithOptions.html +1 -1
  109. data/docs/api/Brut/FrontEnd/Components/Inputs/TextareaTag.html +1 -1
  110. data/docs/api/Brut/FrontEnd/Components/Inputs.html +1 -1
  111. data/docs/api/Brut/FrontEnd/Components/LocaleDetection.html +1 -1
  112. data/docs/api/Brut/FrontEnd/Components/PageIdentifier.html +37 -18
  113. data/docs/api/Brut/FrontEnd/Components/TimeTag.html +1 -1
  114. data/docs/api/Brut/FrontEnd/Components/Traceparent.html +1 -1
  115. data/docs/api/Brut/FrontEnd/Components.html +1 -1
  116. data/docs/api/Brut/FrontEnd/CsrfProtector.html +1 -1
  117. data/docs/api/Brut/FrontEnd/Download.html +1 -1
  118. data/docs/api/Brut/FrontEnd/Flash.html +1 -1
  119. data/docs/api/Brut/FrontEnd/Form.html +1 -1
  120. data/docs/api/Brut/FrontEnd/Forms/Button.html +1 -1
  121. data/docs/api/Brut/FrontEnd/Forms/ButtonInputDefinition.html +1 -1
  122. data/docs/api/Brut/FrontEnd/Forms/ConstraintViolation.html +1 -1
  123. data/docs/api/Brut/FrontEnd/Forms/Input/Color.html +1 -1
  124. data/docs/api/Brut/FrontEnd/Forms/Input/TimeOfDay.html +1 -1
  125. data/docs/api/Brut/FrontEnd/Forms/Input.html +1 -1
  126. data/docs/api/Brut/FrontEnd/Forms/InputDeclarations.html +1 -1
  127. data/docs/api/Brut/FrontEnd/Forms/InputDefinition.html +1 -1
  128. data/docs/api/Brut/FrontEnd/Forms/RadioButtonGroupInput.html +1 -1
  129. data/docs/api/Brut/FrontEnd/Forms/RadioButtonGroupInputDefinition.html +1 -1
  130. data/docs/api/Brut/FrontEnd/Forms/SelectInput.html +1 -1
  131. data/docs/api/Brut/FrontEnd/Forms/SelectInputDefinition.html +1 -1
  132. data/docs/api/Brut/FrontEnd/Forms/ValidityState.html +1 -1
  133. data/docs/api/Brut/FrontEnd/Forms.html +1 -1
  134. data/docs/api/Brut/FrontEnd/GenericResponse.html +1 -1
  135. data/docs/api/Brut/FrontEnd/Handler.html +1 -1
  136. data/docs/api/Brut/FrontEnd/Handlers/CspReportingHandler.html +1 -1
  137. data/docs/api/Brut/FrontEnd/Handlers/InstrumentationHandler/TraceParent.html +1 -1
  138. data/docs/api/Brut/FrontEnd/Handlers/InstrumentationHandler.html +1 -1
  139. data/docs/api/Brut/FrontEnd/Handlers/LocaleDetectionHandler.html +1 -1
  140. data/docs/api/Brut/FrontEnd/Handlers/MissingHandler/Form.html +1 -1
  141. data/docs/api/Brut/FrontEnd/Handlers/MissingHandler.html +1 -1
  142. data/docs/api/Brut/FrontEnd/Handlers.html +1 -1
  143. data/docs/api/Brut/FrontEnd/HandlingResults.html +1 -1
  144. data/docs/api/Brut/FrontEnd/HttpMethod.html +1 -1
  145. data/docs/api/Brut/FrontEnd/HttpStatus.html +1 -1
  146. data/docs/api/Brut/FrontEnd/InlineSvgLocator.html +1 -1
  147. data/docs/api/Brut/FrontEnd/Layout.html +171 -3
  148. data/docs/api/Brut/FrontEnd/Middleware.html +1 -1
  149. data/docs/api/Brut/FrontEnd/Middlewares/AnnotateBrutOwnedPaths.html +1 -1
  150. data/docs/api/Brut/FrontEnd/Middlewares/Favicon.html +1 -1
  151. data/docs/api/Brut/FrontEnd/Middlewares/OpenTelemetrySpan.html +1 -1
  152. data/docs/api/Brut/FrontEnd/Middlewares/ReloadApp.html +1 -1
  153. data/docs/api/Brut/FrontEnd/Middlewares.html +1 -1
  154. data/docs/api/Brut/FrontEnd/Page.html +1 -1
  155. data/docs/api/Brut/FrontEnd/Pages/MissingPage.html +1 -1
  156. data/docs/api/Brut/FrontEnd/Pages.html +1 -1
  157. data/docs/api/Brut/FrontEnd/RequestContext.html +1 -1
  158. data/docs/api/Brut/FrontEnd/RouteHook.html +1 -1
  159. data/docs/api/Brut/FrontEnd/RouteHooks/AgeFlash.html +1 -1
  160. data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineScripts.html +1 -1
  161. data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineStylesOrScripts/ReportOnly.html +1 -1
  162. data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineStylesOrScripts.html +1 -1
  163. data/docs/api/Brut/FrontEnd/RouteHooks/LocaleDetection.html +1 -1
  164. data/docs/api/Brut/FrontEnd/RouteHooks/SetupRequestContext.html +1 -1
  165. data/docs/api/Brut/FrontEnd/RouteHooks.html +1 -1
  166. data/docs/api/Brut/FrontEnd/Routing/FormHandlerRoute.html +1 -1
  167. data/docs/api/Brut/FrontEnd/Routing/FormRoute.html +1 -1
  168. data/docs/api/Brut/FrontEnd/Routing/MissingForm.html +1 -1
  169. data/docs/api/Brut/FrontEnd/Routing/MissingHandler.html +1 -1
  170. data/docs/api/Brut/FrontEnd/Routing/MissingPage.html +1 -1
  171. data/docs/api/Brut/FrontEnd/Routing/MissingPath.html +1 -1
  172. data/docs/api/Brut/FrontEnd/Routing/PageRoute.html +1 -1
  173. data/docs/api/Brut/FrontEnd/Routing/Route.html +1 -1
  174. data/docs/api/Brut/FrontEnd/Routing.html +1 -1
  175. data/docs/api/Brut/FrontEnd/Session.html +1 -1
  176. data/docs/api/Brut/FrontEnd.html +1 -1
  177. data/docs/api/Brut/I18n/BaseMethods.html +1 -1
  178. data/docs/api/Brut/I18n/ForBackEnd.html +1 -1
  179. data/docs/api/Brut/I18n/ForCLI.html +1 -1
  180. data/docs/api/Brut/I18n/ForHTML.html +1 -1
  181. data/docs/api/Brut/I18n/HTTPAcceptLanguage/AlwaysEnglish.html +1 -1
  182. data/docs/api/Brut/I18n/HTTPAcceptLanguage.html +1 -1
  183. data/docs/api/Brut/I18n.html +1 -1
  184. data/docs/api/Brut/Instrumentation/LoggerSpanExporter.html +1 -1
  185. data/docs/api/Brut/Instrumentation/Methods/ClassMethods.html +1 -1
  186. data/docs/api/Brut/Instrumentation/Methods.html +1 -1
  187. data/docs/api/Brut/Instrumentation/OpenTelemetry/NormalizedAttributes.html +1 -1
  188. data/docs/api/Brut/Instrumentation/OpenTelemetry/Span.html +1 -1
  189. data/docs/api/Brut/Instrumentation/OpenTelemetry.html +1 -1
  190. data/docs/api/Brut/Instrumentation.html +1 -1
  191. data/docs/api/Brut/RubocopConfig.html +1 -1
  192. data/docs/api/Brut/SinatraHelpers/ClassMethods.html +1 -1
  193. data/docs/api/Brut/SinatraHelpers.html +1 -1
  194. data/docs/api/Brut/SpecSupport/ClockSupport.html +1 -1
  195. data/docs/api/Brut/SpecSupport/ComponentSupport.html +1 -1
  196. data/docs/api/Brut/SpecSupport/E2ETestServer.html +1 -1
  197. data/docs/api/Brut/SpecSupport/E2eSupport.html +1 -1
  198. data/docs/api/Brut/SpecSupport/EnhancedNode.html +1 -1
  199. data/docs/api/Brut/SpecSupport/FlashSupport.html +1 -1
  200. data/docs/api/Brut/SpecSupport/GeneralSupport/ClassMethods.html +1 -1
  201. data/docs/api/Brut/SpecSupport/GeneralSupport.html +1 -1
  202. data/docs/api/Brut/SpecSupport/HandlerSupport.html +1 -1
  203. data/docs/api/Brut/SpecSupport/Matchers/BeABug.html +1 -1
  204. data/docs/api/Brut/SpecSupport/Matchers/BePageFor.html +1 -1
  205. data/docs/api/Brut/SpecSupport/Matchers/BeRoutingFor.html +1 -1
  206. data/docs/api/Brut/SpecSupport/Matchers/HaveConstraintViolation.html +1 -1
  207. data/docs/api/Brut/SpecSupport/Matchers/HaveGenerated.html +1 -1
  208. data/docs/api/Brut/SpecSupport/Matchers/HaveHTMLAttribute.html +1 -1
  209. data/docs/api/Brut/SpecSupport/Matchers/HaveI18nString.html +1 -1
  210. data/docs/api/Brut/SpecSupport/Matchers/HaveLinkTo.html +1 -1
  211. data/docs/api/Brut/SpecSupport/Matchers/HaveRedirectedTo.html +1 -1
  212. data/docs/api/Brut/SpecSupport/Matchers/HaveReturnedHttpStatus.html +1 -1
  213. data/docs/api/Brut/SpecSupport/Matchers/HaveReturnedRackResponse.html +1 -1
  214. data/docs/api/Brut/SpecSupport/Matchers.html +1 -1
  215. data/docs/api/Brut/SpecSupport/RSpecSetup/OptionalSidekiqSupport.html +1 -1
  216. data/docs/api/Brut/SpecSupport/RSpecSetup.html +1 -1
  217. data/docs/api/Brut/SpecSupport/SessionSupport.html +1 -1
  218. data/docs/api/Brut/SpecSupport.html +1 -1
  219. data/docs/api/Brut.html +1 -1
  220. data/docs/api/Clock.html +1 -1
  221. data/docs/api/ModuleName.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 +1 -1
  225. data/docs/api/Sequel/Extensions/BrutMigrations.html +1 -1
  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/_index.html +1 -1
  237. data/docs/api/file.README.html +1 -1
  238. data/docs/api/index.html +1 -1
  239. data/docs/api/method_list.html +157 -141
  240. data/docs/api/top-level-namespace.html +1 -1
  241. data/docs/assets/adrs.md.YglbWtQe.js +1 -0
  242. data/docs/assets/adrs.md.YglbWtQe.lean.js +1 -0
  243. data/docs/assets/ai.md.ChLnvDAX.js +1 -0
  244. data/docs/assets/ai.md.ChLnvDAX.lean.js +1 -0
  245. data/docs/assets/{app.BDtsVxyd.js → app.Dm7v_ouO.js} +1 -1
  246. data/docs/assets/{assets.md.7C3HWkga.js → assets.md.BEF6Oz6K.js} +2 -2
  247. data/docs/assets/assets.md.BEF6Oz6K.lean.js +1 -0
  248. data/docs/assets/{brut-js.md.B4GYxQVw.js → brut-js.md.BMz0X1Rz.js} +2 -2
  249. data/docs/assets/brut-js.md.BMz0X1Rz.lean.js +1 -0
  250. data/docs/assets/business-logic.md.DbuaOYGU.js +1 -0
  251. data/docs/assets/business-logic.md.DbuaOYGU.lean.js +1 -0
  252. data/docs/assets/chunks/@localSearchIndexroot.BiNFswvo.js +1 -0
  253. data/docs/assets/chunks/VPLocalSearchBox.DQK6jQou.js +8 -0
  254. data/docs/assets/chunks/framework.C4nOkCZI.js +18 -0
  255. data/docs/assets/chunks/{theme.DZKmijwi.js → theme.BuExsdM9.js} +2 -2
  256. data/docs/assets/{cli.md.CjsktgFz.js → cli.md.DDMar_51.js} +2 -2
  257. data/docs/assets/cli.md.DDMar_51.lean.js +1 -0
  258. data/docs/assets/{components.md.rMhQ0WdZ.js → components.md.Dfd3w6UW.js} +5 -5
  259. data/docs/assets/components.md.Dfd3w6UW.lean.js +1 -0
  260. data/docs/assets/{configuration.md.BK42Yjp_.js → configuration.md.DTYoV2Ea.js} +2 -2
  261. data/docs/assets/configuration.md.DTYoV2Ea.lean.js +1 -0
  262. data/docs/assets/{css.md.CltvJqAa.js → css.md.K5rOCOQY.js} +2 -2
  263. data/docs/assets/css.md.K5rOCOQY.lean.js +1 -0
  264. data/docs/assets/{custom-element-tests.md.B_rbta32.js → custom-element-tests.md.DiLe-eFw.js} +2 -2
  265. data/docs/assets/custom-element-tests.md.DiLe-eFw.lean.js +1 -0
  266. data/docs/assets/{database-access.md.gnluu54N.js → database-access.md.Dc8l2Plf.js} +2 -2
  267. data/docs/assets/database-access.md.Dc8l2Plf.lean.js +1 -0
  268. data/docs/assets/{database-schema.md.LpmBPVEU.js → database-schema.md.BJ_JhXmO.js} +2 -2
  269. data/docs/assets/database-schema.md.BJ_JhXmO.lean.js +1 -0
  270. data/docs/assets/{deployment.md.BLseERGV.js → deployment.md.C1u5ep0g.js} +2 -2
  271. data/docs/assets/deployment.md.C1u5ep0g.lean.js +1 -0
  272. data/docs/assets/{dev-environment.md.DRH2D2-O.js → dev-environment.md.B1S9p5ZK.js} +2 -2
  273. data/docs/assets/{dev-environment.md.DRH2D2-O.lean.js → dev-environment.md.B1S9p5ZK.lean.js} +1 -1
  274. data/docs/assets/{dir-structure.md.CWir1pic.js → dir-structure.md.D1T2kGwj.js} +2 -2
  275. data/docs/assets/dir-structure.md.D1T2kGwj.lean.js +1 -0
  276. data/docs/assets/doc-conventions.md.CDnWaEFg.js +1 -0
  277. data/docs/assets/doc-conventions.md.CDnWaEFg.lean.js +1 -0
  278. data/docs/assets/{end-to-end-tests.md.DzqRpZ43.js → end-to-end-tests.md.BJJdNDYL.js} +2 -2
  279. data/docs/assets/end-to-end-tests.md.BJJdNDYL.lean.js +1 -0
  280. data/docs/assets/{features.md.DPFXsy0z.js → features.md.BDWxnyNO.js} +2 -2
  281. data/docs/assets/features.md.BDWxnyNO.lean.js +1 -0
  282. data/docs/assets/{flash-and-session.md.nPvUpnUx.js → flash-and-session.md.CUsMxoNl.js} +2 -2
  283. data/docs/assets/flash-and-session.md.CUsMxoNl.lean.js +1 -0
  284. data/docs/assets/{form-constraints.md.KTv5cdR4.js → form-constraints.md.KlfXSKm2.js} +2 -2
  285. data/docs/assets/form-constraints.md.KlfXSKm2.lean.js +1 -0
  286. data/docs/assets/{forms.md.v9qIbmUM.js → forms.md.DEkmJUvb.js} +3 -3
  287. data/docs/assets/forms.md.DEkmJUvb.lean.js +1 -0
  288. data/docs/assets/{getting-started.md.DTOl4c2g.js → getting-started.md.DO-4eoGW.js} +4 -4
  289. data/docs/assets/getting-started.md.DO-4eoGW.lean.js +1 -0
  290. data/docs/assets/{handlers.md.h84MMB1R.js → handlers.md.C5tUwmmo.js} +2 -2
  291. data/docs/assets/handlers.md.C5tUwmmo.lean.js +1 -0
  292. data/docs/assets/{hooks.md.Jmb5VOLA.js → hooks.md.CoiYCKRc.js} +2 -2
  293. data/docs/assets/hooks.md.CoiYCKRc.lean.js +1 -0
  294. data/docs/assets/{i18n.md.BAm9t9JJ.js → i18n.md.DxkCKhUw.js} +2 -2
  295. data/docs/assets/i18n.md.DxkCKhUw.lean.js +1 -0
  296. data/docs/assets/{index.md.Bn9e0sRJ.js → index.md.DnphWyQd.js} +1 -1
  297. data/docs/assets/{index.md.Bn9e0sRJ.lean.js → index.md.DnphWyQd.lean.js} +1 -1
  298. data/docs/assets/{instrumentation.md._lNSriEZ.js → instrumentation.md.BcxjC4jd.js} +2 -2
  299. data/docs/assets/instrumentation.md.BcxjC4jd.lean.js +1 -0
  300. data/docs/assets/{javascript.md.DzrMxUmI.js → javascript.md.D6fxhaQb.js} +2 -2
  301. data/docs/assets/javascript.md.D6fxhaQb.lean.js +1 -0
  302. data/docs/assets/jobs.md.Bc7Y1YpK.js +1 -0
  303. data/docs/assets/jobs.md.Bc7Y1YpK.lean.js +1 -0
  304. data/docs/assets/{keyword-injection.md.95Zgh2eN.js → keyword-injection.md.CqLnnzIz.js} +2 -2
  305. data/docs/assets/keyword-injection.md.CqLnnzIz.lean.js +1 -0
  306. data/docs/assets/layouts.md.HEbeK7Jr.js +68 -0
  307. data/docs/assets/layouts.md.HEbeK7Jr.lean.js +1 -0
  308. data/docs/assets/lsp.md.bE9dW8n9.js +1 -0
  309. data/docs/assets/lsp.md.bE9dW8n9.lean.js +1 -0
  310. data/docs/assets/{markdown-examples.md.CCFEQO44.js → markdown-examples.md.BPmtHlc-.js} +2 -2
  311. data/docs/assets/markdown-examples.md.BPmtHlc-.lean.js +1 -0
  312. data/docs/assets/{middleware.md.Czz_UlJN.js → middleware.md.BhOIsg59.js} +2 -2
  313. data/docs/assets/middleware.md.BhOIsg59.lean.js +1 -0
  314. data/docs/assets/overview.md.BpWAgPFH.js +1 -0
  315. data/docs/assets/overview.md.BpWAgPFH.lean.js +1 -0
  316. data/docs/assets/{pages.md.B7Hc-i6H.js → pages.md.B3sQXpEd.js} +2 -2
  317. data/docs/assets/pages.md.B3sQXpEd.lean.js +1 -0
  318. data/docs/assets/{recipes_alternate-layouts.md.BwEytl59.js → recipes_alternate-layouts.md.C1QzVkA7.js} +2 -2
  319. data/docs/assets/recipes_alternate-layouts.md.C1QzVkA7.lean.js +1 -0
  320. data/docs/assets/{recipes_authentication.md.nwO6F7Ou.js → recipes_authentication.md.CyvoIW82.js} +2 -2
  321. data/docs/assets/recipes_authentication.md.CyvoIW82.lean.js +1 -0
  322. data/docs/assets/{recipes_custom-flash.md.CrQbI5eH.js → recipes_custom-flash.md.6gFqf2uL.js} +2 -2
  323. data/docs/assets/recipes_custom-flash.md.6gFqf2uL.lean.js +1 -0
  324. data/docs/assets/{recipes_form-errors.md.Bv5RCKqH.js → recipes_form-errors.md.B5ptSzMO.js} +2 -2
  325. data/docs/assets/recipes_form-errors.md.B5ptSzMO.lean.js +1 -0
  326. data/docs/assets/{recipes_indexed-forms.md.CstYyOSo.js → recipes_indexed-forms.md.BYYQGW2C.js} +2 -2
  327. data/docs/assets/recipes_indexed-forms.md.BYYQGW2C.lean.js +1 -0
  328. data/docs/assets/{recipes_migrations.md.CTcnWDJF.js → recipes_migrations.md.Cid7-3cu.js} +2 -2
  329. data/docs/assets/recipes_migrations.md.Cid7-3cu.lean.js +1 -0
  330. data/docs/assets/{recipes_text-field-component.md.H4wLAK0Z.js → recipes_text-field-component.md.VhOsCtKI.js} +2 -2
  331. data/docs/assets/recipes_text-field-component.md.VhOsCtKI.lean.js +1 -0
  332. data/docs/assets/roadmap.md.CJsbUmK_.js +1 -0
  333. data/docs/assets/roadmap.md.CJsbUmK_.lean.js +1 -0
  334. data/docs/assets/{routes.md.BD6y2i-f.js → routes.md.C1dgIBtD.js} +2 -2
  335. data/docs/assets/routes.md.C1dgIBtD.lean.js +1 -0
  336. data/docs/assets/security.md.Jn4SY1uK.js +1 -0
  337. data/docs/assets/security.md.Jn4SY1uK.lean.js +1 -0
  338. data/docs/assets/{seed-data.md.BvFZlqIk.js → seed-data.md.UZW0WxYN.js} +2 -2
  339. data/docs/assets/seed-data.md.UZW0WxYN.lean.js +1 -0
  340. data/docs/assets/space-time-continuum.md.D9rYGDFH.js +1 -0
  341. data/docs/assets/space-time-continuum.md.D9rYGDFH.lean.js +1 -0
  342. data/docs/assets/{tutorial.md.BM40jnoq.js → tutorial.md.BX6f6l00.js} +2 -2
  343. data/docs/assets/tutorial.md.BX6f6l00.lean.js +1 -0
  344. data/docs/assets/{tutorials_01-intro.md.B4sUBY3X.js → tutorials_01-intro.md.CzZ3kpF_.js} +2 -2
  345. data/docs/assets/{tutorials_01-intro.md.B4sUBY3X.lean.js → tutorials_01-intro.md.CzZ3kpF_.lean.js} +1 -1
  346. data/docs/assets/{tutorials_02-dialog.md.CPNK1SC_.js → tutorials_02-dialog.md.D2vSjDVf.js} +2 -2
  347. data/docs/assets/{tutorials_02-dialog.md.CPNK1SC_.lean.js → tutorials_02-dialog.md.D2vSjDVf.lean.js} +1 -1
  348. data/docs/assets/{unit-tests.md.DUGrnLj5.js → unit-tests.md.vDsdBbO_.js} +2 -2
  349. data/docs/assets/unit-tests.md.vDsdBbO_.lean.js +1 -0
  350. data/docs/assets/why.md.4WpxdrQ2.js +1 -0
  351. data/docs/assets/why.md.4WpxdrQ2.lean.js +1 -0
  352. data/docs/assets.html +7 -7
  353. data/docs/brut-js/api/AjaxSubmit.html +2 -2
  354. data/docs/brut-js/api/AjaxSubmit.js.html +2 -2
  355. data/docs/brut-js/api/Autosubmit.html +2 -2
  356. data/docs/brut-js/api/Autosubmit.js.html +2 -2
  357. data/docs/brut-js/api/BaseCustomElement.html +2 -2
  358. data/docs/brut-js/api/BaseCustomElement.js.html +2 -2
  359. data/docs/brut-js/api/BrutCustomElements.html +3 -3
  360. data/docs/brut-js/api/BufferedLogger.html +2 -2
  361. data/docs/brut-js/api/ConfirmSubmit.html +2 -2
  362. data/docs/brut-js/api/ConfirmSubmit.js.html +2 -2
  363. data/docs/brut-js/api/ConfirmationDialog.html +2 -2
  364. data/docs/brut-js/api/ConfirmationDialog.js.html +2 -2
  365. data/docs/brut-js/api/ConstraintViolationMessage.html +2 -2
  366. data/docs/brut-js/api/ConstraintViolationMessage.js.html +2 -2
  367. data/docs/brut-js/api/ConstraintViolationMessages.html +2 -2
  368. data/docs/brut-js/api/ConstraintViolationMessages.js.html +2 -2
  369. data/docs/brut-js/api/CopyToClipboard.html +2 -2
  370. data/docs/brut-js/api/CopyToClipboard.js.html +2 -2
  371. data/docs/brut-js/api/Form.html +2 -2
  372. data/docs/brut-js/api/Form.js.html +2 -2
  373. data/docs/brut-js/api/I18nTranslation.html +2 -2
  374. data/docs/brut-js/api/I18nTranslation.js.html +5 -2
  375. data/docs/brut-js/api/LocaleDetection.html +2 -2
  376. data/docs/brut-js/api/LocaleDetection.js.html +2 -2
  377. data/docs/brut-js/api/Logger.html +2 -2
  378. data/docs/brut-js/api/Logger.js.html +2 -2
  379. data/docs/brut-js/api/Message.html +2 -2
  380. data/docs/brut-js/api/Message.js.html +11 -5
  381. data/docs/brut-js/api/PrefixedLogger.html +2 -2
  382. data/docs/brut-js/api/RichString.html +9 -9
  383. data/docs/brut-js/api/RichString.js.html +6 -3
  384. data/docs/brut-js/api/Tabs.html +2 -2
  385. data/docs/brut-js/api/Tabs.js.html +2 -2
  386. data/docs/brut-js/api/Toast.html +270 -0
  387. data/docs/brut-js/api/Toast.js.html +153 -0
  388. data/docs/brut-js/api/Tracing.html +2 -2
  389. data/docs/brut-js/api/Tracing.js.html +2 -2
  390. data/docs/brut-js/api/external-CustomElementRegistry.html +3 -3
  391. data/docs/brut-js/api/external-Performance.html +3 -3
  392. data/docs/brut-js/api/external-Promise.html +3 -3
  393. data/docs/brut-js/api/external-ValidityState.html +3 -3
  394. data/docs/brut-js/api/external-Window.html +4 -4
  395. data/docs/brut-js/api/external-fetch.html +3 -3
  396. data/docs/brut-js/api/global.html +3 -3
  397. data/docs/brut-js/api/index.html +2 -2
  398. data/docs/brut-js/api/index.js.html +5 -2
  399. data/docs/brut-js/api/module-testing.html +2 -2
  400. data/docs/brut-js/api/testing.AssetMetadata.html +2 -2
  401. data/docs/brut-js/api/testing.AssetMetadataLoader.html +2 -2
  402. data/docs/brut-js/api/testing.CustomElementTest.html +2 -2
  403. data/docs/brut-js/api/testing.DOMCreator.html +2 -2
  404. data/docs/brut-js/api/testing_AssetMetadata.js.html +2 -2
  405. data/docs/brut-js/api/testing_AssetMetadataLoader.js.html +2 -2
  406. data/docs/brut-js/api/testing_CustomElementTest.js.html +2 -2
  407. data/docs/brut-js/api/testing_DOMCreator.js.html +2 -2
  408. data/docs/brut-js/api/testing_index.js.html +2 -2
  409. data/docs/brut-js.html +8 -8
  410. data/docs/business-logic.html +7 -7
  411. data/docs/cli.html +7 -7
  412. data/docs/components.html +10 -10
  413. data/docs/configuration.html +7 -7
  414. data/docs/css.html +7 -7
  415. data/docs/custom-element-tests.html +7 -7
  416. data/docs/database-access.html +7 -7
  417. data/docs/database-schema.html +7 -7
  418. data/docs/deployment.html +7 -7
  419. data/docs/dev-environment.html +7 -7
  420. data/docs/dir-structure.html +7 -7
  421. data/docs/doc-conventions.html +7 -7
  422. data/docs/end-to-end-tests.html +7 -7
  423. data/docs/features.html +7 -7
  424. data/docs/flash-and-session.html +7 -7
  425. data/docs/form-constraints.html +7 -7
  426. data/docs/forms.html +8 -8
  427. data/docs/getting-started.html +9 -9
  428. data/docs/handlers.html +7 -7
  429. data/docs/hashmap.json +1 -1
  430. data/docs/hooks.html +7 -7
  431. data/docs/i18n.html +7 -7
  432. data/docs/index.html +6 -6
  433. data/docs/instrumentation.html +7 -7
  434. data/docs/javascript.html +7 -7
  435. data/docs/jobs.html +7 -7
  436. data/docs/keyword-injection.html +7 -7
  437. data/docs/layouts.html +42 -12
  438. data/docs/lsp.html +7 -7
  439. data/docs/markdown-examples.html +7 -7
  440. data/docs/middleware.html +7 -7
  441. data/docs/overview.html +7 -7
  442. data/docs/pages.html +7 -7
  443. data/docs/recipes/alternate-layouts.html +8 -8
  444. data/docs/recipes/authentication.html +7 -7
  445. data/docs/recipes/custom-flash.html +8 -8
  446. data/docs/recipes/form-errors.html +7 -7
  447. data/docs/recipes/indexed-forms.html +7 -7
  448. data/docs/recipes/migrations.html +7 -7
  449. data/docs/recipes/text-field-component.html +7 -7
  450. data/docs/roadmap.html +7 -7
  451. data/docs/routes.html +7 -7
  452. data/docs/security.html +7 -7
  453. data/docs/seed-data.html +7 -7
  454. data/docs/space-time-continuum.html +7 -7
  455. data/docs/tutorial.html +7 -7
  456. data/docs/tutorials/01-intro.html +7 -7
  457. data/docs/tutorials/02-dialog.html +7 -7
  458. data/docs/unit-tests.html +7 -7
  459. data/docs/why.html +7 -7
  460. data/dx/bash_customizations +3 -4
  461. data/dx/build.pre +15 -0
  462. data/lib/brut/cli/apps/heroku_container_based_deploy.rb +41 -15
  463. data/lib/brut/cli/apps/test.rb +2 -0
  464. data/lib/brut/framework/container.rb +6 -4
  465. data/lib/brut/framework/mcp.rb +1 -1
  466. data/lib/brut/version.rb +1 -1
  467. data/mkbrut/Gemfile.lock +2 -2
  468. data/mkbrut/lib/mkbrut/add_segment.rb +38 -0
  469. data/mkbrut/lib/mkbrut/add_segment_options.rb +22 -0
  470. data/mkbrut/lib/mkbrut/app.rb +7 -2
  471. data/mkbrut/lib/mkbrut/base.rb +1 -0
  472. data/mkbrut/lib/mkbrut/cli.rb +90 -8
  473. data/mkbrut/lib/mkbrut/ops/insert_code_in_method.rb +19 -7
  474. data/mkbrut/lib/mkbrut/ops/insert_into_file.rb +36 -0
  475. data/mkbrut/lib/mkbrut/ops/prism_parsing_op.rb +14 -2
  476. data/mkbrut/lib/mkbrut/ops.rb +1 -0
  477. data/mkbrut/lib/mkbrut/segments/bare_bones.rb +8 -0
  478. data/mkbrut/lib/mkbrut/segments/demo.rb +8 -0
  479. data/mkbrut/lib/mkbrut/segments/heroku.rb +23 -3
  480. data/mkbrut/lib/mkbrut/segments/sidekiq.rb +136 -1
  481. data/mkbrut/lib/mkbrut/segments.rb +1 -1
  482. data/mkbrut/lib/mkbrut/version.rb +1 -1
  483. data/mkbrut/lib/mkbrut.rb +2 -0
  484. data/mkbrut/templates/Base/.gitignore +3 -0
  485. data/mkbrut/templates/Base/Dockerfile.dx +18 -21
  486. data/mkbrut/templates/Base/Gemfile.erb +1 -1
  487. data/mkbrut/templates/Base/bin/run +107 -67
  488. data/mkbrut/templates/Base/bin/run.run +4 -0
  489. data/mkbrut/templates/Base/bin/setup +32 -1
  490. data/mkbrut/templates/Base/dx/bash_customizations +0 -4
  491. data/mkbrut/templates/Base/package.json.erb +1 -1
  492. data/mkbrut/templates/segments/Heroku/deploy/Dockerfile +15 -15
  493. data/mkbrut/templates/segments/Heroku/deploy/heroku_config.rb +5 -4
  494. data/mkbrut/templates/segments/Sidekiq/app/boot_sidekiq.rb +2 -0
  495. data/mkbrut/templates/segments/Sidekiq/app/config/sidekiq.yml +4 -0
  496. data/mkbrut/templates/segments/Sidekiq/app/src/back_end/jobs/app_job.rb +3 -0
  497. data/mkbrut/templates/segments/Sidekiq/app/src/back_end/jobs/example_job.rb +12 -0
  498. data/mkbrut/templates/segments/Sidekiq/app/src/back_end/segments/sidekiq_segment.rb +56 -0
  499. data/mkbrut/templates/segments/Sidekiq/bin/run.sidekiq +4 -0
  500. data/mkbrut/templates/segments/Sidekiq/specs/back_end/jobs/example_job.spec.rb +5 -0
  501. data/mkbrut/templates/segments/Sidekiq/specs/integration/sidekiq_works.spec.rb +38 -0
  502. metadata +131 -116
  503. data/docs/assets/adrs.md.BxjHi9-8.js +0 -1
  504. data/docs/assets/adrs.md.BxjHi9-8.lean.js +0 -1
  505. data/docs/assets/ai.md.Cy9GWnER.js +0 -1
  506. data/docs/assets/ai.md.Cy9GWnER.lean.js +0 -1
  507. data/docs/assets/assets.md.7C3HWkga.lean.js +0 -1
  508. data/docs/assets/brut-js.md.B4GYxQVw.lean.js +0 -1
  509. data/docs/assets/business-logic.md.BY4hGy0m.js +0 -1
  510. data/docs/assets/business-logic.md.BY4hGy0m.lean.js +0 -1
  511. data/docs/assets/chunks/@localSearchIndexroot.BWVzhs5N.js +0 -1
  512. data/docs/assets/chunks/VPLocalSearchBox.DCJk5nAW.js +0 -8
  513. data/docs/assets/chunks/framework.1L-BeKqY.js +0 -18
  514. data/docs/assets/cli.md.CjsktgFz.lean.js +0 -1
  515. data/docs/assets/components.md.rMhQ0WdZ.lean.js +0 -1
  516. data/docs/assets/configuration.md.BK42Yjp_.lean.js +0 -1
  517. data/docs/assets/css.md.CltvJqAa.lean.js +0 -1
  518. data/docs/assets/custom-element-tests.md.B_rbta32.lean.js +0 -1
  519. data/docs/assets/database-access.md.gnluu54N.lean.js +0 -1
  520. data/docs/assets/database-schema.md.LpmBPVEU.lean.js +0 -1
  521. data/docs/assets/deployment.md.BLseERGV.lean.js +0 -1
  522. data/docs/assets/dir-structure.md.CWir1pic.lean.js +0 -1
  523. data/docs/assets/doc-conventions.md.DOkAuXlt.js +0 -1
  524. data/docs/assets/doc-conventions.md.DOkAuXlt.lean.js +0 -1
  525. data/docs/assets/end-to-end-tests.md.DzqRpZ43.lean.js +0 -1
  526. data/docs/assets/features.md.DPFXsy0z.lean.js +0 -1
  527. data/docs/assets/flash-and-session.md.nPvUpnUx.lean.js +0 -1
  528. data/docs/assets/form-constraints.md.KTv5cdR4.lean.js +0 -1
  529. data/docs/assets/forms.md.v9qIbmUM.lean.js +0 -1
  530. data/docs/assets/getting-started.md.DTOl4c2g.lean.js +0 -1
  531. data/docs/assets/handlers.md.h84MMB1R.lean.js +0 -1
  532. data/docs/assets/hooks.md.Jmb5VOLA.lean.js +0 -1
  533. data/docs/assets/i18n.md.BAm9t9JJ.lean.js +0 -1
  534. data/docs/assets/instrumentation.md._lNSriEZ.lean.js +0 -1
  535. data/docs/assets/javascript.md.DzrMxUmI.lean.js +0 -1
  536. data/docs/assets/jobs.md.S-2amAYp.js +0 -1
  537. data/docs/assets/jobs.md.S-2amAYp.lean.js +0 -1
  538. data/docs/assets/keyword-injection.md.95Zgh2eN.lean.js +0 -1
  539. data/docs/assets/layouts.md.CVGl9xIO.js +0 -38
  540. data/docs/assets/layouts.md.CVGl9xIO.lean.js +0 -1
  541. data/docs/assets/lsp.md.Dn1rIiW0.js +0 -1
  542. data/docs/assets/lsp.md.Dn1rIiW0.lean.js +0 -1
  543. data/docs/assets/markdown-examples.md.CCFEQO44.lean.js +0 -1
  544. data/docs/assets/middleware.md.Czz_UlJN.lean.js +0 -1
  545. data/docs/assets/overview.md.DlKiRRG_.js +0 -1
  546. data/docs/assets/overview.md.DlKiRRG_.lean.js +0 -1
  547. data/docs/assets/pages.md.B7Hc-i6H.lean.js +0 -1
  548. data/docs/assets/recipes_alternate-layouts.md.BwEytl59.lean.js +0 -1
  549. data/docs/assets/recipes_authentication.md.nwO6F7Ou.lean.js +0 -1
  550. data/docs/assets/recipes_blank-layouts.md.fyAUJyJR.js +0 -15
  551. data/docs/assets/recipes_blank-layouts.md.fyAUJyJR.lean.js +0 -1
  552. data/docs/assets/recipes_custom-flash.md.CrQbI5eH.lean.js +0 -1
  553. data/docs/assets/recipes_form-errors.md.Bv5RCKqH.lean.js +0 -1
  554. data/docs/assets/recipes_indexed-forms.md.CstYyOSo.lean.js +0 -1
  555. data/docs/assets/recipes_migrations.md.CTcnWDJF.lean.js +0 -1
  556. data/docs/assets/recipes_text-field-component.md.H4wLAK0Z.lean.js +0 -1
  557. data/docs/assets/roadmap.md.C6PRi0DX.js +0 -1
  558. data/docs/assets/roadmap.md.C6PRi0DX.lean.js +0 -1
  559. data/docs/assets/routes.md.BD6y2i-f.lean.js +0 -1
  560. data/docs/assets/security.md.C0G_AZR-.js +0 -1
  561. data/docs/assets/security.md.C0G_AZR-.lean.js +0 -1
  562. data/docs/assets/seed-data.md.BvFZlqIk.lean.js +0 -1
  563. data/docs/assets/space-time-continuum.md.xl44xDos.js +0 -1
  564. data/docs/assets/space-time-continuum.md.xl44xDos.lean.js +0 -1
  565. data/docs/assets/tutorial.md.BM40jnoq.lean.js +0 -1
  566. data/docs/assets/unit-tests.md.DUGrnLj5.lean.js +0 -1
  567. data/docs/assets/why.md.C-hk5xgJ.js +0 -1
  568. data/docs/assets/why.md.C-hk5xgJ.lean.js +0 -1
  569. data/docs/recipes/blank-layouts.html +0 -43
@@ -11,14 +11,56 @@ module MKBrut
11
11
 
12
12
  def run
13
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!
14
+ command = if @args.first == "add-segment"
15
+ @args.shift
16
+ :add_segment
17
+ elsif @args.first == "new-app" || @args.first == "new"
18
+ @args.shift
19
+ :new_app
20
+ else
21
+ :new_app
22
+ end
23
+
24
+ if command == :new_app
25
+ app_options = parse_options(@args, MKBrut::Versions.new)
26
+ new_app = MKBrut::App.new(
27
+ current_dir: Pathname.pwd.expand_path,
28
+ app_options:,
29
+ out: PrefixedIO.new(@out, "mkbrut"),
30
+ err: @err
31
+ )
32
+ new_app.create!
33
+ elsif command == :add_segment
34
+ add_segment_options = parse_add_segment_options(@args, MKBrut::Versions.new)
35
+ add_segment = MKBrut::AddSegment.new(
36
+ current_dir: add_segment_options.project_root,
37
+ add_segment_options:,
38
+ out: PrefixedIO.new(@out, "mkbrut"),
39
+ err: @err
40
+ )
41
+ add_segment.add!
42
+ @out.puts ""
43
+ @out.puts "Sidekiq has now been set up for your app. The configuration used is"
44
+ @out.puts "a basic one, suitable for getting started, however you know own this"
45
+ @out.puts "configuration. Most of it is in these files:"
46
+ @out.puts ""
47
+ @out.puts " app/config/sidekiq.yml"
48
+ @out.puts " app/src/back_end/segments/sidekiq_segment.rb"
49
+ @out.puts ""
50
+ @out.puts "You are encouraged to verify everything is set up as follows:"
51
+ @out.puts ""
52
+ @out.puts "1. Quit dx/start, and start it back up - this will downloaded and set up ValKey/Redis"
53
+ @out.puts "2. Re-run bin/setup. This will install needed gems and create binstubs"
54
+ @out.puts "3. Run the example integration test:"
55
+ @out.puts ""
56
+ @out.puts " bin/test e2e specs/integration/sidekiq_works.spec.rb"
57
+ @out.puts ""
58
+ @out.puts " This will use the actual Sidekiq server, so if it passes, you should"
59
+ @out.puts " all set and can start creating jobs"
60
+ @out.puts ""
61
+ else
62
+ raise "Unknown command #{command}"
63
+ end
22
64
  0
23
65
  rescue => e
24
66
  @err.puts "Error: #{e.message}"
@@ -103,5 +145,45 @@ module MKBrut
103
145
  MKBrut::AppOptions.new(**options, versions:)
104
146
  end
105
147
 
148
+ def parse_add_segment_options(args, versions)
149
+ options = {}
150
+ @option_parser = OptionParser.new do |opts|
151
+ opts.banner = [
152
+ "Usage: mkbrut add-segment [options] segment-name",
153
+ "",
154
+ " Adds a segment to an existing Brut-powered app",
155
+ "",
156
+ "VERSION",
157
+ "",
158
+ " #{MKBrut::VERSION}",
159
+ "",
160
+ "OPTIONS",
161
+ "",
162
+ ].join("\n")
163
+
164
+ opts.on("-r PROJECT_ROOT_DIR", "--project-root=PROJECT_ROOT_DIR", "Path to the root of the existing Brut app (defaults to current directory)")
165
+ opts.on("--dry-run", "Only show what would happen, don't actually do anything")
166
+ opts.on("--[no-]demo", "Include, or not, additional files that demonstrate Brut's features (default is true for now")
167
+ opts.on("-h", "--help", "Show this help message") do
168
+ @out.puts @option_parser
169
+ @out.puts
170
+ @out.puts "ARGUMENTS"
171
+ @out.puts
172
+ @out.puts " segment-name - name of the segment to add. Known values:"
173
+ @out.puts " sidekiq - add support for Sidekiq"
174
+ @out.puts " heroku - add support for Heroku container-based deployment"
175
+ @out.puts
176
+ @out.puts "ENVIRONMENT VARIABLES"
177
+ @out.puts
178
+ @out.puts " BRUT_CLI_RAISE_ON_ERROR - if set to 'true', any error will raise an exception instead of printing to stderr"
179
+ @out.puts
180
+ exit
181
+ end
182
+ end
183
+
184
+ @option_parser.parse!(args, into: options)
185
+ options[:project_root] = Pathname.new(options[:'project-root'] || ".").expand_path
186
+ MKBrut::AddSegmentOptions.new(segment_name: @args[0], **options, versions:)
187
+ end
106
188
  end
107
189
  end
@@ -1,14 +1,25 @@
1
1
  class MKBrut::Ops::InsertCodeInMethod < MKBrut::Ops::PrismParsingOp
2
- def initialize(file:, class_name:, method_name:, code:, where: :end)
3
- @file = file
4
- @class_name = class_name
5
- @method_name = method_name.to_sym
6
- @code = code
7
- @where = where
2
+ def initialize(file:,
3
+ class_name:,
4
+ method_name:,
5
+ class_method: false,
6
+ code:,
7
+ ignore_if_file_not_found: false,
8
+ where: :end)
9
+ @file = file
10
+ @class_name = class_name
11
+ @method_name = method_name.to_sym
12
+ @class_method = class_method
13
+ @code = code
14
+ @ignore_if_file_not_found = ignore_if_file_not_found
15
+ @where = where
8
16
  end
9
17
 
10
18
  def call
11
- method_node = find_method(class_name: @class_name, method_name: @method_name)
19
+ if !@file.exist? && @ignore_if_file_not_found
20
+ return
21
+ end
22
+ method_node = find_method(class_name: @class_name, method_name: @method_name, class_method: @class_method)
12
23
 
13
24
  insertion_point = if @where == :start
14
25
  insertion_point_for_code_at_start_of_method(method_node: method_node)
@@ -42,6 +53,7 @@ private
42
53
  }.join("\n") + post_indent
43
54
  end
44
55
 
56
+ # XXX: This does not work with non-ASCII strings
45
57
  def insertion_point_for_code_at_end_of_method(method_node:)
46
58
  line_number_of_method_end = method_node.location.end_line - 1
47
59
  length_of_method_end = @source.lines[line_number_of_method_end].length
@@ -0,0 +1,36 @@
1
+ class MKBrut::Ops::InsertIntoFile < MKBrut::Ops::BaseOp
2
+ def initialize(file:, content:, before_line:)
3
+ @file = file
4
+ @content = content
5
+ @before_line = before_line
6
+ end
7
+
8
+ def call
9
+
10
+ contents = File.read(@file)
11
+ locations = []
12
+ new_contents = []
13
+ contents.split("\n").each_with_index do |line, index|
14
+ if line == @before_line
15
+ new_contents << @content
16
+ locations << (index + 1)
17
+ end
18
+ new_contents << line
19
+ end
20
+ if locations.empty?
21
+ raise "Did not find line '#{@before_line}' exactly in #{@file}"
22
+ end
23
+ if locations.size > 1
24
+ raise "Found exact line '#{@before_line}' #{locations.size} times in #{@file}, should be exactly once so we know where to insert"
25
+ end
26
+
27
+ if dry_run?
28
+ puts "Would add to #{@file}, resulting in this content:\n#{new_contents.join("\n")}\n"
29
+ return
30
+ end
31
+
32
+ File.open(@file, "w") do |file|
33
+ file.puts new_contents.join("\n")
34
+ end
35
+ end
36
+ end
@@ -56,10 +56,22 @@ private
56
56
  class_node
57
57
  end
58
58
 
59
- def find_method(class_name:, method_name:)
59
+ def find_method(class_name:, method_name:, class_method: false)
60
60
  class_node = find_class(class_name:)
61
61
  method_node = class_node.body.body.detect { |node|
62
- node.is_a?(Prism::DefNode) && node.name == @method_name
62
+
63
+ is_def = node.is_a?(Prism::DefNode)
64
+ method_name_match = node.name == @method_name
65
+ receiver_match = if class_method
66
+ node.receiver.is_a?(Prism::SelfNode)
67
+ else
68
+ node.receiver.nil?
69
+ end
70
+
71
+ if is_def && method_name_match && !receiver_match
72
+ puts "Found method '#{method_name}' in class '#{class_name}' but it was a #{node.receiver.class} receiver"
73
+ end
74
+ is_def && method_name_match && receiver_match
63
75
  }
64
76
 
65
77
  if !method_node
@@ -8,6 +8,7 @@ module MKBrut
8
8
  autoload :InsertRoute, "mkbrut/ops/insert_route"
9
9
  autoload :InsertCodeInMethod, "mkbrut/ops/insert_code_in_method"
10
10
  autoload :AppendToFile, "mkbrut/ops/append_to_file"
11
+ autoload :InsertIntoFile, "mkbrut/ops/insert_into_file"
11
12
  autoload :PrismParsingOp, "mkbrut/ops/prism_parsing_op"
12
13
  autoload :AddI18nMessage, "mkbrut/ops/add_i18n_message"
13
14
  autoload :AddCSSImport, "mkbrut/ops/add_css_import"
@@ -9,6 +9,14 @@ class MKBrut::Segments::BareBones < MKBrut::Base
9
9
  @erb_binding = ErbBindingDelegate.new(app_options)
10
10
  end
11
11
 
12
+ def <=>(other)
13
+ if self.class == other.class
14
+ 0
15
+ else
16
+ -1
17
+ end
18
+ end
19
+
12
20
  def add!
13
21
 
14
22
  operations = copy_files(@templates_dir, @project_root) +
@@ -10,6 +10,14 @@ class MKBrut::Segments::Demo < MKBrut::Base
10
10
  @erb_binding = ErbBindingDelegate.new(app_options)
11
11
  end
12
12
 
13
+ def <=>(other)
14
+ if self.class == other.class
15
+ 0
16
+ else
17
+ 1
18
+ end
19
+ end
20
+
13
21
  def add!
14
22
  operations = copy_files(@templates_dir, @project_root) +
15
23
  other_operations(@project_root)
@@ -1,10 +1,9 @@
1
1
  class MKBrut::Segments::Heroku < MKBrut::Base
2
2
  def self.friendly_name = "Heroku-based Deployment"
3
3
 
4
- def initialize(app_options:, current_dir:, templates_dir:)
5
- @project_root = current_dir / app_options.app_name
4
+ def initialize(project_root:, templates_dir:)
5
+ @project_root = project_root
6
6
  @templates_dir = templates_dir / "segments" / "Heroku"
7
- @erb_binding = MKBrut::ErbBindingDelegate.new(app_options)
8
7
  end
9
8
 
10
9
  def add!
@@ -16,8 +15,29 @@ class MKBrut::Segments::Heroku < MKBrut::Base
16
15
  end
17
16
  end
18
17
 
18
+ def <=>(other)
19
+ if self.class == other.class
20
+ 0
21
+ elsif other.class == MKBrut::Segments::Sidekiq
22
+ # If both herkou and sidekiq segments are activated, we want to do heroku first,
23
+ # since Sidekiq will need to modify it.
24
+ -1
25
+ else
26
+ 1
27
+ end
28
+ end
29
+
19
30
  def other_operations
20
31
  [
32
+ MKBrut::Ops::InsertIntoFile.new(
33
+ file: @project_root / "Dockerfile.dx",
34
+ contents: %{
35
+ # Installs the Heroku CLI, per Heroku docs at
36
+ # https://devcenter.heroku.com/articles/heroku-cli#install-with-ubuntu-debian-apt-get
37
+ RUN curl https://cli-assets.heroku.com/install-ubuntu.sh | sh
38
+ },
39
+ before_line: "# Up to now, RUN directives ran as the root user. When using this container"
40
+ ),
21
41
  MKBrut::Ops::AppendToFile.new(
22
42
  file: @project_root / ".gitignore",
23
43
  content: %{
@@ -1,3 +1,138 @@
1
1
  # Adds Sidekiq to a Brut app
2
- class MKBrut::Segments::Sidekiq
2
+ class MKBrut::Segments::Sidekiq < MKBrut::Base
3
+ def self.friendly_name = "Use Sidekiq to Run Background Jobs"
4
+ def initialize(project_root:, templates_dir:)
5
+ @project_root = project_root
6
+ @templates_dir = templates_dir / "segments" / "Sidekiq"
7
+ end
8
+
9
+ def add!
10
+ operations = copy_files(@templates_dir, @project_root) +
11
+ other_operations
12
+
13
+ operations.each do |operation|
14
+ operation.call
15
+ end
16
+ end
17
+
18
+ def <=>(other)
19
+ if self.class == other.class
20
+ 0
21
+ elsif other.class == MKBrut::Segments::Heroku
22
+ # If both herkou and sidekiq segments are activated, we want to do heroku first,
23
+ # since Sidekiq will need to modify it.
24
+ 1
25
+ else
26
+ -1
27
+ end
28
+ end
29
+
30
+ def other_operations
31
+ [
32
+ MKBrut::Ops::AppendToFile.new(
33
+ file: @project_root / "docker-compose.dx.yml",
34
+ content: %{
35
+ redis:
36
+ # Change the value to what you are using in production.
37
+ # If you are using actual Redis, change that here.
38
+ image: valkey/valkey:8.1
39
+ },
40
+ ),
41
+ MKBrut::Ops::AppendToFile.new(
42
+ file: @project_root / "Procfile.development",
43
+ content: "sidekiq: bin/run sidekiq\n"
44
+ ),
45
+ MKBrut::Ops::AppendToFile.new(
46
+ file: @project_root / "Procfile.test",
47
+ content: "sidekiq: bin/run sidekiq\n"
48
+ ),
49
+ MKBrut::Ops::AppendToFile.new(
50
+ file: @project_root / "Gemfile",
51
+ content: "# Sidekiq is used for background jobs\ngem \"sidekiq\"\n"
52
+ ),
53
+ MKBrut::Ops::AppendToFile.new(
54
+ file: @project_root / ".env.development",
55
+ content: %{
56
+ # URL of the Redis/ValKey to use for Sidekiq
57
+ SIDEKIQ_REDIS_URL=redis://redis:6379/1
58
+ # Tells Sidekiq which ENV var to use for the Redis/ValKey URL
59
+ REDIS_PROVIDER=SIDEKIQ_REDIS_URL
60
+ # Username for basic auth into the Sidekiq Admin UI
61
+ SIDEKIQ_BASIC_AUTH_USER=sidekiq
62
+ # Passsword for basic auth into the Sidekiq Admin UI
63
+ SIDEKIQ_BASIC_AUTH_PASSWORD=password
64
+ }
65
+ ),
66
+ MKBrut::Ops::AppendToFile.new(
67
+ file: @project_root / ".env.test",
68
+ content: %{
69
+ SIDEKIQ_REDIS_URL=redis://redis:6379/2
70
+ REDIS_PROVIDER=SIDEKIQ_REDIS_URL
71
+ SIDEKIQ_BASIC_AUTH_USER=sidekiq-test
72
+ SIDEKIQ_BASIC_AUTH_PASSWORD=password
73
+ }
74
+ ),
75
+ MKBrut::Ops::InsertIntoFile.new(
76
+ file: @project_root / "bin" / "test-server",
77
+ before_line: "wait",
78
+ content: "bin/run sidekiq &\n",
79
+ ),
80
+ MKBrut::Ops::InsertIntoFile.new(
81
+ file: @project_root / "bin" / "setup",
82
+ before_line: " log \"Installing rspec binstubs\"",
83
+ content: %{log "Installing sidekiq binstubs"
84
+ system! "bundle binstub sidekiq"
85
+ }
86
+ ),
87
+ MKBrut::Ops::InsertIntoFile.new(
88
+ file: @project_root / "specs" / "spec_helper.rb",
89
+ before_line: "require \"brut/spec_support\"",
90
+ content: "require \"sidekiq/testing\""
91
+ ),
92
+ MKBrut::Ops::InsertIntoFile.new(
93
+ file: @project_root / "config.ru",
94
+ before_line: "bootstrap = Bootstrap.new.bootstrap!",
95
+ content: "require \"sidekiq/web\"\n"
96
+ ),
97
+ MKBrut::Ops::InsertIntoFile.new(
98
+ file: @project_root / "config.ru",
99
+ before_line: " run bootstrap.rack_app",
100
+ content: %{
101
+ map "/sidekiq" do
102
+ use Rack::Auth::Basic, "Sidekiq" do |username, password|
103
+ [username, password] == [ENV.fetch("SIDEKIQ_BASIC_AUTH_USER"), ENV.fetch("SIDEKIQ_BASIC_AUTH_PASSWORD")]
104
+ end
105
+ run Sidekiq::Web.new
106
+ end
107
+ }
108
+ ),
109
+ MKBrut::Ops::InsertCodeInMethod.new(
110
+ file: @project_root / "app" / "src" / "app.rb",
111
+ class_name: "App",
112
+ method_name: "initialize",
113
+ code: "@sidekiq_segment = SidekiqSegment.new",
114
+ ),
115
+ MKBrut::Ops::InsertCodeInMethod.new(
116
+ file: @project_root / "app" / "src" / "app.rb",
117
+ class_name: "App",
118
+ method_name: "boot!",
119
+ code: "@sidekiq_segment.boot!"
120
+ ),
121
+ MKBrut::Ops::InsertCodeInMethod.new(
122
+ file: project_root / "deploy" / "heroku_config.rb",
123
+ class_name: "HerokuConfig",
124
+ method_name: "additional_images",
125
+ class_method: true,
126
+ ignore_if_file_not_found: true,
127
+ code: %{
128
+ {
129
+ "sidekiq" => {
130
+ cmd: "bin/run sidekiq",
131
+ }
132
+ }
133
+ },
134
+ where: :end
135
+ ),
136
+ ]
137
+ end
3
138
  end
@@ -2,7 +2,7 @@ module MKBrut
2
2
  module Segments
3
3
  autoload :BareBones, "mkbrut/segments/bare_bones"
4
4
  autoload :Demo, "mkbrut/segments/demo"
5
- autoload :Sidekiq, "mkbrut/segments/demo"
5
+ autoload :Sidekiq, "mkbrut/segments/sidekiq"
6
6
  autoload :Heroku, "mkbrut/segments/heroku"
7
7
  end
8
8
  end
@@ -1,3 +1,3 @@
1
1
  module MKBrut
2
- VERSION = "0.14.0"
2
+ VERSION = "0.16.0.pre"
3
3
  end
data/mkbrut/lib/mkbrut.rb CHANGED
@@ -3,6 +3,8 @@ module MKBrut
3
3
  autoload :AppId, "mkbrut/app_id"
4
4
  autoload :AppName, "mkbrut/app_name"
5
5
  autoload :AppOptions, "mkbrut/app_options"
6
+ autoload :AddSegment, "mkbrut/add_segment"
7
+ autoload :AddSegmentOptions, "mkbrut/add_segment_options"
6
8
  autoload :Base, "mkbrut/base"
7
9
  autoload :CLI, "mkbrut/cli"
8
10
  autoload :ErbBindingDelegate, "mkbrut/erb_binding_delegate"
@@ -17,6 +17,9 @@
17
17
 
18
18
  # This could contain actual secrets and should not be checked in
19
19
  /.env.development.local
20
+ # This could also contain actual secrets and should not be checked in - it's configuration
21
+ # per developer, not per app
22
+ /dx/bash_customizations.local
20
23
 
21
24
  # These are generated artifacts
22
25
  /deploy/Dockerfile.*
@@ -25,13 +25,18 @@ ENV DEBIAN_FRONTEND=noninteractive
25
25
  # These packages are needed to set up other repos to install other
26
26
  # packages and/or are useful in installing other software
27
27
  #
28
+ # --mount=type=cache,target=/var/cache/apt means that we'll save
29
+ # the downloaded file used to install between builds, so that if they
30
+ # have not changed, builds will be faster.
31
+ #
28
32
  # - ca-certificates - needed by Postgres client
29
33
  # - curl - needed by various installation instructions
30
34
  # - gnupg - needed to install Docker
31
35
  # - lsb-release - needed by Postgres install instructions
32
36
  # - rsync - needed by Brut's build_assets command
33
37
  # - vim - needed when we want to edit files inside container
34
- RUN apt-get -y clean && \
38
+ RUN --mount=type=cache,target=/var/cache/apt \
39
+ apt-get -y clean && \
35
40
  apt-get -y update && \
36
41
  apt-get install --quiet --yes \
37
42
  ca-certificates \
@@ -47,7 +52,8 @@ RUN apt-get -y clean && \
47
52
  #
48
53
  # npx playwright install-deps --dry-run chromium
49
54
  #
50
- RUN apt-get install -y --no-install-recommends libasound2 \
55
+ RUN --mount=type=cache,target=/var/cache/apt \
56
+ apt-get install -y --no-install-recommends libasound2 \
51
57
  libatk-bridge2.0-0 \
52
58
  libatk1.0-0 \
53
59
  libatspi2.0-0 \
@@ -117,10 +123,6 @@ RUN echo "gem: --no-document" >> ~/.gemrc && \
117
123
  gem update --system && \
118
124
  gem install bundler
119
125
 
120
- # Installs the Heroku CLI, per Heroku docs at
121
- # https://devcenter.heroku.com/articles/heroku-cli#install-with-ubuntu-debian-apt-get
122
- RUN curl https://cli-assets.heroku.com/install-ubuntu.sh | sh
123
-
124
126
  # Up to now, RUN directives ran as the root user. When using this container
125
127
  # for development, we do not want to run as the root user. On macOS, this is not
126
128
  # a big deal, but in Linux, the root user inside this container can create files
@@ -178,28 +180,23 @@ COPY --chown=appuser:${user_gid} dx/bash_customizations.local /home/appuser/.bas
178
180
  # as belonging to.
179
181
  USER appuser
180
182
 
181
- # Install NodeJS, per https://nodejs.org/en/download
182
- #
183
- # Yes, this is seting up nvm just to install one version of node we will ever user in
184
- # here and yes, this sucks. But it's better to use the vendor's official
185
- # recommendation.
186
- RUN curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | bash && \
187
- \. "$HOME/.nvm/nvm.sh" && \
188
- nvm install 22 && \
189
- node -v && nvm current && npm -v
190
-
191
-
183
+ # Installs NodeJS from the official image. This is simpler
184
+ # than the canonical instructions which use NVM. Inside the container, there
185
+ # is not value to use NVM.
186
+ COPY --from=node:22-slim /usr/local /usr/local
192
187
 
193
188
  # This arg should have the version of playwright present in the app's Gemfile.lock.
194
189
  # This is because the playright NodeJS library and the Ruby gem must have the same
195
190
  # version or things won't work right. The value is blank because it really has to be
196
191
  # detected during `dx/build`.
197
192
  ARG PLAYWRIGHT_VERSION
193
+
198
194
  # Now install the version of Playwright we detected as well as Chromium. NOTE, we
199
195
  # must install Chromium, not chrome, because there is no version of Chrome for an
200
196
  # ARM-based Debian linux, which is what this Dockerfile will build on an Apple
201
- # Silicon Mac.
202
- RUN \. "$HOME/.nvm/nvm.sh" && \
203
- nvm use default && \
204
- npm install -g playwright@$PLAYWRIGHT_VERSION && \
197
+ # Silicon Mac. Note that we set NPM_CONFIG_PREFIX so that the global
198
+ # install will work for this user.
199
+ ENV NPM_CONFIG_PREFIX=/home/appuser/.npm-global
200
+ ENV PATH=/home/appuser/.npm-global/bin:$PATH
201
+ RUN npm install -g playwright@$PLAYWRIGHT_VERSION && \
205
202
  npx playwright install chromium
@@ -40,7 +40,7 @@ gem "pg"
40
40
  # We lock the version because it must be the same as the verison
41
41
  # of playwright in package.json. This ensures that we can reliably
42
42
  # install the right browsers and that everything will work right.
43
- gem "playwright-ruby-client", "1.52.0", groups: [ :test ]
43
+ gem "playwright-ruby-client", "1.55.0", groups: [ :test ]
44
44
 
45
45
  # Sinatra needs a webserver and puma is the best
46
46
  gem "puma"