cobra_commander 0.10.0 → 0.14.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: aef5fdfc5c3d583b0b70080e497ee97bc63d25ddcbd0e71fedb1574a73acd0fe
4
- data.tar.gz: d99e9e310fb9958fdd6b15ff057be095b7249a0f37d639048db971d4cd7bf336
3
+ metadata.gz: d3e2348a3ed7ceecd361b13fe0632bb5daa54a59652d4dd9eac5646c2ccc5878
4
+ data.tar.gz: 49dc4f2937e87a8146c685d82d7d45fea82dd16e3e0b880ef9944c8ac0710b0c
5
5
  SHA512:
6
- metadata.gz: a3b8ba0d25eb81df0be99c7dfa06a81b87cd1f1233984d622f5a17d15ff672e50e2fa853e724bbea9038220c15fb5e507f0dffa05c605837f550198a5f9643c7
7
- data.tar.gz: 7b9dbf8804a4d34e7fa083579917b580807ff92e4896a7db177844545f022392590a96500b31fdf2eb89aecd92e1599187cacd506eb0640d2b2fbc766afe5c56
6
+ metadata.gz: 2ca9041bfb43cbec99252e00f52e117d8e2f09778afc686f1b204c4ff2e5733841f10b727adc3e3c9f28b121d61dff6ef4c79745a9e7574eb9bc36c3dd6ee5a9
7
+ data.tar.gz: befffb97f5743fce5066db00f5c434053bdebc275c12b74d95c3d33a375b8c9e6bf5e10fa186188a163f1f9c8fd91f01055d56c8880ee40815915878077aebc9
@@ -1,6 +1,6 @@
1
1
  name: CI
2
2
 
3
- on: [push, pull_request]
3
+ on: push
4
4
 
5
5
  jobs:
6
6
  test:
@@ -13,6 +13,7 @@ jobs:
13
13
  - "2.5"
14
14
  - "2.6"
15
15
  - "2.7"
16
+ - "3.0"
16
17
  bundler:
17
18
  - "1"
18
19
  - "2"
@@ -20,8 +21,7 @@ jobs:
20
21
  BUNDLE_GEMFILE: gemfiles/bundler${{ matrix.bundler }}.gemfile
21
22
  steps:
22
23
  - uses: actions/checkout@v2
23
- - name: Set up Ruby
24
- uses: ruby/setup-ruby@v1
24
+ - uses: ruby/setup-ruby@v1
25
25
  with:
26
26
  ruby-version: ${{ matrix.ruby }}
27
27
  bundler: ${{ matrix.bundler }}
@@ -37,11 +37,10 @@ jobs:
37
37
  runs-on: ubuntu-latest
38
38
  steps:
39
39
  - uses: actions/checkout@v2
40
- - name: rubocop
41
- uses: reviewdog/action-rubocop@v1
40
+ - uses: ruby/setup-ruby@v1
42
41
  with:
