hecks 2.9.0 → 3.0.1
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/exe/hecks +51 -6
- data/lib/hecks/adapters/driven/heki/journal.rb +1 -1
- data/lib/hecks/adapters/driven/lambda/client.rb +1 -1
- data/lib/hecks/adapters/driven/lambda.rb +1 -1
- data/lib/hecks/adapters/driven/memory.rb +16 -3
- data/lib/hecks/adapters/driven/postgres/reconnect.rb +6 -2
- data/lib/hecks/adapters/driven/postgres/shared_connection.rb +180 -0
- data/lib/hecks/adapters/driven/postgres.rb +13 -4
- data/lib/hecks/adapters/driven/prism.rb +41 -8
- data/lib/hecks/adapters/driven/tenant_provisioner.rb +73 -16
- data/lib/hecks/behaviors/expectations.rb +1 -1
- data/lib/hecks/behaviors.rb +1 -1
- data/lib/hecks/bench/cli.rb +62 -0
- data/lib/hecks/bench/environment.rb +71 -0
- data/lib/hecks/bench/postgres_probe.rb +30 -0
- data/lib/hecks/bench/report.rb +76 -0
- data/lib/hecks/bench/ruby_runner.rb +93 -0
- data/lib/hecks/bench/run.rb +55 -0
- data/lib/hecks/bench/rust_runner.rb +101 -0
- data/lib/hecks/bench/stats.rb +46 -0
- data/lib/hecks/bench/suite.rb +98 -0
- data/lib/hecks/bench/workload.rb +89 -0
- data/lib/hecks/bench.rb +17 -0
- data/lib/hecks/bluebook/assembly/contracts.rb +2 -0
- data/lib/hecks/bluebook/attribute.rb +1 -1
- data/lib/hecks/bluebook/behaviour/aggregate.rb +30 -1
- data/lib/hecks/bluebook/behaviour/domain_port.rb +7 -0
- data/lib/hecks/bluebook/chapter.rb +6 -2
- data/lib/hecks/bluebook/domain_port.rb +36 -5
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +10 -0
- data/lib/hecks/bluebook/dsl/bluebook_builder.rb +8 -0
- data/lib/hecks/bluebook/dsl/bootstrap_table.rb +2 -1
- data/lib/hecks/bluebook/dsl/domain_port_builder.rb +31 -3
- data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +122 -3
- data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +1 -1
- data/lib/hecks/bluebook/dsl/query_builder.rb +27 -1
- data/lib/hecks/bluebook/dsl/rule_reference.rb +1 -1
- data/lib/hecks/bluebook/dsl/world_builder.rb +29 -2
- data/lib/hecks/bluebook/expression/canonical_form.rb +1 -1
- data/lib/hecks/bluebook/expression/resolver.rb +3 -2
- data/lib/hecks/bluebook/hexagon.rb +13 -5
- data/lib/hecks/bluebook/meta_validator/reconstruction.rb +1 -0
- data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +4 -1
- data/lib/hecks/bluebook/meta_validator/verdict_cache.rb +246 -0
- data/lib/hecks/bluebook/meta_validator.rb +9 -4
- data/lib/hecks/bluebook/model_check.rb +23 -0
- data/lib/hecks/bluebook/pattern_subset.rb +38 -0
- data/lib/hecks/bluebook/query.rb +20 -5
- data/lib/hecks/bluebook/smoke_test.rb +1 -1
- data/lib/hecks/canonical_json.rb +30 -0
- data/lib/hecks/chapters.rb +102 -0
- data/lib/hecks/cli/behaviors.rb +61 -0
- data/lib/hecks/cli/canonicalise.rb +25 -0
- data/lib/hecks/cli/check_engine_agreement.rb +32 -0
- data/lib/hecks/cli/check_era.rb +88 -0
- data/lib/hecks/cli/console.rb +66 -0
- data/lib/hecks/cli/deploy_template_diff.rb +40 -0
- data/lib/hecks/cli/doc_coverage.rb +32 -0
- data/lib/hecks/cli/document.rb +2 -2
- data/lib/hecks/cli/expression_projection.rb +41 -0
- data/lib/hecks/cli/follow.rb +88 -0
- data/lib/hecks/cli/history.rb +44 -0
- data/lib/hecks/cli/ir.rb +1 -1
- data/lib/hecks/cli/mcp.rb +3 -3
- data/lib/hecks/cli/model_check.rb +15 -8
- data/lib/hecks/cli/pattern_cases.rb +47 -0
- data/lib/hecks/cli/present.rb +39 -0
- data/lib/hecks/cli/project_cli.rb +224 -32
- data/lib/hecks/cli/project_diagrams.rb +4 -3
- data/lib/hecks/cli/project_glossary.rb +44 -0
- data/lib/hecks/cli/reference.rb +46 -0
- data/lib/hecks/cli/refresh_projections.rb +44 -0
- data/lib/hecks/cli/refresh_rspec_runtime_baseline.rb +150 -0
- data/lib/hecks/cli/release.rb +70 -0
- data/lib/hecks/cli/release_gem.rb +59 -0
- data/lib/hecks/cli/rspec_io_parallel_files.rb +137 -0
- data/lib/hecks/cli/rspec_shard_files.rb +66 -0
- data/lib/hecks/cli/run.rb +3 -3
- data/lib/hecks/cli/seed_semantics_corpus.rb +71 -0
- data/lib/hecks/cli/serve_query_ir_mcp.rb +30 -0
- data/lib/hecks/cli/shape.rb +70 -0
- data/lib/hecks/cli/smoke_http.rb +230 -0
- data/lib/hecks/cli/smoke_test.rb +5 -3
- data/lib/hecks/cli/spec_example.rb +27 -0
- data/lib/hecks/cli/statements.rb +32 -0
- data/lib/hecks/cli/stores.rb +4 -5
- data/lib/hecks/cli/stress_concurrency_specs.rb +177 -0
- data/lib/hecks/cli.rb +201 -52
- data/lib/hecks/codemod.rb +324 -0
- data/lib/hecks/corpus.rb +423 -0
- data/lib/hecks/deploy/bluebook/deploy.bluebook +591 -23
- data/lib/hecks/deploy/bluebook/deploy.hecksagon +41 -4
- data/lib/hecks/deploy/oidc.json +82 -2
- data/lib/hecks/doc/reference.rb +351 -0
- data/lib/hecks/doors/cli_door.rb +196 -0
- data/lib/hecks/doors/cli_runner.rb +297 -0
- data/lib/hecks/{facade → doors}/command_request.rb +1 -1
- data/lib/hecks/{facade → doors}/handle.rb +2 -2
- data/lib/hecks/{facade → doors}/json_door.rb +4 -4
- data/lib/hecks/doors/launcher_options.rb +129 -0
- data/lib/hecks/{cli → doors}/mcp_door.rb +7 -7
- data/lib/hecks/doors/mcp_door_scope.rb +97 -0
- data/lib/hecks/{facade/surface → doors/ruby_door}/aggregate_door.rb +8 -3
- data/lib/hecks/{facade/surface → doors/ruby_door}/chapter.rb +3 -3
- data/lib/hecks/{facade/surface.rb → doors/ruby_door.rb} +36 -5
- data/lib/hecks/doors.rb +61 -0
- data/lib/hecks/embryonaut_bluebook/vendor_cli.rb +1 -1
- data/lib/hecks/embryonaut_bluebook.rb +1 -1
- data/lib/hecks/engine_agreement.rb +156 -0
- data/lib/hecks/facade.rb +24 -37
- data/lib/hecks/forms/app.rb +6 -6
- data/lib/hecks/forms/banking_presentation.rb +50 -0
- data/lib/hecks/forms/field_shape.rb +2 -1
- data/lib/hecks/forms/port_argument.rb +1 -1
- data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +393 -0
- data/lib/hecks/fuzzing/combination_miner.rb +86 -0
- data/lib/hecks/fuzzing/concurrent_dispatch.rb +214 -0
- data/lib/hecks/fuzzing/coverage_campaign.rb +143 -0
- data/lib/hecks/fuzzing/differential.rb +156 -0
- data/lib/hecks/fuzzing/domain_generator.rb +615 -0
- data/lib/hecks/fuzzing/era_boundary.rb +71 -0
- data/lib/hecks/fuzzing/form_census.rb +136 -0
- data/lib/hecks/fuzzing/generated_domain_check.rb +93 -0
- data/lib/hecks/fuzzing/invalid_value_generator.rb +64 -0
- data/lib/hecks/fuzzing/isolated_boot.rb +301 -0
- data/lib/hecks/fuzzing/nondeterministic.rb +51 -0
- data/lib/hecks/fuzzing/outside_world.rb +45 -0
- data/lib/hecks/fuzzing/persistence_parity.rb +102 -0
- data/lib/hecks/fuzzing/properties/corrections.rb +88 -0
- data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +314 -0
- data/lib/hecks/fuzzing/properties/guards.rb +190 -0
- data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +300 -0
- data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +93 -0
- data/lib/hecks/fuzzing/properties/outbox.rb +89 -0
- data/lib/hecks/fuzzing/properties/querying.rb +175 -0
- data/lib/hecks/fuzzing/properties.rb +162 -0
- data/lib/hecks/fuzzing/qa_settings.rb +123 -0
- data/lib/hecks/fuzzing/replay.rb +505 -0
- data/lib/hecks/fuzzing/rotation_priority.rb +48 -0
- data/lib/hecks/fuzzing/rust_gap_manifest.rb +108 -0
- data/lib/hecks/fuzzing/self_consistency.rb +339 -0
- data/lib/hecks/fuzzing/sequence_generator/adversary.rb +385 -0
- data/lib/hecks/fuzzing/sequence_generator/catalog.rb +132 -0
- data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +95 -0
- data/lib/hecks/fuzzing/sequence_generator/picker.rb +77 -0
- data/lib/hecks/fuzzing/sequence_generator/query_binding.rb +109 -0
- data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +237 -0
- data/lib/hecks/fuzzing/sequence_generator.rb +189 -0
- data/lib/hecks/fuzzing/shrinker.rb +166 -0
- data/lib/hecks/fuzzing/structural_skips.rb +33 -0
- data/lib/hecks/fuzzing/sweep_depth.rb +43 -0
- data/lib/hecks/fuzzing/target_capabilities.rb +163 -0
- data/lib/hecks/fuzzing/value_generator.rb +216 -0
- data/lib/hecks/fuzzing.rb +23 -0
- data/lib/hecks/grammar/evolve.rb +336 -0
- data/lib/hecks/hecks/adapters/codebase/codemods.rb +45 -0
- data/lib/hecks/hecks/adapters/codebase/conformance.rb +71 -0
- data/lib/hecks/hecks/adapters/codebase/corpus_tasks.rb +202 -0
- data/lib/hecks/hecks/adapters/codebase/gem_registry.rb +80 -0
- data/lib/hecks/hecks/adapters/codebase/kernel_tables.rb +54 -0
- data/lib/hecks/hecks/adapters/codebase/language.rb +240 -0
- data/lib/hecks/hecks/adapters/codebase/npm_registry.rb +102 -0
- data/lib/hecks/hecks/adapters/codebase/publishing.rb +142 -0
- data/lib/hecks/hecks/adapters/codebase/regeneration.rb +67 -0
- data/lib/hecks/hecks/adapters/codebase/release_facts.rb +90 -0
- data/lib/hecks/hecks/adapters/codebase/ruby_child.rb +83 -0
- data/lib/hecks/hecks/adapters/codebase/secret_vault.rb +36 -0
- data/lib/hecks/hecks/adapters/codebase/source_tree.adapter +7 -0
- data/lib/hecks/hecks/adapters/codebase/source_tree.rb +189 -0
- data/lib/hecks/hecks/adapters/codebase/sqlite_fixture.rb +71 -0
- data/lib/hecks/hecks/adapters/codebase/style.rb +171 -0
- data/lib/hecks/hecks/adapters/codebase/test_runner.rb +55 -0
- data/lib/hecks/hecks/adapters/codebase/test_suite.rb +165 -0
- data/lib/hecks/hecks/adapters/codebase/tree.rb +111 -0
- data/lib/hecks/hecks/adapters/console_capture.rb +79 -0
- data/lib/hecks/hecks/adapters/deploy_toolchain.adapter +7 -0
- data/lib/hecks/hecks/adapters/deploy_toolchain.rb +108 -0
- data/lib/hecks/hecks/adapters/git.adapter +7 -0
- data/lib/hecks/hecks/adapters/git.rb +102 -0
- data/lib/hecks/hecks/adapters/host_http.adapter +7 -0
- data/lib/hecks/hecks/adapters/host_http.rb +65 -0
- data/lib/hecks/hecks/adapters/in_process_boot.adapter +7 -0
- data/lib/hecks/hecks/adapters/in_process_boot.rb +197 -0
- data/lib/hecks/hecks/adapters/in_process_operations.rb +202 -0
- data/lib/hecks/hecks/adapters/journal_store/changes.rb +143 -0
- data/lib/hecks/hecks/adapters/journal_store/compaction.rb +145 -0
- data/lib/hecks/hecks/adapters/journal_store/edge_audit.rb +132 -0
- data/lib/hecks/hecks/adapters/journal_store/examination.rb +135 -0
- data/lib/hecks/hecks/adapters/journal_store/held_domain.rb +163 -0
- data/lib/hecks/hecks/adapters/journal_store/readings.rb +187 -0
- data/lib/hecks/hecks/adapters/journal_store.adapter +7 -0
- data/lib/hecks/hecks/adapters/journal_store.rb +64 -0
- data/lib/hecks/hecks/adapters/local_files.adapter +7 -0
- data/lib/hecks/hecks/adapters/local_files.rb +46 -0
- data/lib/hecks/hecks/adapters/pg_admin.adapter +7 -0
- data/lib/hecks/hecks/adapters/pg_admin.rb +245 -0
- data/lib/hecks/hecks/adapters/process_pool.adapter +7 -0
- data/lib/hecks/hecks/adapters/process_pool.rb +119 -0
- data/lib/hecks/hecks/adapters/rust_toolchain.adapter +7 -0
- data/lib/hecks/hecks/adapters/rust_toolchain.rb +179 -0
- data/lib/hecks/hecks/adapters/rust_workspace.rb +150 -0
- data/lib/hecks/hecks/adapters/shell.adapter +7 -0
- data/lib/hecks/hecks/adapters/shell.rb +49 -0
- data/lib/hecks/hecks/adapters/terminal.adapter +7 -0
- data/lib/hecks/hecks/adapters/terminal.rb +74 -0
- data/lib/hecks/hecks/codebase.behaviors +172 -0
- data/lib/hecks/hecks/codebase.bluebook +2453 -0
- data/lib/hecks/hecks/context_map.hecksagon +13 -0
- data/lib/hecks/hecks/custodian.behaviors +179 -0
- data/lib/hecks/hecks/custodian.bluebook +1847 -0
- data/lib/hecks/hecks/deploy.behaviors +74 -0
- data/lib/hecks/hecks/deploy.world +12 -0
- data/lib/hecks/hecks/environments/memory.world +10 -0
- data/lib/hecks/hecks/governance.world +7 -0
- data/lib/hecks/hecks/hecks.behaviors +62 -0
- data/lib/hecks/hecks/hecks.bluebook +124 -0
- data/lib/hecks/hecks/hecks.hecksagon +390 -0
- data/lib/hecks/hecks/hecks.world +42 -0
- data/lib/hecks/kernel_capabilities.rb +194 -0
- data/lib/hecks/language/bluebook/bluebook.bluebook +11 -0
- data/lib/hecks/language/bluebook/query.bluebook +7 -0
- data/lib/hecks/language/bluebook/vocabulary.bluebook +4 -4
- data/lib/hecks/language/hecksagon/domain_port.bluebook +10 -0
- data/lib/hecks/language/hecksagon/hecksagon.bluebook +20 -0
- data/lib/hecks/mcp_stdio_guard.rb +2 -2
- data/lib/hecks/persistence_legacy_fixture/regenerate.rb +161 -0
- data/lib/hecks/persistence_legacy_fixture.rb +113 -0
- data/lib/hecks/ports/persistence/plugins/era/era_check.rb +8 -5
- data/lib/hecks/ports/persistence/plugins/era/era_guard.rb +4 -1
- data/lib/hecks/ports/persistence/plugins/era/expected_era.rb +15 -2
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +1 -1
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +1 -1
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/merge_coordinator.rb +1 -1
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +32 -15
- data/lib/hecks/ports/persistence/plugins/era/postgres_era.rb +15 -5
- data/lib/hecks/ports/persistence/plugins/era/translation/approval_file.rb +227 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +1 -1
- data/lib/hecks/ports/persistence/plugins/era/translation/reattest.rb +43 -24
- data/lib/hecks/ports/persistence/plugins/era/translation.rb +1 -0
- data/lib/hecks/ports/persistence/state_codec.rb +24 -5
- data/lib/hecks/projection_files.rb +285 -0
- data/lib/hecks/projections/bootstrap_table.rb +1 -1
- data/lib/hecks/projections/deploy/fargate.rb +5 -5
- data/lib/hecks/projections/deploy/lambda.rb +43 -32
- data/lib/hecks/projections/deploy/preview/preview.sh.erb +1 -1
- data/lib/hecks/projections/deploy/preview/template.rb +1 -1
- data/lib/hecks/projections/deploy/scripts/deploy-service.sh.tmpl +1 -1
- data/lib/hecks/projections/deploy/scripts/expected-era.header +1 -1
- data/lib/hecks/projections/deploy/scripts/hosting.mk.tmpl +3 -3
- data/lib/hecks/projections/deploy/scripts/smoke-after-deploy.sh.tmpl +1 -1
- data/lib/hecks/projections/deploy/shared.rb +1 -1
- data/lib/hecks/projections/diagrams.rb +3 -3
- data/lib/hecks/projections/model/deviations.rb +1 -1
- data/lib/hecks/projections/parser_table.rb +4 -4
- data/lib/hecks/projections/reference.rb +2 -3
- data/lib/hecks/projections/rust_vocabulary.rb +1 -1
- data/lib/hecks/projector/cli_projector.rb +106 -37
- data/lib/hecks/projector.rb +1 -1
- data/lib/hecks/quality_control/adapters/agent.adapter +7 -0
- data/lib/hecks/quality_control/adapters/agent.rb +58 -0
- data/lib/hecks/quality_control/adapters/angle_tools.adapter +7 -0
- data/lib/hecks/quality_control/adapters/angle_tools.rb +15 -0
- data/lib/hecks/quality_control/adapters/clearance_recorder.rb +36 -0
- data/lib/hecks/quality_control/adapters/clearance_tools.adapter +7 -0
- data/lib/hecks/quality_control/adapters/clearance_tools.rb +17 -0
- data/lib/hecks/quality_control/adapters/git_pr.adapter +7 -0
- data/lib/hecks/quality_control/adapters/git_pr.rb +226 -0
- data/lib/hecks/quality_control/adapters/github_checks.adapter +9 -0
- data/lib/hecks/quality_control/adapters/github_checks.rb +83 -0
- data/lib/hecks/quality_control/adapters/github_ci_webhook.rb +56 -0
- data/lib/hecks/quality_control/adapters/github_issues.adapter +7 -0
- data/lib/hecks/quality_control/adapters/github_issues.rb +49 -0
- data/lib/hecks/quality_control/adapters/qa_tool.rb +59 -0
- data/lib/hecks/quality_control/adapters/sweep_tools.adapter +7 -0
- data/lib/hecks/quality_control/adapters/sweep_tools.rb +51 -0
- data/lib/hecks/quality_control/adapters/target_tools.adapter +7 -0
- data/lib/hecks/quality_control/adapters/target_tools.rb +43 -0
- data/lib/hecks/quality_control/cli/child.rb +45 -0
- data/lib/hecks/quality_control/cli/qa_concurrency_racer.rb +45 -0
- data/lib/hecks/quality_control/cli/qa_discover_external_domains.rb +248 -0
- data/lib/hecks/quality_control/cli/qa_domain_novelty.rb +163 -0
- data/lib/hecks/quality_control/cli/qa_generated_domains.rb +450 -0
- data/lib/hecks/quality_control/cli/qa_log_bug.rb +195 -0
- data/lib/hecks/quality_control/cli/qa_mine_combinations.rb +220 -0
- data/lib/hecks/quality_control/cli/qa_open_pr.rb +305 -0
- data/lib/hecks/quality_control/cli/qa_postgres_migrate.rb +154 -0
- data/lib/hecks/quality_control/cli/qa_postgres_role.rb +159 -0
- data/lib/hecks/quality_control/cli/qa_pr_check.rb +232 -0
- data/lib/hecks/quality_control/cli/qa_seed_angles.rb +142 -0
- data/lib/hecks/quality_control/cli/qa_seed_targets.rb +69 -0
- data/lib/hecks/quality_control/cli/qa_sweep/all_mode.rb +280 -0
- data/lib/hecks/quality_control/cli/qa_sweep/checks.rb +329 -0
- data/lib/hecks/quality_control/cli/qa_sweep/dials.rb +92 -0
- data/lib/hecks/quality_control/cli/qa_sweep/finding_report.rb +117 -0
- data/lib/hecks/quality_control/cli/qa_sweep/parsing.rb +175 -0
- data/lib/hecks/quality_control/cli/qa_sweep/release_mode.rb +60 -0
- data/lib/hecks/quality_control/cli/qa_sweep.rb +611 -0
- data/lib/hecks/quality_control/cli/qa_tick.rb +274 -0
- data/lib/hecks/quality_control/quality_control.bluebook +2224 -0
- data/lib/hecks/quality_control/quality_control.hecksagon +13 -0
- data/lib/hecks/quality_control/quality_control.ports.hecksagon +54 -0
- data/lib/hecks/query_ir.rb +346 -0
- data/lib/hecks/query_ir_mcp.rb +198 -0
- data/lib/hecks/release/runner/ci_publisher.rb +2 -2
- data/lib/hecks/release/runner/clean_tree.rb +42 -0
- data/lib/hecks/release/runner/console.rb +6 -0
- data/lib/hecks/release/runner/gem_publisher.rb +14 -7
- data/lib/hecks/release/runner/npm_publisher.rb +6 -36
- data/lib/hecks/release/runner/preflight.rb +26 -2
- data/lib/hecks/release/runner/published.rb +8 -26
- data/lib/hecks/release/runner/tagger.rb +30 -15
- data/lib/hecks/release/runner.rb +57 -6
- data/lib/hecks/router/namespace_installer.rb +3 -3
- data/lib/hecks/runtime/adapter_lookup.rb +124 -0
- data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +4 -0
- data/lib/hecks/runtime/dependency_planning.rb +20 -2
- data/lib/hecks/runtime/dispatcher.rb +53 -3
- data/lib/hecks/runtime/interpreting.rb +3 -0
- data/lib/hecks/runtime/loader.rb +65 -12
- data/lib/hecks/runtime/outbox.rb +1 -1
- data/lib/hecks/runtime/policy_interpreter.rb +5 -2
- data/lib/hecks/runtime/port_operation_interpreter.rb +2 -10
- data/lib/hecks/runtime/query_interpreter.rb +77 -0
- data/lib/hecks/runtime/reaction_outcome.rb +89 -0
- data/lib/hecks/runtime/refusal_wording.rb +23 -1
- data/lib/hecks/runtime/registry/verification.rb +163 -4
- data/lib/hecks/runtime/registry.rb +57 -3
- data/lib/hecks/runtime/remote_dispatcher.rb +31 -2
- data/lib/hecks/runtime/value/coercion.rb +23 -135
- data/lib/hecks/runtime/value/field_checks.rb +172 -0
- data/lib/hecks/runtime/value.rb +4 -1
- data/lib/hecks/runtime.rb +20 -6
- data/lib/hecks/rust_build/conformance.rb +158 -0
- data/lib/hecks/rust_build/conformance_fuzz.rb +71 -0
- data/lib/hecks/rust_build/coverage.rb +218 -0
- data/lib/hecks/rust_build/native_build.rb +124 -0
- data/lib/hecks/rust_build/project_rust/root_mod.rb +120 -0
- data/lib/hecks/rust_build/project_rust.rb +332 -0
- data/lib/hecks/rust_build/wasm.rb +105 -0
- data/lib/hecks/rust_build/wasm_browser.rb +113 -0
- data/lib/hecks/rust_build.rb +144 -0
- data/lib/hecks/storehouse.rb +16 -16
- data/lib/hecks/tenancy/bluebook/tenancy.bluebook +2 -2
- data/lib/hecks/tenancy/bluebook/tenancy.hecksagon +1 -1
- data/lib/hecks/three_zero/forms.yml +127 -0
- data/lib/hecks/three_zero.rb +12 -0
- data/lib/hecks/tools/argument_gate_matrix.rb +316 -0
- data/lib/hecks/tools/comment_style.rb +1070 -0
- data/lib/hecks/tools/corpus_report.rb +87 -0
- data/lib/hecks/tools/deploy_recipe.rb +255 -0
- data/lib/hecks/tools/deploy_recipe_lint.rb +282 -0
- data/lib/hecks/tools/drop_implicit_append_fields.rb +95 -0
- data/lib/hecks/tools/era_projection_backfill.rb +94 -0
- data/lib/hecks/tools/era_reattest.rb +146 -0
- data/lib/hecks/tools/evolve_run.rb +189 -0
- data/lib/hecks/tools/fuzz_sweep.rb +373 -0
- data/lib/hecks/tools/heki_compaction.rb +100 -0
- data/lib/hecks/tools/hoist_local_givens.rb +246 -0
- data/lib/hecks/tools/journal_compaction.rb +89 -0
- data/lib/hecks/tools/oidc_manifests.rb +70 -0
- data/lib/hecks/tools/query_ir_run.rb +81 -0
- data/lib/hecks/tools/regeneration_run.rb +166 -0
- data/lib/hecks/tools/rust_comment_style.rb +642 -0
- data/lib/hecks/tools/sequence_script.rb +62 -0
- data/lib/hecks/tools/tail_merge.rb +117 -0
- data/lib/hecks/tools/tenant_provisioning.rb +142 -0
- data/lib/hecks/tools/translation_audit.rb +208 -0
- data/lib/hecks/tools/translation_scaffold.rb +131 -0
- data/lib/hecks/tools.rb +86 -0
- data/lib/hecks/version.rb +1 -1
- data/lib/hecks.rb +32 -15
- data/qa/settings.yml +66 -0
- data/rust/.cargo/config.toml +6 -0
- data/rust/.gitignore +2 -0
- data/rust/Cargo.lock +54 -0
- data/rust/Cargo.toml +61 -0
- data/rust/build/.cargo/config.toml +3 -0
- data/rust/build/.gitignore +1 -0
- data/rust/build/Cargo.lock +7 -0
- data/rust/build/Cargo.toml +19 -0
- data/rust/build/src/build_artifact.rs +47 -0
- data/rust/build/src/cargo_sync.rs +248 -0
- data/rust/build/src/json.rs +421 -0
- data/rust/build/src/lineage_pass.rs +285 -0
- data/rust/build/src/main.rs +51 -0
- data/rust/build/src/optional_pass.rs +150 -0
- data/rust/build/src/pipeline.rs +264 -0
- data/rust/build/src/reserved_names.rs +76 -0
- data/rust/build/src/resolve.rs +124 -0
- data/rust/build/src/root.rs +25 -0
- data/rust/build/src/sidecars.rs +73 -0
- data/rust/build/src/subprocess.rs +51 -0
- data/rust/build/src/tmp.rs +31 -0
- data/rust/codegen/.cargo/config.toml +3 -0
- data/rust/codegen/.gitignore +1 -0
- data/rust/codegen/Cargo.lock +7 -0
- data/rust/codegen/Cargo.toml +20 -0
- data/rust/codegen/src/attr.rs +32 -0
- data/rust/codegen/src/bridging.rs +481 -0
- data/rust/codegen/src/commands.rs +1226 -0
- data/rust/codegen/src/constraints.rs +109 -0
- data/rust/codegen/src/dependency_planning.rs +209 -0
- data/rust/codegen/src/domain_generator.rs +1288 -0
- data/rust/codegen/src/exemplar.rs +230 -0
- data/rust/codegen/src/expr_emitter.rs +87 -0
- data/rust/codegen/src/fielded.rs +192 -0
- data/rust/codegen/src/hecks_naming.rs +33 -0
- data/rust/codegen/src/json.rs +335 -0
- data/rust/codegen/src/json_codec.rs +697 -0
- data/rust/codegen/src/literal.rs +207 -0
- data/rust/codegen/src/main.rs +290 -0
- data/rust/codegen/src/manifest.rs +227 -0
- data/rust/codegen/src/mutations.rs +784 -0
- data/rust/codegen/src/naming.rs +286 -0
- data/rust/codegen/src/ports.rs +208 -0
- data/rust/codegen/src/prelude.rs +116 -0
- data/rust/codegen/src/queries.rs +786 -0
- data/rust/codegen/src/reactions.rs +532 -0
- data/rust/codegen/src/read_models.rs +697 -0
- data/rust/codegen/src/reference_specs.rs +40 -0
- data/rust/codegen/src/registry.rs +966 -0
- data/rust/codegen/src/reserved_names.rs +76 -0
- data/rust/codegen/src/shared.rs +93 -0
- data/rust/codegen/src/skip_reason.rs +25 -0
- data/rust/codegen/src/types.rs +362 -0
- data/rust/host/.cargo/config.toml +3 -0
- data/rust/host/.gitignore +1 -0
- data/rust/host/Cargo.lock +4297 -0
- data/rust/host/Cargo.toml +61 -0
- data/rust/host/HECKS_RELEASE +1 -0
- data/rust/host/rds-ca-bundle.pem +2736 -0
- data/rust/host/src/api.rs +1596 -0
- data/rust/host/src/approval.rs +652 -0
- data/rust/host/src/auth.rs +1287 -0
- data/rust/host/src/bin/expr_harness.rs +46 -0
- data/rust/host/src/bin/lineage_harness.rs +119 -0
- data/rust/host/src/bin/mint_harness.rs +135 -0
- data/rust/host/src/checkout.rs +265 -0
- data/rust/host/src/dispatch.rs +1443 -0
- data/rust/host/src/expr_json.rs +1117 -0
- data/rust/host/src/field_hints.rs +52 -0
- data/rust/host/src/ir.rs +733 -0
- data/rust/host/src/journal.rs +874 -0
- data/rust/host/src/lambda_client.rs +378 -0
- data/rust/host/src/log.rs +147 -0
- data/rust/host/src/main.rs +474 -0
- data/rust/host/src/mint.rs +1865 -0
- data/rust/host/src/payments/keystore.rs +330 -0
- data/rust/host/src/payments/tests.rs +1400 -0
- data/rust/host/src/payments.rs +767 -0
- data/rust/host/src/presentation.rs +698 -0
- data/rust/host/src/presentation_write.rs +1431 -0
- data/rust/host/src/query_step.rs +149 -0
- data/rust/host/src/rate_limit.rs +904 -0
- data/rust/host/src/reference_transform.rs +241 -0
- data/rust/host/src/reference_validate.rs +338 -0
- data/rust/host/src/resend.rs +296 -0
- data/rust/host/src/secrets.rs +106 -0
- data/rust/host/src/server.rs +663 -0
- data/rust/host/src/storage_shape.rs +175 -0
- data/rust/host/src/test_pg.rs +40 -0
- data/rust/host/src/ui_schema.rs +1351 -0
- data/rust/host/src/wasm_runner.rs +191 -0
- data/rust/host/src/web/newsletter/cooldown.rs +126 -0
- data/rust/host/src/web/newsletter/tests.rs +104 -0
- data/rust/host/src/web/newsletter.rs +310 -0
- data/rust/host/src/web/newsletter_send.rs +357 -0
- data/rust/host/src/web/registrations.rs +1505 -0
- data/rust/host/src/web.rs +2937 -0
- data/rust/lsp/.gitignore +1 -0
- data/rust/lsp/Cargo.lock +7 -0
- data/rust/lsp/Cargo.toml +16 -0
- data/rust/lsp/README.md +133 -0
- data/rust/lsp/src/diagnostics.rs +165 -0
- data/rust/lsp/src/json.rs +385 -0
- data/rust/lsp/src/main.rs +399 -0
- data/rust/lsp/src/outline.rs +177 -0
- data/rust/lsp/src/rpc.rs +67 -0
- data/rust/parser/.cargo/config.toml +5 -0
- data/rust/parser/.gitignore +1 -0
- data/rust/parser/Cargo.lock +7 -0
- data/rust/parser/Cargo.toml +21 -0
- data/rust/parser/src/build/closed_sets.rs +27 -0
- data/rust/parser/src/build/identity.rs +276 -0
- data/rust/parser/src/build/mod.rs +12 -0
- data/rust/parser/src/build/naming.rs +141 -0
- data/rust/parser/src/build/pattern_subset.rs +161 -0
- data/rust/parser/src/build/query_derive.rs +72 -0
- data/rust/parser/src/build/query_inference.rs +269 -0
- data/rust/parser/src/build/query_options.rs +100 -0
- data/rust/parser/src/build/read_model.rs +94 -0
- data/rust/parser/src/build/references.rs +32 -0
- data/rust/parser/src/canonical.rs +114 -0
- data/rust/parser/src/diag.rs +55 -0
- data/rust/parser/src/emit.rs +953 -0
- data/rust/parser/src/expr/ast_json.rs +147 -0
- data/rust/parser/src/expr/evaluator.rs +81 -0
- data/rust/parser/src/expr/mod.rs +84 -0
- data/rust/parser/src/expr/resolver.rs +497 -0
- data/rust/parser/src/ir.rs +387 -0
- data/rust/parser/src/keywords.rs +585 -0
- data/rust/parser/src/lex.rs +662 -0
- data/rust/parser/src/main.rs +252 -0
- data/rust/parser/src/parse/aggregate.rs +612 -0
- data/rust/parser/src/parse/chapter.rs +517 -0
- data/rust/parser/src/parse/command.rs +637 -0
- data/rust/parser/src/parse/domain_port.rs +127 -0
- data/rust/parser/src/parse/entity.rs +527 -0
- data/rust/parser/src/parse/file.rs +67 -0
- data/rust/parser/src/parse/hecksagon.rs +231 -0
- data/rust/parser/src/parse/lifecycle.rs +162 -0
- data/rust/parser/src/parse/mod.rs +1262 -0
- data/rust/parser/src/parse/policy.rs +87 -0
- data/rust/parser/src/parse/process_manager.rs +305 -0
- data/rust/parser/src/parse/query.rs +100 -0
- data/rust/parser/src/parse/read_model.rs +214 -0
- data/rust/parser/src/parse/value_object.rs +250 -0
- data/rust/parser/src/ruby_value.rs +422 -0
- data/rust/project/bridging.rb +257 -0
- data/rust/project/commands.rb +877 -0
- data/rust/project/constraints.rb +86 -0
- data/rust/project/dependency_planning.rb +178 -0
- data/rust/project/domain_generator.rb +980 -0
- data/rust/project/exemplar.rb +148 -0
- data/rust/project/expr_emitter.rb +72 -0
- data/rust/project/fielded.rb +141 -0
- data/rust/project/json_codec.rb +641 -0
- data/rust/project/mutations.rb +721 -0
- data/rust/project/naming.rb +205 -0
- data/rust/project/ports.rb +65 -0
- data/rust/project/queries.rb +493 -0
- data/rust/project/reactions.rb +317 -0
- data/rust/project/read_models.rb +690 -0
- data/rust/project/reference_specs.rb +27 -0
- data/rust/project/registry.rb +396 -0
- data/rust/project/skip_reason.rb +29 -0
- data/rust/project/types.rb +225 -0
- data/rust/project/write_if_changed.rb +83 -0
- data/rust/project.rb +30 -0
- data/rust/project_rust_pipeline.rb +383 -0
- data/rust/src/exemplar/commands.rs +279 -0
- data/rust/src/exemplar/constraints.rs +18 -0
- data/rust/src/exemplar/fielded.rs +280 -0
- data/rust/src/exemplar/json.rs +277 -0
- data/rust/src/exemplar/mod.rs +17 -0
- data/rust/src/exemplar/mutations.rs +80 -0
- data/rust/src/exemplar/queries.rs +33 -0
- data/rust/src/exemplar/reactions.rs +88 -0
- data/rust/src/exemplar/read_models.rs +48 -0
- data/rust/src/exemplar/registry.rs +166 -0
- data/rust/src/exemplar/types.rs +44 -0
- data/rust/src/kernel/attribute_shapes/composite.rs +88 -0
- data/rust/src/kernel/attribute_shapes/list.rs +18 -0
- data/rust/src/kernel/attribute_shapes/mod.rs +44 -0
- data/rust/src/kernel/attribute_shapes/optional.rs +9 -0
- data/rust/src/kernel/attribute_shapes/scalar.rs +39 -0
- data/rust/src/kernel/cli.rs +585 -0
- data/rust/src/kernel/dispatch.rs +1090 -0
- data/rust/src/kernel/expr.rs +458 -0
- data/rust/src/kernel/expression_operators/arithmetic.rs +128 -0
- data/rust/src/kernel/expression_operators/comparison.rs +48 -0
- data/rust/src/kernel/expression_operators/enumeration.rs +232 -0
- data/rust/src/kernel/expression_operators/logical.rs +13 -0
- data/rust/src/kernel/expression_operators/membership.rs +18 -0
- data/rust/src/kernel/expression_operators/mod.rs +68 -0
- data/rust/src/kernel/expression_operators/pattern_match.rs +73 -0
- data/rust/src/kernel/expression_operators/positional.rs +126 -0
- data/rust/src/kernel/expression_operators/presence.rs +101 -0
- data/rust/src/kernel/expression_operators/sign_test.rs +16 -0
- data/rust/src/kernel/expression_operators/sized.rs +35 -0
- data/rust/src/kernel/expression_operators/text.rs +79 -0
- data/rust/src/kernel/expression_operators/to_string.rs +18 -0
- data/rust/src/kernel/json.rs +779 -0
- data/rust/src/kernel/mod.rs +133 -0
- data/rust/src/kernel/named_query.rs +219 -0
- data/rust/src/kernel/orchestrate.rs +1810 -0
- data/rust/src/kernel/pattern.rs +307 -0
- data/rust/src/kernel/query_comparators.rs +206 -0
- data/rust/src/kernel/query_ordering.rs +103 -0
- data/rust/src/kernel/read_model.rs +914 -0
- data/rust/src/kernel/reference_lookup.rs +149 -0
- data/rust/src/kernel/repository.rs +468 -0
- data/rust/src/kernel/routing.rs +612 -0
- data/rust/src/kernel/vocab/aggregate_dispatch_order.rs +118 -0
- data/rust/src/kernel/vocab/entity_dispatch_order.rs +114 -0
- data/rust/src/kernel/vocab/field_hint.rs +70 -0
- data/rust/src/kernel/vocab/mod.rs +20 -0
- data/rust/src/kernel/vocab/query_comparator.rs +65 -0
- data/rust/src/kernel/vocab/refusal_template.rs +1592 -0
- data/rust/src/lib.rs +8 -0
- data/rust/src/main.rs +16 -0
- data/rust/web/.gitignore +1 -0
- data/rust/web/Cargo.lock +119 -0
- data/rust/web/Cargo.toml +18 -0
- data/rust/web/src/lib.rs +8 -0
- metadata +477 -12
- data/lib/hecks/facade/cli_door.rb +0 -96
- data/lib/hecks/facade/cli_runner.rb +0 -127
- data/lib/hecks/mcp_door_scope.rb +0 -95
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../tools"
|
|
4
|
+
require_relative "../corpus"
|
|
5
|
+
|
|
6
|
+
module Hecks
|
|
7
|
+
module Tools
|
|
8
|
+
# Prints the Rust-facing corpus from `Hecks::Corpus`, for CI steps and for asking what CI walks.
|
|
9
|
+
# Takes one flag (`hecks corpus_rust_coverage <flag>`):
|
|
10
|
+
#
|
|
11
|
+
# --rust-domains feature and path of every in-repo domain with a Cargo feature
|
|
12
|
+
# --rust-regen-order paths `hecks regenerate_corpus` regenerates, in order
|
|
13
|
+
# --rust-coverage run the coverage tool over every generated module; a
|
|
14
|
+
# Corpus::RUST_COVERAGE_PENDING module must still fail,
|
|
15
|
+
# every other one must pass
|
|
16
|
+
module CorpusReport
|
|
17
|
+
USAGE = "usage: hecks corpus_rust_coverage --rust-domains | --rust-regen-order | --rust-coverage"
|
|
18
|
+
|
|
19
|
+
module_function
|
|
20
|
+
|
|
21
|
+
# Prints the report the first argument asks for.
|
|
22
|
+
#
|
|
23
|
+
# @param argv [Array<String>] one of the three flags
|
|
24
|
+
# @param root [String] the checkout
|
|
25
|
+
# @return [Integer] 0, or 1 when a coverage check does not come out as it must
|
|
26
|
+
# @raise [SystemExit] with the usage line when the flag is unknown
|
|
27
|
+
def main(argv, root: Tools::ROOT)
|
|
28
|
+
case argv.first
|
|
29
|
+
when "--rust-domains"
|
|
30
|
+
Hecks::Corpus.rust_domains.each { |domain| puts "#{domain.feature}\t#{relative(domain.dir, root)}" }
|
|
31
|
+
0
|
|
32
|
+
when "--rust-regen-order"
|
|
33
|
+
Hecks::Corpus.rust_regen_order.each { |domain| puts relative(domain.dir, root) }
|
|
34
|
+
0
|
|
35
|
+
when "--rust-coverage" then rust_coverage
|
|
36
|
+
else abort USAGE
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# @param path [String] an absolute path
|
|
41
|
+
# @param root [String] the checkout
|
|
42
|
+
# @return [String] the path relative to `root`
|
|
43
|
+
def relative(path, root) = path.delete_prefix("#{root}/")
|
|
44
|
+
|
|
45
|
+
# Runs the coverage tool for each module in this process.
|
|
46
|
+
#
|
|
47
|
+
# @param modules [Array<String>] the generated modules
|
|
48
|
+
# @return [Hash{String => Array}] each module's `[passed, output]`
|
|
49
|
+
def coverage_results(modules)
|
|
50
|
+
require_relative "../rust_build"
|
|
51
|
+
modules.to_h do |name|
|
|
52
|
+
result = Hecks::RustBuild.capture("rust_coverage", [name])
|
|
53
|
+
[name, [result.ok?, result.out + result.err]]
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# @return [Integer] 0 when every module is as it must be, 1 otherwise
|
|
58
|
+
# @raise [SystemExit] when `RUST_COVERAGE_PENDING` names a module that does not exist
|
|
59
|
+
def rust_coverage
|
|
60
|
+
modules = Hecks::Corpus.generated_modules
|
|
61
|
+
pending = Hecks::Corpus::RUST_COVERAGE_PENDING
|
|
62
|
+
unknown = pending.keys - modules
|
|
63
|
+
if unknown.any?
|
|
64
|
+
abort "hecks corpus_rust_coverage: RUST_COVERAGE_PENDING names #{unknown.join(', ')}, " \
|
|
65
|
+
"which has no generated module"
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
results = coverage_results(modules)
|
|
69
|
+
problems = modules.filter_map do |name|
|
|
70
|
+
passed, output = results.fetch(name)
|
|
71
|
+
if pending.key?(name)
|
|
72
|
+
puts "#{name}: pending (#{passed ? 'NOW PASSES' : 'still fails'}) — #{pending[name]}"
|
|
73
|
+
"#{name} passes now — delete it from Hecks::Corpus::RUST_COVERAGE_PENDING" if passed
|
|
74
|
+
else
|
|
75
|
+
puts "#{name}: #{passed ? 'ok' : 'FAILED'}"
|
|
76
|
+
"#{name} failed:\n#{output}" unless passed
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
return puts("hecks corpus_rust_coverage: #{modules.size} generated modules checked").then { 0 } if problems.empty?
|
|
81
|
+
|
|
82
|
+
warn problems.join("\n\n")
|
|
83
|
+
1
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "hecks"
|
|
4
|
+
require "fileutils"
|
|
5
|
+
require "optparse"
|
|
6
|
+
require_relative "../tools"
|
|
7
|
+
|
|
8
|
+
module Hecks
|
|
9
|
+
module Tools
|
|
10
|
+
# The AWS deployment generator (ADR 0018): resolves a domain's declared `deployed_to(...)`
|
|
11
|
+
# target and dispatches to the matching `Projector` export, writing the recipe (template,
|
|
12
|
+
# scripts and Makefile) under `deploy/<stack>/` of the checkout.
|
|
13
|
+
#
|
|
14
|
+
# hecks deploy project <domain> [--tenant=<slug>] [--schema=<name>]
|
|
15
|
+
# [--out=<dir>] [--environment=<name>]
|
|
16
|
+
#
|
|
17
|
+
# `--environment` layers an overlay `.world` file over the base one; a missing overlay is an
|
|
18
|
+
# error. `--out` writes the recipe elsewhere, for a domain in a client's repo. `--tenant` and
|
|
19
|
+
# `--schema` override the stack for shared-database hosting: `stack_name` is appended to, so
|
|
20
|
+
# re-running with the same `--tenant` regenerates the same stack.
|
|
21
|
+
module DeployRecipe
|
|
22
|
+
USAGE = "usage: hecks deploy project <domain> [--tenant=<slug>] [--schema=<name>] " \
|
|
23
|
+
"[--out=<dir>] [--environment=<name>]"
|
|
24
|
+
|
|
25
|
+
# What the flags asked for.
|
|
26
|
+
Options = Struct.new(:tenant, :out, :environment)
|
|
27
|
+
|
|
28
|
+
module_function
|
|
29
|
+
|
|
30
|
+
# Generates the recipe and prints each file written.
|
|
31
|
+
#
|
|
32
|
+
# @param argv [Array<String>] the domain directory, then the flags
|
|
33
|
+
# @param root [String] the checkout whose `deploy/` receives the recipe unless `--out` says
|
|
34
|
+
# otherwise
|
|
35
|
+
# @return [Integer] 0
|
|
36
|
+
# @raise [SystemExit] with the reason on stderr when the domain or its `.world` cannot
|
|
37
|
+
# generate
|
|
38
|
+
def main(argv, root: Tools::ROOT)
|
|
39
|
+
argv = argv.dup
|
|
40
|
+
options = parse(argv)
|
|
41
|
+
domain = argv.shift or abort USAGE
|
|
42
|
+
|
|
43
|
+
world_file = world_file_for(domain)
|
|
44
|
+
bluebook_basename = File.basename(world_file, ".world")
|
|
45
|
+
world = load_world(world_file, domain, options.environment)
|
|
46
|
+
deploy_settings = tenant_settings(world.for_verb("deployed_to"), options, File.basename(domain))
|
|
47
|
+
infra_name = deploy_settings[:stack_name] || File.basename(domain)
|
|
48
|
+
|
|
49
|
+
registry = cross_domain_registry(domain, bluebook_basename)
|
|
50
|
+
chapter = registry.bluebook(world.domain) or
|
|
51
|
+
abort "#{world_file} declares no #{world.domain} chapter — #{bluebook_basename}.bluebook's own " \
|
|
52
|
+
"Hecks.bluebook name has to match world.domain."
|
|
53
|
+
|
|
54
|
+
out_dir = options.out ? File.expand_path(options.out) : File.join(root, "deploy", infra_name)
|
|
55
|
+
artifact = project(target_key(deploy_settings, world_file, domain), chapter, world, registry,
|
|
56
|
+
deploy_settings: deploy_settings, infra_name: infra_name, options: options,
|
|
57
|
+
domain: domain, root: root, world_file: world_file)
|
|
58
|
+
write(artifact, out_dir)
|
|
59
|
+
0
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# @param argv [Array<String>] the arguments; the flags are removed from it
|
|
63
|
+
# @return [Options] the flags, with `tenant` a hash of `tenant` and `schema`
|
|
64
|
+
# @raise [SystemExit] when `--schema` has no `--tenant`
|
|
65
|
+
def parse(argv)
|
|
66
|
+
options = Options.new({}, nil, nil)
|
|
67
|
+
OptionParser.new do |parser|
|
|
68
|
+
parser.on("--tenant=SLUG") { |v| options.tenant[:tenant] = v }
|
|
69
|
+
parser.on("--schema=NAME") { |v| options.tenant[:schema] = v }
|
|
70
|
+
parser.on("--out=DIR") { |v| options.out = v }
|
|
71
|
+
parser.on("--environment=NAME") { |v| options.environment = v }
|
|
72
|
+
end.parse!(argv)
|
|
73
|
+
|
|
74
|
+
if options.tenant[:schema] && !options.tenant[:tenant]
|
|
75
|
+
abort "--schema needs --tenant=<slug> — a schema override with no tenant to scope it to is meaningless"
|
|
76
|
+
end
|
|
77
|
+
options
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# The `.world` file named after the directory; failing that, the sole `*.world` file under
|
|
81
|
+
# `<domain>/bluebook/`; failing that, the one named after the chapter the domain's hecksagon
|
|
82
|
+
# attaches by name (`qa/` attaches QualityControl and also holds a Governance `.world`).
|
|
83
|
+
# It picks by no other rule: guessing which is the domain is not this generator's call.
|
|
84
|
+
#
|
|
85
|
+
# @param domain [String] the domain directory
|
|
86
|
+
# @return [String] the `.world` file
|
|
87
|
+
# @raise [SystemExit] when there is none
|
|
88
|
+
def world_file_for(domain)
|
|
89
|
+
world_file = File.join(domain, "bluebook", "#{File.basename(domain)}.world")
|
|
90
|
+
unless File.exist?(world_file)
|
|
91
|
+
candidates = Dir.glob(File.join(domain, "bluebook", "*.world"))
|
|
92
|
+
named = candidates.select { |path| attached_stems(domain).include?(File.basename(path, ".world")) }
|
|
93
|
+
pick = candidates.size == 1 ? candidates : named
|
|
94
|
+
world_file = pick.first if pick.size == 1
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
File.exist?(world_file) or
|
|
98
|
+
abort "#{world_file} does not exist — a domain needs a .world file to declare " \
|
|
99
|
+
"deployed_to(\"AwsLambda\") or deployed_to(\"AwsFargate\")"
|
|
100
|
+
world_file
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# The snake-cased names of the chapters a domain's hecksagons attach by name
|
|
104
|
+
# (`Hecks::Chapters.load!("QualityControl")`), which its `.world` and `.hecksagon` files
|
|
105
|
+
# are named after.
|
|
106
|
+
#
|
|
107
|
+
# @param domain [String] the domain directory
|
|
108
|
+
# @return [Array<String>] such as `["quality_control"]`
|
|
109
|
+
def attached_stems(domain)
|
|
110
|
+
Dir.glob(File.join(domain, "bluebook", "*.hecksagon"))
|
|
111
|
+
.flat_map { |path| File.read(path).scan(/Chapters\.load!\(\s*"([^"]+)"\s*\)/).flatten }
|
|
112
|
+
.map { |name| Hecks::Naming.snake(name) }.uniq
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# @param world_file [String] the base `.world` file
|
|
116
|
+
# @param domain [String] the domain directory
|
|
117
|
+
# @param environment [String, nil] the overlay to layer over the base
|
|
118
|
+
# @return [Object] the world's own declaration, named as the bluebook is
|
|
119
|
+
# @raise [SystemExit] when an overlay is missing or the file declares no world
|
|
120
|
+
def load_world(world_file, domain, environment)
|
|
121
|
+
registry = Hecks::Runtime::Registry.new
|
|
122
|
+
Hecks.with_registry(registry) do
|
|
123
|
+
Kernel.load(world_file)
|
|
124
|
+
if environment
|
|
125
|
+
overlay_file = File.join(domain, "bluebook", "environments", "#{environment}.world")
|
|
126
|
+
File.exist?(overlay_file) or
|
|
127
|
+
abort "#{overlay_file} does not exist — --environment=#{environment} needs that overlay"
|
|
128
|
+
Kernel.load(overlay_file)
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
registry.worlds.values.first or abort "#{world_file} declares no Hecks.world block at all"
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# Applied before the stack's name is computed, so the output directory agrees with the stack
|
|
136
|
+
# name `Lambda`/`Fargate` generate: they re-apply this same override against the `world` and
|
|
137
|
+
# `tenant:` they are handed.
|
|
138
|
+
#
|
|
139
|
+
# @param deploy_settings [Hash] the world's `deployed_to` settings
|
|
140
|
+
# @param options [Options] the flags
|
|
141
|
+
# @param domain_name [String] the domain directory's name
|
|
142
|
+
# @return [Hash] the settings, with the tenant appended to `stack_name` when there is one
|
|
143
|
+
def tenant_settings(deploy_settings, options, domain_name)
|
|
144
|
+
return deploy_settings unless options.tenant[:tenant]
|
|
145
|
+
|
|
146
|
+
base = deploy_settings[:stack_name] || domain_name
|
|
147
|
+
deploy_settings.merge(stack_name: "#{base}-#{options.tenant[:tenant]}")
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# Loads the whole registry a domain boots with, not just its own `.bluebook`, because a
|
|
151
|
+
# cross-domain invoke grant can live in any attached chapter. A domain with no bluebook of
|
|
152
|
+
# its own (the QA ledger) gets its chapter from the hecksagon's `Chapters.load!`. `root:` is
|
|
153
|
+
# required or `uses_embryonaut_bluebook` refuses with "needs a registry with a root to
|
|
154
|
+
# vendor from".
|
|
155
|
+
#
|
|
156
|
+
# @param domain [String] the domain directory
|
|
157
|
+
# @param bluebook_basename [String] the file name the domain's bluebook and hecksagon share
|
|
158
|
+
# @return [Hecks::Runtime::Registry] the registry
|
|
159
|
+
def cross_domain_registry(domain, bluebook_basename)
|
|
160
|
+
registry = Hecks::Runtime::Registry.new(root: File.expand_path(domain))
|
|
161
|
+
lib_hecks = File.expand_path("..", __dir__)
|
|
162
|
+
Hecks.with_registry(registry) do
|
|
163
|
+
%w[ports/persistence.port ports/extraction.port adapters/driven/memory.adapter
|
|
164
|
+
adapters/driven/prism.adapter].each { |file| Kernel.load(File.join(lib_hecks, file)) }
|
|
165
|
+
bluebook_file = File.join(domain, "bluebook", "#{bluebook_basename}.bluebook")
|
|
166
|
+
Kernel.load(bluebook_file) if File.exist?(bluebook_file)
|
|
167
|
+
hecksagon_file = File.join(domain, "bluebook", "#{bluebook_basename}.hecksagon")
|
|
168
|
+
Kernel.load(hecksagon_file) if File.exist?(hecksagon_file)
|
|
169
|
+
end
|
|
170
|
+
registry
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# Each adapter maps to a registered `Projector` export (`needs_world: true`); this only finds
|
|
174
|
+
# which one to call.
|
|
175
|
+
#
|
|
176
|
+
# @param deploy_settings [Hash] the world's `deployed_to` settings
|
|
177
|
+
# @param world_file [String] the `.world` file, named in the refusal
|
|
178
|
+
# @param domain [String] the domain directory, named in the refusal
|
|
179
|
+
# @return [Symbol] `:aws_lambda` or `:aws_fargate`
|
|
180
|
+
# @raise [SystemExit] with an example block when the world declares neither
|
|
181
|
+
def target_key(deploy_settings, world_file, domain)
|
|
182
|
+
case deploy_settings[:adapter]
|
|
183
|
+
when "AwsLambda" then :aws_lambda
|
|
184
|
+
when "AwsFargate" then :aws_fargate
|
|
185
|
+
else
|
|
186
|
+
abort <<~MSG
|
|
187
|
+
#{world_file} declares no deployed_to("AwsLambda") or deployed_to("AwsFargate") block. Add one, e.g.:
|
|
188
|
+
|
|
189
|
+
deployed_to("AwsLambda") do
|
|
190
|
+
region "us-east-1"
|
|
191
|
+
memory 512
|
|
192
|
+
timeout 10
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
or:
|
|
196
|
+
|
|
197
|
+
deployed_to("AwsFargate") do
|
|
198
|
+
region "us-east-1"
|
|
199
|
+
cpu 256
|
|
200
|
+
memory 512
|
|
201
|
+
port 8080
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
then re-run hecks deploy project #{domain}.
|
|
205
|
+
MSG
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
# `Lambda` and `Fargate` raise `ArgumentError` for a domain's own deploy conflicts; it is
|
|
210
|
+
# caught here so the message reaches stderr with a failing exit code instead of a backtrace.
|
|
211
|
+
#
|
|
212
|
+
# @param target [Symbol] the projector export
|
|
213
|
+
# @param chapter [Object] the domain's chapter
|
|
214
|
+
# @param world [Object] the domain's world
|
|
215
|
+
# @param registry [Hecks::Runtime::Registry] the registry the chapter came from
|
|
216
|
+
# @param context [Hash] `deploy_settings`, `infra_name`, `options`, `domain`, `root` and
|
|
217
|
+
# `world_file`
|
|
218
|
+
# @return [Hash{String => String}] the files to write
|
|
219
|
+
# @raise [SystemExit] when the domain's own settings conflict
|
|
220
|
+
def project(target, chapter, world, registry, **context)
|
|
221
|
+
artifact = Hecks::Projector.call(
|
|
222
|
+
target,
|
|
223
|
+
bluebook: chapter,
|
|
224
|
+
options: {
|
|
225
|
+
tenant: context[:options].tenant,
|
|
226
|
+
domain_dir: context[:domain],
|
|
227
|
+
root: context[:root],
|
|
228
|
+
world_file: context[:world_file],
|
|
229
|
+
cross_domain_registry: registry
|
|
230
|
+
},
|
|
231
|
+
world: world
|
|
232
|
+
)
|
|
233
|
+
# Only when the domain declared `smoke true`; otherwise nothing is added.
|
|
234
|
+
artifact.merge(Hecks::Projections::Deploy::Smoke.files(context[:deploy_settings],
|
|
235
|
+
stack_name: context[:infra_name]))
|
|
236
|
+
rescue ArgumentError => e
|
|
237
|
+
abort e.message
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
# Writes the files, and prints where the recipe deploys from.
|
|
241
|
+
#
|
|
242
|
+
# @param artifact [Hash{String => String}] the files to write
|
|
243
|
+
# @param out_dir [String] where they go
|
|
244
|
+
# @return [void]
|
|
245
|
+
def write(artifact, out_dir)
|
|
246
|
+
written = Hecks::Projector.write(artifact, out_dir, as: :files)
|
|
247
|
+
|
|
248
|
+
written.each { |path| puts "wrote #{path}" }
|
|
249
|
+
puts
|
|
250
|
+
puts "deploy from #{out_dir} — one command, nothing to type, nothing to remember:"
|
|
251
|
+
puts " make deploy"
|
|
252
|
+
end
|
|
253
|
+
end
|
|
254
|
+
end
|
|
255
|
+
end
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "tmpdir"
|
|
4
|
+
require "fileutils"
|
|
5
|
+
require "hecks"
|
|
6
|
+
require_relative "../tools"
|
|
7
|
+
require_relative "deploy_recipe"
|
|
8
|
+
require_relative "../hecks/adapters/console_capture"
|
|
9
|
+
|
|
10
|
+
module Hecks
|
|
11
|
+
module Tools
|
|
12
|
+
# Lints generated deploy Makefiles for prod-touching recipes that hide failure.
|
|
13
|
+
module DeployRecipeLint
|
|
14
|
+
# Commands that touch AWS or a real Postgres; `sam build` is excluded because it stays local.
|
|
15
|
+
RISKY_REGEX = /\baws\s+(?:cloudformation|ssm|secretsmanager|lambda|ec2)\b|\bpsql\b|DATABASE_URL=|\bsam\s+deploy\b/
|
|
16
|
+
|
|
17
|
+
# Deliberately loose: any real statement with "echo" counts, since only a missing step
|
|
18
|
+
# matters.
|
|
19
|
+
ECHO_REGEX = /\becho\b/
|
|
20
|
+
|
|
21
|
+
# An unconditional `exit 0` statement; `command && exit 0` is one statement and never matches.
|
|
22
|
+
BARE_EXIT_ZERO_REGEX = /\Aexit\s+0\s*\z/
|
|
23
|
+
|
|
24
|
+
# `exit $?` is only sound right after the command it reports on; an `@echo` in between
|
|
25
|
+
# resets it.
|
|
26
|
+
DOLLAR_QUESTION_EXIT_REGEX = /\Aexit\s+\$\$?\?\s*\z/
|
|
27
|
+
|
|
28
|
+
# Print-only or no-op statements, never the command a nearby `exit $?` reports on.
|
|
29
|
+
BENIGN_REGEX = /\A(?:@)?(?:echo\b|:\s*\z)/
|
|
30
|
+
|
|
31
|
+
Violation = Struct.new(:source, :target, :line, :rule, :message, keyword_init: true) do
|
|
32
|
+
def to_s
|
|
33
|
+
"#{source}:#{line}: [#{rule}] target #{target.inspect} — #{message}"
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
module_function
|
|
38
|
+
|
|
39
|
+
# Maps each target name to its `[line_no, raw_text]` recipe lines, tabs and continuations
|
|
40
|
+
# intact.
|
|
41
|
+
def parse_targets(text)
|
|
42
|
+
lines = text.lines
|
|
43
|
+
targets = {}
|
|
44
|
+
i = 0
|
|
45
|
+
while i < lines.length
|
|
46
|
+
line = lines[i]
|
|
47
|
+
if line.start_with?("\t", "#") || line.strip.empty? || line.include?(":=") || line.start_with?(".PHONY")
|
|
48
|
+
i += 1
|
|
49
|
+
next
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
if (m = line.match(%r{\A([A-Za-z0-9_./$(){}-]+):(?:\s.*)?\z}))
|
|
53
|
+
recipe = []
|
|
54
|
+
j = i + 1
|
|
55
|
+
while j < lines.length && (lines[j] == "\n" || lines[j].start_with?("\t") || lines[j].start_with?("#"))
|
|
56
|
+
recipe << [j, lines[j].chomp]
|
|
57
|
+
j += 1
|
|
58
|
+
end
|
|
59
|
+
targets[m[1]] = recipe
|
|
60
|
+
i = j
|
|
61
|
+
else
|
|
62
|
+
i += 1
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
targets
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Drops comment and blank lines; a comment can quote "sam deploy" or "echo" but never runs.
|
|
69
|
+
def real_statements(recipe_lines)
|
|
70
|
+
recipe_lines.reject { |_, text| text.sub(/\A\t/, "").start_with?("#") || text.strip.empty? }
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Splits real statements into shell chains; Make runs each backslash-terminated run as one
|
|
74
|
+
# shell.
|
|
75
|
+
def shell_chains(recipe_lines)
|
|
76
|
+
chains = []
|
|
77
|
+
current = []
|
|
78
|
+
real_statements(recipe_lines).each do |line_no, text|
|
|
79
|
+
stripped = text.sub(/\A\t/, "")
|
|
80
|
+
continues = stripped.end_with?("\\")
|
|
81
|
+
statement = continues ? stripped.sub(/\\\z/, "").rstrip : stripped
|
|
82
|
+
current << [line_no, statement]
|
|
83
|
+
unless continues
|
|
84
|
+
chains << current
|
|
85
|
+
current = []
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
chains << current unless current.empty?
|
|
89
|
+
chains
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Drops the leading Make `@` and trailing `;` so exit-code checks match the bare statement.
|
|
93
|
+
def normalize_statement(text)
|
|
94
|
+
text.sub(/\A@/, "").strip.sub(/;\s*\z/, "")
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Flags a chain that runs an AWS/DB command and later hits an unconditional `exit 0`.
|
|
98
|
+
def check_blind_exit_zero(target, chains)
|
|
99
|
+
chains.filter_map do |chain|
|
|
100
|
+
exit_zero = chain.find { |_, stmt| BARE_EXIT_ZERO_REGEX.match?(normalize_statement(stmt)) }
|
|
101
|
+
next unless exit_zero
|
|
102
|
+
|
|
103
|
+
risky_before = chain.take_while { |line_no, _| line_no != exit_zero.first }
|
|
104
|
+
.select { |_, stmt| RISKY_REGEX.match?(stmt) }
|
|
105
|
+
next if risky_before.empty?
|
|
106
|
+
|
|
107
|
+
Violation.new(
|
|
108
|
+
target: target, line: exit_zero.first + 1, rule: "UNVERIFIED_EXIT_ZERO",
|
|
109
|
+
message: "unconditional `exit 0` follows an AWS/DB-touching command (line #{risky_before.last.first + 1}: " \
|
|
110
|
+
"#{risky_before.last.last.strip.inspect}) whose own exit status this chain never checks — a real " \
|
|
111
|
+
"failure there would still report success."
|
|
112
|
+
)
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Flags `exit $?` whose preceding statement in the chain is a benign echo/no-op.
|
|
117
|
+
def check_stale_dollar_question(target, chains)
|
|
118
|
+
chains.filter_map do |chain|
|
|
119
|
+
idx = chain.index { |_, stmt| DOLLAR_QUESTION_EXIT_REGEX.match?(normalize_statement(stmt)) }
|
|
120
|
+
next unless idx
|
|
121
|
+
|
|
122
|
+
prev = idx.positive? ? chain[idx - 1] : nil
|
|
123
|
+
next unless prev
|
|
124
|
+
next unless BENIGN_REGEX.match?(normalize_statement(prev.last))
|
|
125
|
+
|
|
126
|
+
Violation.new(
|
|
127
|
+
target: target, line: chain[idx].first + 1, rule: "STALE_DOLLAR_QUESTION",
|
|
128
|
+
message: "`exit $?` follows a benign statement (line #{prev.first + 1}: #{prev.last.strip.inspect}), " \
|
|
129
|
+
"not the meaningful command it claims to report on — capture the real command's status into a " \
|
|
130
|
+
"named variable instead (this codebase's own convention, e.g. `BOOT_STATUS=$$?` ... " \
|
|
131
|
+
"`exit $$BOOT_STATUS`) and exit that, not a bare $?."
|
|
132
|
+
)
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Flags a target whose first AWS/DB command has no earlier echo step.
|
|
137
|
+
def check_prod_touch_without_echo(target, recipe_lines)
|
|
138
|
+
ordered = real_statements(recipe_lines)
|
|
139
|
+
first_risky = ordered.find { |_, text| RISKY_REGEX.match?(text) }
|
|
140
|
+
return [] unless first_risky
|
|
141
|
+
|
|
142
|
+
seen_echo = ordered.take_while { |line_no, _| line_no != first_risky.first }
|
|
143
|
+
.any? { |_, text| ECHO_REGEX.match?(text) }
|
|
144
|
+
return [] if seen_echo
|
|
145
|
+
|
|
146
|
+
[Violation.new(
|
|
147
|
+
target: target, line: first_risky.first + 1, rule: "PROD_TOUCH_WITHOUT_ECHO",
|
|
148
|
+
message: "touches AWS/DB (#{first_risky.last.strip.inspect}) with no earlier echo/validation step in this " \
|
|
149
|
+
"recipe naming what it's about to do to a human running it interactively."
|
|
150
|
+
)]
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# Runs all three checks over every target; `source` labels each violation.
|
|
154
|
+
def lint(text, source:)
|
|
155
|
+
targets = parse_targets(text)
|
|
156
|
+
violations = targets.flat_map do |name, recipe_lines|
|
|
157
|
+
chains = shell_chains(recipe_lines)
|
|
158
|
+
check_blind_exit_zero(name, chains) +
|
|
159
|
+
check_stale_dollar_question(name, chains) +
|
|
160
|
+
check_prod_touch_without_echo(name, recipe_lines)
|
|
161
|
+
end
|
|
162
|
+
violations.each { |v| v.source = source }
|
|
163
|
+
violations
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# Writes a minimal bluebook/world pair under `dir` for the deploy generator to generate from.
|
|
167
|
+
def write_fixture(dir, basename, world_body, env_local: nil)
|
|
168
|
+
domain_dir = File.join(dir, basename)
|
|
169
|
+
bluebook_dir = File.join(domain_dir, "bluebook")
|
|
170
|
+
FileUtils.mkdir_p(bluebook_dir)
|
|
171
|
+
bluebook_name = basename.split("_").map(&:capitalize).join
|
|
172
|
+
|
|
173
|
+
File.write(File.join(bluebook_dir, "#{basename}.bluebook"), <<~BLUEBOOK)
|
|
174
|
+
Hecks.bluebook "#{bluebook_name}" do
|
|
175
|
+
aggregate "Thing" do
|
|
176
|
+
identified_by :name
|
|
177
|
+
attribute :name, ThingName
|
|
178
|
+
value_object "ThingName" do
|
|
179
|
+
attribute :value, String
|
|
180
|
+
invariant("named") { !value.to_s.empty? }
|
|
181
|
+
end
|
|
182
|
+
command "Create" do
|
|
183
|
+
attribute :name, ThingName
|
|
184
|
+
sets :name
|
|
185
|
+
emits "ThingCreated"
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
BLUEBOOK
|
|
190
|
+
|
|
191
|
+
File.write(File.join(bluebook_dir, "#{basename}.world"), <<~WORLD)
|
|
192
|
+
Hecks.world "#{bluebook_name}" do
|
|
193
|
+
deployed_to("AwsLambda") do
|
|
194
|
+
#{world_body}
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
WORLD
|
|
198
|
+
|
|
199
|
+
File.write(File.join(domain_dir, ".env.local"), env_local) if env_local
|
|
200
|
+
|
|
201
|
+
domain_dir
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
# Runs the deploy generator on a tmpdir fixture; output always lands in
|
|
205
|
+
# <root>/deploy/<basename>.
|
|
206
|
+
def generate!(root, basename, world_body, env_local: nil)
|
|
207
|
+
Dir.mktmpdir do |dir|
|
|
208
|
+
domain_dir = write_fixture(dir, basename, world_body, env_local: env_local)
|
|
209
|
+
outcome = Hecks::Adapters::ConsoleCapture.capture { DeployRecipe.main([domain_dir], root: root) }
|
|
210
|
+
outcome.ok? or raise "hecks deploy project failed generating the #{basename} fixture: #{outcome.output}"
|
|
211
|
+
end
|
|
212
|
+
File.join(root, "deploy", basename)
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
# Own-RDS, Shared-mode and OAuth domains: together they hit every branch of the recipe
|
|
216
|
+
# builders.
|
|
217
|
+
def fixtures
|
|
218
|
+
{
|
|
219
|
+
"own fixture" => ["region \"us-east-1\"", nil],
|
|
220
|
+
"shared fixture" => ["region \"us-east-1\"\n database \"Shared\"\n owner \"SomeOwner\"", nil],
|
|
221
|
+
"oauth fixture" => [
|
|
222
|
+
"region \"us-east-1\"\n web \"Rust\"",
|
|
223
|
+
"GOOGLE_CLIENT_ID=test-client-id.apps.googleusercontent.com\nGOOGLE_CLIENT_SECRET=test-secret\n"
|
|
224
|
+
]
|
|
225
|
+
}
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
# Lints the Makefiles named in `argv`, or three generated fixture domains when there are none.
|
|
229
|
+
#
|
|
230
|
+
# @param argv [Array<String>] Makefile paths, or `-h`/`--help`
|
|
231
|
+
# @param root [String] the checkout the fixtures are generated into
|
|
232
|
+
# @return [Integer] 0 when clean, 1 when a violation was found
|
|
233
|
+
def main(argv, root: Tools::ROOT)
|
|
234
|
+
return usage if %w[-h --help].include?(argv.first)
|
|
235
|
+
|
|
236
|
+
violations = []
|
|
237
|
+
generated_dirs = []
|
|
238
|
+
begin
|
|
239
|
+
if argv.empty?
|
|
240
|
+
fixtures.each do |label, (world_body, env_local)|
|
|
241
|
+
basename = "lint_deploy_recipes_fixture_#{label.tr(' ', '_')}"
|
|
242
|
+
dir = generate!(root, basename, world_body, env_local: env_local)
|
|
243
|
+
generated_dirs << dir
|
|
244
|
+
makefile_path = File.join(dir, "Makefile")
|
|
245
|
+
violations.concat(lint(File.read(makefile_path), source: "#{label} (#{makefile_path})"))
|
|
246
|
+
end
|
|
247
|
+
else
|
|
248
|
+
argv.each do |path|
|
|
249
|
+
File.exist?(path) or abort "hecks deploy lint: no such file #{path}"
|
|
250
|
+
violations.concat(lint(File.read(path), source: path))
|
|
251
|
+
end
|
|
252
|
+
end
|
|
253
|
+
report(violations)
|
|
254
|
+
ensure
|
|
255
|
+
generated_dirs.each { |dir| FileUtils.rm_rf(dir) }
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
# @return [Integer] 0, after printing the usage
|
|
260
|
+
def usage
|
|
261
|
+
puts "usage: hecks deploy lint [Makefile ...]"
|
|
262
|
+
puts " no args: generates 3 representative fixture domains (own/shared/oauth)"
|
|
263
|
+
puts " via the real deploy generator, lints each one's Makefile"
|
|
264
|
+
puts " with args: lints the given Makefile(s) directly (e.g. deploy/<domain>/Makefile)"
|
|
265
|
+
0
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
# @param violations [Array<Violation>] what the lint found
|
|
269
|
+
# @return [Integer] 0 when there are none, 1 after printing them to stderr
|
|
270
|
+
def report(violations)
|
|
271
|
+
if violations.empty?
|
|
272
|
+
puts "hecks deploy lint: no violations found."
|
|
273
|
+
return 0
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
warn "hecks deploy lint: #{violations.size} violation(s) found:\n\n"
|
|
277
|
+
violations.each { |v| warn " #{v}\n" }
|
|
278
|
+
1
|
|
279
|
+
end
|
|
280
|
+
end
|
|
281
|
+
end
|
|
282
|
+
end
|