hecks 3.0.4 → 3.1.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 +25 -7
- data/lib/hecks/adapters/driven/lambda/client.rb +1 -1
- data/lib/hecks/adapters/driven/lambda.rb +1 -1
- data/lib/hecks/adapters/driven/tenant_provisioner.rb +2 -2
- data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +59 -32
- data/lib/hecks/bluebook/hexagon.rb +45 -25
- data/lib/hecks/bluebook/model_check.rb +7 -7
- data/lib/hecks/chapters.rb +31 -3
- data/lib/hecks/cli/check_era.rb +1 -1
- data/lib/hecks/cli/console.rb +24 -8
- data/lib/hecks/cli/deploy_template_diff.rb +1 -1
- data/lib/hecks/cli/doc_coverage.rb +1 -1
- data/lib/hecks/cli/domain_stub.rb +128 -0
- data/lib/hecks/cli/interview_draft.rb +246 -0
- data/lib/hecks/cli/model_check.rb +1 -1
- data/lib/hecks/cli/project_cli.rb +69 -18
- data/lib/hecks/cli/reference.rb +2 -1
- data/lib/hecks/cli/refresh_rspec_runtime_baseline.rb +1 -1
- data/lib/hecks/cli/seed_semantics_corpus.rb +38 -9
- data/lib/hecks/cli/spec_example.rb +1 -1
- data/lib/hecks/cli/stress_concurrency_specs.rb +14 -5
- data/lib/hecks/corpus.rb +11 -8
- data/lib/hecks/deploy/bluebook/deploy.bluebook +85 -12
- data/lib/hecks/deploy/oidc.json +5 -0
- data/lib/hecks/doc/reference.rb +4 -3
- data/lib/hecks/doors/cli_door.rb +13 -13
- data/lib/hecks/doors/cli_runner.rb +52 -32
- data/lib/hecks/doors/launcher_options.rb +23 -5
- data/lib/hecks/doors/mcp_door.rb +40 -18
- data/lib/hecks/doors/mcp_door_scope.rb +244 -24
- data/lib/hecks/doors.rb +1 -1
- data/lib/hecks/embryonaut_bluebook.rb +2 -2
- data/lib/hecks/forms/banking_presentation.rb +1 -1
- data/lib/hecks/framework/bluebook/compliance.bluebook +1 -1
- data/lib/hecks/framework/bluebook/console_settings.bluebook +1 -1
- data/lib/hecks/framework/bluebook/framework.hecksagon +4 -4
- data/lib/hecks/framework.rb +1 -1
- data/lib/hecks/fuzzing/combination_miner.rb +1 -1
- data/lib/hecks/fuzzing/concurrent_dispatch.rb +1 -1
- data/lib/hecks/fuzzing/differential.rb +4 -2
- data/lib/hecks/fuzzing/era_boundary.rb +1 -1
- data/lib/hecks/fuzzing/form_census.rb +2 -2
- data/lib/hecks/fuzzing/generated_domain_check.rb +1 -1
- data/lib/hecks/fuzzing/isolated_boot.rb +10 -3
- data/lib/hecks/fuzzing/persistence_parity.rb +2 -2
- data/lib/hecks/fuzzing/target_capabilities.rb +3 -3
- data/lib/hecks/gate/stages.yml +8 -4
- data/lib/hecks/grammar/grammar.hecksagon +2 -2
- data/lib/hecks/hecks/adapters/deploy_toolchain.rb +4 -4
- data/lib/hecks/hecks/adapters/host_http.rb +4 -3
- data/lib/hecks/hecks/adapters/local_files.rb +59 -0
- data/lib/hecks/hecks/adapters/site_toolchain.adapter +7 -0
- data/lib/hecks/hecks/adapters/site_toolchain.rb +54 -0
- data/lib/hecks/hecks/codebase.bluebook +4 -3
- data/lib/hecks/hecks/context_map.hecksagon +2 -2
- data/lib/hecks/hecks/custodian.bluebook +45 -3
- data/lib/hecks/hecks/hecks.hecksagon +10 -4
- data/lib/hecks/hecks/hecks.world +7 -4
- data/lib/hecks/hecks/site.behaviors +17 -0
- data/lib/hecks/hecks/site.world +10 -0
- data/lib/hecks/language/bluebook/bluebook.hecksagon +1 -1
- data/lib/hecks/language/hecksagon/hecksagon.bluebook +24 -47
- data/lib/hecks/ports/persistence/plugins/era/era_check.rb +4 -4
- data/lib/hecks/ports/persistence/plugins/era/postgres_era.rb +2 -1
- data/lib/hecks/projections/deploy/box/settings.rb +248 -0
- data/lib/hecks/projections/deploy/box/templates/box.yaml.tmpl +267 -0
- data/lib/hecks/projections/deploy/box/templates/deploy-box.sh.tmpl +70 -0
- data/lib/hecks/projections/deploy/box/templates/fetch-secrets.sh +21 -0
- data/lib/hecks/projections/deploy/box/templates/rds.yaml.tmpl +165 -0
- data/lib/hecks/projections/deploy/box/templates/render-compose.sh.tmpl +43 -0
- data/lib/hecks/projections/deploy/box.rb +387 -0
- data/lib/hecks/projections/deploy/fargate/cdn.rb +13 -1
- data/lib/hecks/projections/deploy/fargate.rb +1 -1
- data/lib/hecks/projections/deploy/lambda.rb +11 -10
- data/lib/hecks/projections/deploy/scripts/hosting.mk.tmpl +1 -1
- data/lib/hecks/projections/diagrams.rb +2 -2
- data/lib/hecks/projections/site/edge/behaviours.rb +107 -0
- data/lib/hecks/projections/site/edge/checks.rb +137 -0
- data/lib/hecks/projections/site/edge/pattern.rb +50 -0
- data/lib/hecks/projections/site/edge/rules.rb +128 -0
- data/lib/hecks/projections/site/edge.rb +136 -0
- data/lib/hecks/projections/site/regions.rb +44 -0
- data/lib/hecks/projections/site/routes_ts.rb +284 -0
- data/lib/hecks/projections/site/site_cdn.rb +78 -0
- data/lib/hecks/projections/site/table/checks.rb +124 -0
- data/lib/hecks/projections/site/table/row_builder.rb +155 -0
- data/lib/hecks/projections/site/table.rb +98 -0
- data/lib/hecks/projections.rb +2 -0
- data/lib/hecks/projector/cli_projector.rb +172 -95
- data/lib/hecks/quality_control/adapters/agent.adapter +1 -1
- data/lib/hecks/quality_control/adapters/agent.rb +2 -2
- data/lib/hecks/quality_control/cli/qa_concurrency_racer.rb +3 -3
- data/lib/hecks/quality_control/cli/qa_discover_external_domains.rb +8 -8
- data/lib/hecks/quality_control/cli/qa_domain_novelty.rb +3 -3
- data/lib/hecks/quality_control/cli/qa_generated_domains.rb +4 -4
- data/lib/hecks/quality_control/cli/qa_log_bug.rb +2 -2
- data/lib/hecks/quality_control/cli/qa_mine_combinations.rb +6 -5
- data/lib/hecks/quality_control/cli/qa_postgres_migrate.rb +2 -2
- data/lib/hecks/quality_control/cli/qa_postgres_role.rb +5 -5
- data/lib/hecks/quality_control/cli/qa_pr_check.rb +3 -3
- data/lib/hecks/quality_control/cli/qa_sweep/all_mode.rb +3 -3
- data/lib/hecks/quality_control/cli/qa_sweep/checks.rb +2 -2
- data/lib/hecks/quality_control/cli/qa_sweep.rb +10 -9
- data/lib/hecks/quality_control/cli/qa_tick.rb +3 -3
- data/lib/hecks/quality_control/quality_control.bluebook +18 -18
- data/lib/hecks/quality_control/quality_control.hecksagon +1 -1
- data/lib/hecks/runtime/command_interpreter.rb +2 -0
- data/lib/hecks/runtime/interpreting.rb +21 -0
- data/lib/hecks/runtime/invocation.rb +5 -3
- data/lib/hecks/runtime/registry/verification.rb +15 -29
- data/lib/hecks/runtime/registry.rb +14 -20
- data/lib/hecks/rust_build/append_optionals.rb +79 -0
- data/lib/hecks/rust_build/conformance_fuzz.rb +2 -1
- data/lib/hecks/rust_build/domain_name.rb +26 -0
- data/lib/hecks/rust_build/kernel_input.rb +58 -0
- data/lib/hecks/rust_build/project_rust/codegen_run.rb +143 -0
- data/lib/hecks/rust_build/project_rust/root_mod.rb +1 -1
- data/lib/hecks/rust_build/project_rust.rb +57 -24
- data/lib/hecks/rust_build/rust_literal.rb +33 -0
- data/lib/hecks/rust_build/write_if_changed.rb +86 -0
- data/lib/hecks/site/bluebook/site.bluebook +205 -0
- data/lib/hecks/site/bluebook/site.hecksagon +5 -0
- data/lib/hecks/site/bluebook/site.ports.hecksagon +15 -0
- data/lib/hecks/sme/bluebook/sme.bluebook +336 -0
- data/lib/hecks/sme/bluebook/sme.hecksagon +5 -0
- data/lib/hecks/storehouse.rb +37 -7
- data/lib/hecks/tenancy/bluebook/tenancy.bluebook +1 -1
- data/lib/hecks/three_zero/forms.yml +88 -88
- data/lib/hecks/tools/ci_gates.rb +1 -1
- data/lib/hecks/tools/deploy_recipe.rb +14 -6
- data/lib/hecks/tools/oidc_manifests.rb +2 -1
- data/lib/hecks/tools/sequence_script.rb +2 -2
- data/lib/hecks/tools/site_routes.rb +152 -0
- data/lib/hecks/tools/tenant_provisioning.rb +1 -1
- data/lib/hecks/tools.rb +1 -0
- data/lib/hecks/version.rb +1 -1
- data/rust/build/src/cargo_sync.rs +1 -1
- data/rust/build/src/pipeline.rs +23 -16
- data/rust/build/src/resolve.rs +39 -6
- data/rust/host/HECKS_RELEASE +1 -1
- data/rust/host/src/api.rs +1 -1
- data/rust/host/src/dispatch.rs +87 -7
- data/rust/host/src/needs.rs +111 -0
- data/rust/host/src/web/newsletter/template.rs +69 -0
- data/rust/host/src/web/newsletter/tests.rs +43 -0
- data/rust/host/src/web/newsletter.rs +6 -1
- data/rust/host/src/web/newsletter_send.rs +1 -1
- data/rust/parser/src/keywords.rs +7 -6
- data/rust/parser/src/main.rs +4 -3
- data/rust/parser/src/parse/chapter.rs +1 -1
- data/rust/parser/src/parse/hecksagon.rs +25 -10
- data/rust/project/mutations.rb +4 -30
- data/rust/project/naming.rb +8 -28
- data/rust/project/write_if_changed.rb +4 -80
- data/rust/project_rust_pipeline.rb +16 -12
- data/rust/src/kernel/cli.rs +2 -0
- data/rust/src/kernel/needs.rs +74 -1
- metadata +39 -2
|
@@ -5,7 +5,7 @@ require "rspec/core"
|
|
|
5
5
|
module Hecks
|
|
6
6
|
module CLI
|
|
7
7
|
# The command behind `hecks run_spec_example`: runs one spec file filtered to one example
|
|
8
|
-
# through `RSpec::Core::Runner`, so a `hecks quality_control log --demonstration` string
|
|
8
|
+
# through `RSpec::Core::Runner`, so a `hecks quality_control bug.log --demonstration` string
|
|
9
9
|
# needs no `-e` quoting.
|
|
10
10
|
module SpecExample
|
|
11
11
|
USAGE = "usage: hecks run_spec_example <spec_file> <example description substring>"
|
|
@@ -31,9 +31,8 @@ module Hecks
|
|
|
31
31
|
USAGE = <<~TEXT
|
|
32
32
|
Usage: hecks stress_concurrency [--runs N] [--parallel N] [--seed-start N]
|
|
33
33
|
|
|
34
|
-
--runs N How many times to run EACH group below
|
|
35
|
-
|
|
36
|
-
index).
|
|
34
|
+
--runs N How many times to run EACH group below. Each run uses
|
|
35
|
+
a different --seed (seed-start + run index).
|
|
37
36
|
--parallel N How many parallel-safe-group runs to have going as
|
|
38
37
|
separate OS processes AT THE SAME TIME (default: this
|
|
39
38
|
machine's own core count, via Etc.nprocessors) — real
|
|
@@ -41,9 +40,12 @@ module Hecks
|
|
|
41
40
|
seeds one after another. The Postgres-backed group
|
|
42
41
|
always runs one process at a time regardless of this
|
|
43
42
|
flag.
|
|
44
|
-
--seed-start N First --seed value
|
|
43
|
+
--seed-start N First --seed value; runs use seed_start,
|
|
45
44
|
seed_start + 1, ... seed_start + runs - 1.
|
|
46
45
|
|
|
46
|
+
`hecks stress_concurrency` fills --runs and --seed-start from the defaults its
|
|
47
|
+
bluebook declares (`hecks stress_concurrency --help` shows them).
|
|
48
|
+
|
|
47
49
|
Exits 0 if every run's every example passed, 1 if anything failed -
|
|
48
50
|
failing runs' full output is saved under tmp/stress-failures/
|
|
49
51
|
for a real repro (same seed, same command, just add CI=true).
|
|
@@ -60,10 +62,17 @@ module Hecks
|
|
|
60
62
|
# unknown argument
|
|
61
63
|
def call(argv, root:, out: $stdout)
|
|
62
64
|
argv = argv.dup
|
|
63
|
-
settings = {
|
|
65
|
+
settings = { parallel: Etc.nprocessors }
|
|
64
66
|
status = parse!(argv, settings, out)
|
|
65
67
|
return status if status
|
|
66
68
|
|
|
69
|
+
missing = %i[runs seed_start].reject { |name| settings.key?(name) }
|
|
70
|
+
unless missing.empty?
|
|
71
|
+
warn "missing #{missing.map { |name| "--#{name.to_s.tr('_', '-')}" }.join(', ')}: " \
|
|
72
|
+
"`hecks stress_concurrency` supplies the defaults its bluebook declares (--help for usage)"
|
|
73
|
+
return 64
|
|
74
|
+
end
|
|
75
|
+
|
|
67
76
|
results = run_all(root, settings, out)
|
|
68
77
|
report(root, results, out)
|
|
69
78
|
end
|
data/lib/hecks/corpus.rb
CHANGED
|
@@ -17,7 +17,7 @@ module Hecks
|
|
|
17
17
|
# The Hecks domain (ADR 0080): its chapters share one namespace and one hecksagon, so
|
|
18
18
|
# they load together, as a directory, never file by file.
|
|
19
19
|
hecks: "lib/hecks/hecks",
|
|
20
|
-
# A package vendored via `
|
|
20
|
+
# A package vendored via `attaches ... from: :vendor` — nested inside the
|
|
21
21
|
# consuming example's own checkout, not this gem's own framework/bluebook/.
|
|
22
22
|
vendored: "examples/*/vendor/embryonaut_bluebooks/*"
|
|
23
23
|
}.freeze
|
|
@@ -31,7 +31,9 @@ module Hecks
|
|
|
31
31
|
qa: "lib/hecks/quality_control/*.bluebook",
|
|
32
32
|
language: "lib/hecks/language/**/*.bluebook",
|
|
33
33
|
deploy: "lib/hecks/deploy/bluebook/*.bluebook",
|
|
34
|
+
site: "lib/hecks/site/bluebook/*.bluebook",
|
|
34
35
|
tenancy: "lib/hecks/tenancy/bluebook/*.bluebook",
|
|
36
|
+
sme: "lib/hecks/sme/bluebook/*.bluebook",
|
|
35
37
|
fixture: "spec/fixtures/**/*.bluebook"
|
|
36
38
|
}.freeze
|
|
37
39
|
|
|
@@ -232,14 +234,14 @@ module Hecks
|
|
|
232
234
|
Elsewhere = Struct.new(:check, :destination, :names, :why)
|
|
233
235
|
|
|
234
236
|
RUST_ELSEWHERE = {
|
|
235
|
-
"meta" => Elsewhere.new(:named_in, "
|
|
237
|
+
"meta" => Elsewhere.new(:named_in, "lib/hecks/tools/regeneration_run.rb", "rust/src/generated",
|
|
236
238
|
"the self-hosted grammar (lib/hecks/language), not a domain directory — every " \
|
|
237
|
-
"hecks project_rust run rewrites it
|
|
238
|
-
"
|
|
239
|
+
"hecks project_rust run rewrites it, so the drift check diffs it with the rest " \
|
|
240
|
+
"of rust/src/generated, and there is no directory to fuzz")
|
|
239
241
|
}.freeze
|
|
240
242
|
|
|
241
|
-
# Every place a Rust-facing domain's own `uses_framework
|
|
242
|
-
# `uses_embryonaut_bluebook` attachment could be declared.
|
|
243
|
+
# Every place a Rust-facing domain's own `attaches` (or the deprecated `uses_framework` /
|
|
244
|
+
# `uses_embryonaut_bluebook`) attachment could be declared.
|
|
243
245
|
#
|
|
244
246
|
# @param root [String] repository root to search under
|
|
245
247
|
# @return [String] every reachable hecksagon file's own text, joined by newlines
|
|
@@ -307,10 +309,11 @@ module Hecks
|
|
|
307
309
|
|
|
308
310
|
# The stamp hecks project_rust writes into metadata.rs, e.g.
|
|
309
311
|
# `examples/pizzas` or `the self-hosted language (...)`, with any
|
|
310
|
-
# ` (
|
|
312
|
+
# ` (attaches "X")`/` (attaches "x", from: :vendor)` suffix stripped (the deprecated
|
|
313
|
+
# `uses_framework` / `uses_embryonaut_bluebook` spellings too).
|
|
311
314
|
SOURCE_STAMP = /
|
|
312
315
|
GENERATED\ by\ hecks\ project_rust\ —\ (.+?)
|
|
313
|
-
(?:\ \((?:uses_framework|uses_embryonaut_bluebook)\ "\w+"
|
|
316
|
+
(?:\ \((?:attaches|uses_framework|uses_embryonaut_bluebook)\ "\w+"(?:,\ from:\ :vendor)?\))?
|
|
314
317
|
's\ own\ canonical\ IR,
|
|
315
318
|
/x
|
|
316
319
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# The Deploy chapter: what `deployed_to(...)` may declare, and the commands that generate, check and
|
|
2
2
|
# compare a deploy recipe, project the OIDC manifests and provision a tenant.
|
|
3
3
|
#
|
|
4
|
-
# `hecks deploy project` and `hecks deploy provision` boot it directly; the Hecks domain attaches it, so
|
|
4
|
+
# `hecks deploy recipe.project` and `hecks deploy tenant.provision` boot it directly; the Hecks domain attaches it, so
|
|
5
5
|
# each command below is also `hecks deploy <verb>`. The targets validate the `deployed_to` shape
|
|
6
6
|
# that `world.bluebook`'s untyped `Setting` map leaves open. Each run below records that it was
|
|
7
7
|
# requested before its DeployToolchain port is asked, and what came of it after. No command
|
|
@@ -10,7 +10,7 @@ Hecks.bluebook "Deploy" do
|
|
|
10
10
|
vision "Generate, check and compare deploy recipes, project OIDC manifests and provision tenants."
|
|
11
11
|
|
|
12
12
|
aggregate "LambdaTarget" do
|
|
13
|
-
description "One domain's validated AWS Lambda deploy target — what deployed_to(\"AwsLambda\") must resolve to before hecks deploy project generates anything from it."
|
|
13
|
+
description "One domain's validated AWS Lambda deploy target — what deployed_to(\"AwsLambda\") must resolve to before hecks deploy recipe.project generates anything from it."
|
|
14
14
|
|
|
15
15
|
identified_by :domain
|
|
16
16
|
|
|
@@ -38,14 +38,14 @@ Hecks.bluebook "Deploy" do
|
|
|
38
38
|
# (Serverless v2);
|
|
39
39
|
# only the emitted CloudFormation resource differs, never the runtime adapter. "Shared" borrows
|
|
40
40
|
# another domain's instance under its own Postgres schema and needs an `owner` setting, which
|
|
41
|
-
# `hecks deploy project` checks, since the owning domain is a deploy-time fact.
|
|
41
|
+
# `hecks deploy recipe.project` checks, since the owning domain is a deploy-time fact.
|
|
42
42
|
value_object "Database" do
|
|
43
43
|
attribute :value, String, one_of: ["Postgres", "Aurora", "Shared"]
|
|
44
44
|
end
|
|
45
45
|
|
|
46
46
|
# "None" (default) or "Rust": the domain's own Lambda (rust/host) also serves a public web UI
|
|
47
47
|
# in-process (rust/host/src/web.rs). "Ruby" is not a member: a Ruby WebFunction comes from a
|
|
48
|
-
# `lambda_handler.rb` file, and `hecks deploy project` refuses `web "Rust"` alongside one.
|
|
48
|
+
# `lambda_handler.rb` file, and `hecks deploy recipe.project` refuses `web "Rust"` alongside one.
|
|
49
49
|
value_object "Web" do
|
|
50
50
|
attribute :value, String, one_of: ["None", "Rust"]
|
|
51
51
|
end
|
|
@@ -87,7 +87,7 @@ Hecks.bluebook "Deploy" do
|
|
|
87
87
|
|
|
88
88
|
# All required, no `default:`: command-level defaults apply only at the JSON/Rust-codegen
|
|
89
89
|
# boundary, not Ruby's direct dispatch (interpreting.rb `normalize_args`). Defaults are
|
|
90
|
-
# resolved by `hecks deploy project` before it dispatches.
|
|
90
|
+
# resolved by `hecks deploy recipe.project` before it dispatches.
|
|
91
91
|
command "Declare" do
|
|
92
92
|
goal "Validate one domain's AwsLambda deploy target before anything is generated from it"
|
|
93
93
|
|
|
@@ -122,7 +122,7 @@ Hecks.bluebook "Deploy" do
|
|
|
122
122
|
# A sibling of `LambdaTarget` with its own value objects, since value objects are not
|
|
123
123
|
# shared across aggregates.
|
|
124
124
|
aggregate "FargateTarget" do
|
|
125
|
-
description "One domain's validated AWS Fargate deploy target — what deployed_to(\"AwsFargate\") must resolve to before hecks deploy project generates anything from it."
|
|
125
|
+
description "One domain's validated AWS Fargate deploy target — what deployed_to(\"AwsFargate\") must resolve to before hecks deploy recipe.project generates anything from it."
|
|
126
126
|
|
|
127
127
|
identified_by :domain
|
|
128
128
|
|
|
@@ -177,7 +177,7 @@ Hecks.bluebook "Deploy" do
|
|
|
177
177
|
end
|
|
178
178
|
|
|
179
179
|
# All seven required for the same reason as `LambdaTarget.Declare`; this validates a
|
|
180
|
-
# target already resolved by `hecks deploy project`'s defaults.
|
|
180
|
+
# target already resolved by `hecks deploy recipe.project`'s defaults.
|
|
181
181
|
command "Declare" do
|
|
182
182
|
goal "Validate one domain's AwsFargate deploy target before anything is generated from it"
|
|
183
183
|
|
|
@@ -201,10 +201,83 @@ Hecks.bluebook "Deploy" do
|
|
|
201
201
|
end
|
|
202
202
|
end
|
|
203
203
|
|
|
204
|
-
|
|
204
|
+
aggregate "BoxTarget" do
|
|
205
|
+
description "One domain's validated single-box deploy target — what deployed_to(\"AwsBox\") must resolve to before hecks deploy project generates a box and an RDS instance from it"
|
|
206
|
+
|
|
207
|
+
identified_by :domain
|
|
208
|
+
|
|
209
|
+
attribute :domain, DomainName
|
|
210
|
+
attribute :region, Region
|
|
211
|
+
attribute :instance_type, InstanceType
|
|
212
|
+
attribute :volume_gb, VolumeGb
|
|
213
|
+
attribute :database_class, DatabaseClass
|
|
214
|
+
attribute :storage_gb, StorageGb
|
|
215
|
+
|
|
216
|
+
value_object "DomainName" do
|
|
217
|
+
attribute :value, String
|
|
218
|
+
invariant("a domain is named") { !value.to_s.empty? }
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
value_object "Region" do
|
|
222
|
+
attribute :value, String
|
|
223
|
+
invariant("a region is named") { !value.to_s.empty? }
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
# An EC2 instance type such as t4g.medium. The family and size are checked for shape only;
|
|
227
|
+
# EC2 refuses an unknown type at stack creation.
|
|
228
|
+
value_object "InstanceType" do
|
|
229
|
+
attribute :value, String
|
|
230
|
+
invariant("an instance type is family.size, such as t4g.medium") { value.to_s.match?(/\A[a-z][a-z0-9-]*\.[a-z0-9]+\z/) }
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
# The box's root disk, which holds images, the compose files and container logs; the
|
|
234
|
+
# database is on RDS.
|
|
235
|
+
value_object "VolumeGb" do
|
|
236
|
+
attribute :value, Integer
|
|
237
|
+
invariant("a volume is at least 8 GB") { value >= 8 }
|
|
238
|
+
invariant("a volume is at most 16384 GB") { value <= 16384 }
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
# An RDS instance class such as db.t4g.small.
|
|
242
|
+
value_object "DatabaseClass" do
|
|
243
|
+
attribute :value, String
|
|
244
|
+
invariant("a database class is db.family.size, such as db.t4g.small") { value.to_s.match?(/\Adb\.[a-z][a-z0-9-]*\.[a-z0-9]+\z/) }
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
# RDS storage grows with the journal; 20 GB is RDS's gp3 floor.
|
|
248
|
+
value_object "StorageGb" do
|
|
249
|
+
attribute :value, Integer
|
|
250
|
+
invariant("storage is at least 20 GB") { value >= 20 }
|
|
251
|
+
invariant("storage is at most 65536 GB") { value <= 65536 }
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
# All six required for the same reason as `FargateTarget.Declare`; this validates a target
|
|
255
|
+
# already resolved by `hecks deploy project`'s defaults.
|
|
256
|
+
command "Declare" do
|
|
257
|
+
goal "Validate one domain's AwsBox deploy target before anything is generated from it"
|
|
258
|
+
|
|
259
|
+
attribute :domain, DomainName
|
|
260
|
+
attribute :region, Region
|
|
261
|
+
attribute :instance_type, InstanceType
|
|
262
|
+
attribute :volume_gb, VolumeGb
|
|
263
|
+
attribute :database_class, DatabaseClass
|
|
264
|
+
attribute :storage_gb, StorageGb
|
|
265
|
+
|
|
266
|
+
sets :domain
|
|
267
|
+
sets :region
|
|
268
|
+
sets :instance_type
|
|
269
|
+
sets :volume_gb
|
|
270
|
+
sets :database_class
|
|
271
|
+
sets :storage_gb
|
|
272
|
+
|
|
273
|
+
emits "BoxTargetDeclared"
|
|
274
|
+
end
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
# The validated shape of `hecks deploy tenant.provision`, which does the generating and provisioning.
|
|
205
278
|
# Not a saga: each of its steps is idempotent and local, so there is nothing to resume.
|
|
206
279
|
aggregate "Tenant" do
|
|
207
|
-
description "One tenant's validated identity — which realm and Postgres schema `hecks deploy provision` provisions before booting a domain directory under Runtime.boot's own `environment:` overlay for it."
|
|
280
|
+
description "One tenant's validated identity — which realm and Postgres schema `hecks deploy tenant.provision` provisions before booting a domain directory under Runtime.boot's own `environment:` overlay for it."
|
|
208
281
|
|
|
209
282
|
identified_by :slug
|
|
210
283
|
|
|
@@ -268,7 +341,7 @@ Hecks.bluebook "Deploy" do
|
|
|
268
341
|
transition "Abandon" => "refused", from: "requested"
|
|
269
342
|
end
|
|
270
343
|
|
|
271
|
-
# `hecks deploy provision <directory> slug=s domain= realm= schema= database= [adapter=]`.
|
|
344
|
+
# `hecks deploy tenant.provision <directory> slug=s domain= realm= schema= database= [adapter=]`.
|
|
272
345
|
# The only command that creates a tenant: its slug, schema and realm are validated by the value
|
|
273
346
|
# objects' patterns and invariants, then the overlay is written through the TenantProvisioning
|
|
274
347
|
# port; the adapter answers PostgresEra when `adapter` is left out. `dry_run?` on this verb
|
|
@@ -424,7 +497,7 @@ Hecks.bluebook "Deploy" do
|
|
|
424
497
|
transition "Fault" => "faulted", from: "requested"
|
|
425
498
|
end
|
|
426
499
|
|
|
427
|
-
# `hecks deploy project <domain> [tenant=] [schema=] [out=] [environment=]`. A schema override
|
|
500
|
+
# `hecks deploy recipe.project <domain> [tenant=] [schema=] [out=] [environment=]`. A schema override
|
|
428
501
|
# needs its tenant; the generator says so, and a domain with no `deployed_to` target, or one
|
|
429
502
|
# whose settings conflict, is faulted with the generator's own sentence.
|
|
430
503
|
command "Project" do
|
|
@@ -630,7 +703,7 @@ Hecks.bluebook "Deploy" do
|
|
|
630
703
|
transition "Refuse" => "refused", from: "requested"
|
|
631
704
|
end
|
|
632
705
|
|
|
633
|
-
# `hecks deploy diff <before> after=<template> [--json] [--strict]`. `--strict` also counts
|
|
706
|
+
# `hecks deploy template_comparison.diff <before> after=<template> [--json] [--strict]`. `--strict` also counts
|
|
634
707
|
# cosmetic differences (comments, key order); `--json` writes the report as JSON.
|
|
635
708
|
command "Diff" do
|
|
636
709
|
goal "Compare two CloudFormation templates and report what differs by logical id"
|
data/lib/hecks/deploy/oidc.json
CHANGED
data/lib/hecks/doc/reference.rb
CHANGED
|
@@ -4,7 +4,8 @@ require_relative "../naming"
|
|
|
4
4
|
module Hecks
|
|
5
5
|
module Doc
|
|
6
6
|
# The DSL reference, projected from the language's own Syntax chapter so
|
|
7
|
-
# its tables can never drift from the live builders. Regenerate with
|
|
7
|
+
# its tables can never drift from the live builders. Regenerate with
|
|
8
|
+
# `hecks language_run.project_reference`.
|
|
8
9
|
module Reference
|
|
9
10
|
GENERATED_END = "<!-- generated:end -->".freeze
|
|
10
11
|
TODO_SENTINEL = "<!-- TODO: document this word -->".freeze
|
|
@@ -84,7 +85,7 @@ module Hecks
|
|
|
84
85
|
|
|
85
86
|
*The tables on this page are generated from the language's own
|
|
86
87
|
aggregate-local syntax tables (`lib/hecks/language/**/*.bluebook`)
|
|
87
|
-
by `hecks project_reference` — do not edit inside the markers. The prose
|
|
88
|
+
by `hecks language_run.project_reference` — do not edit inside the markers. The prose
|
|
88
89
|
between them is hand-written and survives regeneration.*
|
|
89
90
|
#{GENERATED_END}
|
|
90
91
|
#{"\n#{preamble}\n" unless preamble.empty?}
|
|
@@ -172,7 +173,7 @@ module Hecks
|
|
|
172
173
|
# The DSL reference
|
|
173
174
|
|
|
174
175
|
One page per context — the place in a file where a word may be
|
|
175
|
-
typed. Generated by `hecks project_reference` from the Syntax chapter;
|
|
176
|
+
typed. Generated by `hecks language_run.project_reference` from the Syntax chapter;
|
|
176
177
|
the prose between the generated markers is hand-written and
|
|
177
178
|
survives regeneration.
|
|
178
179
|
|
data/lib/hecks/doors/cli_door.rb
CHANGED
|
@@ -11,17 +11,17 @@ module Hecks
|
|
|
11
11
|
|
|
12
12
|
module_function
|
|
13
13
|
|
|
14
|
-
# Builds a
|
|
14
|
+
# Builds a command's nested, typed argument Hash from its `name=value` words.
|
|
15
15
|
#
|
|
16
16
|
# arguments(spec, ["reference.value=A-1", "sequence.value=99"])
|
|
17
17
|
# # => { reference: { value: "A-1" }, sequence: { value: 99 } }
|
|
18
18
|
#
|
|
19
|
-
# @param spec [Hash{Symbol => Object}] one
|
|
20
|
-
# @param pairs [Array<String>] the words after the
|
|
21
|
-
# be the short form of a single-field value object (`reference` for `reference.value`);
|
|
22
|
-
# `--name` for a Boolean; and at most one bare word, which fills the
|
|
19
|
+
# @param spec [Hash{Symbol => Object}] one command's entry from `Projector::CliProjector`
|
|
20
|
+
# @param pairs [Array<String>] the words after the command: `name=value` pairs, where a path
|
|
21
|
+
# may be the short form of a single-field value object (`reference` for `reference.value`);
|
|
22
|
+
# `--name` for a Boolean; and at most one bare word, which fills the first argument
|
|
23
23
|
# @return [Hash{Symbol => Object}] the arguments nested by path, leaves cast to type
|
|
24
|
-
# @raise [Runtime::NotFound] if a path, flag or bare word does not fit the
|
|
24
|
+
# @raise [Runtime::NotFound] if a path, flag or bare word does not fit the command
|
|
25
25
|
# @raise [Runtime::TypeMismatch] if a value does not parse as its Integer or Float
|
|
26
26
|
def arguments(spec, pairs)
|
|
27
27
|
# Extra accepted arguments stay out of help, which teaches only to=....
|
|
@@ -44,7 +44,7 @@ module Hecks
|
|
|
44
44
|
|
|
45
45
|
# Rewrites the short forms into `name=value`: `--name` for a Boolean (a following `yes`,
|
|
46
46
|
# `no`, `true`, `false`, `on`, `off`, `1` or `0` is that flag's value, not an argument),
|
|
47
|
-
# `--name=value` as `name=value`, and one bare word as the
|
|
47
|
+
# `--name=value` as `name=value`, and one bare word as the command's first argument (`to` for
|
|
48
48
|
# a command on an existing aggregate, the first attribute for one that creates).
|
|
49
49
|
def normalize(spec, words, options)
|
|
50
50
|
queue = words.dup
|
|
@@ -84,20 +84,20 @@ module Hecks
|
|
|
84
84
|
path = options.key?(name) ? name : expand(name, options)
|
|
85
85
|
return path if options.dig(path, :type) == "Boolean"
|
|
86
86
|
|
|
87
|
-
raise Runtime::NotFound, "--#{name} is a flag, but this
|
|
87
|
+
raise Runtime::NotFound, "--#{name} is a flag, but this command has no Boolean argument #{name.inspect}"
|
|
88
88
|
end
|
|
89
89
|
|
|
90
|
-
# The one bare word, as a pair for the
|
|
90
|
+
# The one bare word, as a pair for the command's first argument the launcher does not mint.
|
|
91
91
|
def positional(word, spec)
|
|
92
92
|
first = spec[:arguments].find { |argument| !argument[:minted] }
|
|
93
93
|
return "#{first[:path]}=#{word}" if first
|
|
94
94
|
|
|
95
95
|
if spec[:arguments].any?
|
|
96
|
-
raise Runtime::NotFound, "#{word.inspect} is not name=value; this
|
|
96
|
+
raise Runtime::NotFound, "#{word.inspect} is not name=value; this command's only argument is its run key, " \
|
|
97
97
|
"which is minted when omitted (run=<key> names one)"
|
|
98
98
|
end
|
|
99
99
|
|
|
100
|
-
raise Runtime::NotFound, "#{word.inspect} is not name=value, and this
|
|
100
|
+
raise Runtime::NotFound, "#{word.inspect} is not name=value, and this command takes no arguments"
|
|
101
101
|
end
|
|
102
102
|
|
|
103
103
|
# Words the refusal for more than one unnamed argument.
|
|
@@ -187,9 +187,9 @@ module Hecks
|
|
|
187
187
|
hash
|
|
188
188
|
end
|
|
189
189
|
|
|
190
|
-
# Words the refusal for an argument the
|
|
190
|
+
# Words the refusal for an argument the command does not take, listing what it does.
|
|
191
191
|
def unknown(path, known)
|
|
192
|
-
"no argument #{path.inspect} — this
|
|
192
|
+
"no argument #{path.inspect} — this command takes #{known.sort.join(', ')}"
|
|
193
193
|
end
|
|
194
194
|
end
|
|
195
195
|
end
|
|
@@ -13,6 +13,8 @@ module Hecks
|
|
|
13
13
|
module CliRunner
|
|
14
14
|
# The class an adapter raises when the tool it wraps refuses.
|
|
15
15
|
TOOL_REFUSAL = "Hecks::Adapters::ConsoleCapture::Failure".freeze
|
|
16
|
+
# The words that open the query namespace; `ask` is the older spelling.
|
|
17
|
+
QUERY_WORDS = %w[query ask].freeze
|
|
16
18
|
|
|
17
19
|
module_function
|
|
18
20
|
|
|
@@ -20,7 +22,7 @@ module Hecks
|
|
|
20
22
|
# The booted domain's own chapter is projected, or an attached one its first word names.
|
|
21
23
|
#
|
|
22
24
|
# @param runtime [Runtime::Dispatcher, Runtime::RemoteDispatcher] the booted domain
|
|
23
|
-
# @param argv [Array<String>] an optional `ask
|
|
25
|
+
# @param argv [Array<String>] an optional `query` (or `ask`), the command or query name, then
|
|
24
26
|
# `path=value` pairs; `--help`, `-h`, `help` or nothing answers usage
|
|
25
27
|
# @param program [String] how the caller was invoked, echoed in usage and hints
|
|
26
28
|
# @return [Array(String, Integer)] the text and the status: 0 answered, 1 refused or misused
|
|
@@ -71,7 +73,7 @@ module Hecks
|
|
|
71
73
|
cursor = nil
|
|
72
74
|
loop do
|
|
73
75
|
ask = cursor ? args.except(:from_now).merge(since: { value: cursor }) : args
|
|
74
|
-
rows = runtime.query(spec[:
|
|
76
|
+
rows = runtime.query(spec[:command], **ask)
|
|
75
77
|
row = rows.first || {}
|
|
76
78
|
Array(row[:events]).each { |event| out.puts(JSON.generate(entry_line(JsonDoor.materialize(event)))) }
|
|
77
79
|
out.flush
|
|
@@ -93,23 +95,23 @@ module Hecks
|
|
|
93
95
|
# Answers a command line that asks only for usage, from the projection alone.
|
|
94
96
|
#
|
|
95
97
|
# Needs a registry and no bound adapter, so a `Runtime::Loader::Described` serves: the
|
|
96
|
-
# help text, the no-argument usage, a
|
|
98
|
+
# help text, the no-argument usage, a command's `--help` and an unknown one's hint all come
|
|
97
99
|
# out byte-identical to `call`'s.
|
|
98
100
|
#
|
|
99
101
|
# @param runtime [#registry] a booted domain, or what `Hecks.describe` answers
|
|
100
102
|
# @param argv [Array<String>] as for `call`
|
|
101
103
|
# @param program [String] how the caller was invoked
|
|
102
104
|
# @return [Array(String, Integer), nil] the text and status, or nil when the line would
|
|
103
|
-
# run a
|
|
105
|
+
# run a command or question and so needs a booted domain
|
|
104
106
|
def usage(runtime:, argv:, program: "hecks run")
|
|
105
107
|
resolve(runtime, argv, program)[:answer]
|
|
106
108
|
end
|
|
107
109
|
|
|
108
110
|
# Parses a command line against the projection: either the answer it gives without
|
|
109
|
-
# running anything, or the
|
|
111
|
+
# running anything, or the command it would run.
|
|
110
112
|
#
|
|
111
113
|
# @return [Hash] `{answer: [text, status]}`, or `{spec:, name:, rest:, asking:, program:,
|
|
112
|
-
# bluebook:, launcher:}` for a
|
|
114
|
+
# bluebook:, launcher:}` for a command to dispatch
|
|
113
115
|
def resolve(runtime, argv, program)
|
|
114
116
|
bluebook, argv, program = chapter_for(runtime, argv, program)
|
|
115
117
|
launcher = LauncherOptions.settings(runtime, bluebook.name)
|
|
@@ -119,18 +121,11 @@ module Hecks
|
|
|
119
121
|
name = argv.first
|
|
120
122
|
return { answer: [cli[:usage], 0] } if name.nil? || %w[--help -h help].include?(name)
|
|
121
123
|
|
|
122
|
-
|
|
123
|
-
# of one name.
|
|
124
|
-
asking = name == "ask"
|
|
125
|
-
argv = argv[1..] if asking
|
|
126
|
-
name = argv.first
|
|
124
|
+
asking, name, argv = entry_word(cli, argv)
|
|
127
125
|
return { answer: [cli[:usage], 1] } if name.nil?
|
|
128
126
|
|
|
129
|
-
#
|
|
130
|
-
asking
|
|
131
|
-
|
|
132
|
-
# The alias map lets `create_pizza` and `order.create_pizza` reach the same verb.
|
|
133
|
-
pool = asking ? cli[:questions] : cli[:verbs]
|
|
127
|
+
# The alias map lets `create_pizza` and `order.create_pizza` reach the same command.
|
|
128
|
+
pool = asking ? cli[:questions] : cli[:commands]
|
|
134
129
|
key = cli[:names][asking ? :question : :command][name]
|
|
135
130
|
spec = pool[key]
|
|
136
131
|
return { answer: [unknown(cli, name, asking, program), 1] } unless spec
|
|
@@ -138,7 +133,7 @@ module Hecks
|
|
|
138
133
|
rest = argv[1..]
|
|
139
134
|
if rest.include?("--help")
|
|
140
135
|
help = Projector.call(:cli, bluebook: bluebook,
|
|
141
|
-
options: options.merge(
|
|
136
|
+
options: options.merge(command: name, ask: asking))[:usage]
|
|
142
137
|
return { answer: [help, 0] }
|
|
143
138
|
end
|
|
144
139
|
|
|
@@ -146,6 +141,27 @@ module Hecks
|
|
|
146
141
|
launcher: launcher }
|
|
147
142
|
end
|
|
148
143
|
|
|
144
|
+
# Reads the entry words of a line: whether it asks a query, the bare name, and the words
|
|
145
|
+
# from the name on.
|
|
146
|
+
#
|
|
147
|
+
# `query` gives queries their own namespace (`ask` is its older spelling); a chapter may
|
|
148
|
+
# declare a command and a query of one name. A trailing `!` says the word is a command and
|
|
149
|
+
# is not part of the name. A query answers to its bare name too; `query` is needed only when
|
|
150
|
+
# a command shares it.
|
|
151
|
+
#
|
|
152
|
+
# @return [Array(Boolean, String, Array<String>)] `asking`, `name` (nil when absent), `argv`
|
|
153
|
+
def entry_word(cli, argv)
|
|
154
|
+
asking = QUERY_WORDS.include?(argv.first)
|
|
155
|
+
argv = argv[1..] if asking
|
|
156
|
+
name = argv.first
|
|
157
|
+
return [asking, nil, argv] if name.nil?
|
|
158
|
+
|
|
159
|
+
bang = name.end_with?("!")
|
|
160
|
+
name = name.chomp("!") if bang
|
|
161
|
+
asking ||= !bang && !cli[:names][:command].key?(name) && cli[:names][:question].key?(name)
|
|
162
|
+
[asking, name, argv]
|
|
163
|
+
end
|
|
164
|
+
|
|
149
165
|
# The chapter a command line speaks to: the booted domain's own, or, when the first word
|
|
150
166
|
# names a chapter its hecksagon attaches or uses as a framework member (`deploy`,
|
|
151
167
|
# `governance`), that chapter, with the word dropped and added to the program name.
|
|
@@ -158,11 +174,11 @@ module Hecks
|
|
|
158
174
|
[runtime.registry.bluebook(target), argv[1..], "#{program} #{argv.first}"]
|
|
159
175
|
end
|
|
160
176
|
|
|
161
|
-
# Parses one resolved
|
|
177
|
+
# Parses one resolved command's arguments, runs it as a query or command, and turns the
|
|
162
178
|
# outcome, or the domain's refusal, into `[json_or_message, status]`. With `--wait`, a
|
|
163
179
|
# question whose report names a gap (`LauncherOptions.gap_reported?`) answers status 1.
|
|
164
180
|
#
|
|
165
|
-
# @param bluebook [Bluebook::Chapter] the chapter the
|
|
181
|
+
# @param bluebook [Bluebook::Chapter] the chapter the command belongs to
|
|
166
182
|
# @param launcher [Hash, nil] the chapter's `launcher` world setting; nil when not opted in
|
|
167
183
|
def dispatch(runtime, spec, name, rest, program, asking, bluebook: nil, launcher: nil)
|
|
168
184
|
rest, wait = LauncherOptions.take_wait(spec, rest) if launcher
|
|
@@ -171,10 +187,10 @@ module Hecks
|
|
|
171
187
|
return answer_query(runtime, spec, args, wait) if spec[:kind] == :query
|
|
172
188
|
|
|
173
189
|
args, minted = LauncherOptions.run_key(runtime, spec, args, launcher)
|
|
174
|
-
# Answers with only this
|
|
190
|
+
# Answers with only this command's outcome; a full store dump is every record there is.
|
|
175
191
|
request = CommandRequest.normalize(args, receiver: spec[:receiver],
|
|
176
192
|
legacy_receiver: spec[:legacy_receiver])
|
|
177
|
-
handle = runtime.dispatch_flat(spec[:
|
|
193
|
+
handle = runtime.dispatch_flat(spec[:command], request)
|
|
178
194
|
extra = refused_answer(handle)
|
|
179
195
|
extra = { run: minted }.merge(extra) if minted
|
|
180
196
|
return settled(runtime, spec, handle, bluebook, launcher, extra) if wait
|
|
@@ -185,7 +201,7 @@ module Hecks
|
|
|
185
201
|
events: handle.events.map(&:name) }.merge(extra)), 0]
|
|
186
202
|
rescue Runtime::NotFound, Runtime::TypeMismatch => e
|
|
187
203
|
# A bad argument and a missing record both need the same next step: read the help.
|
|
188
|
-
["#{e.message}\n\n #{program} #{'
|
|
204
|
+
["#{e.message}\n\n #{program} #{'query ' if asking}#{name} --help", 1]
|
|
189
205
|
rescue *Runtime::DOMAIN_REFUSALS => e
|
|
190
206
|
# The refusal is the chapter's own sentence, verbatim.
|
|
191
207
|
[e.message, 1]
|
|
@@ -202,7 +218,7 @@ module Hecks
|
|
|
202
218
|
# @param wait [Boolean, nil] whether `--wait` was given: a report naming a gap then fails
|
|
203
219
|
# @return [Array(String, Integer)] the answer and the status
|
|
204
220
|
def answer_query(runtime, spec, args, wait)
|
|
205
|
-
rows = runtime.query(spec[:
|
|
221
|
+
rows = runtime.query(spec[:command], **args)
|
|
206
222
|
text = text_answer(spec, rows)
|
|
207
223
|
[text || JSON.pretty_generate(rows.map { |row| JsonDoor.materialize(row) }),
|
|
208
224
|
wait && LauncherOptions.gap_reported?(text) ? 1 : 0]
|
|
@@ -256,7 +272,7 @@ module Hecks
|
|
|
256
272
|
|
|
257
273
|
# The aggregate a top-level command belongs to; nil for an entity command or a port.
|
|
258
274
|
def aggregate_of(bluebook, spec)
|
|
259
|
-
head = spec[:
|
|
275
|
+
head = spec[:command].split("::", 2).last
|
|
260
276
|
return if head.count(".") != 1
|
|
261
277
|
|
|
262
278
|
bluebook.aggregates.find { |aggregate| aggregate.hecks_name == head.split(".").first }
|
|
@@ -317,19 +333,23 @@ module Hecks
|
|
|
317
333
|
end }
|
|
318
334
|
end
|
|
319
335
|
|
|
320
|
-
# Words the answer for an unknown
|
|
336
|
+
# Words the answer for an unknown command or question, suggesting up to five near names.
|
|
321
337
|
# Ranked by shared prefix, which survives a dropped letter where a substring match would not.
|
|
322
338
|
def unknown(cli, name, asking, program)
|
|
323
339
|
# Both spellings are candidates: `order.create_piza` shares no prefix with `create_pizza`.
|
|
324
340
|
pool = cli[:names][asking ? :question : :command].keys
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
341
|
+
# A bare name is the commonest slip now that the aggregate is required: its homes first.
|
|
342
|
+
homes = (pool + cli[:names][asking ? :command : :question].keys)
|
|
343
|
+
.select { |candidate| candidate.split(".").last == name }.uniq
|
|
344
|
+
similar = pool.map { |candidate| [shared_prefix(candidate, name), candidate] }
|
|
345
|
+
.select { |shared, _| shared >= [name.length / 2, 3].max }
|
|
346
|
+
.sort_by { |shared, candidate| [-shared, candidate] }
|
|
347
|
+
.map(&:last)
|
|
348
|
+
near = homes + (similar - homes)
|
|
349
|
+
|
|
350
|
+
lines = ["no such #{asking ? 'query' : 'command'}: #{name}"]
|
|
331
351
|
lines += ["", "did you mean:", *near.first(5).map { |candidate| " #{candidate}" }] unless near.empty?
|
|
332
|
-
lines += ["", " #{program}#{'
|
|
352
|
+
lines += ["", " #{program}#{' query' if asking} for the full list"]
|
|
333
353
|
lines.join("\n")
|
|
334
354
|
end
|
|
335
355
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
require "stringio"
|
|
1
2
|
require_relative "../ports/identity_generation"
|
|
2
3
|
require_relative "../runtime/errors"
|
|
3
4
|
require_relative "cli_door"
|
|
@@ -46,13 +47,30 @@ module Hecks
|
|
|
46
47
|
{ program: program, names: settings && settings[:names], mint_run_keys: settings && settings[:run_keys] }
|
|
47
48
|
end
|
|
48
49
|
|
|
49
|
-
#
|
|
50
|
+
# Runs the block with standard error held back, so the wiring notes hecks prints about its own
|
|
51
|
+
# chapters (a Memory journal loses sagas on restart) do not open a console session. They are
|
|
52
|
+
# for an operator running a stored journal. A raised error still surfaces, unchanged.
|
|
53
|
+
#
|
|
54
|
+
# @param hold [Boolean] whether to hold standard error back; false runs the block as it is
|
|
55
|
+
# @yield the boot
|
|
56
|
+
# @return [Object] the block's value
|
|
57
|
+
def quietly(hold: true)
|
|
58
|
+
return yield unless hold
|
|
59
|
+
|
|
60
|
+
shown = $stderr
|
|
61
|
+
$stderr = StringIO.new
|
|
62
|
+
yield
|
|
63
|
+
ensure
|
|
64
|
+
$stderr = shown if hold
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Takes `--wait` out of a command's words, unless it declares a `wait` argument of its own.
|
|
50
68
|
#
|
|
51
69
|
# `--wait=false` (or `no`, `0`, `off`) is a `--wait` that was switched off; a bare `--wait`
|
|
52
70
|
# may be followed by its Boolean word.
|
|
53
71
|
#
|
|
54
|
-
# @param spec [Hash] the
|
|
55
|
-
# @param words [Array<String>] the words after the
|
|
72
|
+
# @param spec [Hash] the command's projected spec
|
|
73
|
+
# @param words [Array<String>] the words after the command
|
|
56
74
|
# @return [Array(Array<String>, Boolean)] the remaining words, and whether `--wait` was given
|
|
57
75
|
# @raise [Runtime::TypeMismatch] if `--wait=` carries something that is not a Boolean word
|
|
58
76
|
def take_wait(spec, words)
|
|
@@ -78,7 +96,7 @@ module Hecks
|
|
|
78
96
|
def streams?(launcher, spec)
|
|
79
97
|
return false unless launcher && spec[:kind] == :query
|
|
80
98
|
|
|
81
|
-
Array(launcher[:streams]).map(&:to_s).include?(spec[:
|
|
99
|
+
Array(launcher[:streams]).map(&:to_s).include?(spec[:command].to_s.split(/[.:]+/).last)
|
|
82
100
|
end
|
|
83
101
|
|
|
84
102
|
# Takes `--stream` out of a question's words.
|
|
@@ -119,7 +137,7 @@ module Hecks
|
|
|
119
137
|
# rather than sent on without one.
|
|
120
138
|
#
|
|
121
139
|
# @param runtime [Runtime::Dispatcher, Runtime::RemoteDispatcher] the booted domain
|
|
122
|
-
# @param spec [Hash] the
|
|
140
|
+
# @param spec [Hash] the command's projected spec
|
|
123
141
|
# @param args [Hash] the parsed arguments
|
|
124
142
|
# @param settings [Hash, nil] the chapter's `launcher` setting
|
|
125
143
|
# @return [Array(Hash, String)] the arguments, and the key minted (nil when none was)
|