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.
- checksums.yaml +4 -4
- data/.gitignore +2 -0
- data/CHANGELOG.md +10 -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/brut-js/specs/Toast.spec.js +34 -0
- data/brut-js/src/I18nTranslation.js +3 -0
- data/brut-js/src/Message.js +9 -3
- data/brut-js/src/RichString.js +4 -1
- data/brut-js/src/Toast.js +102 -0
- data/brut-js/src/index.js +3 -0
- data/brutrb.com/.vitepress/config.mjs +4 -3
- data/brutrb.com/brut-js.md +1 -0
- 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 +3 -3
- data/docs/adrs.html +7 -7
- data/docs/ai.html +7 -7
- 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 +37 -18
- 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 +171 -3
- 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/method_list.html +157 -141
- data/docs/api/top-level-namespace.html +1 -1
- data/docs/assets/adrs.md.YglbWtQe.js +1 -0
- data/docs/assets/adrs.md.YglbWtQe.lean.js +1 -0
- data/docs/assets/ai.md.ChLnvDAX.js +1 -0
- data/docs/assets/ai.md.ChLnvDAX.lean.js +1 -0
- data/docs/assets/{app.BDtsVxyd.js → app.Dm7v_ouO.js} +1 -1
- data/docs/assets/{assets.md.7C3HWkga.js → assets.md.BEF6Oz6K.js} +2 -2
- data/docs/assets/assets.md.BEF6Oz6K.lean.js +1 -0
- data/docs/assets/{brut-js.md.B4GYxQVw.js → brut-js.md.BMz0X1Rz.js} +2 -2
- data/docs/assets/brut-js.md.BMz0X1Rz.lean.js +1 -0
- data/docs/assets/business-logic.md.DbuaOYGU.js +1 -0
- data/docs/assets/business-logic.md.DbuaOYGU.lean.js +1 -0
- data/docs/assets/chunks/@localSearchIndexroot.BiNFswvo.js +1 -0
- data/docs/assets/chunks/VPLocalSearchBox.DQK6jQou.js +8 -0
- data/docs/assets/chunks/framework.C4nOkCZI.js +18 -0
- data/docs/assets/chunks/{theme.DZKmijwi.js → theme.BuExsdM9.js} +2 -2
- data/docs/assets/{cli.md.CjsktgFz.js → cli.md.DDMar_51.js} +2 -2
- data/docs/assets/cli.md.DDMar_51.lean.js +1 -0
- data/docs/assets/{components.md.rMhQ0WdZ.js → components.md.Dfd3w6UW.js} +5 -5
- data/docs/assets/components.md.Dfd3w6UW.lean.js +1 -0
- data/docs/assets/{configuration.md.BK42Yjp_.js → configuration.md.DTYoV2Ea.js} +2 -2
- data/docs/assets/configuration.md.DTYoV2Ea.lean.js +1 -0
- data/docs/assets/{css.md.CltvJqAa.js → css.md.K5rOCOQY.js} +2 -2
- data/docs/assets/css.md.K5rOCOQY.lean.js +1 -0
- data/docs/assets/{custom-element-tests.md.B_rbta32.js → custom-element-tests.md.DiLe-eFw.js} +2 -2
- data/docs/assets/custom-element-tests.md.DiLe-eFw.lean.js +1 -0
- data/docs/assets/{database-access.md.gnluu54N.js → database-access.md.Dc8l2Plf.js} +2 -2
- data/docs/assets/database-access.md.Dc8l2Plf.lean.js +1 -0
- data/docs/assets/{database-schema.md.LpmBPVEU.js → database-schema.md.BJ_JhXmO.js} +2 -2
- data/docs/assets/database-schema.md.BJ_JhXmO.lean.js +1 -0
- data/docs/assets/{deployment.md.BLseERGV.js → deployment.md.C1u5ep0g.js} +2 -2
- data/docs/assets/deployment.md.C1u5ep0g.lean.js +1 -0
- data/docs/assets/{dev-environment.md.DRH2D2-O.js → dev-environment.md.B1S9p5ZK.js} +2 -2
- data/docs/assets/{dev-environment.md.DRH2D2-O.lean.js → dev-environment.md.B1S9p5ZK.lean.js} +1 -1
- data/docs/assets/{dir-structure.md.CWir1pic.js → dir-structure.md.D1T2kGwj.js} +2 -2
- data/docs/assets/dir-structure.md.D1T2kGwj.lean.js +1 -0
- data/docs/assets/doc-conventions.md.CDnWaEFg.js +1 -0
- data/docs/assets/doc-conventions.md.CDnWaEFg.lean.js +1 -0
- data/docs/assets/{end-to-end-tests.md.DzqRpZ43.js → end-to-end-tests.md.BJJdNDYL.js} +2 -2
- data/docs/assets/end-to-end-tests.md.BJJdNDYL.lean.js +1 -0
- data/docs/assets/{features.md.DPFXsy0z.js → features.md.BDWxnyNO.js} +2 -2
- data/docs/assets/features.md.BDWxnyNO.lean.js +1 -0
- data/docs/assets/{flash-and-session.md.nPvUpnUx.js → flash-and-session.md.CUsMxoNl.js} +2 -2
- data/docs/assets/flash-and-session.md.CUsMxoNl.lean.js +1 -0
- data/docs/assets/{form-constraints.md.KTv5cdR4.js → form-constraints.md.KlfXSKm2.js} +2 -2
- data/docs/assets/form-constraints.md.KlfXSKm2.lean.js +1 -0
- data/docs/assets/{forms.md.v9qIbmUM.js → forms.md.DEkmJUvb.js} +3 -3
- data/docs/assets/forms.md.DEkmJUvb.lean.js +1 -0
- data/docs/assets/{getting-started.md.DTOl4c2g.js → getting-started.md.DO-4eoGW.js} +4 -4
- data/docs/assets/getting-started.md.DO-4eoGW.lean.js +1 -0
- data/docs/assets/{handlers.md.h84MMB1R.js → handlers.md.C5tUwmmo.js} +2 -2
- data/docs/assets/handlers.md.C5tUwmmo.lean.js +1 -0
- data/docs/assets/{hooks.md.Jmb5VOLA.js → hooks.md.CoiYCKRc.js} +2 -2
- data/docs/assets/hooks.md.CoiYCKRc.lean.js +1 -0
- data/docs/assets/{i18n.md.BAm9t9JJ.js → i18n.md.DxkCKhUw.js} +2 -2
- data/docs/assets/i18n.md.DxkCKhUw.lean.js +1 -0
- data/docs/assets/{index.md.Bn9e0sRJ.js → index.md.DnphWyQd.js} +1 -1
- data/docs/assets/{index.md.Bn9e0sRJ.lean.js → index.md.DnphWyQd.lean.js} +1 -1
- data/docs/assets/{instrumentation.md._lNSriEZ.js → instrumentation.md.BcxjC4jd.js} +2 -2
- data/docs/assets/instrumentation.md.BcxjC4jd.lean.js +1 -0
- data/docs/assets/{javascript.md.DzrMxUmI.js → javascript.md.D6fxhaQb.js} +2 -2
- data/docs/assets/javascript.md.D6fxhaQb.lean.js +1 -0
- data/docs/assets/jobs.md.Bc7Y1YpK.js +1 -0
- data/docs/assets/jobs.md.Bc7Y1YpK.lean.js +1 -0
- data/docs/assets/{keyword-injection.md.95Zgh2eN.js → keyword-injection.md.CqLnnzIz.js} +2 -2
- data/docs/assets/keyword-injection.md.CqLnnzIz.lean.js +1 -0
- data/docs/assets/layouts.md.HEbeK7Jr.js +68 -0
- data/docs/assets/layouts.md.HEbeK7Jr.lean.js +1 -0
- data/docs/assets/lsp.md.bE9dW8n9.js +1 -0
- data/docs/assets/lsp.md.bE9dW8n9.lean.js +1 -0
- data/docs/assets/{markdown-examples.md.CCFEQO44.js → markdown-examples.md.BPmtHlc-.js} +2 -2
- data/docs/assets/markdown-examples.md.BPmtHlc-.lean.js +1 -0
- data/docs/assets/{middleware.md.Czz_UlJN.js → middleware.md.BhOIsg59.js} +2 -2
- data/docs/assets/middleware.md.BhOIsg59.lean.js +1 -0
- data/docs/assets/overview.md.BpWAgPFH.js +1 -0
- data/docs/assets/overview.md.BpWAgPFH.lean.js +1 -0
- data/docs/assets/{pages.md.B7Hc-i6H.js → pages.md.B3sQXpEd.js} +2 -2
- data/docs/assets/pages.md.B3sQXpEd.lean.js +1 -0
- data/docs/assets/{recipes_alternate-layouts.md.BwEytl59.js → recipes_alternate-layouts.md.C1QzVkA7.js} +2 -2
- data/docs/assets/recipes_alternate-layouts.md.C1QzVkA7.lean.js +1 -0
- data/docs/assets/{recipes_authentication.md.nwO6F7Ou.js → recipes_authentication.md.CyvoIW82.js} +2 -2
- data/docs/assets/recipes_authentication.md.CyvoIW82.lean.js +1 -0
- data/docs/assets/{recipes_custom-flash.md.CrQbI5eH.js → recipes_custom-flash.md.6gFqf2uL.js} +2 -2
- data/docs/assets/recipes_custom-flash.md.6gFqf2uL.lean.js +1 -0
- data/docs/assets/{recipes_form-errors.md.Bv5RCKqH.js → recipes_form-errors.md.B5ptSzMO.js} +2 -2
- data/docs/assets/recipes_form-errors.md.B5ptSzMO.lean.js +1 -0
- data/docs/assets/{recipes_indexed-forms.md.CstYyOSo.js → recipes_indexed-forms.md.BYYQGW2C.js} +2 -2
- data/docs/assets/recipes_indexed-forms.md.BYYQGW2C.lean.js +1 -0
- data/docs/assets/{recipes_migrations.md.CTcnWDJF.js → recipes_migrations.md.Cid7-3cu.js} +2 -2
- data/docs/assets/recipes_migrations.md.Cid7-3cu.lean.js +1 -0
- data/docs/assets/{recipes_text-field-component.md.H4wLAK0Z.js → recipes_text-field-component.md.VhOsCtKI.js} +2 -2
- data/docs/assets/recipes_text-field-component.md.VhOsCtKI.lean.js +1 -0
- data/docs/assets/roadmap.md.CJsbUmK_.js +1 -0
- data/docs/assets/roadmap.md.CJsbUmK_.lean.js +1 -0
- data/docs/assets/{routes.md.BD6y2i-f.js → routes.md.C1dgIBtD.js} +2 -2
- data/docs/assets/routes.md.C1dgIBtD.lean.js +1 -0
- data/docs/assets/security.md.Jn4SY1uK.js +1 -0
- data/docs/assets/security.md.Jn4SY1uK.lean.js +1 -0
- data/docs/assets/{seed-data.md.BvFZlqIk.js → seed-data.md.UZW0WxYN.js} +2 -2
- data/docs/assets/seed-data.md.UZW0WxYN.lean.js +1 -0
- data/docs/assets/space-time-continuum.md.D9rYGDFH.js +1 -0
- data/docs/assets/space-time-continuum.md.D9rYGDFH.lean.js +1 -0
- data/docs/assets/{tutorial.md.BM40jnoq.js → tutorial.md.BX6f6l00.js} +2 -2
- data/docs/assets/tutorial.md.BX6f6l00.lean.js +1 -0
- data/docs/assets/{tutorials_01-intro.md.B4sUBY3X.js → tutorials_01-intro.md.CzZ3kpF_.js} +2 -2
- data/docs/assets/{tutorials_01-intro.md.B4sUBY3X.lean.js → tutorials_01-intro.md.CzZ3kpF_.lean.js} +1 -1
- data/docs/assets/{tutorials_02-dialog.md.CPNK1SC_.js → tutorials_02-dialog.md.D2vSjDVf.js} +2 -2
- data/docs/assets/{tutorials_02-dialog.md.CPNK1SC_.lean.js → tutorials_02-dialog.md.D2vSjDVf.lean.js} +1 -1
- data/docs/assets/{unit-tests.md.DUGrnLj5.js → unit-tests.md.vDsdBbO_.js} +2 -2
- data/docs/assets/unit-tests.md.vDsdBbO_.lean.js +1 -0
- data/docs/assets/why.md.4WpxdrQ2.js +1 -0
- data/docs/assets/why.md.4WpxdrQ2.lean.js +1 -0
- data/docs/assets.html +7 -7
- 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 +8 -8
- data/docs/business-logic.html +7 -7
- data/docs/cli.html +7 -7
- data/docs/components.html +10 -10
- data/docs/configuration.html +7 -7
- data/docs/css.html +7 -7
- data/docs/custom-element-tests.html +7 -7
- data/docs/database-access.html +7 -7
- data/docs/database-schema.html +7 -7
- data/docs/deployment.html +7 -7
- data/docs/dev-environment.html +7 -7
- data/docs/dir-structure.html +7 -7
- data/docs/doc-conventions.html +7 -7
- data/docs/end-to-end-tests.html +7 -7
- data/docs/features.html +7 -7
- data/docs/flash-and-session.html +7 -7
- data/docs/form-constraints.html +7 -7
- data/docs/forms.html +8 -8
- data/docs/getting-started.html +9 -9
- data/docs/handlers.html +7 -7
- data/docs/hashmap.json +1 -1
- data/docs/hooks.html +7 -7
- data/docs/i18n.html +7 -7
- data/docs/index.html +6 -6
- data/docs/instrumentation.html +7 -7
- data/docs/javascript.html +7 -7
- data/docs/jobs.html +7 -7
- data/docs/keyword-injection.html +7 -7
- data/docs/layouts.html +42 -12
- data/docs/lsp.html +7 -7
- data/docs/markdown-examples.html +7 -7
- data/docs/middleware.html +7 -7
- data/docs/overview.html +7 -7
- data/docs/pages.html +7 -7
- data/docs/recipes/alternate-layouts.html +8 -8
- data/docs/recipes/authentication.html +7 -7
- data/docs/recipes/custom-flash.html +8 -8
- data/docs/recipes/form-errors.html +7 -7
- data/docs/recipes/indexed-forms.html +7 -7
- data/docs/recipes/migrations.html +7 -7
- data/docs/recipes/text-field-component.html +7 -7
- data/docs/roadmap.html +7 -7
- data/docs/routes.html +7 -7
- data/docs/security.html +7 -7
- data/docs/seed-data.html +7 -7
- data/docs/space-time-continuum.html +7 -7
- data/docs/tutorial.html +7 -7
- data/docs/tutorials/01-intro.html +7 -7
- data/docs/tutorials/02-dialog.html +7 -7
- data/docs/unit-tests.html +7 -7
- data/docs/why.html +7 -7
- 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 +131 -116
- data/docs/assets/adrs.md.BxjHi9-8.js +0 -1
- data/docs/assets/adrs.md.BxjHi9-8.lean.js +0 -1
- data/docs/assets/ai.md.Cy9GWnER.js +0 -1
- data/docs/assets/ai.md.Cy9GWnER.lean.js +0 -1
- data/docs/assets/assets.md.7C3HWkga.lean.js +0 -1
- data/docs/assets/brut-js.md.B4GYxQVw.lean.js +0 -1
- data/docs/assets/business-logic.md.BY4hGy0m.js +0 -1
- data/docs/assets/business-logic.md.BY4hGy0m.lean.js +0 -1
- data/docs/assets/chunks/@localSearchIndexroot.BWVzhs5N.js +0 -1
- data/docs/assets/chunks/VPLocalSearchBox.DCJk5nAW.js +0 -8
- data/docs/assets/chunks/framework.1L-BeKqY.js +0 -18
- data/docs/assets/cli.md.CjsktgFz.lean.js +0 -1
- data/docs/assets/components.md.rMhQ0WdZ.lean.js +0 -1
- data/docs/assets/configuration.md.BK42Yjp_.lean.js +0 -1
- data/docs/assets/css.md.CltvJqAa.lean.js +0 -1
- data/docs/assets/custom-element-tests.md.B_rbta32.lean.js +0 -1
- data/docs/assets/database-access.md.gnluu54N.lean.js +0 -1
- data/docs/assets/database-schema.md.LpmBPVEU.lean.js +0 -1
- data/docs/assets/deployment.md.BLseERGV.lean.js +0 -1
- data/docs/assets/dir-structure.md.CWir1pic.lean.js +0 -1
- data/docs/assets/doc-conventions.md.DOkAuXlt.js +0 -1
- data/docs/assets/doc-conventions.md.DOkAuXlt.lean.js +0 -1
- data/docs/assets/end-to-end-tests.md.DzqRpZ43.lean.js +0 -1
- data/docs/assets/features.md.DPFXsy0z.lean.js +0 -1
- data/docs/assets/flash-and-session.md.nPvUpnUx.lean.js +0 -1
- data/docs/assets/form-constraints.md.KTv5cdR4.lean.js +0 -1
- data/docs/assets/forms.md.v9qIbmUM.lean.js +0 -1
- data/docs/assets/getting-started.md.DTOl4c2g.lean.js +0 -1
- data/docs/assets/handlers.md.h84MMB1R.lean.js +0 -1
- data/docs/assets/hooks.md.Jmb5VOLA.lean.js +0 -1
- data/docs/assets/i18n.md.BAm9t9JJ.lean.js +0 -1
- data/docs/assets/instrumentation.md._lNSriEZ.lean.js +0 -1
- data/docs/assets/javascript.md.DzrMxUmI.lean.js +0 -1
- data/docs/assets/jobs.md.S-2amAYp.js +0 -1
- data/docs/assets/jobs.md.S-2amAYp.lean.js +0 -1
- data/docs/assets/keyword-injection.md.95Zgh2eN.lean.js +0 -1
- data/docs/assets/layouts.md.CVGl9xIO.js +0 -38
- data/docs/assets/layouts.md.CVGl9xIO.lean.js +0 -1
- data/docs/assets/lsp.md.Dn1rIiW0.js +0 -1
- data/docs/assets/lsp.md.Dn1rIiW0.lean.js +0 -1
- data/docs/assets/markdown-examples.md.CCFEQO44.lean.js +0 -1
- data/docs/assets/middleware.md.Czz_UlJN.lean.js +0 -1
- data/docs/assets/overview.md.DlKiRRG_.js +0 -1
- data/docs/assets/overview.md.DlKiRRG_.lean.js +0 -1
- data/docs/assets/pages.md.B7Hc-i6H.lean.js +0 -1
- data/docs/assets/recipes_alternate-layouts.md.BwEytl59.lean.js +0 -1
- data/docs/assets/recipes_authentication.md.nwO6F7Ou.lean.js +0 -1
- data/docs/assets/recipes_blank-layouts.md.fyAUJyJR.js +0 -15
- data/docs/assets/recipes_blank-layouts.md.fyAUJyJR.lean.js +0 -1
- data/docs/assets/recipes_custom-flash.md.CrQbI5eH.lean.js +0 -1
- data/docs/assets/recipes_form-errors.md.Bv5RCKqH.lean.js +0 -1
- data/docs/assets/recipes_indexed-forms.md.CstYyOSo.lean.js +0 -1
- data/docs/assets/recipes_migrations.md.CTcnWDJF.lean.js +0 -1
- data/docs/assets/recipes_text-field-component.md.H4wLAK0Z.lean.js +0 -1
- data/docs/assets/roadmap.md.C6PRi0DX.js +0 -1
- data/docs/assets/roadmap.md.C6PRi0DX.lean.js +0 -1
- data/docs/assets/routes.md.BD6y2i-f.lean.js +0 -1
- data/docs/assets/security.md.C0G_AZR-.js +0 -1
- data/docs/assets/security.md.C0G_AZR-.lean.js +0 -1
- data/docs/assets/seed-data.md.BvFZlqIk.lean.js +0 -1
- data/docs/assets/space-time-continuum.md.xl44xDos.js +0 -1
- data/docs/assets/space-time-continuum.md.xl44xDos.lean.js +0 -1
- data/docs/assets/tutorial.md.BM40jnoq.lean.js +0 -1
- data/docs/assets/unit-tests.md.DUGrnLj5.lean.js +0 -1
- data/docs/assets/why.md.C-hk5xgJ.js +0 -1
- data/docs/assets/why.md.C-hk5xgJ.lean.js +0 -1
- data/docs/recipes/blank-layouts.html +0 -43
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 98447eff97921a8aeb30a9ffa801452a97c8f46a6169e300f6792582c6af191c
|
4
|
+
data.tar.gz: 3040a27ce2283e967f0f0bac694cb0a4fcaa7c7c0bd1bb2c52246dea9dcc2ba5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 834e282f6c08942742733de34e04b9b4ada8cf03bbff02b6604fe54a49af28d1fdebe9da173b852e49f29ab87562335495fc9b6846a0b3a6184aa833677908d1
|
7
|
+
data.tar.gz: 9d41d53d28c12e4d5a99ab81490f10c5e0ccb4f6158c1c1dee51ba5321e3d1e92c04e5e1d566815319dea20bc0071662da174c4d930e84d7a30510da1a382cca
|
data/.gitignore
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,15 @@
|
|
1
1
|
# Brut CHANGELOG
|
2
2
|
|
3
|
+
## v0.16.0.pre - Sep 29, 2025
|
4
|
+
|
5
|
+
* Sidekiq support - this may have some churn in various config files that only reveal themselves on deploy so this
|
6
|
+
is a pre-release. Do not use this version.
|
7
|
+
|
8
|
+
## v0.15.0 - Sep 16, 2025
|
9
|
+
|
10
|
+
* Added `<brut-toast>` to help with toast notifications. They rely on CSS for showing, hiding, and animations, all your
|
11
|
+
client-side JS needs to do is set the `key` attribute to show the Toast with an i18n-provided message
|
12
|
+
|
3
13
|
## v0.14.0 - Sep 9, 2025
|
4
14
|
|
5
15
|
* **BREAKING CHANGE** Layouts now have access to the page object they are
|
data/Dockerfile.dx
CHANGED
@@ -37,6 +37,9 @@ ENV EDITOR=vim
|
|
37
37
|
RUN apt-get install -y vim && \
|
38
38
|
echo "set -o vi" >> /root/.bashrc
|
39
39
|
|
40
|
+
# Install NodeJS
|
41
|
+
COPY --from=node:22-slim /usr/local /usr/local
|
42
|
+
|
40
43
|
|
41
44
|
# Setup a non-root user
|
42
45
|
|
@@ -70,12 +73,6 @@ COPY --chown=appuser:${user_gid} dx/bash_customizations.local /home/appuser/.bas
|
|
70
73
|
# ONLY in that group and not in all the groups in which they are a part.
|
71
74
|
USER appuser
|
72
75
|
|
73
|
-
# Install NodeJS, per https://nodejs.org/en/download
|
74
|
-
RUN curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | bash && \
|
75
|
-
\. "$HOME/.nvm/nvm.sh" && \
|
76
|
-
nvm install 22 && \
|
77
|
-
node -v && nvm current && npm -v
|
78
|
-
|
79
76
|
# Node's colors are hand-crafted to always look bad and render at least some text unreadable
|
80
77
|
# no matter what your setup. Cool.
|
81
78
|
ENV NODE_DISABLE_COLORS=1
|
data/Gemfile.lock
CHANGED
data/brut-css/package-lock.json
CHANGED
@@ -1,12 +1,12 @@
|
|
1
1
|
{
|
2
2
|
"name": "brut-css",
|
3
|
-
"version": "0.
|
3
|
+
"version": "0.16.0.pre",
|
4
4
|
"lockfileVersion": 3,
|
5
5
|
"requires": true,
|
6
6
|
"packages": {
|
7
7
|
"": {
|
8
8
|
"name": "brut-css",
|
9
|
-
"version": "0.
|
9
|
+
"version": "0.16.0.pre",
|
10
10
|
"license": "Hippocratic-2.1",
|
11
11
|
"devDependencies": {
|
12
12
|
"comment-parser": "^1.4.1",
|
data/brut-css/package.json
CHANGED
data/brut-js/package-lock.json
CHANGED
@@ -1,12 +1,12 @@
|
|
1
1
|
{
|
2
2
|
"name": "brut-js",
|
3
|
-
"version": "0.
|
3
|
+
"version": "0.16.0.pre",
|
4
4
|
"lockfileVersion": 3,
|
5
5
|
"requires": true,
|
6
6
|
"packages": {
|
7
7
|
"": {
|
8
8
|
"name": "brut-js",
|
9
|
-
"version": "0.
|
9
|
+
"version": "0.16.0.pre",
|
10
10
|
"license": "Hippocratic-2.1",
|
11
11
|
"devDependencies": {
|
12
12
|
"esbuild": "^0.24.2",
|
data/brut-js/package.json
CHANGED
@@ -0,0 +1,34 @@
|
|
1
|
+
import { withHTML } from "./SpecHelper.js"
|
2
|
+
|
3
|
+
describe("<brut-toast>", () => {
|
4
|
+
withHTML(`
|
5
|
+
<div>
|
6
|
+
<brut-i18n-translation key="toast.saved" value="Save successful"></brut-i18n-translation>
|
7
|
+
<brut-toast show-warnings>
|
8
|
+
<div>
|
9
|
+
<output><span>Message here</span></output>
|
10
|
+
<button>Close</button>
|
11
|
+
</div>
|
12
|
+
<button>Close</button>
|
13
|
+
</brut-toast>
|
14
|
+
</div>
|
15
|
+
`).test("setting the key shows the message, then it's removed when closed", ({window,document,assert}) => {
|
16
|
+
|
17
|
+
const toast = document.querySelector("brut-toast")
|
18
|
+
assert(toast, "brut-toast element should be present")
|
19
|
+
|
20
|
+
toast.setAttribute("key", "toast.saved")
|
21
|
+
const output = toast.querySelector("output")
|
22
|
+
assert.equal(output.textContent.trim(), "Save successful")
|
23
|
+
const message = output.querySelector("brut-message")
|
24
|
+
assert(message, "brut-message should be present")
|
25
|
+
console.log(message.outerHTML)
|
26
|
+
assert.equal(message.getAttribute("role"), "status")
|
27
|
+
assert.equal(message.getAttribute("aria-live"), "polite")
|
28
|
+
assert.equal(message.getAttribute("aria-atomic"), "true")
|
29
|
+
|
30
|
+
const button = toast.querySelector("button")
|
31
|
+
button.click()
|
32
|
+
assert.equal(toast.getAttribute("key"), null)
|
33
|
+
})
|
34
|
+
})
|
@@ -49,6 +49,9 @@ class I18nTranslation extends BaseCustomElement {
|
|
49
49
|
* }
|
50
50
|
*/
|
51
51
|
translation(interpolatedValues) {
|
52
|
+
if (!this.#value) {
|
53
|
+
this.logger.warn("No value attribute for key '%s', so translation will be blank", this.#key)
|
54
|
+
}
|
52
55
|
return this.#value.replaceAll(/%\{([^}%]+)\}/g, (match,key) => {
|
53
56
|
if (interpolatedValues[key]) {
|
54
57
|
return interpolatedValues[key]
|
data/brut-js/src/Message.js
CHANGED
@@ -23,10 +23,16 @@ class Message extends BaseCustomElement {
|
|
23
23
|
"key",
|
24
24
|
]
|
25
25
|
|
26
|
+
/*
|
27
|
+
* Creates a new `<brut-message>` element with the given attributes.
|
28
|
+
*/
|
26
29
|
static createElement(document,attributes) {
|
27
30
|
const element = document.createElement(Message.tagName)
|
28
|
-
|
29
|
-
|
31
|
+
Object.entries(attributes).forEach(([name,value]) => {
|
32
|
+
if (value !== null && value !== undefined) {
|
33
|
+
element.setAttribute(name,value)
|
34
|
+
}
|
35
|
+
})
|
30
36
|
return element
|
31
37
|
}
|
32
38
|
|
@@ -49,7 +55,7 @@ class Message extends BaseCustomElement {
|
|
49
55
|
return
|
50
56
|
}
|
51
57
|
|
52
|
-
this.textContent = RichString.fromString(translation.translation()).capitalize().toString()
|
58
|
+
this.textContent = RichString.fromString(translation.translation(), {allowBlank: true }).capitalize().toString()
|
53
59
|
}
|
54
60
|
}
|
55
61
|
|
data/brut-js/src/RichString.js
CHANGED
@@ -13,10 +13,13 @@ class RichString {
|
|
13
13
|
*
|
14
14
|
* @param {null|undefined|String|RichString} possiblyDefinedStringOrRichString - if `null`, `undefined`, or otherwise falsey, this method returns `null`. If a String, returns a new `RichString` wrapping it. If a `RichString`, returns the `RichString` unchanged.
|
15
15
|
*/
|
16
|
-
static fromString(possiblyDefinedStringOrRichString) {
|
16
|
+
static fromString(possiblyDefinedStringOrRichString, {allowBlank=false} = {}) {
|
17
17
|
if (possiblyDefinedStringOrRichString instanceof RichString) {
|
18
18
|
return possiblyDefinedStringOrRichString
|
19
19
|
}
|
20
|
+
if (allowBlank && possiblyDefinedStringOrRichString === "") {
|
21
|
+
return new RichString("")
|
22
|
+
}
|
20
23
|
if (!possiblyDefinedStringOrRichString) {
|
21
24
|
return null
|
22
25
|
}
|
@@ -0,0 +1,102 @@
|
|
1
|
+
import { BaseCustomElement, RichString, Message } from "brut-js"
|
2
|
+
|
3
|
+
/**
|
4
|
+
* Support for a toast component, which is a momentary message shown to the user, for example
|
5
|
+
* if an aynchronous update has occured.
|
6
|
+
*
|
7
|
+
* To use this element, you should set up your CSS so that the element is hidden if there is no `key`
|
8
|
+
* attribute set. When the `key` attribute *is* set, the element should be shown. You can use CSS animations
|
9
|
+
* for this as needed, but the main thing to remember is that, without a `key` attribute, this element
|
10
|
+
* should not be visible.
|
11
|
+
*
|
12
|
+
* The `key` attribute is expected to be an i18n key that references a `<brut-i18n-message>` on
|
13
|
+
* the page, which contains the actual message to show the visitor. When the `key` attribute is
|
14
|
+
* set, this component will find an `<output>` inside itself, and replace the entire contents
|
15
|
+
* with a `<brut-message>` component, using the same `key`. This will cause the `<brut-message>`
|
16
|
+
* to look up the key and put that text into the element.
|
17
|
+
*
|
18
|
+
* In addition to this lookup, this element will set appropriate ARIA attributes on the
|
19
|
+
* created `<brut-message>` element.
|
20
|
+
*
|
21
|
+
* Further, if there is a `<button>` inside this element, it will be used to close the toast by removing the
|
22
|
+
* `key` attribute (which, assuming your CSS is correct, will hide the element).
|
23
|
+
*
|
24
|
+
* @property {string} key - an I18n key of the message to show in the toast. When you generate
|
25
|
+
* the toast's HTML on the server, do not set key. Then, when you need
|
26
|
+
* to display the toast, use JavaScript to set the key. This will
|
27
|
+
* trigger its behavior as described above.
|
28
|
+
*
|
29
|
+
* @example
|
30
|
+
* <style>
|
31
|
+
* brut-toast {
|
32
|
+
* display: none;
|
33
|
+
* }
|
34
|
+
* brut-toast[key] {
|
35
|
+
* display: block;
|
36
|
+
* }
|
37
|
+
* </style>
|
38
|
+
* <brut-i18n-translation key="toast.saved">Save successful</brut-i18n-translation>
|
39
|
+
* <brut-toast>
|
40
|
+
* <div>
|
41
|
+
* <output></output>
|
42
|
+
* <button>Close</button>
|
43
|
+
* </div>
|
44
|
+
* </brut-toast>
|
45
|
+
* <!-- now, if you set the key to "toast.saved", the HTML will be changed as follows: -->
|
46
|
+
* <brut-toast key="toast.saved">
|
47
|
+
* <div>
|
48
|
+
* <output>
|
49
|
+
* <brut-message key="toast.saved" role="status" aria-live="polite" aria-atomic="true">
|
50
|
+
* Save successful
|
51
|
+
* </brut-message>
|
52
|
+
* </output>
|
53
|
+
* <button>Close</button>
|
54
|
+
* </div>
|
55
|
+
* </brut-toast>
|
56
|
+
*/
|
57
|
+
class Toast extends BaseCustomElement {
|
58
|
+
static tagName = "brut-toast"
|
59
|
+
|
60
|
+
static observedAttributes = [
|
61
|
+
"show-warnings",
|
62
|
+
"key",
|
63
|
+
]
|
64
|
+
|
65
|
+
#key = null
|
66
|
+
#closeListener = (event) => {
|
67
|
+
event.preventDefault()
|
68
|
+
this.removeAttribute("key")
|
69
|
+
}
|
70
|
+
|
71
|
+
keyChangedCallback({newValue}) {
|
72
|
+
this.#key = RichString.fromString(newValue)
|
73
|
+
}
|
74
|
+
|
75
|
+
update() {
|
76
|
+
const closeButton = this.querySelector("button")
|
77
|
+
|
78
|
+
if (closeButton) {
|
79
|
+
closeButton.addEventListener("click", this.#closeListener)
|
80
|
+
}
|
81
|
+
|
82
|
+
if (!this.#key) {
|
83
|
+
return
|
84
|
+
}
|
85
|
+
const output = this.querySelector("output")
|
86
|
+
if (!output) {
|
87
|
+
this.logger.warn("No <output> element found, so toast will not be displayed")
|
88
|
+
return
|
89
|
+
}
|
90
|
+
const messageNode = Message.createElement(document,{
|
91
|
+
"key": this.#key,
|
92
|
+
"role": "status",
|
93
|
+
"aria-live": "polite",
|
94
|
+
"aria-atomic": "true"
|
95
|
+
})
|
96
|
+
output.replaceChildren(messageNode)
|
97
|
+
this.style.animation = "none"
|
98
|
+
this.offsetWidth // Trigger reflow to restart the animation
|
99
|
+
this.style.animation = ""
|
100
|
+
}
|
101
|
+
}
|
102
|
+
export default Toast
|
data/brut-js/src/index.js
CHANGED
@@ -12,6 +12,7 @@ import LocaleDetection from "./LocaleDetection"
|
|
12
12
|
import Message from "./Message"
|
13
13
|
import RichString from "./RichString"
|
14
14
|
import Tabs from "./Tabs"
|
15
|
+
import Toast from "./Toast"
|
15
16
|
import Tracing from "./Tracing"
|
16
17
|
|
17
18
|
/**
|
@@ -105,6 +106,7 @@ BrutCustomElements.addElementClasses(
|
|
105
106
|
AjaxSubmit,
|
106
107
|
ConstraintViolationMessage,
|
107
108
|
Tabs,
|
109
|
+
Toast,
|
108
110
|
LocaleDetection,
|
109
111
|
Autosubmit,
|
110
112
|
Tracing,
|
@@ -125,6 +127,7 @@ export {
|
|
125
127
|
Message,
|
126
128
|
RichString,
|
127
129
|
Tabs,
|
130
|
+
Toast,
|
128
131
|
Tracing
|
129
132
|
}
|
130
133
|
|
@@ -129,12 +129,13 @@ export default defineConfig({
|
|
129
129
|
text: "Recipes",
|
130
130
|
collapsed: true,
|
131
131
|
items: [
|
132
|
-
{ text: "Migration Basics", link: "/recipes/migrations" },
|
133
|
-
{ text: "Styling Form Errors", link: "/recipes/form-errors" },
|
134
|
-
{ text: "Authentication", link: "/recipes/authentication" },
|
135
132
|
{ text: "Alternate Layouts", link: "/recipes/alternate-layouts" },
|
133
|
+
{ text: "Authentication", link: "/recipes/authentication" },
|
136
134
|
{ text: "Custom Flash Class", link: "/recipes/custom-flash" },
|
137
135
|
{ text: "Indexed Form Elements", link: "/recipes/indexed-forms" },
|
136
|
+
{ text: "Managing Secrets in the Dev Environment", link: "/recipes/dev-env-secrets" },
|
137
|
+
{ text: "Migration Basics", link: "/recipes/migrations" },
|
138
|
+
{ text: "Styling Form Errors", link: "/recipes/form-errors" },
|
138
139
|
{ text: "Text Field Component", link: "/recipes/text-field-component" },
|
139
140
|
],
|
140
141
|
},
|
data/brutrb.com/brut-js.md
CHANGED
@@ -52,6 +52,7 @@ of what each one does.
|
|
52
52
|
| `<brut-locale-detection>` | Sends an Ajax request to the server with the browser's reported locale and timezone. See [space-time continuum](/space-time-continuum#getting-timezone-from-the-browser) for more details. |
|
53
53
|
| `<brut-message>` | Shows a message using an [i18n](/i18n) key to dynamically pull a localized message for client-side constraint violations. |
|
54
54
|
| `<brut-tabs>` | Uses ARIA roles related to a tab control and implements it client-side. |
|
55
|
+
| `<brut-toast>` | Support for managing i18-capable [toast notifications](https://en.wikipedia.org/wiki/Pop-up_notification) |
|
55
56
|
| `<brut-tracing>` | Sends observability data back to the server to unify a server-side request with client-side tracing.|
|
56
57
|
|
57
58
|
> [!NOTE]
|
data/brutrb.com/deployment.md
CHANGED
@@ -22,30 +22,45 @@ When creating your Brut app with `mkbrut`, the Heroku segment can be used to cre
|
|
22
22
|
|
23
23
|
How to deploy:
|
24
24
|
|
25
|
-
1.
|
25
|
+
1. Get an auth token from Heroku, which you can do from inside the container, and save it to
|
26
|
+
`bash_customizations.local`:
|
26
27
|
|
27
28
|
```
|
28
29
|
your-computer> dx/exec bash
|
29
30
|
devcontainer> heroku auth:login
|
30
31
|
# You will need to copy/paste the URL to log in
|
31
|
-
devcontainer> heroku container
|
32
|
+
devcontainer> heroku authorizations:create -d "container pushes" --expires-in 31536000
|
33
|
+
# Copy the token output by this command
|
34
|
+
devcontainer> echo "HEROKU_API_KEY=«TOKEN YOU COPIED»" >> dx/bash_customizations.local
|
32
35
|
```
|
36
|
+
2. Exit the devcontainer and stop `dx/start` (e.g. hit `Ctrl-C` wherever you ran it)
|
37
|
+
3. Rebuild and restart the devcontainer (this will set `HEROKU_API_KEY` for you)
|
33
38
|
|
34
|
-
|
39
|
+
```
|
40
|
+
your-computer> dx/build
|
41
|
+
your-computer> dx/start
|
42
|
+
# In another terminal window
|
43
|
+
your-computer> dx/exec bash
|
44
|
+
devcontainer> echo $HEROKU_API_KEY
|
45
|
+
# You should see the token
|
46
|
+
```
|
47
|
+
|
48
|
+
Setting this environment variable avoids having to constantly re-authenticate to Heroku.
|
49
|
+
|
50
|
+
4. Create your app using the container stack:
|
35
51
|
|
36
52
|
```
|
37
53
|
> heroku create --stack container -a «your heroku app name»
|
38
54
|
```
|
39
|
-
|
40
|
-
|
55
|
+
5. Ensure your app's source code is all checked in, there are no uncommitted or unadded files, and you have pushed to the `main` branch of your remote Git repository.
|
56
|
+
6. `bin/deploy`
|
41
57
|
|
42
58
|
This will generate a `Dockerfile` for each process (by default, `Dockerfile.web` and `Dockerfile.release`), build images, push those images to Heroku, and ask Heroku to release them.
|
43
59
|
|
44
60
|
Debugging Tips:
|
45
61
|
|
46
|
-
* Keep in mind it's hard to make general deployment tools. You are expected to understand your deployment and be capable of deploying an arbitrary Rack app manually. Brut's tooling automates what you need to know.
|
47
|
-
* `bin/deploy` runs the `deploy` subcommand, so `bin/deploy help deploy` can provide
|
48
|
-
some options for debugging issues:
|
62
|
+
* Keep in mind it's hard to make general deployment tools. You are expected to understand your deployment and be capable of deploying an arbitrary Rack app manually. Brut's tooling automates what you need to do based on what you already need to know.
|
63
|
+
* `bin/deploy` runs the `deploy` subcommand, so `bin/deploy help deploy` can provide some options for debugging issues:
|
49
64
|
|
50
65
|
```
|
51
66
|
devcontainer> bin/deploy help deploy
|
@@ -106,7 +121,6 @@ some options for debugging issues:
|
|
106
121
|
You'll need to have a better understanding of Docker to do this, however if you
|
107
122
|
are deploying with Docker, this is an understanding you hopefully already have.
|
108
123
|
|
109
|
-
|
110
124
|
### Other Mechanisms for Deployment
|
111
125
|
|
112
126
|
As a Rack app, other deployments should be possible. To make the app work, you'll need to make sure a few things are dealt with:
|
data/brutrb.com/jobs.md
CHANGED
@@ -1,14 +1,114 @@
|
|
1
1
|
# Background Jobs
|
2
2
|
|
3
|
-
Brut
|
4
|
-
|
3
|
+
Brut ships without any background job system, however it should work with any system you'd like to use. Brut
|
4
|
+
can install/configure Sidekiq for you, however you are expected to understand Sidekiq in order to use it.
|
5
5
|
|
6
|
-
|
6
|
+
## Setting up Sidekiq
|
7
7
|
|
8
|
-
|
8
|
+
Brut's code-generation system used for installing capabilities are called *segments*, and Brut provides a
|
9
|
+
Sidekiq segment you can use to get an initial working setup of Sidekiq in your Brut app.
|
9
10
|
|
10
|
-
|
11
|
-
> The way Sidekiq is configured with Brut is effective and reliable, but it is complex. It currently
|
12
|
-
> involves several moving parts to make it work properly. This will be an area for improvement.
|
11
|
+
### Adding the Segment
|
13
12
|
|
13
|
+
1. Ensure your project files are all committed. This is so you can easily see (and, if needed, undo) the
|
14
|
+
changes `mkbrut` will make.
|
15
|
+
2. Use `mkbrut` to add the segment:
|
14
16
|
|
17
|
+
```
|
18
|
+
docker run \
|
19
|
+
--pull always \
|
20
|
+
-v "$PWD":"$PWD" \
|
21
|
+
-w "$PWD" \
|
22
|
+
-u $(id -u):$(id -g) \
|
23
|
+
-it \
|
24
|
+
thirdtank/mkbrut \
|
25
|
+
mkbrut add-segment -r /path/to/your/project sidekiq
|
26
|
+
```
|
27
|
+
3. This will modify and create various files in your project. Check them out if you like:
|
28
|
+
|
29
|
+
```
|
30
|
+
> git status
|
31
|
+
```
|
32
|
+
4. Exit your dev environment (i.e. hit `Ctrl-C` wherever you ran `dx/start`).
|
33
|
+
5. Rebuild and restart your dev environment. This may take a moment, since Valkey will be downloaded.
|
34
|
+
|
35
|
+
```
|
36
|
+
your-computer> dx/build
|
37
|
+
your-computer> dx/start
|
38
|
+
```
|
39
|
+
6. In another Terminal, connect to your dev container and run `bin/setup`
|
40
|
+
|
41
|
+
```
|
42
|
+
your-computer> dx/exec bash
|
43
|
+
devcontainer> bin/setup
|
44
|
+
```
|
45
|
+
7. The segment provides an integration test that will use the actual Sidekiq server and client, running
|
46
|
+
against the actual Valkey database that was installed:
|
47
|
+
|
48
|
+
```
|
49
|
+
devcontainer> bin/test e2e specs/integration/sidekiq_works.spec.rb
|
50
|
+
```
|
51
|
+
|
52
|
+
If this test passes, you are ready to go.
|
53
|
+
|
54
|
+
### Using Sidekiq in Brut
|
55
|
+
|
56
|
+
Jobs live in `app/src/back_end/jobs`, however this is just a convention and is not enforced - you can place a
|
57
|
+
job anywhere that Zeitwerk will find the class. Brut also provides basic configuration and a base job.
|
58
|
+
|
59
|
+
| File | Purpose|
|
60
|
+
|------|--------|
|
61
|
+
| `app/config/sidekiq.yml` | Standard configuration for Sidekiq |
|
62
|
+
| `app/src/back-end/jobs/app_job.r` | Base class for your jobs that includes `Sidekiq::Job` |
|
63
|
+
| `app/src/back-end/segments/sidekiq_segment.rb` | Initial client and server configuration for Sidekiq (that you can't do with `sidekiq.yml`. This sets up basic observability for your jobs |
|
64
|
+
|
65
|
+
### Accessing the Web UI
|
66
|
+
|
67
|
+
The Sidekiq segment mounts the Sidekiq Web UI to your app inside `config.ru`:
|
68
|
+
|
69
|
+
```ruby
|
70
|
+
# ...
|
71
|
+
map "/sidekiq" do
|
72
|
+
use Rack::Auth::Basic, "Sidekiq" do |username, password|
|
73
|
+
[username, password] == [ENV.fetch("SIDEKIQ_BASIC_AUTH_USER"), ENV.fetch("SIDEKIQ_BASIC_AUTH_PASSWORD")]
|
74
|
+
end
|
75
|
+
run Sidekiq::Web.new
|
76
|
+
end
|
77
|
+
# ...
|
78
|
+
```
|
79
|
+
|
80
|
+
Values for `SIDEKIQ_BASIC_AUTH_USER` and `SIDEKIQ_BASIC_AUTH_PASSWORD` for dev and test are placed into
|
81
|
+
`.env.development` and `.env.test`, respectively. You must provide these values for production, based on
|
82
|
+
however you are managing environment variables.
|
83
|
+
|
84
|
+
Once you start the app, navigat to `http://localhost:6502/sidekiq` and enter the username/password from
|
85
|
+
`.env.development`. You should see the web UI.
|
86
|
+
|
87
|
+
### Deploying with The Heroku Segment
|
88
|
+
|
89
|
+
If you have set up [Heroku Container-based Deployment](/deployment.md#heroku-container-based-deployment), you
|
90
|
+
may need to modify `deploy/heroku_config.rb`. The Sidekiq segement should have edited this, however if you
|
91
|
+
installed the Heroku segment after setting up Sidekiq, you'll need to add to the file:
|
92
|
+
|
93
|
+
```ruby [2-6]
|
94
|
+
class HerokuConfig
|
95
|
+
def self.additional_images
|
96
|
+
{
|
97
|
+
"sidekiq" => {
|
98
|
+
cmd: "bin/run-sidekiq",
|
99
|
+
}
|
100
|
+
}
|
101
|
+
end
|
102
|
+
end
|
103
|
+
```
|
104
|
+
|
105
|
+
## Setting Up Other Job Systems
|
106
|
+
|
107
|
+
To use another job system, you'll likely want to start with `app/src/app.rb`. You can place all your
|
108
|
+
initialize code in `#boot!` to get things working, then factor it out from there. `App`, the class in that
|
109
|
+
file, is a normal class, so you can extract your setup to other normal classes and bring them in as you would
|
110
|
+
in any other Ruby app.
|
111
|
+
|
112
|
+
Just note that `App`'s `initialize` method should avoid making network connections, so while you are safe to
|
113
|
+
create objects and configuration here, do not connect to databases or anything like that. You *can* do that
|
114
|
+
inside `boot!`.
|
@@ -0,0 +1,87 @@
|
|
1
|
+
# Managing Secrets in the Dev Environment
|
2
|
+
|
3
|
+
Often, you need API keys like GitHub or Heroku tokens in order to perform development tasks. These should not be checked into version
|
4
|
+
control, however you can still manage them.
|
5
|
+
|
6
|
+
## Feature - API Keys
|
7
|
+
|
8
|
+
* Developers need do use the Heroku command-line app inside the dev container.
|
9
|
+
* Develoeprs do not want to have to perform a daily, browser-based authentication via `heroku auth:login`
|
10
|
+
|
11
|
+
### Recipe
|
12
|
+
|
13
|
+
The file `dx/bash_customizations.local` is set up for exactly this. It is not checked into version control (see your `.gitignore`), and it
|
14
|
+
is included when the development environment is built.
|
15
|
+
|
16
|
+
```bash
|
17
|
+
# dx/bash_customizations.local
|
18
|
+
HEROKU_API_KEY=xxxxxx
|
19
|
+
```
|
20
|
+
|
21
|
+
When you change this file, you must rebuild your dev environment:
|
22
|
+
|
23
|
+
1. `Ctrl-C` wherever you ran `dx/start`
|
24
|
+
2. `dx/build`
|
25
|
+
3. `dx/start`
|
26
|
+
4. `dx/exec bash`, then `bin/setup`, then continue where you left off
|
27
|
+
|
28
|
+
#### How This Works
|
29
|
+
|
30
|
+
Here is a snippet of how this works. In the first `RUN` directlive, the non-root user is created. When that is completed, `~/.profile` and
|
31
|
+
`~/.bashrc` are modified to source both `bash_customizations` (per-project customizations that should **not** contain secrets) and
|
32
|
+
`bash_customizations.local`, which is the file we are discussing.
|
33
|
+
|
34
|
+
After that, the files are copied into the image via the `COPY` directives.
|
35
|
+
|
36
|
+
```dockerfile
|
37
|
+
# Snippet from Dockerfile.dx
|
38
|
+
RUN useradd --uid ${user_uid} --gid ${user_gid} --groups ${sadly_user_must_be_added_to_root}${docker_gid} --create-home --home-dir /home/appuser appuser && \
|
39
|
+
echo ". ~/.bash_customizations" >> /home/appuser/.profile && \
|
40
|
+
echo ". ~/.bash_customizations.local" >> /home/appuser/.profile && \
|
41
|
+
echo ". ~/.bash_customizations" >> /home/appuser/.bashrc && \
|
42
|
+
echo ". ~/.bash_customizations.local" >> /home/appuser/.bashrc
|
43
|
+
|
44
|
+
COPY --chown=appuser:${user_gid} dx/show-help-in-app-container-then-wait.sh /home/appuser
|
45
|
+
COPY --chown=appuser:${user_gid} dx/bash_customizations /home/appuser/.bash_customizations
|
46
|
+
COPY --chown=appuser:${user_gid} dx/bash_customizations.local /home/appuser/.bash_customizations.local
|
47
|
+
```
|
48
|
+
|
49
|
+
> [!WARNING]
|
50
|
+
> The resulting image **will** contain the secrets from `bash_customizations.local`, so it's
|
51
|
+
> **very important** you never push that image to a regsitry.
|
52
|
+
|
53
|
+
## Feature - SSH Keys
|
54
|
+
|
55
|
+
* You need an SSH key in order to push to GitHub from the dev container
|
56
|
+
* You do not want to creata new key every time
|
57
|
+
|
58
|
+
### Recipe
|
59
|
+
|
60
|
+
Ultimately, you want the SSH key to be copied into the container and set up as if you'd created the key there. The recipe below is an
|
61
|
+
example of how you could do this, and should demonstrate the various seams in Brut's dev environment to allow you to craft it how you like.
|
62
|
+
|
63
|
+
1. Choose a directory in the project where each developer will store their keys. **This directory should be excluded from version control**
|
64
|
+
|
65
|
+
```
|
66
|
+
mkdir dx/credentials
|
67
|
+
echo "/dx/credetials" >> .gitignore
|
68
|
+
```
|
69
|
+
|
70
|
+
2. Assuming you create an SSH key already, place `id_ed25519` (private key) and `id_ed25519.pub` (public key) into `dx/credentials`.
|
71
|
+
3. Create `dx/credentials/known_hosts` using `id_ed25519.pub`:
|
72
|
+
|
73
|
+
```
|
74
|
+
github.com ssh-ed25519 «key from id_ed25519.pub here»
|
75
|
+
```
|
76
|
+
4. Your dev container will have access to `dx/credentials` already, so you can use `bin/setup` to copy them to the right place. How
|
77
|
+
you do this depends on how complicated you want to get. You can examine Brut's `bin/setup` to see how it manages it. You will
|
78
|
+
see that ti uses `ssh-agent` to avoid requiring the passcode every time, and that it uses `chmod` to make sure the SSH
|
79
|
+
directories are the right permissions.
|
80
|
+
|
81
|
+
> [!WARNING]
|
82
|
+
> The resulting image **will** contain your SSH key, so it's
|
83
|
+
> **very important** you never push that image to a regsitry.
|
84
|
+
|
85
|
+
|
86
|
+
This recipe is scant on details, since each credential is highly specific. The key points to know are that you can store information in the
|
87
|
+
project, but not checked in, then rely on that information being available to `bin/setup` inside the container.
|