cobra_commander 0.4.0 → 0.7.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: fb2f9f51193bd4ec022e4f12eae0b43f02d0f4f7adac086ac8c38f2bd7f3bf56
4
- data.tar.gz: bb44e1c12845cab98efdcb8cf1da499f67fa335bd781bf14f4c07d11ed3c9eb5
3
+ metadata.gz: 7155830962a165a9d34da676e053580b6a375543c0823cf89ce72db41b8fdc7e
4
+ data.tar.gz: db6fd674597394e4c5ee1f8e42b42d9e24305de678a9043f17ed026238f6edcc
5
5
  SHA512:
6
- metadata.gz: fd838a67943d9e2ae8cc7038b81ccacaaea719f8cc08cea7704df5147ffc1609ef2a91622549d423954003465d1ec310b0d09bf10ec8f3cf915850e0dbdbe39c
7
- data.tar.gz: a4dec3544d6a5fbcd258b759b9ba143955acf054b4ff44af1fb25d0f3391f37741e9c2d9e31221fd192ec08c843f39b62833a6741de6313e17453e37ca82722b
6
+ metadata.gz: ee123f0d3b655a028e76e991fd3c7c0d411f9ece583965f9f4547a6356cce527049b41e90d665b18c433d5c8a293e43726c9a5b3fa822fb0a433d08b23df8f6e
7
+ data.tar.gz: 3f9514a4c74710643361ac3d3950e3f3d3b72e864b44c6f5addfc35997ae99a779bf07d5382cb38fa424a9d859de741d7836cd93e57624e55d044d8c0a0cfc0a
@@ -0,0 +1,11 @@
1
+ # EditorConfig is awesome: https://EditorConfig.org
2
+ # top-most EditorConfig file
3
+ root = true
4
+
5
+ # Unix-style newlines with a newline ending every file
6
+ [*]
7
+ end_of_line = lf
8
+ insert_final_newline = true
9
+ trim_trailing_whitespace = true
10
+ indent_style = space
11
+ indent_size = 2
@@ -4,8 +4,9 @@ before_install:
4
4
  - "find /home/travis/.rvm/rubies -wholename '*default/bundler-*.gemspec' -delete"
5
5
  - gem install bundler:"$BUNDLER_VERSION"
6
6
  - sudo apt-get -qq install graphviz
7
+ - curl -o- -L https://yarnpkg.com/install.sh | bash -s -- --version 1.13.0
8
+ - export PATH="$HOME/.yarn/bin:$PATH"
7
9
  rvm:
8
10
  - 2.5.1
9
11
  env:
