brut 0.15.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 (400) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -0
  3. data/CHANGELOG.md +5 -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/brutrb.com/.vitepress/config.mjs +4 -3
  11. data/brutrb.com/deployment.md +23 -9
  12. data/brutrb.com/jobs.md +107 -7
  13. data/brutrb.com/recipes/dev-env-secrets.md +87 -0
  14. data/brutrb.com/roadmap.md +2 -7
  15. data/docs/404.html +2 -2
  16. data/docs/adrs.html +3 -3
  17. data/docs/ai.html +3 -3
  18. data/docs/api/Brut/BackEnd/SeedData.html +1 -1
  19. data/docs/api/Brut/BackEnd/Sidekiq/Middlewares/Server/FlushSpans.html +1 -1
  20. data/docs/api/Brut/BackEnd/Sidekiq/Middlewares/Server.html +1 -1
  21. data/docs/api/Brut/BackEnd/Sidekiq/Middlewares.html +1 -1
  22. data/docs/api/Brut/BackEnd/Sidekiq.html +1 -1
  23. data/docs/api/Brut/BackEnd/Validators/FormValidator.html +1 -1
  24. data/docs/api/Brut/BackEnd/Validators.html +1 -1
  25. data/docs/api/Brut/BackEnd.html +1 -1
  26. data/docs/api/Brut/CLI/App.html +1 -1
  27. data/docs/api/Brut/CLI/AppRunner.html +1 -1
  28. data/docs/api/Brut/CLI/Apps/BuildAssets/All.html +1 -1
  29. data/docs/api/Brut/CLI/Apps/BuildAssets/CSS.html +1 -1
  30. data/docs/api/Brut/CLI/Apps/BuildAssets/Images.html +1 -1
  31. data/docs/api/Brut/CLI/Apps/BuildAssets/JS.html +1 -1
  32. data/docs/api/Brut/CLI/Apps/BuildAssets.html +1 -1
  33. data/docs/api/Brut/CLI/Apps/DB/Create.html +1 -1
  34. data/docs/api/Brut/CLI/Apps/DB/Drop.html +1 -1
  35. data/docs/api/Brut/CLI/Apps/DB/Migrate.html +1 -1
  36. data/docs/api/Brut/CLI/Apps/DB/NewMigration.html +1 -1
  37. data/docs/api/Brut/CLI/Apps/DB/Rebuild.html +1 -1
  38. data/docs/api/Brut/CLI/Apps/DB/Seed.html +1 -1
  39. data/docs/api/Brut/CLI/Apps/DB/Status.html +1 -1
  40. data/docs/api/Brut/CLI/Apps/DB.html +1 -1
  41. data/docs/api/Brut/CLI/Apps/DeployBase/GitChecks.html +1 -1
  42. data/docs/api/Brut/CLI/Apps/DeployBase.html +1 -1
  43. data/docs/api/Brut/CLI/Apps/HerokuContainerBasedDeploy/Deploy.html +1 -1
  44. data/docs/api/Brut/CLI/Apps/HerokuContainerBasedDeploy.html +1 -1
  45. data/docs/api/Brut/CLI/Apps/Scaffold/Action/Route.html +1 -1
  46. data/docs/api/Brut/CLI/Apps/Scaffold/Action.html +1 -1
  47. data/docs/api/Brut/CLI/Apps/Scaffold/Component.html +1 -1
  48. data/docs/api/Brut/CLI/Apps/Scaffold/CustomElementTest.html +1 -1
  49. data/docs/api/Brut/CLI/Apps/Scaffold/DbModel.html +1 -1
  50. data/docs/api/Brut/CLI/Apps/Scaffold/E2ETest.html +1 -1
  51. data/docs/api/Brut/CLI/Apps/Scaffold/Form.html +1 -1
  52. data/docs/api/Brut/CLI/Apps/Scaffold/Page/Route.html +1 -1
  53. data/docs/api/Brut/CLI/Apps/Scaffold/Page.html +1 -1
  54. data/docs/api/Brut/CLI/Apps/Scaffold/RoutesEditor.html +1 -1
  55. data/docs/api/Brut/CLI/Apps/Scaffold/Test.html +1 -1
  56. data/docs/api/Brut/CLI/Apps/Scaffold.html +1 -1
  57. data/docs/api/Brut/CLI/Apps/Test/Audit.html +1 -1
  58. data/docs/api/Brut/CLI/Apps/Test/E2e.html +1 -1
  59. data/docs/api/Brut/CLI/Apps/Test/JS.html +1 -1
  60. data/docs/api/Brut/CLI/Apps/Test/Run.html +1 -1
  61. data/docs/api/Brut/CLI/Apps/Test.html +1 -1
  62. data/docs/api/Brut/CLI/Apps.html +1 -1
  63. data/docs/api/Brut/CLI/Command.html +1 -1
  64. data/docs/api/Brut/CLI/Error.html +1 -1
  65. data/docs/api/Brut/CLI/ExecutionResults/Result.html +1 -1
  66. data/docs/api/Brut/CLI/ExecutionResults.html +1 -1
  67. data/docs/api/Brut/CLI/Executor.html +1 -1
  68. data/docs/api/Brut/CLI/InvalidOption.html +1 -1
  69. data/docs/api/Brut/CLI/Options.html +1 -1
  70. data/docs/api/Brut/CLI/Output.html +1 -1
  71. data/docs/api/Brut/CLI/SystemExecError.html +1 -1
  72. data/docs/api/Brut/CLI.html +1 -1
  73. data/docs/api/Brut/FactoryBot.html +1 -1
  74. data/docs/api/Brut/Framework/App.html +1 -1
  75. data/docs/api/Brut/Framework/Config.html +1 -1
  76. data/docs/api/Brut/Framework/Container.html +1 -1
  77. data/docs/api/Brut/Framework/Error.html +1 -1
  78. data/docs/api/Brut/Framework/Errors/AbstractMethod.html +1 -1
  79. data/docs/api/Brut/Framework/Errors/Bug.html +1 -1
  80. data/docs/api/Brut/Framework/Errors/MissingConfiguration.html +1 -1
  81. data/docs/api/Brut/Framework/Errors/MissingParameter.html +1 -1
  82. data/docs/api/Brut/Framework/Errors/NoClassForPath.html +1 -1
  83. data/docs/api/Brut/Framework/Errors/NotFound.html +1 -1
  84. data/docs/api/Brut/Framework/Errors/NotImplemented.html +1 -1
  85. data/docs/api/Brut/Framework/Errors.html +1 -1
  86. data/docs/api/Brut/Framework/FussyTypeEnforcement.html +1 -1
  87. data/docs/api/Brut/Framework/MCP.html +1 -1
  88. data/docs/api/Brut/Framework/ProjectEnvironment.html +1 -1
  89. data/docs/api/Brut/Framework.html +1 -1
  90. data/docs/api/Brut/FrontEnd/AssetPathResolver.html +1 -1
  91. data/docs/api/Brut/FrontEnd/Component/Helpers.html +1 -1
  92. data/docs/api/Brut/FrontEnd/Component.html +1 -1
  93. data/docs/api/Brut/FrontEnd/Components/ConstraintViolations.html +1 -1
  94. data/docs/api/Brut/FrontEnd/Components/FormTag.html +1 -1
  95. data/docs/api/Brut/FrontEnd/Components/I18nTranslations.html +1 -1
  96. data/docs/api/Brut/FrontEnd/Components/Input.html +1 -1
  97. data/docs/api/Brut/FrontEnd/Components/Inputs/ButtonTag.html +1 -1
  98. data/docs/api/Brut/FrontEnd/Components/Inputs/CsrfToken.html +1 -1
  99. data/docs/api/Brut/FrontEnd/Components/Inputs/InputTag.html +1 -1
  100. data/docs/api/Brut/FrontEnd/Components/Inputs/RadioButton.html +1 -1
  101. data/docs/api/Brut/FrontEnd/Components/Inputs/SelectTagWithOptions.html +1 -1
  102. data/docs/api/Brut/FrontEnd/Components/Inputs/TextareaTag.html +1 -1
  103. data/docs/api/Brut/FrontEnd/Components/Inputs.html +1 -1
  104. data/docs/api/Brut/FrontEnd/Components/LocaleDetection.html +1 -1
  105. data/docs/api/Brut/FrontEnd/Components/PageIdentifier.html +1 -1
  106. data/docs/api/Brut/FrontEnd/Components/TimeTag.html +1 -1
  107. data/docs/api/Brut/FrontEnd/Components/Traceparent.html +1 -1
  108. data/docs/api/Brut/FrontEnd/Components.html +1 -1
  109. data/docs/api/Brut/FrontEnd/CsrfProtector.html +1 -1
  110. data/docs/api/Brut/FrontEnd/Download.html +1 -1
  111. data/docs/api/Brut/FrontEnd/Flash.html +1 -1
  112. data/docs/api/Brut/FrontEnd/Form.html +1 -1
  113. data/docs/api/Brut/FrontEnd/Forms/Button.html +1 -1
  114. data/docs/api/Brut/FrontEnd/Forms/ButtonInputDefinition.html +1 -1
  115. data/docs/api/Brut/FrontEnd/Forms/ConstraintViolation.html +1 -1
  116. data/docs/api/Brut/FrontEnd/Forms/Input/Color.html +1 -1
  117. data/docs/api/Brut/FrontEnd/Forms/Input/TimeOfDay.html +1 -1
  118. data/docs/api/Brut/FrontEnd/Forms/Input.html +1 -1
  119. data/docs/api/Brut/FrontEnd/Forms/InputDeclarations.html +1 -1
  120. data/docs/api/Brut/FrontEnd/Forms/InputDefinition.html +1 -1
  121. data/docs/api/Brut/FrontEnd/Forms/RadioButtonGroupInput.html +1 -1
  122. data/docs/api/Brut/FrontEnd/Forms/RadioButtonGroupInputDefinition.html +1 -1
  123. data/docs/api/Brut/FrontEnd/Forms/SelectInput.html +1 -1
  124. data/docs/api/Brut/FrontEnd/Forms/SelectInputDefinition.html +1 -1
  125. data/docs/api/Brut/FrontEnd/Forms/ValidityState.html +1 -1
  126. data/docs/api/Brut/FrontEnd/Forms.html +1 -1
  127. data/docs/api/Brut/FrontEnd/GenericResponse.html +1 -1
  128. data/docs/api/Brut/FrontEnd/Handler.html +1 -1
  129. data/docs/api/Brut/FrontEnd/Handlers/CspReportingHandler.html +1 -1
  130. data/docs/api/Brut/FrontEnd/Handlers/InstrumentationHandler/TraceParent.html +1 -1
  131. data/docs/api/Brut/FrontEnd/Handlers/InstrumentationHandler.html +1 -1
  132. data/docs/api/Brut/FrontEnd/Handlers/LocaleDetectionHandler.html +1 -1
  133. data/docs/api/Brut/FrontEnd/Handlers/MissingHandler/Form.html +1 -1
  134. data/docs/api/Brut/FrontEnd/Handlers/MissingHandler.html +1 -1
  135. data/docs/api/Brut/FrontEnd/Handlers.html +1 -1
  136. data/docs/api/Brut/FrontEnd/HandlingResults.html +1 -1
  137. data/docs/api/Brut/FrontEnd/HttpMethod.html +1 -1
  138. data/docs/api/Brut/FrontEnd/HttpStatus.html +1 -1
  139. data/docs/api/Brut/FrontEnd/InlineSvgLocator.html +1 -1
  140. data/docs/api/Brut/FrontEnd/Layout.html +1 -1
  141. data/docs/api/Brut/FrontEnd/Middleware.html +1 -1
  142. data/docs/api/Brut/FrontEnd/Middlewares/AnnotateBrutOwnedPaths.html +1 -1
  143. data/docs/api/Brut/FrontEnd/Middlewares/Favicon.html +1 -1
  144. data/docs/api/Brut/FrontEnd/Middlewares/OpenTelemetrySpan.html +1 -1
  145. data/docs/api/Brut/FrontEnd/Middlewares/ReloadApp.html +1 -1
  146. data/docs/api/Brut/FrontEnd/Middlewares.html +1 -1
  147. data/docs/api/Brut/FrontEnd/Page.html +1 -1
  148. data/docs/api/Brut/FrontEnd/Pages/MissingPage.html +1 -1
  149. data/docs/api/Brut/FrontEnd/Pages.html +1 -1
  150. data/docs/api/Brut/FrontEnd/RequestContext.html +1 -1
  151. data/docs/api/Brut/FrontEnd/RouteHook.html +1 -1
  152. data/docs/api/Brut/FrontEnd/RouteHooks/AgeFlash.html +1 -1
  153. data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineScripts.html +1 -1
  154. data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineStylesOrScripts/ReportOnly.html +1 -1
  155. data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineStylesOrScripts.html +1 -1
  156. data/docs/api/Brut/FrontEnd/RouteHooks/LocaleDetection.html +1 -1
  157. data/docs/api/Brut/FrontEnd/RouteHooks/SetupRequestContext.html +1 -1
  158. data/docs/api/Brut/FrontEnd/RouteHooks.html +1 -1
  159. data/docs/api/Brut/FrontEnd/Routing/FormHandlerRoute.html +1 -1
  160. data/docs/api/Brut/FrontEnd/Routing/FormRoute.html +1 -1
  161. data/docs/api/Brut/FrontEnd/Routing/MissingForm.html +1 -1
  162. data/docs/api/Brut/FrontEnd/Routing/MissingHandler.html +1 -1
  163. data/docs/api/Brut/FrontEnd/Routing/MissingPage.html +1 -1
  164. data/docs/api/Brut/FrontEnd/Routing/MissingPath.html +1 -1
  165. data/docs/api/Brut/FrontEnd/Routing/PageRoute.html +1 -1
  166. data/docs/api/Brut/FrontEnd/Routing/Route.html +1 -1
  167. data/docs/api/Brut/FrontEnd/Routing.html +1 -1
  168. data/docs/api/Brut/FrontEnd/Session.html +1 -1
  169. data/docs/api/Brut/FrontEnd.html +1 -1
  170. data/docs/api/Brut/I18n/BaseMethods.html +1 -1
  171. data/docs/api/Brut/I18n/ForBackEnd.html +1 -1
  172. data/docs/api/Brut/I18n/ForCLI.html +1 -1
  173. data/docs/api/Brut/I18n/ForHTML.html +1 -1
  174. data/docs/api/Brut/I18n/HTTPAcceptLanguage/AlwaysEnglish.html +1 -1
  175. data/docs/api/Brut/I18n/HTTPAcceptLanguage.html +1 -1
  176. data/docs/api/Brut/I18n.html +1 -1
  177. data/docs/api/Brut/Instrumentation/LoggerSpanExporter.html +1 -1
  178. data/docs/api/Brut/Instrumentation/Methods/ClassMethods.html +1 -1
  179. data/docs/api/Brut/Instrumentation/Methods.html +1 -1
  180. data/docs/api/Brut/Instrumentation/OpenTelemetry/NormalizedAttributes.html +1 -1
  181. data/docs/api/Brut/Instrumentation/OpenTelemetry/Span.html +1 -1
  182. data/docs/api/Brut/Instrumentation/OpenTelemetry.html +1 -1
  183. data/docs/api/Brut/Instrumentation.html +1 -1
  184. data/docs/api/Brut/RubocopConfig.html +1 -1
  185. data/docs/api/Brut/SinatraHelpers/ClassMethods.html +1 -1
  186. data/docs/api/Brut/SinatraHelpers.html +1 -1
  187. data/docs/api/Brut/SpecSupport/ClockSupport.html +1 -1
  188. data/docs/api/Brut/SpecSupport/ComponentSupport.html +1 -1
  189. data/docs/api/Brut/SpecSupport/E2ETestServer.html +1 -1
  190. data/docs/api/Brut/SpecSupport/E2eSupport.html +1 -1
  191. data/docs/api/Brut/SpecSupport/EnhancedNode.html +1 -1
  192. data/docs/api/Brut/SpecSupport/FlashSupport.html +1 -1
  193. data/docs/api/Brut/SpecSupport/GeneralSupport/ClassMethods.html +1 -1
  194. data/docs/api/Brut/SpecSupport/GeneralSupport.html +1 -1
  195. data/docs/api/Brut/SpecSupport/HandlerSupport.html +1 -1
  196. data/docs/api/Brut/SpecSupport/Matchers/BeABug.html +1 -1
  197. data/docs/api/Brut/SpecSupport/Matchers/BePageFor.html +1 -1
  198. data/docs/api/Brut/SpecSupport/Matchers/BeRoutingFor.html +1 -1
  199. data/docs/api/Brut/SpecSupport/Matchers/HaveConstraintViolation.html +1 -1
  200. data/docs/api/Brut/SpecSupport/Matchers/HaveGenerated.html +1 -1
  201. data/docs/api/Brut/SpecSupport/Matchers/HaveHTMLAttribute.html +1 -1
  202. data/docs/api/Brut/SpecSupport/Matchers/HaveI18nString.html +1 -1
  203. data/docs/api/Brut/SpecSupport/Matchers/HaveLinkTo.html +1 -1
  204. data/docs/api/Brut/SpecSupport/Matchers/HaveRedirectedTo.html +1 -1
  205. data/docs/api/Brut/SpecSupport/Matchers/HaveReturnedHttpStatus.html +1 -1
  206. data/docs/api/Brut/SpecSupport/Matchers/HaveReturnedRackResponse.html +1 -1
  207. data/docs/api/Brut/SpecSupport/Matchers.html +1 -1
  208. data/docs/api/Brut/SpecSupport/RSpecSetup/OptionalSidekiqSupport.html +1 -1
  209. data/docs/api/Brut/SpecSupport/RSpecSetup.html +1 -1
  210. data/docs/api/Brut/SpecSupport/SessionSupport.html +1 -1
  211. data/docs/api/Brut/SpecSupport.html +1 -1
  212. data/docs/api/Brut.html +1 -1
  213. data/docs/api/Clock.html +1 -1
  214. data/docs/api/ModuleName.html +1 -1
  215. data/docs/api/RichString.html +1 -1
  216. data/docs/api/SemanticLogger/Appender/Async.html +1 -1
  217. data/docs/api/Sequel/Extensions/BrutInstrumentation.html +1 -1
  218. data/docs/api/Sequel/Extensions/BrutMigrations.html +1 -1
  219. data/docs/api/Sequel/Extensions.html +1 -1
  220. data/docs/api/Sequel/Plugins/CreatedAt/InstanceMethods.html +1 -1
  221. data/docs/api/Sequel/Plugins/CreatedAt.html +1 -1
  222. data/docs/api/Sequel/Plugins/ExternalId/ClassMethods.html +1 -1
  223. data/docs/api/Sequel/Plugins/ExternalId/InstanceMethods.html +1 -1
  224. data/docs/api/Sequel/Plugins/ExternalId.html +1 -1
  225. data/docs/api/Sequel/Plugins/FindBang/ClassMethods.html +1 -1
  226. data/docs/api/Sequel/Plugins/FindBang.html +1 -1
  227. data/docs/api/Sequel/Plugins.html +1 -1
  228. data/docs/api/Sequel.html +1 -1
  229. data/docs/api/_index.html +1 -1
  230. data/docs/api/file.README.html +1 -1
  231. data/docs/api/index.html +1 -1
  232. data/docs/api/top-level-namespace.html +1 -1
  233. data/docs/assets/{app.B0X8upRm.js → app.Dm7v_ouO.js} +1 -1
  234. data/docs/assets/{brut-js.md.CbJAe2Ky.js → brut-js.md.BMz0X1Rz.js} +1 -1
  235. data/docs/assets/chunks/@localSearchIndexroot.BiNFswvo.js +1 -0
  236. data/docs/assets/chunks/{VPLocalSearchBox.jLmhant1.js → VPLocalSearchBox.DQK6jQou.js} +1 -1
  237. data/docs/assets/chunks/{theme.CtVUdCdt.js → theme.BuExsdM9.js} +2 -2
  238. data/docs/assets/{components.md.C6nWgDP0.js → components.md.Dfd3w6UW.js} +3 -3
  239. data/docs/assets/{configuration.md.CpbYHWPb.js → configuration.md.DTYoV2Ea.js} +1 -1
  240. data/docs/assets/{forms.md.Bii91k3E.js → forms.md.DEkmJUvb.js} +1 -1
  241. data/docs/assets/{getting-started.md.ChAvueK7.js → getting-started.md.DO-4eoGW.js} +2 -2
  242. data/docs/assets/{tutorials_02-dialog.md.De6iTsWX.js → tutorials_02-dialog.md.D2vSjDVf.js} +1 -1
  243. data/docs/assets.html +3 -3
  244. data/docs/brut-js/api/AjaxSubmit.html +2 -2
  245. data/docs/brut-js/api/AjaxSubmit.js.html +2 -2
  246. data/docs/brut-js/api/Autosubmit.html +2 -2
  247. data/docs/brut-js/api/Autosubmit.js.html +2 -2
  248. data/docs/brut-js/api/BaseCustomElement.html +2 -2
  249. data/docs/brut-js/api/BaseCustomElement.js.html +2 -2
  250. data/docs/brut-js/api/BrutCustomElements.html +3 -3
  251. data/docs/brut-js/api/BufferedLogger.html +2 -2
  252. data/docs/brut-js/api/ConfirmSubmit.html +2 -2
  253. data/docs/brut-js/api/ConfirmSubmit.js.html +2 -2
  254. data/docs/brut-js/api/ConfirmationDialog.html +2 -2
  255. data/docs/brut-js/api/ConfirmationDialog.js.html +2 -2
  256. data/docs/brut-js/api/ConstraintViolationMessage.html +2 -2
  257. data/docs/brut-js/api/ConstraintViolationMessage.js.html +2 -2
  258. data/docs/brut-js/api/ConstraintViolationMessages.html +2 -2
  259. data/docs/brut-js/api/ConstraintViolationMessages.js.html +2 -2
  260. data/docs/brut-js/api/CopyToClipboard.html +2 -2
  261. data/docs/brut-js/api/CopyToClipboard.js.html +2 -2
  262. data/docs/brut-js/api/Form.html +2 -2
  263. data/docs/brut-js/api/Form.js.html +2 -2
  264. data/docs/brut-js/api/I18nTranslation.html +2 -2
  265. data/docs/brut-js/api/I18nTranslation.js.html +5 -2
  266. data/docs/brut-js/api/LocaleDetection.html +2 -2
  267. data/docs/brut-js/api/LocaleDetection.js.html +2 -2
  268. data/docs/brut-js/api/Logger.html +2 -2
  269. data/docs/brut-js/api/Logger.js.html +2 -2
  270. data/docs/brut-js/api/Message.html +2 -2
  271. data/docs/brut-js/api/Message.js.html +11 -5
  272. data/docs/brut-js/api/PrefixedLogger.html +2 -2
  273. data/docs/brut-js/api/RichString.html +9 -9
  274. data/docs/brut-js/api/RichString.js.html +6 -3
  275. data/docs/brut-js/api/Tabs.html +2 -2
  276. data/docs/brut-js/api/Tabs.js.html +2 -2
  277. data/docs/brut-js/api/Toast.html +270 -0
  278. data/docs/brut-js/api/Toast.js.html +153 -0
  279. data/docs/brut-js/api/Tracing.html +2 -2
  280. data/docs/brut-js/api/Tracing.js.html +2 -2
  281. data/docs/brut-js/api/external-CustomElementRegistry.html +3 -3
  282. data/docs/brut-js/api/external-Performance.html +3 -3
  283. data/docs/brut-js/api/external-Promise.html +3 -3
  284. data/docs/brut-js/api/external-ValidityState.html +3 -3
  285. data/docs/brut-js/api/external-Window.html +4 -4
  286. data/docs/brut-js/api/external-fetch.html +3 -3
  287. data/docs/brut-js/api/global.html +3 -3
  288. data/docs/brut-js/api/index.html +2 -2
  289. data/docs/brut-js/api/index.js.html +5 -2
  290. data/docs/brut-js/api/module-testing.html +2 -2
  291. data/docs/brut-js/api/testing.AssetMetadata.html +2 -2
  292. data/docs/brut-js/api/testing.AssetMetadataLoader.html +2 -2
  293. data/docs/brut-js/api/testing.CustomElementTest.html +2 -2
  294. data/docs/brut-js/api/testing.DOMCreator.html +2 -2
  295. data/docs/brut-js/api/testing_AssetMetadata.js.html +2 -2
  296. data/docs/brut-js/api/testing_AssetMetadataLoader.js.html +2 -2
  297. data/docs/brut-js/api/testing_CustomElementTest.js.html +2 -2
  298. data/docs/brut-js/api/testing_DOMCreator.js.html +2 -2
  299. data/docs/brut-js/api/testing_index.js.html +2 -2
  300. data/docs/brut-js.html +5 -5
  301. data/docs/business-logic.html +3 -3
  302. data/docs/cli.html +3 -3
  303. data/docs/components.html +7 -7
  304. data/docs/configuration.html +5 -5
  305. data/docs/css.html +3 -3
  306. data/docs/custom-element-tests.html +3 -3
  307. data/docs/database-access.html +3 -3
  308. data/docs/database-schema.html +3 -3
  309. data/docs/deployment.html +3 -3
  310. data/docs/dev-environment.html +3 -3
  311. data/docs/dir-structure.html +3 -3
  312. data/docs/doc-conventions.html +3 -3
  313. data/docs/end-to-end-tests.html +3 -3
  314. data/docs/features.html +3 -3
  315. data/docs/flash-and-session.html +3 -3
  316. data/docs/form-constraints.html +3 -3
  317. data/docs/forms.html +5 -5
  318. data/docs/getting-started.html +6 -6
  319. data/docs/handlers.html +3 -3
  320. data/docs/hashmap.json +1 -1
  321. data/docs/hooks.html +3 -3
  322. data/docs/i18n.html +3 -3
  323. data/docs/index.html +3 -3
  324. data/docs/instrumentation.html +3 -3
  325. data/docs/javascript.html +3 -3
  326. data/docs/jobs.html +3 -3
  327. data/docs/keyword-injection.html +3 -3
  328. data/docs/layouts.html +3 -3
  329. data/docs/lsp.html +3 -3
  330. data/docs/markdown-examples.html +3 -3
  331. data/docs/middleware.html +3 -3
  332. data/docs/overview.html +3 -3
  333. data/docs/pages.html +3 -3
  334. data/docs/recipes/alternate-layouts.html +3 -3
  335. data/docs/recipes/authentication.html +3 -3
  336. data/docs/recipes/custom-flash.html +3 -3
  337. data/docs/recipes/form-errors.html +3 -3
  338. data/docs/recipes/indexed-forms.html +3 -3
  339. data/docs/recipes/migrations.html +3 -3
  340. data/docs/recipes/text-field-component.html +3 -3
  341. data/docs/roadmap.html +3 -3
  342. data/docs/routes.html +3 -3
  343. data/docs/security.html +3 -3
  344. data/docs/seed-data.html +3 -3
  345. data/docs/space-time-continuum.html +3 -3
  346. data/docs/tutorial.html +3 -3
  347. data/docs/tutorials/01-intro.html +3 -3
  348. data/docs/tutorials/02-dialog.html +5 -5
  349. data/docs/unit-tests.html +3 -3
  350. data/docs/why.html +3 -3
  351. data/dx/bash_customizations +3 -4
  352. data/dx/build.pre +15 -0
  353. data/lib/brut/cli/apps/heroku_container_based_deploy.rb +41 -15
  354. data/lib/brut/cli/apps/test.rb +2 -0
  355. data/lib/brut/framework/container.rb +6 -4
  356. data/lib/brut/framework/mcp.rb +1 -1
  357. data/lib/brut/version.rb +1 -1
  358. data/mkbrut/Gemfile.lock +2 -2
  359. data/mkbrut/lib/mkbrut/add_segment.rb +38 -0
  360. data/mkbrut/lib/mkbrut/add_segment_options.rb +22 -0
  361. data/mkbrut/lib/mkbrut/app.rb +7 -2
  362. data/mkbrut/lib/mkbrut/base.rb +1 -0
  363. data/mkbrut/lib/mkbrut/cli.rb +90 -8
  364. data/mkbrut/lib/mkbrut/ops/insert_code_in_method.rb +19 -7
  365. data/mkbrut/lib/mkbrut/ops/insert_into_file.rb +36 -0
  366. data/mkbrut/lib/mkbrut/ops/prism_parsing_op.rb +14 -2
  367. data/mkbrut/lib/mkbrut/ops.rb +1 -0
  368. data/mkbrut/lib/mkbrut/segments/bare_bones.rb +8 -0
  369. data/mkbrut/lib/mkbrut/segments/demo.rb +8 -0
  370. data/mkbrut/lib/mkbrut/segments/heroku.rb +23 -3
  371. data/mkbrut/lib/mkbrut/segments/sidekiq.rb +136 -1
  372. data/mkbrut/lib/mkbrut/segments.rb +1 -1
  373. data/mkbrut/lib/mkbrut/version.rb +1 -1
  374. data/mkbrut/lib/mkbrut.rb +2 -0
  375. data/mkbrut/templates/Base/.gitignore +3 -0
  376. data/mkbrut/templates/Base/Dockerfile.dx +18 -21
  377. data/mkbrut/templates/Base/Gemfile.erb +1 -1
  378. data/mkbrut/templates/Base/bin/run +107 -67
  379. data/mkbrut/templates/Base/bin/run.run +4 -0
  380. data/mkbrut/templates/Base/bin/setup +32 -1
  381. data/mkbrut/templates/Base/dx/bash_customizations +0 -4
  382. data/mkbrut/templates/Base/package.json.erb +1 -1
  383. data/mkbrut/templates/segments/Heroku/deploy/Dockerfile +15 -15
  384. data/mkbrut/templates/segments/Heroku/deploy/heroku_config.rb +5 -4
  385. data/mkbrut/templates/segments/Sidekiq/app/boot_sidekiq.rb +2 -0
  386. data/mkbrut/templates/segments/Sidekiq/app/config/sidekiq.yml +4 -0
  387. data/mkbrut/templates/segments/Sidekiq/app/src/back_end/jobs/app_job.rb +3 -0
  388. data/mkbrut/templates/segments/Sidekiq/app/src/back_end/jobs/example_job.rb +12 -0
  389. data/mkbrut/templates/segments/Sidekiq/app/src/back_end/segments/sidekiq_segment.rb +56 -0
  390. data/mkbrut/templates/segments/Sidekiq/bin/run.sidekiq +4 -0
  391. data/mkbrut/templates/segments/Sidekiq/specs/back_end/jobs/example_job.spec.rb +5 -0
  392. data/mkbrut/templates/segments/Sidekiq/specs/integration/sidekiq_works.spec.rb +38 -0
  393. metadata +34 -18
  394. data/docs/assets/chunks/@localSearchIndexroot.C0s1k0UQ.js +0 -1
  395. /data/docs/assets/{brut-js.md.CbJAe2Ky.lean.js → brut-js.md.BMz0X1Rz.lean.js} +0 -0
  396. /data/docs/assets/{components.md.C6nWgDP0.lean.js → components.md.Dfd3w6UW.lean.js} +0 -0
  397. /data/docs/assets/{configuration.md.CpbYHWPb.lean.js → configuration.md.DTYoV2Ea.lean.js} +0 -0
  398. /data/docs/assets/{forms.md.Bii91k3E.lean.js → forms.md.DEkmJUvb.lean.js} +0 -0
  399. /data/docs/assets/{getting-started.md.ChAvueK7.lean.js → getting-started.md.DO-4eoGW.lean.js} +0 -0
  400. /data/docs/assets/{tutorials_02-dialog.md.De6iTsWX.lean.js → tutorials_02-dialog.md.D2vSjDVf.lean.js} +0 -0
