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
@@ -1,17 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module CobraCommander
4
- module Dependencies
5
- # Calculates ruby bundler dependencies
6
- class Bundler::Package
7
- attr_reader :path, :name, :dependencies
8
-
9
- def initialize(spec = nil, path: spec&.loaded_from, name: spec&.name, dependencies: spec&.dependencies)
10
- @spec = spec
11
- @path = path
12
- @name = name
13
- @dependencies = dependencies&.map(&:name)
14
- end
15
- end
16
- end
17
- end
@@ -1,44 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "bundler"
4
- require "bundler/lockfile_parser"
5
- require "pathname"
6
-
7
- module CobraCommander
8
- module Dependencies
9
- # Calculates ruby bundler dependencies
10
- class Bundler
11
- autoload :Package, "cobra_commander/dependencies/bundler/package"
12
-
13
- attr_reader :path
14
-
15
- def initialize(root)
16
- @root = Pathname.new(root)
17
- @path = @root.join("Gemfile.lock").realpath
18
- end
19
-
20
- def root
21
- Package.new(path: path, dependencies: lockfile.dependencies.values)
22
- end
23
-
24
- def packages
25
- @packages ||= components_source.specs.map do |spec|
26
- Package.new(spec)
27
- end
28
- end
29
-
30
- private
31
-
32
- def lockfile
33
- @lockfile ||= ::Bundler::LockfileParser.new(::Bundler.read_file(path))
34
- end
35
-
36
- def components_source
37
- @components_source ||= begin
38
- source = @lockfile.sources.find { |s| s.path.to_s.eql?("components") }
39
- ::Bundler::Source::Path.new(source.options.merge("root_path" => @root))
40
- end
41
- end
42
- end
43
- end
44
- end
@@ -1,21 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module CobraCommander
4
- module Dependencies
5
- class Yarn::Package
6
- attr_reader :path, :name, :dependencies
7
-
8
- def initialize(path:, dependencies:, name: nil)
9
- @path = path
10
- @name = untag(name)
11
- @dependencies = dependencies.map { |dep| untag(dep) }
12
- end
13
-
14
- private
15
-
16
- def untag(name)
17
- name&.gsub(%r{^@[\w-]+/}, "")
18
- end
19
- end
20
- end
21
- end
@@ -1,40 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "json"
4
- require "open3"
5
- require "pathname"
6
-
7
- module CobraCommander
8
- module Dependencies
9
- # Yarn workspace components source for an umbrella
10
- class Yarn
11
- PACKAGE_FILE = "package.json"
12
-
13
- autoload :Package, "cobra_commander/dependencies/yarn/package"
14
-
15
- def initialize(root_path)
16
- @root_path = Pathname.new(root_path)
17
- end
18
-
19
- def root
20
- @root ||= Package.new(
21
- path: @root_path.join(PACKAGE_FILE).realpath,
22
- dependencies: packages.map(&:name)
23
- )
24
- end
25
-
26
- def packages
27
- @packages ||= begin
28
- output, = Open3.capture2("yarn workspaces --json info", chdir: @root_path.to_s)
29
- JSON.parse(JSON.parse(output)["data"]).map do |name, spec|
30
- Package.new(
31
- path: @root_path.join(spec["location"], PACKAGE_FILE).realpath,
32
- dependencies: spec["workspaceDependencies"],
33
- name: name
34
- )
35
- end
36
- end
37
- end
38
- end
39
- end
40
- end
@@ -1,4 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "dependencies/yarn"
4
- require_relative "dependencies/bundler"
@@ -1,51 +0,0 @@
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
@@ -1,49 +0,0 @@
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,16 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module CobraCommander
4
- module Output
5
- # Prints a list of components' names sorted alphabetically
6
- class FlatList
7
- def initialize(components)
8
- @components = components
9
- end
10
-
11
- def to_s
12
- @components.map(&:name).sort
13
- end
14
- end
15
- end
16
- end
@@ -1,27 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "graphviz"
4
-
5
- module CobraCommander
6
- module Output
7
- # Generates graphs of components
8
- module GraphViz
9
- def self.generate(component, output)
10
- g = ::GraphViz.new(:G, type: :digraph, concentrate: true)
11
- ([component] + component.deep_dependencies).each do |comp|
12
- g.add_nodes comp.name
13
- g.add_edges comp.name, comp.dependencies.map(&:name)
14
- end
15
-
16
- g.output(extract_format(output) => output)
17
- end
18
-
19
- private_class_method def self.extract_format(output)
20
- format = output[-3..]
21
- return format if %w[png dot].include?(format)
22
-
23
- raise ArgumentError, "output format must be 'png' or 'dot'"
24
- end
25
- end
26
- end
27
- end
@@ -1,53 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "pastel"
4
- require "tty-prompt"
5
-
6
- module CobraCommander
7
- module Output
8
- # Runs an interactive output printer
9
- class InteractivePrinter
10
- pastel = Pastel.new
11
- SUCCESS = "#{pastel.green('✔')} %s"
12
- ERROR = "#{pastel.red('✖')} %s"
13
- BYE = pastel.decorate("\n\n👋 Bye!", :white, :on_black, :bold).freeze
14
-
15
- def self.run(contexts, output)
16
- new(contexts).run(output)
17
- end
18
-
19
- def initialize(contexts)
20
- @prompt = TTY::Prompt.new
21
- @options = map_options(contexts)
22
- end
23
-
24
- def run(output)
25
- selected_context = nil
26
- loop do
27
- selected_context = @prompt.select("Print output?", @options, default: @options.key(selected_context))
28
- output.puts selected_context.output
29
- end
30
- rescue TTY::Reader::InputInterrupt
31
- output.puts BYE
32
- end
33
-
34
- private
35
-
36
- def map_options(contexts)
37
- contexts.sort(&method(:sort_contexts))
38
- .reduce({}) do |options, context|
39
- template = context.success? ? SUCCESS : ERROR
40
- options.merge format(template, context.component_name) => context
41
- end
42
- end
43
-
44
- def sort_contexts(context_a, context_b)
45
- if context_a.success? == context_b.success?
46
- context_a.component_name <=> context_b.component_name
47
- else
48
- context_a.success? ? 1 : -1
49
- end
50
- end
51
- end
52
- end
53
- end
@@ -1,24 +0,0 @@
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
@@ -1,7 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "output/flat_list"
4
- require_relative "output/ascii_tree"
5
- require_relative "output/graph_viz"
6
- require_relative "output/interactive_printer"
7
- require_relative "output/markdown_printer"
data/portal.yml DELETED
@@ -1,12 +0,0 @@
1
- apiVersion: backstage.io/v1alpha1
2
- kind: Component
3
- metadata:
4
- name: cobra-commander
5
- title: Cobra Commander
6
- description: Tools for working with Component Based Rails Apps
7
- annotations:
8
- backstage.io/techdocs-ref: dir:.
9
- spec:
10
- type: library
11
- owner: heroes-for-hire
12
- lifecycle: production
data/renovate.json DELETED
@@ -1,8 +0,0 @@
1
- {
2
- "extends": [
3
- "config:base"
4
- ],
5
- "ignorePaths": [
6
- "spec/fixtures"
7
- ]
8
- }