hecks 3.12.0 → 3.13.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.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/lib/hecks/adapters/driven/script_runner.adapter +3 -0
  3. data/lib/hecks/adapters/driven/script_runner.rb +93 -0
  4. data/lib/hecks/adapters/driven.rb +1 -0
  5. data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +16 -12
  6. data/lib/hecks/bluebook/meta_validator.rb +28 -21
  7. data/lib/hecks/deploy/bluebook/deploy.bluebook +1 -1
  8. data/lib/hecks/framework/bluebook/change.behaviors +30 -0
  9. data/lib/hecks/framework/bluebook/change.bluebook +162 -0
  10. data/lib/hecks/framework/bluebook/change.hecksagon +21 -0
  11. data/lib/hecks/framework/change.oidc.json +34 -0
  12. data/lib/hecks/framework/console_settings.oidc.json +103 -0
  13. data/lib/hecks/framework/governance.oidc.json +28 -0
  14. data/lib/hecks/framework/identity.oidc.json +18 -0
  15. data/lib/hecks/framework/privacy.oidc.json +30 -0
  16. data/lib/hecks/gate/stages.yml +3 -0
  17. data/lib/hecks/grammar/translation.oidc.json +48 -0
  18. data/lib/hecks/hecks/adapters/codebase/test_runner.rb +12 -1
  19. data/lib/hecks/hecks/adapters/deploy_toolchain.rb +2 -1
  20. data/lib/hecks/projections/deploy/fargate/cdn/lines.rb +12 -1
  21. data/lib/hecks/projections/site/admin_cms/templates/membership.rb +10 -7
  22. data/lib/hecks/projections/site/edge/behaviours.rb +10 -6
  23. data/lib/hecks/projections/site/edge/checks.rb +2 -0
  24. data/lib/hecks/projections/site/edge/function_checks.rb +104 -0
  25. data/lib/hecks/projections/site/edge/function_code.rb +86 -0
  26. data/lib/hecks/projections/site/edge/redirect_host.js.tmpl +65 -0
  27. data/lib/hecks/projections/site/edge.rb +35 -9
  28. data/lib/hecks/projections/site/site_cdn.rb +21 -7
  29. data/lib/hecks/projections/site/table/row_builder.rb +12 -2
  30. data/lib/hecks/projections/site/table.rb +4 -2
  31. data/lib/hecks/site/bluebook/site.bluebook +16 -1
  32. data/lib/hecks/tools/oidc_manifests.rb +44 -6
  33. data/lib/hecks/tools/site_routes/templates.rb +13 -9
  34. data/lib/hecks/version.rb +1 -1
  35. data/prebuilt/{verdicts-df19f35868d3284a70f55e42dcef87ef4e65e6a678e5135f72e1f6cff4c1357b.json → verdicts-48318ca172edd21443b1ad02801c697fff100b746e828f09db987647641a5df2.json} +1 -1
  36. data/rust/host/HECKS_RELEASE +1 -1
  37. data/rust/host/src/auth.rs +58 -0
  38. data/rust/host/src/platform_signin/session.rs +1 -1
  39. metadata +21 -7
  40. /data/lib/hecks/framework/{oidc.json → compliance.oidc.json} +0 -0
  41. /data/lib/hecks/grammar/{oidc.json → expression.oidc.json} +0 -0
  42. /data/prebuilt/{d74a00e996222c1d8b676d5959092effc46b4dc59e0555f3bf9554ac58e3094a.marshal → 2b59737fbdbd5f44edda0fddce2611f45d76087b3a7c782d1f1a17ea58c77948.marshal} +0 -0
  43. /data/prebuilt/{9ee54e2f0d4d6d3fe046827779b221e02de665fba911a048ae5dd8b8fbfb7d90.marshal → 58208399bada10b0db99928c48e38789288fc58f7b71a72b21bfa386680e32ed.marshal} +0 -0
@@ -60,7 +60,8 @@ module Hecks
60
60
 
61
61
  # @param registry [Runtime::Registry] a registry holding the Site chapter
62
62
  # @return [Hash{Symbol => Array<String>}] each constrained field to its allowed values, and
63
- # `:http_method` to the verbs a route may answer
63
+ # `:http_method` to the verbs a route may answer and `:edge_function_kind` to the kinds of
64
+ # `EdgeFunction` row
64
65
  # @raise [Invalid] when the registry does not hold the Site chapter
65
66
  def self.vocabulary(registry)
66
67
  site = registry.bluebook("Site") or
@@ -69,7 +70,8 @@ module Hecks
69
70
  members = lambda do |name|
70
71
  route.value_objects.find { |object| object.hecks_name == name }.members.map { |member| member.fetch(:value) }
71
72
  end