@@ -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.15.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"
@@ -1,86 +1,126 @@
1
- #!/bin/sh
1
+ #!/usr/bin/env bash
2
2
 
3
3
  set -e
4
- SCRIPT_DIR=$( cd -- "$( dirname -- "${0}" )" > /dev/null 2>&1 && pwd )
5
-
6
- usage() {
7
- echo "Usage: $0"
8
- echo
9
- echo " Run the app in the given RACK_ENV."
10
- echo " You likely want to use bin/dev instead of this command."
11
- echo
12
- echo "ENVIRONMENT VARIABLES"
13
- echo
14
- echo " PORT - The port to run the app on. Default is 6502"
15
- echo " RACK_ENV - The Rack environment to use. Default is development"
16
- echo
17
- }
18
-
19
- for arg in "$@"; do
20
- if [ "${arg}" = "-h" ] || [ "${arg}" = "--help" ] || [ "${arg}" = "help" ]; then
21
- usage
22
- exit 0
23
- fi
24
- done
25
4
 
5
+ SCRIPT_DIR=$( cd -- "$( dirname -- "${0}" )" > /dev/null 2>&1 && pwd )
26
6
  PORT="${PORT:-6502}"
27
7
  RACK_ENV="${RACK_ENV:-development}"
28
8
 
