boxwerk 0.1.0 → 0.3.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 +4 -4
- data/AGENTS.md +24 -0
- data/ARCHITECTURE.md +264 -0
- data/CHANGELOG.md +74 -3
- data/README.md +61 -335
- data/Rakefile +46 -3
- data/TODO.md +317 -0
- data/USAGE.md +505 -0
- data/exe/boxwerk +55 -22
- data/lib/boxwerk/autoloader_mixin.rb +65 -0
- data/lib/boxwerk/box_manager.rb +405 -0
- data/lib/boxwerk/cli.rb +775 -68
- data/lib/boxwerk/constant_resolver.rb +236 -0
- data/lib/boxwerk/gem_resolver.rb +235 -0
- data/lib/boxwerk/gemfile_require_parser.rb +50 -0
- data/lib/boxwerk/global_context.rb +85 -0
- data/lib/boxwerk/package.rb +71 -28
- data/lib/boxwerk/package_context.rb +103 -0
- data/lib/boxwerk/package_resolver.rb +122 -0
- data/lib/boxwerk/privacy_checker.rb +159 -0
- data/lib/boxwerk/setup.rb +123 -36
- data/lib/boxwerk/version.rb +1 -1
- data/lib/boxwerk/zeitwerk_scanner.rb +172 -0
- data/lib/boxwerk.rb +26 -4
- metadata +54 -24
- data/example/Gemfile +0 -6
- data/example/Gemfile.lock +0 -66
- data/example/README.md +0 -130
- data/example/app.rb +0 -101
- data/example/package.yml +0 -6
- data/example/packages/finance/lib/invoice.rb +0 -51
- data/example/packages/finance/lib/tax_calculator.rb +0 -26
- data/example/packages/finance/package.yml +0 -10
- data/example/packages/util/lib/calculator.rb +0 -21
- data/example/packages/util/lib/geometry.rb +0 -26
- data/example/packages/util/package.yml +0 -5
- data/lib/boxwerk/graph.rb +0 -111
- data/lib/boxwerk/loader.rb +0 -277
- data/lib/boxwerk/registry.rb +0 -37
- data/sig/boxwerk.rbs +0 -4
data/lib/boxwerk/setup.rb
CHANGED
|
@@ -1,70 +1,157 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Boxwerk
|
|
4
|
-
# Setup orchestrates the Boxwerk boot process
|
|
5
4
|
module Setup
|
|
6
5
|
class << self
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
raise 'Cannot find package.yml in current directory or ancestors'
|
|
15
|
-
end
|
|
6
|
+
def run(start_dir: Dir.pwd, packages: nil, config: {})
|
|
7
|
+
root_path = find_root(start_dir)
|
|
8
|
+
|
|
9
|
+
resolver = Boxwerk::PackageResolver.new(root_path, config_overrides: config)
|
|
10
|
+
config = resolver.boxwerk_config
|
|
11
|
+
eager_load_global = config.fetch('eager_load_global', true)
|
|
12
|
+
eager_load_packages = config.fetch('eager_load_packages', false)
|
|
16
13
|
|
|
17
|
-
#
|
|
18
|
-
|
|
14
|
+
# Create GlobalContext and expose it as Boxwerk.global before boot.rb runs
|
|
15
|
+
global_context = GlobalContext.new(root_path)
|
|
16
|
+
root_box = Ruby::Box.root
|
|
17
|
+
root_box.__send__(:remove_const, :BOXWERK_GLOBAL) if root_box.const_defined?(:BOXWERK_GLOBAL, false)
|
|
18
|
+
root_box.const_set(:BOXWERK_GLOBAL, global_context)
|
|
19
19
|
|
|
20
|
-
#
|
|
21
|
-
|
|
20
|
+
# Run global boot in root box (after gems, before package boxes).
|
|
21
|
+
run_global_boot(root_path, eager_load: eager_load_global, global_context: global_context)
|
|
22
22
|
|
|
23
|
-
#
|
|
24
|
-
|
|
23
|
+
# Eager-load all Zeitwerk-managed constants in root box so child
|
|
24
|
+
# boxes inherit fully resolved constants (not pending autoloads).
|
|
25
|
+
eager_load_zeitwerk if eager_load_global
|
|
26
|
+
|
|
27
|
+
@box_manager = Boxwerk::BoxManager.new(root_path)
|
|
28
|
+
if packages
|
|
29
|
+
packages.each do |pkg|
|
|
30
|
+
@box_manager.boot_package(
|
|
31
|
+
pkg,
|
|
32
|
+
resolver,
|
|
33
|
+
eager_load_packages: eager_load_packages,
|
|
34
|
+
)
|
|
35
|
+
end
|
|
36
|
+
else
|
|
37
|
+
@box_manager.boot_all(
|
|
38
|
+
resolver,
|
|
39
|
+
eager_load_packages: eager_load_packages,
|
|
40
|
+
)
|
|
41
|
+
end
|
|
25
42
|
|
|
26
|
-
|
|
27
|
-
|
|
43
|
+
check_gem_conflicts(@box_manager.gem_resolver, resolver)
|
|
44
|
+
|
|
45
|
+
@resolver = resolver
|
|
28
46
|
@booted = true
|
|
29
47
|
|
|
30
|
-
|
|
48
|
+
{ resolver: resolver, box_manager: @box_manager, root_path: root_path }
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def resolver
|
|
52
|
+
@resolver
|
|
31
53
|
end
|
|
32
54
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
def graph
|
|
36
|
-
@graph
|
|
55
|
+
def box_manager
|
|
56
|
+
@box_manager
|
|
37
57
|
end
|
|
38
58
|
|
|
39
|
-
# Check if Boxwerk has been booted
|
|
40
|
-
# @return [Boolean]
|
|
41
59
|
def booted?
|
|
42
60
|
@booted || false
|
|
43
61
|
end
|
|
44
62
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
63
|
+
def root_box
|
|
64
|
+
return nil unless @box_manager && @resolver&.root
|
|
65
|
+
|
|
66
|
+
@box_manager.boxes[@resolver.root.name]
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def reset
|
|
70
|
+
@resolver = nil
|
|
71
|
+
@box_manager = nil
|
|
48
72
|
@booted = false
|
|
73
|
+
if defined?(Ruby::Box)
|
|
74
|
+
root_box = Ruby::Box.root
|
|
75
|
+
root_box.__send__(:remove_const, :BOXWERK_GLOBAL) if root_box.const_defined?(:BOXWERK_GLOBAL, false)
|
|
76
|
+
end
|
|
49
77
|
end
|
|
50
78
|
|
|
51
79
|
private
|
|
52
80
|
|
|
53
|
-
#
|
|
54
|
-
#
|
|
55
|
-
#
|
|
56
|
-
def
|
|
81
|
+
# Finds the project root directory. Walks up the directory tree
|
|
82
|
+
# looking for boxwerk.yml or package.yml. Falls back to start_dir
|
|
83
|
+
# if neither is found (implicit root).
|
|
84
|
+
def find_root(start_dir)
|
|
57
85
|
current = File.expand_path(start_dir)
|
|
58
86
|
loop do
|
|
59
|
-
|
|
60
|
-
return current if File.exist?(
|
|
87
|
+
return current if File.exist?(File.join(current, 'boxwerk.yml'))
|
|
88
|
+
return current if File.exist?(File.join(current, 'package.yml'))
|
|
61
89
|
|
|
62
90
|
parent = File.dirname(current)
|
|
63
|
-
break if parent == current
|
|
91
|
+
break if parent == current
|
|
64
92
|
|
|
65
93
|
current = parent
|
|
66
94
|
end
|
|
67
|
-
|
|
95
|
+
|
|
96
|
+
# Fall back to CWD as implicit root
|
|
97
|
+
File.expand_path(start_dir)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Runs the optional global boot in the root box. The global/
|
|
101
|
+
# directory is autoloaded and global/boot.rb is required in the root
|
|
102
|
+
# box. These run after global gems are loaded but before package
|
|
103
|
+
# boxes are created, so definitions here are inherited by all boxes.
|
|
104
|
+
#
|
|
105
|
+
# Root-level boot.rb is NOT handled here — it runs in the root
|
|
106
|
+
# package box via BoxManager (like any other package boot.rb).
|
|
107
|
+
def run_global_boot(root_path, eager_load: true, global_context: nil)
|
|
108
|
+
root_box = Ruby::Box.root
|
|
109
|
+
global_dir = File.join(root_path, 'global')
|
|
110
|
+
global_boot = File.join(global_dir, 'boot.rb')
|
|
111
|
+
global_entries = nil
|
|
112
|
+
|
|
113
|
+
# Always register lazy autoloads for global/ dir so constants are
|
|
114
|
+
# accessible in boot.rb (autoload fires on access in root box context).
|
|
115
|
+
# This works regardless of eager_load_global so global constants can
|
|
116
|
+
# be used in boot.rb even when eager loading is disabled.
|
|
117
|
+
if File.directory?(global_dir)
|
|
118
|
+
global_context&.__send__(:record_scanned_dir, 'global/')
|
|
119
|
+
entries = ZeitwerkScanner.scan(global_dir)
|
|
120
|
+
global_entries = entries.reject { |e| e.file == global_boot }
|
|
121
|
+
ZeitwerkScanner.register_autoloads(root_box, global_entries) if global_entries.any?
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# Run global/boot.rb in root box (always, regardless of eager_load)
|
|
125
|
+
root_box.require(global_boot) if File.exist?(global_boot)
|
|
126
|
+
|
|
127
|
+
# After boot.rb, register lazy autoloads for any dirs added via
|
|
128
|
+
# Boxwerk.global.autoloader.push_dir during boot.
|
|
129
|
+
global_context&.autoloader&.setup
|
|
130
|
+
|
|
131
|
+
# Eager-load AFTER boot.rb so boot scripts run first.
|
|
132
|
+
if eager_load
|
|
133
|
+
global_entries&.each { |e| root_box.require(e.file) if e.file }
|
|
134
|
+
global_context&.autoloader&.__send__(:eager_load!)
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def check_gem_conflicts(gem_resolver, package_resolver)
|
|
139
|
+
conflicts = gem_resolver.check_conflicts(package_resolver)
|
|
140
|
+
conflicts.each do |c|
|
|
141
|
+
warn "Boxwerk: gem '#{c[:gem_name]}' is #{c[:package_version]} in #{c[:package]} " \
|
|
142
|
+
"but #{c[:global_version]} in global gems — both versions will be loaded into memory"
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# Eager-load all Zeitwerk-managed constants (gem autoloads) in the root
|
|
147
|
+
# box. Child boxes created via Ruby::Box.new inherit a snapshot of the
|
|
148
|
+
# root box's constants. Zeitwerk autoloads are lazy — without eager
|
|
149
|
+
# loading, child boxes inherit pending autoload entries that may not
|
|
150
|
+
# resolve correctly across box boundaries.
|
|
151
|
+
def eager_load_zeitwerk
|
|
152
|
+
Ruby::Box.root.eval(<<~RUBY)
|
|
153
|
+
Zeitwerk::Loader.eager_load_all if defined?(Zeitwerk)
|
|
154
|
+
RUBY
|
|
68
155
|
end
|
|
69
156
|
end
|
|
70
157
|
end
|
data/lib/boxwerk/version.rb
CHANGED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'zeitwerk'
|
|
4
|
+
|
|
5
|
+
module Boxwerk
|
|
6
|
+
# Uses Zeitwerk's file system scanner and inflector to discover constants
|
|
7
|
+
# in a directory. Zeitwerk's autoload registration cannot be used directly
|
|
8
|
+
# inside Ruby::Box (autoloads register in the box where the code was
|
|
9
|
+
# defined, not where it's called), so we only use Zeitwerk for:
|
|
10
|
+
#
|
|
11
|
+
# - File discovery (respecting Zeitwerk conventions: hidden dirs, etc.)
|
|
12
|
+
# - Inflection (file names → constant names)
|
|
13
|
+
#
|
|
14
|
+
# The actual autoload registration is done by BoxManager using box.eval.
|
|
15
|
+
module ZeitwerkScanner
|
|
16
|
+
Entry = Data.define(:type, :cname, :full_path, :file, :parent, :dir)
|
|
17
|
+
|
|
18
|
+
# Scans a directory and returns an array of Entry structs describing
|
|
19
|
+
# the constants and namespaces found. Uses a temporary Zeitwerk::Loader
|
|
20
|
+
# for its FileSystem scanner and Inflector.
|
|
21
|
+
def self.scan(dir)
|
|
22
|
+
loader = Zeitwerk::Loader.new
|
|
23
|
+
loader.push_dir(dir)
|
|
24
|
+
inflector = loader.inflector
|
|
25
|
+
fs = Zeitwerk::Loader::FileSystem.new(loader)
|
|
26
|
+
|
|
27
|
+
entries = []
|
|
28
|
+
scan_dir(fs, inflector, dir, '', entries)
|
|
29
|
+
entries
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Registers autoloads in a Ruby::Box based on scan results.
|
|
33
|
+
# Implicit namespaces become Module.new, explicit namespaces are
|
|
34
|
+
# eagerly loaded so child autoloads can attach to them.
|
|
35
|
+
def self.register_autoloads(box, entries)
|
|
36
|
+
namespaces = entries.select { |e| e.type == :namespace }
|
|
37
|
+
files = entries.select { |e| e.type == :file }
|
|
38
|
+
|
|
39
|
+
# Deduplicate namespaces: when both lib/ and public/ contribute a
|
|
40
|
+
# namespace with the same full_path, prefer the explicit one (has a
|
|
41
|
+
# .rb file) so the module definition is loaded rather than replaced
|
|
42
|
+
# by an empty Module.new.
|
|
43
|
+
namespaces =
|
|
44
|
+
namespaces
|
|
45
|
+
.group_by(&:full_path)
|
|
46
|
+
.values
|
|
47
|
+
.map { |group| group.find { |ns| ns.file } || group.first }
|
|
48
|
+
|
|
49
|
+
# Phase 1: Set up namespaces
|
|
50
|
+
namespaces.each do |ns|
|
|
51
|
+
if ns.file
|
|
52
|
+
# Explicit namespace: autoload the .rb file
|
|
53
|
+
register_autoload(box, ns.parent, ns.cname, ns.file)
|
|
54
|
+
else
|
|
55
|
+
# Implicit namespace: create empty module
|
|
56
|
+
define_implicit_module(box, ns.parent, ns.cname, ns.full_path)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Phase 2: Eagerly trigger explicit namespaces so children can attach
|
|
61
|
+
namespaces.each { |ns| box.eval(ns.full_path) if ns.file }
|
|
62
|
+
|
|
63
|
+
# Phase 3: Register file autoloads
|
|
64
|
+
files.each { |f| register_autoload(box, f.parent, f.cname, f.file) }
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Scans files in a collapsed directory, computing the parent namespace
|
|
68
|
+
# from the path between root_dir and the collapsed dir. E.g. if root_dir
|
|
69
|
+
# is lib/ and dir is lib/analytics/formatters/, files map to Analytics::*.
|
|
70
|
+
def self.scan_files_only(dir, root_dir: nil)
|
|
71
|
+
inflector = Zeitwerk::Inflector.new
|
|
72
|
+
entries = []
|
|
73
|
+
|
|
74
|
+
parent_cnames =
|
|
75
|
+
if root_dir && dir.start_with?("#{root_dir}/")
|
|
76
|
+
rel = dir.delete_prefix("#{root_dir}/")
|
|
77
|
+
rel.split('/')[0...-1].map { |p| inflector.camelize(p, root_dir) }
|
|
78
|
+
else
|
|
79
|
+
[]
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
Dir
|
|
83
|
+
.glob(File.join(dir, '**', '*.rb'))
|
|
84
|
+
.sort
|
|
85
|
+
.each do |abspath|
|
|
86
|
+
relative = abspath.delete_prefix("#{dir}/").delete_suffix('.rb')
|
|
87
|
+
parts = relative.split('/')
|
|
88
|
+
file_cnames = parts.map { |part| inflector.camelize(part, dir) }
|
|
89
|
+
all_cnames = parent_cnames + file_cnames
|
|
90
|
+
full_path = all_cnames.join('::')
|
|
91
|
+
cname = all_cnames.last
|
|
92
|
+
parent = all_cnames[0...-1].join('::')
|
|
93
|
+
|
|
94
|
+
entries << Entry.new(
|
|
95
|
+
type: :file,
|
|
96
|
+
cname: cname,
|
|
97
|
+
full_path: full_path,
|
|
98
|
+
file: abspath,
|
|
99
|
+
parent: parent,
|
|
100
|
+
dir: nil,
|
|
101
|
+
)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
entries
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Builds a file index (const_name → file_path) from scan entries.
|
|
108
|
+
# Used by ConstantResolver for dependency wiring.
|
|
109
|
+
def self.build_file_index(entries)
|
|
110
|
+
index = {}
|
|
111
|
+
entries.each { |e| index[e.full_path] = e.file if e.file }
|
|
112
|
+
index
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
class << self
|
|
116
|
+
private
|
|
117
|
+
|
|
118
|
+
def scan_dir(fs, inflector, dir, parent_path, entries)
|
|
119
|
+
fs.ls(dir) do |basename, abspath, ftype|
|
|
120
|
+
if ftype == :file
|
|
121
|
+
# Skip files that have a matching directory (explicit namespaces).
|
|
122
|
+
# These are already handled as namespace entries with their .rb file.
|
|
123
|
+
next if File.directory?(abspath.delete_suffix('.rb'))
|
|
124
|
+
|
|
125
|
+
cname = inflector.camelize(basename.delete_suffix('.rb'), dir)
|
|
126
|
+
full_path = parent_path.empty? ? cname : "#{parent_path}::#{cname}"
|
|
127
|
+
entries << Entry.new(
|
|
128
|
+
type: :file,
|
|
129
|
+
cname: cname,
|
|
130
|
+
full_path: full_path,
|
|
131
|
+
file: abspath,
|
|
132
|
+
parent: parent_path,
|
|
133
|
+
dir: nil,
|
|
134
|
+
)
|
|
135
|
+
elsif ftype == :directory
|
|
136
|
+
cname = inflector.camelize(basename, dir)
|
|
137
|
+
full_path = parent_path.empty? ? cname : "#{parent_path}::#{cname}"
|
|
138
|
+
rb_file = "#{abspath}.rb"
|
|
139
|
+
has_rb = File.exist?(rb_file)
|
|
140
|
+
entries << Entry.new(
|
|
141
|
+
type: :namespace,
|
|
142
|
+
cname: cname,
|
|
143
|
+
full_path: full_path,
|
|
144
|
+
file: has_rb ? rb_file : nil,
|
|
145
|
+
parent: parent_path,
|
|
146
|
+
dir: abspath,
|
|
147
|
+
)
|
|
148
|
+
scan_dir(fs, inflector, abspath, full_path, entries)
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def register_autoload(box, parent, cname, file)
|
|
154
|
+
if parent.empty?
|
|
155
|
+
box.eval("autoload :#{cname}, #{file.inspect}")
|
|
156
|
+
else
|
|
157
|
+
box.eval("#{parent}.autoload(:#{cname}, #{file.inspect})")
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def define_implicit_module(box, parent, cname, full_path)
|
|
162
|
+
if parent.empty?
|
|
163
|
+
box.eval("#{cname} = Module.new unless defined?(#{cname})")
|
|
164
|
+
else
|
|
165
|
+
box.eval(
|
|
166
|
+
"#{parent}.const_set(:#{cname}, Module.new) unless defined?(#{full_path})",
|
|
167
|
+
)
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
end
|
data/lib/boxwerk.rb
CHANGED
|
@@ -1,14 +1,36 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative 'boxwerk/autoloader_mixin'
|
|
4
|
+
require_relative 'boxwerk/box_manager'
|
|
3
5
|
require_relative 'boxwerk/cli'
|
|
4
|
-
require_relative 'boxwerk/
|
|
5
|
-
require_relative 'boxwerk/
|
|
6
|
+
require_relative 'boxwerk/constant_resolver'
|
|
7
|
+
require_relative 'boxwerk/gem_resolver'
|
|
8
|
+
require_relative 'boxwerk/gemfile_require_parser'
|
|
9
|
+
require_relative 'boxwerk/global_context'
|
|
6
10
|
require_relative 'boxwerk/package'
|
|
7
|
-
require_relative 'boxwerk/
|
|
11
|
+
require_relative 'boxwerk/package_context'
|
|
12
|
+
require_relative 'boxwerk/package_resolver'
|
|
13
|
+
require_relative 'boxwerk/privacy_checker'
|
|
8
14
|
require_relative 'boxwerk/setup'
|
|
9
15
|
require_relative 'boxwerk/version'
|
|
16
|
+
require_relative 'boxwerk/zeitwerk_scanner'
|
|
10
17
|
|
|
18
|
+
# Boxwerk is a package isolation system for Ruby applications built on
|
|
19
|
+
# Ruby::Box. It loads each package in its own +Ruby::Box+, enforcing
|
|
20
|
+
# dependency and privacy boundaries declared in +package.yml+ files.
|
|
11
21
|
module Boxwerk
|
|
12
|
-
class
|
|
22
|
+
class << self
|
|
23
|
+
# Returns the {PackageContext} for the currently executing package.
|
|
24
|
+
# Returns +nil+ in the root box.
|
|
25
|
+
# @return [PackageContext, nil]
|
|
26
|
+
def package
|
|
27
|
+
nil
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Returns the {GlobalContext}.
|
|
31
|
+
# @return [GlobalContext]
|
|
32
|
+
def global
|
|
33
|
+
Ruby::Box.root.const_get(:BOXWERK_GLOBAL)
|
|
34
|
+
end
|
|
13
35
|
end
|
|
14
36
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: boxwerk
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- David Cristofaro
|
|
@@ -9,24 +9,54 @@ bindir: exe
|
|
|
9
9
|
cert_chain: []
|
|
10
10
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: bundler
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '4.0'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '4.0'
|
|
12
26
|
- !ruby/object:Gem::Dependency
|
|
13
27
|
name: irb
|
|
14
28
|
requirement: !ruby/object:Gem::Requirement
|
|
15
29
|
requirements:
|
|
16
30
|
- - "~>"
|
|
17
31
|
- !ruby/object:Gem::Version
|
|
18
|
-
version: '1.
|
|
32
|
+
version: '1.17'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '1.17'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: zeitwerk
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - "~>"
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '2.7'
|
|
19
47
|
type: :runtime
|
|
20
48
|
prerelease: false
|
|
21
49
|
version_requirements: !ruby/object:Gem::Requirement
|
|
22
50
|
requirements:
|
|
23
51
|
- - "~>"
|
|
24
52
|
- !ruby/object:Gem::Version
|
|
25
|
-
version: '
|
|
26
|
-
description: Boxwerk
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
53
|
+
version: '2.7'
|
|
54
|
+
description: Boxwerk is a tool for creating modular Ruby and Rails applications. It
|
|
55
|
+
organizes code into packages with clear boundaries and explicit dependencies, enforcing
|
|
56
|
+
them at runtime using Ruby::Box constant isolation. It reads standard Packwerk package.yml
|
|
57
|
+
files (without requiring Packwerk), providing per-package gem isolation, Zeitwerk-based
|
|
58
|
+
autoloading, monkey patch isolation between packages, and a CLI for running, testing,
|
|
59
|
+
and inspecting your modular application.
|
|
30
60
|
email:
|
|
31
61
|
- david@dtcristo.com
|
|
32
62
|
executables:
|
|
@@ -34,31 +64,30 @@ executables:
|
|
|
34
64
|
extensions: []
|
|
35
65
|
extra_rdoc_files: []
|
|
36
66
|
files:
|
|
67
|
+
- AGENTS.md
|
|
68
|
+
- ARCHITECTURE.md
|
|
37
69
|
- CHANGELOG.md
|
|
38
70
|
- LICENSE.txt
|
|
39
71
|
- README.md
|
|
40
72
|
- Rakefile
|
|
41
|
-
-
|
|
42
|
-
-
|
|
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
|
|
73
|
+
- TODO.md
|
|
74
|
+
- USAGE.md
|
|
52
75
|
- exe/boxwerk
|
|
53
76
|
- lib/boxwerk.rb
|
|
77
|
+
- lib/boxwerk/autoloader_mixin.rb
|
|
78
|
+
- lib/boxwerk/box_manager.rb
|
|
54
79
|
- lib/boxwerk/cli.rb
|
|
55
|
-
- lib/boxwerk/
|
|
56
|
-
- lib/boxwerk/
|
|
80
|
+
- lib/boxwerk/constant_resolver.rb
|
|
81
|
+
- lib/boxwerk/gem_resolver.rb
|
|
82
|
+
- lib/boxwerk/gemfile_require_parser.rb
|
|
83
|
+
- lib/boxwerk/global_context.rb
|
|
57
84
|
- lib/boxwerk/package.rb
|
|
58
|
-
- lib/boxwerk/
|
|
85
|
+
- lib/boxwerk/package_context.rb
|
|
86
|
+
- lib/boxwerk/package_resolver.rb
|
|
87
|
+
- lib/boxwerk/privacy_checker.rb
|
|
59
88
|
- lib/boxwerk/setup.rb
|
|
60
89
|
- lib/boxwerk/version.rb
|
|
61
|
-
-
|
|
90
|
+
- lib/boxwerk/zeitwerk_scanner.rb
|
|
62
91
|
homepage: https://github.com/dtcristo/boxwerk
|
|
63
92
|
licenses:
|
|
64
93
|
- MIT
|
|
@@ -67,6 +96,7 @@ metadata:
|
|
|
67
96
|
homepage_uri: https://github.com/dtcristo/boxwerk
|
|
68
97
|
source_code_uri: https://github.com/dtcristo/boxwerk
|
|
69
98
|
changelog_uri: https://github.com/dtcristo/boxwerk/blob/main/CHANGELOG.md
|
|
99
|
+
documentation_uri: https://dtcristo.github.io/boxwerk/
|
|
70
100
|
rdoc_options: []
|
|
71
101
|
require_paths:
|
|
72
102
|
- lib
|
|
@@ -74,7 +104,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
74
104
|
requirements:
|
|
75
105
|
- - ">="
|
|
76
106
|
- !ruby/object:Gem::Version
|
|
77
|
-
version: 4.0
|
|
107
|
+
version: '4.0'
|
|
78
108
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
79
109
|
requirements:
|
|
80
110
|
- - ">="
|
|
@@ -83,5 +113,5 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
83
113
|
requirements: []
|
|
84
114
|
rubygems_version: 4.0.3
|
|
85
115
|
specification_version: 4
|
|
86
|
-
summary: Ruby package system with Box-powered
|
|
116
|
+
summary: Ruby package system with Box-powered boundary enforcement
|
|
87
117
|
test_files: []
|
data/example/Gemfile
DELETED
data/example/Gemfile.lock
DELETED
|
@@ -1,66 +0,0 @@
|
|
|
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
|