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
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Boxwerk
|
|
4
|
+
# Loader handles the creation and initialization of isolated package boxes
|
|
5
|
+
class Loader
|
|
6
|
+
class << self
|
|
7
|
+
# Boot all packages in topological order
|
|
8
|
+
# @param graph [Boxwerk::Graph] The dependency graph
|
|
9
|
+
# @param registry [Boxwerk::Registry] The registry instance
|
|
10
|
+
def boot_all(graph, registry)
|
|
11
|
+
order = graph.topological_order
|
|
12
|
+
|
|
13
|
+
order.each { |package| boot(package, graph, registry) }
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Boot a single package
|
|
17
|
+
# @param package [Boxwerk::Package] The package to boot
|
|
18
|
+
# @param graph [Boxwerk::Graph] The dependency graph
|
|
19
|
+
# @param registry [Boxwerk::Registry] The registry instance
|
|
20
|
+
def boot(package, graph, registry)
|
|
21
|
+
return package if package.booted?
|
|
22
|
+
|
|
23
|
+
# Check if RUBY_BOX environment variable is set
|
|
24
|
+
unless ENV['RUBY_BOX'] == '1'
|
|
25
|
+
raise 'Boxwerk requires RUBY_BOX=1 environment variable to enable Ruby::Box support'
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Check if we have Ruby::Box support
|
|
29
|
+
unless defined?(Ruby::Box)
|
|
30
|
+
raise 'Boxwerk requires Ruby 4.0+ with Ruby::Box support'
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# All packages (including root) get their own isolated boxes
|
|
34
|
+
box = Ruby::Box.new
|
|
35
|
+
|
|
36
|
+
# Store the box reference first so it's available during wiring
|
|
37
|
+
package.box = box
|
|
38
|
+
|
|
39
|
+
# Wire imports based on configuration (exports loaded lazily on-demand)
|
|
40
|
+
wire_imports(box, package, graph)
|
|
41
|
+
|
|
42
|
+
# Register in the registry
|
|
43
|
+
registry.register(package.name, package)
|
|
44
|
+
|
|
45
|
+
package
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
# Load a specific exported constant from package's lib directory using Zeitwerk conventions
|
|
51
|
+
# This enforces strict isolation - only requested exports are loaded, lazily
|
|
52
|
+
# @param package [Boxwerk::Package] The package
|
|
53
|
+
# @param const_name [String] The constant name to load
|
|
54
|
+
def load_export(package, const_name)
|
|
55
|
+
# Skip if already loaded (cached)
|
|
56
|
+
return if package.loaded_exports.key?(const_name)
|
|
57
|
+
|
|
58
|
+
lib_path = File.join(package.path, 'lib')
|
|
59
|
+
return unless File.directory?(lib_path)
|
|
60
|
+
|
|
61
|
+
# Find the file path for this constant using Zeitwerk conventions
|
|
62
|
+
file_path = find_file_for_constant(lib_path, const_name)
|
|
63
|
+
|
|
64
|
+
unless file_path
|
|
65
|
+
raise "Cannot find file for exported constant '#{const_name}' in package '#{package.name}' at #{lib_path}"
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Load the file in the package's box
|
|
69
|
+
package.box.require(file_path)
|
|
70
|
+
|
|
71
|
+
# Cache the mapping AFTER successful load
|
|
72
|
+
package.loaded_exports[const_name] = file_path
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Load only exported constants from package's lib directory using Zeitwerk conventions
|
|
76
|
+
# @param box [Ruby::Box] The box instance
|
|
77
|
+
# @param package [Boxwerk::Package] The package
|
|
78
|
+
def load_exports(box, package)
|
|
79
|
+
lib_path = File.join(package.path, 'lib')
|
|
80
|
+
return unless File.directory?(lib_path)
|
|
81
|
+
|
|
82
|
+
# Find all files that might contain exported constants and map them to constants
|
|
83
|
+
files_to_load = {} # file_path => [const_name, ...]
|
|
84
|
+
|
|
85
|
+
package.exports.each do |const_name|
|
|
86
|
+
# Skip if already loaded (cached)
|
|
87
|
+
next if package.loaded_exports.key?(const_name)
|
|
88
|
+
|
|
89
|
+
# Find the file path for this constant using Zeitwerk conventions
|
|
90
|
+
file_path = find_file_for_constant(lib_path, const_name)
|
|
91
|
+
|
|
92
|
+
unless file_path
|
|
93
|
+
raise "Cannot find file for exported constant '#{const_name}' in package '#{package.name}' at #{lib_path}"
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
files_to_load[file_path] ||= []
|
|
97
|
+
files_to_load[file_path] << const_name
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Load only the discovered files in the box (strict mode - no fallback)
|
|
101
|
+
files_to_load.keys.sort.each do |file|
|
|
102
|
+
box.require(file)
|
|
103
|
+
|
|
104
|
+
# Cache the mapping AFTER successful load
|
|
105
|
+
files_to_load[file].each do |const_name|
|
|
106
|
+
package.loaded_exports[const_name] = file
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Find the file that should define a constant using Zeitwerk's conventions
|
|
112
|
+
# Handles nested constants: Foo::Bar can be in lib/foo/bar.rb OR lib/foo.rb
|
|
113
|
+
# @param lib_path [String] The lib directory path
|
|
114
|
+
# @param const_name [String] The constant name (can include ::)
|
|
115
|
+
# @return [String, nil] The file path or nil if not found
|
|
116
|
+
def find_file_for_constant(lib_path, const_name)
|
|
117
|
+
# For nested constants like Foo::Bar, try the nested path first
|
|
118
|
+
if const_name.include?('::')
|
|
119
|
+
# Try conventional nested path: Foo::Bar -> lib/foo/bar.rb
|
|
120
|
+
nested_path = File.join(lib_path, "#{underscore(const_name)}.rb")
|
|
121
|
+
return nested_path if File.exist?(nested_path)
|
|
122
|
+
|
|
123
|
+
# Fall back to parent path: Foo::Bar -> lib/foo.rb
|
|
124
|
+
# The parent file might define the nested constant
|
|
125
|
+
parts = const_name.split('::')
|
|
126
|
+
parent_name = parts[0..-2].join('::')
|
|
127
|
+
parent_file = if parent_name.empty?
|
|
128
|
+
# Top-level nested constant (shouldn't happen, but handle it)
|
|
129
|
+
File.join(lib_path, "#{underscore(parts[-1])}.rb")
|
|
130
|
+
else
|
|
131
|
+
File.join(lib_path, "#{underscore(parent_name)}.rb")
|
|
132
|
+
end
|
|
133
|
+
return parent_file if File.exist?(parent_file)
|
|
134
|
+
else
|
|
135
|
+
# For top-level constants, try conventional path
|
|
136
|
+
conventional_path = File.join(lib_path, "#{underscore(const_name)}.rb")
|
|
137
|
+
return conventional_path if File.exist?(conventional_path)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
nil
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# Convert CamelCase to snake_case (Zeitwerk-compatible)
|
|
144
|
+
# @param string [String] The string to convert
|
|
145
|
+
# @return [String] The underscored string
|
|
146
|
+
def underscore(string)
|
|
147
|
+
string
|
|
148
|
+
.gsub(/::/, '/')
|
|
149
|
+
.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
|
|
150
|
+
.gsub(/([a-z\d])([A-Z])/, '\1_\2')
|
|
151
|
+
.tr('-', '_')
|
|
152
|
+
.downcase
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# Wire imports according to YAML configuration (list format)
|
|
156
|
+
# @param box [Ruby::Box] The box instance
|
|
157
|
+
# @param package [Boxwerk::Package] The package being booted
|
|
158
|
+
# @param graph [Boxwerk::Graph] The dependency graph
|
|
159
|
+
def wire_imports(box, package, graph)
|
|
160
|
+
package.imports.each do |import_item|
|
|
161
|
+
# Normalize: String or Hash
|
|
162
|
+
if import_item.is_a?(String)
|
|
163
|
+
path = import_item
|
|
164
|
+
config = nil
|
|
165
|
+
else
|
|
166
|
+
path = import_item.keys.first
|
|
167
|
+
config = import_item.values.first
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
dep_name = File.basename(path)
|
|
171
|
+
dependency = graph.packages[dep_name]
|
|
172
|
+
|
|
173
|
+
unless dependency
|
|
174
|
+
raise "Cannot resolve dependency '#{path}' for package '#{package.name}'"
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
unless dependency.booted?
|
|
178
|
+
raise "Dependency '#{dep_name}' not booted yet"
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
wire_import_strategy(box, package, path, config, dependency)
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
# Execute the appropriate wiring strategy based on config type
|
|
186
|
+
# @param box [Ruby::Box] The box instance
|
|
187
|
+
# @param package [Boxwerk::Package] The current package being wired
|
|
188
|
+
# @param path [String] The dependency path
|
|
189
|
+
# @param config [nil, String, Array, Hash] The import configuration
|
|
190
|
+
# @param dependency [Boxwerk::Package] The dependency package
|
|
191
|
+
def wire_import_strategy(box, package, path, config, dependency)
|
|
192
|
+
case config
|
|
193
|
+
when nil
|
|
194
|
+
# Strategy 1: Default Namespace ("packages/billing" -> "Billing")
|
|
195
|
+
name = camelize(File.basename(path))
|
|
196
|
+
create_namespace(package, name, dependency)
|
|
197
|
+
when String
|
|
198
|
+
# Strategy 2: Aliased Namespace ("packages/identity": "Auth")
|
|
199
|
+
create_namespace(package, config, dependency)
|
|
200
|
+
when Array
|
|
201
|
+
# Strategy 3: Selective List (["Log", "Metrics"])
|
|
202
|
+
config.each do |const_name|
|
|
203
|
+
value = get_constant(dependency, const_name)
|
|
204
|
+
set_constant(package, const_name, value)
|
|
205
|
+
end
|
|
206
|
+
when Hash
|
|
207
|
+
# Strategy 4: Selective Rename ({Invoice: "Bill"})
|
|
208
|
+
config.each do |remote_name, local_alias|
|
|
209
|
+
value = get_constant(dependency, remote_name)
|
|
210
|
+
set_constant(package, local_alias, value)
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
# Create a namespace module and populate with all exports
|
|
216
|
+
# Or import directly if single export (optimization)
|
|
217
|
+
# @param package [Boxwerk::Package] The current package
|
|
218
|
+
# @param namespace_name [String] The module name to create
|
|
219
|
+
# @param dependency [Boxwerk::Package] The dependency package
|
|
220
|
+
def create_namespace(package, namespace_name, dependency)
|
|
221
|
+
if dependency.exports.size == 1
|
|
222
|
+
# Single export optimization: import directly
|
|
223
|
+
value = get_constant(dependency, dependency.exports.first)
|
|
224
|
+
set_constant(package, namespace_name, value)
|
|
225
|
+
else
|
|
226
|
+
# Multiple exports: create namespace module
|
|
227
|
+
mod = create_module(package.box, namespace_name)
|
|
228
|
+
dependency.exports.each do |export_name|
|
|
229
|
+
value = get_constant(dependency, export_name)
|
|
230
|
+
mod.const_set(export_name.to_sym, value)
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
# Get a constant from a package's box, loading it lazily if needed
|
|
236
|
+
# @param package [Boxwerk::Package] The package to get the constant from
|
|
237
|
+
# @param name [String] The constant name
|
|
238
|
+
# @return [Object] The constant value
|
|
239
|
+
def get_constant(package, name)
|
|
240
|
+
# Load the export lazily
|
|
241
|
+
load_export(package, name)
|
|
242
|
+
|
|
243
|
+
package.box.const_get(name.to_sym)
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
# Set a constant in a package's box
|
|
247
|
+
# @param package [Boxwerk::Package] The package to set the constant in
|
|
248
|
+
# @param name [String] The constant name
|
|
249
|
+
# @param value [Object] The constant value
|
|
250
|
+
def set_constant(package, name, value)
|
|
251
|
+
package.box.const_set(name.to_sym, value)
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
# Create a module in the box
|
|
255
|
+
def create_module(box, name)
|
|
256
|
+
unless box.const_defined?(name.to_sym, false)
|
|
257
|
+
mod = Module.new
|
|
258
|
+
box.const_set(name.to_sym, mod)
|
|
259
|
+
mod
|
|
260
|
+
else
|
|
261
|
+
box.const_get(name.to_sym)
|
|
262
|
+
end
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
# Set a constant within a module in the box
|
|
266
|
+
def set_const_in_module(box, module_name, const_name, value)
|
|
267
|
+
mod = box.const_get(module_name.to_sym, false)
|
|
268
|
+
mod.const_set(const_name.to_sym, value)
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
# Simple camelization (underscore to CamelCase)
|
|
272
|
+
def camelize(string)
|
|
273
|
+
string.split('_').map(&:capitalize).join
|
|
274
|
+
end
|
|
275
|
+
end
|
|
276
|
+
end
|
|
277
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'yaml'
|
|
4
|
+
|
|
5
|
+
module Boxwerk
|
|
6
|
+
# Represents a package loaded from package.yml
|
|
7
|
+
class Package
|
|
8
|
+
attr_reader :name, :path, :exports, :imports, :box, :loaded_exports
|
|
9
|
+
|
|
10
|
+
def initialize(name, path)
|
|
11
|
+
@name = name
|
|
12
|
+
@path = path
|
|
13
|
+
@box = nil
|
|
14
|
+
@exports = []
|
|
15
|
+
@imports = []
|
|
16
|
+
@loaded_exports = {}
|
|
17
|
+
|
|
18
|
+
load_config
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def booted?
|
|
22
|
+
!@box.nil?
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def box=(box_instance)
|
|
26
|
+
@box = box_instance
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def dependencies
|
|
30
|
+
@imports.map do |item|
|
|
31
|
+
if item.is_a?(String)
|
|
32
|
+
item
|
|
33
|
+
else
|
|
34
|
+
item.keys.first
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
private
|
|
40
|
+
|
|
41
|
+
def load_config
|
|
42
|
+
config_path = File.join(@path, 'package.yml')
|
|
43
|
+
return unless File.exist?(config_path)
|
|
44
|
+
|
|
45
|
+
config = YAML.load_file(config_path)
|
|
46
|
+
|
|
47
|
+
@exports = config['exports'] || []
|
|
48
|
+
@imports = config['imports'] || []
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Boxwerk
|
|
4
|
+
# Registry for tracking booted package instances
|
|
5
|
+
# This allows packages to be booted once and reused
|
|
6
|
+
class Registry
|
|
7
|
+
def initialize
|
|
8
|
+
@registry = {}
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# Register a booted package
|
|
12
|
+
# @param name [Symbol] Package name
|
|
13
|
+
# @param instance [Object] The booted package instance
|
|
14
|
+
def register(name, instance)
|
|
15
|
+
@registry[name] = instance
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Retrieve a booted package
|
|
19
|
+
# @param name [Symbol] Package name
|
|
20
|
+
# @return [Object, nil] The package instance or nil
|
|
21
|
+
def get(name)
|
|
22
|
+
@registry[name]
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Check if a package is registered
|
|
26
|
+
# @param name [Symbol] Package name
|
|
27
|
+
# @return [Boolean]
|
|
28
|
+
def registered?(name)
|
|
29
|
+
@registry.key?(name)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Clear the registry (useful for testing)
|
|
33
|
+
def clear!
|
|
34
|
+
@registry = {}
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Boxwerk
|
|
4
|
+
# Setup orchestrates the Boxwerk boot process
|
|
5
|
+
module Setup
|
|
6
|
+
class << self
|
|
7
|
+
# Main entry point for setup
|
|
8
|
+
# @param start_dir [String] Directory to start searching for package.yml
|
|
9
|
+
# @return [Boxwerk::Graph] The loaded and validated graph
|
|
10
|
+
def run!(start_dir: Dir.pwd)
|
|
11
|
+
# Find the root package.yml
|
|
12
|
+
root_path = find_package_yml(start_dir)
|
|
13
|
+
unless root_path
|
|
14
|
+
raise 'Cannot find package.yml in current directory or ancestors'
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Build and validate dependency graph (happens automatically in constructor)
|
|
18
|
+
graph = Boxwerk::Graph.new(root_path)
|
|
19
|
+
|
|
20
|
+
# Create a registry instance for tracking booted packages
|
|
21
|
+
registry = Boxwerk::Registry.new
|
|
22
|
+
|
|
23
|
+
# Boot all packages in topological order (all in isolated boxes)
|
|
24
|
+
Boxwerk::Loader.boot_all(graph, registry)
|
|
25
|
+
|
|
26
|
+
# Store graph for introspection
|
|
27
|
+
@graph = graph
|
|
28
|
+
@booted = true
|
|
29
|
+
|
|
30
|
+
graph
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Returns the loaded graph (for introspection)
|
|
34
|
+
# @return [Boxwerk::Graph, nil]
|
|
35
|
+
def graph
|
|
36
|
+
@graph
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Check if Boxwerk has been booted
|
|
40
|
+
# @return [Boolean]
|
|
41
|
+
def booted?
|
|
42
|
+
@booted || false
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Reset the setup state (useful for testing)
|
|
46
|
+
def reset!
|
|
47
|
+
@graph = nil
|
|
48
|
+
@booted = false
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
private
|
|
52
|
+
|
|
53
|
+
# Find package.yml by searching up the directory tree
|
|
54
|
+
# @param start_dir [String] Directory to start searching from
|
|
55
|
+
# @return [String, nil] Path to directory containing package.yml, or nil
|
|
56
|
+
def find_package_yml(start_dir)
|
|
57
|
+
current = File.expand_path(start_dir)
|
|
58
|
+
loop do
|
|
59
|
+
package_yml = File.join(current, 'package.yml')
|
|
60
|
+
return current if File.exist?(package_yml)
|
|
61
|
+
|
|
62
|
+
parent = File.dirname(current)
|
|
63
|
+
break if parent == current # reached filesystem root
|
|
64
|
+
|
|
65
|
+
current = parent
|
|
66
|
+
end
|
|
67
|
+
nil
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
data/lib/boxwerk.rb
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'boxwerk/cli'
|
|
4
|
+
require_relative 'boxwerk/graph'
|
|
5
|
+
require_relative 'boxwerk/loader'
|
|
6
|
+
require_relative 'boxwerk/package'
|
|
7
|
+
require_relative 'boxwerk/registry'
|
|
8
|
+
require_relative 'boxwerk/setup'
|
|
9
|
+
require_relative 'boxwerk/version'
|
|
10
|
+
|
|
11
|
+
module Boxwerk
|
|
12
|
+
class Error < StandardError
|
|
13
|
+
end
|
|
14
|
+
end
|
data/sig/boxwerk.rbs
ADDED
metadata
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: boxwerk
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- David Cristofaro
|
|
8
|
+
bindir: exe
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: irb
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '1.16'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '1.16'
|
|
26
|
+
description: Boxwerk provides strict package isolation for Ruby applications using
|
|
27
|
+
Ruby 4.0+ Box. It is used to organize code into packages with explicit dependency
|
|
28
|
+
graphs and strict access to constants between packages. It is inspired by Packwerk,
|
|
29
|
+
a static package system.
|
|
30
|
+
email:
|
|
31
|
+
- david@dtcristo.com
|
|
32
|
+
executables:
|
|
33
|
+
- boxwerk
|
|
34
|
+
extensions: []
|
|
35
|
+
extra_rdoc_files: []
|
|
36
|
+
files:
|
|
37
|
+
- CHANGELOG.md
|
|
38
|
+
- LICENSE.txt
|
|
39
|
+
- README.md
|
|
40
|
+
- Rakefile
|
|
41
|
+
- example/Gemfile
|
|
42
|
+
- example/Gemfile.lock
|
|
43
|
+
- example/README.md
|
|
44
|
+
- example/app.rb
|
|
45
|
+
- example/package.yml
|
|
46
|
+
- example/packages/finance/lib/invoice.rb
|
|
47
|
+
- example/packages/finance/lib/tax_calculator.rb
|
|
48
|
+
- example/packages/finance/package.yml
|
|
49
|
+
- example/packages/util/lib/calculator.rb
|
|
50
|
+
- example/packages/util/lib/geometry.rb
|
|
51
|
+
- example/packages/util/package.yml
|
|
52
|
+
- exe/boxwerk
|
|
53
|
+
- lib/boxwerk.rb
|
|
54
|
+
- lib/boxwerk/cli.rb
|
|
55
|
+
- lib/boxwerk/graph.rb
|
|
56
|
+
- lib/boxwerk/loader.rb
|
|
57
|
+
- lib/boxwerk/package.rb
|
|
58
|
+
- lib/boxwerk/registry.rb
|
|
59
|
+
- lib/boxwerk/setup.rb
|
|
60
|
+
- lib/boxwerk/version.rb
|
|
61
|
+
- sig/boxwerk.rbs
|
|
62
|
+
homepage: https://github.com/dtcristo/boxwerk
|
|
63
|
+
licenses:
|
|
64
|
+
- MIT
|
|
65
|
+
metadata:
|
|
66
|
+
allowed_push_host: https://rubygems.org
|
|
67
|
+
homepage_uri: https://github.com/dtcristo/boxwerk
|
|
68
|
+
source_code_uri: https://github.com/dtcristo/boxwerk
|
|
69
|
+
changelog_uri: https://github.com/dtcristo/boxwerk/blob/main/CHANGELOG.md
|
|
70
|
+
rdoc_options: []
|
|
71
|
+
require_paths:
|
|
72
|
+
- lib
|
|
73
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
74
|
+
requirements:
|
|
75
|
+
- - ">="
|
|
76
|
+
- !ruby/object:Gem::Version
|
|
77
|
+
version: 4.0.0
|
|
78
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
79
|
+
requirements:
|
|
80
|
+
- - ">="
|
|
81
|
+
- !ruby/object:Gem::Version
|
|
82
|
+
version: '0'
|
|
83
|
+
requirements: []
|
|
84
|
+
rubygems_version: 4.0.3
|
|
85
|
+
specification_version: 4
|
|
86
|
+
summary: Ruby package system with Box-powered constant isolation
|
|
87
|
+
test_files: []
|