packwerk-extensions 0.0.5 → 0.0.7
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/README.md +41 -23
 - data/lib/packwerk/architecture/checker.rb +96 -0
 - data/lib/packwerk/architecture/layers.rb +38 -0
 - data/lib/packwerk/architecture/package.rb +54 -0
 - data/lib/packwerk/architecture/validator.rb +112 -0
 - data/lib/packwerk-extensions.rb +4 -0
 - metadata +6 -2
 
    
        checksums.yaml
    CHANGED
    
    | 
         @@ -1,7 +1,7 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            ---
         
     | 
| 
       2 
2 
     | 
    
         
             
            SHA256:
         
     | 
| 
       3 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       4 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 3 
     | 
    
         
            +
              metadata.gz: 558107be67a748dfb9a175b9d90be2acc373913f36a66c5bccbb3401e80cd85f
         
     | 
| 
      
 4 
     | 
    
         
            +
              data.tar.gz: fb98b8c64bb3043d267aab3d966d404934a5a530020dc18e3e599a13153fe121
         
     | 
| 
       5 
5 
     | 
    
         
             
            SHA512:
         
     | 
| 
       6 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       7 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 6 
     | 
    
         
            +
              metadata.gz: e6beb350fd3089406f7a0ec0409b79f261136b89ffe0ede267215893ca7abb9f5613a7aaf44248f1c0b53560a0596a78739568f85bd2a081edf4b3d0f69fe0d0
         
     | 
| 
      
 7 
     | 
    
         
            +
              data.tar.gz: 706bc3779b3af6ce3d6780ba9106a8bb4fc84cd81ddd93c7809b62f2663de1baf4c906f961abc6a4982c8815142478d839054b27c504639e1eb5dc88e0b267e3
         
     | 
    
        data/README.md
    CHANGED
    
    | 
         @@ -2,40 +2,29 @@ 
     | 
|
| 
       2 
2 
     | 
    
         | 
| 
       3 
3 
     | 
    
         
             
            `packwerk-extensions` is a home for checker extensions for packwerk.
         
     | 
| 
       4 
4 
     | 
    
         | 
| 
       5 
     | 
    
         
            -
             
     | 
| 
      
 5 
     | 
    
         
            +
            Currently, it ships the following checkers to help improve the boundaries between packages. These checkers are:
         
     | 
| 
      
 6 
     | 
    
         
            +
            - A `privacy` checker that ensures other packages are using your package's public API
         
     | 
| 
      
 7 
     | 
    
         
            +
            - A `visibility` checker that allows packages to be private except to an explicit group of other packages.
         
     | 
| 
      
 8 
     | 
    
         
            +
            - An experimental `architecture` checker that allows packages to specify their "layer" and requires that each layer only communicate with layers below it.
         
     | 
| 
       6 
9 
     | 
    
         | 
| 
      
 10 
     | 
    
         
            +
            ## Privacy Checker
         
     | 
| 
       7 
