boxwerk 0.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 +7 -0
- data/CHANGELOG.md +8 -0
- data/LICENSE.txt +19 -0
- data/README.md +376 -0
- data/Rakefile +8 -0
- data/example/Gemfile +6 -0
- data/example/Gemfile.lock +66 -0
- data/example/README.md +130 -0
- data/example/app.rb +101 -0
- data/example/package.yml +6 -0
- data/example/packages/finance/lib/invoice.rb +51 -0
- data/example/packages/finance/lib/tax_calculator.rb +26 -0
- data/example/packages/finance/package.yml +10 -0
- data/example/packages/util/lib/calculator.rb +21 -0
- data/example/packages/util/lib/geometry.rb +26 -0
- data/example/packages/util/package.yml +5 -0
- data/exe/boxwerk +28 -0
- data/lib/boxwerk/cli.rb +130 -0
- data/lib/boxwerk/graph.rb +111 -0
- data/lib/boxwerk/loader.rb +277 -0
- data/lib/boxwerk/package.rb +51 -0
- data/lib/boxwerk/registry.rb +37 -0
- data/lib/boxwerk/setup.rb +71 -0
- data/lib/boxwerk/version.rb +5 -0
- data/lib/boxwerk.rb +14 -0
- data/sig/boxwerk.rbs +4 -0
- metadata +87 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 45cabb7d61d5029a494703a95c1b974f1af7ee1286001d457ea7aaf8b870496c
|
|
4
|
+
data.tar.gz: fb14ec2556787d19618ee3599685a06a06a4d1ed40a3113c47f61420f84fb202
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 8e11bee2fd442bc29b537d4e7099e4380b19bed851f4f40bd42e119c35f139ee680866bd0f6189334e46652881c224559e10c6ab4dcf722c4d50c455fccdf16e
|
|
7
|
+
data.tar.gz: 0c69bf3a342d8fd502d6e64d085edfa9d1a2864b60d8f3f683addc680080f5762246f9901926c81667c40ddad82fdae7ead6e939ea3f297c0f907c983e21622f
|
data/CHANGELOG.md
ADDED
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
5
|
+
in the Software without restriction, including without limitation the rights
|
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
8
|
+
furnished to do so, subject to the following conditions:
|
|
9
|
+
|
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
|
11
|
+
all copies or substantial portions of the Software.
|
|
12
|
+
|
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
19
|
+
THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
# Boxwerk
|
|
2
|
+
|
|
3
|
+
Boxwerk is a runtime package system for Ruby with strict isolation of constants using Ruby 4.0's [`Ruby::Box`](https://docs.ruby-lang.org/en/master/Ruby/Box.html). It is used to organize code into packages with explicit dependency graphs and strict access to constants between packages. It is inspired by [Packwerk](https://github.com/Shopify/packwerk), a static package system.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Strict Isolation**: Each package runs in its own `Ruby::Box`, preventing constants from leaking without explicit imports or exports.
|
|
8
|
+
- **Explicit Dependencies**: Dependencies are declared in `package.yml` files, forming a validated DAG.
|
|
9
|
+
- **Ergonomic Imports**: Flexible import strategies (namespaced, aliased, selective, renamed).
|
|
10
|
+
|
|
11
|
+
## Limtations
|
|
12
|
+
|
|
13
|
+
- There is no isolation of gems.
|
|
14
|
+
- Gems are required to be eager loaded in the root box to be accessible in packages.
|
|
15
|
+
- No support for reloading of constants.
|
|
16
|
+
- Exported constants must follow Zeitwerk naming conventions for their source location.
|
|
17
|
+
|
|
18
|
+
## Requirements
|
|
19
|
+
|
|
20
|
+
- Ruby 4.0+ with [`Ruby::Box`](https://docs.ruby-lang.org/en/master/Ruby/Box.html) support
|
|
21
|
+
- `RUBY_BOX=1` environment variable must be set at process startup
|
|
22
|
+
|
|
23
|
+
## Quick Start
|
|
24
|
+
|
|
25
|
+
### 1. Create a Package Structure
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
my_app/
|
|
29
|
+
├── Gemfile # Your gem dependencies
|
|
30
|
+
├── package.yml # Root package
|
|
31
|
+
├── app.rb # Your application entrypoint
|
|
32
|
+
└── packages/
|
|
33
|
+
└── billing/
|
|
34
|
+
├── package.yml # Package manifest
|
|
35
|
+
└── lib/
|
|
36
|
+
└── invoice.rb # Package code
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### 2. Define Your Gemfile
|
|
40
|
+
|
|
41
|
+
**`Gemfile`:**
|
|
42
|
+
```ruby
|
|
43
|
+
source 'https://rubygems.org'
|
|
44
|
+
|
|
45
|
+
gem 'boxwerk'
|
|
46
|
+
gem 'money' # Example: gems are auto-required and globally accessible
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### 3. Define Packages
|
|
50
|
+
|
|
51
|
+
**Root `package.yml`:**
|
|
52
|
+
```yaml
|
|
53
|
+
imports:
|
|
54
|
+
- packages/finance # Will define a `Finance` module to hold finance package exports
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
**`packages/finance/package.yml`:**
|
|
58
|
+
```yaml
|
|
59
|
+
exports:
|
|
60
|
+
- Invoice
|
|
61
|
+
- TaxCalculator
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
**`packages/finance/lib/invoice.rb`:**
|
|
65
|
+
```ruby
|
|
66
|
+
class Invoice
|
|
67
|
+
def initialize(amount_cents)
|
|
68
|
+
# Money gem is accessible because it's in the Gemfile
|
|
69
|
+
@amount = Money.new(amount_cents, 'USD')
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def total
|
|
73
|
+
@amount
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
**`packages/finance/lib/tax_calculator.rb`:**
|
|
79
|
+
```ruby
|
|
80
|
+
class TaxCalculator
|
|
81
|
+
# ...
|
|
82
|
+
end
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### 4. Use in Your Application
|
|
86
|
+
|
|
87
|
+
**`app.rb`:**
|
|
88
|
+
```ruby
|
|
89
|
+
# No requires needed - imports are wired by Boxwerk
|
|
90
|
+
invoice = Finance::Invoice.new(10_000)
|
|
91
|
+
puts invoice.total # => #<Money fractional:10000 currency:USD>
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### 5. Run Your Application
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
RUBY_BOX=1 boxwerk run app.rb
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Boxwerk automatically:
|
|
101
|
+
1. Sets up Bundler
|
|
102
|
+
2. Requires all gems from your Gemfile in the root box
|
|
103
|
+
3. Loads and wires all packages
|
|
104
|
+
4. Executes your script in the root package context
|
|
105
|
+
|
|
106
|
+
## Usage
|
|
107
|
+
|
|
108
|
+
### Running Scripts
|
|
109
|
+
|
|
110
|
+
Execute a Ruby script in the root package context:
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
boxwerk run script.rb [args...]
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
The script has access to:
|
|
117
|
+
- All gems from your Gemfile (automatically required)
|
|
118
|
+
- All imports defined in the root `package.yml`
|
|
119
|
+
|
|
120
|
+
### Interactive Console
|
|
121
|
+
|
|
122
|
+
TODO: This feature is currenly broken and will run IRB from the root box, not the root package as desired.
|
|
123
|
+
|
|
124
|
+
Start an IRB session in the root package context:
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
boxwerk console [irb-args...]
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
All imports and gems are available for interactive exploration.
|
|
131
|
+
|
|
132
|
+
### Help
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
boxwerk help
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## Package Configuration
|
|
139
|
+
|
|
140
|
+
A `package.yml` defines what a package exports and imports:
|
|
141
|
+
|
|
142
|
+
```yaml
|
|
143
|
+
exports:
|
|
144
|
+
- PublicClass
|
|
145
|
+
- PublicModule
|
|
146
|
+
|
|
147
|
+
imports:
|
|
148
|
+
- packages/dependency1
|
|
149
|
+
- packages/dependency2: Alias
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### Exports
|
|
153
|
+
|
|
154
|
+
Constants that should be visible to packages that import this one.
|
|
155
|
+
|
|
156
|
+
### Imports
|
|
157
|
+
|
|
158
|
+
Dependencies this package needs. **Note**: Dependencies are NOT transitive. If package A imports B, and B imports C, then A cannot access C unless it explicitly imports it.
|
|
159
|
+
|
|
160
|
+
## Import Strategies
|
|
161
|
+
|
|
162
|
+
Boxwerk supports four import strategies in `package.yml`:
|
|
163
|
+
|
|
164
|
+
### 1. Default Namespace
|
|
165
|
+
|
|
166
|
+
Import all exports under a module named after the package:
|
|
167
|
+
|
|
168
|
+
```yaml
|
|
169
|
+
imports:
|
|
170
|
+
- packages/finance
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Result: `Finance::Invoice`, `Finance::TaxCalculator`
|
|
174
|
+
|
|
175
|
+
### 2. Aliased Namespace
|
|
176
|
+
|
|
177
|
+
Import under a custom module name:
|
|
178
|
+
|
|
179
|
+
```yaml
|
|
180
|
+
imports:
|
|
181
|
+
- packages/finance: Billing
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
Result: `Billing::Invoice`, `Billing::TaxCalculator`
|
|
185
|
+
|
|
186
|
+
**Single Export Optimization**: If a package exports only one constant, it's imported directly (not wrapped in a module):
|
|
187
|
+
|
|
188
|
+
```yaml
|
|
189
|
+
# util exports only Calculator
|
|
190
|
+
imports:
|
|
191
|
+
- packages/util: Calc
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
Result: `Calc` (not `Calc::Calculator`)
|
|
195
|
+
|
|
196
|
+
### 3. Selective Import
|
|
197
|
+
|
|
198
|
+
Import specific constants directly:
|
|
199
|
+
|
|
200
|
+
```yaml
|
|
201
|
+
imports:
|
|
202
|
+
- packages/finance:
|
|
203
|
+
- Invoice
|
|
204
|
+
- TaxCalculator
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
Result: `Invoice`, `TaxCalculator` (no namespace)
|
|
208
|
+
|
|
209
|
+
### 4. Selective Rename
|
|
210
|
+
|
|
211
|
+
Import specific constants with custom names:
|
|
212
|
+
|
|
213
|
+
```yaml
|
|
214
|
+
imports:
|
|
215
|
+
- packages/finance:
|
|
216
|
+
Invoice: Bill
|
|
217
|
+
TaxCalculator: Calculator
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
Result: `Bill`, `Calculator`
|
|
221
|
+
|
|
222
|
+
## Gems and Packages
|
|
223
|
+
|
|
224
|
+
### How Gems Work in Boxwerk
|
|
225
|
+
|
|
226
|
+
When you run `boxwerk`, all gems in your `Gemfile` are:
|
|
227
|
+
1. Automatically loaded via Bundler in the root box
|
|
228
|
+
2. Accessible globally in all package boxes (gems are not isolated)
|
|
229
|
+
|
|
230
|
+
This means:
|
|
231
|
+
- You can use any gem from your Gemfile in any package.
|
|
232
|
+
- Gems don't need to be declared in `package.yml`.
|
|
233
|
+
- You do not `require` gems manually.
|
|
234
|
+
|
|
235
|
+
### Isolation Model
|
|
236
|
+
|
|
237
|
+
- **Root Box**: The box where Ruby bootstraps and all builtin classes/modules are defined. In Boxwerk, the root box performs all setup operations (Bundler setup, gem loading, dependency graph building, package box creation, and import wiring).
|
|
238
|
+
- **Main Box**: The first user box created automatically by Ruby (copied from root box). In Boxwerk, it only runs the `exe/boxwerk` executable file, which then calls into the root box to execute the setup. The main box has no other purpose.
|
|
239
|
+
- **Package Boxes**: Each package (including root package) runs in its own isolated `Ruby::Box` (created by copying from root box after gems are loaded).
|
|
240
|
+
- **Box Inheritance**: All boxes are created via copy-on-write from the root box, inheriting builtin classes and loaded gems.
|
|
241
|
+
- **Gems are Global**: All gems from Gemfile are accessible in all boxes (loaded in root box before package boxes are created).
|
|
242
|
+
- **Package Exports are Isolated**: Only explicit imports from packages are accessible.
|
|
243
|
+
- **No Transitive Access**: Packages can only see their explicit imports.
|
|
244
|
+
|
|
245
|
+
For more details on how Ruby::Box works, see the [official Ruby::Box documentation](https://docs.ruby-lang.org/en/master/Ruby/Box.html).
|
|
246
|
+
|
|
247
|
+
## Known Issues
|
|
248
|
+
|
|
249
|
+
These issues are related to the current state of Ruby::Box in Ruby 4.0+. See the [Ruby::Box documentation](https://docs.ruby-lang.org/en/master/Ruby/Box.html) for known issues with the feature itself.
|
|
250
|
+
|
|
251
|
+
### Gem Requiring in Boxes
|
|
252
|
+
|
|
253
|
+
Requiring any gem from within a box (after boot) currently crashes the Ruby VM. This is likely an issue with Ruby::Box itself. As a workaround, Boxwerk automatically requires all gems from the Gemfile in the root box before creating package boxes, so gems are already loaded and accessible everywhere.
|
|
254
|
+
|
|
255
|
+
### Console Context
|
|
256
|
+
|
|
257
|
+
The console does not correctly run in the root package box—it runs in the context of the root box instead. It should run in the root package box. However, if we attempt to `require 'irb'` in the root package box, the Ruby VM crashes due to the gem requiring issue described above.
|
|
258
|
+
|
|
259
|
+
### IRB Autocomplete
|
|
260
|
+
|
|
261
|
+
Autocomplete is disabled for the console/IRB by default. When enabled, the Ruby VM crashes as soon as any key is pressed. This appears to be an issue with Ruby::Box and IRB's autocomplete feature interacting poorly.
|
|
262
|
+
|
|
263
|
+
## Architecture
|
|
264
|
+
|
|
265
|
+
### Boot Process
|
|
266
|
+
|
|
267
|
+
1. Setup Bundler and require all gems in the root box
|
|
268
|
+
2. Find root `package.yml` (searches up from current directory)
|
|
269
|
+
3. Build dependency graph from package manifests
|
|
270
|
+
4. Validate dependency graph (no circular dependencies)
|
|
271
|
+
5. Boot packages in topological order
|
|
272
|
+
6. Wire imports into each package box
|
|
273
|
+
7. Execute command in root package context
|
|
274
|
+
|
|
275
|
+
### Internal Components
|
|
276
|
+
|
|
277
|
+
Boxwerk consists of several internal components that work together to provide package isolation:
|
|
278
|
+
|
|
279
|
+
#### `Boxwerk::CLI`
|
|
280
|
+
|
|
281
|
+
The command-line interface handler that:
|
|
282
|
+
- Parses commands (`run`, `console`, `help`)
|
|
283
|
+
- Validates the Ruby environment (checks for `RUBY_BOX=1` and Ruby::Box support)
|
|
284
|
+
- Delegates to `Boxwerk::Setup` for the boot process
|
|
285
|
+
- Executes the requested command in the root package's box context
|
|
286
|
+
|
|
287
|
+
#### `Boxwerk::Setup`
|
|
288
|
+
|
|
289
|
+
The setup orchestrator that:
|
|
290
|
+
- Searches up the directory tree to find the root `package.yml`
|
|
291
|
+
- Creates a `Boxwerk::Graph` instance to build and validate the dependency graph
|
|
292
|
+
- Creates a `Boxwerk::Registry` instance to track booted packages
|
|
293
|
+
- Calls `Boxwerk::Loader.boot_all` to boot all packages in topological order
|
|
294
|
+
- Returns the loaded graph for introspection
|
|
295
|
+
|
|
296
|
+
#### `Boxwerk::Graph`
|
|
297
|
+
|
|
298
|
+
The dependency graph builder that:
|
|
299
|
+
- Parses the root `package.yml` and recursively discovers all package dependencies
|
|
300
|
+
- Builds a directed acyclic graph (DAG) of package relationships
|
|
301
|
+
- Validates that there are no circular dependencies
|
|
302
|
+
- Performs topological sorting to determine boot order (dependencies before consumers)
|
|
303
|
+
- Provides access to all packages in the graph
|
|
304
|
+
|
|
305
|
+
#### `Boxwerk::Package`
|
|
306
|
+
|
|
307
|
+
The package manifest parser that:
|
|
308
|
+
- Represents a single package with its configuration
|
|
309
|
+
- Parses `package.yml` files to extract exports and imports
|
|
310
|
+
- Normalizes the polymorphic import syntax (String, Array, Hash)
|
|
311
|
+
- Stores the package path, name, exports, imports, and box reference
|
|
312
|
+
- Tracks which exports have been loaded via `loaded_exports` hash (export name → file path)
|
|
313
|
+
- Tracks whether the package has been booted
|
|
314
|
+
|
|
315
|
+
#### `Boxwerk::Loader`
|
|
316
|
+
|
|
317
|
+
The package loader that:
|
|
318
|
+
- Creates a new `Ruby::Box` for each package (including the root package)
|
|
319
|
+
- Loads exported constants lazily on-demand when they are imported by other packages
|
|
320
|
+
- Uses Zeitwerk naming conventions to discover file locations for exported constants
|
|
321
|
+
- Caches loaded exports in `package.loaded_exports` to avoid redundant file loading
|
|
322
|
+
- Wires imports by injecting constants from dependency boxes into consumer boxes
|
|
323
|
+
- Implements all four import strategies (default namespace, aliased namespace, selective import, selective rename)
|
|
324
|
+
- Handles the single-export optimization for namespace imports
|
|
325
|
+
- Registers each booted package in the registry
|
|
326
|
+
- Only loads files that define exported constants, never loading non-exported code
|
|
327
|
+
|
|
328
|
+
#### `Boxwerk::Registry`
|
|
329
|
+
|
|
330
|
+
The package registry that:
|
|
331
|
+
- Tracks all booted package instances
|
|
332
|
+
- Allows packages to be retrieved by name during the wiring phase
|
|
333
|
+
- Ensures each package is only booted once
|
|
334
|
+
- Provides a clean interface for package lookup
|
|
335
|
+
|
|
336
|
+
## Example
|
|
337
|
+
|
|
338
|
+
See the [example/](example/) directory for a complete working example with:
|
|
339
|
+
|
|
340
|
+
- Multi-package application
|
|
341
|
+
- Gem usage
|
|
342
|
+
- Transitive dependency demonstration
|
|
343
|
+
- Isolation verification
|
|
344
|
+
- Console usage examples
|
|
345
|
+
|
|
346
|
+
Run it with:
|
|
347
|
+
|
|
348
|
+
```bash
|
|
349
|
+
cd example
|
|
350
|
+
RUBY_BOX=1 boxwerk run app.rb
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
Or explore interactively:
|
|
354
|
+
|
|
355
|
+
```bash
|
|
356
|
+
cd example
|
|
357
|
+
RUBY_BOX=1 boxwerk console
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
## Development
|
|
361
|
+
|
|
362
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run the tests:
|
|
363
|
+
|
|
364
|
+
```bash
|
|
365
|
+
RUBY_BOX=1 bundle exec rake test
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
To install this gem onto your local machine, run `bundle exec rake install`.
|
|
369
|
+
|
|
370
|
+
## License
|
|
371
|
+
|
|
372
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
|
373
|
+
|
|
374
|
+
## Contribution
|
|
375
|
+
|
|
376
|
+
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you shall be dual licensed as above, without any additional terms or conditions.
|
data/Rakefile
ADDED
data/example/Gemfile
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
PATH
|
|
2
|
+
remote: ..
|
|
3
|
+
specs:
|
|
4
|
+
boxwerk (0.1.0)
|
|
5
|
+
irb (~> 1.16)
|
|
6
|
+
|
|
7
|
+
GEM
|
|
8
|
+
remote: https://rubygems.org/
|
|
9
|
+
specs:
|
|
10
|
+
bigdecimal (4.0.1)
|
|
11
|
+
concurrent-ruby (1.3.6)
|
|
12
|
+
date (3.5.1)
|
|
13
|
+
erb (6.0.1)
|
|
14
|
+
i18n (1.14.8)
|
|
15
|
+
concurrent-ruby (~> 1.0)
|
|
16
|
+
io-console (0.8.2)
|
|
17
|
+
irb (1.16.0)
|
|
18
|
+
pp (>= 0.6.0)
|
|
19
|
+
rdoc (>= 4.0.0)
|
|
20
|
+
reline (>= 0.4.2)
|
|
21
|
+
money (7.0.0)
|
|
22
|
+
bigdecimal
|
|
23
|
+
i18n (~> 1.9)
|
|
24
|
+
pp (0.6.3)
|
|
25
|
+
prettyprint
|
|
26
|
+
prettyprint (0.2.0)
|
|
27
|
+
psych (5.3.1)
|
|
28
|
+
date
|
|
29
|
+
stringio
|
|
30
|
+
rdoc (7.0.3)
|
|
31
|
+
erb
|
|
32
|
+
psych (>= 4.0.0)
|
|
33
|
+
tsort
|
|
34
|
+
reline (0.6.3)
|
|
35
|
+
io-console (~> 0.5)
|
|
36
|
+
stringio (3.2.0)
|
|
37
|
+
tsort (0.2.0)
|
|
38
|
+
|
|
39
|
+
PLATFORMS
|
|
40
|
+
arm64-darwin-25
|
|
41
|
+
ruby
|
|
42
|
+
|
|
43
|
+
DEPENDENCIES
|
|
44
|
+
boxwerk!
|
|
45
|
+
money
|
|
46
|
+
|
|
47
|
+
CHECKSUMS
|
|
48
|
+
bigdecimal (4.0.1) sha256=8b07d3d065a9f921c80ceaea7c9d4ae596697295b584c296fe599dd0ad01c4a7
|
|
49
|
+
boxwerk (0.1.0)
|
|
50
|
+
concurrent-ruby (1.3.6) sha256=6b56837e1e7e5292f9864f34b69c5a2cbc75c0cf5338f1ce9903d10fa762d5ab
|
|
51
|
+
date (3.5.1) sha256=750d06384d7b9c15d562c76291407d89e368dda4d4fff957eb94962d325a0dc0
|
|
52
|
+
erb (6.0.1) sha256=28ecdd99c5472aebd5674d6061e3c6b0a45c049578b071e5a52c2a7f13c197e5
|
|
53
|
+
i18n (1.14.8) sha256=285778639134865c5e0f6269e0b818256017e8cde89993fdfcbfb64d088824a5
|
|
54
|
+
io-console (0.8.2) sha256=d6e3ae7a7cc7574f4b8893b4fca2162e57a825b223a177b7afa236c5ef9814cc
|
|
55
|
+
irb (1.16.0) sha256=2abe56c9ac947cdcb2f150572904ba798c1e93c890c256f8429981a7675b0806
|
|
56
|
+
money (7.0.0) sha256=6de776ee00aced8b9a435d3aac25ce8c600566625809b3d69bbe0c319e941dd5
|
|
57
|
+
pp (0.6.3) sha256=2951d514450b93ccfeb1df7d021cae0da16e0a7f95ee1e2273719669d0ab9df6
|
|
58
|
+
prettyprint (0.2.0) sha256=2bc9e15581a94742064a3cc8b0fb9d45aae3d03a1baa6ef80922627a0766f193
|
|
59
|
+
psych (5.3.1) sha256=eb7a57cef10c9d70173ff74e739d843ac3b2c019a003de48447b2963d81b1974
|
|
60
|
+
rdoc (7.0.3) sha256=dfe3d0981d19b7bba71d9dbaeb57c9f4e3a7a4103162148a559c4fc687ea81f9
|
|
61
|
+
reline (0.6.3) sha256=1198b04973565b36ec0f11542ab3f5cfeeec34823f4e54cebde90968092b1835
|
|
62
|
+
stringio (3.2.0) sha256=c37cb2e58b4ffbd33fe5cd948c05934af997b36e0b6ca6fdf43afa234cf222e1
|
|
63
|
+
tsort (0.2.0) sha256=9650a793f6859a43b6641671278f79cfead60ac714148aabe4e3f0060480089f
|
|
64
|
+
|
|
65
|
+
BUNDLED WITH
|
|
66
|
+
4.0.3
|
data/example/README.md
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# Boxwerk Example Application
|
|
2
|
+
|
|
3
|
+
This directory contains a complete example of a Boxwerk application demonstrating strict package isolation and dependency management.
|
|
4
|
+
|
|
5
|
+
## Architecture
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
example/
|
|
9
|
+
├── Gemfile # Gem dependencies (money gem)
|
|
10
|
+
├── package.yml # Root package (imports finance)
|
|
11
|
+
├── app.rb # Application entry point
|
|
12
|
+
└── packages/
|
|
13
|
+
├── finance/
|
|
14
|
+
│ ├── package.yml # Exports Invoice, TaxCalculator; imports util
|
|
15
|
+
│ └── lib/
|
|
16
|
+
│ ├── invoice.rb
|
|
17
|
+
│ └── tax_calculator.rb
|
|
18
|
+
└── util/
|
|
19
|
+
├── package.yml # Exports Calculator, Geometry
|
|
20
|
+
└── lib/
|
|
21
|
+
├── calculator.rb
|
|
22
|
+
└── geometry.rb
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Dependency Graph
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
util (isolated box)
|
|
29
|
+
└── Calculator, Geometry
|
|
30
|
+
|
|
31
|
+
finance (isolated box)
|
|
32
|
+
├── imports: util (Calculator renamed to UtilCalculator)
|
|
33
|
+
└── Invoice, TaxCalculator
|
|
34
|
+
|
|
35
|
+
root (isolated box)
|
|
36
|
+
├── imports: finance
|
|
37
|
+
└── Finance::Invoice, Finance::TaxCalculator available
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Running the Example
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
cd example
|
|
44
|
+
RUBY_BOX=1 boxwerk run app.rb
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
**Architecture:**
|
|
48
|
+
- Root box: Contains gems (including money gem) and Boxwerk runtime
|
|
49
|
+
- Util box: Contains Calculator and Geometry classes
|
|
50
|
+
- Finance box: Contains Invoice and TaxCalculator (imports Calculator from Util box as UtilCalculator)
|
|
51
|
+
- Root package box: Runs app.rb (imports from Finance box)
|
|
52
|
+
|
|
53
|
+
**Important:** ALL packages (including root) run in isolated boxes. The main Ruby process only contains gems and the Boxwerk runtime.
|
|
54
|
+
|
|
55
|
+
### Interactive Console
|
|
56
|
+
|
|
57
|
+
You can also start an IRB console in the root package context:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
cd example
|
|
61
|
+
RUBY_BOX=1 boxwerk console
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
This gives you an interactive session with all imports available (e.g., `Finance::Invoice`).
|
|
65
|
+
|
|
66
|
+
## What This Demonstrates
|
|
67
|
+
|
|
68
|
+
### ✓ Strict Isolation
|
|
69
|
+
- `Finance::Invoice` and `Finance::TaxCalculator` are accessible (explicit import)
|
|
70
|
+
- `Calculator` and `Geometry` are NOT accessible (transitive dependency - not imported)
|
|
71
|
+
- `UtilCalculator` is NOT accessible (only available in Finance package's box)
|
|
72
|
+
- `Invoice` at top level is NOT accessible (must use `Finance::` namespace)
|
|
73
|
+
|
|
74
|
+
### ✓ Namespace Control
|
|
75
|
+
- Finance package exports are grouped under `Finance::` module
|
|
76
|
+
- Import strategies control how dependencies are wired
|
|
77
|
+
|
|
78
|
+
### ✓ Selective Rename Strategy
|
|
79
|
+
- Finance imports `Calculator` from util and renames it to `UtilCalculator`
|
|
80
|
+
- This demonstrates the selective rename import strategy
|
|
81
|
+
|
|
82
|
+
### ✓ Transitive Dependency Blocking
|
|
83
|
+
- Root imports Finance
|
|
84
|
+
- Finance imports Util
|
|
85
|
+
- Root cannot access Util classes (no transitive access)
|
|
86
|
+
|
|
87
|
+
### ✓ Gem Access
|
|
88
|
+
- Money gem is globally accessible in all packages
|
|
89
|
+
- Gems from Gemfile are auto-required and available everywhere
|
|
90
|
+
|
|
91
|
+
### ✓ Clean API Surface
|
|
92
|
+
- Each package explicitly declares exports
|
|
93
|
+
- Consumers only see what's exported
|
|
94
|
+
- Internal implementation details remain hidden
|
|
95
|
+
|
|
96
|
+
## Package Configuration
|
|
97
|
+
|
|
98
|
+
### Root Package (`package.yml`)
|
|
99
|
+
|
|
100
|
+
```yaml
|
|
101
|
+
imports:
|
|
102
|
+
- packages/finance # Default namespace strategy
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Finance Package (`packages/finance/package.yml`)
|
|
106
|
+
|
|
107
|
+
```yaml
|
|
108
|
+
exports:
|
|
109
|
+
- Invoice
|
|
110
|
+
- TaxCalculator
|
|
111
|
+
|
|
112
|
+
imports:
|
|
113
|
+
# Import Calculator from util and rename it to UtilCalculator
|
|
114
|
+
- packages/util:
|
|
115
|
+
Calculator: UtilCalculator
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Util Package (`packages/util/package.yml`)
|
|
119
|
+
|
|
120
|
+
```yaml
|
|
121
|
+
exports:
|
|
122
|
+
- Calculator
|
|
123
|
+
- Geometry
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Requirements
|
|
127
|
+
|
|
128
|
+
- Ruby 4.0+ with `Ruby::Box` support
|
|
129
|
+
- `RUBY_BOX=1` environment variable must be set
|
|
130
|
+
- Boxwerk gem installed or loaded from `../lib`
|