72
- RowBuilder::VOCABULARY.transform_values(&members).merge(http_method: members.call("HttpMethod"))
73
+ closed = RowBuilder::VOCABULARY.transform_values(&members)
74
+ closed.merge(http_method: members.call("HttpMethod"), edge_function_kind: members.call("EdgeFunctionKind"))
73
75
  end
74
76
 
75
77
  # @param chapter [Bluebook::Chapter] a chapter to search
@@ -12,7 +12,9 @@
12
12
  # The same command projects the CDN: a project that adds Edge, EdgePolicy, EdgeOrigin and EdgeRule rows
13
13
  # to its chapter has the CloudFront behaviours and the listener rules written into the marked regions
14
14
  # of the template it owns (the `:site_cdn` projection). Those rows are the template's facts, so only the
15
- # closed sets they draw on, cache class and origin, are named here.
15
+ # closed sets they draw on, cache class, origin and the kind of an EdgeFunction row, are named here. A
16
+ # route's `edge:` names an EdgeFunction row, a viewer-request function the projection writes into the
17
+ # template's `edge_functions` region and attaches to that route's behaviour.
16
18
  #
17
19
  # Each run records that it was requested before the SiteToolchain port is asked, and what came of
18
20
  # it after. No command declares a role: the chapter boots standing alone.
@@ -33,6 +35,7 @@ Hecks.bluebook "Site" do
33
35
  attribute :source, Source, optional: true
34
36
  attribute :http_method, HttpMethod, optional: true
35
37
  attribute :preview, Preview, optional: true
38
+ attribute :edge, EdgeName, optional: true
36
39
 
37
40
  # `/blog/:slug.html` names a parameter; a trailing `/*` matches everything beneath a prefix. A
38
41
  # row with a `command:` or `query:` source may leave its path out, and the projection derives it
@@ -78,6 +81,18 @@ Hecks.bluebook "Site" do
78
81
  attribute :value, String, one_of: ["public", "draft"]
79
82
  end
80
83
 
84
+ # The logical id of an `EdgeFunction` row: the viewer-request function the route's behaviour runs.
85
+ value_object "EdgeName" do
86
+ attribute :value, String, pattern: '\A[A-Za-z][A-Za-z0-9]*\z'
87
+ end
88
+
89
+ # What an `EdgeFunction` row asks the projection to write: `redirect_host` answers a request for
90
+ # a watched path on an alias host with a redirect to the canonical host and leaves every other
91
+ # request alone.
92
+ value_object "EdgeFunctionKind" do
93
+ attribute :value, String, one_of: ["redirect_host"]
94
+ end
95
+
81
96
  # The HTTP verbs a route answers.
82
97
  value_object "HttpMethod" do
83
98
  attribute :value, String, one_of: ["GET", "POST", "PUT", "PATCH", "DELETE"]
@@ -6,7 +6,8 @@ require_relative "../tools"
6
6
  module Hecks
7
7
  module Tools
8
8
  # Projects each domain's OIDC client and scope manifest (`Hecks::Projections::OIDC`) into an
9
- # `oidc.json` beside the domain.
9
+ # `oidc.json` beside the domain, or into one `<chapter>.oidc.json` for each chapter of a
10
+ # directory that holds several.
10
11
  #
11
12
  # A domain that has no bluebook is skipped and one that cannot be projected is reported, so a
12
13
  # sweep over the whole checkout carries on past either. `hecks deploy
@@ -45,20 +46,57 @@ module Hecks
45
46
  .uniq.sort
46
47
  end
47
48
 
49
+ # Projects one manifest for each chapter the directory holds.
50
+ #
48
51
  # @param root [String] the checkout
49
52
  # @param path [String] a domain directory, relative to `root`
50
53
  # @return [void]
51
54
  def project(root, path)
52
- runtime = Hecks.boot(File.join(root, path), install_driving: false)
53
- name = runtime.registry.bluebooks.keys.first
54
- return warn(" #{path}: no bluebook found — skipped") unless name
55
+ registry = Hecks.boot(File.join(root, path), install_driving: false).registry
56
+ return warn(" #{path}: no bluebook found — skipped") if registry.bluebooks.empty?
55
57
 
56
- write_manifest(runtime.registry, name, File.join(root, path, "oidc.json"))
57
- puts " #{path}/oidc.json <- #{name}"
58
+ project_chapters(registry, root, path)
58
59
  rescue StandardError => e
59
60
  warn " #{path}: cannot project — #{e.message.lines.first.strip}"
60
61
  end
61
62
 