11 
     | 
    
         
             
            The privacy checker extension was originally extracted from [packwerk](https://github.com/Shopify/packwerk).
         
     | 
| 
       8 
12 
     | 
    
         | 
| 
       9 
13 
     | 
    
         
             
            A package's privacy boundary is violated when there is a reference to the package's private constants from a source outside the package.
         
     | 
| 
       10 
14 
     | 
    
         | 
| 
       11 
     | 
    
         
            -
             
     | 
| 
       12 
     | 
    
         
            -
             
     | 
| 
       13 
     | 
    
         
            -
            1. Enforce privacy for all external sources
         
     | 
| 
      
 15 
     | 
    
         
            +
            To enforce privacy for your package, set `enforce_privacy` to `true` on your pack:
         
     | 
| 
       14 
16 
     | 
    
         | 
| 
       15 
17 
     | 
    
         
             
            ```yaml
         
     | 
| 
       16 
18 
     | 
    
         
             
            # components/merchandising/package.yml
         
     | 
| 
       17 
     | 
    
         
            -
            enforce_privacy: true 
     | 
| 
       18 
     | 
    
         
            -
                                    # the components/merchandising/app/public folder
         
     | 
| 
      
 19 
     | 
    
         
            +
            enforce_privacy: true
         
     | 
| 
       19 
20 
     | 
    
         
             
            ```
         
     | 
| 
       20 
21 
     | 
    
         | 
| 
       21 
22 
     | 
    
         
             
            Setting `enforce_privacy` to true will make all references to private constants in your package a violation.
         
     | 
| 
       22 
23 
     | 
    
         | 
| 
       23 
     | 
    
         
            -
             
     | 
| 
       24 
     | 
    
         
            -
             
     | 
| 
       25 
     | 
    
         
            -
            ```yaml
         
     | 
| 
       26 
     | 
    
         
            -
            # components/merchandising/package.yml
         
     | 
| 
       27 
     | 
    
         
            -
            enforce_privacy:
         
     | 
| 
       28 
     | 
    
         
            -
              - "::Merchandising::Product"
         
     | 
| 
       29 
     | 
    
         
            -
              - "::SomeNamespace"  # enforces privacy for the namespace and
         
     | 
| 
       30 
     | 
    
         
            -
                                   # everything nested in it
         
     | 
| 
       31 
     | 
    
         
            -
            ```
         
     | 
| 
       32 
     | 
    
         
            -
             
     | 
| 
       33 
     | 
    
         
            -
            It will be a privacy violation when a file outside of the `components/merchandising` package tries to reference `Merchandising::Product`.
         
     | 
| 
       34 
     | 
    
         
            -
             
     | 
| 
       35 
     | 
    
         
            -
            ##### Using public folders
         
     | 
| 
      
 24 
     | 
    
         
            +
            ### Using public folders
         
     | 
| 
       36 
25 
     | 
    
         
             
            You may enforce privacy either way mentioned above and still expose a public API for your package by placing constants in the public folder, which by default is `app/public`. The constants in the public folder will be made available for use by the rest of the application.
         
     | 
| 
       37 
26 
     | 
    
         | 
| 
       38 
     | 
    
         
            -
             
     | 
| 
      
 27 
     | 
    
         
            +
            ### Defining your own public folder
         
     | 
| 
       39 
28 
     | 
    
         | 
| 
       40 
29 
     | 
    
         
             
            You may prefer to override the default public folder, you can do so on a per-package basis by defining a `public_path`.
         
     | 
| 
       41 
30 
     | 
    
         | 
| 
         @@ -48,8 +37,6 @@ public_path: my/custom/path/ 
     | 
|
| 
       48 
37 
     | 
    
         
             
            ### Package Privacy violation
         
     | 
| 
       49 
38 
     | 
    
         
             
            A constant that is private to its package has been referenced from outside of the package. Constants are declared private in their package’s `package.yml`.
         
     | 
| 
       50 
39 
     | 
    
         | 
| 
       51 
     | 
    
         
            -
            See: [USAGE.md - Enforcing privacy boundary](USAGE.md#Enforcing-privacy-boundary)
         
     | 
| 
       52 
     | 
    
         
            -
             
     | 
| 
       53 
40 
     | 
    
         
             
            #### Interpreting Privacy violation
         
     | 
| 
       54 
41 
     | 
    
         | 
| 
       55 
42 
     | 
    
         
             
            > /Users/JaneDoe/src/github.com/sample-project/user/app/controllers/labels_controller.rb:170:30
         
     | 
| 
         @@ -60,7 +47,38 @@ See: [USAGE.md - Enforcing privacy boundary](USAGE.md#Enforcing-privacy-boundary 
     | 
|
| 
       60 
47 
     | 
    
         | 
| 
       61 
48 
     | 
    
         
             
            There has been a privacy violation of the package `billing` in the package `user`, through the use of the constant `Billing::CarrierInvoiceTransaction` in the file `user/app/controllers/labels_controller.rb`.
         
     | 
| 
       62 
49 
     | 
    
         | 
| 
       63 
     | 
    
         
            -
             
     | 
| 
      
 50 
     | 
    
         
            +
            #### Suggestions
         
     | 
| 
       64 
51 
     | 
    
         
             
            You may be accessing the implementation of a piece of functionality that is supposed to be accessed through a public interface on the package. Try to use the public interface instead. A package’s public interface should be defined in its `app/public` folder and documented.
         
     | 
| 
       65 
52 
     | 
    
         | 
| 
       66 
53 
     | 
    
         
             
            The functionality you’re looking for may not be intended to be reused across packages at all. If there is no public interface for it but you have a good reason to use it from outside of its package, find the people responsible for the package and discuss a solution with them.
         
     | 
| 
      
 54 
     | 
    
         
            +
             
     | 
| 
      
 55 
     | 
    
         
            +
            ## Visibility Checker
         
     | 
| 
      
 56 
     | 
    
         
            +
            The visibility checker can be used to allow a package to be private implementation detail of other packages.
         
     | 
| 
      
 57 
     | 
    
         
            +
             
     | 
| 
      
 58 
     | 
    
         
            +
            To enforce visibility for your package, set `enforce_visibility` to `true` on your pack and specify `visible_to` for other packages that can use your package.
         
     | 
| 
      
 59 
     | 
    
         
            +
             
     | 
| 
      
 60 
     | 
    
         
            +
            ```yaml
         
     | 
| 
      
 61 
     | 
    
         
            +
            # components/merchandising/package.yml
         
     | 
| 
      
 62 
     | 
    
         
            +
            enforce_visibility: true
         
     | 
| 
      
 63 
     | 
    
         
            +
            visible_to:
         
     | 
| 
      
 64 
     | 
    
         
            +
              - components/other_package
         
     | 
| 
      
 65 
     | 
    
         
            +
            ```
         
     | 
| 
      
 66 
     | 
    
         
            +
             
     | 
| 
      
 67 
     | 
    
         
            +
            ## Architecture Checker
         
     | 
| 
      
 68 
     | 
    
         
            +
            The architecture checker can be used to enforce constraints on what can depend on what.
         
     | 
| 
      
 69 
     | 
    
         
            +
             
     | 
| 
      
 70 
     | 
    
         
            +
            To enforce architecture for your package, first define the `architecture_layers` in `packwerk.yml`, for example:
         
     | 
| 
      
 71 
     | 
    
         
            +
            ```
         
     | 
| 
      
 72 
     | 
    
         
            +
            architecture_layers:
         
     | 
| 
      
 73 
     | 
    
         
            +
              - package
         
     | 
| 
      
 74 
     | 
    
         
            +
              - utility
         
     | 
| 
      
 75 
     | 
    
         
            +
            ```
         
     | 
| 
      
 76 
     | 
    
         
            +
             
     | 
| 
      
 77 
     | 
    
         
            +
            Then, turn on the checker in your package:
         
     | 
| 
      
 78 
     | 
    
         
            +
            ```yaml
         
     | 
| 
      
 79 
     | 
    
         
            +
            # components/merchandising/package.yml
         
     | 
| 
      
 80 
     | 
    
         
            +
            enforce_architecture: true
         
     | 
| 
      
 81 
     | 
    
         
            +
            layer: utility
         
     | 
| 
      
 82 
     | 
    
         
            +
            ```
         
     | 
| 
      
 83 
     | 
    
         
            +
             
     | 
| 
      
 84 
     | 
    
         
            +
            Now this pack can only depend on other utility packages.
         
     | 
| 
         @@ -0,0 +1,96 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # typed: strict
         
     | 
| 
      
 2 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
            require 'packwerk/architecture/layers'
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
            module Packwerk
         
     | 
| 
      
 7 
     | 
    
         
            +
              module Architecture
         
     | 
| 
      
 8 
     | 
    
         
            +
                # This enforces "layered architecture," which allows each class to be designated as one of N layers
         
     | 
| 
      
 9 
     | 
    
         
            +
                # configured by the client in `packwerk.yml`, for example:
         
     | 
| 
      
 10 
     | 
    
         
            +
                #
         
     | 
| 
      
 11 
     | 
    
         
            +
                # architecture_layers:
         
     | 
| 
      
 12 
     | 
    
         
            +
                #   - orchestrator
         
     | 
| 
      
 13 
     | 
    
         
            +
                #   - business_domain
         
     | 
| 
      
 14 
     | 
    
         
            +
                #   - platform
         
     | 
| 
      
 15 
     | 
    
         
            +
                #   - utility
         
     | 
| 
      
 16 
     | 
    
         
            +
                #   - specification
         
     | 
| 
      
 17 
     | 
    
         
            +
                #
         
     | 
| 
      
 18 
     | 
    
         
            +
                # Then a package can configure:
         
     | 
| 
      
 19 
     | 
    
         
            +
                # enforce_architecture: true | false | strict
         
     | 
| 
      
 20 
     | 
    
         
            +
                # layer: utility
         
     | 
| 
      
 21 
     | 
    
         
            +
                #
         
     | 
| 
      
 22 
     | 
    
         
            +
                # This is intended to provide:
         
     | 
| 
      
 23 
     | 
    
         
            +
                # A) Direction for which dependency violations to tackle
         
     | 
| 
      
 24 
     | 
    
         
            +
                # B) What dependencies should or should not exist
         
     | 
| 
      
 25 
     | 
    
         
            +
                # C) A potential sequencing for modularizing a system (starting with lower layers first).
         
     | 
| 
      
 26 
     | 
    
         
            +
                #
         
     | 
| 
      
 27 
     | 
    
         
            +
                class Checker
         
     | 
| 
      
 28 
     | 
    
         
            +
                  extend T::Sig
         
     | 
| 
      
 29 
     | 
    
         
            +
                  include Packwerk::Checker
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
                  VIOLATION_TYPE = T.let('architecture', String)
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
                  sig { override.returns(String) }
         
     | 
| 
      
 34 
     | 
    
         
            +
                  def violation_type
         
     | 
| 
      
 35 
     | 
    
         
            +
                    VIOLATION_TYPE
         
     | 
| 
      
 36 
     | 
    
         
            +
                  end
         
     | 
| 
      
 37 
     | 
    
         
            +
             
     | 
| 
      
 38 
     | 
    
         
            +
                  sig do
         
     | 
| 
      
 39 
     | 
    
         
            +
                    override
         
     | 
| 
      
 40 
     | 
    
         
            +
                      .params(reference: Packwerk::Reference)
         
     | 
| 
      
 41 
     | 
    
         
            +
                      .returns(T::Boolean)
         
     | 
| 
      
 42 
     | 
    
         
            +
                  end
         
     | 
| 
      
 43 
     | 
    
         
            +
                  def invalid_reference?(reference)
         
     | 
| 
      
 44 
     | 
    
         
            +
                    constant_package = Package.from(reference.constant.package, layers)
         
     | 
| 
      
 45 
     | 
    
         
            +
                    referencing_package = Package.from(reference.package, layers)
         
     | 
| 
      
 46 
     | 
    
         
            +
                    !referencing_package.can_depend_on?(constant_package, layers: layers)
         
     | 
| 
      
 47 
     | 
    
         
            +
                  end
         
     | 
| 
      
 48 
     | 
    
         
            +
             
     | 
| 
      
 49 
     | 
    
         
            +
                  sig do
         
     | 
| 
      
 50 
     | 
    
         
            +
                    override
         
     | 
| 
      
 51 
     | 
    
         
            +
                      .params(listed_offense: Packwerk::ReferenceOffense)
         
     | 
| 
      
 52 
     | 
    
         
            +
                      .returns(T::Boolean)
         
     | 
| 
      
 53 
     | 
    
         
            +
                  end
         
     | 
| 
      
 54 
     | 
    
         
            +
                  def strict_mode_violation?(listed_offense)
         
     | 
| 
      
 55 
     | 
    
         
            +
                    constant_package = listed_offense.reference.package
         
     | 
| 
      
 56 
     | 
    
         
            +
                    constant_package.config['enforce_architecture'] == 'strict'
         
     | 
| 
      
 57 
     | 
    
         
            +
                  end
         
     | 
| 
      
 58 
     | 
    
         
            +
             
     | 
| 
      
 59 
     | 
    
         
            +
                  sig do
         
     | 
| 
      
 60 
     | 
    
         
            +
                    override
         
     | 
| 
      
 61 
     | 
    
         
            +
                      .params(reference: Packwerk::Reference)
         
     | 
| 
      
 62 
     | 
    
         
            +
                      .returns(String)
         
     | 
| 
      
 63 
     | 
    
         
            +
                  end
         
     | 
| 
      
 64 
     | 
    
         
            +
                  def message(reference)
         
     | 
| 
      
 65 
     | 
    
         
            +
                    constant_package = Package.from(reference.constant.package, layers)
         
     | 
| 
      
 66 
     | 
    
         
            +
                    referencing_package = Package.from(reference.package, layers)
         
     | 
| 
      
 67 
     | 
    
         
            +
             
     | 
| 
      
 68 
     | 
    
         
            +
                    message = <<~MESSAGE
         
     | 
| 
      
 69 
     | 
    
         
            +
                      Architecture layer violation: '#{reference.constant.name}' belongs to '#{reference.constant.package}', whose architecture layer type is "#{constant_package.layer}."
         
     | 
| 
      
 70 
     | 
    
         
            +
                      This constant cannot be referenced by '#{reference.package}', whose architecture layer type is "#{referencing_package.layer}."
         
     | 
| 
      
 71 
     | 
    
         
            +
                      Can we organize our code logic to respect the layers of these packs? See all layers in packwerk.yml.
         
     | 
| 
      
 72 
     | 
    
         
            +
             
     | 
| 
      
 73 
     | 
    
         
            +
                      #{standard_help_message(reference)}
         
     | 
| 
      
 74 
     | 
    
         
            +
                    MESSAGE
         
     | 
| 
      
 75 
     | 
    
         
            +
             
     | 
| 
      
 76 
     | 
    
         
            +
                    message.chomp
         
     | 
| 
      
 77 
     | 
    
         
            +
                  end
         
     | 
| 
      
 78 
     | 
    
         
            +
             
     | 
| 
      
 79 
     | 
    
         
            +
                  # TODO: Extract this out into a common helper, can call it StandardViolationHelpMessage.new(...) and implements .to_s
         
     | 
| 
      
 80 
     | 
    
         
            +
                  sig { params(reference: Reference).returns(String) }
         
     | 
| 
      
 81 
     | 
    
         
            +
                  def standard_help_message(reference)
         
     | 
| 
      
 82 
     | 
    
         
            +
                    standard_message = <<~MESSAGE.chomp
         
     | 
| 
      
 83 
     | 
    
         
            +
                      Inference details: this is a reference to #{reference.constant.name} which seems to be defined in #{reference.constant.location}.
         
     | 
| 
      
 84 
     | 
    
         
            +
                      To receive help interpreting or resolving this error message, see: https://github.com/Shopify/packwerk/blob/main/TROUBLESHOOT.md#Troubleshooting-violations
         
     | 
| 
      
 85 
     | 
    
         
            +
                    MESSAGE
         
     | 
| 
      
 86 
     | 
    
         
            +
             
     | 
| 
      
 87 
     | 
    
         
            +
                    standard_message.chomp
         
     | 
| 
      
 88 
     | 
    
         
            +
                  end
         
     | 
| 
      
 89 
     | 
    
         
            +
             
     | 
| 
      
 90 
     | 
    
         
            +
                  sig { returns(Layers) }
         
     | 
| 
      
 91 
     | 
    
         
            +
                  def layers
         
     | 
| 
      
 92 
     | 
    
         
            +
                    @layers ||= T.let(Layers.new, T.nilable(Packwerk::Architecture::Layers))
         
     | 
| 
      
 93 
     | 
    
         
            +
                  end
         
     | 
| 
      
 94 
     | 
    
         
            +
                end
         
     | 
| 
      
 95 
     | 
    
         
            +
              end
         
     | 
| 
      
 96 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,38 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # typed: strict
         
     | 
| 
      
 2 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
            module Packwerk
         
     | 
| 
      
 5 
     | 
    
         
            +
              module Architecture
         
     | 
| 
      
 6 
     | 
    
         
            +
                class Layers
         
     | 
| 
      
 7 
     | 
    
         
            +
                  extend T::Sig
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
                  sig { void }
         
     | 
| 
      
 10 
     | 
    
         
            +
                  def initialize
         
     | 
| 
      
 11 
     | 
    
         
            +
                    @names = T.let(@names, T.nilable(T::Set[String]))
         
     | 
| 
      
 12 
     | 
    
         
            +
                    @names_list = T.let(@names_list, T.nilable(T::Array[String]))
         
     | 
| 
      
 13 
     | 
    
         
            +
                  end
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
                  sig { params(layer: String).returns(Integer) }
         
     | 
| 
      
 16 
     | 
    
         
            +
                  def index_of(layer)
         
     | 
| 
      
 17 
     | 
    
         
            +
                    index = names_list.reverse.find_index(layer)
         
     | 
| 
      
 18 
     | 
    
         
            +
                    if index.nil?
         
     | 
| 
      
 19 
     | 
    
         
            +
                      raise "Layer #{layer} not find, please run `bin/packwerk validate`"
         
     | 
| 
      
 20 
     | 
    
         
            +
                    end
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
                    index
         
     | 
| 
      
 23 
     | 
    
         
            +
                  end
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
                  sig { returns(T::Set[String]) }
         
     | 
| 
      
 26 
     | 
    
         
            +
                  def names
         
     | 
| 
      
 27 
     | 
    
         
            +
                    @names ||= Set.new(names_list)
         
     | 
| 
      
 28 
     | 
    
         
            +
                  end
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
                  private
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
                  sig { returns(T::Array[String]) }
         
     | 
| 
      
 33 
     | 
    
         
            +
                  def names_list
         
     | 
| 
      
 34 
     | 
    
         
            +
                    @names_list ||= YAML.load_file('packwerk.yml')['architecture_layers'] || []
         
     | 
| 
      
 35 
     | 
    
         
            +
                  end
         
     | 
| 
      
 36 
     | 
    
         
            +
                end
         
     | 
| 
      
 37 
     | 
    
         
            +
              end
         
     | 
| 
      
 38 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,54 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # typed: strict
         
     | 
| 
      
 2 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
            module Packwerk
         
     | 
| 
      
 5 
     | 
    
         
            +
              module Architecture
         
     | 
| 
      
 6 
     | 
    
         
            +
                class Package < T::Struct
         
     | 
| 
      
 7 
     | 
    
         
            +
                  extend T::Sig
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
                  const :layer, T.nilable(String)
         
     | 
| 
      
 10 
     | 
    
         
            +
                  const :enforcement_setting, T.nilable(T.any(T::Boolean, String, T::Array[String]))
         
     | 
| 
      
 11 
     | 
    
         
            +
                  const :config, T::Hash[T.untyped, T.untyped]
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                  sig { returns(T::Boolean) }
         
     | 
| 
      
 14 
     | 
    
         
            +
                  def enforces?
         
     | 
| 
      
 15 
     | 
    
         
            +
                    enforcement_setting == true || enforcement_setting == 'strict'
         
     | 
| 
      
 16 
     | 
    
         
            +
                  end
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
                  sig { params(other_package: Package, layers: Layers).returns(T::Boolean) }
         
     | 
| 
      
 19 
     | 
    
         
            +
                  def can_depend_on?(other_package, layers:)
         
     | 
| 
      
 20 
     | 
    
         
            +
                    return true if !enforces?
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
                    flow_sensitive_layer = layer
         
     | 
| 
      
 23 
     | 
    
         
            +
                    flow_sensitive_other_layer = other_package.layer
         
     | 
| 
      
 24 
     | 
    
         
            +
                    return true if flow_sensitive_layer.nil?
         
     | 
| 
      
 25 
     | 
    
         
            +
                    return true if flow_sensitive_other_layer.nil?
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
                    layers.index_of(flow_sensitive_layer) >= layers.index_of(flow_sensitive_other_layer)
         
     | 
| 
      
 28 
     | 
    
         
            +
                  end
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
                  class << self
         
     | 
| 
      
 31 
     | 
    
         
            +
                    extend T::Sig
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
                    sig { params(package: ::Packwerk::Package, layers: Layers).returns(Package) }
         
     | 
| 
      
 34 
     | 
    
         
            +
                    def from(package, layers)
         
     | 
| 
      
 35 
     | 
    
         
            +
                      config = package.config
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
                      # This allows the layer to be inferred based on the package root
         
     | 
| 
      
 38 
     | 
    
         
            +
                      package_root = package.name.split('/').first
         
     | 
| 
      
 39 
     | 
    
         
            +
                      if package_root && layers.names.include?(package_root)
         
     | 
| 
      
 40 
     | 
    
         
            +
                        layer = package_root
         
     | 
| 
      
 41 
     | 
    
         
            +
                      else
         
     | 
| 
      
 42 
     | 
    
         
            +
                        layer = config['layer']
         
     | 
| 
      
 43 
     | 
    
         
            +
                      end
         
     | 
| 
      
 44 
     | 
    
         
            +
             
     | 
| 
      
 45 
     | 
    
         
            +
                      Package.new(
         
     | 
| 
      
 46 
     | 
    
         
            +
                        layer: layer,
         
     | 
| 
      
 47 
     | 
    
         
            +
                        enforcement_setting: config['enforce_architecture'],
         
     | 
| 
      
 48 
     | 
    
         
            +
                        config: config
         
     | 
| 
      
 49 
     | 
    
         
            +
                      )
         
     | 
| 
      
 50 
     | 
    
         
            +
                    end
         
     | 
| 
      
 51 
     | 
    
         
            +
                  end
         
     | 
| 
      
 52 
     | 
    
         
            +
                end
         
     | 
| 
      
 53 
     | 
    
         
            +
              end
         
     | 
| 
      
 54 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,112 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # typed: strict
         
     | 
| 
      
 2 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
            module Packwerk
         
     | 
| 
      
 5 
     | 
    
         
            +
              module Architecture
         
     | 
| 
      
 6 
     | 
    
         
            +
                class Validator
         
     | 
| 
      
 7 
     | 
    
         
            +
                  extend T::Sig
         
     | 
| 
      
 8 
     | 
    
         
            +
                  include Packwerk::Validator
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
                  Result = Packwerk::Validator::Result
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
                  sig { override.params(package_set: PackageSet, configuration: Configuration).returns(Result) }
         
     | 
| 
      
 13 
     | 
    
         
            +
                  def call(package_set, configuration)
         
     | 
| 
      
 14 
     | 
    
         
            +
                    results = T.let([], T::Array[Result])
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
                    package_set.each do |package|
         
     | 
| 
      
 17 
     | 
    
         
            +
                      config = package.config
         
     | 
| 
      
 18 
     | 
    
         
            +
                      f = Pathname.new(package.name).join('package.yml').to_s
         
     | 
| 
      
 19 
     | 
    
         
            +
                      next if !config
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
                      result = check_enforce_architecture_setting(f, config['enforce_architecture'])
         
     | 
| 
      
 22 
     | 
    
         
            +
                      results << result
         
     | 
| 
      
 23 
     | 
    
         
            +
                      next if !result.ok?
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
                      result = check_layer_setting(config, f, config['layer'])
         
     | 
| 
      
 26 
     | 
    
         
            +
                      results << result
         
     | 
| 
      
 27 
     | 
    
         
            +
                      next if !result.ok?
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
                      package = Package.from(package, layers)
         
     | 
| 
      
 30 
     | 
    
         
            +
                      results += check_dependencies_setting(package_set, package, f)
         
     | 
| 
      
 31 
     | 
    
         
            +
                    end
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
                    merge_results(results, separator: "\n---\n")
         
     | 
| 
      
 34 
     | 
    
         
            +
                  end
         
     | 
| 
      
 35 
     | 
    
         
            +
             
     | 
| 
      
 36 
     | 
    
         
            +
                  sig { returns(Layers) }
         
     | 
| 
      
 37 
     | 
    
         
            +
                  def layers
         
     | 
| 
      
 38 
     | 
    
         
            +
                    @layers ||= T.let(Layers.new, T.nilable(Packwerk::Architecture::Layers))
         
     | 
| 
      
 39 
     | 
    
         
            +
                  end
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
                  sig { override.returns(T::Array[String]) }
         
     | 
| 
      
 42 
     | 
    
         
            +
                  def permitted_keys
         
     | 
| 
      
 43 
     | 
    
         
            +
                    %w[enforce_architecture layer]
         
     | 
| 
      
 44 
     | 
    
         
            +
                  end
         
     | 
| 
      
 45 
     | 
    
         
            +
             
     | 
| 
      
 46 
     | 
    
         
            +
                  sig do
         
     | 
| 
      
 47 
     | 
    
         
            +
                    params(package_set: PackageSet, package: Package, config_file_path: String).returns(T::Array[Result])
         
     | 
| 
      
 48 
     | 
    
         
            +
                  end
         
     | 
| 
      
 49 
     | 
    
         
            +
                  def check_dependencies_setting(package_set, package, config_file_path)
         
     | 
| 
      
 50 
     | 
    
         
            +
                    results = T.let([], T::Array[Result])
         
     | 
| 
      
 51 
     | 
    
         
            +
                    package.config.fetch('dependencies', []).each do |dependency|
         
     | 
| 
      
 52 
     | 
    
         
            +
                      other_packwerk_package = package_set.fetch(dependency)
         
     | 
| 
      
 53 
     | 
    
         
            +
                      next if other_packwerk_package.nil?
         
     | 
| 
      
 54 
     | 
    
         
            +
             
     | 
| 
      
 55 
     | 
    
         
            +
                      other_package = Package.from(other_packwerk_package, layers)
         
     | 
| 
      
 56 
     | 
    
         
            +
                      next if package.can_depend_on?(other_package, layers: layers)
         
     | 
| 
      
 57 
     | 
    
         
            +
             
     | 
| 
      
 58 
     | 
    
         
            +
                      results << Result.new(
         
     | 
| 
      
 59 
     | 
    
         
            +
                        ok: false,
         
     | 
| 
      
 60 
     | 
    
         
            +
                        error_value: "Invalid 'dependencies' in #{config_file_path.inspect}. '#{config_file_path}' has a layer type of '#{package.layer},' which cannot rely on '#{other_packwerk_package.name},' which has a layer type of '#{other_package.layer}.' `architecture_layers` can be found in packwerk.yml."
         
     | 
| 
      
 61 
     | 
    
         
            +
                      )
         
     | 
| 
      
 62 
     | 
    
         
            +
                    end
         
     | 
| 
      
 63 
     | 
    
         
            +
             
     | 
| 
      
 64 
     | 
    
         
            +
                    results
         
     | 
| 
      
 65 
     | 
    
         
            +
                  end
         
     | 
| 
      
 66 
     | 
    
         
            +
             
     | 
| 
      
 67 
     | 
    
         
            +
                  sig do
         
     | 
| 
      
 68 
     | 
    
         
            +
                    params(config: T::Hash[T.untyped, T.untyped], config_file_path: String, layer: T.untyped).returns(Result)
         
     | 
| 
      
 69 
     | 
    
         
            +
                  end
         
     | 
| 
      
 70 
     | 
    
         
            +
                  def check_layer_setting(config, config_file_path, layer)
         
     | 
| 
      
 71 
     | 
    
         
            +
                    enforce_architecture = config['enforce_architecture']
         
     | 
| 
      
 72 
     | 
    
         
            +
                    enforce_architecture_enabled = !(enforce_architecture.nil? || enforce_architecture == false)
         
     | 
| 
      
 73 
     | 
    
         
            +
                    valid_layer = layer.nil? || layers.names.include?(layer)
         
     | 
| 
      
 74 
     | 
    
         
            +
             
     | 
| 
      
 75 
     | 
    
         
            +
                    if layer.nil? && enforce_architecture_enabled
         
     | 
| 
      
 76 
     | 
    
         
            +
                      Result.new(
         
     | 
| 
      
 77 
     | 
    
         
            +
                        ok: false,
         
     | 
| 
      
 78 
     | 
    
         
            +
                        error_value: "Invalid 'layer' option in #{config_file_path.inspect}: #{layer.inspect}. `layer` must be set if `enforce_architecture` is on."
         
     | 
| 
      
 79 
     | 
    
         
            +
                      )
         
     | 
| 
      
 80 
     | 
    
         
            +
                    elsif valid_layer
         
     | 
| 
      
 81 
     | 
    
         
            +
                      Result.new(ok: true)
         
     | 
| 
      
 82 
     | 
    
         
            +
                    else
         
     | 
| 
      
 83 
     | 
    
         
            +
                      Result.new(
         
     | 
| 
      
 84 
     | 
    
         
            +
                        ok: false,
         
     | 
| 
      
 85 
     | 
    
         
            +
                        error_value: "Invalid 'layer' option in #{config_file_path.inspect}: #{layer.inspect}. Must be one of #{layers.names.to_a.inspect}"
         
     | 
| 
      
 86 
     | 
    
         
            +
                      )
         
     | 
| 
      
 87 
     | 
    
         
            +
                    end
         
     | 
| 
      
 88 
     | 
    
         
            +
                  end
         
     | 
| 
      
 89 
     | 
    
         
            +
             
     | 
| 
      
 90 
     | 
    
         
            +
                  sig do
         
     | 
| 
      
 91 
     | 
    
         
            +
                    params(config_file_path: String, setting: T.untyped).returns(Result)
         
     | 
| 
      
 92 
     | 
    
         
            +
                  end
         
     | 
| 
      
 93 
     | 
    
         
            +
                  def check_enforce_architecture_setting(config_file_path, setting)
         
     | 
| 
      
 94 
     | 
    
         
            +
                    valid_value = [true, nil, false, 'strict'].include?(setting)
         
     | 
| 
      
 95 
     | 
    
         
            +
                    layers_set = layers.names.any?
         
     | 
| 
      
 96 
     | 
    
         
            +
                    if valid_value && layers_set
         
     | 
| 
      
 97 
     | 
    
         
            +
                      Result.new(ok: true)
         
     | 
| 
      
 98 
     | 
    
         
            +
                    elsif valid_value
         
     | 
| 
      
 99 
     | 
    
         
            +
                      Result.new(
         
     | 
| 
      
 100 
     | 
    
         
            +
                        ok: false,
         
     | 
| 
      
 101 
     | 
    
         
            +
                        error_value: "Cannot set 'enforce_architecture' option in #{config_file_path.inspect} until `architectural_layers` have been specified in `packwerk.yml`"
         
     | 
| 
      
 102 
     | 
    
         
            +
                      )
         
     | 
| 
      
 103 
     | 
    
         
            +
                    else
         
     | 
| 
      
 104 
     | 
    
         
            +
                      Result.new(
         
     | 
| 
      
 105 
     | 
    
         
            +
                        ok: false,
         
     | 
| 
      
 106 
     | 
    
         
            +
                        error_value: "Invalid 'enforce_architecture' option in #{config_file_path.inspect}: #{setting.inspect}"
         
     | 
| 
      
 107 
     | 
    
         
            +
                      )
         
     | 
| 
      
 108 
     | 
    
         
            +
                    end
         
     | 
| 
      
 109 
     | 
    
         
            +
                  end
         
     | 
| 
      
 110 
     | 
    
         
            +
                end
         
     | 
| 
      
 111 
     | 
    
         
            +
              end
         
     | 
| 
      
 112 
     | 
    
         
            +
            end
         
     | 
    
        data/lib/packwerk-extensions.rb
    CHANGED
    
    | 
         @@ -12,6 +12,10 @@ require 'packwerk/visibility/checker' 
     | 
|
| 
       12 
12 
     | 
    
         
             
            require 'packwerk/visibility/package'
         
     | 
| 
       13 
13 
     | 
    
         
             
            require 'packwerk/visibility/validator'
         
     | 
| 
       14 
14 
     | 
    
         | 
| 
      
 15 
     | 
    
         
            +
            require 'packwerk/architecture/checker'
         
     | 
| 
      
 16 
     | 
    
         
            +
            require 'packwerk/architecture/package'
         
     | 
| 
      
 17 
     | 
    
         
            +
            require 'packwerk/architecture/validator'
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
       15 
19 
     | 
    
         
             
            module Packwerk
         
     | 
| 
       16 
20 
     | 
    
         
             
              module Extensions
         
     | 
| 
       17 
21 
     | 
    
         
             
              end
         
     | 
    
        metadata
    CHANGED
    
    | 
         @@ -1,14 +1,14 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            --- !ruby/object:Gem::Specification
         
     | 
| 
       2 
2 
     | 
    
         
             
            name: packwerk-extensions
         
     | 
| 
       3 
3 
     | 
    
         
             
            version: !ruby/object:Gem::Version
         
     | 
| 
       4 
     | 
    
         
            -
              version: 0.0. 
     | 
| 
      
 4 
     | 
    
         
            +
              version: 0.0.7
         
     | 
| 
       5 
5 
     | 
    
         
             
            platform: ruby
         
     | 
| 
       6 
6 
     | 
    
         
             
            authors:
         
     | 
| 
       7 
7 
     | 
    
         
             
            - Gusto Engineers
         
     | 
| 
       8 
8 
     | 
    
         
             
            autorequire: 
         
     | 
| 
       9 
9 
     | 
    
         
             
            bindir: bin
         
     | 
| 
       10 
10 
     | 
    
         
             
            cert_chain: []
         
     | 
| 
       11 
     | 
    
         
            -
            date: 2022-12- 
     | 
| 
      
 11 
     | 
    
         
            +
            date: 2022-12-28 00:00:00.000000000 Z
         
     | 
| 
       12 
12 
     | 
    
         
             
            dependencies:
         
     | 
| 
       13 
13 
     | 
    
         
             
            - !ruby/object:Gem::Dependency
         
     | 
| 
       14 
14 
     | 
    
         
             
              name: packwerk
         
     | 
| 
         @@ -173,6 +173,10 @@ extra_rdoc_files: [] 
     | 
|
| 
       173 
173 
     | 
    
         
             
            files:
         
     | 
| 
       174 
174 
     | 
    
         
             
            - README.md
         
     | 
| 
       175 
175 
     | 
    
         
             
            - lib/packwerk-extensions.rb
         
     | 
| 
      
 176 
     | 
    
         
            +
            - lib/packwerk/architecture/checker.rb
         
     | 
| 
      
 177 
     | 
    
         
            +
            - lib/packwerk/architecture/layers.rb
         
     | 
| 
      
 178 
     | 
    
         
            +
            - lib/packwerk/architecture/package.rb
         
     | 
| 
      
 179 
     | 
    
         
            +
            - lib/packwerk/architecture/validator.rb
         
     | 
| 
       176 
180 
     | 
    
         
             
            - lib/packwerk/privacy/checker.rb
         
     | 
| 
       177 
181 
     | 
    
         
             
            - lib/packwerk/privacy/package.rb
         
     | 
| 
       178 
182 
     | 
    
         
             
            - lib/packwerk/privacy/validator.rb
         
     |