cobra_commander 0.9.2 → 0.13.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 +4 -4
- data/.github/workflows/ci.yml +19 -11
- data/.github/workflows/release.yml +20 -0
- data/.gitignore +3 -0
- data/CHANGELOG.md +30 -1
- data/Gemfile +0 -1
- data/README.md +3 -4
- data/Rakefile +3 -5
- data/cobra_commander.gemspec +19 -17
- data/gemfiles/bundler1.gemfile +7 -0
- data/gemfiles/bundler2.gemfile +7 -0
- data/lib/cobra_commander/affected.rb +4 -7
- data/lib/cobra_commander/change.rb +3 -3
- data/lib/cobra_commander/cli/filters.rb +30 -0
- data/lib/cobra_commander/cli.rb +24 -27
- data/lib/cobra_commander/component.rb +2 -2
- data/lib/cobra_commander/dependencies/bundler.rb +4 -4
- data/lib/cobra_commander/dependencies/yarn/package.rb +2 -2
- data/lib/cobra_commander/dependencies/yarn_workspace.rb +2 -2
- data/lib/cobra_commander/executor/concurrent.rb +51 -0
- data/lib/cobra_commander/executor/context.rb +49 -0
- data/lib/cobra_commander/executor.rb +6 -11
- data/lib/cobra_commander/output/ascii_tree.rb +6 -4
- data/lib/cobra_commander/output/graph_viz.rb +1 -1
- data/lib/cobra_commander/output/interactive_printer.rb +48 -0
- data/lib/cobra_commander/output/markdown_printer.rb +24 -0
- data/lib/cobra_commander/output.rb +2 -0
- data/lib/cobra_commander/umbrella.rb +3 -3
- data/lib/cobra_commander/version.rb +1 -1
- metadata +62 -29
- data/lib/cobra_commander/executor/component_exec.rb +0 -33
- data/lib/cobra_commander/executor/multi_exec.rb +0 -46
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4eb007805f5f11954ed14e349409c7486fc6b52d0a041f8066c75c47f8d045b8
|
4
|
+
data.tar.gz: 68ac383fb087d118a276e5aed0199b51fd09b05c110d7ff4539f18097b1ea367
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6a5f69e411bf49bd1de717934f27c0d364378e07ce1728855b2783de0c73dff791be433565b689656d4481b2ad294086fc43118beafc16ca445a2c91ea7916c9
|
7
|
+
data.tar.gz: afdf327649c953bc155f5e01a6db9f4cef12ed625f7c791534e7ae65ac0d05c2260bfd20b03f382eac9aaee9f2e84612806c050327491c2f806223d7e612aa67
|
data/.github/workflows/ci.yml
CHANGED
@@ -1,24 +1,33 @@
|
|
1
1
|
name: CI
|
2
2
|
|
3
|
-
on:
|
3
|
+
on: push
|
4
4
|
|
5
5
|
jobs:
|
6
6
|
test:
|
7
7
|
name: Tests
|
8
8
|
runs-on: ubuntu-latest
|
9
9
|
strategy:
|
10
|
+
fail-fast: false
|
10
11
|
matrix:
|
11
12
|
ruby:
|
12
13
|
- "2.5"
|
13
14
|
- "2.6"
|
15
|
+
- "2.7"
|
16
|
+
- "3.0"
|
17
|
+
bundler:
|
18
|
+
- "1"
|
19
|
+
- "2"
|
20
|
+
env:
|
21
|
+
BUNDLE_GEMFILE: gemfiles/bundler${{ matrix.bundler }}.gemfile
|
14
22
|
steps:
|
15
23
|
- uses: actions/checkout@v2
|
16
|
-
-
|
17
|
-
uses: ruby/setup-ruby@v1
|
24
|
+
- uses: ruby/setup-ruby@v1
|
18
25
|
with:
|
19
26
|
ruby-version: ${{ matrix.ruby }}
|
20
|
-
bundler:
|
27
|
+
bundler: ${{ matrix.bundler }}
|
21
28
|
bundler-cache: true
|
29
|
+
- name: Install bundler v1 # Even if we're testing bundler v2, we need v1 available because the fixture components have it in their Gemfile.locks
|
30
|
+
run: gem install bundler -v "~> 1" --no-document
|
22
31
|
- name: Install Graphviz
|
23
32
|
run: sudo apt -qq install graphviz
|
24
33
|
- name: Run tests
|
@@ -28,11 +37,10 @@ jobs:
|
|
28
37
|
runs-on: ubuntu-latest
|
29
38
|
steps:
|
30
39
|
- uses: actions/checkout@v2
|
31
|
-
-
|
32
|
-
uses: reviewdog/action-rubocop@v1
|
40
|
+
- uses: ruby/setup-ruby@v1
|
33
41
|
with:
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
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/.gitignore
CHANGED
data/CHANGELOG.md
CHANGED
@@ -2,9 +2,38 @@
|
|
2
2
|
|
3
3
|
## Unreleased
|
4
4
|
|
5
|
+
## Version 0.13.0 - 2021-10-21
|
6
|
+
|
7
|
+
* Sorts the interactive UI alphabetically with failures first [#66](https://github.com/powerhome/cobra_commander/pull/66)
|
8
|
+
* Switch linter to standardrb [#67](https://github.com/powerhome/cobra_commander/pull/67)
|
9
|
+
|
10
|
+
## Version 0.12.0 - 2021-09-21
|
11
|
+
|
12
|
+
* Add interactive UI and Markdown output to `cobra exec` [#63](https://github.com/powerhome/cobra_commander/pull/63)
|
13
|
+
* Add `--self` and `--no-self` filters to `cobra exec` and `cobra ls`, defaults to `--self` [#61](https://github.com/powerhome/cobra_commander/pull/61)
|
14
|
+
|
15
|
+
## Version 0.11.0 - 2021-04-23
|
16
|
+
|
17
|
+
* Add concurrency limit to multi exec [#57](https://github.com/powerhome/cobra_commander/pull/57)
|
18
|
+
* Ruby 3.0 compatibility [#55](https://github.com/powerhome/cobra_commander/pull/55)
|
19
|
+
|
20
|
+
## Version 0.10.0 - 2021-02-25
|
21
|
+
|
22
|
+
* Add support for Bundler 2 [#54](https://github.com/powerhome/cobra_commander/pull/54)
|
23
|
+
* Add support for Ruby 2.7 [#53](https://github.com/powerhome/cobra_commander/pull/53)
|
24
|
+
|
25
|
+
## Version 0.9.2 -
|
26
|
+
|
27
|
+
* Another fix for binstubs [#51](https://github.com/powerhome/cobra_commander/pull/51)
|
28
|
+
|
29
|
+
## Version 0.9.1 -
|
30
|
+
|
31
|
+
* Replace bundler encapsulation violation by LockfileParser [#48](https://github.com/powerhome/cobra_commander/pull/48)
|
32
|
+
* Fix bundle binstubs [#50](https://github.com/powerhome/cobra_commander/pull/50)
|
33
|
+
|
5
34
|
## Version 0.9.0 - 2020-08-26
|
6
35
|
|
7
|
-
* Add support for parallel task execution to `cobra exec
|
36
|
+
* Add support for parallel task execution to `cobra exec` [#49](https://github.com/powerhome/cobra_commander/pull/49)
|
8
37
|
|
9
38
|
## Version 0.8.1 - 2020-07-29
|
10
39
|
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -2,11 +2,10 @@
|
|
2
2
|
|
3
3
|
[](https://rubygems.org/gems/cobra_commander)
|
4
4
|
[](https://rubygems.org/gems/cobra_commander)
|
5
|
-
[](https://gemnasium.com/github.com/powerhome/cobra_commander)
|
5
|
+
[](https://github.com/powerhome/cobra_commander/actions/workflows/ci.yml)
|
6
|
+
[](https://codeclimate.com/github/powerhome/cobra_commander/maintainability)
|
8
7
|
|
9
|
-
Tools for working with Component Based Rails Apps (see
|
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
|
-
|
9
|
-
RuboCop::RakeTask.new
|
7
|
+
RSpec::Core::RakeTask.new(:spec)
|
10
8
|
|
11
|
-
task default: %i[spec
|
9
|
+
task default: %i[spec standard]
|
data/cobra_commander.gemspec
CHANGED
@@ -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
|
9
|
-
spec.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
|
21
|
-
spec.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
|
27
|
-
spec.license
|
26
|
+
spec.homepage = "http://tech.powerhrg.com/cobra_commander/"
|
27
|
+
spec.license = "MIT"
|
28
28
|
|
29
|
-
spec.files
|
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
|
33
|
-
spec.executables
|
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
|
-
spec.add_dependency "bundler"
|
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.
|
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
|
-
spec.add_development_dependency "bundler"
|
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", "
|
48
|
+
spec.add_development_dependency "rake", ">= 12.3.3"
|
47
49
|
spec.add_development_dependency "rspec", "~> 3.5"
|
48
|
-
spec.add_development_dependency "
|
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
|
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
|
-
|
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
|
-
|
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,30 @@
|
|
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, try one of `cobra ls`") || exit(1)
|
18
|
+
end
|
19
|
+
|
20
|
+
def components_filtered(component_name)
|
21
|
+
return umbrella.components unless component_name
|
22
|
+
|
23
|
+
component = find_component(component_name)
|
24
|
+
components = options.self ? [component] : []
|
25
|
+
components.concat component.deep_dependencies if options.dependencies
|
26
|
+
components.concat component.deep_dependents if options.dependents
|
27
|
+
components
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
data/lib/cobra_commander/cli.rb
CHANGED
@@ -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
|
-
|
26
|
-
|
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
|
-
|
38
|
-
|
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
|
-
|
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
|
@@ -11,8 +11,8 @@ module CobraCommander
|
|
11
11
|
attr_reader :path
|
12
12
|
|
13
13
|
def initialize(root)
|
14
|
-
@root = root
|
15
|
-
@path =
|
14
|
+
@root = Pathname.new(root)
|
15
|
+
@path = @root.join("Gemfile.lock").realpath
|
16
16
|
end
|
17
17
|
|
18
18
|
def dependencies
|
@@ -21,11 +21,11 @@ module CobraCommander
|
|
21
21
|
|
22
22
|
def components
|
23
23
|
components_source.specs.map do |spec|
|
24
|
-
{
|
24
|
+
{path: spec.loaded_from, name: spec.name, dependencies: spec.dependencies.map(&:name)}
|
25
25
|
end
|
26
26
|
end
|
27
27
|
|
28
|
-
|
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
|
-
|
27
|
+
.merge(json.fetch("devDependencies", {}))
|
28
28
|
end
|
29
29
|
|
30
|
-
|
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
|
-
{
|
31
|
+
{path: spec.path, name: untag(spec.name), dependencies: spec.dependencies.keys.map(&method(:untag))}
|
32
32
|
end
|
33
33
|
end
|
34
34
|
|
35
|
-
|
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/
|
4
|
-
require_relative "executor/
|
3
|
+
require_relative "executor/context"
|
4
|
+
require_relative "executor/concurrent"
|
5
5
|
|
6
6
|
module CobraCommander
|
7
|
-
# Execute
|
7
|
+
# Execute a command on all given components
|
8
8
|
module Executor
|
9
|
-
def self.exec(components
|
10
|
-
components
|
11
|
-
|
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
|
-
|
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,48 @@
|
|
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 = contexts.sort(&method(:sort_contexts))
|
22
|
+
.reduce({}) do |options, context|
|
23
|
+
template = context.success? ? SUCCESS : ERROR
|
24
|
+
options.merge format(template, context.component_name) => context
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def run(output)
|
29
|
+
loop do
|
30
|
+
context = @prompt.select("Print output?", @options)
|
31
|
+
output.puts context.output
|
32
|
+
end
|
33
|
+
rescue TTY::Reader::InputInterrupt
|
34
|
+
output.puts BYE
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def sort_contexts(context_a, context_b)
|
40
|
+
if context_a.success? == context_b.success?
|
41
|
+
context_a.component_name <=> context_b.component_name
|
42
|
+
else
|
43
|
+
context_a.success? ? 1 : -1
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
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
|
@@ -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 |
|
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
|
|
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.
|
4
|
+
version: 0.13.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ben Langfeld
|
@@ -10,22 +10,36 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: exe
|
12
12
|
cert_chain: []
|
13
|
-
date:
|
13
|
+
date: 2021-10-21 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: bundler
|
17
|
+
requirement: !ruby/object:Gem::Requirement
|
18
|
+
requirements:
|
19
|
+
- - ">="
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
requirements:
|
26
|
+
- - ">="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
version: '0'
|
29
|
+
- !ruby/object:Gem::Dependency
|
30
|
+
name: concurrent-ruby
|
17
31
|
requirement: !ruby/object:Gem::Requirement
|
18
32
|
requirements:
|
19
33
|
- - "~>"
|
20
34
|
- !ruby/object:Gem::Version
|
21
|
-
version: '1.
|
35
|
+
version: '1.1'
|
22
36
|
type: :runtime
|
23
37
|
prerelease: false
|
24
38
|
version_requirements: !ruby/object:Gem::Requirement
|
25
39
|
requirements:
|
26
40
|
- - "~>"
|
27
41
|
- !ruby/object:Gem::Version
|
28
|
-
version: '1.
|
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.
|
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.
|
104
|
+
version: 0.23.1
|
77
105
|
- !ruby/object:Gem::Dependency
|
78
106
|
name: tty-spinner
|
79
107
|
requirement: !ruby/object:Gem::Requirement
|
@@ -106,16 +134,16 @@ dependencies:
|
|
106
134
|
name: bundler
|
107
135
|
requirement: !ruby/object:Gem::Requirement
|
108
136
|
requirements:
|
109
|
-
- - "
|
137
|
+
- - ">="
|
110
138
|
- !ruby/object:Gem::Version
|
111
|
-
version: '
|
139
|
+
version: '0'
|
112
140
|
type: :development
|
113
141
|
prerelease: false
|
114
142
|
version_requirements: !ruby/object:Gem::Requirement
|
115
143
|
requirements:
|
116
|
-
- - "
|
144
|
+
- - ">="
|
117
145
|
- !ruby/object:Gem::Version
|
118
|
-
version: '
|
146
|
+
version: '0'
|
119
147
|
- !ruby/object:Gem::Dependency
|
120
148
|
name: guard-rspec
|
121
149
|
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:
|
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:
|
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:
|
204
|
+
name: standard
|
177
205
|
requirement: !ruby/object:Gem::Requirement
|
178
206
|
requirements:
|
179
|
-
- -
|
207
|
+
- - ">="
|
180
208
|
- !ruby/object:Gem::Version
|
181
|
-
version:
|
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:
|
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"
|
@@ -216,10 +245,13 @@ files:
|
|
216
245
|
- bin/setup
|
217
246
|
- cobra_commander.gemspec
|
218
247
|
- exe/cobra
|
248
|
+
- gemfiles/bundler1.gemfile
|
249
|
+
- gemfiles/bundler2.gemfile
|
219
250
|
- lib/cobra_commander.rb
|
220
251
|
- lib/cobra_commander/affected.rb
|
221
252
|
- lib/cobra_commander/change.rb
|
222
253
|
- lib/cobra_commander/cli.rb
|
254
|
+
- lib/cobra_commander/cli/filters.rb
|
223
255
|
- lib/cobra_commander/component.rb
|
224
256
|
- lib/cobra_commander/dependencies.rb
|
225
257
|
- lib/cobra_commander/dependencies/bundler.rb
|
@@ -227,12 +259,14 @@ files:
|
|
227
259
|
- lib/cobra_commander/dependencies/yarn/package_repo.rb
|
228
260
|
- lib/cobra_commander/dependencies/yarn_workspace.rb
|
229
261
|
- lib/cobra_commander/executor.rb
|
230
|
-
- lib/cobra_commander/executor/
|
231
|
-
- lib/cobra_commander/executor/
|
262
|
+
- lib/cobra_commander/executor/concurrent.rb
|
263
|
+
- lib/cobra_commander/executor/context.rb
|
232
264
|
- lib/cobra_commander/output.rb
|
233
265
|
- lib/cobra_commander/output/ascii_tree.rb
|
234
266
|
- lib/cobra_commander/output/flat_list.rb
|
235
267
|
- lib/cobra_commander/output/graph_viz.rb
|
268
|
+
- lib/cobra_commander/output/interactive_printer.rb
|
269
|
+
- lib/cobra_commander/output/markdown_printer.rb
|
236
270
|
- lib/cobra_commander/umbrella.rb
|
237
271
|
- lib/cobra_commander/version.rb
|
238
272
|
- renovate.json
|
@@ -255,8 +289,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
255
289
|
- !ruby/object:Gem::Version
|
256
290
|
version: '0'
|
257
291
|
requirements: []
|
258
|
-
|
259
|
-
rubygems_version: 2.7.3
|
292
|
+
rubygems_version: 3.0.3
|
260
293
|
signing_key:
|
261
294
|
specification_version: 4
|
262
295
|
summary: Tools for working with Component Based Rails Apps
|
@@ -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
|