cobra_commander 0.15.0 → 1.0.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/.gitignore +0 -2
- data/.rubocop.yml +0 -5
- data/Gemfile +7 -0
- data/cobra_commander.gemspec +6 -1
- data/doc/dependency_decisions.yml +9 -0
- data/docs/CHANGELOG.md +19 -2
- data/docs/README.md +3 -129
- data/exe/cobra +5 -0
- data/lib/cobra_commander/affected.rb +19 -51
- data/lib/cobra_commander/cli/filters.rb +1 -3
- data/lib/cobra_commander/{output → cli/output}/ascii_tree.rb +7 -5
- data/lib/cobra_commander/cli/output/change.rb +91 -0
- data/lib/cobra_commander/cli/output/dot_graph.rb +21 -0
- data/lib/cobra_commander/cli.rb +58 -30
- data/lib/cobra_commander/component.rb +6 -10
- data/lib/cobra_commander/executor/command.rb +75 -0
- data/lib/cobra_commander/executor/execution.rb +52 -0
- data/lib/cobra_commander/executor/interactive_printer.rb +53 -0
- data/lib/cobra_commander/executor/job.rb +51 -0
- data/lib/cobra_commander/executor/markdown_printer.rb +21 -0
- data/lib/cobra_commander/executor/package_criteria.rb +21 -0
- data/lib/cobra_commander/executor/script.rb +45 -0
- data/lib/cobra_commander/executor/spinners.rb +40 -0
- data/lib/cobra_commander/executor.rb +37 -6
- data/lib/cobra_commander/git_changed.rb +16 -11
- data/lib/cobra_commander/package.rb +21 -0
- data/lib/cobra_commander/registry.rb +29 -0
- data/lib/cobra_commander/source.rb +37 -0
- data/lib/cobra_commander/umbrella.rb +70 -17
- data/lib/cobra_commander/version.rb +1 -1
- data/lib/cobra_commander.rb +10 -16
- data/mkdocs.yml +1 -1
- metadata +37 -43
- data/.editorconfig +0 -11
- data/.github/workflows/ci.yml +0 -44
- data/.github/workflows/release.yml +0 -20
- data/.rubocop_todo.yml +0 -15
- data/LICENSE.txt +0 -21
- data/_config.yml +0 -1
- data/docs/CODE_OF_CONDUCT.md +0 -74
- data/gemfiles/bundler1.gemfile +0 -7
- data/gemfiles/bundler2.gemfile +0 -7
- data/lib/cobra_commander/change.rb +0 -87
- data/lib/cobra_commander/dependencies/bundler/package.rb +0 -17
- data/lib/cobra_commander/dependencies/bundler.rb +0 -44
- data/lib/cobra_commander/dependencies/yarn/package.rb +0 -21
- data/lib/cobra_commander/dependencies/yarn.rb +0 -40
- data/lib/cobra_commander/dependencies.rb +0 -4
- data/lib/cobra_commander/executor/concurrent.rb +0 -51
- data/lib/cobra_commander/executor/context.rb +0 -49
- data/lib/cobra_commander/output/flat_list.rb +0 -16
- data/lib/cobra_commander/output/graph_viz.rb +0 -27
- data/lib/cobra_commander/output/interactive_printer.rb +0 -53
- data/lib/cobra_commander/output/markdown_printer.rb +0 -24
- data/lib/cobra_commander/output.rb +0 -7
- data/portal.yml +0 -12
- data/renovate.json +0 -8
data/lib/cobra_commander/cli.rb
CHANGED
@@ -5,22 +5,21 @@ require "fileutils"
|
|
5
5
|
require "concurrent-ruby"
|
6
6
|
|
7
7
|
require "cobra_commander"
|
8
|
-
require "cobra_commander/affected"
|
9
|
-
require "cobra_commander/change"
|
10
|
-
require "cobra_commander/git_changed"
|
11
|
-
require "cobra_commander/executor"
|
12
|
-
require "cobra_commander/output"
|
13
8
|
|
14
9
|
module CobraCommander
|
15
10
|
# Implements the tool's CLI
|
16
11
|
class CLI < Thor
|
17
|
-
|
12
|
+
require_relative "cli/filters"
|
13
|
+
require_relative "cli/output/ascii_tree"
|
14
|
+
require_relative "cli/output/change"
|
15
|
+
require_relative "cli/output/dot_graph"
|
18
16
|
|
19
17
|
DEFAULT_CONCURRENCY = (Concurrent.processor_count / 2.0).ceil
|
20
18
|
|
21
19
|
class_option :app, default: Dir.pwd, aliases: "-a", type: :string
|
22
|
-
|
23
|
-
|
20
|
+
Source.all.keys.each do |key|
|
21
|
+
class_option key, default: false, type: :boolean, desc: "Consider only the #{key} dependency graph"
|
22
|
+
end
|
24
23
|
|
25
24
|
desc "version", "Prints version"
|
26
25
|
def version
|
@@ -33,11 +32,31 @@ module CobraCommander
|
|
33
32
|
method_option :total, type: :boolean, aliases: "-t", desc: "Prints the total count of components"
|
34
33
|
def ls(components = nil)
|
35
34
|
components = components_filtered(components)
|
36
|
-
puts options.total ? components.size :
|
35
|
+
puts options.total ? components.size : components.map(&:name).sort
|
36
|
+
end
|
37
|
+
|
38
|
+
desc "exec [components] <script>", "Executes the script in the context of a given component or set thereof. " \
|
39
|
+
"Defaults to all components."
|
40
|
+
filter_options dependents: "Run the script on each dependent of a given component",
|
41
|
+
dependencies: "Run the script 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"
|
47
|
+
def exec(script_or_components, script = nil)
|
48
|
+
jobs = CobraCommander::Executor::Script.for(
|
49
|
+
components_filtered(script && script_or_components),
|
50
|
+
script || script_or_components
|
51
|
+
)
|
52
|
+
output_mode = options.interactive && jobs.count > 1 ? :interactive : :markdown
|
53
|
+
execution = CobraCommander::Executor.execute(jobs: jobs, workers: options.concurrency,
|
54
|
+
output_mode: output_mode, output: $stdout, status_output: $stderr)
|
55
|
+
exit 1 unless execution.success?
|
37
56
|
end
|
38
57
|
|
39
|
-
desc "
|
40
|
-
|
58
|
+
desc "cmd [components] <command>", "Executes the command in the context of a given component or set thereof. " \
|
59
|
+
"Defaults to all components."
|
41
60
|
filter_options dependents: "Run the command on each dependent of a given component",
|
42
61
|
dependencies: "Run the command on each dependency of a given component"
|
43
62
|
method_option :concurrency, type: :numeric, default: DEFAULT_CONCURRENCY, aliases: "-c",
|
@@ -45,49 +64,58 @@ module CobraCommander
|
|
45
64
|
method_option :interactive, type: :boolean, default: true, aliases: "-i",
|
46
65
|
desc: "Runs in interactive mode to allow the user to inspect the output of each " \
|
47
66
|
"component"
|
48
|
-
def
|
49
|
-
|
50
|
-
|
51
|
-
command
|
52
|
-
concurrency: options.concurrency, status_output: $stderr
|
67
|
+
def cmd(command_or_components, command = nil)
|
68
|
+
jobs = CobraCommander::Executor::Command.for(
|
69
|
+
components_filtered(command && command_or_components),
|
70
|
+
command || command_or_components
|
53
71
|
)
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
end
|
72
|
+
output_mode = options.interactive && jobs.count > 1 ? :interactive : :markdown
|
73
|
+
execution = CobraCommander::Executor.execute(jobs: jobs, workers: options.concurrency,
|
74
|
+
output_mode: output_mode, output: $stdout, status_output: $stderr)
|
75
|
+
exit 1 unless execution.success?
|
59
76
|
end
|
60
77
|
|
61
78
|
desc "tree [component]", "Prints the dependency tree of a given component or umbrella"
|
62
79
|
def tree(component = nil)
|
63
|
-
|
64
|
-
puts
|
80
|
+
components = component ? [find_component(component)] : umbrella.components
|
81
|
+
puts Output::AsciiTree.new(components).to_s
|
65
82
|
end
|
66
83
|
|
67
84
|
desc "graph [component]", "Outputs a graph of a given component or umbrella"
|
68
|
-
method_option :output, default: File.join(Dir.pwd, "output.
|
69
|
-
desc: "Output file, accepts .png or .dot"
|
85
|
+
method_option :output, default: File.join(Dir.pwd, "output.dot"), aliases: "-o"
|
70
86
|
def graph(component = nil)
|
71
|
-
|
72
|
-
|
73
|
-
|
87
|
+
output = File.open(options.output, "w")
|
88
|
+
Output::DotGraph.generate(
|
89
|
+
component ? [find_component(component)] : umbrella.components,
|
90
|
+
output
|
74
91
|
)
|
75
92
|
puts "Graph generated at #{options.output}"
|
76
93
|
rescue ArgumentError => e
|
77
94
|
error e.message
|
95
|
+
ensure
|
96
|
+
output&.close
|
78
97
|
end
|
79
98
|
|
80
99
|
desc "changes [--results=RESULTS] [--branch=BRANCH]", "Prints list of changed files"
|
81
100
|
method_option :results, default: "test", aliases: "-r", desc: "Accepts test, full, name or json"
|
82
101
|
method_option :branch, default: "master", aliases: "-b", desc: "Specified target to calculate against"
|
83
102
|
def changes
|
84
|
-
Change.new(umbrella, options.results, options.branch).run!
|
103
|
+
Output::Change.new(umbrella, options.results, options.branch).run!
|
85
104
|
end
|
86
105
|
|
87
106
|
private
|
88
107
|
|
89
108
|
def umbrella
|
90
|
-
|
109
|
+
selector = Source.all.keys.reduce({}) do |sel, key|
|
110
|
+
sel.merge(key => options.public_send(key))
|
111
|
+
end
|
112
|
+
@umbrella ||= CobraCommander::Umbrella.new(
|
113
|
+
options.app,
|
114
|
+
**selector
|
115
|
+
)
|
116
|
+
rescue ::CobraCommander::Source::Error => e
|
117
|
+
error(e.message)
|
118
|
+
exit(1)
|
91
119
|
end
|
92
120
|
end
|
93
121
|
end
|
@@ -9,22 +9,20 @@ module CobraCommander
|
|
9
9
|
@umbrella = umbrella
|
10
10
|
@name = name
|
11
11
|
@dependency_names = []
|
12
|
-
@packages =
|
12
|
+
@packages = []
|
13
13
|
end
|
14
14
|
|
15
|
-
def add_package(
|
16
|
-
@packages
|
15
|
+
def add_package(package)
|
16
|
+
@packages << package
|
17
17
|
@dependency_names |= package.dependencies
|
18
18
|
end
|
19
19
|
|
20
20
|
def root_paths
|
21
|
-
@packages.
|
22
|
-
File.dirname(package.path)
|
23
|
-
end.uniq
|
21
|
+
@packages.map(&:path).uniq
|
24
22
|
end
|
25
23
|
|
26
24
|
def inspect
|
27
|
-
"#<CobraCommander::Component:#{object_id} #{name} dependencies=#{dependencies.map(&:name)} packages=#{
|
25
|
+
"#<CobraCommander::Component:#{object_id} #{name} dependencies=#{dependencies.map(&:name)} packages=#{packages}>"
|
28
26
|
end
|
29
27
|
|
30
28
|
def deep_dependents
|
@@ -46,9 +44,7 @@ module CobraCommander
|
|
46
44
|
end
|
47
45
|
|
48
46
|
def dependencies
|
49
|
-
@dependencies ||= @dependency_names.sort
|
50
|
-
.map(&@umbrella.method(:find))
|
51
|
-
.compact
|
47
|
+
@dependencies ||= @dependency_names.sort.filter_map { |name| @umbrella.find(name) }
|
52
48
|
end
|
53
49
|
end
|
54
50
|
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "./job"
|
4
|
+
require_relative "./package_criteria"
|
5
|
+
|
6
|
+
module CobraCommander
|
7
|
+
module Executor
|
8
|
+
class Command
|
9
|
+
include ::CobraCommander::Executor::Job
|
10
|
+
include ::CobraCommander::Executor::PackageCriteria
|
11
|
+
|
12
|
+
SKIP_UNEXISTING = "Command %s does not exist. Check your cobra.yml for existing commands in %s."
|
13
|
+
SKIP_CRITERIA = "Package %s does not match criteria."
|
14
|
+
|
15
|
+
# Builds the given commands in all packages of all components.
|
16
|
+
#
|
17
|
+
# @param components [Enumerable<CobraCommander::Component>] the target components
|
18
|
+
# @param command [String] shell script to run from the directories of the component's packages
|
19
|
+
# @return [Array<CobraCommander::Executor::Command>]
|
20
|
+
def self.for(components, command)
|
21
|
+
components.flat_map(&:packages).map do |package|
|
22
|
+
new(package, command)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# Calls the commands sequentially, stopping ony if an :error happens.
|
27
|
+
#
|
28
|
+
# If one of the commands skips, the result will be :success.
|
29
|
+
#
|
30
|
+
# @param commands [Enumerable<CobraCommander::Component>] the target components
|
31
|
+
# @return [Array<CobraCommander::Executor::Command>]
|
32
|
+
# @see CobraCommander::Executor::Job
|
33
|
+
def self.join(commands)
|
34
|
+
commands.lazy.map(&:call).reduce do |(_, prev_output), (result, output)|
|
35
|
+
new_output = [prev_output&.strip, output&.strip].join("\n")
|
36
|
+
return [:error, new_output] if result == :error
|
37
|
+
|
38
|
+
[:success, new_output]
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def initialize(package, command)
|
43
|
+
@package = package
|
44
|
+
@command = command
|
45
|
+
end
|
46
|
+
|
47
|
+
def to_s
|
48
|
+
"#{@package.name} (#{@package.key})"
|
49
|
+
end
|
50
|
+
|
51
|
+
# @see CobraCommander::Executor::Job
|
52
|
+
def call
|
53
|
+
command = @package.source.config&.dig("commands", @command)
|
54
|
+
case command
|
55
|
+
when Array then run_multiple(@package, command)
|
56
|
+
when Hash then run_with_criteria(command)
|
57
|
+
when nil then skip(format(SKIP_UNEXISTING, @command, @package.key))
|
58
|
+
else run_script(command, @package.path)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def run_with_criteria(command)
|
65
|
+
return skip(format(SKIP_CRITERIA, @package.name)) unless match_criteria?(@package, command.fetch("if", {}))
|
66
|
+
|
67
|
+
run_script(command["run"], @package.path)
|
68
|
+
end
|
69
|
+
|
70
|
+
def run_multiple(package, commands)
|
71
|
+
Command.join(commands.map { |command| Command.new(package, command) })
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "concurrent-ruby"
|
4
|
+
|
5
|
+
module CobraCommander
|
6
|
+
module Executor
|
7
|
+
# A threaded execution environment, limited by the number of given workers
|
8
|
+
class Execution < Hash
|
9
|
+
#
|
10
|
+
# @param jobs [Array<#call>] array of job objects
|
11
|
+
# @param workers [Integer] number of workers to process this execution
|
12
|
+
# @see CobraCommander::Executor::Job
|
13
|
+
def initialize(jobs, workers:)
|
14
|
+
super()
|
15
|
+
|
16
|
+
@executor = Concurrent::FixedThreadPool.new(workers, auto_terminate: true)
|
17
|
+
merge! create_futures(jobs)
|
18
|
+
end
|
19
|
+
|
20
|
+
# Wait for all jobs to complete, returns a future with all execution futures
|
21
|
+
# @return [Concurrent::Promises::Future]
|
22
|
+
def wait
|
23
|
+
Concurrent::Promises.zip_futures_on(@executor, *values)
|
24
|
+
.tap(&:wait)
|
25
|
+
end
|
26
|
+
|
27
|
+
# The execution succeeds when all jobs succeeded
|
28
|
+
# @return [Boolean]
|
29
|
+
def success?
|
30
|
+
values.all?(&:fulfilled?)
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def create_future(job)
|
36
|
+
Concurrent::Promises.future_on(@executor, job, &:call).then do |result|
|
37
|
+
status, output = result
|
38
|
+
case status
|
39
|
+
when :error then Concurrent::Promises.rejected_future(output)
|
40
|
+
when :success, :skip then Concurrent::Promises.fulfilled_future(output)
|
41
|
+
else
|
42
|
+
Concurrent::Promises.fulfilled_future(result)
|
43
|
+
end
|
44
|
+
end.flat
|
45
|
+
end
|
46
|
+
|
47
|
+
def create_futures(jobs)
|
48
|
+
jobs.to_h { |job| [job, create_future(job)] }
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "pastel"
|
4
|
+
require "tty-prompt"
|
5
|
+
|
6
|
+
module CobraCommander
|
7
|
+
module Executor
|
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(execution, output)
|
16
|
+
new(execution).run(output)
|
17
|
+
end
|
18
|
+
|
19
|
+
def initialize(execution)
|
20
|
+
@prompt = TTY::Prompt.new
|
21
|
+
@execution = execution
|
22
|
+
end
|
23
|
+
|
24
|
+
def run(output)
|
25
|
+
selected = nil
|
26
|
+
loop do
|
27
|
+
selected = @prompt.select("Print output?", options, default: options.key(selected))
|
28
|
+
output.puts selected.fulfilled? ? selected.value : selected.reason
|
29
|
+
end
|
30
|
+
rescue TTY::Reader::InputInterrupt
|
31
|
+
output.puts BYE
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def options
|
37
|
+
@options ||= @execution.sort { |*args| sort_results(*args.flatten) }
|
38
|
+
.reduce({}) do |options, (job, result)|
|
39
|
+
template = result.rejected? ? ERROR : SUCCESS
|
40
|
+
options.merge format(template, job.to_s) => result
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def sort_results(job_a, result_a, job_b, result_b)
|
45
|
+
if result_a.rejected? == result_b.rejected?
|
46
|
+
job_a.to_s <=> job_b.to_s
|
47
|
+
else
|
48
|
+
result_b.rejected? ? 1 : -1
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "tty-command"
|
4
|
+
|
5
|
+
module CobraCommander
|
6
|
+
module Executor
|
7
|
+
#
|
8
|
+
# This is a helper module to help jobs return valid errors and success outputs.
|
9
|
+
#
|
10
|
+
# A CobraCommander::Executor::Job is actually any object responding to #call, and returning
|
11
|
+
# valid error or success responses.
|
12
|
+
#
|
13
|
+
# An error is an array containint `:error` and the output (i.e.: [:error, "string output"]).
|
14
|
+
# A success is either an array containint `:success` and the output, or just the output
|
15
|
+
# (i.e.: [:error, "string output"] or just "string output").
|
16
|
+
#
|
17
|
+
module Job
|
18
|
+
def skip(reason)
|
19
|
+
[:skip, reason]
|
20
|
+
end
|
21
|
+
|
22
|
+
def error(output)
|
23
|
+
[:error, output]
|
24
|
+
end
|
25
|
+
|
26
|
+
def success(output)
|
27
|
+
[:success, output]
|
28
|
+
end
|
29
|
+
|
30
|
+
def run_script(script, path)
|
31
|
+
result = isolate_bundle do
|
32
|
+
TTY::Command.new(pty: true, printer: :null)
|
33
|
+
.run!(script, chdir: path, err: :out)
|
34
|
+
end
|
35
|
+
return error(result.out) if result.failed?
|
36
|
+
|
37
|
+
success(result.out)
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def isolate_bundle(&block)
|
43
|
+
if Bundler.respond_to?(:with_unbundled_env)
|
44
|
+
Bundler.with_unbundled_env(&block)
|
45
|
+
else
|
46
|
+
Bundler.with_clean_env(&block)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CobraCommander
|
4
|
+
module Executor
|
5
|
+
# Prints the given CobraCommander::Executor::Context to [output] collection in markdown
|
6
|
+
module MarkdownPrinter
|
7
|
+
SUCCESS = "\n## ✔ %s\n"
|
8
|
+
ERROR = "\n## ✖ %s\n"
|
9
|
+
OUTPUT = "\n```\n\n%s\n```\n"
|
10
|
+
|
11
|
+
def self.run(execution, output)
|
12
|
+
execution.each do |job, result|
|
13
|
+
template = result.fulfilled? ? SUCCESS : ERROR
|
14
|
+
|
15
|
+
output.print format(template, job)
|
16
|
+
output.print format(OUTPUT, result.fulfilled? ? result.value : result.reason)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "./job"
|
4
|
+
require_relative "./package_criteria"
|
5
|
+
|
6
|
+
module CobraCommander
|
7
|
+
module Executor
|
8
|
+
module PackageCriteria
|
9
|
+
def match_criteria?(package, criteria)
|
10
|
+
criteria.all? do |criteria_key, criteria_value|
|
11
|
+
criteria_method = "_match_#{criteria_key}?"
|
12
|
+
!respond_to?(criteria_method, true) || send(criteria_method, package, criteria_value)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def _match_depends_on?(package, packages)
|
17
|
+
(Array(packages) - package.dependencies).empty?
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "./job"
|
4
|
+
|
5
|
+
module CobraCommander
|
6
|
+
module Executor
|
7
|
+
# This is a script job. It can tarket any CobraCommander::Package.
|
8
|
+
#
|
9
|
+
# If you want to target a Component, you can use Script.for to target
|
10
|
+
# individual paths for each given component.
|
11
|
+
#
|
12
|
+
# @see Script.for
|
13
|
+
class Script
|
14
|
+
include ::CobraCommander::Executor::Job
|
15
|
+
|
16
|
+
# Returns a set of scripts to be executed on the given commends.
|
17
|
+
#
|
18
|
+
# If a component has two packages in the same path, only one script for that component will be
|
19
|
+
# returned.
|
20
|
+
#
|
21
|
+
# @param components [Enumerable<CobraCommander::Component>] the target components
|
22
|
+
# @param script [String] shell script to run from the directories of the component's packages
|
23
|
+
# @return [Array<CobraCommander::Executor::Script>]
|
24
|
+
def self.for(components, script)
|
25
|
+
components.flat_map(&:packages).uniq(&:path).map do |package|
|
26
|
+
new(package, script)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def initialize(package, script)
|
31
|
+
@package = package
|
32
|
+
@script = script
|
33
|
+
end
|
34
|
+
|
35
|
+
def to_s
|
36
|
+
@package.name
|
37
|
+
end
|
38
|
+
|
39
|
+
# @see CobraCommander::Executor::Job
|
40
|
+
def call
|
41
|
+
run_script @script, @package.path
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "pastel"
|
4
|
+
require "tty-spinner"
|
5
|
+
|
6
|
+
module CobraCommander
|
7
|
+
module Executor
|
8
|
+
class Spinners
|
9
|
+
pastel = ::Pastel.new
|
10
|
+
SPINNER_OPTIONS = {
|
11
|
+
format: :bouncing,
|
12
|
+
success_mark: pastel.green("[DONE]"),
|
13
|
+
error_mark: pastel.red("[ERROR]"),
|
14
|
+
}.freeze
|
15
|
+
|
16
|
+
def self.start(execution, output:)
|
17
|
+
new(execution, output: output).start
|
18
|
+
end
|
19
|
+
|
20
|
+
def initialize(execution, output:)
|
21
|
+
@multi = TTY::Spinner::Multi.new(":spinner :task", output: output)
|
22
|
+
@multi.top_spinner.update(task: "Running")
|
23
|
+
execution.each { |job, result| register_spinner(job, result) }
|
24
|
+
end
|
25
|
+
|
26
|
+
def start
|
27
|
+
@multi.auto_spin
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def register_spinner(job, result)
|
33
|
+
@multi.register(":spinner #{job}", **SPINNER_OPTIONS) do |spinner|
|
34
|
+
result.on_fulfillment!(spinner) { |_, spin| spin.success }
|
35
|
+
.on_rejection!(spinner) { |_, spin| spin.error }
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -1,14 +1,45 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative "executor/
|
4
|
-
require_relative "executor/
|
3
|
+
require_relative "executor/execution"
|
4
|
+
require_relative "executor/job"
|
5
|
+
require_relative "executor/script"
|
6
|
+
require_relative "executor/command"
|
7
|
+
require_relative "executor/spinners"
|
8
|
+
require_relative "executor/interactive_printer"
|
9
|
+
require_relative "executor/markdown_printer"
|
5
10
|
|
6
11
|
module CobraCommander
|
7
|
-
# Execute a command on all given
|
12
|
+
# Execute a command on all given packages
|
8
13
|
module Executor
|
9
|
-
|
10
|
-
|
11
|
-
|
14
|
+
module_function
|
15
|
+
|
16
|
+
# Executes the given jobs in an CobraCommander::Executor::Execution.
|
17
|
+
#
|
18
|
+
# This facade also allows to execute the jobs with a spinner (@see CobraCommander::Executor::Spinners) to display
|
19
|
+
# the execution status of each job.
|
20
|
+
#
|
21
|
+
# You can also determine how to display the execution once it's completed, by setting `output_mode` to either
|
22
|
+
# :interactive or :markdown. When using :interactive, a menu with each job will be displayed allowing the user
|
23
|
+
# to select a job and see its output. When using :markdown, a markdown will be printed to `output` with the
|
24
|
+
# output of each job.
|
25
|
+
#
|
26
|
+
# @param jobs [Enumerable<CobraCommander::Executor::Job>] the jobs to run
|
27
|
+
# @param status_output [IO,nil] if not nil, will print the spinners for each job in this output
|
28
|
+
# @param workers [Integer] number of workers processing the jobs queue (see CobraCommander::Executor::Execution)
|
29
|
+
# @param output_mode [:interactive,:markdown,nil] how the output will be printed after execution
|
30
|
+
# @param workers [Integer] number of workers processing the jobs queue (see CobraCommander::Executor::Execution)
|
31
|
+
# @return [CobraCommander::Executor::Execution]
|
32
|
+
# @see CobraCommander::Executor::Execution
|
33
|
+
# @see CobraCommander::Executor::Spinners
|
34
|
+
# @see CobraCommander::Executor::InterativePrinter
|
35
|
+
# @see CobraCommander::Executor::MarkdownPrinter
|
36
|
+
def execute(jobs:, status_output: nil, output_mode: nil, output: nil, **kwargs)
|
37
|
+
Execution.new(jobs, **kwargs).tap do |execution|
|
38
|
+
Spinners.start(execution, output: status_output) if status_output
|
39
|
+
execution.wait
|
40
|
+
InteractivePrinter.run(execution, output) if output_mode == :interactive
|
41
|
+
MarkdownPrinter.run(execution, output) if output_mode == :markdown
|
42
|
+
end
|
12
43
|
end
|
13
44
|
end
|
14
45
|
end
|
@@ -11,10 +11,11 @@ module CobraCommander
|
|
11
11
|
class GitChanged
|
12
12
|
include Enumerable
|
13
13
|
|
14
|
-
|
14
|
+
Error = Class.new(StandardError)
|
15
|
+
InvalidSelectionError = Class.new(Error)
|
15
16
|
|
16
|
-
def initialize(
|
17
|
-
@
|
17
|
+
def initialize(path, base_branch)
|
18
|
+
@path = path
|
18
19
|
@base_branch = base_branch
|
19
20
|
end
|
20
21
|
|
@@ -25,17 +26,21 @@ module CobraCommander
|
|
25
26
|
private
|
26
27
|
|
27
28
|
def changes
|
28
|
-
@changes ||=
|
29
|
-
|
30
|
-
|
31
|
-
end
|
29
|
+
@changes ||= Dir.chdir(@path) do
|
30
|
+
git_dir, _, result = Open3.capture3("git", "rev-parse", "--git-dir")
|
31
|
+
validate_result!(result)
|
32
32
|
|
33
|
-
|
33
|
+
diff, _, result = Open3.capture3("git", "diff", "--name-only", @base_branch)
|
34
|
+
validate_result!(result)
|
34
35
|
|
35
|
-
|
36
|
-
|
37
|
-
end
|
36
|
+
git_dir = Pathname.new(git_dir)
|
37
|
+
diff.split("\n").map { |f| git_dir.dirname.join(f) }
|
38
38
|
end
|
39
39
|
end
|
40
|
+
|
41
|
+
def validate_result!(result)
|
42
|
+
raise InvalidSelectionError, "Specified branch #{@base_branch} could not be found" if result.exitstatus == 128
|
43
|
+
raise Error, "Uknown git error: #{result.inspect}" if result.exitstatus > 0
|
44
|
+
end
|
40
45
|
end
|
41
46
|
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "forwardable"
|
4
|
+
|
5
|
+
module CobraCommander
|
6
|
+
# A package within the
|
7
|
+
class Package
|
8
|
+
extend Forwardable
|
9
|
+
|
10
|
+
attr_reader :source, :path, :name, :dependencies
|
11
|
+
|
12
|
+
def_delegators :source, :key
|
13
|
+
|
14
|
+
def initialize(source, path:, dependencies:, name:)
|
15
|
+
@source = source
|
16
|
+
@path = path
|
17
|
+
@name = name
|
18
|
+
@dependencies = dependencies
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|