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
@@ -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,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
|
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
|