cobra_commander 0.10.0 → 0.14.0

Sign up to get free protection for your applications and to get access to all the features.
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