cobra_commander 0.7.0 → 0.8.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7155830962a165a9d34da676e053580b6a375543c0823cf89ce72db41b8fdc7e
4
- data.tar.gz: db6fd674597394e4c5ee1f8e42b42d9e24305de678a9043f17ed026238f6edcc
3
+ metadata.gz: 6f1de2b5ceb4fbde7dcd12fd71606c55cdd733e6928e4c6a866dfc1f3ec65af3
4
+ data.tar.gz: 5a9537dabd13b974df919be911755debf92761b6bdf99b370c59cec2c8fc09af
5
5
  SHA512:
6
- metadata.gz: ee123f0d3b655a028e76e991fd3c7c0d411f9ece583965f9f4547a6356cce527049b41e90d665b18c433d5c8a293e43726c9a5b3fa822fb0a433d08b23df8f6e
7
- data.tar.gz: 3f9514a4c74710643361ac3d3950e3f3d3b72e864b44c6f5addfc35997ae99a779bf07d5382cb38fa424a9d859de741d7836cd93e57624e55d044d8c0a0cfc0a
6
+ metadata.gz: 58f066894063d46d3b1c25baf2b085f3212db9ac716eee5c8ef6c2465c82233f06fae79e30c04205d95b75551799cd3f1c67901a60a516bf079e2cb154baa2b2
7
+ data.tar.gz: 99babd4c1ae0b5e218ed913bf175cbd38d4efe4bc19d11ea108d8f914c9f3381a7f43bb6c8a4d64171bec648e5c5e8441c39c30f5c3a83f11e0c9f1aa4e0948b
@@ -2,6 +2,13 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## Version 0.8 - 2020-07-21
6
+
7
+ * Standardize Cobra CLI options
8
+ * More powerful filters to all cobra commands (--js, --ruby)
9
+ * More powerful filters to cobra ls and exec (--dependencies, --dependents)
10
+ * Graphs for components, not just umbrella
11
+
5
12
  ## Version 0.7 - 2020-07-08
6
13
 
7
14
  * Introduces CobraCommander::Umbrella with optimizations for dependency resolution
data/README.md CHANGED
@@ -24,19 +24,110 @@ Or install it yourself as:
24
24
 
25
25
  $ gem install cobra_commander
26
26
 
27
- ## Usage
27
+ ## Usage (cobra help)
28
28
 
