Module: Moult::Boundaries::Packwerk
- Defined in:
- lib/moult/boundaries/packwerk.rb
Overview
The architecture-boundary adapter — Moult's reader of Packwerk's on-disk artifacts and the only file that names Packwerk. Everything downstream consumes the Moult-owned Violation/Result value objects, never a packwerk type, so the backend is swappable (the "swap, not rewrite" invariant).
Like Coverage (which ingests SimpleCov/stdlib coverage files), this slice
ingests packwerk's files rather than booting it: a live bin/packwerk check
needs a bootable Rails/Zeitwerk app and emits only human prose / new-violation
deltas, whereas packwerk serialises every recorded violation to stable,
diffable package_todo.yml files. We read those (the package graph + the
recorded violations packwerk already resolved via Zeitwerk) and own no part of
the constant-resolution graph. Consequently Moult needs NO packwerk gem
dependency (exactly as Coverage needs no simplecov). Live re-analysis — the
fresh, line-level offense set — is deferred, the same way the Coverband and
Flipper live stores are.
The package_todo.yml shape we parse (packwerk's own serialization):
<defining-package>: # the package that OWNS the referenced constant
"::Some::Constant": # the constant crossing the boundary
violations:
- dependency # one or more violation types
- privacy
files:
- path/to/referencing.rb # the referencing files (root-relative)
The file lives at <referencing-package-dir>/package_todo.yml, so the
referencing package is the file's directory (root-relative; "." for the root
package). packwerk reports violations at FILE granularity (no line numbers),
which fixes this slice's join at path level.
Defined Under Namespace
Class Method Summary collapse
-
.backend_version ⇒ Object
packwerk is not a Moult dependency, so its constant is normally absent; the version is recorded when it happens to be loaded, else nil (nullable in the contract).
-
.configured?(root) ⇒ Boolean
A
packwerk.ymlat the root is the unambiguous "this is a packwerk project" marker (it is required for any packwerk run). - .detect(root:) ⇒ Result
-
.package_name(dir, root) ⇒ Object
Root-relative package name; "." for the root package (packwerk's convention).
- .todo_files(root) ⇒ Object
-
.violations_in(file, root) ⇒ Object
Parse one
package_todo.ymlinto flat Violations.
Class Method Details
.backend_version ⇒ Object
packwerk is not a Moult dependency, so its constant is normally absent; the version is recorded when it happens to be loaded, else nil (nullable in the contract). This is the only reference to the Packwerk constant in Moult.
109 110 111 |
# File 'lib/moult/boundaries/packwerk.rb', line 109 def backend_version defined?(::Packwerk::VERSION) ? ::Packwerk::VERSION : nil end |
.configured?(root) ⇒ Boolean
A packwerk.yml at the root is the unambiguous "this is a packwerk project"
marker (it is required for any packwerk run).
64 65 66 |
# File 'lib/moult/boundaries/packwerk.rb', line 64 def configured?(root) File.exist?(File.join(root, "packwerk.yml")) end |
.detect(root:) ⇒ Result
53 54 55 56 57 58 59 60 |
# File 'lib/moult/boundaries/packwerk.rb', line 53 def detect(root:) unless configured?(root) return Result.new(violations: [], backend: "packwerk", backend_version: backend_version, configured: false) end violations = todo_files(root).flat_map { |file| violations_in(file, root) } Result.new(violations: violations, backend: "packwerk", backend_version: backend_version, configured: true) end |
.package_name(dir, root) ⇒ Object
Root-relative package name; "." for the root package (packwerk's convention).
102 103 104 |
# File 'lib/moult/boundaries/packwerk.rb', line 102 def package_name(dir, root) SymbolId.relative_path(dir, root) end |
.todo_files(root) ⇒ Object
68 69 70 |
# File 'lib/moult/boundaries/packwerk.rb', line 68 def todo_files(root) Dir.glob(File.join(root, "**", "package_todo.yml")).sort end |
.violations_in(file, root) ⇒ Object
Parse one package_todo.yml into flat Violations. The referencing package
is the file's directory (root-relative). A malformed/empty file is skipped
rather than crashing the whole run.
75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 |
# File 'lib/moult/boundaries/packwerk.rb', line 75 def violations_in(file, root) referencing_package = package_name(File.dirname(file), root) data = YAML.safe_load_file(file) return [] unless data.is_a?(Hash) data.flat_map do |defining_package, constants| next [] unless constants.is_a?(Hash) constants.flat_map do |constant, detail| next [] unless detail.is_a?(Hash) types = Array(detail["violations"]) paths = Array(detail["files"]) types.product(paths).map do |type, path| Violation.new( violation_type: type.to_s, referencing_package: referencing_package, defining_package: defining_package.to_s, constant: constant.to_s, path: path.to_s ) end end end rescue Psych::Exception [] end |