cobra_commander 0.15.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +0 -2
  3. data/.rubocop.yml +0 -5
  4. data/Gemfile +7 -0
  5. data/cobra_commander.gemspec +6 -1
  6. data/doc/dependency_decisions.yml +9 -0
  7. data/docs/CHANGELOG.md +19 -2
  8. data/docs/README.md +3 -129
  9. data/exe/cobra +5 -0
  10. data/lib/cobra_commander/affected.rb +19 -51
  11. data/lib/cobra_commander/cli/filters.rb +1 -3
  12. data/lib/cobra_commander/{output → cli/output}/ascii_tree.rb +7 -5
  13. data/lib/cobra_commander/cli/output/change.rb +91 -0
  14. data/lib/cobra_commander/cli/output/dot_graph.rb +21 -0
  15. data/lib/cobra_commander/cli.rb +58 -30
  16. data/lib/cobra_commander/component.rb +6 -10
  17. data/lib/cobra_commander/executor/command.rb +75 -0
  18. data/lib/cobra_commander/executor/execution.rb +52 -0
  19. data/lib/cobra_commander/executor/interactive_printer.rb +53 -0
  20. data/lib/cobra_commander/executor/job.rb +51 -0
  21. data/lib/cobra_commander/executor/markdown_printer.rb +21 -0
  22. data/lib/cobra_commander/executor/package_criteria.rb +21 -0
  23. data/lib/cobra_commander/executor/script.rb +45 -0
  24. data/lib/cobra_commander/executor/spinners.rb +40 -0
  25. data/lib/cobra_commander/executor.rb +37 -6
  26. data/lib/cobra_commander/git_changed.rb +16 -11
  27. data/lib/cobra_commander/package.rb +21 -0
  28. data/lib/cobra_commander/registry.rb +29 -0
  29. data/lib/cobra_commander/source.rb +37 -0
  30. data/lib/cobra_commander/umbrella.rb +70 -17
  31. data/lib/cobra_commander/version.rb +1 -1
  32. data/lib/cobra_commander.rb +10 -16
  33. data/mkdocs.yml +1 -1
  34. metadata +37 -43
  35. data/.editorconfig +0 -11
  36. data/.github/workflows/ci.yml +0 -44
  37. data/.github/workflows/release.yml +0 -20
  38. data/.rubocop_todo.yml +0 -15
  39. data/LICENSE.txt +0 -21
  40. data/_config.yml +0 -1
  41. data/docs/CODE_OF_CONDUCT.md +0 -74
  42. data/gemfiles/bundler1.gemfile +0 -7
  43. data/gemfiles/bundler2.gemfile +0 -7
  44. data/lib/cobra_commander/change.rb +0 -87
  45. data/lib/cobra_commander/dependencies/bundler/package.rb +0 -17
  46. data/lib/cobra_commander/dependencies/bundler.rb +0 -44
  47. data/lib/cobra_commander/dependencies/yarn/package.rb +0 -21
  48. data/lib/cobra_commander/dependencies/yarn.rb +0 -40
  49. data/lib/cobra_commander/dependencies.rb +0 -4
  50. data/lib/cobra_commander/executor/concurrent.rb +0 -51
  51. data/lib/cobra_commander/executor/context.rb +0 -49
  52. data/lib/cobra_commander/output/flat_list.rb +0 -16
  53. data/lib/cobra_commander/output/graph_viz.rb +0 -27
  54. data/lib/cobra_commander/output/interactive_printer.rb +0 -53
  55. data/lib/cobra_commander/output/markdown_printer.rb +0 -24
  56. data/lib/cobra_commander/output.rb +0 -7
  57. data/portal.yml +0 -12
  58. data/renovate.json +0 -8
@@ -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
- require "cobra_commander/cli/filters"
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
- class_option :js, default: false, type: :boolean, desc: "Consider only the JS dependency graph"
23
- class_option :ruby, default: false, type: :boolean, desc: "Consider only the Ruby dependency graph"
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 : CobraCommander::Output::FlatList.new(components).to_s
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 "exec [components] <command>", "Executes the command in the context of a given component or set thereof. " \
40
- "Defaults to all components."
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 exec(command_or_components, command = nil)
49
- results = CobraCommander::Executor.exec(
50
- components: components_filtered(command && command_or_components),
51
- command: command || command_or_components,
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
- if options.interactive && results.size > 1
55
- CobraCommander::Output::InteractivePrinter.run(results, $stdout)
56
- else
57
- CobraCommander::Output::MarkdownPrinter.run(results, $stdout)
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
- component = find_component(component)
64
- puts CobraCommander::Output::AsciiTree.new(component).to_s
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.png"), aliases: "-o",
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
- CobraCommander::Output::GraphViz.generate(
72
- find_component(component),
73
- options.output
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
- @umbrella ||= CobraCommander.umbrella(options.app, yarn: options.js, bundler: options.ruby)
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(key, package)
16
- @packages[key] = package
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.values.map do |package|
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=#{sources}>"
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/context"
4
- require_relative "executor/concurrent"
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 components
12
+ # Execute a command on all given packages
8
13
  module Executor
9
- def self.exec(components:, command:, concurrency:, status_output:)
10
- Concurrent.new(components, concurrency: concurrency, spin_output: status_output)
11
- .exec(command)
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
- InvalidSelectionError = Class.new(StandardError)
14
+ Error = Class.new(StandardError)
15
+ InvalidSelectionError = Class.new(Error)
15
16
 
16
- def initialize(repo_root, base_branch)
17
- @repo_root = repo_root
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 ||= begin
29
- diff, _, result = Dir.chdir(@repo_root) do
30
- Open3.capture3("git", "diff", "--name-only", @base_branch)
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
- raise InvalidSelectionError, "Specified branch #{@base_branch} could not be found" if result.exitstatus == 128
33
+ diff, _, result = Open3.capture3("git", "diff", "--name-only", @base_branch)
34
+ validate_result!(result)
34
35
 
35
- diff.split("\n").map do |f|
36
- File.join(@repo_root, f)
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