43
- rubocop_version: 0.88.0
44
- filter_mode: nofilter
45
- fail_on_error: true
46
- rubocop_extensions: ""
47
- github_token: ${{ secrets.github_token }}
42
+ ruby-version: 3.0
43
+ - name: Bundle
44
+ run: bundle
45
+ - name: Run standard
46
+ run: bundle exec rake standard
@@ -0,0 +1,20 @@
1
+ name: Publish Gem
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+ tags:
8
+ - v*
9
+ jobs:
10
+ build:
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - uses: actions/checkout@v2
14
+ - name: Release Gem
15
+ uses: cadwallion/publish-rubygems-action@master
16
+ if: contains(github.ref, 'refs/tags/v')
17
+ env:
18
+ GITHUB_TOKEN: ${{ secrets.github_token }}
19
+ RUBYGEMS_API_KEY: ${{ secrets.rubygems_api_key }}
20
+ RELEASE_COMMAND: rake release
data/CHANGELOG.md CHANGED
@@ -2,9 +2,44 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## Version 0.14.0 - 2021-11-24
6
+
7
+ * Retain selection on Interactive Printer [#69](https://github.com/powerhome/cobra_commander/pull/69)
8
+ * Sorts the interactive UI alphabetically with failures first [#68](https://github.com/powerhome/cobra_commander/pull/68)
9
+ * Switch from Rubocop to Standardrb [#67](https://github.com/powerhome/cobra_commander/pull/67)
10
+
11
+ ## Version 0.13.0 - 2021-10-21
12
+
13
+ * Sorts the interactive UI alphabetically with failures first [#66](https://github.com/powerhome/cobra_commander/pull/66)
14
+ * Switch linter to standardrb [#67](https://github.com/powerhome/cobra_commander/pull/67)
15
+
16
+ ## Version 0.12.0 - 2021-09-21
17
+
18
+ * Add interactive UI and Markdown output to `cobra exec` [#63](https://github.com/powerhome/cobra_commander/pull/63)
19
+ * Add `--self` and `--no-self` filters to `cobra exec` and `cobra ls`, defaults to `--self` [#61](https://github.com/powerhome/cobra_commander/pull/61)
20
+
21
+ ## Version 0.11.0 - 2021-04-23
22
+
23
+ * Add concurrency limit to multi exec [#57](https://github.com/powerhome/cobra_commander/pull/57)
24
+ * Ruby 3.0 compatibility [#55](https://github.com/powerhome/cobra_commander/pull/55)
25
+
26
+ ## Version 0.10.0 - 2021-02-25
27
+
28
+ * Add support for Bundler 2 [#54](https://github.com/powerhome/cobra_commander/pull/54)
29
+ * Add support for Ruby 2.7 [#53](https://github.com/powerhome/cobra_commander/pull/53)
30
+
31
+ ## Version 0.9.2 -
32
+
33
+ * Another fix for binstubs [#51](https://github.com/powerhome/cobra_commander/pull/51)
34
+
35
+ ## Version 0.9.1 -
36
+
37
+ * Replace bundler encapsulation violation by LockfileParser [#48](https://github.com/powerhome/cobra_commander/pull/48)
38
+ * Fix bundle binstubs [#50](https://github.com/powerhome/cobra_commander/pull/50)
39
+
5
40
  ## Version 0.9.0 - 2020-08-26
6
41
 
7
- * Add support for parallel task execution to `cobra exec`.
42
+ * Add support for parallel task execution to `cobra exec` [#49](https://github.com/powerhome/cobra_commander/pull/49)
8
43
 
9
44
  ## Version 0.8.1 - 2020-07-29
10
45
 
data/README.md CHANGED
@@ -2,11 +2,10 @@
2
2
 
3
3
  [![Gem](https://img.shields.io/gem/dv/cobra_commander/stable.svg)](https://rubygems.org/gems/cobra_commander)
4
4
  [![Gem](https://img.shields.io/gem/v/cobra_commander.svg)](https://rubygems.org/gems/cobra_commander)
5
- [![Travis](https://img.shields.io/travis/powerhome/cobra_commander.svg)](https://travis-ci.org/powerhome/cobra_commander)
6
- [![Code Climate](https://img.shields.io/codeclimate/github/powerhome/cobra_commander.svg)](https://codeclimate.com/github/powerhome/cobra_commander)
7
- [![Gemnasium](https://img.shields.io/gemnasium/powerhome/cobra_commander.svg)](https://gemnasium.com/github.com/powerhome/cobra_commander)
5
+ [![CI](https://github.com/powerhome/cobra_commander/actions/workflows/ci.yml/badge.svg)](https://github.com/powerhome/cobra_commander/actions/workflows/ci.yml)
6
+ [![Maintainability](https://api.codeclimate.com/v1/badges/7fe0781c18f6923ab753/maintainability)](https://codeclimate.com/github/powerhome/cobra_commander/maintainability)
8
7
 
9
- Tools for working with Component Based Rails Apps (see http://shageman.github.io/cbra.info/). Includes tools for graphing both Ruby and Javascript components in an application and their relationships, as well as selectively testing components based on changes made.
8
+ Tools for working with Component Based Rails Apps (see https://cbra.info/). Includes tools for graphing both Ruby and Javascript components in an application and their relationships, as well as selectively testing components based on changes made.
10
9
 
11
10
  ## Installation
12
11
 
data/Rakefile CHANGED
@@ -1,11 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "bundler/gem_tasks"
4
-
4
+ require "standard/rake"
5
5
  require "rspec/core/rake_task"
6
- RSpec::Core::RakeTask.new(:spec)
7
6
 
8
- require "rubocop/rake_task"
9
- RuboCop::RakeTask.new
7
+ RSpec::Core::RakeTask.new(:spec)
10
8
 
11
- task default: %i[spec rubocop]
9
+ task default: %i[spec standard]
@@ -5,45 +5,47 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
5
  require "cobra_commander/version"
6
6
 
7
7
  Gem::Specification.new do |spec|
8
- spec.name = "cobra_commander"
9
- spec.version = CobraCommander::VERSION
10
- spec.authors = [
8
+ spec.name = "cobra_commander"
9
+ spec.version = CobraCommander::VERSION
10
+ spec.authors = [
11
11
  "Ben Langfeld",
12
12
  "Garett Arrowood",
13
- "Carlos Palhares",
13
+ "Carlos Palhares"
14
14
  ]
15
15
  spec.email = [
16
16
  "blangfeld@powerhrg.com",
17
17
  "garett.arrowood@powerhrg.com",
18
- "carlos.palhares@powerhrg.com",
18
+ "carlos.palhares@powerhrg.com"
19
19
  ]
20
- spec.summary = "Tools for working with Component Based Rails Apps"
21
- spec.description = <<~DESCRIPTION
20
+ spec.summary = "Tools for working with Component Based Rails Apps"
21
+ spec.description = <<~DESCRIPTION
22
22
  Tools for working with Component Based Rails Apps (see http://shageman.github.io/cbra.info/).
23
23
  Includes tools for graphing the components of an app and their relationships, as well as selectively
24
24
  testing components based on changes made.
25
25
  DESCRIPTION
26
- spec.homepage = "http://tech.powerhrg.com/cobra_commander/"
27
- spec.license = "MIT"
26
+ spec.homepage = "http://tech.powerhrg.com/cobra_commander/"
27
+ spec.license = "MIT"
28
28
 
29
- spec.files = `git ls-files -z`.split("\x0").reject do |f|
29
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
30
30
  f.match(%r{^(test|spec|features)/})
31
31
  end
32
- spec.bindir = "exe"
33
- spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
32
+ spec.bindir = "exe"
33
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
34
34
  spec.require_paths = ["lib"]
35
35
 
36
36
  spec.add_dependency "bundler"
37
+ spec.add_dependency "concurrent-ruby", "~> 1.1"
37
38
  spec.add_dependency "ruby-graphviz", "~> 1.2.3"
38
39
  spec.add_dependency "thor", ["< 2.0", ">= 0.18.1"]
39
- spec.add_dependency "tty-command", "~> 0.9.0"
40
+ spec.add_dependency "tty-command", "~> 0.10.0"
41
+ spec.add_dependency "tty-prompt", "~> 0.23.1"
40
42
  spec.add_dependency "tty-spinner", "~> 0.9.3"
41
43
 
42
44
  spec.add_development_dependency "aruba", "~> 0.14.2"
43
45
  spec.add_development_dependency "bundler"
44
46
  spec.add_development_dependency "guard-rspec"
45
47
  spec.add_development_dependency "pry"
46
- spec.add_development_dependency "rake", "~> 10.0"
48
+ spec.add_development_dependency "rake", ">= 12.3.3"
47
49
  spec.add_development_dependency "rspec", "~> 3.5"
48
- spec.add_development_dependency "rubocop", "0.88.0"
50
+ spec.add_development_dependency "standard", ">= 1.3.0"
49
51
  end
@@ -25,21 +25,18 @@ module CobraCommander
25
25
  @transitively.map(&method(:affected_component))
26
26
  end
27
27
 
28
- def json_representation # rubocop:disable Metrics/MethodLength
28
+ def json_representation
29
29
  {
30
30
  changed_files: @changes,
31
31
  directly_affected_components: directly,
32
32
  transitively_affected_components: transitively,
33
33
  test_scripts: scripts,
34
34
  component_names: names,
35
- languages: {
36
- ruby: contains_ruby?,
37
- javascript: contains_js?,
38
- },
35
+ languages: {ruby: contains_ruby?, javascript: contains_js?}
39
36
  }.to_json
40
37
  end
41
38
 
42
- private
39
+ private
43
40
 
44
41
  def run!
45
42
  @transitively = Set.new
@@ -68,7 +65,7 @@ module CobraCommander
68
65
  {
69
66
  name: component.name,
70
67
  path: component.root_paths,
71
- type: component.sources.keys.map(&:to_s).map(&:capitalize).join(" & "),
68
+ type: component.sources.keys.map(&:to_s).map(&:capitalize).join(" & ")
72
69
  }
73
70
  end
74
71
 
@@ -28,7 +28,7 @@ module CobraCommander
28
28
  puts e.message
29
29
  end
30
30
 
31
- private
31
+ private
32
32
 
33
33
  def show_full
34
34
  changes_since_last_commit
@@ -66,13 +66,13 @@ module CobraCommander
66
66
 
67
67
  def directly_affected_components
68
68
  puts "<<< Directly affected components >>>"
69
- @affected.directly.each { |component| puts display(component) }
69
+ @affected.directly.each { |component| puts display(**component) }
70
70
  puts blank_line
71
71
  end
72
72
 
73
73
  def transitively_affected_components
74
74
  puts "<<< Transitively affected components >>>"
75
- @affected.transitively.each { |component| puts display(component) }
75
+ @affected.transitively.each { |component| puts display(**component) }
76
76
  puts blank_line
77
77
  end
78
78
 
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CobraCommander
4
+ # @private
5
+ class CLI
6
+ private_class_method def self.filter_options(dependents:, dependencies:)
7
+ method_option :dependencies, type: :boolean, aliases: "-d", desc: dependencies
8
+ method_option :dependents, type: :boolean, aliases: "-D", desc: dependents
9
+ method_option :self, type: :boolean, default: true, desc: "Include the own component"
10
+ end
11
+
12
+ private
13
+
14
+ def find_component(name)
15
+ return umbrella.root unless name
16
+
17
+ umbrella.find(name) || error("Component #{name} not found, maybe #{suggestion(name)}") || exit(1)
18
+ end
19
+
20
+ def suggestion(name)
21
+ [*suggestions(name), 'one of "cobra ls"'].join(", ")
22
+ end
23
+
24
+ def suggestions(name)
25
+ spell_checker = DidYouMean::SpellChecker.new(dictionary: umbrella.components.map(&:name))
26
+ spell_checker.correct(name)
27
+ rescue NameError
28
+ []
29
+ end
30
+
31
+ def components_filtered(component_name)
32
+ return umbrella.components unless component_name
33
+
34
+ component = find_component(component_name)
35
+ components = options.self ? [component] : []
36
+ components.concat component.deep_dependencies if options.dependencies
37
+ components.concat component.deep_dependents if options.dependents
38
+ components
39
+ end
40
+ end
41
+ end
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "thor"
4
4
  require "fileutils"
5
+ require "concurrent-ruby"
5
6
 
6
7
  require "cobra_commander"
7
8
  require "cobra_commander/affected"
@@ -12,6 +13,10 @@ require "cobra_commander/output"
12
13
  module CobraCommander
13
14
  # Implements the tool's CLI
14
15
  class CLI < Thor
16
+ require "cobra_commander/cli/filters"
17
+
18
+ DEFAULT_CONCURRENCY = (Concurrent.processor_count / 2.0).ceil
19
+
15
20
  class_option :app, default: Dir.pwd, aliases: "-a", type: :string
16
21
  class_option :js, default: false, type: :boolean, desc: "Consider only the JS dependency graph"
17
22
  class_option :ruby, default: false, type: :boolean, desc: "Consider only the Ruby dependency graph"
@@ -22,10 +27,8 @@ module CobraCommander
22
27
  end
23
28
 
24
29
  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"
30
+ filter_options dependents: "Lists all dependents of a given component",
31
+ dependencies: "Lists all dependencies of a given component"
29
32
  method_option :total, type: :boolean, aliases: "-t", desc: "Prints the total count of components"
30
33
  def ls(component = nil)
31
34
  components = components_filtered(component)
@@ -34,13 +37,24 @@ module CobraCommander
34
37
 
35
38
  desc "exec [component] <command>", "Executes the command in the context of a given component or set thereof. " \
36
39
  "Defaults to 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"
40
+ filter_options dependents: "Run the command on each dependent of a given component",
41
+ dependencies: "Run the command on each dependency of a given component"
42
+ method_option :concurrency, type: :numeric, default: DEFAULT_CONCURRENCY, aliases: "-c",
43
+ desc: "Max number of jobs to run concurrently"
44
+ method_option :interactive, type: :boolean, default: true, aliases: "-i",
45
+ desc: "Runs in interactive mode to allow the user to inspect the output of each " \
46
+ "component"
39
47
  def exec(command_or_component, command = nil)
40
- CobraCommander::Executor.exec(
41
- components_filtered(command && command_or_component),
42
- command || command_or_component
48
+ results = CobraCommander::Executor.exec(
49
+ components: components_filtered(command && command_or_component),
50
+ command: command || command_or_component,
51
+ concurrency: options.concurrency, status_output: $stderr
43
52
  )
53
+ if options.interactive && results.size > 1
54
+ CobraCommander::Output::InteractivePrinter.run(results, $stdout)
55
+ else
56
+ CobraCommander::Output::MarkdownPrinter.run(results, $stdout)
57
+ end
44
58
  end
45
59
 
46
60
  desc "tree [component]", "Prints the dependency tree of a given component or umbrella"
@@ -69,27 +83,10 @@ module CobraCommander
69
83
  Change.new(umbrella, options.results, options.branch).run!
70
84
  end
71
85
 
72
- private
86
+ private
73
87
 
74
88
  def umbrella
75
89
  @umbrella ||= CobraCommander.umbrella(options.app, yarn: options.js, bundler: options.ruby)
76
90
  end
77
-
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)
82
- end
83
-
84
- def components_filtered(component_name)
85
- return umbrella.components unless component_name
86
-
87
- component = find_component(component_name)
88
-
89
- return component.deep_dependencies if options.dependencies
90
- return component.deep_dependents if options.dependents
91
-
92
- [component]
93
- end
94
91
  end
95
92
  end
@@ -45,8 +45,8 @@ module CobraCommander
45
45
 
46
46
  def dependencies
47
47
  @dependencies ||= @dependency_names.sort
48
- .map(&@umbrella.method(:find))
49
- .compact
48
+ .map(&@umbrella.method(:find))
49
+ .compact
50
50
  end
51
51
  end
52
52
  end
@@ -21,11 +21,11 @@ module CobraCommander
21
21
 
22
22
  def components
23
23
  components_source.specs.map do |spec|
24
- { path: spec.loaded_from, name: spec.name, dependencies: spec.dependencies.map(&:name) }
24
+ {path: spec.loaded_from, name: spec.name, dependencies: spec.dependencies.map(&:name)}
25
25
  end
26
26
  end
27
27
 
28
- private
28
+ private
29
29
 
30
30
  def lockfile
31
31
  @lockfile ||= ::Bundler::LockfileParser.new(::Bundler.read_file(path))
@@ -24,10 +24,10 @@ module CobraCommander
24
24
 
25
25
  def dependencies
26
26
  json.fetch("dependencies", {})
27
- .merge(json.fetch("devDependencies", {}))
27
+ .merge(json.fetch("devDependencies", {}))
28
28
  end
29
29
 
30
- private
30
+ private
31
31
 
32
32
  def json
33
33
  @json ||= JSON.parse(File.read(@path))
@@ -28,11 +28,11 @@ module CobraCommander
28
28
 
29
29
  def components
30
30
  @repo.specs.map do |spec|
31
- { path: spec.path, name: untag(spec.name), dependencies: spec.dependencies.keys.map(&method(:untag)) }
31
+ {path: spec.path, name: untag(spec.name), dependencies: spec.dependencies.keys.map(&method(:untag))}
32
32
  end
33
33
  end
34
34
 
35
- private
35
+ private
36
36
 
37
37
  def load_workspace_packages
38
38
  workspace_spec.map do |_name, spec|
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "tty-spinner"
4
+ require "concurrent-ruby"
5
+
6
+ module CobraCommander
7
+ module Executor
8
+ # Execute a command on multiple components concurrently
9
+ class Concurrent
10
+ def initialize(components, concurrency:, spin_output:)
11
+ @components = components
12
+ @multi = TTY::Spinner::Multi.new(":spinner :task", output: spin_output)
13
+ @semaphore = ::Concurrent::Semaphore.new(concurrency)
14
+ end
15
+
16
+ def exec(command)
17
+ @multi.top_spinner.update(task: "Running #{command}")
18
+ @results = []
19
+ @components.each do |component|
20
+ register_job(component, command)
21
+ end
22
+ @multi.auto_spin
23
+ @results
24
+ end
25
+
26
+ private
27
+
28
+ def pastel
29
+ @pastel ||= Pastel.new
30
+ end
31
+
32
+ def spinner_options
33
+ @spinner_options ||= {
34
+ format: :bouncing,
35
+ success_mark: pastel.green("[DONE]"),
36
+ error_mark: pastel.red("[ERROR]")
37
+ }
38
+ end
39
+
40
+ def register_job(component, command)
41
+ @multi.register(":spinner #{component.name}", **spinner_options) do |spinner|
42
+ @semaphore.acquire
43
+ context = Context.new(component, command)
44
+ context.success? ? spinner.success : spinner.error
45
+ @results << context
46
+ @semaphore.release
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "tty-command"
4
+
5
+ module CobraCommander
6
+ module Executor
7
+ # Represents a component context to execute a command
8
+ # @private
9
+ class Context
10
+ attr_reader :component, :command
11
+
12
+ def initialize(component, command)
13
+ @component = component
14
+ @command = command
15
+ @tty = TTY::Command.new(pty: true, printer: :null, stderr: :stdout)
16
+ end
17
+
18
+ def results
19
+ @results ||= @component.root_paths.map do |path|
20
+ isolate_bundle do
21
+ @tty.run!(command, chdir: path)
22
+ end
23
+ end
24
+ end
25
+
26
+ def component_name
27
+ component.name
28
+ end
29
+
30
+ def success?
31
+ results.all?(&:success?)
32
+ end
33
+
34
+ def output
35
+ results.join("\n")
36
+ end
37
+
38
+ private
39
+
40
+ def isolate_bundle(&block)
41
+ if Bundler.respond_to?(:with_unbundled_env)
42
+ Bundler.with_unbundled_env(&block)
43
+ else
44
+ Bundler.with_clean_env(&block)
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -1,19 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "executor/component_exec"
4
- require_relative "executor/multi_exec"
3
+ require_relative "executor/context"
4
+ require_relative "executor/concurrent"
5
5
 
6
6
  module CobraCommander
7
- # Execute commands on all components of a ComponentTree
7
+ # Execute a command on all given components
8
8
  module Executor
9
- def self.exec(components, command, output = $stdout, status_output = $stderr)
10
- components = Array(components)
11
- exec = if components.size == 1
12
- ComponentExec.new(components.first)
13
- else
14
- MultiExec.new(components)
15
- end
16
- exec.run(command, output: output, spin_output: status_output)
9
+ def self.exec(components:, command:, concurrency:, status_output:)
10
+ Concurrent.new(components, concurrency: concurrency, spin_output: status_output)
11
+ .exec(command)
17
12
  end
18
13
  end
19
14
  end
@@ -1,12 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "stringio"
4
+
3
5
  module CobraCommander
4
6
  module Output
5
7
  # Prints the tree in a nice tree form
6
8
  class AsciiTree
7
- SPACE = " "
8
- BAR = "│   "
9
- TEE = "├── "
9
+ SPACE = " "
10
+ BAR = "│   "
11
+ TEE = "├── "
10
12
  CORNER = "└── "
11
13
 
12
14
  def initialize(component)
@@ -20,7 +22,7 @@ module CobraCommander
20
22
  end.string
21
23
  end
22
24
 
23
- private
25
+ private
24
26
 
25
27
  def list_dependencies(io, component, outdents = [])
26
28
  component.dependencies.each do |dep|
@@ -17,7 +17,7 @@ module CobraCommander
17
17
  end
18
18
 
19
19
  private_class_method def self.extract_format(output)
20
- format = output[-3..-1]
20
+ format = output[-3..-1] # standard:disable Style/SlicingWithRange
21
21
  return format if %w[png dot].include?(format)
22
22
 
23
23
  raise ArgumentError, "output format must be 'png' or 'dot'"
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pastel"
4
+ require "tty-prompt"
5
+
6
+ module CobraCommander
7
+ module Output
8
+ # Runs an interactive output printer
9
+ class InteractivePrinter
10
+ pastel = Pastel.new
11
+ SUCCESS = "#{pastel.green("✔")} %s"
12
+ ERROR = "#{pastel.red("✖")} %s"
13
+ BYE = pastel.decorate("\n\n👋 Bye!", :white, :on_black, :bold).freeze
14
+
15
+ def self.run(contexts, output)
16
+ new(contexts).run(output)
17
+ end
18
+
19
+ def initialize(contexts)
20
+ @prompt = TTY::Prompt.new
21
+ @options = map_options(contexts)
22
+ end
23
+
24
+ def run(output)
25
+ selected_context = nil
26
+ loop do
27
+ selected_context = @prompt.select("Print output?", @options, default: @options.key(selected_context))
28
+ output.puts selected_context.output
29
+ end
30
+ rescue TTY::Reader::InputInterrupt
31
+ output.puts BYE
32
+ end
33
+
34
+ private
35
+
36
+ def map_options(contexts)
37
+ contexts.sort(&method(:sort_contexts))
38
+ .reduce({}) do |options, context|
39
+ template = context.success? ? SUCCESS : ERROR
40
+ options.merge format(template, context.component_name) => context
41
+ end
42
+ end
43
+
44
+ def sort_contexts(context_a, context_b)
45
+ if context_a.success? == context_b.success?
46
+ context_a.component_name <=> context_b.component_name
47
+ else
48
+ context_a.success? ? 1 : -1
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pastel"
4
+ require "tty-prompt"
5
+
6
+ module CobraCommander
7
+ module Output
8
+ # Prints the given CobraCommander::Executor::Context to [output] collection in markdown
9
+ module MarkdownPrinter
10
+ SUCCESS = "\n## ✔ %s\n"
11
+ ERROR = "\n## ✖ %s\n"
12
+ OUTPUT = "\n```\n$ %s\n\n%s\n```\n"
13
+
14
+ def self.run(contexts, output)
15
+ contexts.each do |context|
16
+ template = context.success? ? SUCCESS : ERROR
17
+
18
+ output.print format(template, context.component_name)
19
+ output.print format(OUTPUT, context.command, context.output)
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -3,3 +3,5 @@
3
3
  require_relative "output/flat_list"
4
4
  require_relative "output/ascii_tree"
5
5
  require_relative "output/graph_viz"
6
+ require_relative "output/interactive_printer"
7
+ require_relative "output/markdown_printer"
@@ -29,9 +29,9 @@ module CobraCommander
29
29
 
30
30
  def add_source(key, source)
31
31
  @root_component.add_source key, source.path, source.dependencies
32
- source.components.each do |path:, name:, dependencies:|
33
- @components[name] ||= Component.new(self, name)
34
- @components[name].add_source key, path, dependencies
32
+ source.components.each do |component|
33
+ @components[component[:name]] ||= Component.new(self, component[:name])
34
+ @components[component[:name]].add_source key, component[:path], component[:dependencies]
35
35
  end
36
36
  end
37
37
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CobraCommander
4
- VERSION = "0.10.0"
4
+ VERSION = "0.14.0"
5
5
  end
metadata CHANGED
@@ -1,16 +1,16 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cobra_commander
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.10.0
4
+ version: 0.14.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ben Langfeld
8
8
  - Garett Arrowood
9
9
  - Carlos Palhares
10
- autorequire:
10
+ autorequire:
11
11
  bindir: exe
12
12
  cert_chain: []
13
- date: 2021-02-25 00:00:00.000000000 Z
13
+ date: 2021-11-24 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: bundler
@@ -26,6 +26,20 @@ dependencies:
26
26
  - - ">="
27
27
  - !ruby/object:Gem::Version
28
28
  version: '0'
29
+ - !ruby/object:Gem::Dependency
30
+ name: concurrent-ruby
31
+ requirement: !ruby/object:Gem::Requirement
32
+ requirements:
33
+ - - "~>"
34
+ - !ruby/object:Gem::Version
35
+ version: '1.1'
36
+ type: :runtime
37
+ prerelease: false
38
+ version_requirements: !ruby/object:Gem::Requirement
39
+ requirements:
40
+ - - "~>"
41
+ - !ruby/object:Gem::Version
42
+ version: '1.1'
29
43
  - !ruby/object:Gem::Dependency
30
44
  name: ruby-graphviz
31
45
  requirement: !ruby/object:Gem::Requirement
@@ -44,36 +58,50 @@ dependencies:
44
58
  name: thor
45
59
  requirement: !ruby/object:Gem::Requirement
46
60
  requirements:
47
- - - "<"
48
- - !ruby/object:Gem::Version
49
- version: '2.0'
50
61
  - - ">="
51
62
  - !ruby/object:Gem::Version
52
63
  version: 0.18.1
64
+ - - "<"
65
+ - !ruby/object:Gem::Version
66
+ version: '2.0'
53
67
  type: :runtime
54
68
  prerelease: false
55
69
  version_requirements: !ruby/object:Gem::Requirement
56
70
  requirements:
57
- - - "<"
58
- - !ruby/object:Gem::Version
59
- version: '2.0'
60
71
  - - ">="
61
72
  - !ruby/object:Gem::Version
62
73
  version: 0.18.1
74
+ - - "<"
75
+ - !ruby/object:Gem::Version
76
+ version: '2.0'
63
77
  - !ruby/object:Gem::Dependency
64
78
  name: tty-command
65
79
  requirement: !ruby/object:Gem::Requirement
66
80
  requirements:
67
81
  - - "~>"
68
82
  - !ruby/object:Gem::Version
69
- version: 0.9.0
83
+ version: 0.10.0
84
+ type: :runtime
85
+ prerelease: false
86
+ version_requirements: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - "~>"
89
+ - !ruby/object:Gem::Version
90
+ version: 0.10.0
91
+ - !ruby/object:Gem::Dependency
92
+ name: tty-prompt
93
+ requirement: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - "~>"
96
+ - !ruby/object:Gem::Version
97
+ version: 0.23.1
70
98
  type: :runtime
71
99
  prerelease: false
72
100
  version_requirements: !ruby/object:Gem::Requirement
73
101
  requirements:
74
102
  - - "~>"
75
103
  - !ruby/object:Gem::Version
76
- version: 0.9.0
104
+ version: 0.23.1
77
105
  - !ruby/object:Gem::Dependency
78
106
  name: tty-spinner
79
107
  requirement: !ruby/object:Gem::Requirement
@@ -148,16 +176,16 @@ dependencies:
148
176
  name: rake
149
177
  requirement: !ruby/object:Gem::Requirement
150
178
  requirements:
151
- - - "~>"
179
+ - - ">="
152
180
  - !ruby/object:Gem::Version
153
- version: '10.0'
181
+ version: 12.3.3
154
182
  type: :development
155
183
  prerelease: false
156
184
  version_requirements: !ruby/object:Gem::Requirement
157
185
  requirements:
158
- - - "~>"
186
+ - - ">="
159
187
  - !ruby/object:Gem::Version
160
- version: '10.0'
188
+ version: 12.3.3
161
189
  - !ruby/object:Gem::Dependency
162
190
  name: rspec
163
191
  requirement: !ruby/object:Gem::Requirement
@@ -173,19 +201,19 @@ dependencies:
173
201
  - !ruby/object:Gem::Version
174
202
  version: '3.5'
175
203
  - !ruby/object:Gem::Dependency
176
- name: rubocop
204
+ name: standard
177
205
  requirement: !ruby/object:Gem::Requirement
178
206
  requirements:
179
- - - '='
207
+ - - ">="
180
208
  - !ruby/object:Gem::Version
181
- version: 0.88.0
209
+ version: 1.3.0
182
210
  type: :development
183
211
  prerelease: false
184
212
  version_requirements: !ruby/object:Gem::Requirement
185
213
  requirements:
186
- - - '='
214
+ - - ">="
187
215
  - !ruby/object:Gem::Version
188
- version: 0.88.0
216
+ version: 1.3.0
189
217
  description: |
190
218
  Tools for working with Component Based Rails Apps (see http://shageman.github.io/cbra.info/).
191
219
  Includes tools for graphing the components of an app and their relationships, as well as selectively
@@ -201,6 +229,7 @@ extra_rdoc_files: []
201
229
  files:
202
230
  - ".editorconfig"
203
231
  - ".github/workflows/ci.yml"
232
+ - ".github/workflows/release.yml"
204
233
  - ".gitignore"
205
234
  - ".rspec"
206
235
  - ".rubocop.yml"
@@ -222,6 +251,7 @@ files:
222
251
  - lib/cobra_commander/affected.rb
223
252
  - lib/cobra_commander/change.rb
224
253
  - lib/cobra_commander/cli.rb
254
+ - lib/cobra_commander/cli/filters.rb
225
255
  - lib/cobra_commander/component.rb
226
256
  - lib/cobra_commander/dependencies.rb
227
257
  - lib/cobra_commander/dependencies/bundler.rb
@@ -229,12 +259,14 @@ files:
229
259
  - lib/cobra_commander/dependencies/yarn/package_repo.rb
230
260
  - lib/cobra_commander/dependencies/yarn_workspace.rb
231
261
  - lib/cobra_commander/executor.rb
232
- - lib/cobra_commander/executor/component_exec.rb
233
- - lib/cobra_commander/executor/multi_exec.rb
262
+ - lib/cobra_commander/executor/concurrent.rb
263
+ - lib/cobra_commander/executor/context.rb
234
264
  - lib/cobra_commander/output.rb
235
265
  - lib/cobra_commander/output/ascii_tree.rb
236
266
  - lib/cobra_commander/output/flat_list.rb
237
267
  - lib/cobra_commander/output/graph_viz.rb
268
+ - lib/cobra_commander/output/interactive_printer.rb
269
+ - lib/cobra_commander/output/markdown_printer.rb
238
270
  - lib/cobra_commander/umbrella.rb
239
271
  - lib/cobra_commander/version.rb
240
272
  - renovate.json
@@ -242,7 +274,7 @@ homepage: http://tech.powerhrg.com/cobra_commander/
242
274
  licenses:
243
275
  - MIT
244
276
  metadata: {}
245
- post_install_message:
277
+ post_install_message:
246
278
  rdoc_options: []
247
279
  require_paths:
248
280
  - lib
@@ -257,9 +289,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
257
289
  - !ruby/object:Gem::Version
258
290
  version: '0'
259
291
  requirements: []
260
- rubyforge_project:
261
- rubygems_version: 2.7.3
262
- signing_key:
292
+ rubygems_version: 3.0.3
293
+ signing_key:
263
294
  specification_version: 4
264
295
  summary: Tools for working with Component Based Rails Apps
265
296
  test_files: []
@@ -1,33 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "tty-command"
4
-
5
- module CobraCommander
6
- module Executor
7
- # Execute a command on a single component
8
- class ComponentExec
9
- def initialize(component)
10
- @component = component
11
- end
12
-
13
- def run(command, output: $stdout, **cmd_options)
14
- tty = TTY::Command.new(pty: true, printer: :quiet, output: output)
15
- isolate_bundle do
16
- @component.root_paths.all? do |path|
17
- tty.run!(command, chdir: path, **cmd_options).success?
18
- end
19
- end
20
- end
21
-
22
- private
23
-
24
- def isolate_bundle(&block)
25
- if Bundler.respond_to?(:with_unbundled_env)
26
- Bundler.with_unbundled_env(&block)
27
- else
28
- Bundler.with_clean_env(&block)
29
- end
30
- end
31
- end
32
- end
33
- end
@@ -1,46 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "tty-spinner"
4
- require "stringio"
5
-
6
- require_relative "component_exec"
7
-
8
- module CobraCommander
9
- module Executor
10
- # Executes a command on multiple components simultaniously
11
- class MultiExec
12
- def initialize(components)
13
- @components = components
14
- end
15
-
16
- def run(command, output: $stdout, spin_output: $stderr, only_output_on_error: true, **cmd_options)
17
- cmmd_output = StringIO.new
18
- multi = TTY::Spinner::Multi.new("Running #{command}", output: spin_output)
19
- @components.each do |component|
20
- component_exec(multi, component, command, only_output_on_error: only_output_on_error,
21
- stderr: :stdout, output: cmmd_output,
22
- **cmd_options)
23
- end
24
- multi.auto_spin
25
- output << cmmd_output.string
26
- true
27
- end
28
-
29
- private
30
-
31
- def component_exec(multi, component, command, **options)
32
- exec = ComponentExec.new(component)
33
- multi.register(*spinner(component.name)) do |spin|
34
- exec.run(command, **options) ? spin.success : spin.error
35
- end
36
- end
37
-
38
- def spinner(title)
39
- pastel = Pastel.new
40
- [":spinner #{title}", { format: :bouncing,
41
- success_mark: pastel.green("[DONE]"),
42
- error_mark: pastel.red("[ERROR]"), },]
43
- end
44
- end
45
- end
46
- end