Skip to content

Commit 1342a87

Browse files
authored
adding the checkers (#12)
1 parent d55cce0 commit 1342a87

File tree

4 files changed

+250
-85
lines changed

4 files changed

+250
-85
lines changed

CHECKERS.md

Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
# Checkers
2+
## Privacy Checker
3+
The privacy checker extension was originally extracted from [packwerk](https://github.com/Shopify/packwerk).
4+
5+
A package's privacy boundary is violated when there is a reference to the package's private constants from a source outside the package.
6+
7+
To enforce privacy for your package, set `enforce_privacy` to `true` or `strict` on your pack:
8+
9+
```yaml
10+
# components/merchandising/package.yml
11+
enforce_privacy: true
12+
```
13+
14+
Setting `enforce_privacy` to `true` will make all references to private constants in your package a violation.
15+
16+
Setting `enforce_privacy` to `strict` will forbid all references to private constants in your package. **This includes violations that have been added to other packages' `package_todo.yml` files.**
17+
18+
Note: You will need to remove all existing privacy violations before setting `enforce_privacy` to `strict`.
19+
20+
### Using public folders
21+
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.
22+
23+
### Defining your own public folder
24+
25+
You may prefer to override the default public folder, you can do so on a per-package basis by defining a `public_path`.
26+
27+
Example:
28+
29+
```yaml
30+
public_path: my/custom/path/
31+
```
32+
33+
### Defining public constants through sigil
34+
35+
> [!WARNING]
36+
> This way of of defining the public API of a package should be considered WIP. It is not supported by all tooling in the RubyAtScale ecosystem, as @alexevanczuk pointed out in a [comment on the PR](https://github.com/rubyatscale/packwerk-extensions/pull/35#discussion_r1334331797):
37+
>
38+
> There are a couple of other places that will require changes related to this sigil. Namely, everything that is coupled to the public folder implementation of privacy.
39+
>
40+
> In the rubyatscale org:
41+
>
42+
> * pack_stats, example https://github.com/rubyatscale/pack_stats/blob/main/lib/pack_stats/private/metrics/public_usage.rb. (IMO though we can just remove this metric – it has never been useful)
43+
> * Other places that mention public_path or app/public.
44+
> * Org wide search for app/public link
45+
> * Org wide search for public_path link
46+
> * packs (the Rust port of packwerk – I could take this one over unless someone is interested in implementing whatever we come up with there
47+
48+
49+
50+
You may make individual files public within a private package by usage of a comment within the first 5 lines of the `.rb` file containing `pack_public: true`.
51+
52+
Example:
53+
54+
```ruby
55+
# pack_public: true
56+
module Foo
57+
class Update
58+
end
59+
end
60+
```
61+
Now `Foo::Update` is considered public even though the `foo` package might be set to `enforce_privacy: (true || strict)`.
62+
63+
It's important to note that when combining `public_api: true` with the declaration of `private_constants`,
64+
`packwerk validate` will raise an exception if both are used for the same constant. This must be resolved by removing
65+
the sigil from the `.rb` file or removing the constant from the list of `private_constants`.
66+
67+
If you are using rubocop, it may be configured in such a way that there must be an empty line after the magic keywords at the top of the file. Currently, this extension is not modifying rubocop in any way so it does not recognize `pack_public: true` as a valid magic keyword option. That means placing it at the end of the magic keywords will throw a rubocop exception. However, you can place it first in the list to avoid an exception in rubocop.
68+
```
69+
-----
70+
# typed: ignore
71+
# frozen_string_literal: true
72+
# pack_public: true
73+
74+
class Foo
75+
...
76+
end => Layout/EmptyLineAfterMagicComment: Add an empty line after magic comments.
77+
78+
------
79+
# typed: ignore
80+
# frozen_string_literal: true
81+
82+
# pack_public: true
83+
84+
class Foo
85+
...
86+
end => Less than ideal. This won't raise an issue in rubocop, however, only the first 5 lines are scanned for the magic comment of pack_public so there is risk at it being missed. It also is requiring extra empty lines in the group of magic comments.
87+
88+
-----
89+
# pack_public: true
90+
# typed: ignore
91+
# frozen_string_literal: true
92+
93+
class Foo
94+
...
95+
end => Ideal solution. No exceptions from rubocop and very low risk of the magic comment being out of range since
96+
```
97+
98+
### Using specific private constants
99+
Sometimes it is desirable to only enforce privacy on a subset of constants in a package. You can do so by defining a `private_constants` list in your package.yml. Note that `enforce_privacy` must be set to `true` or `'strict'` for this to work.
100+
101+
### Ignore strict mode for violation coming from specific path patterns
102+
If you want to activate `'strict'` mode on your package but have a few privacy violations you know you will deal with later,
103+
you can set a list of patterns to exclude.
104+
105+
```yaml
106+
enforce_privacy: strict
107+
strict_privacy_ignored_patterns:
108+
- engines/another_engine/test/**/*
109+
```
110+
111+
In this example, violations on constants of your engine referenced in those files `engines/another_engine/test/**/*` will not fail Packwerk checks.
112+
113+
### Package Privacy violation
114+
Packwerk thinks something is a privacy violation if you're referencing a constant, class, or module defined in the private implementation (i.e. not the public folder) of another package. We care about these because we want to make sure we only use parts of a package that have been exposed as public API.
115+
116+
#### Interpreting Privacy violation
117+
118+
> /Users/JaneDoe/src/github.com/sample-project/user/app/controllers/labels_controller.rb:170:30
119+
> Privacy violation: '::Billing::CarrierInvoiceTransaction' is private to 'billing' but referenced from 'user'.
120+
> Is there a public entrypoint in 'billing/app/public/' that you can use instead?
121+
>
122+
> Inference details: 'Billing::CarrierInvoiceTransaction' refers to ::Billing::CarrierInvoiceTransaction which seems to be defined in billing/app/models/billing/carrier_invoice_transaction.rb.
123+
124+
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`.
125+
126+
#### Suggestions
127+
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.
128+
129+
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.
130+
131+
## Visibility Checker
132+
The visibility checker can be used to allow a package to be a private implementation detail of other packages.
133+
134+
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.
135+
136+
```yaml
137+
# components/merchandising/package.yml
138+
enforce_visibility: true
139+
visible_to:
140+
- components/other_package
141+
```
142+
143+
## Folder-Privacy Checker
144+
The folder privacy checker can be used to allow a package to be private to their sibling packs and parent packs and will create todos if used by any other package.
145+
146+
To enforce folder privacy for your package, set `enforce_folder_privacy` to `true` on your pack.
147+
148+
```yaml
149+
# components/merchandising/package.yml
150+
enforce_folder_privacy: true
151+
```
152+
153+
Here is an example of paths and whether their use of `packs/b/packs/e` is OK or not, assuming that protects itself via `enforce_folder_privacy`
154+
155+
```
156+
. OK (parent of parent)
157+
packs/a VIOLATION
158+
packs/b OK (parent)
159+
packs/b/packs/d OK (sibling)
160+
packs/b/packs/e ENFORCE_NESTED_VISIBILITY: TRUE
161+
packs/b/packs/e/packs/f VIOLATION
162+
packs/b/packs/e/packs/g VIOLATION
163+
packs/b/packs/h OK (sibling)
164+
packs/c VIOLATION
165+
```
166+
167+
## Layer Checker
168+
The layer checker can be used to enforce constraints on what can depend on what.
169+
170+
To enforce layers for your package, first define the `layers` in `packwerk.yml`, for example:
171+
```
172+
layers:
173+
- package
174+
- utility
175+
```
176+
177+
Then, turn on the checker in your package:
178+
```yaml
179+
# components/merchandising/package.yml
180+
enforce_layers: true
181+
layer: utility
182+
```
183+
184+
Now this pack can only depend on other utility packages.
185+
186+
# Enforcement Globs Ignore
187+
`enforcement_globs_ignore` can be used to specify gitignore-style rules for not enforcing violations.
188+
189+
### Examples
190+
191+
```yml
192+
# packs/product_services/serv1/foo/package.yml
193+
enforce_privacy: true
194+
enforce_visibility: true
195+
196+
enforcement_globs_ignore:
197+
- enforcements:
198+
- privacy
199+
- visiblity
200+
ignores:
201+
- "**/*"
202+
# Enforce incoming privacy and visibility violation references _only_ in `pks/product_services/serv1/**/*`
203+
- "!packs/product_services/serv1/**/*"
204+
reason: "It was decided only to fix incoming violations from serv1. See ticket #232"
205+
```
206+
207+
```yml
208+
# packs/pack2/package.yml
209+
enforce_dependencies: true
210+
dependencies:
211+
# not required because of the below enforcement_globs_ignore
212+
# - packs/pack1
213+
# required because of the enforcement_globs_ignore exception line
214+
- packs/pack3
215+
216+
enforcement_globs_ignore:
217+
- enforcements:
218+
- dependency
219+
ignores:
220+
- "**/*"
221+
# Enforce outgoing dependency violation references _only_ to `pks/pack3/**/*`
222+
- "!packs/pack3/**/*"
223+
reason: "The other dependency violations are fine as those packs will be absorbed into this one."
224+
```
225+

INSTALLATION.md

Lines changed: 6 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,12 @@
11
# Installation
22
## Option 1:
3+
- Install [dotslash](https://dotslash-cli.com/docs/installation/)
4+
- Download the latest packs release dotslash `pks` file. Example: https://github.com/rubyatscale/pks/releases/tag/v0.2.21/pks
5+
- Save the `pks` file to your ruby project's bin/ directory. You'll then have a `bin/pks` file in your project.
6+
- Use `bin/pks` to execute the CLI.
7+
8+
## Option 2:
39
- Install Rust: https://www.rust-lang.org/tools/install
410
- Note: If `which cargo` returns a path, skip this step!
511
- TLDR: `curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh`, and you're done!
612
- `cargo install pks` (it's like `gem install`)
7-
8-
## Option 2:
9-
(Mac only – for other platforms, please create an issue/PR or try option 1.)
10-
11-
- Go to https://github.com/alexevanczuk/packs/releases
12-
- Download the `packs` asset and run `chmod +x path/to/packs`. This makes the asset executable on your machine.
13-
- Open the containing directory, right click on the binary, click open, and then accept the warning message that says its from an unknown developer (it's me!)
14-
- Execute `path/to/packs` to see the CLI help message.
15-
16-
You can add `path/to/packs` to your `PATH` so it's available in every terminal session.
17-
18-
## Option 3 (coming soon):
19-
I'm looking into installing via `brew` or as a native ruby gem extension. More coming soon!
20-
21-
## Option 4:
22-
- Install [dotslash](https://dotslash-cli.com/docs/installation/)
23-
- Download the latest packs release dotslash `pks` file. Example: https://github.com/alexevanczuk/packs/releases/download/v0.2.8/pks
24-
- Save the `pks` file to your ruby project's bin/ directory. You'll then have a `bin/pks` file in your project.
25-
- Use `bin/pks` to execute the CLI.

README.md

Lines changed: 17 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,21 @@
44
[![CI](https://github.com/rubyatscale/pks/actions/workflows/ci.yml/badge.svg)](https://github.com/rubyatscale/pks/actions)
55
[![Security Audit](https://github.com/rubyatscale/pks/actions/workflows/audit.yml/badge.svg)](https://github.com/rubyatscale/pks/actions?query=workflow%3A%22Security+audit%22++)
66

7-
A 100% Rust implementation of [packwerk](https://github.com/Shopify/packwerk), a gradual modularization platform for Ruby.
7+
A 100% Rust implementation of [packwerk](https://github.com/Shopify/packwerk) and [packwerk-extensions](https://github.com/rubyatscale/packwerk-extensions), a gradual modularization platform for Ruby.
8+
9+
Currently, it ships the following checkers to help improve the boundaries between packages. These checkers are:
10+
- A `dependency` checker requires that a pack specifies packs on which it depends. Cyclic dependencies are not allowed. See [packwerk](https://github.com/Shopify/packwerk)
11+
- A `privacy` checker that ensures other packages are using your package's public API
12+
- A `visibility` checker that allows packages to be private except to an explicit group of other packages.
13+
- A `folder_privacy` checker that allows packages to their sibling packs and parent pack (to be used in an application that uses folder packs)
14+
- A `layer` (formerly `architecture`) checker that allows packages to specify their "layer" and requires that each layer only communicate with layers below it.
15+
16+
See [Checkers](CHECKERS.md) for detailed descriptions.
817

918
# Fork
1019
This repo was forked directly from https://github.com/alexevanczuk/packs
1120

1221
# Goals:
13-
## Serve as a drop-in replacement for `packwerk` on most projects
14-
- Currently can serve as a drop-in replacement on Gusto's extra-large Rails monolith
15-
- This is a work in progress! Please see [Verification](#verification) for instructions on how to verify the output of `pks` is the same as `packwerk`.
1622

1723
## Run 20x faster than `packwerk` on most projects
1824
- Currently ~10-20x as fast as the ruby implementation. See [BENCHMARKS.md](https://github.com/rubyatscale/pks/blob/main/BENCHMARKS.md).
@@ -40,7 +46,7 @@ Commands:
4046
update Update package_todo.yml files with the current violations
4147
validate Look for validation errors in the codebase
4248
add-dependency Add a dependency from one pack to another
43-
check-unnecessary-dependencies Check for dependencies that when removed produce no violations.
49+
check-unused-dependencies Check for dependencies that when removed produce no violations.
4450
lint-package-yml-files Lint package.yml files
4551
expose-monkey-patches Expose monkey patches of the Ruby stdlib, gems your app uses, and your application itself
4652
delete-cache `rm -rf` on your cache directory, default `tmp/cache/packwerk`
@@ -59,6 +65,11 @@ Options:
5965
-V, --version Print version
6066
```
6167

68+
69+
# Fixing `pks check` privacy and dependency violations
70+
[Violation Flow Chart](https://drive.google.com/file/d/1Y1x0ncF6EsJxj9fM2wm-k35auXaxRjEB/view)
71+
72+
6273
# Installation
6374
See [INSTALLATION.md](https://github.com/rubyatscale/pks/blob/main/INSTALLATION.md)
6475

@@ -73,25 +84,6 @@ Directions:
7384
- Follow [INSTALLATION.md](https://github.com/rubyatscale/pks/blob/main/INSTALLATION.md) instructions to install `pks`
7485
- Follow the [configuration](https://github.com/rubyatscale/packwerk-vscode/tree/main#configuration) directions to configure the extension to use `pks` instead of the ruby gem by setting the executable to `pks check`
7586

76-
# Verification
77-
As `pks` is still a work-in-progress, it's possible it will not produce the same results as the ruby implementation (see [Not Yet Supported](#not-yet-supported)). If so, please file an issue – I'd love to try to support your use case!
78-
79-
Instructions:
80-
- Follow the directions above to install `pks`
81-
- Run `pks update`
82-
- Confirm the output of `git diff` is empty
83-
- Please file an issue if it's not!
84-
85-
86-
# New to Rust?
87-
Me too! This is my first Rust project, so I'd love to have feedback, advice, and contributions!
88-
89-
Rust is a low-level language with high-level abstractions, a rich type system, with a focus on memory safety through innovative compile-time checks on memory usage.
90-
91-
If you're new to Rust, don't be intimidated! [https://www.rust-lang.org](https://www.rust-lang.org/learn) has tons of great learning resources.
92-
93-
If you'd like to contribute but don't know where to start, please reach out! I'd love to help you get started.
94-
9587
# Not yet supported
9688
- custom inflections
9789
- custom load paths
@@ -116,46 +108,6 @@ autoload_roots:
116108
packs/foo/app/domain: "::Foo"
117109
```
118110
119-
## Enforcement Globs Ignore
120-
`enforcement_globs_ignore` can be used to specify gitignore-style rules for not enforcing violations.
121-
122-
### Examples
123-
124-
```yml
125-
# packs/product_services/serv1/foo/package.yml
126-
enforce_privacy: true
127-
enforce_visibility: true
128-
129-
enforcement_globs_ignore:
130-
- enforcements:
131-
- privacy
132-
- visiblity
133-
ignores:
134-
- "**/*"
135-
# Enforce incoming privacy and visibility violation references _only_ in `pks/product_services/serv1/**/*`
136-
- "!packs/product_services/serv1/**/*"
137-
reason: "It was decided only to fix incoming violations from serv1. See ticket #232"
138-
```
139-
140-
```yml
141-
# packs/pack2/package.yml
142-
enforce_dependencies: true
143-
dependencies:
144-
# not required because of the below enforcement_globs_ignore
145-
# - packs/pack1
146-
# required because of the enforcement_globs_ignore exception line
147-
- packs/pack3
148-
149-
enforcement_globs_ignore:
150-
- enforcements:
151-
- dependency
152-
ignores:
153-
- "**/*"
154-
# Enforce outgoing dependency violation references _only_ to `pks/pack3/**/*`
155-
- "!packs/pack3/**/*"
156-
reason: "The other dependency violations are fine as those packs will be absorbed into this one."
157-
```
158-
159111
## "check" error messages
160112
The error messages resulting from running `pks check` can be customized with mustache-style interpolation. The available
161113
variables are:
@@ -186,6 +138,7 @@ checker_overrides:
186138
See [BENCHMARKS.md](https://github.com/rubyatscale/pks/blob/main/BENCHMARKS.md)
187139
188140
# Kudos
141+
- @alexevanczuk for https://github.com/alexevanczuk/packs
189142
- Current (@gmcgibbon, @rafaelfranca), and Ex-Shopifolks (@exterm, @wildmaples) for open-sourcing and maintaining `packwerk`
190143
- Gusties, and the [Ruby/Rails Modularity Slack Server](https://join.slack.com/t/rubymod/shared_invite/zt-1dgyrxji9-sihGNX43mVh5T6tw18hFaQ), for continued feedback and support
191144
- @mzruya for the initial implementation and Rust inspiration

dev/notes.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
- Privacy violation inversion?
66

77
## Performance
8-
Although `packs` is intended to be fast, there are ways it can be made a lot faster!
9-
- Conditional cache usage. For example, implemented as an LSP, packs could always use cache and only bust specific caches (asychronously) when certain events (e.g. file changes) are received.
8+
Although `pks` is intended to be fast, there are ways it can be made a lot faster!
9+
- Conditional cache usage. For example, implemented as an LSP, pks could always use cache and only bust specific caches (asychronously) when certain events (e.g. file changes) are received.
1010
- By using modified time, we can avoid opening the entire file and parsing it and calculating the md5 hash. It's possible this would not be a meaningful performance improvement.
1111

1212
### Improved use of references (less cloning)

0 commit comments

Comments
 (0)