cobra_commander 0.5.1 → 0.8.1
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/.editorconfig +11 -0
- data/.github/workflows/ci.yml +38 -0
- data/.travis.yml +3 -2
- data/CHANGELOG.md +25 -0
- data/README.md +110 -11
- data/cobra_commander.gemspec +4 -1
- data/exe/cobra +1 -1
- data/lib/cobra_commander.rb +15 -8
- data/lib/cobra_commander/affected.rb +43 -28
- data/lib/cobra_commander/change.rb +6 -7
- data/lib/cobra_commander/cli.rb +70 -39
- data/lib/cobra_commander/component.rb +52 -0
- data/lib/cobra_commander/dependencies.rb +4 -0
- data/lib/cobra_commander/dependencies/bundler.rb +38 -0
- data/lib/cobra_commander/dependencies/yarn/package.rb +37 -0
- data/lib/cobra_commander/dependencies/yarn/package_repo.rb +31 -0
- data/lib/cobra_commander/dependencies/yarn_workspace.rb +55 -0
- data/lib/cobra_commander/executor.rb +10 -22
- data/lib/cobra_commander/output.rb +3 -72
- data/lib/cobra_commander/output/ascii_tree.rb +55 -0
- data/lib/cobra_commander/output/flat_list.rb +16 -0
- data/lib/cobra_commander/output/graph_viz.rb +27 -0
- data/lib/cobra_commander/umbrella.rb +51 -0
- data/lib/cobra_commander/version.rb +1 -1
- data/renovate.json +8 -0
- metadata +33 -6
- data/lib/cobra_commander/component_tree.rb +0 -170
- data/lib/cobra_commander/graph.rb +0 -46
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CobraCommander
|
4
|
+
module Output
|
5
|
+
# Prints the tree in a nice tree form
|
6
|
+
class AsciiTree
|
7
|
+
SPACE = " "
|
8
|
+
BAR = "│ "
|
9
|
+
TEE = "├── "
|
10
|
+
CORNER = "└── "
|
11
|
+
|
12
|
+
def initialize(component)
|
13
|
+
@component = component
|
14
|
+
end
|
15
|
+
|
16
|
+
def to_s
|
17
|
+
StringIO.new.tap do |io|
|
18
|
+
io.puts @component.name
|
19
|
+
list_dependencies(io, @component)
|
20
|
+
end.string
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def list_dependencies(io, component, outdents = [])
|
26
|
+
component.dependencies.each do |dep|
|
27
|
+
decide_on_line(io, component, dep, outdents)
|
28
|
+
end
|
29
|
+
nil
|
30
|
+
end
|
31
|
+
|
32
|
+
def decide_on_line(io, parent, dep, outdents)
|
33
|
+
if parent.dependencies.last != dep
|
34
|
+
add_tee(io, outdents, dep)
|
35
|
+
else
|
36
|
+
add_corner(io, outdents, dep)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def add_tee(io, outdents, dep)
|
41
|
+
io.puts line(outdents, TEE, dep.name)
|
42
|
+
list_dependencies(io, dep, (outdents + [BAR]))
|
43
|
+
end
|
44
|
+
|
45
|
+
def add_corner(io, outdents, dep)
|
46
|
+
io.puts line(outdents, CORNER, dep.name)
|
47
|
+
list_dependencies(io, dep, (outdents + [SPACE]))
|
48
|
+
end
|
49
|
+
|
50
|
+
def line(outdents, sym, name)
|
51
|
+
(outdents + [sym] + [name]).join
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CobraCommander
|
4
|
+
module Output
|
5
|
+
# Prints a list of components' names sorted alphabetically
|
6
|
+
class FlatList
|
7
|
+
def initialize(components)
|
8
|
+
@components = components
|
9
|
+
end
|
10
|
+
|
11
|
+
def to_s
|
12
|
+
@components.map(&:name).sort
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "graphviz"
|
4
|
+
|
5
|
+
module CobraCommander
|
6
|
+
module Output
|
7
|
+
# Generates graphs of components
|
8
|
+
module GraphViz
|
9
|
+
def self.generate(component, output)
|
10
|
+
g = ::GraphViz.new(:G, type: :digraph, concentrate: true)
|
11
|
+
([component] + component.deep_dependencies).each do |comp|
|
12
|
+
g.add_nodes comp.name
|
13
|
+
g.add_edges comp.name, comp.dependencies.map(&:name)
|
14
|
+
end
|
15
|
+
|
16
|
+
g.output(extract_format(output) => output)
|
17
|
+
end
|
18
|
+
|
19
|
+
private_class_method def self.extract_format(output)
|
20
|
+
format = output[-3..-1]
|
21
|
+
return format if format == "png" || format == "dot"
|
22
|
+
|
23
|
+
raise ArgumentError, "output format must be 'png' or 'dot'"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CobraCommander
|
4
|
+
# An umbrella application
|
5
|
+
class Umbrella
|
6
|
+
attr_reader :name, :path
|
7
|
+
|
8
|
+
def initialize(name, path)
|
9
|
+
@root_component = Component.new(self, name)
|
10
|
+
@path = path
|
11
|
+
@components = {}
|
12
|
+
end
|
13
|
+
|
14
|
+
def find(name)
|
15
|
+
@components[name]
|
16
|
+
end
|
17
|
+
|
18
|
+
def root
|
19
|
+
@root_component
|
20
|
+
end
|
21
|
+
|
22
|
+
def resolve(component_root_path)
|
23
|
+
return root if root.root_paths.include?(component_root_path)
|
24
|
+
components.find do |component|
|
25
|
+
component.root_paths.include?(component_root_path)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def add_source(key, source)
|
30
|
+
@root_component.add_source key, source.path, source.dependencies
|
31
|
+
source.components.each do |path:, name:, dependencies:|
|
32
|
+
@components[name] ||= Component.new(self, name)
|
33
|
+
@components[name].add_source key, path, dependencies
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def components
|
38
|
+
@components.values
|
39
|
+
end
|
40
|
+
|
41
|
+
def dependents_of(component)
|
42
|
+
find(component)&.deep_dependents
|
43
|
+
&.sort_by(&:name)
|
44
|
+
end
|
45
|
+
|
46
|
+
def dependencies_of(name)
|
47
|
+
find(name)&.deep_dependencies
|
48
|
+
&.sort_by(&:name)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
data/renovate.json
ADDED
metadata
CHANGED
@@ -1,16 +1,31 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cobra_commander
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.8.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ben Langfeld
|
8
8
|
- Garett Arrowood
|
9
|
+
- Carlos Palhares
|
9
10
|
autorequire:
|
10
11
|
bindir: exe
|
11
12
|
cert_chain: []
|
12
|
-
date:
|
13
|
+
date: 2020-07-29 00:00:00.000000000 Z
|
13
14
|
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: bundler
|
17
|
+
requirement: !ruby/object:Gem::Requirement
|
18
|
+
requirements:
|
19
|
+
- - "~>"
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '1.17'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
requirements:
|
26
|
+
- - "~>"
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
version: '1.17'
|
14
29
|
- !ruby/object:Gem::Dependency
|
15
30
|
name: thor
|
16
31
|
requirement: !ruby/object:Gem::Requirement
|
@@ -51,14 +66,14 @@ dependencies:
|
|
51
66
|
requirements:
|
52
67
|
- - "~>"
|
53
68
|
- !ruby/object:Gem::Version
|
54
|
-
version: '1.
|
69
|
+
version: '1.17'
|
55
70
|
type: :development
|
56
71
|
prerelease: false
|
57
72
|
version_requirements: !ruby/object:Gem::Requirement
|
58
73
|
requirements:
|
59
74
|
- - "~>"
|
60
75
|
- !ruby/object:Gem::Version
|
61
|
-
version: '1.
|
76
|
+
version: '1.17'
|
62
77
|
- !ruby/object:Gem::Dependency
|
63
78
|
name: rake
|
64
79
|
requirement: !ruby/object:Gem::Requirement
|
@@ -150,11 +165,14 @@ description: |
|
|
150
165
|
email:
|
151
166
|
- blangfeld@powerhrg.com
|
152
167
|
- garett.arrowood@powerhrg.com
|
168
|
+
- carlos.palhares@powerhrg.com
|
153
169
|
executables:
|
154
170
|
- cobra
|
155
171
|
extensions: []
|
156
172
|
extra_rdoc_files: []
|
157
173
|
files:
|
174
|
+
- ".editorconfig"
|
175
|
+
- ".github/workflows/ci.yml"
|
158
176
|
- ".gitignore"
|
159
177
|
- ".rspec"
|
160
178
|
- ".rubocop.yml"
|
@@ -175,11 +193,20 @@ files:
|
|
175
193
|
- lib/cobra_commander/affected.rb
|
176
194
|
- lib/cobra_commander/change.rb
|
177
195
|
- lib/cobra_commander/cli.rb
|
178
|
-
- lib/cobra_commander/
|
196
|
+
- lib/cobra_commander/component.rb
|
197
|
+
- lib/cobra_commander/dependencies.rb
|
198
|
+
- lib/cobra_commander/dependencies/bundler.rb
|
199
|
+
- lib/cobra_commander/dependencies/yarn/package.rb
|
200
|
+
- lib/cobra_commander/dependencies/yarn/package_repo.rb
|
201
|
+
- lib/cobra_commander/dependencies/yarn_workspace.rb
|
179
202
|
- lib/cobra_commander/executor.rb
|
180
|
-
- lib/cobra_commander/graph.rb
|
181
203
|
- lib/cobra_commander/output.rb
|
204
|
+
- lib/cobra_commander/output/ascii_tree.rb
|
205
|
+
- lib/cobra_commander/output/flat_list.rb
|
206
|
+
- lib/cobra_commander/output/graph_viz.rb
|
207
|
+
- lib/cobra_commander/umbrella.rb
|
182
208
|
- lib/cobra_commander/version.rb
|
209
|
+
- renovate.json
|
183
210
|
homepage: http://tech.powerhrg.com/cobra_commander/
|
184
211
|
licenses:
|
185
212
|
- MIT
|
@@ -1,170 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "bundler"
|
4
|
-
require "json"
|
5
|
-
|
6
|
-
module CobraCommander
|
7
|
-
# Represents a dependency tree in a given context
|
8
|
-
class ComponentTree
|
9
|
-
attr_reader :name, :path
|
10
|
-
|
11
|
-
def initialize(name, path, ancestry = Set.new)
|
12
|
-
@name = name
|
13
|
-
@path = path
|
14
|
-
@ancestry = ancestry
|
15
|
-
@ruby = Ruby.new(path)
|
16
|
-
@js = Js.new(path)
|
17
|
-
@type = type_of_component
|
18
|
-
end
|
19
|
-
|
20
|
-
def flatten
|
21
|
-
_flatten(self)
|
22
|
-
end
|
23
|
-
|
24
|
-
def subtree(name)
|
25
|
-
_subtree(name, self)
|
26
|
-
end
|
27
|
-
|
28
|
-
def depends_on?(component_name)
|
29
|
-
dependencies.any? do |component|
|
30
|
-
component.name == component_name || component.depends_on?(component_name)
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
|
-
def dependents_of(component_name)
|
35
|
-
depends = depends_on?(component_name) ? self : nil
|
36
|
-
dependents_below = dependencies.map do |component|
|
37
|
-
component.dependents_of(component_name)
|
38
|
-
end
|
39
|
-
[depends, dependents_below].flatten.compact.uniq(&:name)
|
40
|
-
end
|
41
|
-
|
42
|
-
def to_h
|
43
|
-
{
|
44
|
-
name: @name,
|
45
|
-
path: path,
|
46
|
-
type: @type,
|
47
|
-
ancestry: @ancestry,
|
48
|
-
dependencies: dependencies.map(&:to_h),
|
49
|
-
}
|
50
|
-
end
|
51
|
-
|
52
|
-
def dependencies
|
53
|
-
@deps ||= begin
|
54
|
-
deps = @ruby.dependencies + @js.dependencies
|
55
|
-
deps.sort_by { |dep| dep[:name] }
|
56
|
-
.map(&method(:dep_representation))
|
57
|
-
end
|
58
|
-
end
|
59
|
-
|
60
|
-
private
|
61
|
-
|
62
|
-
def _flatten(component)
|
63
|
-
component.dependencies.map do |dep|
|
64
|
-
[dep] + _flatten(dep)
|
65
|
-
end.flatten.uniq(&:name)
|
66
|
-
end
|
67
|
-
|
68
|
-
def _subtree(name, tree)
|
69
|
-
return tree if tree.name == name
|
70
|
-
tree.dependencies.each do |component|
|
71
|
-
presence = _subtree(name, component)
|
72
|
-
return presence if presence
|
73
|
-
end
|
74
|
-
nil
|
75
|
-
end
|
76
|
-
|
77
|
-
def type_of_component
|
78
|
-
return "Ruby & JS" if @ruby.gem? && @js.node?
|
79
|
-
return "Ruby" if @ruby.gem?
|
80
|
-
return "JS" if @js.node?
|
81
|
-
end
|
82
|
-
|
83
|
-
def dep_representation(dep)
|
84
|
-
full_path = File.expand_path(File.join(path, dep[:path]))
|
85
|
-
ancestry = @ancestry + [{ name: @name, path: path, type: @type }]
|
86
|
-
ComponentTree.new(dep[:name], full_path, ancestry)
|
87
|
-
end
|
88
|
-
|
89
|
-
# Calculates ruby dependencies
|
90
|
-
class Ruby
|
91
|
-
def initialize(root_path)
|
92
|
-
@root_path = root_path
|
93
|
-
end
|
94
|
-
|
95
|
-
def dependencies
|
96
|
-
@deps ||= begin
|
97
|
-
return [] unless gem?
|
98
|
-
gems = bundler_definition.dependencies.select { |dep| path?(dep.source) }
|
99
|
-
format(gems)
|
100
|
-
end
|
101
|
-
end
|
102
|
-
|
103
|
-
def path?(source)
|
104
|
-
return if source.nil?
|
105
|
-
source_has_path = source.respond_to?(:path?) ? source.path? : source.is_a_path?
|
106
|
-
source_has_path && source.path.to_s != "."
|
107
|
-
end
|
108
|
-
|
109
|
-
def format(deps)
|
110
|
-
deps.map do |dep|
|
111
|
-
path = File.join(dep.source.path, dep.name)
|
112
|
-
{ name: dep.name, path: path }
|
113
|
-
end
|
114
|
-
end
|
115
|
-
|
116
|
-
def gem?
|
117
|
-
@gem ||= File.exist?(gemfile_path)
|
118
|
-
end
|
119
|
-
|
120
|
-
def bundler_definition
|
121
|
-
::Bundler::Definition.build(gemfile_path, gemfile_lock_path, nil)
|
122
|
-
end
|
123
|
-
|
124
|
-
def gemfile_path
|
125
|
-
File.join(@root_path, "Gemfile")
|
126
|
-
end
|
127
|
-
|
128
|
-
def gemfile_lock_path
|
129
|
-
File.join(@root_path, "Gemfile.lock")
|
130
|
-
end
|
131
|
-
end
|
132
|
-
|
133
|
-
# Calculates js dependencies
|
134
|
-
class Js
|
135
|
-
def initialize(root_path)
|
136
|
-
@root_path = root_path
|
137
|
-
end
|
138
|
-
|
139
|
-
def dependencies
|
140
|
-
@deps ||= begin
|
141
|
-
return [] unless node?
|
142
|
-
json = JSON.parse(File.read(package_json_path))
|
143
|
-
format combined_deps(json)
|
144
|
-
end
|
145
|
-
end
|
146
|
-
|
147
|
-
def format(deps)
|
148
|
-
return [] if deps.nil?
|
149
|
-
linked_deps = deps.select { |_, v| v.start_with? "link:" }
|
150
|
-
linked_deps.map do |_, v|
|
151
|
-
relational_path = v.split("link:")[1]
|
152
|
-
dep_name = relational_path.split("/")[-1]
|
153
|
-
{ name: dep_name, path: relational_path }
|
154
|
-
end
|
155
|
-
end
|
156
|
-
|
157
|
-
def node?
|
158
|
-
@node ||= File.exist?(package_json_path)
|
159
|
-
end
|
160
|
-
|
161
|
-
def package_json_path
|
162
|
-
File.join(@root_path, "package.json")
|
163
|
-
end
|
164
|
-
|
165
|
-
def combined_deps(json)
|
166
|
-
Hash(json["dependencies"]).merge(Hash(json["devDependencies"]))
|
167
|
-
end
|
168
|
-
end
|
169
|
-
end
|
170
|
-
end
|
@@ -1,46 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "graphviz"
|
4
|
-
require "cobra_commander/component_tree"
|
5
|
-
|
6
|
-
module CobraCommander
|
7
|
-
# Generates graphs of components
|
8
|
-
class Graph
|
9
|
-
def initialize(app_path, format)
|
10
|
-
@format = format
|
11
|
-
@tree = CobraCommander.umbrella_tree(app_path).to_h
|
12
|
-
end
|
13
|
-
|
14
|
-
def generate!
|
15
|
-
return unless valid_format?
|
16
|
-
|
17
|
-
g = GraphViz.new(:G, type: :digraph, concentrate: true)
|
18
|
-
|
19
|
-
app_node = g.add_nodes(@tree[:name])
|
20
|
-
map_nodes(g, app_node, @tree)
|
21
|
-
output(g)
|
22
|
-
end
|
23
|
-
|
24
|
-
private
|
25
|
-
|
26
|
-
def map_nodes(g, parent_node, tree)
|
27
|
-
tree[:dependencies].each do |dep|
|
28
|
-
node = g.find_node(dep[:name]) || g.add_nodes(dep[:name])
|
29
|
-
g.add_edges(parent_node, node)
|
30
|
-
map_nodes(g, node, dep)
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
|
-
def output(g)
|
35
|
-
graph = "graph.#{@format}"
|
36
|
-
g.output(@format => graph)
|
37
|
-
puts "Graph generated at #{`pwd`.chomp}/#{graph}"
|
38
|
-
end
|
39
|
-
|
40
|
-
def valid_format?
|
41
|
-
return true if @format == "png" || @format == "dot"
|
42
|
-
puts "FORMAT must be 'png' or 'dot'"
|
43
|
-
false
|
44
|
-
end
|
45
|
-
end
|
46
|
-
end
|