hecks 2.6.0 → 2.8.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/exe/hecks +13 -0
- data/lib/hecks/adapters/driven/google_authentication.rb +1 -1
- data/lib/hecks/adapters/driven/lambda/client.rb +1 -1
- data/lib/hecks/adapters/driven/memory.rb +1 -1
- data/lib/hecks/adapters/driven/postgres.rb +1 -1
- data/lib/hecks/adapters/driven/prism.rb +2 -2
- data/lib/hecks/adapters/driven/sqlite/projection.rb +1 -1
- data/lib/hecks/adapters/driven/tenant_provisioner.rb +31 -10
- data/lib/hecks/behaviors/expectations.rb +2 -2
- data/lib/hecks/bluebook/assembly/contract.rb +1 -1
- data/lib/hecks/bluebook/assembly/contracts.rb +5 -5
- data/lib/hecks/bluebook/assembly/specializer.rb +2 -2
- data/lib/hecks/bluebook/assembly.rb +1 -1
- data/lib/hecks/bluebook/behaviour/chapter.rb +5 -2
- data/lib/hecks/bluebook/behaviour/read_model.rb +18 -0
- data/lib/hecks/bluebook/capabilities.rb +2 -2
- data/lib/hecks/bluebook/chapter.rb +1 -1
- data/lib/hecks/bluebook/dsl/aggregate_builder.rb +2 -2
- data/lib/hecks/bluebook/dsl/binding_proxy.rb +6 -1
- data/lib/hecks/bluebook/dsl/bluebook_builder.rb +2 -2
- data/lib/hecks/bluebook/dsl/bootstrap_table.rb +2 -0
- data/lib/hecks/bluebook/dsl/command_builder.rb +3 -3
- data/lib/hecks/bluebook/dsl/entity_builder.rb +1 -1
- data/lib/hecks/bluebook/dsl/generic_dispatch.rb +1 -1
- data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +14 -9
- data/lib/hecks/bluebook/dsl/port_operation_builder.rb +1 -1
- data/lib/hecks/bluebook/dsl/rule_reference.rb +3 -3
- data/lib/hecks/bluebook/dsl/translation_builder.rb +1 -1
- data/lib/hecks/bluebook/dsl/world_builder.rb +47 -3
- data/lib/hecks/bluebook/hexagon.rb +15 -7
- data/lib/hecks/bluebook/meta_validator/judge.rb +3 -3
- data/lib/hecks/bluebook/meta_validator/plan.rb +4 -4
- data/lib/hecks/bluebook/meta_validator/readings.rb +1 -1
- data/lib/hecks/bluebook/meta_validator/reconstruction.rb +2 -2
- data/lib/hecks/bluebook/meta_validator/shapes.rb +1 -1
- data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +11 -10
- data/lib/hecks/bluebook/meta_validator/world_judge.rb +3 -1
- data/lib/hecks/bluebook/meta_validator.rb +1 -1
- data/lib/hecks/bluebook/model_check/client_profile.rb +4 -70
- data/lib/hecks/bluebook/model_check.rb +13 -14
- data/lib/hecks/cache_dir.rb +103 -0
- data/lib/hecks/cli/document.rb +104 -0
- data/lib/hecks/cli/ir.rb +47 -0
- data/lib/hecks/cli/mcp.rb +32 -0
- data/lib/hecks/cli/mcp_door.rb +461 -0
- data/lib/hecks/cli/model_check.rb +270 -0
- data/lib/hecks/cli/project_cli.rb +123 -0
- data/lib/hecks/cli/project_diagrams.rb +43 -0
- data/lib/hecks/cli/run.rb +219 -0
- data/lib/hecks/cli/smoke_test.rb +59 -0
- data/lib/hecks/cli/stores.rb +54 -0
- data/lib/hecks/cli.rb +166 -0
- data/lib/hecks/embryonaut_bluebook/lock.rb +72 -0
- data/lib/hecks/embryonaut_bluebook/shape.rb +47 -0
- data/lib/hecks/embryonaut_bluebook/vendor.rb +194 -0
- data/lib/hecks/embryonaut_bluebook/vendor_cli.rb +88 -0
- data/lib/hecks/embryonaut_bluebook.rb +29 -3
- data/lib/hecks/facade/handle.rb +2 -2
- data/lib/hecks/facade/surface/aggregate_door.rb +1 -1
- data/lib/hecks/forms/value_object_shape.rb +1 -1
- data/lib/hecks/framework/bluebook/console_settings.bluebook +3 -3
- data/lib/hecks/language/bluebook/vocabulary.bluebook +16 -0
- data/lib/hecks/language/world/world.bluebook +32 -2
- data/lib/hecks/mcp_door_scope.rb +176 -0
- data/lib/hecks/ports/key_vault.rb +1 -1
- data/lib/hecks/ports/persistence/append_only.rb +1 -1
- data/lib/hecks/ports/persistence/binding_policy.rb +16 -5
- data/lib/hecks/ports/persistence/plugins/era/era_check.rb +3 -2
- data/lib/hecks/ports/persistence/plugins/era/era_guard.rb +2 -2
- data/lib/hecks/ports/persistence/plugins/era/era_tamper.rb +2 -2
- data/lib/hecks/ports/persistence/plugins/era/expected_era.rb +132 -0
- data/lib/hecks/ports/persistence/plugins/era/lineage.rb +1 -1
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +2 -2
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/head_compiler.rb +1 -1
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +1 -1
- data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +13 -7
- data/lib/hecks/ports/persistence/plugins/era.rb +7 -4
- data/lib/hecks/ports/persistence/postgres_dump/connection.rb +73 -0
- data/lib/hecks/ports/persistence/postgres_dump.rb +125 -0
- data/lib/hecks/ports/persistence/repository_factory.rb +2 -2
- data/lib/hecks/ports/persistence/state_codec.rb +1 -1
- data/lib/hecks/ports/persistence.rb +4 -0
- data/lib/hecks/projections/deploy/fargate/assembly.rb +193 -0
- data/lib/hecks/projections/deploy/fargate/cdn.rb +318 -0
- data/lib/hecks/projections/deploy/fargate/cdn_default.rb +87 -0
- data/lib/hecks/projections/deploy/fargate/check.rb +172 -0
- data/lib/hecks/projections/deploy/fargate/containers.rb +412 -0
- data/lib/hecks/projections/deploy/fargate/extras.rb +268 -0
- data/lib/hecks/projections/deploy/fargate/monitoring.rb +448 -0
- data/lib/hecks/projections/deploy/fargate/settings.rb +235 -0
- data/lib/hecks/projections/deploy/fargate/yaml.rb +116 -0
- data/lib/hecks/projections/deploy/fargate.rb +188 -139
- data/lib/hecks/projections/deploy/lambda.rb +8 -8
- data/lib/hecks/projections/deploy/preview/access_yaml.rb +145 -0
- data/lib/hecks/projections/deploy/preview/containers.rb +218 -0
- data/lib/hecks/projections/deploy/preview/preview.sh.erb +446 -0
- data/lib/hecks/projections/deploy/preview/routing_yaml.rb +216 -0
- data/lib/hecks/projections/deploy/preview/script.rb +40 -0
- data/lib/hecks/projections/deploy/preview/settings.rb +140 -0
- data/lib/hecks/projections/deploy/preview/task_yaml.rb +215 -0
- data/lib/hecks/projections/deploy/preview/template.rb +247 -0
- data/lib/hecks/projections/deploy/preview/yaml_text.rb +48 -0
- data/lib/hecks/projections/deploy/preview.rb +103 -0
- data/lib/hecks/projections/deploy/scripts/deploy-service.sh.tmpl +182 -0
- data/lib/hecks/projections/deploy/scripts/expected-era.header +17 -0
- data/lib/hecks/projections/deploy/scripts/hosting.mk.tmpl +54 -0
- data/lib/hecks/projections/deploy/scripts/settings.rb +114 -0
- data/lib/hecks/projections/deploy/scripts/smoke-after-deploy.sh.tmpl +232 -0
- data/lib/hecks/projections/deploy/scripts.rb +171 -0
- data/lib/hecks/projections/deploy/smoke/harness.js +366 -0
- data/lib/hecks/projections/deploy/smoke/workflow.yml.tmpl +56 -0
- data/lib/hecks/projections/deploy/smoke.rb +159 -0
- data/lib/hecks/projections/deploy/template_diff/comparer.rb +231 -0
- data/lib/hecks/projections/deploy/template_diff/loader.rb +105 -0
- data/lib/hecks/projections/deploy/template_diff.rb +173 -0
- data/lib/hecks/projections/glossary/sensitivity.rb +1 -1
- data/lib/hecks/projections/reference.rb +7 -1
- data/lib/hecks/projections.rb +2 -0
- data/lib/hecks/projector/exporter.rb +4 -4
- data/lib/hecks/projector/target.rb +1 -1
- data/lib/hecks/release/gem_pin/published_versions.rb +58 -0
- data/lib/hecks/release/gem_pin.rb +112 -0
- data/lib/hecks/release/runner/ci_publisher.rb +96 -0
- data/lib/hecks/release/runner/commands.rb +68 -0
- data/lib/hecks/release/runner/console.rb +52 -0
- data/lib/hecks/release/runner/gem_publisher.rb +44 -0
- data/lib/hecks/release/runner/git.rb +48 -0
- data/lib/hecks/release/runner/npm_publisher.rb +89 -0
- data/lib/hecks/release/runner/preflight.rb +119 -0
- data/lib/hecks/release/runner/published.rb +55 -0
- data/lib/hecks/release/runner/tagger.rb +87 -0
- data/lib/hecks/release/runner.rb +244 -0
- data/lib/hecks/router/namespace_installer.rb +1 -1
- data/lib/hecks/runtime/command_interpreter.rb +3 -3
- data/lib/hecks/runtime/command_rules/admissibility.rb +2 -2
- data/lib/hecks/runtime/command_rules/arithmetic.rb +2 -2
- data/lib/hecks/runtime/entity_element.rb +1 -1
- data/lib/hecks/runtime/entity_interpreter.rb +1 -1
- data/lib/hecks/runtime/invocation.rb +1 -1
- data/lib/hecks/runtime/loader.rb +27 -3
- data/lib/hecks/runtime/outbox.rb +1 -1
- data/lib/hecks/runtime/reaction_invocation.rb +3 -3
- data/lib/hecks/runtime/read_model_interpreter.rb +37 -15
- data/lib/hecks/runtime/reference_hop.rb +4 -4
- data/lib/hecks/runtime/registry/saga_persistence.rb +8 -2
- data/lib/hecks/runtime/registry/verification.rb +15 -13
- data/lib/hecks/runtime/registry/world_defaults.rb +106 -0
- data/lib/hecks/runtime/registry.rb +22 -13
- data/lib/hecks/runtime/saga_interpreter.rb +1 -1
- data/lib/hecks/runtime/tenant_check.rb +1 -1
- data/lib/hecks/runtime/value/coercion.rb +3 -3
- data/lib/hecks/runtime/value/entity_list_coercion.rb +1 -1
- data/lib/hecks/storehouse.rb +18 -12
- data/lib/hecks/vendoring/git_environment.rb +35 -0
- data/lib/hecks/vendoring/git_source.rb +102 -0
- data/lib/hecks/vendoring.rb +102 -0
- data/lib/hecks/version.rb +1 -1
- data/lib/hecks/vocabulary.rb +5 -0
- data/lib/hecks.rb +8 -2
- metadata +72 -58
- data/lib/hecks/bench/cli.rb +0 -74
- data/lib/hecks/bench/environment.rb +0 -100
- data/lib/hecks/bench/postgres_probe.rb +0 -41
- data/lib/hecks/bench/report.rb +0 -116
- data/lib/hecks/bench/ruby_runner.rb +0 -151
- data/lib/hecks/bench/run.rb +0 -55
- data/lib/hecks/bench/rust_runner.rb +0 -165
- data/lib/hecks/bench/stats.rb +0 -75
- data/lib/hecks/bench/suite.rb +0 -172
- data/lib/hecks/bench/workload.rb +0 -137
- data/lib/hecks/bench.rb +0 -28
- data/lib/hecks/codemod.rb +0 -443
- data/lib/hecks/corpus.rb +0 -479
- data/lib/hecks/doc/reference.rb +0 -585
- data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +0 -684
- data/lib/hecks/fuzzing/combination_miner.rb +0 -178
- data/lib/hecks/fuzzing/concurrent_dispatch.rb +0 -469
- data/lib/hecks/fuzzing/coverage_campaign.rb +0 -161
- data/lib/hecks/fuzzing/differential.rb +0 -192
- data/lib/hecks/fuzzing/domain_generator.rb +0 -871
- data/lib/hecks/fuzzing/era_boundary.rb +0 -154
- data/lib/hecks/fuzzing/form_census.rb +0 -285
- data/lib/hecks/fuzzing/generated_domain_check.rb +0 -171
- data/lib/hecks/fuzzing/invalid_value_generator.rb +0 -125
- data/lib/hecks/fuzzing/isolated_boot.rb +0 -604
- data/lib/hecks/fuzzing/nondeterministic.rb +0 -79
- data/lib/hecks/fuzzing/persistence_parity.rb +0 -253
- data/lib/hecks/fuzzing/properties/corrections.rb +0 -125
- data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +0 -688
- data/lib/hecks/fuzzing/properties/guards.rb +0 -398
- data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +0 -399
- data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +0 -137
- data/lib/hecks/fuzzing/properties/outbox.rb +0 -180
- data/lib/hecks/fuzzing/properties/querying.rb +0 -260
- data/lib/hecks/fuzzing/properties.rb +0 -267
- data/lib/hecks/fuzzing/qa_settings.rb +0 -164
- data/lib/hecks/fuzzing/replay.rb +0 -930
- data/lib/hecks/fuzzing/rotation_priority.rb +0 -114
- data/lib/hecks/fuzzing/rust_gap_manifest.rb +0 -139
- data/lib/hecks/fuzzing/self_consistency.rb +0 -825
- data/lib/hecks/fuzzing/sequence_generator/adversary.rb +0 -532
- data/lib/hecks/fuzzing/sequence_generator/catalog.rb +0 -171
- data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +0 -127
- data/lib/hecks/fuzzing/sequence_generator/picker.rb +0 -116
- data/lib/hecks/fuzzing/sequence_generator/query_binding.rb +0 -138
- data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +0 -344
- data/lib/hecks/fuzzing/sequence_generator.rb +0 -311
- data/lib/hecks/fuzzing/shrinker.rb +0 -309
- data/lib/hecks/fuzzing/structural_skips.rb +0 -53
- data/lib/hecks/fuzzing/sweep_depth.rb +0 -61
- data/lib/hecks/fuzzing/target_capabilities.rb +0 -255
- data/lib/hecks/fuzzing/value_generator.rb +0 -340
- data/lib/hecks/fuzzing.rb +0 -23
- data/lib/hecks/grammar/evolve.rb +0 -524
- data/lib/hecks/query_ir.rb +0 -482
|
@@ -56,7 +56,13 @@ module Hecks
|
|
|
56
56
|
end
|
|
57
57
|
end
|
|
58
58
|
|
|
59
|
-
#
|
|
59
|
+
# Every domain whose sagas can persist somewhere: one with a hecksagon, or one a
|
|
60
|
+
# world's `default_adapter` binds though it has none.
|
|
61
|
+
#
|
|
62
|
+
# @return [Array<String>] the domain names, hecksagon domains first
|
|
63
|
+
def saga_domains = @hecksagons.keys | @bluebooks.keys.select { |domain| default_adapter_for(domain) }
|
|
64
|
+
|
|
65
|
+
# Walks every loaded domain in `saga_domains`, repopulating `saga_instances` from
|
|
60
66
|
# whatever `saga_persistence(domain)` resolves to — a real store
|
|
61
67
|
# for a domain whose adapter answers the capability, `each_saga`
|
|
62
68
|
# yielding real rows; `NULL_SAGA_STORE`'s own `each_saga` for
|
|
@@ -79,7 +85,7 @@ module Hecks
|
|
|
79
85
|
#
|
|
80
86
|
# @return [Hecks::Runtime::Registry] self
|
|
81
87
|
def rehydrate_sagas!
|
|
82
|
-
|
|
88
|
+
saga_domains.each do |domain|
|
|
83
89
|
saga_persistence(domain).each_saga do |process_manager, correlation, state, memory, completed_compensations = []|
|
|
84
90
|
pending = memory.delete(SAGA_PENDING_DISPATCH_KEY)
|
|
85
91
|
@saga_instances[process_manager][correlation] =
|
|
@@ -17,10 +17,12 @@ module Hecks
|
|
|
17
17
|
# @raise [Runtime::WiringError] if a bind names an undeclared aggregate, an
|
|
18
18
|
# adapter cannot satisfy its port's verb or declared `answers`, a world
|
|
19
19
|
# setting names a field its adapter does not declare, the default adapter
|
|
20
|
-
# is unusable, a
|
|
21
|
-
#
|
|
20
|
+
# is unusable, a world's `default_adapter` is not a persistence adapter, a
|
|
21
|
+
# command declares a role with no authorization provider attached, or a
|
|
22
|
+
# membership chapter is loaded with no identity chapter
|
|
22
23
|
def verify!
|
|
23
24
|
verify_default_adapter!
|
|
25
|
+
verify_world_defaults!
|
|
24
26
|
verify_singleton_port_answers!
|
|
25
27
|
refuse_cross_package_bluebook_merge!
|
|
26
28
|
refuse_membership_without_identity!
|
|
@@ -270,7 +272,7 @@ module Hecks
|
|
|
270
272
|
# types the consumer inlines. `uses_embryonaut_bluebook` only loads
|
|
271
273
|
# the package's `.bluebook` files — persistence, Governance, and the
|
|
272
274
|
# anti-corruption layer (`translates` field mapping) live on a named
|
|
273
|
-
# sibling hecksagon the
|
|
275
|
+
# sibling hecksagon the consumer must declare. Without that hexagon
|
|
274
276
|
# the package has no wiring of its own, and cross-context field
|
|
275
277
|
# mapping has nowhere to be written. Breaking in 2.0: attaching a
|
|
276
278
|
# vendored chapter without `Hecks.hecksagon "PackageName"` refuses
|
|
@@ -309,7 +311,7 @@ module Hecks
|
|
|
309
311
|
# An explicit `bounded` mark on a consumer chapter always needs an
|
|
310
312
|
# ACL — at least one `translates` block. Any field can be mapped;
|
|
311
313
|
# the BC does not list which. `uses_framework` / `uses_embryonaut_bluebook`
|
|
312
|
-
# mark the
|
|
314
|
+
# mark the attached chapter bounded (module wrap, no Object shortcut)
|
|
313
315
|
# and require the sibling hecksagon above; they do not require a
|
|
314
316
|
# `translates` on that sibling unless the consumer also wrote `bounded`.
|
|
315
317
|
# rust/host Google sign-in reads `ir.json`'s `membership` and
|
|
@@ -391,22 +393,22 @@ module Hecks
|
|
|
391
393
|
bluebook_ir.aggregates.flat_map { |aggregate| aggregate.commands + aggregate.entities.flat_map(&:commands) }
|
|
392
394
|
end
|
|
393
395
|
|
|
394
|
-
#
|
|
396
|
+
# Two unrelated packages, one chapter name by coincidence — the
|
|
395
397
|
# real risk `Registry#bluebook_sources` exists to catch (found
|
|
396
398
|
# live: a stale `vendor/hecksagain` fork's own copy of Governance/
|
|
397
399
|
# Identity/Deploy, still reachable on 4 consuming apps' own load
|
|
398
400
|
# paths alongside the real gem). `BluebookBuilder.build`'s own
|
|
399
|
-
# accumulation (several files declaring the
|
|
400
|
-
#
|
|
401
|
+
# accumulation (several files declaring the same chapter name on
|
|
402
|
+
# purpose — `lib/hecks/language/bluebook/*.bluebook` all open
|
|
401
403
|
# `Hecks.bluebook "Bluebook"`) is never touched here — that merge
|
|
402
|
-
# stays unconditional, checked only
|
|
404
|
+
# stays unconditional, checked only after every file has loaded,
|
|
403
405
|
# the same "check the merged final result once" timing
|
|
404
406
|
# `refuse_ungoverned_roles!` already uses and for the same reason
|
|
405
407
|
# (a check against an incomplete load can never see the real
|
|
406
408
|
# shape). What distinguishes intentional accumulation from
|
|
407
|
-
# coincidence is
|
|
409
|
+
# coincidence is package root, not file identity: files a real
|
|
408
410
|
# gemspec or a `vendor/` boundary already treats as one unit are
|
|
409
|
-
# expected to share a name; files from two
|
|
411
|
+
# expected to share a name; files from two different roots never
|
|
410
412
|
# legitimately do.
|
|
411
413
|
def refuse_cross_package_bluebook_merge!
|
|
412
414
|
@bluebook_sources.each do |name, paths|
|
|
@@ -422,9 +424,9 @@ module Hecks
|
|
|
422
424
|
end
|
|
423
425
|
end
|
|
424
426
|
|
|
425
|
-
#
|
|
426
|
-
# gemspec (this
|
|
427
|
-
# it), or a bare `vendor/` path component, treated as its
|
|
427
|
+
# The nearest boundary a path already belongs to — a real
|
|
428
|
+
# gemspec (this is a package, whatever depends on it or vendors
|
|
429
|
+
# it), or a bare `vendor/` path component, treated as its own
|
|
428
430
|
# root regardless of what gemspec might sit above it: vendored
|
|
429
431
|
# code should never be considered "the same package" as whatever
|
|
430
432
|
# it's vendored into, even when nothing else marks the boundary.
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
module Hecks
|
|
2
|
+
module Runtime
|
|
3
|
+
class Registry
|
|
4
|
+
# **World defaults** — the `default_database` and `default_adapter` words a
|
|
5
|
+
# `.world` can declare once for every chapter a boot loaded, resolved against
|
|
6
|
+
# the registry's own worlds. Included into Registry; the callers are the
|
|
7
|
+
# persistence binding policy (`default_adapter_for`), every reader of a bind's
|
|
8
|
+
# world settings (`binding_settings`), and `verify!` (`verify_world_defaults!`).
|
|
9
|
+
#
|
|
10
|
+
# **Resolution order**, most specific first — nothing here ever overrides a
|
|
11
|
+
# chapter's own explicit declaration:
|
|
12
|
+
#
|
|
13
|
+
# 1. the chapter's own `persisted_by(...) { database ... }` block, and the
|
|
14
|
+
# hecksagon bind (aggregate-specific, then domain-level) that names its adapter;
|
|
15
|
+
# 2. the default declared by the chapter's own world;
|
|
16
|
+
# 3. the default declared by the project's world — the world of the first
|
|
17
|
+
# chapter the boot loaded, the same convention `Loader#dispatcher_for` uses
|
|
18
|
+
# to find the domain a boot targets;
|
|
19
|
+
# 4. today's behavior: no database setting, and the framework's in-memory
|
|
20
|
+
# adapter for a chapter with no hecksagon.
|
|
21
|
+
#
|
|
22
|
+
# A default is resolved when a bind's settings or binding are asked for, never
|
|
23
|
+
# written into a chapter's world or hecksagon, so a chapter's own declarations
|
|
24
|
+
# stay exactly what its files say and a hecksagon-less chapter still trips the
|
|
25
|
+
# sibling-hecksagon gates the way it always did.
|
|
26
|
+
module WorldDefaults
|
|
27
|
+
# The settings a chapter's world gives one bind, with the project's default
|
|
28
|
+
# database filled in where the chapter left it out.
|
|
29
|
+
#
|
|
30
|
+
# The default database only ever reaches a persistence bind whose adapter
|
|
31
|
+
# declares a `database` field — a projection or an adapter without one is
|
|
32
|
+
# untouched. A `database` the chapter's own settings name wins; every other
|
|
33
|
+
# setting the chapter declared rides along unchanged.
|
|
34
|
+
#
|
|
35
|
+
# @param domain [String, Symbol] the domain whose world is asked
|
|
36
|
+
# @param verb [String, Symbol] the bind's port verb, such as `"persisted_by"`
|
|
37
|
+
# @param adapter [String, Symbol] the bind's adapter name
|
|
38
|
+
# @return [Hash{Symbol => Object}] the chapter's own settings for the bind, plus
|
|
39
|
+
# `:database` from the default when one applies; `{}` when there is nothing
|
|
40
|
+
def binding_settings(domain, verb, adapter)
|
|
41
|
+
declared = world(domain)&.for_binding(verb, adapter) || {}
|
|
42
|
+
database = default_database_for(domain)
|
|
43
|
+
return declared unless database && default_database_applies?(verb, adapter)
|
|
44
|
+
|
|
45
|
+
{ adapter: adapter.to_s, database: database }.merge(declared)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# The persistence adapter a chapter's aggregates fall back to when their
|
|
49
|
+
# hecksagon does not bind them.
|
|
50
|
+
#
|
|
51
|
+
# @param domain [String, Symbol] the domain to resolve the default for
|
|
52
|
+
# @return [String, nil] the chapter's own world's `default_adapter`, else the
|
|
53
|
+
# project's; `nil` when neither declares one
|
|
54
|
+
def default_adapter_for(domain) = world_default(domain, :default_adapter)
|
|
55
|
+
|
|
56
|
+
# The database connection a chapter's database-taking persistence adapter uses
|
|
57
|
+
# when its own settings name none.
|
|
58
|
+
#
|
|
59
|
+
# @param domain [String, Symbol] the domain to resolve the default for
|
|
60
|
+
# @return [String, nil] the chapter's own world's `default_database`, else the
|
|
61
|
+
# project's; `nil` when neither declares one
|
|
62
|
+
def default_database_for(domain) = world_default(domain, :default_database)
|
|
63
|
+
|
|
64
|
+
# Refuses a `default_adapter` that no persistence adapter answers to.
|
|
65
|
+
#
|
|
66
|
+
# @return [Runtime::Registry] self
|
|
67
|
+
# @raise [Runtime::WiringError] if a world's `default_adapter` names an unknown
|
|
68
|
+
# adapter, one that is not a persistence adapter, or one with no Ruby
|
|
69
|
+
# implementation
|
|
70
|
+
def verify_world_defaults!
|
|
71
|
+
@worlds.each_value do |declared|
|
|
72
|
+
name = declared.default_adapter
|
|
73
|
+
next unless name
|
|
74
|
+
|
|
75
|
+
verify_default_adapter_named!(declared.domain, name)
|
|
76
|
+
end
|
|
77
|
+
self
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
private
|
|
81
|
+
|
|
82
|
+
# The chapter's own world answers first; the project's world fills the gap.
|
|
83
|
+
def world_default(domain, field)
|
|
84
|
+
world(domain)&.public_send(field) || project_world&.public_send(field)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# The world of the first chapter this registry loaded — the target of a boot, ahead of
|
|
88
|
+
# any framework member it attaches.
|
|
89
|
+
def project_world = world(@bluebooks.keys.first || @worlds.keys.first)
|
|
90
|
+
|
|
91
|
+
def default_database_applies?(verb, adapter)
|
|
92
|
+
verb.to_s == Ports::Persistence::VERB && @adapters[adapter.to_s]&.declares?(:database)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def verify_default_adapter_named!(domain, name)
|
|
96
|
+
check_verb(Bluebook::Bind.new(aggregate: "(default)", verb: Ports::Persistence::VERB, adapter: name))
|
|
97
|
+
adapter_class(name)
|
|
98
|
+
rescue WiringError => e
|
|
99
|
+
raise WiringError,
|
|
100
|
+
"#{domain}'s world declares default_adapter #{name.inspect}, which is not a usable " \
|
|
101
|
+
"persistence adapter: #{e.message}"
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
require_relative "registry/verification"
|
|
2
2
|
require_relative "registry/saga_persistence"
|
|
3
|
+
require_relative "registry/world_defaults"
|
|
3
4
|
require_relative "outbox"
|
|
4
5
|
require_relative "../naming"
|
|
5
6
|
|
|
@@ -10,10 +11,12 @@ module Hecks
|
|
|
10
11
|
# The collections a boot gathers — bluebooks, hexagons, ports, adapters,
|
|
11
12
|
# worlds, the logs — and how a repository is resolved from them. The
|
|
12
13
|
# wiring gate lives in registry/verification.rb, saga persistence
|
|
13
|
-
# resolution in registry/saga_persistence.rb
|
|
14
|
+
# resolution in registry/saga_persistence.rb, and a `.world`'s project-wide
|
|
15
|
+
# defaults in registry/world_defaults.rb.
|
|
14
16
|
class Registry
|
|
15
17
|
include Verification
|
|
16
18
|
include SagaPersistence
|
|
19
|
+
include WorldDefaults
|
|
17
20
|
|
|
18
21
|
attr_reader :root, :bluebooks, :hecksagons, :ports, :adapters, :worlds, :event_log,
|
|
19
22
|
:reaction_log, :saga_log, :saga_instances, :translations, :saga_mutex,
|
|
@@ -36,7 +39,7 @@ module Hecks
|
|
|
36
39
|
@event_log = []
|
|
37
40
|
@reaction_log = []
|
|
38
41
|
@saga_log = []
|
|
39
|
-
# A
|
|
42
|
+
# A declarative fact, not yet a dispatched one — `AggregateDoor#
|
|
40
43
|
# mark_sensitive` (called from a `.hecksagon` file, the same way
|
|
41
44
|
# `port`/`persisted_by` already are) appends here at
|
|
42
45
|
# hecksagon-build time; `Runtime::Loader.boot`'s own post-dispatcher
|
|
@@ -159,19 +162,23 @@ module Hecks
|
|
|
159
162
|
# @return [Bluebook::Chapter] `item`, unchanged
|
|
160
163
|
def add_bluebook(item) = @bluebooks[item.name] = item
|
|
161
164
|
|
|
162
|
-
#
|
|
165
|
+
# Provenance, side-channel — which real `.bluebook` file(s)
|
|
163
166
|
# contributed to a chapter name, never part of the exported IR (a
|
|
164
167
|
# boot-time loading fact, not a domain fact) and never Rust-mirrored
|
|
165
168
|
# (the same "additive, Ruby-only" shape `@translations` above already
|
|
166
|
-
# is). Legitimate accumulation (several files declaring the
|
|
169
|
+
# is). Legitimate accumulation (several files declaring the same
|
|
167
170
|
# chapter name on purpose — `lib/hecks/language/bluebook/*.bluebook`
|
|
168
171
|
# all open `Hecks.bluebook "Bluebook"`) pushes more than one path
|
|
169
172
|
# here too; that alone is not a problem. What this exists to let
|
|
170
173
|
# `refuse_cross_package_bluebook_merge!` (registry/verification.rb)
|
|
171
|
-
# catch is
|
|
174
|
+
# catch is two unrelated packages accumulating into the same name by
|
|
172
175
|
# coincidence — a stale vendored fork's own copy of a real gem's
|
|
173
176
|
# chapter, still reachable on the load path, silently merging its
|
|
174
177
|
# aggregates into the real one via this exact accumulation mechanism.
|
|
178
|
+
#
|
|
179
|
+
# @param name [String, Symbol] the chapter name the file contributed to
|
|
180
|
+
# @param path [String] the `.bluebook` file that declared it
|
|
181
|
+
# @return [Array<String>] every path recorded for that chapter, `path` last
|
|
175
182
|
def record_bluebook_source(name, path)
|
|
176
183
|
(@bluebook_sources[name.to_s] ||= []) << path
|
|
177
184
|
end
|
|
@@ -257,7 +264,7 @@ module Hecks
|
|
|
257
264
|
# entry into a real `Privacy::Marking.Mark` once a dispatcher exists.
|
|
258
265
|
#
|
|
259
266
|
# @param domain [String] the marked attribute's own aggregate FQN, e.g.
|
|
260
|
-
# `"
|
|
267
|
+
# `"Site::Registration"`
|
|
261
268
|
# @param attribute_path [String] the dotted path within that aggregate, e.g.
|
|
262
269
|
# `"attendee.medications"`
|
|
263
270
|
# @param category [String] the marking's own sensitivity category, e.g. `"phi"`
|
|
@@ -593,7 +600,7 @@ module Hecks
|
|
|
593
600
|
# concatenated, `base` then `overlay`
|
|
594
601
|
def merge_hecksagons(base, overlay)
|
|
595
602
|
# Same-name blocks concatenate regardless of which file they came
|
|
596
|
-
# from (`
|
|
603
|
+
# from (`site.hecksagon`, `context_map.hecksagon`, an
|
|
597
604
|
# environment overlay). Order-independent: list facts uniq, so
|
|
598
605
|
# loading context_map before or after the domain file is the same
|
|
599
606
|
# merged hecksagon. Binds stay concatenated (BindingPolicy still
|
|
@@ -610,8 +617,8 @@ module Hecks
|
|
|
610
617
|
end
|
|
611
618
|
|
|
612
619
|
# Recovered and generalized — see `add_world`'s own comment. `realm`/
|
|
613
|
-
# `latest` are scalars, so the overlay's
|
|
614
|
-
# else the base's survives; `settings` is a shallow merge keyed by
|
|
620
|
+
# `latest`, `default_database` and `default_adapter` are scalars, so the overlay's
|
|
621
|
+
# value wins when present, else the base's survives; `settings` is a shallow merge keyed by
|
|
615
622
|
# verb (and `"verb:adapter"`) — an overlay entry for a key the base
|
|
616
623
|
# also declares replaces that key's whole resolved hash (the same
|
|
617
624
|
# all-or-nothing shape `WorldBuilder#method_missing` already builds
|
|
@@ -623,10 +630,12 @@ module Hecks
|
|
|
623
630
|
# present, and `settings` shallow-merged, `overlay`'s keys winning
|
|
624
631
|
def merge_worlds(base, overlay)
|
|
625
632
|
Bluebook::World.new(
|
|
626
|
-
domain:
|
|
627
|
-
realm:
|
|
628
|
-
latest:
|
|
629
|
-
settings:
|
|
633
|
+
domain: base.domain,
|
|
634
|
+
realm: overlay.realm || base.realm,
|
|
635
|
+
latest: overlay.latest || base.latest,
|
|
636
|
+
settings: base.settings.merge(overlay.settings),
|
|
637
|
+
default_database: overlay.default_database || base.default_database,
|
|
638
|
+
default_adapter: overlay.default_adapter || base.default_adapter
|
|
630
639
|
)
|
|
631
640
|
end
|
|
632
641
|
end
|
|
@@ -425,7 +425,7 @@ module Hecks
|
|
|
425
425
|
#
|
|
426
426
|
# A compensation that is itself refused does not unwind again, and needs no
|
|
427
427
|
# flag to stop it: the state moves to the compensating leg's to_state before
|
|
428
|
-
# its dispatches run, so a second refusal finds the instance
|
|
428
|
+
# its dispatches run, so a second refusal finds the instance already out of
|
|
429
429
|
# from_state and records that instead. The check is the guard.
|
|
430
430
|
def unwind(process_manager, event, instance, correlation, domain)
|
|
431
431
|
return unless instance && process_manager.handles?(REFUSED)
|
|
@@ -38,7 +38,7 @@ module Hecks
|
|
|
38
38
|
# search_path` — proven for real, not assumed, by
|
|
39
39
|
# tenant_isolation_spec.rb. Plain Postgres (no schema story) and D1
|
|
40
40
|
# (no schema-equivalent at all — see world.bluebook's own comment on
|
|
41
|
-
# the
|
|
41
|
+
# the D1 tradeoff) answer false, or don't answer at all,
|
|
42
42
|
# which this module treats identically to false.
|
|
43
43
|
module TenantCheck
|
|
44
44
|
module_function
|
|
@@ -916,11 +916,11 @@ module Hecks
|
|
|
916
916
|
# C3.8 — the boundary check for an attribute whose type is a bare
|
|
917
917
|
# primitive rather than a value object: `Integer`/`Float` by exact
|
|
918
918
|
# numeric class (`NUMERIC`), `String`/booleans by rejecting only a
|
|
919
|
-
# composite shape (`COMPOSITE_SHAPES`) —
|
|
919
|
+
# composite shape (`COMPOSITE_SHAPES`) — a looser test than
|
|
920
920
|
# `check_scalar_shapes` holds a value object's own `String` field to
|
|
921
|
-
#
|
|
921
|
+
# (QualityControl BUG#125 has that one also refuse
|
|
922
922
|
# a non-string scalar; a bare `String` argument here still admits
|
|
923
|
-
# any other scalar
|
|
923
|
+
# any other scalar — a bare-primitive
|
|
924
924
|
# attribute was never part of BUG#125's own investigation or fix,
|
|
925
925
|
# and whether it needs the same tightening, and against what real
|
|
926
926
|
# Judge dependency if any, is still open); worded by the same
|
|
@@ -27,7 +27,7 @@ module Hecks
|
|
|
27
27
|
# because coercion has to resolve value objects, and only the
|
|
28
28
|
# root answers `.value_object` at all (Entity's own header
|
|
29
29
|
# comment: an entity must not answer to it, or `Value.
|
|
30
|
-
# for_attribute` could
|
|
30
|
+
# for_attribute` could not tell a piece from a head). So
|
|
31
31
|
# a nested entity — Dispatch, inside Handler — is not a direct
|
|
32
32
|
# child of the root the way Handler itself is, and a plain
|
|
33
33
|
# `aggregate.entities.find` stops one level short of it.
|
data/lib/hecks/storehouse.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
require "json"
|
|
2
2
|
require "fileutils"
|
|
3
3
|
require "time"
|
|
4
|
+
require_relative "cache_dir"
|
|
4
5
|
require_relative "facade/command_request"
|
|
5
6
|
require_relative "facade/json_door"
|
|
6
7
|
require_relative "naming"
|
|
@@ -8,7 +9,7 @@ require_relative "projector"
|
|
|
8
9
|
require_relative "runtime/errors"
|
|
9
10
|
|
|
10
11
|
module Hecks
|
|
11
|
-
# **The bus, not a door** — `docs/hecks-survey-what-we-wish-we-had.md` and
|
|
12
|
+
# **The bus, not a door** — `docs/archive/hecks-survey-what-we-wish-we-had.md` and
|
|
12
13
|
# `docs/future-features.md` both name the sibling project's own
|
|
13
14
|
# "Storehouse" the single highest-priority gap this repo had: "no
|
|
14
15
|
# per-command tool... the bluebook is the contract, the [door] just
|
|
@@ -90,19 +91,24 @@ module Hecks
|
|
|
90
91
|
module Storehouse
|
|
91
92
|
module_function
|
|
92
93
|
|
|
93
|
-
# The same closed set `docs/hecks-survey-what-we-wish-we-had.md`'s
|
|
94
|
+
# The same closed set `docs/archive/hecks-survey-what-we-wish-we-had.md`'s
|
|
94
95
|
# `SourceTag` names — who dispatched, not what. Optional: a caller
|
|
95
96
|
# that omits it gets `source: nil` recorded, honestly, rather than a
|
|
96
97
|
# guessed default.
|
|
97
98
|
SOURCE_TAGS = %w[process-manager operator hook sidequest-agent cascade daemon].freeze
|
|
98
99
|
|
|
99
|
-
#
|
|
100
|
-
#
|
|
101
|
-
#
|
|
102
|
-
#
|
|
103
|
-
#
|
|
104
|
-
#
|
|
105
|
-
|
|
100
|
+
# Names the directory holding the bus's own audit trail.
|
|
101
|
+
#
|
|
102
|
+
# The trail is a JSONL file per domain, one line per
|
|
103
|
+
# `dispatch`/`query`/`state`/dry-run call, and `follow` tails it back.
|
|
104
|
+
# It is kept under `Hecks::CacheDir`, not the domain's own directory: this is
|
|
105
|
+
# the bus's record of what was asked of it, not part of the domain's own
|
|
106
|
+
# persisted state, and not the gem's own directory either, which is
|
|
107
|
+
# read-only on an installed gem.
|
|
108
|
+
#
|
|
109
|
+
# @return [String] the absolute path of the `storehouse` directory under
|
|
110
|
+
# the cache root
|
|
111
|
+
def log_root = Hecks::CacheDir.path("storehouse")
|
|
106
112
|
|
|
107
113
|
# The root every `domain:`/`under:` must resolve under — the project
|
|
108
114
|
# directory by default, `HECKS_STOREHOUSE_ROOT` to widen or move it.
|
|
@@ -321,9 +327,9 @@ module Hecks
|
|
|
321
327
|
#
|
|
322
328
|
# @param domain_name [String, Symbol, #to_s] the domain name
|
|
323
329
|
# @return [String] the absolute path to that domain's JSONL log file, under
|
|
324
|
-
# `
|
|
330
|
+
# `log_root`
|
|
325
331
|
def log_path(domain_name)
|
|
326
|
-
File.join(
|
|
332
|
+
File.join(log_root, "#{domain_name.to_s.gsub(/[^A-Za-z0-9_-]/, '_')}.jsonl")
|
|
327
333
|
end
|
|
328
334
|
|
|
329
335
|
# Appends one call's outcome to its domain's audit log.
|
|
@@ -351,7 +357,7 @@ module Hecks
|
|
|
351
357
|
entry = { time: Time.now.utc.iso8601, tool: tool, verb: verb, summary: summary, source: source,
|
|
352
358
|
role: role, actor_id: actor_id,
|
|
353
359
|
ok: outcome[:ok], id: outcome[:id], error: outcome[:error], events: outcome[:events] }.compact
|
|
354
|
-
FileUtils.mkdir_p(
|
|
360
|
+
FileUtils.mkdir_p(log_root)
|
|
355
361
|
File.open(log_path(domain_name), "a") { |f| f.puts(JSON.generate(entry)) }
|
|
356
362
|
rescue StandardError
|
|
357
363
|
nil
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
module Hecks
|
|
2
|
+
module Vendoring
|
|
3
|
+
# The environment a `git` subprocess needs so it resolves its repository
|
|
4
|
+
# from the directory it is pointed at, not from whoever launched Ruby.
|
|
5
|
+
#
|
|
6
|
+
# Git exports `GIT_DIR`, `GIT_INDEX_FILE` and their kin to the hooks it
|
|
7
|
+
# runs (a push from a linked worktree runs `pre-push` this way). Every
|
|
8
|
+
# `git` that a hook's process tree starts inherits them, and they outrank
|
|
9
|
+
# both `git -C` and the working directory: a command aimed at a scratch
|
|
10
|
+
# repository would read, or commit into, the repository that was pushing.
|
|
11
|
+
module GitEnvironment
|
|
12
|
+
# The variables that pin git to one repository, work tree or index.
|
|
13
|
+
INHERITED = %w[
|
|
14
|
+
GIT_DIR GIT_WORK_TREE GIT_INDEX_FILE GIT_PREFIX GIT_COMMON_DIR
|
|
15
|
+
GIT_OBJECT_DIRECTORY GIT_ALTERNATE_OBJECT_DIRECTORIES
|
|
16
|
+
].freeze
|
|
17
|
+
|
|
18
|
+
# An environment hash for `Open3`, `IO.popen` and `system` that unsets
|
|
19
|
+
# every repository-pinning variable and leaves the rest alone.
|
|
20
|
+
#
|
|
21
|
+
# @return [Hash{String => nil}] each inherited variable mapped to nil
|
|
22
|
+
def self.clean
|
|
23
|
+
INHERITED.to_h { |name| [name, nil] }
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Removes the repository-pinning variables from this process's own
|
|
27
|
+
# environment, so every later `git` child starts clean.
|
|
28
|
+
#
|
|
29
|
+
# @return [void]
|
|
30
|
+
def self.scrub!
|
|
31
|
+
INHERITED.each { |name| ENV.delete(name) }
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
require "open3"
|
|
2
|
+
require "tmpdir"
|
|
3
|
+
require_relative "git_environment"
|
|
4
|
+
|
|
5
|
+
module Hecks
|
|
6
|
+
module Vendoring
|
|
7
|
+
# A local git repository read through the `git` executable, never through
|
|
8
|
+
# its working tree.
|
|
9
|
+
#
|
|
10
|
+
# Everything here names a commit and reads what that commit holds, so a
|
|
11
|
+
# shared checkout with uncommitted edits, or a different branch checked
|
|
12
|
+
# out, cannot leak into what gets vendored. Every command runs under
|
|
13
|
+
# {GitEnvironment.clean}, so a `GIT_DIR` inherited from a hook cannot
|
|
14
|
+
# redirect it to some other repository.
|
|
15
|
+
class GitSource
|
|
16
|
+
# @param path [String] a directory that is a git repository (or a
|
|
17
|
+
# worktree of one)
|
|
18
|
+
# @raise [Vendoring::Error] if `path` is not a directory
|
|
19
|
+
def initialize(path)
|
|
20
|
+
@path = File.expand_path(path.to_s)
|
|
21
|
+
raise Error, "no source repository at #{@path}" unless File.directory?(@path)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# @return [String] the absolute path of the repository
|
|
25
|
+
attr_reader :path
|
|
26
|
+
|
|
27
|
+
# Resolves a commit-ish to the full commit id it names.
|
|
28
|
+
#
|
|
29
|
+
# @param ref [String] a branch, tag, abbreviated or full commit id
|
|
30
|
+
# @return [String] the 40-character commit id
|
|
31
|
+
# @raise [Vendoring::Error] if `ref` names no commit in the repository
|
|
32
|
+
def commit(ref)
|
|
33
|
+
out, _err, status = git("rev-parse", "--verify", "--quiet", "#{ref}^{commit}")
|
|
34
|
+
raise Error, "no commit #{ref.inspect} in #{@path}" unless status.success?
|
|
35
|
+
|
|
36
|
+
out.strip
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Lists tag names matching a shell pattern.
|
|
40
|
+
#
|
|
41
|
+
# @param pattern [String] a `git tag --list` pattern, such as `"payments-v*"`
|
|
42
|
+
# @return [Array<String>] the matching tag names, in git's own order
|
|
43
|
+
def tags(pattern)
|
|
44
|
+
out, _err, status = git("tag", "--list", pattern)
|
|
45
|
+
status.success? ? out.lines.map(&:chomp) : []
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Reads one file as a commit holds it.
|
|
49
|
+
#
|
|
50
|
+
# @param ref [String] the commit-ish to read from
|
|
51
|
+
# @param file [String] a repository-relative path
|
|
52
|
+
# @return [String, nil] the file's content, or nil when the commit has no such file
|
|
53
|
+
def read(ref, file)
|
|
54
|
+
out, _err, status = git("show", "#{ref}:#{file}")
|
|
55
|
+
status.success? ? out : nil
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Lists the files directly inside a directory of a commit.
|
|
59
|
+
#
|
|
60
|
+
# Subdirectories are left out on purpose: a vendored package carries the
|
|
61
|
+
# top-level files of its subtree and nothing nested beneath.
|
|
62
|
+
#
|
|
63
|
+
# @param ref [String] the commit-ish to read from
|
|
64
|
+
# @param subtree [String] a repository-relative directory, such as `"payments/bluebook"`
|
|
65
|
+
# @param glob [String] a `File.fnmatch` pattern the file's own name must match
|
|
66
|
+
# @return [Array<String>] repository-relative paths, sorted
|
|
67
|
+
def files(ref, subtree, glob = "*")
|
|
68
|
+
out, _err, status = git("ls-tree", "-r", "--name-only", ref, "--", subtree)
|
|
69
|
+
return [] unless status.success?
|
|
70
|
+
|
|
71
|
+
out.lines.map(&:chomp).select do |file|
|
|
72
|
+
File.dirname(file) == subtree && File.fnmatch(glob, File.basename(file))
|
|
73
|
+
end.sort
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Extracts named files of a commit into a directory with `git archive`,
|
|
77
|
+
# keeping their repository-relative paths.
|
|
78
|
+
#
|
|
79
|
+
# @param ref [String] the commit-ish to export
|
|
80
|
+
# @param files [Array<String>] repository-relative paths to export
|
|
81
|
+
# @param into [String] an existing directory to extract into
|
|
82
|
+
# @return [void]
|
|
83
|
+
# @raise [Vendoring::Error] if `git archive` or `tar` fails
|
|
84
|
+
def export(ref, files, into)
|
|
85
|
+
Dir.mktmpdir("hecks-archive") do |scratch|
|
|
86
|
+
tarball = File.join(scratch, "export.tar")
|
|
87
|
+
run!("git archive", "git", "-C", @path, "archive", "--format=tar", "--output=#{tarball}", ref, "--", *files)
|
|
88
|
+
run!("tar", "tar", "-xf", tarball, "-C", into)
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
private
|
|
93
|
+
|
|
94
|
+
def git(*) = Open3.capture3(GitEnvironment.clean, "git", "-C", @path, *)
|
|
95
|
+
|
|
96
|
+
def run!(label, *command)
|
|
97
|
+
_out, err, status = Open3.capture3(GitEnvironment.clean, *command)
|
|
98
|
+
raise Error, "#{label} failed: #{err.lines.first.to_s.strip}" unless status.success?
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|