29
9
  export PORT
30
10
  export RACK_ENV
31
11
 
32
- set -e
12
+ declare -A CMD_MAP
13
+ declare -A PIDFILE_MAP
14
+ declare -A CREATES_PIDFILE_MAP
33
15
 
34
- # Everything here is trying its best to prevent more than
35
- # one server from running, as this is extremely confusing.
36
- # Note that the puma invocation at the bottom
37
- # of this script uses the pidfile concept.
38
- if [ -f tmp/pidfile ]; then
39
- echo "[ $0 ] pidfile found"
40
- pid=$(cat tmp/pidfile)
41
-
42
- # First, try to gracefully stop the server with kill
43
- if ps -p "${pid}" > /dev/null; then
44
- echo "[ $0 ] Attempting to kill PID '${pid}'"
45
- kill "${pid}"
46
- else
47
- echo "[ $0 ] PID '${pid}' no longer running"
16
+ kill_previous() {
17
+ local pidfile=$1
18
+
19
+ if [[ -z "$pidfile" ]]; then
20
+ echo "[ $0 ] Error: no pidfile path given"
21
+ return 0
48
22
  fi
49
23
 
50
- # Now, wait 5 seconds to see if it stopped
51
- if timeout 5 tail --pid="${pid}" -f /dev/null; then
52
- echo "[ $0 ] PID '${pid}' stopped. Restarting server"
53
- else
54
- # if it has not stopeed, use kill -9 which should work.
55
- # But, like all things computer, it's not guaranteed.
56
- echo "[ $0 ] PID '${pid}' has not stopped. Trying kill -9"
57
- kill -9 "${pid}"
58
- if timeout 1 tail --pid="${pid}" -f /dev/null; then
59
- echo "[ $0 ] PID '${pid}' killed. Restarting server"
24
+ if [ -f "${pidfile}" ]; then
25
+ echo "[ $0 ] pidfile found"
26
+ pid=$(cat "${pidfile}")
27
+
28
+ if [[ -z "$pid" || ! "$pid" =~ ^[0-9]+$ ]]; then
29
+ echo "[ $0 ] Invalid or empty PID in $pidfile: '$pid'; removing"
30
+ rm -f "$pidfile"
31
+ return 0
32
+ fi
33
+
34
+ if ps -p "${pid}" > /dev/null; then
35
+ echo "[ $0 ] Attempting to kill PID '${pid}'"
36
+ kill "${pid}"
37
+ else
38
+ echo "[ $0 ] PID '${pid}' no longer running"
39
+ fi
40
+ if timeout 5 tail --pid="${pid}" -f /dev/null; then
41
+ echo "[ $0 ] PID '${pid}' stopped. Restarting server"
60
42
  else
61
- echo "[ $0 ] PID '${pid}' still running. Something seriously wrong"
62
- echo "[ $0 ] You may need to stop all Docker containers and restart them"
43
+ echo "[ $0 ] PID '${pid}' has not stopped. Trying kill -9"
44
+ kill -9 "${pid}"
45
+ if timeout 1 tail --pid="${pid}" -f /dev/null; then
46
+ echo "[ $0 ] PID '${pid}' killed. Restarting server"
47
+ else
48
+ echo "[ $0 ] PID '${pid}' still running. Something seriously wrong"
49
+ fi
63
50
  exit 1
64
51
  fi
52
+
53
+ else
54
+ echo "[ $0 ] No pidfile-Starting up"
65
55
  fi
56
+ }
66
57
 
