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.
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