29
29
  ```bash
30
30
  Commands:
31
- cobra cache APP_PATH CACHE_PATH # Caches a representation of the component structure of the app
32
- cobra changes APP_PATH [--results=RESULTS] [--branch=BRANCH] [--cache=nil] # Prints list of changed files
33
- cobra dependencies_of [component] [--app=pwd] [--format=FORMAT] [--cache=nil] # Outputs a list of components that [component] depends on within [app] context
34
- cobra dependents_of [component] [--app=pwd] [--format=FORMAT] [--cache=nil] # Outputs count of components in [app] dependent on [component]
35
- cobra do [command] [--app=pwd] [--cache=nil] # Executes the command in the context of each component in [app]
36
- cobra graph APP_PATH [--format=FORMAT] [--cache=nil] # Outputs graph
37
- cobra help [COMMAND] # Describe available commands or one specific command
38
- cobra ls [app_path] [--app=pwd] [--format=FORMAT] [--cache=nil] # Prints tree of components for an app
39
- cobra version # Prints version
31
+ cobra changes [--results=RESULTS] [--branch=BRANCH] # Prints list of changed files
32
+ cobra exec [component] <command> # Executes the command in the context of a given component or set of components. If no component is given executes the command in all components.
33
+ cobra graph [component] # Outputs a graph of a given component or umbrella
34
+ cobra help [COMMAND] # Describe available commands or one specific command
35
+ cobra ls [component] # Lists the components in the context of a given component or umbrella
36
+ cobra tree [component] # Prints the dependency tree of a given component or umbrella
37
+ cobra version # Prints version
38
+
39
+ Options:
40
+ -a, [--app=APP]
41
+ # Default: /Users/chjunior/workspace/power/cobra_commander
42
+ [--js], [--no-js] # Consider only the JS dependency graph
43
+ [--ruby], [--no-ruby] # Consider only the Ruby dependency graph
44
+ ```
45
+
46
+ ### cobra changes
47
+
48
+ ```sh
49
+ ➜ nitro git:(nova/remove-cobra-commander) be cobra help changes
50
+ Usage:
51
+ cobra changes [--results=RESULTS] [--branch=BRANCH]
52
+
53
+ Options:
54
+ -r, [--results=RESULTS] # Accepts test, full, name or json
55
+ # Default: test
56
+ -b, [--branch=BRANCH] # Specified target to calculate against
57
+ # Default: master
58
+ -a, [--app=APP]
59
+ # Default: /Users/chjunior/workspace/power/nitro
60
+ [--js], [--no-js] # Consider only the JS dependency graph
61
+ [--ruby], [--no-ruby] # Consider only the Ruby dependency graph
62
+
63
+ Prints list of changed files
64
+ ```
65
+
66
+ ### cobra exec
67
+
68
+ ```sh
69
+ Usage:
70
+ cobra exec [component] <command>
71
+
72
+ Options:
73
+ [--dependencies], [--no-dependencies] # Run the command on each dependency of a given component
74
+ [--dependents], [--no-dependents] # Run the command on each dependency of a given component
75
+ -a, [--app=APP]
76
+ # Default: /Users/chjunior/workspace/power/cobra_commander
77
+ [--js], [--no-js] # Consider only the JS dependency graph
78
+ [--ruby], [--no-ruby] # Consider only the Ruby dependency graph
79
+
80
+ Executes the command in the context of a given component or set of components. If no component is given executes the command in all components.
81
+ ```
82
+
83
+ ### cobra graph
84
+
85
+ ```sh
86
+ Usage:
87
+ cobra graph [component]
88
+
89
+ Options:
90
+ -o, [--output=OUTPUT] # Output file, accepts .png or .dot
91
+ # Default: /Users/chjunior/workspace/power/cobra_commander/output.png
92
+ -a, [--app=APP]
93
+ # Default: /Users/chjunior/workspace/power/cobra_commander
94
+ [--js], [--no-js] # Consider only the JS dependency graph
95
+ [--ruby], [--no-ruby] # Consider only the Ruby dependency graph
96
+
97
+ Outputs a graph of a given component or umbrella
98
+ ```
99
+
100
+ ### cobra ls
101
+
102
+ ```sh
103
+ Usage:
104
+ cobra ls [component]
105
+
106
+ Options:
107
+ -d, [--dependencies], [--no-dependencies] # Run the command on each dependency of a given component
108
+ -D, [--dependents], [--no-dependents] # Run the command on each dependency of a given component
109
+ -t, [--total], [--no-total] # Prints the total count of components
110
+ -a, [--app=APP]
111
+ # Default: /Users/chjunior/workspace/power/cobra_commander
112
+ [--js], [--no-js] # Consider only the JS dependency graph
113
+ [--ruby], [--no-ruby] # Consider only the Ruby dependency graph
114
+
115
+ Lists the components in the context of a given component or umbrella
116
+ ```
117
+
118
+ ### cobra tree
119
+
120
+ ```sh
121
+ Usage:
122
+ cobra tree [component]
123
+
124
+ Options:
125
+ -a, [--app=APP]
126
+ # Default: /Users/chjunior/workspace/power/cobra_commander
127
+ [--js], [--no-js] # Consider only the JS dependency graph
128
+ [--ruby], [--no-ruby] # Consider only the Ruby dependency graph
129
+
130
+ Prints the dependency tree of a given component or umbrella
40
131
  ```
41
132
 
42
133
  ## Development
@@ -34,6 +34,7 @@ DESCRIPTION
34
34
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
35
35
  spec.require_paths = ["lib"]
36
36
 
37
+ spec.add_dependency "bundler", "~> 1.17"
37
38
  spec.add_dependency "thor", ["< 2.0", ">= 0.18.1"]
38
39
  spec.add_dependency "ruby-graphviz", "~> 1.2.3"
39
40
 
data/exe/cobra CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
3
 
4
- require "cobra_commander"
4
+ require "cobra_commander/cli"
5
5
 
6
6
  CobraCommander::CLI.start
@@ -3,16 +3,7 @@
3
3
  require "cobra_commander/dependencies"
