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.
- checksums.yaml +4 -4
- data/.gitignore +2 -0
- data/CHANGELOG.md +5 -0
- data/Dockerfile.dx +3 -6
- data/Gemfile.lock +1 -1
- data/brut-css/package-lock.json +2 -2
- data/brut-css/package.json +1 -1
- data/brut-js/package-lock.json +2 -2
- data/brut-js/package.json +1 -1
- data/brutrb.com/.vitepress/config.mjs +4 -3
- data/brutrb.com/deployment.md +23 -9
- data/brutrb.com/jobs.md +107 -7
- data/brutrb.com/recipes/dev-env-secrets.md +87 -0
- data/brutrb.com/roadmap.md +2 -7
- data/docs/404.html +2 -2
- data/docs/adrs.html +3 -3
- data/docs/ai.html +3 -3
- data/docs/api/Brut/BackEnd/SeedData.html +1 -1
- data/docs/api/Brut/BackEnd/Sidekiq/Middlewares/Server/FlushSpans.html +1 -1
- data/docs/api/Brut/BackEnd/Sidekiq/Middlewares/Server.html +1 -1
- data/docs/api/Brut/BackEnd/Sidekiq/Middlewares.html +1 -1
- data/docs/api/Brut/BackEnd/Sidekiq.html +1 -1
- data/docs/api/Brut/BackEnd/Validators/FormValidator.html +1 -1
- data/docs/api/Brut/BackEnd/Validators.html +1 -1
- data/docs/api/Brut/BackEnd.html +1 -1
- data/docs/api/Brut/CLI/App.html +1 -1
- data/docs/api/Brut/CLI/AppRunner.html +1 -1
- data/docs/api/Brut/CLI/Apps/BuildAssets/All.html +1 -1
- data/docs/api/Brut/CLI/Apps/BuildAssets/CSS.html +1 -1
- data/docs/api/Brut/CLI/Apps/BuildAssets/Images.html +1 -1
- data/docs/api/Brut/CLI/Apps/BuildAssets/JS.html +1 -1
- data/docs/api/Brut/CLI/Apps/BuildAssets.html +1 -1
- data/docs/api/Brut/CLI/Apps/DB/Create.html +1 -1
- data/docs/api/Brut/CLI/Apps/DB/Drop.html +1 -1
- data/docs/api/Brut/CLI/Apps/DB/Migrate.html +1 -1
- data/docs/api/Brut/CLI/Apps/DB/NewMigration.html +1 -1
- data/docs/api/Brut/CLI/Apps/DB/Rebuild.html +1 -1
- data/docs/api/Brut/CLI/Apps/DB/Seed.html +1 -1
- data/docs/api/Brut/CLI/Apps/DB/Status.html +1 -1
- data/docs/api/Brut/CLI/Apps/DB.html +1 -1
- data/docs/api/Brut/CLI/Apps/DeployBase/GitChecks.html +1 -1
- data/docs/api/Brut/CLI/Apps/DeployBase.html +1 -1
- data/docs/api/Brut/CLI/Apps/HerokuContainerBasedDeploy/Deploy.html +1 -1
- data/docs/api/Brut/CLI/Apps/HerokuContainerBasedDeploy.html +1 -1
- data/docs/api/Brut/CLI/Apps/Scaffold/Action/Route.html +1 -1
- data/docs/api/Brut/CLI/Apps/Scaffold/Action.html +1 -1
- data/docs/api/Brut/CLI/Apps/Scaffold/Component.html +1 -1
- data/docs/api/Brut/CLI/Apps/Scaffold/CustomElementTest.html +1 -1
- data/docs/api/Brut/CLI/Apps/Scaffold/DbModel.html +1 -1
- data/docs/api/Brut/CLI/Apps/Scaffold/E2ETest.html +1 -1
- data/docs/api/Brut/CLI/Apps/Scaffold/Form.html +1 -1
- data/docs/api/Brut/CLI/Apps/Scaffold/Page/Route.html +1 -1
- data/docs/api/Brut/CLI/Apps/Scaffold/Page.html +1 -1
- data/docs/api/Brut/CLI/Apps/Scaffold/RoutesEditor.html +1 -1
- data/docs/api/Brut/CLI/Apps/Scaffold/Test.html +1 -1
- data/docs/api/Brut/CLI/Apps/Scaffold.html +1 -1
- data/docs/api/Brut/CLI/Apps/Test/Audit.html +1 -1
- data/docs/api/Brut/CLI/Apps/Test/E2e.html +1 -1
- data/docs/api/Brut/CLI/Apps/Test/JS.html +1 -1
- data/docs/api/Brut/CLI/Apps/Test/Run.html +1 -1
- data/docs/api/Brut/CLI/Apps/Test.html +1 -1
- data/docs/api/Brut/CLI/Apps.html +1 -1
- data/docs/api/Brut/CLI/Command.html +1 -1
- data/docs/api/Brut/CLI/Error.html +1 -1
- data/docs/api/Brut/CLI/ExecutionResults/Result.html +1 -1
- data/docs/api/Brut/CLI/ExecutionResults.html +1 -1
- data/docs/api/Brut/CLI/Executor.html +1 -1
- data/docs/api/Brut/CLI/InvalidOption.html +1 -1
- data/docs/api/Brut/CLI/Options.html +1 -1
- data/docs/api/Brut/CLI/Output.html +1 -1
- data/docs/api/Brut/CLI/SystemExecError.html +1 -1
- data/docs/api/Brut/CLI.html +1 -1
- data/docs/api/Brut/FactoryBot.html +1 -1
- data/docs/api/Brut/Framework/App.html +1 -1
- data/docs/api/Brut/Framework/Config.html +1 -1
- data/docs/api/Brut/Framework/Container.html +1 -1
- data/docs/api/Brut/Framework/Error.html +1 -1
- data/docs/api/Brut/Framework/Errors/AbstractMethod.html +1 -1
- data/docs/api/Brut/Framework/Errors/Bug.html +1 -1
- data/docs/api/Brut/Framework/Errors/MissingConfiguration.html +1 -1
- data/docs/api/Brut/Framework/Errors/MissingParameter.html +1 -1
- data/docs/api/Brut/Framework/Errors/NoClassForPath.html +1 -1
- data/docs/api/Brut/Framework/Errors/NotFound.html +1 -1
- data/docs/api/Brut/Framework/Errors/NotImplemented.html +1 -1
- data/docs/api/Brut/Framework/Errors.html +1 -1
- data/docs/api/Brut/Framework/FussyTypeEnforcement.html +1 -1
- data/docs/api/Brut/Framework/MCP.html +1 -1
- data/docs/api/Brut/Framework/ProjectEnvironment.html +1 -1
- data/docs/api/Brut/Framework.html +1 -1
- data/docs/api/Brut/FrontEnd/AssetPathResolver.html +1 -1
- data/docs/api/Brut/FrontEnd/Component/Helpers.html +1 -1
- data/docs/api/Brut/FrontEnd/Component.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/ConstraintViolations.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/FormTag.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/I18nTranslations.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/Input.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/Inputs/ButtonTag.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/Inputs/CsrfToken.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/Inputs/InputTag.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/Inputs/RadioButton.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/Inputs/SelectTagWithOptions.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/Inputs/TextareaTag.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/Inputs.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/LocaleDetection.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/PageIdentifier.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/TimeTag.html +1 -1
- data/docs/api/Brut/FrontEnd/Components/Traceparent.html +1 -1
- data/docs/api/Brut/FrontEnd/Components.html +1 -1
- data/docs/api/Brut/FrontEnd/CsrfProtector.html +1 -1
- data/docs/api/Brut/FrontEnd/Download.html +1 -1
- data/docs/api/Brut/FrontEnd/Flash.html +1 -1
- data/docs/api/Brut/FrontEnd/Form.html +1 -1
- data/docs/api/Brut/FrontEnd/Forms/Button.html +1 -1
- data/docs/api/Brut/FrontEnd/Forms/ButtonInputDefinition.html +1 -1
- data/docs/api/Brut/FrontEnd/Forms/ConstraintViolation.html +1 -1
- data/docs/api/Brut/FrontEnd/Forms/Input/Color.html +1 -1
- data/docs/api/Brut/FrontEnd/Forms/Input/TimeOfDay.html +1 -1
- data/docs/api/Brut/FrontEnd/Forms/Input.html +1 -1
- data/docs/api/Brut/FrontEnd/Forms/InputDeclarations.html +1 -1
- data/docs/api/Brut/FrontEnd/Forms/InputDefinition.html +1 -1
- data/docs/api/Brut/FrontEnd/Forms/RadioButtonGroupInput.html +1 -1
- data/docs/api/Brut/FrontEnd/Forms/RadioButtonGroupInputDefinition.html +1 -1
- data/docs/api/Brut/FrontEnd/Forms/SelectInput.html +1 -1
- data/docs/api/Brut/FrontEnd/Forms/SelectInputDefinition.html +1 -1
- data/docs/api/Brut/FrontEnd/Forms/ValidityState.html +1 -1
- data/docs/api/Brut/FrontEnd/Forms.html +1 -1
- data/docs/api/Brut/FrontEnd/GenericResponse.html +1 -1
- data/docs/api/Brut/FrontEnd/Handler.html +1 -1
- data/docs/api/Brut/FrontEnd/Handlers/CspReportingHandler.html +1 -1
- data/docs/api/Brut/FrontEnd/Handlers/InstrumentationHandler/TraceParent.html +1 -1
- data/docs/api/Brut/FrontEnd/Handlers/InstrumentationHandler.html +1 -1
- data/docs/api/Brut/FrontEnd/Handlers/LocaleDetectionHandler.html +1 -1
- data/docs/api/Brut/FrontEnd/Handlers/MissingHandler/Form.html +1 -1
- data/docs/api/Brut/FrontEnd/Handlers/MissingHandler.html +1 -1
- data/docs/api/Brut/FrontEnd/Handlers.html +1 -1
- data/docs/api/Brut/FrontEnd/HandlingResults.html +1 -1
- data/docs/api/Brut/FrontEnd/HttpMethod.html +1 -1
- data/docs/api/Brut/FrontEnd/HttpStatus.html +1 -1
- data/docs/api/Brut/FrontEnd/InlineSvgLocator.html +1 -1
- data/docs/api/Brut/FrontEnd/Layout.html +1 -1
- data/docs/api/Brut/FrontEnd/Middleware.html +1 -1
- data/docs/api/Brut/FrontEnd/Middlewares/AnnotateBrutOwnedPaths.html +1 -1
- data/docs/api/Brut/FrontEnd/Middlewares/Favicon.html +1 -1
- data/docs/api/Brut/FrontEnd/Middlewares/OpenTelemetrySpan.html +1 -1
- data/docs/api/Brut/FrontEnd/Middlewares/ReloadApp.html +1 -1
- data/docs/api/Brut/FrontEnd/Middlewares.html +1 -1
- data/docs/api/Brut/FrontEnd/Page.html +1 -1
- data/docs/api/Brut/FrontEnd/Pages/MissingPage.html +1 -1
- data/docs/api/Brut/FrontEnd/Pages.html +1 -1
- data/docs/api/Brut/FrontEnd/RequestContext.html +1 -1
- data/docs/api/Brut/FrontEnd/RouteHook.html +1 -1
- data/docs/api/Brut/FrontEnd/RouteHooks/AgeFlash.html +1 -1
- data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineScripts.html +1 -1
- data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineStylesOrScripts/ReportOnly.html +1 -1
- data/docs/api/Brut/FrontEnd/RouteHooks/CSPNoInlineStylesOrScripts.html +1 -1
- data/docs/api/Brut/FrontEnd/RouteHooks/LocaleDetection.html +1 -1
- data/docs/api/Brut/FrontEnd/RouteHooks/SetupRequestContext.html +1 -1
- data/docs/api/Brut/FrontEnd/RouteHooks.html +1 -1
- data/docs/api/Brut/FrontEnd/Routing/FormHandlerRoute.html +1 -1
- data/docs/api/Brut/FrontEnd/Routing/FormRoute.html +1 -1
- data/docs/api/Brut/FrontEnd/Routing/MissingForm.html +1 -1
- data/docs/api/Brut/FrontEnd/Routing/MissingHandler.html +1 -1
- data/docs/api/Brut/FrontEnd/Routing/MissingPage.html +1 -1
- data/docs/api/Brut/FrontEnd/Routing/MissingPath.html +1 -1
- data/docs/api/Brut/FrontEnd/Routing/PageRoute.html +1 -1
- data/docs/api/Brut/FrontEnd/Routing/Route.html +1 -1
- data/docs/api/Brut/FrontEnd/Routing.html +1 -1
- data/docs/api/Brut/FrontEnd/Session.html +1 -1
- data/docs/api/Brut/FrontEnd.html +1 -1
- data/docs/api/Brut/I18n/BaseMethods.html +1 -1
- data/docs/api/Brut/I18n/ForBackEnd.html +1 -1
- data/docs/api/Brut/I18n/ForCLI.html +1 -1
- data/docs/api/Brut/I18n/ForHTML.html +1 -1
- data/docs/api/Brut/I18n/HTTPAcceptLanguage/AlwaysEnglish.html +1 -1
- data/docs/api/Brut/I18n/HTTPAcceptLanguage.html +1 -1
- data/docs/api/Brut/I18n.html +1 -1
- data/docs/api/Brut/Instrumentation/LoggerSpanExporter.html +1 -1
- data/docs/api/Brut/Instrumentation/Methods/ClassMethods.html +1 -1
- data/docs/api/Brut/Instrumentation/Methods.html +1 -1
- data/docs/api/Brut/Instrumentation/OpenTelemetry/NormalizedAttributes.html +1 -1
- data/docs/api/Brut/Instrumentation/OpenTelemetry/Span.html +1 -1
- data/docs/api/Brut/Instrumentation/OpenTelemetry.html +1 -1
- data/docs/api/Brut/Instrumentation.html +1 -1
- data/docs/api/Brut/RubocopConfig.html +1 -1
- data/docs/api/Brut/SinatraHelpers/ClassMethods.html +1 -1
- data/docs/api/Brut/SinatraHelpers.html +1 -1
- data/docs/api/Brut/SpecSupport/ClockSupport.html +1 -1
- data/docs/api/Brut/SpecSupport/ComponentSupport.html +1 -1
- data/docs/api/Brut/SpecSupport/E2ETestServer.html +1 -1
- data/docs/api/Brut/SpecSupport/E2eSupport.html +1 -1
- data/docs/api/Brut/SpecSupport/EnhancedNode.html +1 -1
- data/docs/api/Brut/SpecSupport/FlashSupport.html +1 -1
- data/docs/api/Brut/SpecSupport/GeneralSupport/ClassMethods.html +1 -1
- data/docs/api/Brut/SpecSupport/GeneralSupport.html +1 -1
- data/docs/api/Brut/SpecSupport/HandlerSupport.html +1 -1
- data/docs/api/Brut/SpecSupport/Matchers/BeABug.html +1 -1
- data/docs/api/Brut/SpecSupport/Matchers/BePageFor.html +1 -1
- data/docs/api/Brut/SpecSupport/Matchers/BeRoutingFor.html +1 -1
- data/docs/api/Brut/SpecSupport/Matchers/HaveConstraintViolation.html +1 -1
- data/docs/api/Brut/SpecSupport/Matchers/HaveGenerated.html +1 -1
- data/docs/api/Brut/SpecSupport/Matchers/HaveHTMLAttribute.html +1 -1
- data/docs/api/Brut/SpecSupport/Matchers/HaveI18nString.html +1 -1
- data/docs/api/Brut/SpecSupport/Matchers/HaveLinkTo.html +1 -1
- data/docs/api/Brut/SpecSupport/Matchers/HaveRedirectedTo.html +1 -1
- data/docs/api/Brut/SpecSupport/Matchers/HaveReturnedHttpStatus.html +1 -1
- data/docs/api/Brut/SpecSupport/Matchers/HaveReturnedRackResponse.html +1 -1
- data/docs/api/Brut/SpecSupport/Matchers.html +1 -1
- data/docs/api/Brut/SpecSupport/RSpecSetup/OptionalSidekiqSupport.html +1 -1
- data/docs/api/Brut/SpecSupport/RSpecSetup.html +1 -1
- data/docs/api/Brut/SpecSupport/SessionSupport.html +1 -1
- data/docs/api/Brut/SpecSupport.html +1 -1
- data/docs/api/Brut.html +1 -1
- data/docs/api/Clock.html +1 -1
- data/docs/api/ModuleName.html +1 -1
- data/docs/api/RichString.html +1 -1
- data/docs/api/SemanticLogger/Appender/Async.html +1 -1
- data/docs/api/Sequel/Extensions/BrutInstrumentation.html +1 -1
- data/docs/api/Sequel/Extensions/BrutMigrations.html +1 -1
- data/docs/api/Sequel/Extensions.html +1 -1
- data/docs/api/Sequel/Plugins/CreatedAt/InstanceMethods.html +1 -1
- data/docs/api/Sequel/Plugins/CreatedAt.html +1 -1
- data/docs/api/Sequel/Plugins/ExternalId/ClassMethods.html +1 -1
- data/docs/api/Sequel/Plugins/ExternalId/InstanceMethods.html +1 -1
- data/docs/api/Sequel/Plugins/ExternalId.html +1 -1
- data/docs/api/Sequel/Plugins/FindBang/ClassMethods.html +1 -1
- data/docs/api/Sequel/Plugins/FindBang.html +1 -1
- data/docs/api/Sequel/Plugins.html +1 -1
- data/docs/api/Sequel.html +1 -1
- data/docs/api/_index.html +1 -1
- data/docs/api/file.README.html +1 -1
- data/docs/api/index.html +1 -1
- data/docs/api/top-level-namespace.html +1 -1
- data/docs/assets/{app.B0X8upRm.js → app.Dm7v_ouO.js} +1 -1
- data/docs/assets/{brut-js.md.CbJAe2Ky.js → brut-js.md.BMz0X1Rz.js} +1 -1
- data/docs/assets/chunks/@localSearchIndexroot.BiNFswvo.js +1 -0
- data/docs/assets/chunks/{VPLocalSearchBox.jLmhant1.js → VPLocalSearchBox.DQK6jQou.js} +1 -1
- data/docs/assets/chunks/{theme.CtVUdCdt.js → theme.BuExsdM9.js} +2 -2
- data/docs/assets/{components.md.C6nWgDP0.js → components.md.Dfd3w6UW.js} +3 -3
- data/docs/assets/{configuration.md.CpbYHWPb.js → configuration.md.DTYoV2Ea.js} +1 -1
- data/docs/assets/{forms.md.Bii91k3E.js → forms.md.DEkmJUvb.js} +1 -1
- data/docs/assets/{getting-started.md.ChAvueK7.js → getting-started.md.DO-4eoGW.js} +2 -2
- data/docs/assets/{tutorials_02-dialog.md.De6iTsWX.js → tutorials_02-dialog.md.D2vSjDVf.js} +1 -1
- data/docs/assets.html +3 -3
- data/docs/brut-js/api/AjaxSubmit.html +2 -2
- data/docs/brut-js/api/AjaxSubmit.js.html +2 -2
- data/docs/brut-js/api/Autosubmit.html +2 -2
- data/docs/brut-js/api/Autosubmit.js.html +2 -2
- data/docs/brut-js/api/BaseCustomElement.html +2 -2
- data/docs/brut-js/api/BaseCustomElement.js.html +2 -2
- data/docs/brut-js/api/BrutCustomElements.html +3 -3
- data/docs/brut-js/api/BufferedLogger.html +2 -2
- data/docs/brut-js/api/ConfirmSubmit.html +2 -2
- data/docs/brut-js/api/ConfirmSubmit.js.html +2 -2
- data/docs/brut-js/api/ConfirmationDialog.html +2 -2
- data/docs/brut-js/api/ConfirmationDialog.js.html +2 -2
- data/docs/brut-js/api/ConstraintViolationMessage.html +2 -2
- data/docs/brut-js/api/ConstraintViolationMessage.js.html +2 -2
- data/docs/brut-js/api/ConstraintViolationMessages.html +2 -2
- data/docs/brut-js/api/ConstraintViolationMessages.js.html +2 -2
- data/docs/brut-js/api/CopyToClipboard.html +2 -2
- data/docs/brut-js/api/CopyToClipboard.js.html +2 -2
- data/docs/brut-js/api/Form.html +2 -2
- data/docs/brut-js/api/Form.js.html +2 -2
- data/docs/brut-js/api/I18nTranslation.html +2 -2
- data/docs/brut-js/api/I18nTranslation.js.html +5 -2
- data/docs/brut-js/api/LocaleDetection.html +2 -2
- data/docs/brut-js/api/LocaleDetection.js.html +2 -2
- data/docs/brut-js/api/Logger.html +2 -2
- data/docs/brut-js/api/Logger.js.html +2 -2
- data/docs/brut-js/api/Message.html +2 -2
- data/docs/brut-js/api/Message.js.html +11 -5
- data/docs/brut-js/api/PrefixedLogger.html +2 -2
- data/docs/brut-js/api/RichString.html +9 -9
- data/docs/brut-js/api/RichString.js.html +6 -3
- data/docs/brut-js/api/Tabs.html +2 -2
- data/docs/brut-js/api/Tabs.js.html +2 -2
- data/docs/brut-js/api/Toast.html +270 -0
- data/docs/brut-js/api/Toast.js.html +153 -0
- data/docs/brut-js/api/Tracing.html +2 -2
- data/docs/brut-js/api/Tracing.js.html +2 -2
- data/docs/brut-js/api/external-CustomElementRegistry.html +3 -3
- data/docs/brut-js/api/external-Performance.html +3 -3
- data/docs/brut-js/api/external-Promise.html +3 -3
- data/docs/brut-js/api/external-ValidityState.html +3 -3
- data/docs/brut-js/api/external-Window.html +4 -4
- data/docs/brut-js/api/external-fetch.html +3 -3
- data/docs/brut-js/api/global.html +3 -3
- data/docs/brut-js/api/index.html +2 -2
- data/docs/brut-js/api/index.js.html +5 -2
- data/docs/brut-js/api/module-testing.html +2 -2
- data/docs/brut-js/api/testing.AssetMetadata.html +2 -2
- data/docs/brut-js/api/testing.AssetMetadataLoader.html +2 -2
- data/docs/brut-js/api/testing.CustomElementTest.html +2 -2
- data/docs/brut-js/api/testing.DOMCreator.html +2 -2
- data/docs/brut-js/api/testing_AssetMetadata.js.html +2 -2
- data/docs/brut-js/api/testing_AssetMetadataLoader.js.html +2 -2
- data/docs/brut-js/api/testing_CustomElementTest.js.html +2 -2
- data/docs/brut-js/api/testing_DOMCreator.js.html +2 -2
- data/docs/brut-js/api/testing_index.js.html +2 -2
- data/docs/brut-js.html +5 -5
- data/docs/business-logic.html +3 -3
- data/docs/cli.html +3 -3
- data/docs/components.html +7 -7
- data/docs/configuration.html +5 -5
- data/docs/css.html +3 -3
- data/docs/custom-element-tests.html +3 -3
- data/docs/database-access.html +3 -3
- data/docs/database-schema.html +3 -3
- data/docs/deployment.html +3 -3
- data/docs/dev-environment.html +3 -3
- data/docs/dir-structure.html +3 -3
- data/docs/doc-conventions.html +3 -3
- data/docs/end-to-end-tests.html +3 -3
- data/docs/features.html +3 -3
- data/docs/flash-and-session.html +3 -3
- data/docs/form-constraints.html +3 -3
- data/docs/forms.html +5 -5
- data/docs/getting-started.html +6 -6
- data/docs/handlers.html +3 -3
- data/docs/hashmap.json +1 -1
- data/docs/hooks.html +3 -3
- data/docs/i18n.html +3 -3
- data/docs/index.html +3 -3
- data/docs/instrumentation.html +3 -3
- data/docs/javascript.html +3 -3
- data/docs/jobs.html +3 -3
- data/docs/keyword-injection.html +3 -3
- data/docs/layouts.html +3 -3
- data/docs/lsp.html +3 -3
- data/docs/markdown-examples.html +3 -3
- data/docs/middleware.html +3 -3
- data/docs/overview.html +3 -3
- data/docs/pages.html +3 -3
- data/docs/recipes/alternate-layouts.html +3 -3
- data/docs/recipes/authentication.html +3 -3
- data/docs/recipes/custom-flash.html +3 -3
- data/docs/recipes/form-errors.html +3 -3
- data/docs/recipes/indexed-forms.html +3 -3
- data/docs/recipes/migrations.html +3 -3
- data/docs/recipes/text-field-component.html +3 -3
- data/docs/roadmap.html +3 -3
- data/docs/routes.html +3 -3
- data/docs/security.html +3 -3
- data/docs/seed-data.html +3 -3
- data/docs/space-time-continuum.html +3 -3
- data/docs/tutorial.html +3 -3
- data/docs/tutorials/01-intro.html +3 -3
- data/docs/tutorials/02-dialog.html +5 -5
- data/docs/unit-tests.html +3 -3
- data/docs/why.html +3 -3
- data/dx/bash_customizations +3 -4
- data/dx/build.pre +15 -0
- data/lib/brut/cli/apps/heroku_container_based_deploy.rb +41 -15
- data/lib/brut/cli/apps/test.rb +2 -0
- data/lib/brut/framework/container.rb +6 -4
- data/lib/brut/framework/mcp.rb +1 -1
- data/lib/brut/version.rb +1 -1
- data/mkbrut/Gemfile.lock +2 -2
- data/mkbrut/lib/mkbrut/add_segment.rb +38 -0
- data/mkbrut/lib/mkbrut/add_segment_options.rb +22 -0
- data/mkbrut/lib/mkbrut/app.rb +7 -2
- data/mkbrut/lib/mkbrut/base.rb +1 -0
- data/mkbrut/lib/mkbrut/cli.rb +90 -8
- data/mkbrut/lib/mkbrut/ops/insert_code_in_method.rb +19 -7
- data/mkbrut/lib/mkbrut/ops/insert_into_file.rb +36 -0
- data/mkbrut/lib/mkbrut/ops/prism_parsing_op.rb +14 -2
- data/mkbrut/lib/mkbrut/ops.rb +1 -0
- data/mkbrut/lib/mkbrut/segments/bare_bones.rb +8 -0
- data/mkbrut/lib/mkbrut/segments/demo.rb +8 -0
- data/mkbrut/lib/mkbrut/segments/heroku.rb +23 -3
- data/mkbrut/lib/mkbrut/segments/sidekiq.rb +136 -1
- data/mkbrut/lib/mkbrut/segments.rb +1 -1
- data/mkbrut/lib/mkbrut/version.rb +1 -1
- data/mkbrut/lib/mkbrut.rb +2 -0
- data/mkbrut/templates/Base/.gitignore +3 -0
- data/mkbrut/templates/Base/Dockerfile.dx +18 -21
- data/mkbrut/templates/Base/Gemfile.erb +1 -1
- data/mkbrut/templates/Base/bin/run +107 -67
- data/mkbrut/templates/Base/bin/run.run +4 -0
- data/mkbrut/templates/Base/bin/setup +32 -1
- data/mkbrut/templates/Base/dx/bash_customizations +0 -4
- data/mkbrut/templates/Base/package.json.erb +1 -1
- data/mkbrut/templates/segments/Heroku/deploy/Dockerfile +15 -15
- data/mkbrut/templates/segments/Heroku/deploy/heroku_config.rb +5 -4
- data/mkbrut/templates/segments/Sidekiq/app/boot_sidekiq.rb +2 -0
- data/mkbrut/templates/segments/Sidekiq/app/config/sidekiq.yml +4 -0
- data/mkbrut/templates/segments/Sidekiq/app/src/back_end/jobs/app_job.rb +3 -0
- data/mkbrut/templates/segments/Sidekiq/app/src/back_end/jobs/example_job.rb +12 -0
- data/mkbrut/templates/segments/Sidekiq/app/src/back_end/segments/sidekiq_segment.rb +56 -0
- data/mkbrut/templates/segments/Sidekiq/bin/run.sidekiq +4 -0
- data/mkbrut/templates/segments/Sidekiq/specs/back_end/jobs/example_job.spec.rb +5 -0
- data/mkbrut/templates/segments/Sidekiq/specs/integration/sidekiq_works.spec.rb +38 -0
- metadata +34 -18
- data/docs/assets/chunks/@localSearchIndexroot.C0s1k0UQ.js +0 -1
- /data/docs/assets/{brut-js.md.CbJAe2Ky.lean.js → brut-js.md.BMz0X1Rz.lean.js} +0 -0
- /data/docs/assets/{components.md.C6nWgDP0.lean.js → components.md.Dfd3w6UW.lean.js} +0 -0
- /data/docs/assets/{configuration.md.CpbYHWPb.lean.js → configuration.md.DTYoV2Ea.lean.js} +0 -0
- /data/docs/assets/{forms.md.Bii91k3E.lean.js → forms.md.DEkmJUvb.lean.js} +0 -0
- /data/docs/assets/{getting-started.md.ChAvueK7.lean.js → getting-started.md.DO-4eoGW.lean.js} +0 -0
- /data/docs/assets/{tutorials_02-dialog.md.De6iTsWX.lean.js → tutorials_02-dialog.md.D2vSjDVf.lean.js} +0 -0
data/mkbrut/lib/mkbrut/ops.rb
CHANGED
@@ -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(
|
5
|
-
@project_root =
|
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/
|
5
|
+
autoload :Sidekiq, "mkbrut/segments/sidekiq"
|
6
6
|
autoload :Heroku, "mkbrut/segments/heroku"
|
7
7
|
end
|
8
8
|
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
|
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
|
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
|
-
#
|
182
|
-
#
|
183
|
-
#
|
184
|
-
|
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
|
-
|
203
|
-
|
204
|
-
|
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.
|
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/
|
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
|
-
|
12
|
+
declare -A CMD_MAP
|
13
|
+
declare -A PIDFILE_MAP
|
14
|
+
declare -A CREATES_PIDFILE_MAP
|
33
15
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
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
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
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}'
|
62
|
-
|
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
|
-
|
68
|
-
|
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
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
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
|
@@ -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
|