67
- else
68
- echo "[ $0 ] No pidfile-Starting up"
58
+ dotenv_run() {
59
+ local env_files="${SCRIPT_DIR}/../.env.${RACK_ENV}.local,${SCRIPT_DIR}/../.env.${RACK_ENV}"
60
+ dotenv -f "$env_files" --ignore -- "$@"
61
+ }
62
+
63
+
64
+ for f in "$SCRIPT_DIR"/run.*; do
65
+ if [[ -f "$f" ]]; then
66
+ echo "[ $0 ] Sourcing $f"
67
+ . "$f"
68
+ fi
69
+ done
70
+
71
+ case $# in
72
+ 0) name="run" ;;
73
+ 1) name="$1" ;;
74
+ *) echo "Error: too many arguments ($#)." >&2; usage; exit 2 ;;
75
+ esac
76
+
77
+ if [[ ! -v CMD_MAP[$name] ]]; then
78
+ echo "[ $0 ] Error: unknown service '$name'." >&2
79
+ echo -n "[ $0 ] Known services: " >&2
80
+ printf "%s " "${!CMD_MAP[@]}" >&2
81
+ echo >&2
82
+ exit 1
69
83
  fi
70
84
 
71
- # Run puma in a UNIX environment provided by dotenv.
72
- #
73
- # A few things to note:
74
- #
75
- # * dotenv is OK if -f is given non-existent files. That's why this works
76
- # in production where there are no .env files.
77
- # * the `--` marks the end of dotenv's options and the start of the command
78
- # to run. That's why the flags given to puma are not interpreted by dotenv
79
- # as flags for itself.
80
- dotenv \
81
- -f "${SCRIPT_DIR}/../.env.${RACK_ENV}.local,${SCRIPT_DIR}/../.env.${RACK_ENV}" \
82
- --ignore \
83
- -- \
84
- bin/puma \
85
- -C puma.config.rb \
86
- --pid tmp/pidfile
85
+ cmd="${CMD_MAP[$name]}"
86
+ creates_pidfile="${CREATES_PIDFILE_MAP[$name]:-false}"
87
+ pidfile="${PIDFILE_MAP[$name]:-}"
88
+
89
+ echo "[ $0 ]: Command for $name:"
90
+ echo
91
+ echo " $cmd"
92
+ echo
93
+
94
+ kill_previous "$pidfile"
95
+
96
+ if [[ "$creates_pidfile" == true ]]; then
97
+ echo "[ $0 ] Running '$cmd', which creates its own pidfile"
98
+ eval "dotenv_run $cmd"
99
+ else
100
+ # Command wont' create a pidfile, so we will
101
+ if [[ -z "${pidfile:-}" ]]; then
102
+ echo "Error: no pidfile path configured for '$name' but creates_pidfile=false." >&2
103
+ exit 1
104
+ fi
105
+ echo "[ $0 ] Running '$cmd', which will not create a pidfile"
106
+ echo "[ $0 ] We will do that and save it to ${pidfile}"
107
+
108
+ echo "[ $0 ] Ensuring $(dirname "$pidfile") exists"
109
+ mkdir -p "$(dirname "$pidfile")"
110
+
111
+ eval "dotenv_run $cmd &"
112
+ child_pid=$!
113
+
114
+ set +e
115
+ echo "$child_pid" > "$pidfile"
116
+
117
+ trap 'kill -TERM "$child_pid" 2>/dev/null' TERM INT
118
+
119
+ wait "$child_pid"
120
+
121
+ echo "[ $0 ] TERM or INT received, removing ${pidfile} and exiting"
122
+
123
+ status=$?
124
+ rm -f "$pidfile"
125
+ exit "$status"
126
+ fi
@@ -0,0 +1,4 @@
1
+ PIDFILE="${SCRIPT_DIR}/../tmp/pidfile"
2
+ CMD_MAP[run]="bin/puma --port ${PORT} -C puma.config.rb --environment ${RACK_ENV} --pid ${PIDFILE}"
3
+ PIDFILE_MAP[run]="${PIDFILE}"
4
+ CREATES_PIDFILE_MAP[run]="true"
@@ -1,10 +1,11 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
-
4
3
  require "fileutils"