63
+ # The chapters a directory declares: those with a `.bluebook` file under it. A chapter it
64
+ # only attaches (Governance, for one) is loaded from elsewhere and has its own directory's
65
+ # manifest.
66
+ #
67
+ # @param registry [Hecks::Runtime::Registry] the booted registry of the directory
68
+ # @param directory [String] the directory that was booted
69
+ # @return [Array<String>] the chapter names, in load order; the first loaded when none has a
70
+ # recorded source
71
+ def chapters_of(registry, directory)
72
+ under = "#{File.expand_path(directory)}/"
73
+ own = registry.bluebooks.keys.select do |name|
74
+ Array(registry.bluebook_sources[name]).any? { |source| File.expand_path(source).start_with?(under) }
75
+ end
76
+ own.empty? ? registry.bluebooks.keys.first(1) : own
77
+ end
78
+
79
+ # @param registry [Hecks::Runtime::Registry] the booted registry of the directory
80
+ # @param root [String] the checkout
81
+ # @param path [String] the domain directory, relative to `root`
82
+ # @return [void]
83
+ def project_chapters(registry, root, path)
84
+ names = chapters_of(registry, File.join(root, path))
85
+ names.each do |name|
86
+ file = file_for(names, name)
87
+ write_manifest(registry, name, File.join(root, path, file))
88
+ puts " #{path}/#{file} <- #{name}"
89
+ end
90
+ end
91
+
92
+ # A domain of one chapter keeps `oidc.json`; a directory of several chapters, such as the
93
+ # framework members, gets one manifest each, so none of them stands for the others.
94
+ #
95
+ # @param names [Array<String>] every chapter the directory holds
96
+ # @param name [String] the chapter the manifest is for
97
+ # @return [String] the manifest's file name: `oidc.json`, or `<chapter>.oidc.json`
98
+ def file_for(names, name) = names.one? ? "oidc.json" : "#{Naming.snake(name)}.oidc.json"
99
+
62
100
  # @param registry [Hecks::Runtime::Registry] the booted registry
63
101
  # @param name [String] the bluebook to project
64
102
  # @param out [String] where the manifest goes
@@ -27,22 +27,19 @@ module Hecks
27
27
  regions = Projector.call(:site_cdn, bluebook: site.chapter, options: { table: site.table, edge: edge })
28
28
  relative = template || edge.setting.template
29
29
  source = template_source(root, template, relative)
30
- text = rewritten(File.read(source), regions, relative, edge)
30
+ text = rewritten(File.read(source), regions, relative)
31
31
  { destination(source, out, template, relative) => text }
32
32
  end
33
33
 
34
34
  # @param text [String] the template
35
35
  # @param regions [Hash{String => String}] each region's name to the block that goes in it
36
36
  # @param relative [String] the template's name, for a message
37
- # @param edge [Projections::Site::Edge] the checked edge
38
37
  # @return [String] the template with each region rewritten
39
- # @raise [SystemExit] when a region is missing, or a listener_rules region is left behind
40
- # by an edge with no load balancer
41
- def rewritten(text, regions, relative, edge)
42
- if !edge.alb? && Projections::Site::Regions.region?(text, "listener_rules")
43
- abort "project_site: #{relative} has a BEGIN/END GENERATED site_cdn listener_rules region, " \
44
- "and the Edge row says alb: false; remove the region"
45
- end
38
+ # @raise [SystemExit] when a region is missing, or a listener_rules or edge_functions region
39
+ # is left behind by an edge that has no load balancer or no EdgeFunction row
40
+ def rewritten(text, regions, relative)
41
+ refuse_leftover(text, regions, relative, "listener_rules", "the Edge row says alb: false")
42
+ refuse_leftover(text, regions, relative, "edge_functions", "the project declares no EdgeFunction rows")
46
43
  regions.reduce(text) do |current, (name, block)|
47
44
  unless Projections::Site::Regions.region?(current, name)
48
45
  abort "project_site: #{relative} has no BEGIN/END GENERATED site_cdn #{name} region"
@@ -52,6 +49,13 @@ module Hecks
52
49
  end
53
50
  end
54
51
 
52
+ # Refuses a region the edge does not use, so the stale text in it is removed by hand.
53
+ def refuse_leftover(text, regions, relative, name, reason)
54
+ return if regions.key?(name) || !Projections::Site::Regions.region?(text, name)
55
+
56
+ abort "project_site: #{relative} has a BEGIN/END GENERATED site_cdn #{name} region, and #{reason}; remove the region"
57
+ end
58
+
55
59
  # @return [Hash] nothing, after refusing a template named for a project with no edge
56
60
  def missing_edge(template)
57
61
  abort "project_site: --template names #{template}, but the project declares no Edge rows" if template
data/lib/hecks/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  module Hecks
2
2
  # Read directly by the gemspec, so this file must never require the
3
3
  # rest of the gem (its dependencies aren't resolved yet at that point).
4
- VERSION = "3.12.0".freeze
4
+ VERSION = "3.13.0".freeze
5
5
  end