4
4
  require "cobra_commander/component"
5
5
  require "cobra_commander/umbrella"
6
-
7
- require "cobra_commander/cli"
8
- require "cobra_commander/cached_component_tree"
9
- require "cobra_commander/calculated_component_tree"
10
6
  require "cobra_commander/version"
11
- require "cobra_commander/graph"
12
- require "cobra_commander/change"
13
- require "cobra_commander/affected"
14
- require "cobra_commander/output"
15
- require "cobra_commander/executor"
16
7
 
17
8
  # Tools for working with Component Based Rails Apps (see http://shageman.github.io/cbra.info/).
18
9
  # Includes tools for graphing the components of an app and their relationships, as well as selectively
@@ -3,86 +3,91 @@
3
3
  require "thor"
4
4
  require "fileutils"
5
5
 
6
+ require "cobra_commander"
7
+ require "cobra_commander/affected"
8
+ require "cobra_commander/change"
9
+ require "cobra_commander/executor"
10
+ require "cobra_commander/output"
11
+
6
12
  module CobraCommander
7
13
  # Implements the tool's CLI
8
14
  class CLI < Thor
9
- CACHE_DESCRIPTION = "[DEPRECATED] Path to a cache file to use (default: nil). If specified, this file will " \
10
- "be used to store the component tree for the app to speed up subsequent invocations. Must be rotated any time " \
11
- "the component dependency structure changes."
12
- COMMON_OPTIONS = "[--app=pwd] [--format=FORMAT] [--cache=nil]"
13
-
14
- desc "do [command] [--app=pwd] [--cache=nil]", "Executes the command in the context of each component in [app]"
15
- method_option :app, default: Dir.pwd, aliases: "-a", desc: "App path (default: CWD)"
16
- method_option :cache, default: nil, aliases: "-c", desc: CACHE_DESCRIPTION
17
- def do(command)
18
- executor = Executor.new(umbrella(options.app).components)
19
- executor.exec(command)
20
- end
15
+ class_option :app, default: Dir.pwd, aliases: "-a", type: :string
16
+ class_option :js, default: false, type: :boolean, desc: "Consider only the JS dependency graph"
17
+ class_option :ruby, default: false, type: :boolean, desc: "Consider only the Ruby dependency graph"
21
18
 
22
- desc "ls [app_path] #{COMMON_OPTIONS}", "Prints tree of components for an app"
23
- method_option :app, default: Dir.pwd, aliases: "-a", desc: "App path (default: CWD)"
24
- method_option :format, default: "tree", aliases: "-f", desc: "Format (list or tree, default: list)"
25
- method_option :cache, default: nil, aliases: "-c", desc: CACHE_DESCRIPTION
26
- def ls(app_path = Dir.pwd)
27
- Output.print(
28
- umbrella(app_path).root,
29
- options.format
30
- )
19
+ desc "version", "Prints version"
20
+ def version
21
+ puts CobraCommander::VERSION
31
22
  end
32
23
 
33
- desc "dependents_of [component] #{COMMON_OPTIONS}", "Outputs count of components in [app] dependent on [component]"
34
- method_option :app, default: Dir.pwd, aliases: "-a", desc: "Path to the root app where the component is mounted"
35
- method_option :format, default: "count", aliases: "-f", desc: "count or list"
36
- method_option :cache, default: nil, aliases: "-c", desc: CACHE_DESCRIPTION
37
- def dependents_of(component)
38
- dependents = umbrella(options.app).dependents_of(component)
39
- return unless dependents
40
- puts "list" == options.format ? dependents.map(&:name) : dependents.size
24
+ desc "ls [component]", "Lists the components in the context of a given component or umbrella"
25
+ method_option :dependencies, type: :boolean, aliases: "-d",
26
+ desc: "Run the command on each dependency of a given component"
27
+ method_option :dependents, type: :boolean, aliases: "-D",
28
+ desc: "Run the command on each dependency of a given component"
29
+ method_option :total, type: :boolean, aliases: "-t", desc: "Prints the total count of components"
30
+ def ls(component = nil)
31
+ components = components_filtered(component)
32
+ puts options.total ? components.size : CobraCommander::Output::FlatList.new(components).to_s
41
33
  end
42
34
 