5
4
  require "open3"
6
5
  require "optparse"
7
6
  require "pathname"
7
+ require "bundler"
8
+ require "json"
8
9
 
9
10
  # This is intended to run inside the Workspace (i.e. Docker container) to
10
11
  # set up the Foundation, thus enabling development. This script should:
@@ -21,6 +22,7 @@ require "pathname"
21
22
  # Take care to only add code and not change what is here.
22
23
 
23
24
  def setup(update_gems:,update_node:)
25
+ ensure_versions_are_consistent!
24
26
  if update_gems
25
27
  log "Updating gems"
26
28
  system! "bundle update"
@@ -82,6 +84,35 @@ def setup(update_gems:,update_node:)
82
84
  help
83
85
  end
84
86
 
87
+ def ensure_versions_are_consistent!
88
+ gemfile = Bundler::Definition.build(ROOT_DIR / "Gemfile", ROOT_DIR / "Gemfile.lock", nil)
89
+ package_json = JSON.parse(File.read(ROOT_DIR / "package.json"))
90
+
91
+ playwright_version = nil
92
+ gemfile.dependencies.each do |dep|
93
+ if dep.name == "playwright-ruby-client"
94
+ if !dep.specific?
95
+ puts "You must specify an exact/specific version of 'playwright-ruby-client' in your Gemfile"
96
+ exit 1
97
+ end
98
+ playwright_version = dep.requirement.requirements.first[1]
99
+ end
100
+ end
101
+
102
+ if playwright_version
103
+ playwright_version_in_package_json = package_json.dig("devDependencies","playwright")
104
+ if !playwright_version_in_package_json
105
+ puts "You must have 'playwright' in the devDependencies section of your package.json"
106
+ exit 1
107
+ end
108
+ if playwright_version != playwright_version_in_package_json
109
+ puts "The version of 'playwright' in package.json (#{playwright_version_in_package_json}) does not match the version of 'playwright-ruby-client' in the Gemfile (#{playwright_version})"
110
+ puts "They must be exactly the same, or your end-to-end tests may behave strangely"
111
+ exit 1
112
+ end
113
+ end
114
+ end
115
+
85
116
  def setup_dot_env_local
86
117
 
87
118
  dot_env = ROOT_DIR / ".env.development"
@@ -6,7 +6,3 @@ if [ ! -n "$PROJECT_ROOT" ]; then
6
6
  fi
7
7
  export GEM_HOME=${PROJECT_ROOT}/local-gems/gem-home
8
8
  export PATH=${PATH}:${GEM_HOME}/bin
9
- # This sets up the Node version so we don't have to do it before every. single.
10
- # shell. invocation.
11
- . ~/.nvm/nvm.sh
12
- nvm use default
@@ -11,7 +11,7 @@
11
11
  "esbuild": "^0.20.2",
12
12
  "jsdom": "^25.0.1",
13
13
  "mocha": "^10.7.3",
14
- "playwright": "1.50.1",
14
+ "playwright": "1.55.0",
15
15
  "typescript": "^5.8.3",
16
16
  "typescript-language-server": "^4.3.4",
17
17
  "vscode-langservers-extracted": "^4.10.0"