ruby_workspace_manager 0.2.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/LICENSE.txt +21 -0
- data/README.md +32 -0
- data/bin/rwm +6 -0
- data/lib/rwm/affected_detector.rb +102 -0
- data/lib/rwm/cli.rb +98 -0
- data/lib/rwm/commands/affected.rb +52 -0
- data/lib/rwm/commands/bootstrap.rb +147 -0
- data/lib/rwm/commands/check.rb +27 -0
- data/lib/rwm/commands/graph.rb +46 -0
- data/lib/rwm/commands/info.rb +41 -0
- data/lib/rwm/commands/init.rb +113 -0
- data/lib/rwm/commands/list.rb +45 -0
- data/lib/rwm/commands/new.rb +150 -0
- data/lib/rwm/commands/run.rb +132 -0
- data/lib/rwm/convention_checker.rb +70 -0
- data/lib/rwm/dependency_graph.rb +226 -0
- data/lib/rwm/errors.rb +43 -0
- data/lib/rwm/gemfile.rb +32 -0
- data/lib/rwm/gemfile_parser.rb +48 -0
- data/lib/rwm/git_hooks.rb +55 -0
- data/lib/rwm/overcommit.rb +99 -0
- data/lib/rwm/package.rb +62 -0
- data/lib/rwm/rake.rb +51 -0
- data/lib/rwm/task_cache.rb +114 -0
- data/lib/rwm/task_runner.rb +148 -0
- data/lib/rwm/version.rb +5 -0
- data/lib/rwm/vscode_workspace.rb +57 -0
- data/lib/rwm/workspace.rb +73 -0
- data/lib/rwm.rb +19 -0
- metadata +72 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 6e8c287aa2d28cf7ed4c21f9ce567eb25f5a2164e0d064619aa2110676461ae0
|
|
4
|
+
data.tar.gz: b2d3ca1d17eecdf4b9233469bdaf24c4f43a9d7ec3cb6bd99fdb9fb2a527b419
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 9b1c5e6dac68401d8ec61e70323af0bea421370354a5bfa36d66917bcd08222026b9ac535e800c1aa5f8405ffde6759381c53fecea629972f799e42e1176f4b2
|
|
7
|
+
data.tar.gz: a535d8642e4c5c3736a4606954e4a21a2d32e8926293d89ca2622fc3c8c0ced4b3667e0d5375b51c5b23cece660d76b1aa9e062cf270d73c001977b2e4a5ab7d
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Siddharth Bhatt
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
[](https://github.com/sidbhatt11/ruby-workspace-manager/actions/workflows/ci.yml)
|
|
2
|
+

|
|
3
|
+
|
|
4
|
+
# RWM — Ruby Workspace Manager
|
|
5
|
+
|
|
6
|
+
An Nx-like monorepo tool for Ruby. Convention-over-configuration, zero runtime dependencies, delegates to Rake.
|
|
7
|
+
|
|
8
|
+
## What it does
|
|
9
|
+
|
|
10
|
+
RWM manages Ruby monorepos with multiple apps and libraries. It builds a dependency graph from your Gemfiles, enforces structural conventions, runs tasks in parallel respecting dependency order, detects affected packages from git changes, and caches results so unchanged work is never repeated.
|
|
11
|
+
|
|
12
|
+
## Commands
|
|
13
|
+
|
|
14
|
+
| Command | Description |
|
|
15
|
+
|---------|-------------|
|
|
16
|
+
| `rwm init` | Initialize a workspace. |
|
|
17
|
+
| `rwm bootstrap` | Install deps, build graph, install hooks, run bootstrap tasks. |
|
|
18
|
+
| `rwm new <app\|lib> <name>` | Scaffold a new package. |
|
|
19
|
+
| `rwm graph` | Rebuild the dependency graph. `--dot` / `--mermaid` for visualization. |
|
|
20
|
+
| `rwm run <task> [pkg]` | Run a Rake task across packages. Packages without the task are skipped. |
|
|
21
|
+
| `rwm test` | Shortcut for `rwm run test`. Also: `rwm spec`, `rwm build`. |
|
|
22
|
+
| `rwm run <task> --affected` | Run only on packages affected by current changes. |
|
|
23
|
+
| `rwm check` | Validate conventions. |
|
|
24
|
+
| `rwm list` | List all packages. |
|
|
25
|
+
| `rwm info <name>` | Show package details. |
|
|
26
|
+
| `rwm affected` | Show affected packages. |
|
|
27
|
+
|
|
28
|
+
See [GUIDE.md](GUIDE.md) for full usage documentation — dependencies, caching, affected detection, git hooks, design decisions, and more.
|
|
29
|
+
|
|
30
|
+
## License
|
|
31
|
+
|
|
32
|
+
MIT
|
data/bin/rwm
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rwm
|
|
4
|
+
class AffectedDetector
|
|
5
|
+
attr_reader :workspace, :graph, :base_branch
|
|
6
|
+
|
|
7
|
+
def initialize(workspace, graph, committed_only: false)
|
|
8
|
+
@workspace = workspace
|
|
9
|
+
@graph = graph
|
|
10
|
+
@committed_only = committed_only
|
|
11
|
+
@base_branch = detect_base_branch
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Returns packages directly changed + their transitive dependents
|
|
15
|
+
def affected_packages
|
|
16
|
+
changed_files = detect_changed_files
|
|
17
|
+
directly_changed = map_files_to_packages(changed_files)
|
|
18
|
+
|
|
19
|
+
# If root-level files changed (outside any package), all packages are affected
|
|
20
|
+
root_files = changed_files.reject { |f| file_in_any_package?(f) }
|
|
21
|
+
unless root_files.empty?
|
|
22
|
+
return workspace.packages
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Collect transitive dependents of directly changed packages
|
|
26
|
+
all_affected = Set.new(directly_changed.map(&:name))
|
|
27
|
+
directly_changed.each do |pkg|
|
|
28
|
+
graph.transitive_dependents(pkg.name).each { |name| all_affected << name }
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
workspace.packages.select { |pkg| all_affected.include?(pkg.name) }
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Just the directly changed packages (no dependents)
|
|
35
|
+
def directly_changed_packages
|
|
36
|
+
changed_files = detect_changed_files
|
|
37
|
+
map_files_to_packages(changed_files)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
def detect_base_branch
|
|
43
|
+
# Try to read the remote's default branch
|
|
44
|
+
ref = `git -C #{workspace.root} symbolic-ref refs/remotes/origin/HEAD 2>/dev/null`.chomp
|
|
45
|
+
unless ref.empty?
|
|
46
|
+
# refs/remotes/origin/main → main
|
|
47
|
+
return ref.sub(%r{^refs/remotes/origin/}, "")
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Fall back: check if main or master exists
|
|
51
|
+
branches = `git -C #{workspace.root} branch --list main master 2>/dev/null`.lines.map(&:strip)
|
|
52
|
+
return "main" if branches.include?("main")
|
|
53
|
+
return "master" if branches.include?("master")
|
|
54
|
+
|
|
55
|
+
# Last resort
|
|
56
|
+
"main"
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def detect_changed_files
|
|
60
|
+
files = Set.new
|
|
61
|
+
|
|
62
|
+
# 1. Committed changes: base branch vs HEAD
|
|
63
|
+
committed = `git -C #{workspace.root} diff --name-only #{base_branch}...HEAD 2>/dev/null`.chomp
|
|
64
|
+
committed.lines.each { |l| files << l.chomp }
|
|
65
|
+
|
|
66
|
+
unless @committed_only
|
|
67
|
+
# 2. Staged changes (not yet committed)
|
|
68
|
+
staged = `git -C #{workspace.root} diff --name-only --cached 2>/dev/null`.chomp
|
|
69
|
+
staged.lines.each { |l| files << l.chomp }
|
|
70
|
+
|
|
71
|
+
# 3. Unstaged working directory changes
|
|
72
|
+
unstaged = `git -C #{workspace.root} diff --name-only 2>/dev/null`.chomp
|
|
73
|
+
unstaged.lines.each { |l| files << l.chomp }
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
files.reject(&:empty?).to_a
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def map_files_to_packages(files)
|
|
80
|
+
packages = workspace.packages
|
|
81
|
+
matched = Set.new
|
|
82
|
+
|
|
83
|
+
files.each do |file|
|
|
84
|
+
packages.each do |pkg|
|
|
85
|
+
rel_path = pkg.relative_path(workspace.root)
|
|
86
|
+
if file.start_with?("#{rel_path}/")
|
|
87
|
+
matched << pkg
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
matched.to_a
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def file_in_any_package?(file)
|
|
96
|
+
workspace.packages.any? do |pkg|
|
|
97
|
+
rel_path = pkg.relative_path(workspace.root)
|
|
98
|
+
file.start_with?("#{rel_path}/")
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
data/lib/rwm/cli.rb
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "optparse"
|
|
4
|
+
|
|
5
|
+
module Rwm
|
|
6
|
+
class CLI
|
|
7
|
+
COMMANDS = {
|
|
8
|
+
"init" => "Commands::Init",
|
|
9
|
+
"bootstrap" => "Commands::Bootstrap",
|
|
10
|
+
"new" => "Commands::New",
|
|
11
|
+
"info" => "Commands::Info",
|
|
12
|
+
"graph" => "Commands::Graph",
|
|
13
|
+
"check" => "Commands::Check",
|
|
14
|
+
"list" => "Commands::List",
|
|
15
|
+
"run" => "Commands::Run",
|
|
16
|
+
"affected" => "Commands::Affected"
|
|
17
|
+
}.freeze
|
|
18
|
+
|
|
19
|
+
# Shortcuts that expand to `run <task>`
|
|
20
|
+
TASK_SHORTCUTS = %w[test spec build].freeze
|
|
21
|
+
|
|
22
|
+
def self.run(argv)
|
|
23
|
+
new(argv).run
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def initialize(argv)
|
|
27
|
+
@argv = argv.dup
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def run
|
|
31
|
+
command_name = @argv.shift
|
|
32
|
+
|
|
33
|
+
if command_name.nil? || %w[-h --help help].include?(command_name)
|
|
34
|
+
print_help
|
|
35
|
+
return 0
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
if %w[-v --version version].include?(command_name)
|
|
39
|
+
puts "rwm #{Rwm::VERSION}"
|
|
40
|
+
return 0
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Expand task shortcuts: `rwm test` → `rwm run test`
|
|
44
|
+
if TASK_SHORTCUTS.include?(command_name)
|
|
45
|
+
@argv.unshift(command_name)
|
|
46
|
+
command_name = "run"
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
const_name = COMMANDS[command_name]
|
|
50
|
+
unless const_name
|
|
51
|
+
$stderr.puts "Unknown command: #{command_name}"
|
|
52
|
+
$stderr.puts "Run `rwm help` for available commands."
|
|
53
|
+
return 1
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Autoload the command
|
|
57
|
+
require "rwm/commands/#{command_name}"
|
|
58
|
+
command_class = const_name.split("::").reduce(Rwm) { |mod, name| mod.const_get(name) }
|
|
59
|
+
command_class.new(@argv).run
|
|
60
|
+
rescue Rwm::Error => e
|
|
61
|
+
$stderr.puts "Error: #{e.message}"
|
|
62
|
+
1
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
private
|
|
66
|
+
|
|
67
|
+
def print_help
|
|
68
|
+
puts <<~HELP
|
|
69
|
+
rwm #{Rwm::VERSION} — Ruby Workspace Manager
|
|
70
|
+
|
|
71
|
+
Usage: rwm <command> [options]
|
|
72
|
+
|
|
73
|
+
Commands:
|
|
74
|
+
init Initialize a new rwm workspace
|
|
75
|
+
bootstrap Install deps and run bootstrap tasks in all packages
|
|
76
|
+
new <type> <name> Scaffold a new app or lib
|
|
77
|
+
info <name> Show details about a package
|
|
78
|
+
graph Build and save the dependency graph
|
|
79
|
+
--dot Output in Graphviz DOT format
|
|
80
|
+
--mermaid Output in Mermaid format
|
|
81
|
+
check Validate dependency graph and conventions
|
|
82
|
+
run <task> [pkg] Run a rake task across all (or one) package(s)
|
|
83
|
+
test Shortcut for `rwm run test`
|
|
84
|
+
affected Show packages affected by current changes
|
|
85
|
+
list List all packages in the workspace
|
|
86
|
+
help Show this help
|
|
87
|
+
|
|
88
|
+
Run options:
|
|
89
|
+
--affected Only run on affected packages
|
|
90
|
+
--no-cache Bypass task-level caching
|
|
91
|
+
|
|
92
|
+
Options:
|
|
93
|
+
-h, --help Show this help
|
|
94
|
+
-v, --version Show version
|
|
95
|
+
HELP
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "optparse"
|
|
4
|
+
|
|
5
|
+
module Rwm
|
|
6
|
+
module Commands
|
|
7
|
+
class Affected
|
|
8
|
+
def initialize(argv)
|
|
9
|
+
@argv = argv
|
|
10
|
+
@committed_only = false
|
|
11
|
+
parse_options
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def run
|
|
15
|
+
workspace = Workspace.find
|
|
16
|
+
graph = DependencyGraph.load(workspace)
|
|
17
|
+
detector = AffectedDetector.new(workspace, graph, committed_only: @committed_only)
|
|
18
|
+
|
|
19
|
+
affected = detector.affected_packages
|
|
20
|
+
directly_changed = detector.directly_changed_packages
|
|
21
|
+
|
|
22
|
+
if affected.empty?
|
|
23
|
+
puts "No packages affected."
|
|
24
|
+
return 0
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
puts "Base branch: #{detector.base_branch}"
|
|
28
|
+
puts "Affected packages (#{affected.size}):"
|
|
29
|
+
puts
|
|
30
|
+
|
|
31
|
+
affected.each do |pkg|
|
|
32
|
+
marker = directly_changed.include?(pkg) ? "(changed)" : "(dependent)"
|
|
33
|
+
puts " #{pkg.name} #{marker}"
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
0
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
private
|
|
40
|
+
|
|
41
|
+
def parse_options
|
|
42
|
+
parser = OptionParser.new do |opts|
|
|
43
|
+
opts.on("--committed", "Only consider committed changes (ignore staged/unstaged)") do
|
|
44
|
+
@committed_only = true
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
parser.order!(@argv)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rwm
|
|
4
|
+
module Commands
|
|
5
|
+
class Bootstrap
|
|
6
|
+
def initialize(argv)
|
|
7
|
+
@argv = argv
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def run
|
|
11
|
+
workspace = Workspace.find
|
|
12
|
+
|
|
13
|
+
bootstrap_root(workspace)
|
|
14
|
+
setup_hooks(workspace)
|
|
15
|
+
|
|
16
|
+
graph = DependencyGraph.build(workspace)
|
|
17
|
+
bootstrap_packages(workspace, graph)
|
|
18
|
+
save_graph(workspace, graph)
|
|
19
|
+
validate_conventions(workspace, graph)
|
|
20
|
+
update_vscode_workspace(workspace)
|
|
21
|
+
|
|
22
|
+
puts
|
|
23
|
+
puts "Bootstrap complete!"
|
|
24
|
+
0
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
def bootstrap_root(workspace)
|
|
30
|
+
puts "==> Bootstrapping workspace root..."
|
|
31
|
+
run_bundle_install(workspace.root)
|
|
32
|
+
run_rake_bootstrap(workspace.root)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def setup_hooks(workspace)
|
|
36
|
+
if File.exist?(File.join(workspace.root, ".overcommit.yml"))
|
|
37
|
+
puts "==> Setting up overcommit..."
|
|
38
|
+
|
|
39
|
+
overcommit = Overcommit.new(workspace.root)
|
|
40
|
+
if overcommit.setup
|
|
41
|
+
puts " Overcommit configured."
|
|
42
|
+
else
|
|
43
|
+
$stderr.puts " Warning: Could not install overcommit hooks."
|
|
44
|
+
$stderr.puts " Run `overcommit --install` manually after installing the overcommit gem."
|
|
45
|
+
puts " Hook scripts and config created (will activate once overcommit is installed)."
|
|
46
|
+
end
|
|
47
|
+
else
|
|
48
|
+
puts "==> Installing git hooks..."
|
|
49
|
+
hooks = GitHooks.new(workspace.root)
|
|
50
|
+
if hooks.setup
|
|
51
|
+
puts " Git hooks installed."
|
|
52
|
+
else
|
|
53
|
+
$stderr.puts " Warning: Could not install git hooks (no .git directory found)."
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def bootstrap_packages(workspace, graph)
|
|
59
|
+
packages = workspace.packages
|
|
60
|
+
if packages.empty?
|
|
61
|
+
puts "==> No packages found. Skipping package bootstrap."
|
|
62
|
+
return
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Step 1: bundle install in all packages (parallel by execution level)
|
|
66
|
+
puts "==> Installing gems in #{packages.size} package(s)..."
|
|
67
|
+
install_runner = TaskRunner.new(graph, packages: packages)
|
|
68
|
+
install_runner.run_command do |pkg|
|
|
69
|
+
["bundle", "install"]
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
unless install_runner.success?
|
|
73
|
+
failed = install_runner.failed_results
|
|
74
|
+
raise BootstrapError, "bundle install failed in: #{failed.map(&:package_name).join(", ")}"
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Step 2: rake bootstrap in all packages (parallel by execution level)
|
|
78
|
+
bootstrappable = packages.select(&:has_rakefile?)
|
|
79
|
+
unless bootstrappable.empty?
|
|
80
|
+
puts
|
|
81
|
+
puts "==> Running bootstrap tasks in #{bootstrappable.size} package(s)..."
|
|
82
|
+
bootstrap_runner = TaskRunner.new(graph, packages: bootstrappable)
|
|
83
|
+
bootstrap_runner.run_command do |pkg|
|
|
84
|
+
["bundle", "exec", "rake", "bootstrap"]
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
unless bootstrap_runner.success?
|
|
88
|
+
failed = bootstrap_runner.failed_results
|
|
89
|
+
raise BootstrapError, "rake bootstrap failed in: #{failed.map(&:package_name).join(", ")}"
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def save_graph(workspace, graph)
|
|
95
|
+
puts
|
|
96
|
+
puts "==> Saving dependency graph..."
|
|
97
|
+
graph.save(workspace.graph_path, workspace.root)
|
|
98
|
+
puts " Graph saved to .rwm/graph.json (#{graph.packages.size} packages, #{graph.edges.values.flatten.size} edges)"
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def validate_conventions(_workspace, graph)
|
|
102
|
+
puts "==> Validating conventions..."
|
|
103
|
+
checker = ConventionChecker.new(graph)
|
|
104
|
+
violations = checker.check
|
|
105
|
+
|
|
106
|
+
if violations.empty?
|
|
107
|
+
puts " All conventions passed."
|
|
108
|
+
else
|
|
109
|
+
$stderr.puts "Convention violations found:"
|
|
110
|
+
violations.each { |v| $stderr.puts " - #{v}" }
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def update_vscode_workspace(workspace)
|
|
115
|
+
vscode = VscodeWorkspace.new(workspace.root)
|
|
116
|
+
return unless File.exist?(vscode.file_path)
|
|
117
|
+
|
|
118
|
+
vscode.generate(workspace.packages)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def run_bundle_install(dir)
|
|
122
|
+
return unless File.exist?(File.join(dir, "Gemfile"))
|
|
123
|
+
|
|
124
|
+
puts " bundle install..."
|
|
125
|
+
success = system("bundle", "install", chdir: dir)
|
|
126
|
+
unless success
|
|
127
|
+
raise BootstrapError, "bundle install failed in #{dir}"
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def run_rake_bootstrap(dir)
|
|
132
|
+
return unless File.exist?(File.join(dir, "Rakefile"))
|
|
133
|
+
|
|
134
|
+
# Check if bootstrap task exists before running it
|
|
135
|
+
has_task = system("bundle", "exec", "rake", "-s", "-T", "bootstrap",
|
|
136
|
+
chdir: dir, out: File::NULL, err: File::NULL)
|
|
137
|
+
return unless has_task
|
|
138
|
+
|
|
139
|
+
puts " rake bootstrap..."
|
|
140
|
+
success = system("bundle", "exec", "rake", "bootstrap", chdir: dir)
|
|
141
|
+
unless success
|
|
142
|
+
raise BootstrapError, "rake bootstrap failed in #{dir}"
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rwm
|
|
4
|
+
module Commands
|
|
5
|
+
class Check
|
|
6
|
+
def initialize(argv)
|
|
7
|
+
@argv = argv
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def run
|
|
11
|
+
workspace = Workspace.find
|
|
12
|
+
graph = DependencyGraph.load(workspace)
|
|
13
|
+
checker = ConventionChecker.new(graph)
|
|
14
|
+
violations = checker.check
|
|
15
|
+
|
|
16
|
+
if violations.empty?
|
|
17
|
+
puts " All conventions passed."
|
|
18
|
+
0
|
|
19
|
+
else
|
|
20
|
+
$stderr.puts "Convention violations found:"
|
|
21
|
+
violations.each { |v| $stderr.puts " - #{v}" }
|
|
22
|
+
1
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "optparse"
|
|
4
|
+
|
|
5
|
+
module Rwm
|
|
6
|
+
module Commands
|
|
7
|
+
class Graph
|
|
8
|
+
def initialize(argv)
|
|
9
|
+
@argv = argv
|
|
10
|
+
@format = nil
|
|
11
|
+
parse_options
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def run
|
|
15
|
+
workspace = Workspace.find
|
|
16
|
+
graph = DependencyGraph.build(workspace)
|
|
17
|
+
graph.save(workspace.graph_path, workspace.root)
|
|
18
|
+
|
|
19
|
+
puts " Graph saved to .rwm/graph.json (#{graph.packages.size} packages, #{graph.edges.values.flatten.size} edges)"
|
|
20
|
+
|
|
21
|
+
case @format
|
|
22
|
+
when :dot
|
|
23
|
+
puts graph.to_dot(workspace.root)
|
|
24
|
+
when :mermaid
|
|
25
|
+
puts graph.to_mermaid(workspace.root)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
0
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
def parse_options
|
|
34
|
+
OptionParser.new do |opts|
|
|
35
|
+
opts.on("--dot", "Output graph in Graphviz DOT format") do
|
|
36
|
+
@format = :dot
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
opts.on("--mermaid", "Output graph in Mermaid format") do
|
|
40
|
+
@format = :mermaid
|
|
41
|
+
end
|
|
42
|
+
end.parse!(@argv)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rwm
|
|
4
|
+
module Commands
|
|
5
|
+
class Info
|
|
6
|
+
def initialize(argv)
|
|
7
|
+
@argv = argv
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def run
|
|
11
|
+
name = @argv.shift
|
|
12
|
+
|
|
13
|
+
unless name
|
|
14
|
+
$stderr.puts "Usage: rwm info <package_name>"
|
|
15
|
+
return 1
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
workspace = Workspace.find
|
|
19
|
+
pkg = workspace.find_package(name)
|
|
20
|
+
graph = DependencyGraph.load(workspace)
|
|
21
|
+
|
|
22
|
+
deps = graph.dependencies(name)
|
|
23
|
+
dependents = graph.direct_dependents(name)
|
|
24
|
+
transitive = graph.transitive_dependents(name)
|
|
25
|
+
|
|
26
|
+
puts "Package: #{pkg.name}"
|
|
27
|
+
puts "Type: #{pkg.type}"
|
|
28
|
+
puts "Path: #{pkg.relative_path(workspace.root)}"
|
|
29
|
+
puts "Has Rakefile: #{pkg.has_rakefile? ? "yes" : "no"}"
|
|
30
|
+
puts "Dependencies: #{deps.empty? ? "(none)" : deps.sort.join(", ")}"
|
|
31
|
+
puts "Dependents: #{dependents.empty? ? "(none)" : dependents.sort.join(", ")}"
|
|
32
|
+
|
|
33
|
+
if transitive.size > dependents.size
|
|
34
|
+
puts "Transitive: #{transitive.sort.join(", ")}"
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
0
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|