43
- desc "dependencies_of [component] #{COMMON_OPTIONS}", "Outputs a list of components that [component] depends on"
44
- method_option :app, default: Dir.pwd, aliases: "-a", desc: "App path (default: CWD)"
45
- method_option :format, default: "list", aliases: "-f", desc: "Format (list or tree, default: list)"
46
- method_option :cache, default: nil, aliases: "-c", desc: CACHE_DESCRIPTION
47
- def dependencies_of(component)
48
- Output.print(
49
- umbrella(options.app).find(component),
50
- options.format
35
+ desc "exec [component] <command>", "Executes the command in the context of a given component or set of components. " \
36
+ "If no component is given executes the command in all components."
37
+ method_option :dependencies, type: :boolean, desc: "Run the command on each dependency of a given component"
38
+ method_option :dependents, type: :boolean, desc: "Run the command on each dependency of a given component"
39
+ def exec(command_or_component, command = nil)
40
+ CobraCommander::Executor.exec(
41
+ components_filtered(command && command_or_component),
42
+ command ? command : command_or_component
51
43
  )
52
44
  end
53
45
 
54
- desc "version", "Prints version"
55
- def version
56
- puts CobraCommander::VERSION
46
+ desc "tree [component]", "Prints the dependency tree of a given component or umbrella"
47
+ def tree(component = nil)
48
+ component = find_component(component)
49
+ puts CobraCommander::Output::AsciiTree.new(component).to_s
57
50
  end
58
51
 
59
- desc "graph APP_PATH [--format=FORMAT] [--cache=nil] [--component]", "Outputs graph"
60
- method_option :format, default: "png", aliases: "-f", desc: "Accepts png or dot"
61
- method_option :cache, default: nil, aliases: "-c", desc: CACHE_DESCRIPTION
62
- def graph(app_path)
63
- Graph.new(umbrella(app_path).root, options.format).generate!
52
+ desc "graph [component]", "Outputs a graph of a given component or umbrella"
53
+ method_option :output, default: File.join(Dir.pwd, "output.png"), aliases: "-o",
54
+ desc: "Output file, accepts .png or .dot"
55
+ def graph(component = nil)
56
+ CobraCommander::Output::GraphViz.generate(
57
+ find_component(component),
58
+ options.output
59
+ )
60
+ puts "Graph generated at #{options.output}"
61
+ rescue ArgumentError => error
62
+ error error.message
64
63
  end
65
64
 
66
- desc "changes APP_PATH [--results=RESULTS] [--branch=BRANCH] [--cache=nil]", "Prints list of changed files"
65
+ desc "changes [--results=RESULTS] [--branch=BRANCH]", "Prints list of changed files"
67
66
  method_option :results, default: "test", aliases: "-r", desc: "Accepts test, full, name or json"
68
67
  method_option :branch, default: "master", aliases: "-b", desc: "Specified target to calculate against"
69
- method_option :cache, default: nil, aliases: "-c", desc: CACHE_DESCRIPTION
70
- def changes(app_path)
71
- Change.new(umbrella(app_path), options.results, options.branch).run!
68
+ def changes
69
+ Change.new(umbrella, options.results, options.branch).run!
70
+ end
71
+
72
+ private
73
+
74
+ def umbrella
75
+ @umbrella ||= CobraCommander.umbrella(options.app, yarn: options.js, bundler: options.ruby)
72
76
  end
73
77
 
74
- desc "cache APP_PATH CACHE_PATH", "[DEPRECATED] Caches a representation of the component structure of the app"
75
- def cache(app_path, cache_path)
76
- tree = CobraCommander.umbrella_tree(app_path)
77
- FileUtils.mkdir_p(File.dirname(cache_path))
78
- File.write(cache_path, tree.to_json)
79
- puts "Created cache of component tree at #{cache_path}"
78
+ def find_component(name)
79
+ return umbrella.root unless name
80
+
81
+ umbrella.find(name) || error("Component #{name} not found, try one of `cobra ls`") || exit(1)
80
82
  end
81
83
 
82
- private
84
+ def components_filtered(component_name)
85
+ return umbrella.components unless component_name
86
+ component = find_component(component_name)
83
87
 
84
- def umbrella(path)
85
- CobraCommander.umbrella(path)
88
+ return component.deep_dependencies if options.dependencies
89
+ return component.deep_dependents if options.dependents
90
+ [component]
86
91
  end
87
92
  end
88
93
  end
@@ -2,13 +2,9 @@
2
2
 
3
3
  module CobraCommander
4
4
  # Execute commands on all components of a ComponentTree
5
- class Executor
6
- def initialize(components)
7
- @components = components
8
- end
9
-
10
- def exec(command, printer = $stdout)
11
- @components.each do |component|
5
+ module Executor
6
+ def self.exec(components, command, printer = $stdout)
7
+ components.each do |component|
12
8
  component.root_paths.each do |path|
13
9
  printer.puts "===> #{component.name} (#{path})"
14
10
  output, = Open3.capture2e(command, chdir: path, unsetenv_others: true)
@@ -1,74 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module CobraCommander
4
- # Module for pretty printing dependency trees
5
- module Output
6
- def self.print(node, format)
7
- output = format == "list" ? Output::FlatList.new(node) : Output::Tree.new(node)
8
- puts output.to_s
9
- end
10
-
11
- # Flattens a tree and prints unique items
12
- class FlatList
13
- def initialize(tree)
14
- @tree = tree
15
- end
16
-
17
- def to_s
18
- @tree.deep_dependencies.map(&:name).sort
19
- end
20
- end
21
-
22
- # Prints the tree in a nice tree form
23
- class Tree
24
- attr_accessor :tree
25
-
26
- SPACE = " "
27
- BAR = "│   "
28
- TEE = "├── "
29
- CORNER = "└── "
30
-
31
- def initialize(node)
32
- @node = node
33
- end
34
-
35
- def to_s
36
- StringIO.new.tap do |io|
37
- io.puts @node.name
38
- list_dependencies(io, @node)
39
- end.string
40
- end
41
-
42
- private
43
-
44
- def list_dependencies(io, node, outdents = [])
45
- node.dependencies.each do |dep|
46
- decide_on_line(io, node, dep, outdents)
47
- end
48
- nil
49
- end
50
-
51
- def decide_on_line(io, parent, dep, outdents)
52
- if parent.dependencies.last != dep
53
- add_tee(io, outdents, dep)
54
- else
55
- add_corner(io, outdents, dep)
56
- end
57
- end
58
-
59
- def add_tee(io, outdents, dep)
60
- io.puts line(outdents, TEE, dep.name)
61
- list_dependencies(io, dep, (outdents + [BAR]))
62
- end
63
-
64
- def add_corner(io, outdents, dep)
65
- io.puts line(outdents, CORNER, dep.name)
66
- list_dependencies(io, dep, (outdents + [SPACE]))
67
- end
68
-
69
- def line(outdents, sym, name)
70
- (outdents + [sym] + [name]).join
71
- end
72
- end
73
- end
74
- end
3
+ require_relative "output/flat_list"
4
+ require_relative "output/ascii_tree"
5
+ require_relative "output/graph_viz"
@@ -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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CobraCommander
4
- VERSION = "0.7.0"
4
+ VERSION = "0.8.0"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cobra_commander
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.0
4
+ version: 0.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ben Langfeld
@@ -10,8 +10,22 @@ authors:
10
10
  autorequire:
11
11
  bindir: exe
12
12
  cert_chain: []
13
- date: 2020-07-08 00:00:00.000000000 Z
13
+ date: 2020-07-27 00:00:00.000000000 Z
14
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'
15
29
  - !ruby/object:Gem::Dependency
16
30
  name: thor
17
31
  requirement: !ruby/object:Gem::Requirement
@@ -176,20 +190,19 @@ files:
176
190
  - exe/cobra
177
191
  - lib/cobra_commander.rb
178
192
  - lib/cobra_commander/affected.rb
179
- - lib/cobra_commander/cached_component_tree.rb
180
- - lib/cobra_commander/calculated_component_tree.rb
181
193
  - lib/cobra_commander/change.rb
182
194
  - lib/cobra_commander/cli.rb
183
195
  - lib/cobra_commander/component.rb
184
- - lib/cobra_commander/component_tree.rb
185
196
  - lib/cobra_commander/dependencies.rb
186
197
  - lib/cobra_commander/dependencies/bundler.rb
187
198
  - lib/cobra_commander/dependencies/yarn/package.rb
188
199
  - lib/cobra_commander/dependencies/yarn/package_repo.rb
189
200
  - lib/cobra_commander/dependencies/yarn_workspace.rb
190
201
  - lib/cobra_commander/executor.rb
191
- - lib/cobra_commander/graph.rb
192
202
  - lib/cobra_commander/output.rb
203
+ - lib/cobra_commander/output/ascii_tree.rb
204
+ - lib/cobra_commander/output/flat_list.rb
205
+ - lib/cobra_commander/output/graph_viz.rb
193
206
  - lib/cobra_commander/umbrella.rb
194
207
  - lib/cobra_commander/version.rb
195
208
  - renovate.json
@@ -1,24 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "cobra_commander/component_tree"
4
-
5
- module CobraCommander
6
- # Represents a dependency tree in a given context, built from a cache
7
- class CachedComponentTree < ComponentTree
8
- attr_reader :dependencies
9
-
10
- def self.from_cache_file(cache_file)
11
- cache = JSON.parse(File.read(cache_file), symbolize_names: true)
12
- new(cache)
13
- end
14
-
15
- def initialize(cache)
16
- super(cache[:name], cache[:path])
17
- @type = cache[:type]
18
- @ancestry = Set.new(cache[:ancestry])
19
- @dependencies = (cache[:dependencies] || []).map do |dep|
20
- CachedComponentTree.new(dep)
21
- end
22
- end
23
- end
24
- end
@@ -1,141 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "cobra_commander/component_tree"
4
- require "open3"
5
-
6
- module CobraCommander
7
- # Represents a dependency tree in a given context
8
- class CalculatedComponentTree < ComponentTree
9
- attr_reader :name, :path
10
-
11
- def initialize(name, path, ancestry = Set.new)
12
- super(name, path)
13
- @ancestry = ancestry
14
- @ruby = Ruby.new(path)
15
- @js = Js.new(path)
16
- @type = type_of_component
17
- end
18
-
19
- def dependencies
20
- @deps ||= begin
21
- deps = @ruby.dependencies + @js.dependencies
22
- deps.sort_by { |dep| dep[:name] }
23
- .map(&method(:dep_representation))
24
- end
25
- end
26
-
27
- private
28
-
29
- def type_of_component
30
- return "Ruby & JS" if @ruby.gem? && @js.node?
31
- return "Ruby" if @ruby.gem?
32
- return "JS" if @js.node?
33
- end
34
-
35
- def dep_representation(dep)
36
- full_path = File.expand_path(File.join(path, dep[:path]))
37
- ancestry = @ancestry + [{ name: @name, path: path, type: @type }]
38
- CalculatedComponentTree.new(dep[:name], full_path, ancestry)
39
- end
40
-
41
- # Calculates ruby dependencies
42
- class Ruby
43
- def initialize(root_path)
44
- @root_path = root_path
45
- end
46
-
47
- def dependencies
48
- @deps ||= begin
49
- return [] unless gem?
50
- gems = bundler_definition.dependencies.select { |dep| path?(dep.source) }
51
- format(gems)
52
- end
53
- end
54
-
55
- def path?(source)
56
- return if source.nil?
57
- source_has_path = source.respond_to?(:path?) ? source.path? : source.is_a_path?
58
- source_has_path && source.path.to_s != "."
59
- end
60
-
61
- def format(deps)
62
- deps.map do |dep|
63
- path = File.join(dep.source.path, dep.name)
64
- { name: dep.name, path: path }
65
- end
66
- end
67
-
68
- def gem?
69
- @gem ||= File.exist?(gemfile_path)
70
- end
71
-
72
- def bundler_definition
73
- ::Bundler::Definition.build(gemfile_path, gemfile_lock_path, nil)
74
- end
75
-
76
- def gemfile_path
77
- File.join(@root_path, "Gemfile")
78
- end
79
-
80
- def gemfile_lock_path
81
- File.join(@root_path, "Gemfile.lock")
82
- end
83
- end
84
-
85
- # Calculates js dependencies
86
- class Js
87
- def initialize(root_path)
88
- @root_path = root_path
89
- end
90
-
91
- def dependencies
92
- @deps ||= begin
93
- return [] unless node?
94
- json = JSON.parse(File.read(package_json_path))
95
- combined_deps(json)
96
- end
97
- end
98
-
99
- def format_dependencies(deps)
100
- return [] if deps.nil?
101
- linked_deps = deps.select { |_, v| v.start_with? "link:" }
102
- linked_deps.map do |_, v|
103
- relational_path = v.split("link:")[1]
104
- dep_name = relational_path.split("/")[-1]
105
- { name: dep_name, path: relational_path }
106
- end
107
- end
108
-
109
- def node?
110
- @node ||= File.exist?(package_json_path)
111
- end
112
-
113
- def package_json_path
114
- File.join(@root_path, "package.json")
115
- end
116
-
117
- def combined_deps(json)
118
- worskpace_dependencies = build_workspaces(json["workspaces"])
119
- dependencies = format_dependencies Hash(json["dependencies"]).merge(Hash(json["devDependencies"]))
120
- (dependencies + worskpace_dependencies).uniq
121
- end
122
-
123
- def build_workspaces(workspaces)
124
- return [] if workspaces.nil?
125
-
126
- yarn_workspaces.map do |_, component|
127
- { name: component["location"].split("/")[-1], path: component["location"] }
128
- end
129
- end
130
-
131
- private
132
-
133
- def yarn_workspaces
134
- @yarn_workspaces ||= begin
135
- output, = Open3.capture2("yarn --json workspaces info", chdir: @root_path)
136
- JSON.parse(JSON.parse(output)["data"])
137
- end
138
- end
139
- end
140
- end
141
- end
@@ -1,69 +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)
12
- @name = name
13
- @path = path
14
- end
15
-
16
- def flatten
17
- _flatten(self)
18
- end
19
-
20
- def subtree(name)
21
- _subtree(name, self)
22
- end
23
-
24
- def depends_on?(component_name)
25
- dependencies.any? do |component|
26
- component.name == component_name || component.depends_on?(component_name)
27
- end
28
- end
29
-
30
- def dependents_of(component_name)
31
- depends = depends_on?(component_name) ? self : nil
32
- dependents_below = dependencies.map do |component|
33
- component.dependents_of(component_name)
34
- end
35
- [depends, dependents_below].flatten.compact.uniq(&:name)
36
- end
37
-
38
- def to_h(json_compatible: false)
39
- {
40
- name: @name,
41
- path: path,
42
- type: @type,
43
- ancestry: json_compatible ? @ancestry.to_a : @ancestry,
44
- dependencies: dependencies.map { |dep| dep.to_h(json_compatible: json_compatible) },
45
- }
46
- end
47
-
48
- def to_json
49
- JSON.dump(to_h(json_compatible: true))
50
- end
51
-
52
- private
53
-
54
- def _flatten(component)
55
- component.dependencies.map do |dep|
56
- [dep] + _flatten(dep)
57
- end.flatten.uniq(&:name)
58
- end
59
-
60
- def _subtree(name, tree)
61
- return tree if tree.name == name
62
- tree.dependencies.each do |component|
63
- presence = _subtree(name, component)
64
- return presence if presence
65
- end
66
- nil
67
- end
68
- end
69
- end
@@ -1,37 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "graphviz"
4
-
5
- module CobraCommander
6
- # Generates graphs of components
7
- class Graph
8
- def initialize(node, format)
9
- @format = format
10
- @node = node
11
- end
12
-
13
- def generate!
14
- return unless valid_format?
15
-
16
- g = GraphViz.new(:G, type: :digraph, concentrate: true)
17
- ([@node] + @node.deep_dependencies).each do |component|
18
- g.add_nodes component.name
19
- g.add_edges component.name, component.dependencies.map(&:name)
20
- end
21
-
22
- output(g)
23
- end
24
-
25
- def output(g)
26
- graph = "graph.#{@format}"
27
- g.output(@format => graph)
28
- puts "Graph generated at #{`pwd`.chomp}/#{graph}"
29
- end
30
-
31
- def valid_format?
32
- return true if @format == "png" || @format == "dot"
33
- puts "FORMAT must be 'png' or 'dot'"
34
- false
35
- end
36
- end
37
- end