hecks 3.9.1 → 3.11.0
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/examples/pizzas/bluebook/pizzas.bluebook +29 -0
- data/examples/pizzas/bluebook/pizzas.hecksagon +7 -0
- data/exe/hecks +4 -4
- data/lib/hecks/adapters/driven/d1.rb +2 -10
- data/lib/hecks/adapters/driven/heki.adapter +1 -1
- data/lib/hecks/adapters/driven/heki.rb +2 -12
- data/lib/hecks/adapters/driven/lambda.adapter +1 -1
- data/lib/hecks/adapters/driven/lambda.rb +3 -18
- data/lib/hecks/adapters/driven/postgres.rb +4 -11
- data/lib/hecks/adapters/driven/sqlite.adapter +1 -1
- data/lib/hecks/adapters/driven/sqlite.rb +2 -10
- data/lib/hecks/adapters/driving/admission.rb +46 -0
- data/lib/hecks/adapters/driving/cli/nesting.rb +36 -0
- data/lib/hecks/adapters/driving/cli/shorthand.rb +88 -0
- data/lib/hecks/adapters/driving/cli.rb +127 -0
- data/lib/hecks/adapters/driving/cli_runner/answers.rb +160 -0
- data/lib/hecks/adapters/driving/cli_runner/resolution.rb +155 -0
- data/lib/hecks/adapters/driving/cli_runner/settling.rb +120 -0
- data/lib/hecks/adapters/driving/cli_runner/streaming.rb +93 -0
- data/lib/hecks/adapters/driving/cli_runner/suggestions.rb +47 -0
- data/lib/hecks/adapters/driving/cli_runner.rb +69 -0
- data/lib/hecks/adapters/driving/command_request.rb +121 -0
- data/lib/hecks/adapters/driving/handle.rb +214 -0
- data/lib/hecks/adapters/driving/json.rb +136 -0
- data/lib/hecks/adapters/driving/launcher_options.rb +267 -0
- data/lib/hecks/adapters/driving/mcp/properties.rb +49 -0
- data/lib/hecks/adapters/driving/mcp/tool_calls.rb +116 -0
- data/lib/hecks/adapters/driving/mcp/tool_definitions/auditing.rb +53 -0
- data/lib/hecks/adapters/driving/mcp/tool_definitions/calling.rb +71 -0
- data/lib/hecks/adapters/driving/mcp/tool_definitions/reading.rb +99 -0
- data/lib/hecks/adapters/driving/mcp/tool_definitions.rb +18 -0
- data/lib/hecks/adapters/driving/mcp.rb +159 -0
- data/lib/hecks/adapters/driving/mcp_guide_cache.rb +78 -0
- data/lib/hecks/adapters/driving/mcp_scope/argument_policy.rb +97 -0
- data/lib/hecks/adapters/driving/mcp_scope/env_problems.rb +69 -0
- data/lib/hecks/adapters/driving/mcp_scope/presenter.rb +83 -0
- data/lib/hecks/adapters/driving/mcp_scope.rb +213 -0
- data/lib/hecks/adapters/driving/ruby/aggregate_module.rb +171 -0
- data/lib/hecks/adapters/driving/ruby/chapter.rb +91 -0
- data/lib/hecks/adapters/driving/ruby.rb +83 -0
- data/lib/hecks/adapters/driving/usage_cache.rb +144 -0
- data/lib/hecks/{doors.rb → adapters/driving.rb} +10 -6
- data/lib/hecks/behaviors/expectations/boot.rb +1 -1
- data/lib/hecks/bench/ruby_runner.rb +1 -1
- data/lib/hecks/bluebook/ask_resolution.rb +130 -0
- data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +8 -2
- data/lib/hecks/bluebook/assembly/contracts/rules.rb +4 -2
- data/lib/hecks/bluebook/assembly/contracts/structure.rb +3 -1
- data/lib/hecks/bluebook/assembly/marks.rb +1 -0
- data/lib/hecks/bluebook/attribute.rb +1 -1
- data/lib/hecks/bluebook/behaviour/command.rb +13 -8
- data/lib/hecks/bluebook/behaviour/policy.rb +30 -0
- data/lib/hecks/bluebook/capabilities/attribute_values.rb +72 -0
- data/lib/hecks/bluebook/capabilities.rb +91 -9
- data/lib/hecks/bluebook/domain_port.rb +16 -4
- data/lib/hecks/bluebook/dsl/adapter_builder.rb +18 -6
- data/lib/hecks/bluebook/dsl/binding_proxy.rb +13 -0
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation/computed_sources.rb +98 -0
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation/fan_out_rows.rb +42 -0
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation/projected_fields.rb +14 -2
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation/provisions.rb +69 -3
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation/with_projections.rb +17 -10
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +5 -0
- data/lib/hecks/bluebook/dsl/bootstrap_table.rb +2 -0
- data/lib/hecks/bluebook/dsl/command_builder/mutations.rb +8 -1
- data/lib/hecks/bluebook/dsl/command_builder/operands.rb +121 -0
- data/lib/hecks/bluebook/dsl/command_builder.rb +2 -0
- data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +10 -1
- data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +45 -2
- data/lib/hecks/bluebook/dsl/policy_builder.rb +47 -10
- data/lib/hecks/bluebook/dsl/rule_reference.rb +7 -4
- data/lib/hecks/bluebook/dsl/world_builder.rb +1 -1
- data/lib/hecks/bluebook/expression/ast_json/emitters.rb +6 -0
- data/lib/hecks/bluebook/expression/ast_json.rb +9 -6
- data/lib/hecks/bluebook/expression/ast_reader.rb +14 -9
- data/lib/hecks/bluebook/expression/projection.json +36 -0
- data/lib/hecks/bluebook/expression/resolver/operations.rb +31 -0
- data/lib/hecks/bluebook/expression/resolver/parsing.rb +38 -5
- data/lib/hecks/bluebook/expression/resolver/scanning.rb +22 -5
- data/lib/hecks/bluebook/expression/resolver/values.rb +10 -0
- data/lib/hecks/bluebook/expression/resolver.rb +20 -1
- data/lib/hecks/bluebook/hecksagon.rb +23 -3
- data/lib/hecks/bluebook/lifecycle.rb +22 -3
- data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +13 -1
- data/lib/hecks/bluebook/meta_validator/modes.rb +4 -1
- data/lib/hecks/bluebook/meta_validator/readings.rb +18 -1
- data/lib/hecks/bluebook/meta_validator/reconstruction/heads.rb +2 -1
- data/lib/hecks/bluebook/meta_validator/shapes.rb +5 -0
- data/lib/hecks/bluebook/meta_validator/verdict_cache.rb +1 -1
- data/lib/hecks/bluebook/meta_validator.rb +12 -12
- data/lib/hecks/bluebook/model_check/ask_checks.rb +71 -0
- data/lib/hecks/bluebook/model_check/policy_checks.rb +2 -1
- data/lib/hecks/bluebook/model_check/reference_checks.rb +70 -0
- data/lib/hecks/bluebook/model_check.rb +8 -2
- data/lib/hecks/bluebook/policy.rb +4 -2
- data/lib/hecks/bluebook/smoke_test.rb +1 -1
- data/lib/hecks/bluebook/synthesizer.rb +2 -0
- data/lib/hecks/bluebook.rb +1 -0
- data/lib/hecks/cli/command_table.rb +1 -1
- data/lib/hecks/cli/console.rb +2 -1
- data/lib/hecks/cli/document.rb +1 -1
- data/lib/hecks/cli/launcher_words.rb +4 -4
- data/lib/hecks/cli/mcp.rb +4 -4
- data/lib/hecks/cli/project_cli/launcher_source.rb +2 -2
- data/lib/hecks/cli/project_cli/templates.rb +5 -5
- data/lib/hecks/cli/project_cli.rb +9 -6
- data/lib/hecks/cli/run.rb +2 -2
- data/lib/hecks/cli/smoke_http/checks.rb +4 -1
- data/lib/hecks/cli/smoke_http/transport.rb +3 -3
- data/lib/hecks/cli/smoke_http.rb +3 -0
- data/lib/hecks/codemod.rb +5 -2
- data/lib/hecks/corpus/routes.rb +7 -0
- data/lib/hecks/corpus/rust.rb +3 -2
- data/lib/hecks/deploy/bluebook/deploy.bluebook +124 -2
- data/lib/hecks/deploy/bluebook/deploy.hecksagon +8 -0
- data/lib/hecks/deploy/oidc.json +16 -3
- data/lib/hecks/doc/reference/rendering.rb +6 -3
- data/lib/hecks/forms/app/commands.rb +8 -7
- data/lib/hecks/forms/app/queries.rb +2 -1
- data/lib/hecks/forms/app.rb +1 -1
- data/lib/hecks/forms/record_renderer.rb +1 -1
- data/lib/hecks/framework/bluebook/privacy.bluebook +11 -0
- data/lib/hecks/fuzzing/concurrent_dispatch.rb +4 -1
- data/lib/hecks/fuzzing/failure_triage.rb +2 -1
- data/lib/hecks/fuzzing/mutation/run.rb +11 -4
- data/lib/hecks/fuzzing/properties/catalog.rb +6 -4
- data/lib/hecks/fuzzing/properties/guaranteed_by_construction.rb +18 -11
- data/lib/hecks/fuzzing/properties/guards.rb +1 -1
- data/lib/hecks/fuzzing/self_consistency/saga_redelivery.rb +1 -1
- data/lib/hecks/fuzzing/self_consistency.rb +6 -2
- data/lib/hecks/fuzzing/shrinker.rb +3 -1
- data/lib/hecks/fuzzing/value_generator.rb +19 -5
- data/lib/hecks/gate/stages.yml +4 -10
- data/lib/hecks/grammar/evolve.rb +1 -1
- data/lib/hecks/grammar/expression_operators.json +372 -0
- data/lib/hecks/grammar/operators.rb +3 -1
- data/lib/hecks/hecks/adapters/codebase/corpus_tasks.rb +10 -10
- data/lib/hecks/hecks/adapters/codebase/inspection/rubycritic.rb +35 -0
- data/lib/hecks/hecks/adapters/codebase/inspection.rb +104 -0
- data/lib/hecks/hecks/adapters/codebase/publishing.rb +2 -1
- data/lib/hecks/hecks/adapters/codebase/release_facts.rb +12 -2
- data/lib/hecks/hecks/adapters/codebase/source_tree.rb +2 -1
- data/lib/hecks/hecks/adapters/codebase/test_runner.rb +1 -1
- data/lib/hecks/hecks/adapters/cost_explorer.rb +8 -5
- data/lib/hecks/hecks/adapters/deploy_toolchain/clearing.rb +51 -0
- data/lib/hecks/hecks/adapters/deploy_toolchain/statuses.rb +2 -1
- data/lib/hecks/hecks/adapters/deploy_toolchain.rb +2 -0
- data/lib/hecks/hecks/adapters/finding_github.rb +13 -6
- data/lib/hecks/hecks/adapters/first_admin.rb +2 -2
- data/lib/hecks/hecks/adapters/github_rulesets.rb +6 -3
- data/lib/hecks/hecks/adapters/in_process_boot.rb +1 -1
- data/lib/hecks/hecks/adapters/journal_store/compaction.rb +1 -1
- data/lib/hecks/hecks/adapters/journal_store/edge_audit.rb +10 -20
- data/lib/hecks/hecks/adapters/journal_store/held_domain.rb +28 -5
- data/lib/hecks/hecks/adapters/local_files.rb +2 -2
- data/lib/hecks/hecks/adapters/rehearsal/audit_report.rb +107 -0
- data/lib/hecks/hecks/adapters/rehearsal/census.rb +71 -0
- data/lib/hecks/hecks/adapters/rehearsal/declaration.rb +133 -0
- data/lib/hecks/hecks/adapters/rehearsal/environment.rb +83 -0
- data/lib/hecks/hecks/adapters/rehearsal/inquiry.rb +142 -0
- data/lib/hecks/hecks/adapters/rehearsal/memory_store.rb +96 -0
- data/lib/hecks/hecks/adapters/rehearsal/postgres_census.rb +100 -0
- data/lib/hecks/hecks/adapters/rehearsal/postgres_reading.rb +69 -0
- data/lib/hecks/hecks/adapters/rehearsal/postgres_store.rb +129 -0
- data/lib/hecks/hecks/adapters/rehearsal/rows.rb +41 -0
- data/lib/hecks/hecks/adapters/rehearsal/rust_audit.rb +83 -0
- data/lib/hecks/hecks/adapters/rehearsal/subject.rb +104 -0
- data/lib/hecks/hecks/adapters/rehearsal.rb +54 -0
- data/lib/hecks/hecks/adapters/rehearsal_store.adapter +7 -0
- data/lib/hecks/hecks/adapters/rehearsal_store.rb +81 -0
- data/lib/hecks/hecks/adapters/site_toolchain/role_prober.rb +78 -0
- data/lib/hecks/hecks/adapters/site_toolchain.rb +29 -4
- data/lib/hecks/hecks/adapters/terminal.rb +13 -11
- data/lib/hecks/hecks/adapters/world_clearing.rb +65 -0
- data/lib/hecks/hecks/codebase.behaviors +13 -1
- data/lib/hecks/hecks/codebase.bluebook +239 -5
- data/lib/hecks/hecks/custodian.behaviors +24 -6
- data/lib/hecks/hecks/custodian.bluebook +6 -1227
- data/lib/hecks/hecks/deploy.behaviors +1 -1
- data/lib/hecks/hecks/deploy.world +1 -3
- data/lib/hecks/hecks/hecks.behaviors +1 -1
- data/lib/hecks/hecks/hecks.hecksagon +61 -18
- data/lib/hecks/hecks/hecks.world +11 -10
- data/lib/hecks/hecks/rehearsal.bluebook +477 -0
- data/lib/hecks/hecks/site.behaviors +1 -1
- data/lib/hecks/hecks/site.world +0 -1
- data/lib/hecks/hecks/tooling.bluebook +1238 -0
- data/lib/hecks/indifferent_key.rb +20 -0
- data/lib/hecks/language/adapter.bluebook +13 -3
- data/lib/hecks/language/bluebook/aggregate.bluebook +23 -0
- data/lib/hecks/language/bluebook/bluebook.bluebook +15 -0
- data/lib/hecks/language/bluebook/entity.bluebook +18 -0
- data/lib/hecks/language/bluebook/policy.bluebook +9 -1
- data/lib/hecks/language/bluebook/query.bluebook +1 -1
- data/lib/hecks/language/bluebook/vocabulary.bluebook +11 -4
- data/lib/hecks/language/hecksagon/domain_port.bluebook +1 -1
- data/lib/hecks/language/hecksagon/hecksagon.bluebook +22 -0
- data/lib/hecks/language/oidc.json +10 -0
- data/lib/hecks/language/port.bluebook +4 -4
- data/lib/hecks/literal.rb +12 -3
- data/lib/hecks/ports/key_vault.rb +7 -2
- data/lib/hecks/ports/persistence/plugins/era/era_check.rb +21 -10
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/boot.rb +4 -2
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/era_resolver.rb +2 -1
- data/lib/hecks/ports/persistence/repository_factory.rb +6 -3
- data/lib/hecks/projections/deploy/box/templates/deploy-box.sh.tmpl +29 -0
- data/lib/hecks/projections/deploy/box/templates/deploy-service.sh.tmpl +1 -1
- data/lib/hecks/projections/deploy/fargate/settings/tuning.rb +15 -0
- data/lib/hecks/projections/deploy/fargate/settings.rb +3 -1
- data/lib/hecks/projections/deploy/fargate/stack/readers.rb +1 -0
- data/lib/hecks/projections/deploy/preview/preview.sh.erb +1 -1
- data/lib/hecks/projections/deploy/shared.rb +5 -2
- data/lib/hecks/projections/deploy/templates/fargate/Makefile.tmpl +7 -4
- data/lib/hecks/projections/model/deviations.rb +2 -2
- data/lib/hecks/projections/model.rb +2 -2
- data/lib/hecks/projections/oidc.rb +13 -1
- data/lib/hecks/projections/shape.rb +5 -2
- data/lib/hecks/projections/site/cms_editor/attributes.rb +95 -0
- data/lib/hecks/projections/site/cms_editor/blocks.rb +157 -0
- data/lib/hecks/projections/site/cms_editor/checks.rb +77 -0
- data/lib/hecks/projections/site/cms_editor/clearing.rb +70 -0
- data/lib/hecks/projections/site/cms_editor/color.rb +124 -0
- data/lib/hecks/projections/site/cms_editor/composition.rb +42 -0
- data/lib/hecks/projections/site/cms_editor/destructive.rb +61 -0
- data/lib/hecks/projections/site/cms_editor/drafts.rb +102 -0
- data/lib/hecks/projections/site/cms_editor/hints.rb +74 -0
- data/lib/hecks/projections/site/cms_editor/icons.rb +86 -0
- data/lib/hecks/projections/site/cms_editor/key_kinds.rb +31 -0
- data/lib/hecks/projections/site/cms_editor/limits.rb +72 -0
- data/lib/hecks/projections/site/cms_editor/media.rb +85 -0
- data/lib/hecks/projections/site/cms_editor/moment.rb +34 -0
- data/lib/hecks/projections/site/cms_editor/palette.rb +115 -0
- data/lib/hecks/projections/site/cms_editor/pickers.rb +85 -0
- data/lib/hecks/projections/site/cms_editor/pretty.rb +40 -0
- data/lib/hecks/projections/site/cms_editor/preview.rb +91 -0
- data/lib/hecks/projections/site/cms_editor/relations.rb +92 -0
- data/lib/hecks/projections/site/cms_editor/scale_checks.rb +66 -0
- data/lib/hecks/projections/site/cms_editor/scheduling.rb +150 -0
- data/lib/hecks/projections/site/cms_editor/schema.rb +161 -0
- data/lib/hecks/projections/site/cms_editor/setting.rb +130 -0
- data/lib/hecks/projections/site/cms_editor/targets.rb +86 -0
- data/lib/hecks/projections/site/cms_editor/templates/package.json.tmpl +36 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/app.ts.tmpl +370 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/assets.ts.tmpl +98 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/auth/membership.ts.tmpl +87 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/auth/session.ts.tmpl +36 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/auth/sso.ts.tmpl +20 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/app.css.tmpl +412 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/autosave.js.tmpl +144 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/blocks.js.tmpl +500 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/body_doc.js.tmpl +175 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/body_editor.js.tmpl +418 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/epoch.js.tmpl +61 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/forms.js.tmpl +210 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/lists.js.tmpl +40 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/main.js.tmpl +26 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/media_picker.js.tmpl +232 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/moments.js.tmpl +73 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/pickers.js.tmpl +152 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/preview.js.tmpl +98 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/shell.js.tmpl +140 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/browser/toast.js.tmpl +66 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/choices.ts.tmpl +99 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/commands.ts.tmpl +133 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/config.ts.tmpl +29 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/flash.ts.tmpl +52 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/host.ts.tmpl +43 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/media/handler.ts.tmpl +177 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/media/multipart.ts.tmpl +53 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/media/sniff.ts.tmpl +94 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/media/storage.ts.tmpl +50 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/names.ts.tmpl +36 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/schema.ts.tmpl +214 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/server.ts.tmpl +65 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/availability.ts.tmpl +15 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/block_list.js.tmpl +213 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/blocks.ts.tmpl +275 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/body_model.js.tmpl +123 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/body_parse.js.tmpl +320 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/detail.ts.tmpl +88 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/dialogs.ts.tmpl +43 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/fields.ts.tmpl +275 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/html.ts.tmpl +61 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/icons.ts.tmpl +15 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/input.ts.tmpl +92 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/links.ts.tmpl +100 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/moments.ts.tmpl +44 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/outline.ts.tmpl +63 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/pages.ts.tmpl +213 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/paging.ts.tmpl +82 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/pickers.ts.tmpl +110 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/preview.ts.tmpl +87 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/related.ts.tmpl +97 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/schedule.ts.tmpl +160 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/shell.ts.tmpl +211 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/table.ts.tmpl +151 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/theme.js.tmpl +16 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/values.ts.tmpl +69 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/words.ts.tmpl +97 -0
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/world.ts.tmpl +35 -0
- data/lib/hecks/projections/site/cms_editor/templates/tsconfig.json.tmpl +17 -0
- data/lib/hecks/projections/site/cms_editor/theme.rb +29 -0
- data/lib/hecks/projections/site/cms_editor/theme_checks.rb +57 -0
- data/lib/hecks/projections/site/cms_editor.rb +133 -0
- data/lib/hecks/projections/site/role_probe.rb +74 -0
- data/lib/hecks/projector/cli_audience.rb +2 -1
- data/lib/hecks/projector/cli_projector/listing.rb +1 -1
- data/lib/hecks/projector/cli_projector/option_specs.rb +1 -1
- data/lib/hecks/projector/docs_projector/closing.rb +1 -1
- data/lib/hecks/projector/docs_projector.rb +1 -1
- data/lib/hecks/projector/exporter/bindings.rb +40 -10
- data/lib/hecks/projector/exporter/capability_values.rb +55 -0
- data/lib/hecks/projector/narrate_projector/reactions.rb +1 -1
- data/lib/hecks/projector/target.rb +11 -0
- data/lib/hecks/projector.rb +32 -5
- data/lib/hecks/quality_control/adapters/github_checks.rb +12 -8
- data/lib/hecks/quality_control/adapters/github_issues.rb +8 -4
- data/lib/hecks/quality_control/cli/qa_open_pr.rb +2 -2
- data/lib/hecks/quality_control/cli/qa_sweep/checks/shrinking.rb +1 -1
- data/lib/hecks/quality_control/cli/qa_sweep/finding_report.rb +1 -1
- data/lib/hecks/quality_control/cli/qa_sweep.rb +1 -1
- data/lib/hecks/quality_control/quality_control.bluebook +6 -2
- data/lib/hecks/release/approval.rb +64 -0
- data/lib/hecks/router/{door.rb → aggregate_reader.rb} +5 -4
- data/lib/hecks/router/namespace_installer.rb +16 -16
- data/lib/hecks/router.rb +1 -1
- data/lib/hecks/runtime/best_effort.rb +22 -0
- data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +12 -2
- data/lib/hecks/runtime/command_interpreter.rb +3 -2
- data/lib/hecks/runtime/command_rules/emission.rb +1 -1
- data/lib/hecks/runtime/dispatcher.rb +4 -4
- data/lib/hecks/runtime/entity_element/location.rb +1 -1
- data/lib/hecks/runtime/event.rb +5 -0
- data/lib/hecks/runtime/identity.rb +7 -6
- data/lib/hecks/runtime/interpreting.rb +2 -1
- data/lib/hecks/runtime/invocation_builder.rb +1 -1
- data/lib/hecks/runtime/loader/boot_steps.rb +8 -5
- data/lib/hecks/runtime/loader.rb +10 -10
- data/lib/hecks/runtime/policy_interpreter/arguments.rb +7 -4
- data/lib/hecks/runtime/policy_interpreter/fan_out.rb +3 -3
- data/lib/hecks/runtime/policy_interpreter.rb +6 -6
- data/lib/hecks/runtime/port_operation_interpreter.rb +1 -1
- data/lib/hecks/runtime/reaction_invocation.rb +1 -1
- data/lib/hecks/runtime/rebuild_sweep.rb +11 -1
- data/lib/hecks/runtime/registry/ask_verification.rb +35 -0
- data/lib/hecks/runtime/registry/hecksagon_rules.rb +10 -0
- data/lib/hecks/runtime/registry/merging.rb +1 -1
- data/lib/hecks/runtime/registry/providers.rb +16 -0
- data/lib/hecks/runtime/registry/read_repository.rb +6 -4
- data/lib/hecks/runtime/registry/verification.rb +4 -1
- data/lib/hecks/runtime/registry/world_defaults.rb +29 -0
- data/lib/hecks/runtime/registry.rb +1 -1
- data/lib/hecks/runtime/saga_interpreter/compensation.rb +1 -1
- data/lib/hecks/runtime/saga_interpreter/delivery.rb +4 -4
- data/lib/hecks/runtime/saga_interpreter/transitions.rb +1 -1
- data/lib/hecks/runtime/saga_interpreter.rb +3 -3
- data/lib/hecks/runtime/tenant_check.rb +5 -4
- data/lib/hecks/runtime/value/coercion.rb +2 -2
- data/lib/hecks/runtime/value/validation.rb +16 -4
- data/lib/hecks/runtime.rb +9 -9
- data/lib/hecks/rust_build/project_rust/target_ir.rb +1 -1
- data/lib/hecks/rust_build/wasm.rb +1 -1
- data/lib/hecks/rust_build/wasm_browser.rb +1 -1
- data/lib/hecks/rust_build.rb +11 -0
- data/lib/hecks/served_options.rb +53 -0
- data/lib/hecks/site/bluebook/site.bluebook +29 -1
- data/lib/hecks/site/bluebook/site.ports.hecksagon +6 -0
- data/lib/hecks/storehouse/audit.rb +1 -1
- data/lib/hecks/storehouse/dispatching.rb +8 -6
- data/lib/hecks/storehouse/introspection.rb +2 -2
- data/lib/hecks/storehouse/querying.rb +6 -4
- data/lib/hecks/storehouse.rb +4 -4
- data/lib/hecks/tools/comment_style/source_file.rb +4 -1
- data/lib/hecks/tools/deploy_recipe_lint/fixtures.rb +4 -1
- data/lib/hecks/tools/lanes/rows.rb +6 -0
- data/lib/hecks/tools/oidc_manifests.rb +4 -5
- data/lib/hecks/tools/regeneration_run.rb +11 -4
- data/lib/hecks/tools/site_routes/cli.rb +1 -1
- data/lib/hecks/tools/site_routes/editor_files.rb +45 -0
- data/lib/hecks/tools/site_routes.rb +14 -7
- data/lib/hecks/tools/tenant_provisioning.rb +2 -2
- data/lib/hecks/tools/tools_doc.rb +4 -4
- data/lib/hecks/version.rb +1 -1
- data/lib/hecks/vocabulary.rb +4 -4
- data/lib/hecks.rb +15 -15
- data/lib/rubocop/cop/hecks/env_dump.rb +69 -0
- data/lib/rubocop/cop/hecks/monotonic_duration.rb +53 -0
- data/lib/rubocop/cop/hecks/no_wall_clock_in_runtime.rb +34 -0
- data/lib/rubocop/cop/hecks/raw_shell_interpolation.rb +79 -0
- data/lib/rubocop/cop/hecks/sleep_in_spec.rb +34 -0
- data/lib/rubocop/cop/hecks/swallowed_rescue.rb +37 -0
- data/lib/rubocop/cop/hecks/symbol_string_key_mix.rb +50 -0
- data/lib/rubocop/cop/hecks/untyped_raise.rb +37 -0
- data/prebuilt/05c358d38d661a3774f813d49c66f5b68939af94432d66257a56f49ee2598994.marshal +0 -0
- data/prebuilt/26c40c61f09b09b11651c64668eda3ceb180f83cf2a3802d3033d2fd99ac841b.marshal +0 -0
- data/prebuilt/verdicts-230ac18c25abfd3d92ede032bee2661a4c2cd2773ab0cf0e2ae52546ba2f7af9.json +1 -0
- data/rust/.gitignore +6 -0
- data/rust/Cargo.toml +9 -1
- data/rust/build/src/binding_pass.rs +103 -0
- data/rust/build/src/cargo_sync.rs +6 -0
- data/rust/build/src/lineage_pass.rs +3 -3
- data/rust/build/src/main.rs +17 -5
- data/rust/build/src/pipeline.rs +48 -5
- data/rust/build/src/seams_pass.rs +123 -0
- data/rust/build/src/translation_pass.rs +167 -0
- data/rust/codegen/src/asks.rs +103 -0
- data/rust/codegen/src/bridging.rs +1 -1
- data/rust/codegen/src/commands.rs +13 -8
- data/rust/codegen/src/constraints.rs +25 -0
- data/rust/codegen/src/domain_generator.rs +11 -9
- data/rust/codegen/src/expr_emitter.rs +13 -0
- data/rust/codegen/src/json_codec.rs +54 -15
- data/rust/codegen/src/main.rs +2 -1
- data/rust/codegen/src/mutations.rs +174 -4
- data/rust/codegen/src/naming.rs +30 -0
- data/rust/codegen/src/queries.rs +11 -2
- data/rust/codegen/src/reactions.rs +1 -1
- data/rust/codegen/src/types.rs +166 -8
- data/rust/host/HECKS_RELEASE +1 -1
- data/rust/host/src/api.rs +4 -4
- data/rust/host/src/bin/validate_harness.rs +43 -0
- data/rust/host/src/boundary_fuzz/expr_json.rs +2 -2
- data/rust/host/src/boundary_fuzz/ir.rs +1 -0
- data/rust/host/src/boundary_fuzz/server.rs +1 -1
- data/rust/host/src/checkout.rs +35 -19
- data/rust/host/src/commerce_ir.rs +408 -2
- data/rust/host/src/expr_json.rs +118 -0
- data/rust/host/src/mint.rs +1 -1
- data/rust/host/src/payments/tests.rs +27 -1
- data/rust/host/src/payments.rs +12 -4
- data/rust/host/src/query_step.rs +22 -4
- data/rust/host/src/reference_validate.rs +653 -108
- data/rust/host/src/resend.rs +2 -0
- data/rust/host/src/server.rs +19 -6
- data/rust/host/src/ui_schema.rs +132 -5
- data/rust/host/src/web/newsletter.rs +7 -8
- data/rust/host/src/web/newsletter_send.rs +38 -13
- data/rust/host/src/web/registrations.rs +229 -26
- data/rust/host/src/web.rs +3 -3
- data/rust/parser/src/emit.rs +38 -6
- data/rust/parser/src/expr/ast_json.rs +7 -1
- data/rust/parser/src/expr/resolver.rs +171 -6
- data/rust/parser/src/ir.rs +7 -0
- data/rust/parser/src/keywords.rs +24 -0
- data/rust/parser/src/main.rs +3 -0
- data/rust/parser/src/parse/aggregate.rs +2 -1
- data/rust/parser/src/parse/chapter.rs +2 -2
- data/rust/parser/src/parse/command.rs +2 -0
- data/rust/parser/src/parse/hecksagon.rs +5 -2
- data/rust/parser/src/parse/lifecycle.rs +81 -0
- data/rust/parser/src/parse/mod.rs +15 -1
- data/rust/parser/src/parse/policy.rs +13 -2
- data/rust/parser/src/parse/value_object.rs +13 -8
- data/rust/parser/src/ruby_value.rs +1 -1
- data/rust/src/exemplar/commands.rs +4 -4
- data/rust/src/exemplar/constraints.rs +1 -1
- data/rust/src/exemplar/json.rs +43 -1
- data/rust/src/kernel/cli.rs +7 -5
- data/rust/src/kernel/dispatch.rs +2 -2
- data/rust/src/kernel/dispatch_guard_tests.rs +1 -1
- data/rust/src/kernel/expr.rs +73 -4
- data/rust/src/kernel/expression_operators/arithmetic.rs +130 -5
- data/rust/src/kernel/expression_operators/presence.rs +41 -3
- data/rust/src/kernel/expression_operators/text.rs +26 -2
- data/rust/src/kernel/json.rs +15 -0
- data/rust/src/kernel/mod.rs +1 -1
- data/rust/src/kernel/orchestrate.rs +54 -5
- data/rust/src/kernel/orchestrate_guard_tests.rs +67 -0
- data/rust/src/kernel/pattern.rs +60 -0
- data/rust/src/kernel/reference_lookup.rs +90 -7
- metadata +174 -36
- data/lib/hecks/doors/cli_door/nesting.rb +0 -34
- data/lib/hecks/doors/cli_door/shorthand.rb +0 -85
- data/lib/hecks/doors/cli_door.rb +0 -124
- data/lib/hecks/doors/cli_runner/answers.rb +0 -138
- data/lib/hecks/doors/cli_runner/resolution.rb +0 -146
- data/lib/hecks/doors/cli_runner/settling.rb +0 -115
- data/lib/hecks/doors/cli_runner/streaming.rb +0 -90
- data/lib/hecks/doors/cli_runner/suggestions.rb +0 -43
- data/lib/hecks/doors/cli_runner.rb +0 -66
- data/lib/hecks/doors/command_request.rb +0 -119
- data/lib/hecks/doors/handle.rb +0 -208
- data/lib/hecks/doors/json_door.rb +0 -132
- data/lib/hecks/doors/launcher_options.rb +0 -247
- data/lib/hecks/doors/mcp_door/properties.rb +0 -47
- data/lib/hecks/doors/mcp_door/tool_calls.rb +0 -113
- data/lib/hecks/doors/mcp_door/tool_definitions/auditing.rb +0 -51
- data/lib/hecks/doors/mcp_door/tool_definitions/calling.rb +0 -69
- data/lib/hecks/doors/mcp_door/tool_definitions/reading.rb +0 -97
- data/lib/hecks/doors/mcp_door/tool_definitions.rb +0 -15
- data/lib/hecks/doors/mcp_door.rb +0 -156
- data/lib/hecks/doors/mcp_door_scope/argument_policy.rb +0 -92
- data/lib/hecks/doors/mcp_door_scope/env_problems.rb +0 -55
- data/lib/hecks/doors/mcp_door_scope/presenter.rb +0 -78
- data/lib/hecks/doors/mcp_door_scope.rb +0 -201
- data/lib/hecks/doors/mcp_guide_cache.rb +0 -75
- data/lib/hecks/doors/ruby_door/aggregate_door.rb +0 -162
- data/lib/hecks/doors/ruby_door/chapter.rb +0 -89
- data/lib/hecks/doors/ruby_door.rb +0 -80
- data/lib/hecks/doors/usage_cache.rb +0 -139
- data/prebuilt/0ee7f83cba59ce7d1c45c65ba166b071779f70143449f584f33ff7989f64eccb.marshal +0 -0
- data/prebuilt/6e4a6bcf0aa880e2e6b056e1846b70e5a768090b9dc998bbadeaf5d6bcfd3db8.marshal +0 -0
- data/prebuilt/verdicts-91aaf93ce8981bd9a754948f075d95650dc2ec6435015376f5c60992b36a24b1.json +0 -1
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Hecks
|
|
4
|
+
module Projections
|
|
5
|
+
module Site
|
|
6
|
+
module CmsEditor
|
|
7
|
+
# An aggregate whose records are actions to run later, found by shape. The editor makes
|
|
8
|
+
# and changes the records and never runs an action: a runner elsewhere does.
|
|
9
|
+
#
|
|
10
|
+
# It has a **subject** (a plain `<kind>:<slug>` key, see `KeyKinds`), an **action** from
|
|
11
|
+
# a closed set (`one_of`), a **due time** that is a moment (see `Moment`), a
|
|
12
|
+
# **lifecycle** whose first state moves to at least two states that move nowhere, and a
|
|
13
|
+
# **creating command** that takes the three. The command that reschedules takes the due
|
|
14
|
+
# time; the one that cancels is a destructive move from the first state to a final one;
|
|
15
|
+
# the reason is the first optional plain text left over; the query takes the subject
|
|
16
|
+
# alone. The first such aggregate is the one.
|
|
17
|
+
module Scheduling
|
|
18
|
+
# What a scheduling aggregate's attributes are called in its record: `subject`,
|
|
19
|
+
# `action`, `due`.
|
|
20
|
+
ROLES = %w[subject action due].freeze
|
|
21
|
+
|
|
22
|
+
# The widgets of an integer that is a moment.
|
|
23
|
+
MOMENTS = %w[date datetime].freeze
|
|
24
|
+
|
|
25
|
+
module_function
|
|
26
|
+
|
|
27
|
+
# @param aggregates [Array<Hash{String => Object}>] the aggregates as `Pickers` leaves
|
|
28
|
+
# them
|
|
29
|
+
# @return [Hash{String => Object}, nil] the scheduling aggregate and its parts; nil when
|
|
30
|
+
# none
|
|
31
|
+
def read(aggregates)
|
|
32
|
+
aggregates.lazy.filter_map { |agg| shape(agg) }.first
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# @return [Hash{String => Object}, nil] `agg` as the scheduling aggregate, or nil
|
|
36
|
+
def shape(agg)
|
|
37
|
+
parts = parts_of(agg)
|
|
38
|
+
schedule = parts && endings(agg["lifecycle"]).size > 1 && creating(agg, parts)
|
|
39
|
+
return nil unless schedule
|
|
40
|
+
|
|
41
|
+
{ "aggregate" => agg["name"], "chapter" => agg["chapter"], **parts, "pending" => agg.dig("lifecycle", "default"),
|
|
42
|
+
"schedule" => schedule["name"], **optional(agg, parts, schedule) }.compact
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# @return [Hash{String => Object}, nil] the subject, action, actions and due time, or nil
|
|
46
|
+
def parts_of(agg)
|
|
47
|
+
return nil unless agg["lifecycle"]
|
|
48
|
+
|
|
49
|
+
plain = agg["attributes"].reject { |attr| attr["list"] }
|
|
50
|
+
found = [subject_of(agg, plain), plain.find { |attr| attr["options"] }, plain.find { |attr| moment?(attr) }]
|
|
51
|
+
describe(*found) if found.all?
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# @return [Boolean] whether the attribute is an integer that is a moment
|
|
55
|
+
def moment?(attr) = MOMENTS.include?(attr["widget"])
|
|
56
|
+
|
|
57
|
+
# @return [Hash{String => Object}] the three attributes' names, and the action's members
|
|
58
|
+
def describe(subject, action, due)
|
|
59
|
+
{ "subject" => subject["name"], "action" => action["name"], "actions" => action["options"], "due" => due["name"] }
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# @return [Hash{String => Object}, nil] the attribute that is a key of a kind, the
|
|
63
|
+
# identity last
|
|
64
|
+
def subject_of(agg, plain)
|
|
65
|
+
plain.select { |attr| attr.dig("picker", "kinds") }.min_by { |attr| attr["name"] == agg["identity"] ? 1 : 0 }
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# @return [Array<String>] the final states the first state moves to: states nothing
|
|
69
|
+
# moves out of
|
|
70
|
+
def endings(lifecycle)
|
|
71
|
+
moves = lifecycle["transitions"]
|
|
72
|
+
first = moves.select { |move| move["from"].include?(lifecycle["default"]) }.map { |move| move["to"] }.uniq
|
|
73
|
+
first - moves.flat_map { |move| move["from"] }
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# @return [Hash{String => Object}, nil] the creating command that takes the subject,
|
|
77
|
+
# action and due time
|
|
78
|
+
def creating(agg, parts)
|
|
79
|
+
agg["commands"].find { |cmd| cmd["creates"] && takes?(cmd, parts.values_at(*ROLES)) }
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# @return [Boolean] whether the command takes every one of the named attributes
|
|
83
|
+
def takes?(cmd, names) = (names - cmd["attributes"].map { |attr| attr["name"] }).empty?
|
|
84
|
+
|
|
85
|
+
# @return [Hash{String => Object}] the commands, query and reason that may be missing
|
|
86
|
+
def optional(agg, parts, schedule)
|
|
87
|
+
{ "reschedule" => rescheduling(agg, parts), "cancel" => cancelling(agg), "query" => querying(agg, parts),
|
|
88
|
+
"reason" => reason(agg, parts), "generated" => generated?(agg, parts, schedule) }
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# @return [String, nil] the command on a record, not a lifecycle move, that takes the
|
|
92
|
+
# due time
|
|
93
|
+
def rescheduling(agg, parts)
|
|
94
|
+
agg["commands"].find { |cmd| !cmd["creates"] && !move?(agg, cmd) && takes?(cmd, [parts["due"]]) }&.fetch("name")
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# @return [String, nil] the destructive lifecycle move from the first state to a final
|
|
98
|
+
# one
|
|
99
|
+
def cancelling(agg)
|
|
100
|
+
agg["commands"].find { |cmd| cmd["destructive"] && ends?(agg, cmd) }&.fetch("name")
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# @return [String, nil] the query whose one argument is the subject
|
|
104
|
+
def querying(agg, parts)
|
|
105
|
+
agg["queries"].find { |query| query["attributes"].map { |attr| attr["name"] } == [parts["subject"]] }&.fetch("name")
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# @return [Boolean] whether the command is a lifecycle move
|
|
109
|
+
def move?(agg, cmd) = agg["lifecycle"]["transitions"].any? { |move| move["verb"] == cmd["name"] }
|
|
110
|
+
|
|
111
|
+
# @return [Boolean] whether the command moves a first-state record to a final state
|
|
112
|
+
def ends?(agg, cmd)
|
|
113
|
+
lifecycle = agg["lifecycle"]
|
|
114
|
+
final = endings(lifecycle)
|
|
115
|
+
lifecycle["transitions"].any? do |move|
|
|
116
|
+
move["verb"] == cmd["name"] && move["from"].include?(lifecycle["default"]) && final.include?(move["to"])
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# @return [String, nil] the first optional plain text attribute no other part uses
|
|
121
|
+
def reason(agg, parts)
|
|
122
|
+
used = [agg["identity"], *parts.values_at(*ROLES)]
|
|
123
|
+
spare = agg["attributes"].find { |attr| attr["optional"] && !used.include?(attr["name"]) && plain_text?(agg, attr) }
|
|
124
|
+
spare&.fetch("name")
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# @return [Boolean] whether the attribute is one plain text, alone or as a value
|
|
128
|
+
# object's only part
|
|
129
|
+
def plain_text?(agg, attr)
|
|
130
|
+
return false if %w[list picker options widget].any? { |key| attr[key] }
|
|
131
|
+
|
|
132
|
+
parts = agg["valueObjects"][attr["type"]]
|
|
133
|
+
parts ? lone_text?(parts) : attr["kind"] == "text"
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# @return [Boolean] whether the parts are one plain text
|
|
137
|
+
def lone_text?(parts) = parts.size == 1 && parts.first["kind"] == "text" && !parts.first["list"]
|
|
138
|
+
|
|
139
|
+
# @return [true, nil] whether the creating command takes an identity that is not the
|
|
140
|
+
# subject,
|
|
141
|
+
# which the editor then makes itself
|
|
142
|
+
def generated?(agg, parts, schedule)
|
|
143
|
+
taken = schedule["attributes"].any? { |attr| attr["name"] == agg["identity"] }
|
|
144
|
+
true if taken && agg["identity"] != parts["subject"]
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
end
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "attributes"
|
|
4
|
+
require_relative "blocks"
|
|
5
|
+
require_relative "clearing"
|
|
6
|
+
require_relative "composition"
|
|
7
|
+
require_relative "destructive"
|
|
8
|
+
require_relative "drafts"
|
|
9
|
+
require_relative "limits"
|
|
10
|
+
require_relative "media"
|
|
11
|
+
require_relative "pickers"
|
|
12
|
+
require_relative "relations"
|
|
13
|
+
require_relative "scheduling"
|
|
14
|
+
|
|
15
|
+
module Hecks
|
|
16
|
+
module Projections
|
|
17
|
+
module Site
|
|
18
|
+
module CmsEditor
|
|
19
|
+
# What the editor reads of a domain chapter, as plain data: each aggregate with its
|
|
20
|
+
# identity, attributes, value objects, lifecycle, commands and queries. The editor's
|
|
21
|
+
# `schema.ts` is this hash as a typed constant, and nothing else about the domain reaches
|
|
22
|
+
# the generated files, so the output is a pure function of the chapter.
|
|
23
|
+
#
|
|
24
|
+
# An aggregate that registers pictures (see `Media`) is also named under `media`, so the
|
|
25
|
+
# editor can offer an upload; a chapter with none has no such key.
|
|
26
|
+
#
|
|
27
|
+
# A query that `returns` a value object is answered outside the domain and is left out: the
|
|
28
|
+
# host refuses it, so a list cannot be built from it.
|
|
29
|
+
class Schema
|
|
30
|
+
# @param chapters [Bluebook::Chapter, Array<Bluebook::Chapter>] the chapter, or the
|
|
31
|
+
# chapters in the order the row names them; the first names the domain addressed
|
|
32
|
+
# @param skip [Array<String>] aggregates to leave out: `Name`, or `Chapter::Name`
|
|
33
|
+
# @param pictures [Bluebook::Chapter, nil] another chapter whose picture aggregate the
|
|
34
|
+
# editor's pictures use, in place of any in `chapters`
|
|
35
|
+
# @param roles [Hash{String => Array<String>}] the roles each chapter is limited to
|
|
36
|
+
# @return [Hash{String => Object}] the domain's name and its aggregates; `chapters` too
|
|
37
|
+
# when there are several
|
|
38
|
+
# @raise [ArgumentError] when no aggregate is left, or `pictures` has no picture aggregate
|
|
39
|
+
def self.read(chapters, skip: [], pictures: nil, roles: {})
|
|
40
|
+
list = chapters.is_a?(Array) ? chapters : [chapters]
|
|
41
|
+
shaped = Composition.aggregates(list, skip: skip, roles: roles) { |agg| new(agg).to_h }
|
|
42
|
+
raise ArgumentError, "the #{list.map(&:name).join(", ")} chapter has no aggregate to edit" if shaped.empty?
|
|
43
|
+
|
|
44
|
+
media = pictures ? elsewhere(pictures) : within(shaped, list.first.name)
|
|
45
|
+
assemble(list, Relations.apply(Pickers.apply(shaped, media), media), media)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# @return [Hash{String => Object}] the schema: the domain, the chapters when several, the
|
|
49
|
+
# aggregates, and the picture and scheduling aggregates when there are such
|
|
50
|
+
def self.assemble(list, aggregates, media)
|
|
51
|
+
chapters = list.size > 1 ? { "chapters" => list.map(&:name) } : {}
|
|
52
|
+
scheduling = Scheduling.read(aggregates)
|
|
53
|
+
{ "domain" => list.first.name, **chapters, "aggregates" => aggregates, **(media ? { "media" => media } : {}),
|
|
54
|
+
**(scheduling ? { "scheduling" => scheduling } : {}) }
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# The picture aggregate among the editor's own, with the chapter that holds it when that
|
|
58
|
+
# is not the first, since the host is addressed by chapter.
|
|
59
|
+
#
|
|
60
|
+
# @return [Hash{String => Object}, nil] the picture aggregate, as `Media` describes it
|
|
61
|
+
def self.within(shaped, first)
|
|
62
|
+
found = Media.read(shaped)
|
|
63
|
+
chapter = found&.delete("chapter")
|
|
64
|
+
chapter && chapter != first ? found.merge("domain" => chapter) : found
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# The picture aggregate of another chapter: the same record as one in the editor's own
|
|
68
|
+
# chapter, with the chapter's name and the aggregate itself, since the editor's own
|
|
69
|
+
# aggregates do not hold it.
|
|
70
|
+
#
|
|
71
|
+
# @return [Hash{String => Object}] the picture aggregate, as `Media` describes it
|
|
72
|
+
# @raise [ArgumentError] when the chapter has no aggregate that registers pictures
|
|
73
|
+
def self.elsewhere(chapter)
|
|
74
|
+
shaped = chapter.aggregates.map { |agg| new(agg).to_h }
|
|
75
|
+
found = Media.read(shaped)
|
|
76
|
+
raise ArgumentError, "the #{chapter.name} chapter has no aggregate that registers pictures" unless found
|
|
77
|
+
|
|
78
|
+
found.merge("domain" => chapter.name, "definition" => shaped.find { |agg| agg["name"] == found["aggregate"] })
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# @param aggregate [Bluebook::Aggregate] one aggregate of the chapter
|
|
82
|
+
def initialize(aggregate)
|
|
83
|
+
@agg = aggregate
|
|
84
|
+
@attributes = Attributes.new(aggregate.value_objects)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# @return [Hash{String => Object}] the aggregate as the editor reads it
|
|
88
|
+
def to_h
|
|
89
|
+
held = aggregate_attributes
|
|
90
|
+
drafts = Drafts.of(@agg, held)
|
|
91
|
+
{ "name" => @agg.hecks_name, "description" => @agg.description, "identity" => @agg.identified_by.to_s,
|
|
92
|
+
"lifecycle" => lifecycle, "attributes" => held, "valueObjects" => value_objects,
|
|
93
|
+
"commands" => @agg.commands.map { |command| command(command) }, "queries" => queries,
|
|
94
|
+
**(drafts ? { "drafts" => drafts } : {}) }
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
private
|
|
98
|
+
|
|
99
|
+
def lifecycle_field = @agg.lifecycle&.field
|
|
100
|
+
|
|
101
|
+
# The aggregate's own attributes, bounded by the aggregate's invariants (see `Limits`).
|
|
102
|
+
def aggregate_attributes
|
|
103
|
+
bounded(attributes(@agg.attributes.reject { |a| a.name == lifecycle_field }), @agg.invariants)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def attributes(list) = list.map { |attribute| limited(@attributes.of(attribute)) }
|
|
107
|
+
|
|
108
|
+
# `list` with what the holder's rules say about the most each may hold (see `Limits`).
|
|
109
|
+
def bounded(list, rules) = Limits.apply(list, rules)
|
|
110
|
+
|
|
111
|
+
# `attr` with the most its block list may hold, when a command's `given` says.
|
|
112
|
+
def limited(attr)
|
|
113
|
+
most = attr.key?("blockList") ? Blocks.maximum(@agg.commands, attr["name"]) : nil
|
|
114
|
+
most ? attr.merge("max" => most) : attr
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def value_objects
|
|
118
|
+
@agg.value_objects.to_h do |object|
|
|
119
|
+
[object.hecks_name, bounded(attributes(object.attributes), object.invariants)]
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def lifecycle
|
|
124
|
+
@lifecycle ||= shaped_lifecycle
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def shaped_lifecycle
|
|
128
|
+
lifecycle = @agg.lifecycle
|
|
129
|
+
return nil unless lifecycle
|
|
130
|
+
|
|
131
|
+
shaped = { "field" => lifecycle.field.to_s, "default" => lifecycle.default,
|
|
132
|
+
"transitions" => lifecycle.transitions.map { |verb, move| transition(verb, move) } }
|
|
133
|
+
shaped.merge("tones" => Destructive.tones(shaped))
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def transition(verb, move)
|
|
137
|
+
{ "verb" => verb, "from" => Array(move.from), "to" => move.target }
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def command(command)
|
|
141
|
+
{ "name" => command.hecks_name, "goal" => command.goal, "role" => command.role, "creates" => command.creates?,
|
|
142
|
+
"on" => command.creates? ? nil : command.references.to_s,
|
|
143
|
+
"destructive" => Destructive.command?(command.hecks_name, lifecycle),
|
|
144
|
+
"attributes" => bounded(attributes(command.attributes), command.givens) }
|
|
145
|
+
.then { |shaped| clearing(shaped, command) }
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# Leaves the arguments that only clear (see `Clearing`) out of the command's form, and
|
|
149
|
+
# names those that are lists as `empty`, for the editor to send as empty lists.
|
|
150
|
+
def clearing(shaped, command) = Clearing.apply(shaped, Clearing.of(command, @agg))
|
|
151
|
+
|
|
152
|
+
def queries
|
|
153
|
+
@agg.queries.reject(&:returns).map do |query|
|
|
154
|
+
{ "name" => query.name, "description" => query.description, "attributes" => attributes(query.attributes) }
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
end
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require_relative "../root_rows"
|
|
5
|
+
require_relative "../routes_ts"
|
|
6
|
+
require_relative "checks"
|
|
7
|
+
require_relative "preview"
|
|
8
|
+
require_relative "../../../version"
|
|
9
|
+
|
|
10
|
+
module Hecks
|
|
11
|
+
module Projections
|
|
12
|
+
module Site
|
|
13
|
+
module CmsEditor
|
|
14
|
+
# The project's `Editor` row, checked: which domain chapter the editor edits, where the
|
|
15
|
+
# editor is served, the session cookie it keeps, and the host and sign-in it reads.
|
|
16
|
+
#
|
|
17
|
+
# `docs/site-routes.md` lists the fields. A project that declares no row has no editor to
|
|
18
|
+
# project.
|
|
19
|
+
class Setting
|
|
20
|
+
# The `Editor` row: its fields, which are required and what the others default to.
|
|
21
|
+
ROW = RootRows.new(
|
|
22
|
+
"Editor",
|
|
23
|
+
fields: { domain: String, chapter: String, base_path: String, sso_path: String, session_cookie: String,
|
|
24
|
+
host_cookie: String, host_env: String, host_default: String, roles: String, login: String,
|
|
25
|
+
title: String, brand: String, accent: String, logo: String, skip: String, media: String,
|
|
26
|
+
media_dir: String, media_max_bytes: String, chapter_roles: String, page_size: [String, Integer],
|
|
27
|
+
preview: String },
|
|
28
|
+
required: %i[domain chapter host_env login],
|
|
29
|
+
defaults: { base_path: "/editor", session_cookie: "hecks_editor", host_cookie: "hecks_session",
|
|
30
|
+
host_default: "http://127.0.0.1:4322", roles: "Admin,Owner", title: "Editor", brand: "",
|
|
31
|
+
accent: "", logo: "", skip: "", media: "", media_dir: "media", media_max_bytes: "5242880",
|
|
32
|
+
chapter_roles: "", page_size: "25", preview: "" }
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
# @return [Hash{Symbol => String}] the checked row, defaults filled
|
|
36
|
+
attr_reader :row
|
|
37
|
+
|
|
38
|
+
# Reads and checks the row a chapter declares.
|
|
39
|
+
#
|
|
40
|
+
# @param chapter [Bluebook::Chapter] the chapter that declares the route table
|
|
41
|
+
# @param table [Table, nil] the checked route table, to check the login page against
|
|
42
|
+
# @return [Setting, nil] the checked setting, or nil when the chapter declares no row
|
|
43
|
+
# @raise [Table::Invalid] when the row is refused
|
|
44
|
+
def self.read(chapter, table: nil)
|
|
45
|
+
row = ROW.read(chapter).first
|
|
46
|
+
row && new(row, table: table)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# @param row [Hash{Symbol => String}] the row, defaults filled
|
|
50
|
+
# @param table [Table, nil] the checked route table
|
|
51
|
+
# @raise [Table::Invalid] naming every problem when there is one
|
|
52
|
+
def initialize(row, table: nil)
|
|
53
|
+
@row = { sso_path: "#{row[:base_path]}/api/sso" }.merge(row, page_size: row[:page_size].to_s)
|
|
54
|
+
problems = Checks.problems(@row, roles, table)
|
|
55
|
+
return if problems.empty?
|
|
56
|
+
|
|
57
|
+
raise Table::Invalid, "the Editor row is refused:\n#{problems.map { |line| " - #{line}" }.join("\n")}"
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# @return [String] the domain's directory, relative to the project
|
|
61
|
+
def domain = row.fetch(:domain)
|
|
62
|
+
|
|
63
|
+
# @return [String] the first chapter the editor edits, which names the generated package
|
|
64
|
+
def chapter = chapters.first.to_s
|
|
65
|
+
|
|
66
|
+
# @return [Array<String>] every chapter the editor edits, in the order the row names them:
|
|
67
|
+
# `chapter` is one name or several, comma separated
|
|
68
|
+
def chapters = row.fetch(:chapter).split(",").map(&:strip).reject(&:empty?)
|
|
69
|
+
|
|
70
|
+
# @return [Hash{String => Array<String>}] the roles a chapter's aggregates are limited to,
|
|
71
|
+
# from `Chapter=Role,Role;Chapter=Role`; a chapter not named is open to every role the
|
|
72
|
+
# editor admits
|
|
73
|
+
def chapter_roles
|
|
74
|
+
row.fetch(:chapter_roles).split(";").map(&:strip).reject(&:empty?).to_h do |entry|
|
|
75
|
+
name, roles = entry.split("=", 2)
|
|
76
|
+
[name.to_s.strip, roles.to_s.split(",").map(&:strip).reject(&:empty?)]
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# @return [String, nil] the URL template a record is previewed at, or nil for no preview
|
|
81
|
+
def preview = row.fetch(:preview).strip.then { |template| template.empty? ? nil : template }
|
|
82
|
+
|
|
83
|
+
# @return [Integer] how many instances a list shows to a page
|
|
84
|
+
def page_size = row.fetch(:page_size).to_i
|
|
85
|
+
|
|
86
|
+
# @return [String, nil] the other chapter whose picture aggregate the editor's pictures
|
|
87
|
+
# use, or nil when the pictures (if any) are in the editor's own chapter
|
|
88
|
+
def media_chapter = row.fetch(:media).strip.then { |name| name.empty? ? nil : name }
|
|
89
|
+
|
|
90
|
+
# @return [String, nil] the product name the header shows, or nil for the domain's name
|
|
91
|
+
def brand = row.fetch(:brand).strip.then { |name| name.empty? ? nil : name }
|
|
92
|
+
|
|
93
|
+
# @return [String, nil] the accent colour as hex, or nil for the default theme
|
|
94
|
+
def accent = row.fetch(:accent).strip.then { |hex| hex.empty? ? nil : hex }
|
|
95
|
+
|
|
96
|
+
# @return [Array<String>] the aggregates left out: `Name`, or `Chapter::Name`
|
|
97
|
+
def skip = row.fetch(:skip).split(",").map(&:strip).reject(&:empty?)
|
|
98
|
+
|
|
99
|
+
# @return [Array<String>] the roles that count as an editor
|
|
100
|
+
def roles = row.fetch(:roles).split(",").map(&:strip).reject(&:empty?)
|
|
101
|
+
|
|
102
|
+
# @return [String] the generated package's name
|
|
103
|
+
def package = "#{chapter.gsub(/([a-z])([A-Z])/, '\1-\2').tr("_", "-").downcase}-editor"
|
|
104
|
+
|
|
105
|
+
# @return [Hash{String => String}] each placeholder to the text that replaces it
|
|
106
|
+
def tokens
|
|
107
|
+
{ "__BANNER__" => RoutesTs::BANNER, "__PACKAGE__" => package, "__ROLES__" => RoutesTs.literal(roles),
|
|
108
|
+
"__MEDIA_MAX_BYTES__" => row.fetch(:media_max_bytes), "__PAGE_SIZE__" => page_size.to_s,
|
|
109
|
+
"__CLIENT_VERSION__" => Hecks::VERSION, **preview_tokens, **quoted }
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# @return [Hash{String => String}] the preview template as a literal, and the policy line
|
|
113
|
+
# that lets the editor frame its origin, which is empty when there is no preview
|
|
114
|
+
def preview_tokens
|
|
115
|
+
source = Preview.frame_source(row.fetch(:preview).strip)
|
|
116
|
+
{ "__PREVIEW__" => JSON.generate(preview), "__FRAME_POLICY__" => source ? " \"frame-src #{source}\"," : "" }
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# @return [Hash{String => String}] each text field's placeholder to the field as JSON
|
|
120
|
+
def quoted
|
|
121
|
+
{ "TITLE" => :title, "BASE_PATH" => :base_path, "SSO_PATH" => :sso_path, "LOGIN" => :login,
|
|
122
|
+
"SESSION_COOKIE" => :session_cookie, "HOST_COOKIE" => :host_cookie, "HOST_ENV" => :host_env,
|
|
123
|
+
"HOST_DEFAULT" => :host_default, "MEDIA_DIR" => :media_dir, "LOGO" => :logo }
|
|
124
|
+
.to_h { |token, field| ["__#{token}__", JSON.generate(row.fetch(field))] }
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Hecks
|
|
4
|
+
module Projections
|
|
5
|
+
module Site
|
|
6
|
+
module CmsEditor
|
|
7
|
+
# The aggregates an attribute can name, found among the aggregates of one editor, and what
|
|
8
|
+
# a picker needs to know about each: its key, the attribute that labels it and the query
|
|
9
|
+
# that lists the ones to offer.
|
|
10
|
+
class Targets
|
|
11
|
+
# The attribute names that label an instance, in the order they are tried; an aggregate
|
|
12
|
+
# with none is labelled by its identity.
|
|
13
|
+
LABELS = %w[title name label].freeze
|
|
14
|
+
|
|
15
|
+
# The zero-argument queries that list what is on offer, tried in this order: ones that
|
|
16
|
+
# list everything, then the one that lists what is in use.
|
|
17
|
+
LISTINGS = %w[all list listing active].freeze
|
|
18
|
+
|
|
19
|
+
# @param aggregates [Array<Hash{String => Object}>] the aggregates as `Schema` shapes them
|
|
20
|
+
# @param media [Hash{String => Object}, nil] the picture aggregate, as `Media` says
|
|
21
|
+
def initialize(aggregates, media = nil)
|
|
22
|
+
@aggregates = aggregates
|
|
23
|
+
@media = media
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# @return [Hash{String => Object}, nil] the aggregate named `name`, as a picker names it
|
|
27
|
+
def named(name, from)
|
|
28
|
+
pick(@aggregates.select { |agg| agg["name"] == name }, from)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# @return [Hash{String => Object}, nil] the aggregate whose identity is of type `stem`, or
|
|
32
|
+
# that is named `stem`, as a picker names it
|
|
33
|
+
def keyed_by(stem, from)
|
|
34
|
+
pick(@aggregates.select { |agg| identity_type(agg) == stem }, from) || named(stem, from)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# @return [Hash{String => Object}, nil] the aggregate that is `word` in snake case
|
|
38
|
+
def kind(word, from)
|
|
39
|
+
pick(@aggregates.select { |agg| snake(agg["name"]) == word }, from)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# @return [Hash{String => Object}, nil] the picture aggregate, which offers keys and
|
|
43
|
+
# accepts any other text as well, since a picture may be named by an address
|
|
44
|
+
def pictures
|
|
45
|
+
return nil unless @media
|
|
46
|
+
|
|
47
|
+
agg = @media["definition"] || @aggregates.find { |candidate| candidate["name"] == @media["aggregate"] }
|
|
48
|
+
return nil unless agg
|
|
49
|
+
|
|
50
|
+
described = describe(agg, agg["chapter"] || @media["domain"], @media["listing"])
|
|
51
|
+
described.merge("label" => @media.dig("fields", "alt", "name"), "strict" => false)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
private
|
|
55
|
+
|
|
56
|
+
def pick(found, from)
|
|
57
|
+
own = found.select { |agg| agg["chapter"] == from["chapter"] }
|
|
58
|
+
chosen = own.first || (found.first if found.size == 1)
|
|
59
|
+
chosen && describe(chosen, chosen["chapter"], listing(chosen)).merge("strict" => true)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def describe(agg, chapter, listing)
|
|
63
|
+
{ "target" => agg["name"], "chapter" => chapter, "key" => agg["identity"], "label" => label(agg),
|
|
64
|
+
"listing" => listing }.compact
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def identity_type(agg)
|
|
68
|
+
agg["attributes"].find { |attr| attr["name"] == agg["identity"] }&.fetch("type", nil)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def label(agg)
|
|
72
|
+
plain = agg["attributes"].reject { |attr| attr["list"] || attr["widget"] == "body" }.map { |attr| attr["name"] }
|
|
73
|
+
LABELS.find { |name| plain.include?(name) } || agg["identity"]
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def listing(agg)
|
|
77
|
+
named = agg["queries"].select { |query| query["attributes"].empty? }.map { |query| query["name"] }
|
|
78
|
+
LISTINGS.filter_map { |word| named.find { |name| name.downcase == word } }.first
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def snake(name) = name.gsub(/([a-z0-9])([A-Z])/, "\\1_\\2").downcase
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "__PACKAGE__",
|
|
3
|
+
"private": true,
|
|
4
|
+
"type": "module",
|
|
5
|
+
"engines": {
|
|
6
|
+
"node": ">=22.18"
|
|
7
|
+
},
|
|
8
|
+
"scripts": {
|
|
9
|
+
"clean": "node -e \"fs.rmSync('dist', { recursive: true, force: true })\"",
|
|
10
|
+
"build": "npm run clean && npm run build:css && npm run build:js",
|
|
11
|
+
"build:css": "tailwindcss --input src/browser/app.css --output dist/editor.css --minify",
|
|
12
|
+
"build:js": "esbuild src/browser/main.js --bundle --splitting --format=esm --target=es2022 --minify --outdir=dist --entry-names=editor --chunk-names=[name]-[hash]",
|
|
13
|
+
"prestart": "npm run build",
|
|
14
|
+
"start": "node src/server.ts",
|
|
15
|
+
"check": "tsc -p tsconfig.json"
|
|
16
|
+
},
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"@hecks/client": "^__CLIENT_VERSION__",
|
|
19
|
+
"@tiptap/core": "3.31.4",
|
|
20
|
+
"@tiptap/extension-blockquote": "3.31.4",
|
|
21
|
+
"@tiptap/extension-image": "3.31.4",
|
|
22
|
+
"@tiptap/extensions": "3.31.4",
|
|
23
|
+
"@tiptap/pm": "3.31.4",
|
|
24
|
+
"@tiptap/starter-kit": "3.31.4",
|
|
25
|
+
"tiptap-extension-flat-list": "0.4.0"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@tailwindcss/cli": "4.3.3",
|
|
29
|
+
"@tailwindcss/typography": "0.5.20",
|
|
30
|
+
"@types/node": "^22.10.0",
|
|
31
|
+
"daisyui": "5.7.47",
|
|
32
|
+
"esbuild": "0.28.2",
|
|
33
|
+
"tailwindcss": "4.3.3",
|
|
34
|
+
"typescript": "^5.8.0"
|
|
35
|
+
}
|
|
36
|
+
}
|