10
- - BUNDLER_VERSION=1.15.4
11
- - BUNDLER_VERSION=1.16.3
12
+ - BUNDLER_VERSION=1.17.3
@@ -2,6 +2,31 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## Version 0.7 - 2020-07-08
6
+
7
+ * Introduces CobraCommander::Umbrella with optimizations for dependency resolution
8
+ * Deprecates cobra cache and cache usages
9
+
10
+ ## Version 0.6.1 - 2019-07-05
11
+
12
+ * Better supports yarn workspaces globbing by delegating to yarn to calculate the list of components rather than re-implementing in Ruby. PR [#34](https://github.com/powerhome/cobra_commander/pull/34)
13
+
14
+ ## Version 0.6.0 - 2019-07-03
15
+
16
+ * Tracks package.json `workspaces` in addition to `dependencies` and `devDependencies`. PR [#31](https://github.com/powerhome/cobra_commander/pull/31)
17
+ * Permits caching the component tree of a project on disk to avoid calculating it repeatedly on subsequent cobra invocations.
18
+
19
+ ## Version 0.5.1 - 2018-10-15
20
+
21
+ * Fix a bug with dependencies_of where it wouldn't match components further down the list of umbrella's dependencies
22
+ * Bumps Thor version from 0.19.4 to a range (< 2.0, >= 0.18.1) compatible with railties
23
+
24
+ ## Version 0.5.0 - 2018-09-20
25
+
26
+ * Renames `dependencies_of` to `dependents_of`. PR [#25](https://github.com/powerhome/cobra_commander/pull/25)
27
+ * Add `dependencies_of` command list the direct and indirect dependencies of one component. PR [#25](https://github.com/powerhome/cobra_commander/pull/25)
28
+ * Add `do` command allow executing a command in the context of each component. PR [#26](https://github.com/powerhome/cobra_commander/pull/26)
29
+
5
30
  ## Version 0.4.0 - 2018-09-06
6
31
 
7
32
  * Add `dependencies_of` command to permit listing or counting the dependencies of a particular component. PR [#24](https://github.com/powerhome/cobra_commander/pull/24)
data/README.md CHANGED
@@ -28,16 +28,20 @@ Or install it yourself as:
28
28
 
29
29
  ```bash
30
30
  Commands:
31
- cobra changes APP_PATH [--results=RESULTS] [--branch=BRANCH] # Prints list of changed files
32
- cobra graph APP_PATH [--format=FORMAT] # Outputs graph
33
- cobra help [COMMAND] # Describe available commands or one specific command
34
- cobra ls APP_PATH # Prints tree of components for an app
35
- cobra version # Prints version
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
36
40
  ```
37
41
 
38
42
  ## Development
39
43
 
40
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
44
+ After checking out the repo, run `bin/setup` to install dependencies. You will also need to install `graphviz` by running `brew install graphviz`. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
41
45
 
42
46
  To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
43
47
 
@@ -11,10 +11,12 @@ Gem::Specification.new do |spec|
11
11
  spec.authors = [
12
12
  "Ben Langfeld",
13
13
  "Garett Arrowood",
14
+ "Carlos Palhares",
14
15
  ]
15
16
  spec.email = [
16
17
  "blangfeld@powerhrg.com",
17
18
  "garett.arrowood@powerhrg.com",
19
+ "carlos.palhares@powerhrg.com",
18
20
  ]
19
21
  spec.summary = "Tools for working with Component Based Rails Apps"
20
22
  spec.description = <<~DESCRIPTION
@@ -32,10 +34,10 @@ DESCRIPTION
32
34
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
33
35
  spec.require_paths = ["lib"]
34
36
 
35
- spec.add_dependency "thor", "~> 0.19.4"
37
+ spec.add_dependency "thor", ["< 2.0", ">= 0.18.1"]
36
38
  spec.add_dependency "ruby-graphviz", "~> 1.2.3"
37
39
 
38
- spec.add_development_dependency "bundler", "~> 1.13"
40
+ spec.add_development_dependency "bundler", "~> 1.17"
39
41
  spec.add_development_dependency "rake", "~> 10.0"
40
42
  spec.add_development_dependency "rspec", "~> 3.5"
41
43
  spec.add_development_dependency "guard-rspec"
@@ -1,16 +1,37 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "cobra_commander/dependencies"
4
+ require "cobra_commander/component"
5
+ require "cobra_commander/umbrella"
6
+
3
7
  require "cobra_commander/cli"
4
- require "cobra_commander/component_tree"
8
+ require "cobra_commander/cached_component_tree"
9
+ require "cobra_commander/calculated_component_tree"
5
10
  require "cobra_commander/version"
6
- require "cobra_commander/formatted_output"
7
11
  require "cobra_commander/graph"
8
12
  require "cobra_commander/change"
9
13
  require "cobra_commander/affected"
14
+ require "cobra_commander/output"
15
+ require "cobra_commander/executor"
10
16
 
11
17
  # Tools for working with Component Based Rails Apps (see http://shageman.github.io/cbra.info/).
12
18
  # Includes tools for graphing the components of an app and their relationships, as well as selectively
13
19
  # testing components based on changes made.
14
20
  module CobraCommander
15
21
  UMBRELLA_APP_NAME = "App"
22
+
23
+ def self.umbrella(root_path, yarn: false, bundler: false, name: UMBRELLA_APP_NAME)
24
+ umbrella = Umbrella.new(name, root_path)
25
+ umbrella.add_source(:yarn, Dependencies::YarnWorkspace.new(root_path)) unless bundler
26
+ umbrella.add_source(:bundler, Dependencies::Bundler.new(root_path)) unless yarn
27
+ umbrella
28
+ end
29
+
30
+ def self.umbrella_tree(path)
31
+ CalculatedComponentTree.new(UMBRELLA_APP_NAME, path)
32
+ end
33
+
34
+ def self.tree_from_cache(cache_file)
35
+ CachedComponentTree.from_cache_file(cache_file)
36
+ end
16
37
  end
@@ -3,28 +3,33 @@
3
3
  module CobraCommander
4
4
  # Calculates directly & transitively affected components
5
5
  class Affected
6
- attr_reader :directly, :transitively
7
-
8
- def initialize(tree, changes, path)
9
- @tree = tree
6
+ def initialize(umbrella, changes)
7
+ @umbrella = umbrella
10
8
  @changes = changes
11
- @path = path
12
9
  run!
13
10
  end
14
11
 
15
12
  def names
16
- @names ||= paths.map { |path| File.basename(path) }
13
+ @names ||= all_affected.map(&:name)
17
14
  end
18
15
 
19
16
  def scripts
20
17
  @scripts ||= paths.map { |path| File.join(path, "test.sh") }
21
18
  end
22
19
 
20
+ def directly
21
+ @directly.map(&method(:affected_component))
22
+ end
23
+
24
+ def transitively
25
+ @transitively.map(&method(:affected_component))
26
+ end
27
+
23
28
  def json_representation # rubocop:disable Metrics/MethodLength
24
29
  {
25
30
  changed_files: @changes,
26
- directly_affected_components: @directly,
27
- transitively_affected_components: @transitively,
31
+ directly_affected_components: directly,
32
+ transitively_affected_components: transitively,
28
33
  test_scripts: scripts,
29
34
  component_names: names,
30
35
  languages: {
@@ -39,46 +44,48 @@ module CobraCommander
39
44
  def run!
40
45
  @transitively = Set.new
41
46
  @directly = Set.new
42
- find_dependencies(@tree)
43
- @transitively.delete(name: UMBRELLA_APP_NAME, path: @path, type: @tree[:type])
44
- @transitively = @transitively.to_a.sort_by { |h| h[:name] }
45
- @directly = @directly.to_a.sort_by { |h| h[:name] }
47
+ @umbrella.components.each(&method(:add_if_changed))
48
+ @transitively = @transitively.sort_by(&:name)
49
+ @directly = @directly.sort_by(&:name)
46
50
  end
47
51
 
48
- def find_dependencies(parent_component)
49
- parent_component[:dependencies].each do |component|
50
- add_if_changed(component)
51
- find_dependencies(component)
52
- end
52
+ def add_if_changed(component)
53
+ return if component.root_paths.uniq.none? { |path| @changes.any?(Regexp.new(path)) }
54
+
55
+ @directly << component
56
+ @transitively.merge(component.deep_dependents)
53
57
  end
54
58
 
55
- def add_if_changed(component)
56
- @changes.each do |change|
57
- if change.start_with?(component[:path])
58
- @directly << component.reject { |k| k == :dependencies || k == :ancestry }
59
- @transitively.merge component[:ancestry]
60
- end
61
- end
59
+ def affected_component(component)
60
+ {
61
+ name: component.name,
62
+ path: component.root_paths,
63
+ type: component.sources.keys.map(&:to_s).map(&:capitalize).join(" & "),
64
+ }
62
65
  end
63
66
 
64
67
  def all_affected
65
- @all_affected ||= (@directly + @transitively).uniq.sort_by { |h| h[:path] }
68
+ @all_affected ||= (@directly | @transitively).sort_by(&:name)
66
69
  end
67
70
 
68
71
  def paths
69
- @paths ||= all_affected.map { |component| component[:path] }
72
+ @paths ||= all_affected.map(&:root_paths).flatten.uniq
70
73
  end
71
74
 
72
- def types
73
- @types ||= all_affected.map { |component| component[:type] }
75
+ def all_affected_sources
76
+ all_affected
77
+ .map(&:sources)
78
+ .map(&:keys)
79
+ .flatten
80
+ .uniq
74
81
  end
75
82
 
76
83
  def contains_ruby?
77
- types.uniq.join.include?("Ruby")
84
+ all_affected_sources.include?(:bundler)
78
85
  end
79
86
 
80
87
  def contains_js?
81
- types.uniq.join.include?("JS")
88
+ all_affected_sources.include?(:yarn)
82
89
  end
83
90
  end
84
91
  end
@@ -0,0 +1,24 @@
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
@@ -0,0 +1,141 @@
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,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "cobra_commander/component_tree"
4
3
  require "cobra_commander/affected"
5
4
  require "open3"
6
5
 
@@ -9,12 +8,12 @@ module CobraCommander
9
8
  class Change
10
9
  InvalidSelectionError = Class.new(StandardError)
11
10
 
12
- def initialize(path, results, branch)
13
- @root_dir = Dir.chdir(path) { `git rev-parse --show-toplevel`.chomp }
11
+ def initialize(umbrella, results, branch)
12
+ @root_dir = Dir.chdir(umbrella.path) { `git rev-parse --show-toplevel`.chomp }
14
13
  @results = results
15
14
  @branch = branch
16
- @tree = ComponentTree.new(path).to_h
17
- @affected = Affected.new(@tree, changes, path)
15
+ @umbrella = umbrella
16
+ @affected = Affected.new(@umbrella, changes)
18
17
  end
19
18
 
20
19
  def run!
@@ -86,8 +85,8 @@ module CobraCommander
86
85
  end
87
86
  end
88
87
 
89
- def display(component)
90
- "#{component[:name]} - #{component[:type]}"
88
+ def display(name:, type:, **)
89
+ "#{name} - #{type}"
91
90
  end
92
91
 
93
92
  def blank_line