docktor_rails 0.1.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 +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +8 -0
- data/CHANGELOG.md +8 -0
- data/LICENSE.txt +21 -0
- data/README.md +70 -0
- data/Rakefile +17 -0
- data/exe/docktor_rails +6 -0
- data/lib/docktor_rails/checks/base_check.rb +35 -0
- data/lib/docktor_rails/checks/compose_build_paths.rb +66 -0
- data/lib/docktor_rails/checks/compose_entrypoint_exec.rb +82 -0
- data/lib/docktor_rails/checks/compose_entrypoint_sanity.rb +87 -0
- data/lib/docktor_rails/checks/compose_env_file_present.rb +47 -0
- data/lib/docktor_rails/checks/compose_file_present.rb +32 -0
- data/lib/docktor_rails/checks/compose_healthchecks.rb +37 -0
- data/lib/docktor_rails/checks/compose_platform_risk.rb +53 -0
- data/lib/docktor_rails/checks/rails_master_key_required.rb +62 -0
- data/lib/docktor_rails/checks/shell_scripts_crlf.rb +53 -0
- data/lib/docktor_rails/cli.rb +91 -0
- data/lib/docktor_rails/compose.rb +24 -0
- data/lib/docktor_rails/dotenv.rb +28 -0
- data/lib/docktor_rails/guidance.rb +48 -0
- data/lib/docktor_rails/platform.rb +63 -0
- data/lib/docktor_rails/preflight/runner.rb +67 -0
- data/lib/docktor_rails/reporters/json_reporter.rb +43 -0
- data/lib/docktor_rails/reporters/text_reporter.rb +78 -0
- data/lib/docktor_rails/result.rb +29 -0
- data/lib/docktor_rails/version.rb +5 -0
- data/lib/docktor_rails.rb +10 -0
- data/sig/docktor_rails.rbs +4 -0
- metadata +134 -0
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base_check"
|
|
4
|
+
|
|
5
|
+
module DocktorRails
|
|
6
|
+
module Checks
|
|
7
|
+
class ShellScriptsCrlf < BaseCheck
|
|
8
|
+
DEFAULT_IGNORES = [
|
|
9
|
+
"/.git/",
|
|
10
|
+
"/node_modules/",
|
|
11
|
+
"/vendor/",
|
|
12
|
+
"/tmp/",
|
|
13
|
+
"/log/"
|
|
14
|
+
].freeze
|
|
15
|
+
|
|
16
|
+
def id
|
|
17
|
+
"fs.shell_crlf"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def run(ctx)
|
|
21
|
+
root = ctx.fetch(:root)
|
|
22
|
+
offenders = []
|
|
23
|
+
|
|
24
|
+
Dir.glob(File.join(root, "**", "*.sh"), File::FNM_DOTMATCH).each do |path|
|
|
25
|
+
next unless File.file?(path)
|
|
26
|
+
next if ignored?(path)
|
|
27
|
+
|
|
28
|
+
offenders << rel(path, root) if File.binread(path).include?("\r\n")
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
if offenders.empty?
|
|
32
|
+
pass("No CRLF line endings in .sh files")
|
|
33
|
+
else
|
|
34
|
+
fail(
|
|
35
|
+
"CRLF line endings found in shell scripts",
|
|
36
|
+
files: offenders.sort,
|
|
37
|
+
hint: "Convert to LF (e.g. with git) and enforce via .gitattributes: *.sh text eol=lf"
|
|
38
|
+
)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
|
|
44
|
+
def ignored?(path)
|
|
45
|
+
DEFAULT_IGNORES.any? { |seg| path.include?(seg) }
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def rel(path, root)
|
|
49
|
+
path.delete_prefix(root + File::SEPARATOR)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "thor"
|
|
4
|
+
|
|
5
|
+
require_relative "version"
|
|
6
|
+
require_relative "preflight/runner"
|
|
7
|
+
require_relative "reporters/text_reporter"
|
|
8
|
+
require_relative "reporters/json_reporter"
|
|
9
|
+
|
|
10
|
+
module DocktorRails
|
|
11
|
+
class CLI < Thor
|
|
12
|
+
class_option :root, type: :string, desc: "Project root to scan (default: current directory)"
|
|
13
|
+
class_option :format, type: :string, default: "text", desc: "Output format: text|json"
|
|
14
|
+
class_option :output, type: :string, desc: "Write output to a file (useful with --format json)"
|
|
15
|
+
class_option :verbose, type: :boolean, default: false, desc: "Show passing checks"
|
|
16
|
+
class_option :quiet, type: :boolean, default: false, desc: "Only show errors/warnings and the summary"
|
|
17
|
+
class_option :no_color, type: :boolean, default: false, desc: "Disable colored output"
|
|
18
|
+
|
|
19
|
+
desc "diagnose", "Run preflight checks (read-only)"
|
|
20
|
+
def diagnose
|
|
21
|
+
report = Preflight::Runner.new(root: root_dir).run
|
|
22
|
+
|
|
23
|
+
io = output_io
|
|
24
|
+
render_report(report, io)
|
|
25
|
+
|
|
26
|
+
exit(report.fetch(:status) == :fail ? 1 : 0)
|
|
27
|
+
rescue StandardError => e
|
|
28
|
+
$stderr.puts("docktor_rails error: #{e.class}: #{e.message}")
|
|
29
|
+
exit(2)
|
|
30
|
+
ensure
|
|
31
|
+
io&.close if io && io != $stdout
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
desc "up", "Run preflight checks, then print the suggested docker compose commands"
|
|
35
|
+
def up
|
|
36
|
+
report = Preflight::Runner.new(root: root_dir).run
|
|
37
|
+
|
|
38
|
+
Reporters::TextReporter.new(
|
|
39
|
+
color: color_enabled?,
|
|
40
|
+
verbose: options[:verbose],
|
|
41
|
+
quiet: options[:quiet]
|
|
42
|
+
).render(report)
|
|
43
|
+
exit(1) if report.fetch(:status) == :fail
|
|
44
|
+
|
|
45
|
+
puts
|
|
46
|
+
puts "Preflight passed. Next:"
|
|
47
|
+
puts " docker compose up"
|
|
48
|
+
rescue StandardError => e
|
|
49
|
+
$stderr.puts("docktor_rails error: #{e.class}: #{e.message}")
|
|
50
|
+
exit(2)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
desc "version", "Print version"
|
|
54
|
+
def version
|
|
55
|
+
puts DocktorRails::VERSION
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
private
|
|
59
|
+
|
|
60
|
+
def output_io
|
|
61
|
+
path = options[:output]
|
|
62
|
+
return $stdout unless path
|
|
63
|
+
|
|
64
|
+
File.open(path, "w")
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def render_report(report, io)
|
|
68
|
+
case options[:format]
|
|
69
|
+
when "json"
|
|
70
|
+
Reporters::JsonReporter.new(io: io).render(report, tool_version: DocktorRails::VERSION)
|
|
71
|
+
else
|
|
72
|
+
Reporters::TextReporter.new(
|
|
73
|
+
io: io,
|
|
74
|
+
color: color_enabled?,
|
|
75
|
+
verbose: options[:verbose],
|
|
76
|
+
quiet: options[:quiet]
|
|
77
|
+
).render(report)
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def color_enabled?
|
|
82
|
+
return false if options[:no_color]
|
|
83
|
+
|
|
84
|
+
$stdout.tty?
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def root_dir
|
|
88
|
+
options[:root] ? File.expand_path(options[:root]) : Dir.pwd
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "yaml"
|
|
4
|
+
|
|
5
|
+
module DocktorRails
|
|
6
|
+
module Compose
|
|
7
|
+
CANDIDATES = ["compose.yml", "docker-compose.yml"].freeze
|
|
8
|
+
|
|
9
|
+
def self.find_file(root)
|
|
10
|
+
CANDIDATES.map { |f| File.join(root, f) }.find { |p| File.file?(p) }
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def self.load_file(path)
|
|
14
|
+
data = YAML.safe_load(File.read(path), aliases: true)
|
|
15
|
+
data.is_a?(Hash) ? data : {}
|
|
16
|
+
rescue Psych::Exception => e
|
|
17
|
+
raise DocktorRails::Error, "Invalid compose YAML in #{File.basename(path)}: #{e.message}"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def self.services(doc)
|
|
21
|
+
doc.fetch("services", {}) || {}
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DocktorRails
|
|
4
|
+
class Dotenv
|
|
5
|
+
def self.parse_file(path)
|
|
6
|
+
return {} unless File.file?(path)
|
|
7
|
+
|
|
8
|
+
parse(File.read(path))
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def self.parse(content)
|
|
12
|
+
env = {}
|
|
13
|
+
content.each_line do |line|
|
|
14
|
+
line = line.strip
|
|
15
|
+
next if line.empty? || line.start_with?("#")
|
|
16
|
+
|
|
17
|
+
key, value = line.split("=", 2)
|
|
18
|
+
next if key.nil? || key.empty?
|
|
19
|
+
|
|
20
|
+
value = "" if value.nil?
|
|
21
|
+
value = value.strip
|
|
22
|
+
value = value[1..-2] if (value.start_with?("\"") && value.end_with?("\"")) || (value.start_with?("'") && value.end_with?("'"))
|
|
23
|
+
env[key] = value
|
|
24
|
+
end
|
|
25
|
+
env
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DocktorRails
|
|
4
|
+
module Guidance
|
|
5
|
+
def self.hint_for(result, platform)
|
|
6
|
+
return result.hint if result.hint
|
|
7
|
+
|
|
8
|
+
id = result.id
|
|
9
|
+
status = result.status
|
|
10
|
+
return nil if status == :pass
|
|
11
|
+
|
|
12
|
+
case id
|
|
13
|
+
when "fs.shell_crlf", "compose.entrypoint_sanity"
|
|
14
|
+
crlf_hint(platform)
|
|
15
|
+
when "compose.healthchecks"
|
|
16
|
+
"Add compose healthchecks so readiness is deterministic across machines (then you can wait on health status instead of sleeps)."
|
|
17
|
+
when "env.rails_master_key"
|
|
18
|
+
"Provide config/master.key (local dev), or set RAILS_MASTER_KEY (CI/prod), or add it to .env.docker."
|
|
19
|
+
when "compose.env_file_present"
|
|
20
|
+
"Ensure each env_file referenced in compose exists (or remove the reference)."
|
|
21
|
+
when "compose.build_paths"
|
|
22
|
+
"Fix compose build paths: ensure build.context directories exist and build.dockerfile paths resolve correctly."
|
|
23
|
+
when "compose.entrypoint_exec"
|
|
24
|
+
entrypoint_exec_hint(platform)
|
|
25
|
+
when "compose.platform_risk"
|
|
26
|
+
"If teammates use different CPU architectures (arm64 vs amd64), consider multi-arch images or removing hard platform pins unless necessary."
|
|
27
|
+
else
|
|
28
|
+
nil
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def self.crlf_hint(platform)
|
|
33
|
+
if platform&.windows?
|
|
34
|
+
"CRLF usually comes from Windows checkouts. Add .gitattributes: '*.sh text eol=lf', then run: git add --renormalize ."
|
|
35
|
+
else
|
|
36
|
+
"Convert scripts to LF and enforce via .gitattributes: '*.sh text eol=lf'"
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def self.entrypoint_exec_hint(platform)
|
|
41
|
+
if platform&.windows?
|
|
42
|
+
"Executable bit/shebang checks are not meaningful on Windows hosts. Prefer running via WSL2 for Linux-like behavior."
|
|
43
|
+
else
|
|
44
|
+
"Ensure entrypoint scripts are executable (chmod +x) and start with a shebang (e.g. #!/usr/bin/env bash)."
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rbconfig"
|
|
4
|
+
|
|
5
|
+
module DocktorRails
|
|
6
|
+
class Platform
|
|
7
|
+
attr_reader :os, :arch
|
|
8
|
+
|
|
9
|
+
def initialize(os:, arch:)
|
|
10
|
+
@os = os
|
|
11
|
+
@arch = arch
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def self.detect
|
|
15
|
+
new(os: detect_os, arch: detect_arch)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def windows?
|
|
19
|
+
os == :windows
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def macos?
|
|
23
|
+
os == :macos
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def linux?
|
|
27
|
+
os == :linux
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def arm64?
|
|
31
|
+
arch == :arm64
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def amd64?
|
|
35
|
+
arch == :amd64
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def to_h
|
|
39
|
+
{ os: os.to_s, arch: arch.to_s }
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def label
|
|
43
|
+
"#{os} #{arch}"
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def self.detect_os
|
|
47
|
+
p = RUBY_PLATFORM
|
|
48
|
+
return :windows if p.match?(/mswin|mingw|cygwin/i)
|
|
49
|
+
return :macos if p.match?(/darwin/i)
|
|
50
|
+
return :linux if p.match?(/linux/i)
|
|
51
|
+
|
|
52
|
+
:unknown
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def self.detect_arch
|
|
56
|
+
cpu = RbConfig::CONFIG["host_cpu"].to_s.downcase
|
|
57
|
+
return :arm64 if cpu.match?(/arm64|aarch64/)
|
|
58
|
+
return :amd64 if cpu.match?(/x86_64|amd64/)
|
|
59
|
+
|
|
60
|
+
:unknown
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../platform"
|
|
4
|
+
|
|
5
|
+
require_relative "../checks/compose_file_present"
|
|
6
|
+
require_relative "../checks/compose_build_paths"
|
|
7
|
+
require_relative "../checks/compose_env_file_present"
|
|
8
|
+
require_relative "../checks/compose_entrypoint_sanity"
|
|
9
|
+
require_relative "../checks/compose_entrypoint_exec"
|
|
10
|
+
require_relative "../checks/shell_scripts_crlf"
|
|
11
|
+
require_relative "../checks/compose_healthchecks"
|
|
12
|
+
require_relative "../checks/compose_platform_risk"
|
|
13
|
+
require_relative "../checks/rails_master_key_required"
|
|
14
|
+
|
|
15
|
+
module DocktorRails
|
|
16
|
+
module Preflight
|
|
17
|
+
class Runner
|
|
18
|
+
DEFAULT_CHECKS = [
|
|
19
|
+
Checks::ComposeFilePresent,
|
|
20
|
+
Checks::ComposeBuildPaths,
|
|
21
|
+
Checks::ComposeEnvFilePresent,
|
|
22
|
+
Checks::ComposeEntrypointSanity,
|
|
23
|
+
Checks::ComposeEntrypointExec,
|
|
24
|
+
Checks::ShellScriptsCrlf,
|
|
25
|
+
Checks::ComposeHealthchecks,
|
|
26
|
+
Checks::ComposePlatformRisk,
|
|
27
|
+
Checks::RailsMasterKeyRequired
|
|
28
|
+
].freeze
|
|
29
|
+
|
|
30
|
+
def initialize(root: Dir.pwd, checks: DEFAULT_CHECKS)
|
|
31
|
+
@root = root
|
|
32
|
+
@checks = checks
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def run
|
|
36
|
+
platform = Platform.detect
|
|
37
|
+
ctx = { root: @root, platform: platform }
|
|
38
|
+
|
|
39
|
+
results = @checks.map do |check_class|
|
|
40
|
+
check_class.new.run(ctx)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
summary = {
|
|
44
|
+
pass: results.count(&:pass?),
|
|
45
|
+
warn: results.count(&:warn?),
|
|
46
|
+
fail: results.count(&:fail?)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
status = if summary[:fail].positive?
|
|
50
|
+
:fail
|
|
51
|
+
elsif summary[:warn].positive?
|
|
52
|
+
:warn
|
|
53
|
+
else
|
|
54
|
+
:pass
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
{
|
|
58
|
+
root: @root,
|
|
59
|
+
platform: platform,
|
|
60
|
+
status: status,
|
|
61
|
+
summary: summary,
|
|
62
|
+
checks: results
|
|
63
|
+
}
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
5
|
+
require_relative "../guidance"
|
|
6
|
+
|
|
7
|
+
module DocktorRails
|
|
8
|
+
module Reporters
|
|
9
|
+
class JsonReporter
|
|
10
|
+
SCHEMA_VERSION = 1
|
|
11
|
+
|
|
12
|
+
def initialize(io: $stdout)
|
|
13
|
+
@io = io
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def render(report, tool_version:)
|
|
17
|
+
platform = report[:platform]
|
|
18
|
+
|
|
19
|
+
checks = report.fetch(:checks).map do |r|
|
|
20
|
+
h = r.to_h
|
|
21
|
+
hint = Guidance.hint_for(r, platform)
|
|
22
|
+
h[:hint] = hint if hint && !h.key?(:hint)
|
|
23
|
+
h
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
payload = {
|
|
27
|
+
schema_version: SCHEMA_VERSION,
|
|
28
|
+
tool: { name: "docktor_rails", version: tool_version },
|
|
29
|
+
context: {
|
|
30
|
+
root: report[:root],
|
|
31
|
+
host: platform&.to_h
|
|
32
|
+
},
|
|
33
|
+
status: report.fetch(:status).to_s,
|
|
34
|
+
summary: report.fetch(:summary),
|
|
35
|
+
checks: checks
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
@io.write(JSON.pretty_generate(payload))
|
|
39
|
+
@io.write("\n")
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "pastel"
|
|
4
|
+
|
|
5
|
+
require_relative "../guidance"
|
|
6
|
+
|
|
7
|
+
module DocktorRails
|
|
8
|
+
module Reporters
|
|
9
|
+
class TextReporter
|
|
10
|
+
def initialize(io: $stdout, color: true, verbose: false, quiet: false)
|
|
11
|
+
@io = io
|
|
12
|
+
@pastel = Pastel.new(enabled: color)
|
|
13
|
+
@verbose = verbose
|
|
14
|
+
@quiet = quiet
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def render(report)
|
|
18
|
+
@root = report[:root]
|
|
19
|
+
@platform = report[:platform]
|
|
20
|
+
|
|
21
|
+
unless @quiet
|
|
22
|
+
@io.puts "docktor_rails — preflight check"
|
|
23
|
+
@io.puts @pastel.dim("host: #{@platform&.label || "unknown"}")
|
|
24
|
+
@io.puts @pastel.dim("root: #{@root}") if @root
|
|
25
|
+
@io.puts "—" * 46
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
checks = report.fetch(:checks)
|
|
29
|
+
render_group("Errors", checks.select(&:fail?))
|
|
30
|
+
render_group("Warnings", checks.select(&:warn?))
|
|
31
|
+
render_group("Passed", checks.select(&:pass?)) if @verbose && !@quiet
|
|
32
|
+
|
|
33
|
+
@io.puts "—" * 46 unless @quiet
|
|
34
|
+
s = report.fetch(:summary)
|
|
35
|
+
@io.puts "#{s.fetch(:fail)} errors · #{s.fetch(:warn)} warnings · #{s.fetch(:pass)} passed"
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
def render_group(title, results)
|
|
41
|
+
return if results.empty?
|
|
42
|
+
|
|
43
|
+
@io.puts @pastel.bold(title) unless @quiet
|
|
44
|
+
results.each do |result|
|
|
45
|
+
@io.puts format_line(result)
|
|
46
|
+
render_details(result)
|
|
47
|
+
end
|
|
48
|
+
@io.puts unless @quiet
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def format_line(result)
|
|
52
|
+
case result.status
|
|
53
|
+
when :pass
|
|
54
|
+
@pastel.green("✅ #{result.message}")
|
|
55
|
+
when :warn
|
|
56
|
+
@pastel.yellow("⚠️ #{result.message}")
|
|
57
|
+
else
|
|
58
|
+
@pastel.red("❌ #{result.message}")
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def render_details(result)
|
|
63
|
+
files = Array(result.files).compact
|
|
64
|
+
hint = Guidance.hint_for(result, @platform)
|
|
65
|
+
|
|
66
|
+
return if files.empty? && hint.nil?
|
|
67
|
+
|
|
68
|
+
if files.any?
|
|
69
|
+
rendered = files.first(8).join(", ")
|
|
70
|
+
rendered += " …" if files.length > 8
|
|
71
|
+
@io.puts @pastel.dim(" files: #{rendered}")
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
@io.puts @pastel.dim(" hint: #{hint}") if hint
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DocktorRails
|
|
4
|
+
Result = Struct.new(:id, :status, :message, :files, :hint, keyword_init: true) do
|
|
5
|
+
def to_h
|
|
6
|
+
h = {
|
|
7
|
+
id: id,
|
|
8
|
+
status: status.to_s,
|
|
9
|
+
message: message,
|
|
10
|
+
files: Array(files).compact
|
|
11
|
+
}
|
|
12
|
+
h[:hint] = hint if hint
|
|
13
|
+
h.delete(:files) if h[:files].empty?
|
|
14
|
+
h
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def pass?
|
|
18
|
+
status == :pass
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def warn?
|
|
22
|
+
status == :warn
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def fail?
|
|
26
|
+
status == :fail
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "docktor_rails/version"
|
|
4
|
+
require_relative "docktor_rails/platform"
|
|
5
|
+
require_relative "docktor_rails/guidance"
|
|
6
|
+
require_relative "docktor_rails/cli"
|
|
7
|
+
|
|
8
|
+
module DocktorRails
|
|
9
|
+
class Error < StandardError; end
|
|
10
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: docktor_rails
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- FAllS
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: exe
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2026-06-11 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: pastel
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '0.8'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '0.8'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: thor
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - "~>"
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '1.3'
|
|
34
|
+
type: :runtime
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - "~>"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '1.3'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: fakefs
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - ">="
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '0'
|
|
48
|
+
type: :development
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - ">="
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '0'
|
|
55
|
+
- !ruby/object:Gem::Dependency
|
|
56
|
+
name: rspec
|
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - ">="
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '0'
|
|
62
|
+
type: :development
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - ">="
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '0'
|
|
69
|
+
description: Diagnose common cross-machine Docker/Compose issues in Rails apps (CRLF,
|
|
70
|
+
missing files/env, healthcheck/readiness risks) with CI-friendly output.
|
|
71
|
+
email:
|
|
72
|
+
- abdullah_alavi@hotmail.com
|
|
73
|
+
executables:
|
|
74
|
+
- docktor_rails
|
|
75
|
+
extensions: []
|
|
76
|
+
extra_rdoc_files: []
|
|
77
|
+
files:
|
|
78
|
+
- ".rspec"
|
|
79
|
+
- ".rubocop.yml"
|
|
80
|
+
- CHANGELOG.md
|
|
81
|
+
- LICENSE.txt
|
|
82
|
+
- README.md
|
|
83
|
+
- Rakefile
|
|
84
|
+
- exe/docktor_rails
|
|
85
|
+
- lib/docktor_rails.rb
|
|
86
|
+
- lib/docktor_rails/checks/base_check.rb
|
|
87
|
+
- lib/docktor_rails/checks/compose_build_paths.rb
|
|
88
|
+
- lib/docktor_rails/checks/compose_entrypoint_exec.rb
|
|
89
|
+
- lib/docktor_rails/checks/compose_entrypoint_sanity.rb
|
|
90
|
+
- lib/docktor_rails/checks/compose_env_file_present.rb
|
|
91
|
+
- lib/docktor_rails/checks/compose_file_present.rb
|
|
92
|
+
- lib/docktor_rails/checks/compose_healthchecks.rb
|
|
93
|
+
- lib/docktor_rails/checks/compose_platform_risk.rb
|
|
94
|
+
- lib/docktor_rails/checks/rails_master_key_required.rb
|
|
95
|
+
- lib/docktor_rails/checks/shell_scripts_crlf.rb
|
|
96
|
+
- lib/docktor_rails/cli.rb
|
|
97
|
+
- lib/docktor_rails/compose.rb
|
|
98
|
+
- lib/docktor_rails/dotenv.rb
|
|
99
|
+
- lib/docktor_rails/guidance.rb
|
|
100
|
+
- lib/docktor_rails/platform.rb
|
|
101
|
+
- lib/docktor_rails/preflight/runner.rb
|
|
102
|
+
- lib/docktor_rails/reporters/json_reporter.rb
|
|
103
|
+
- lib/docktor_rails/reporters/text_reporter.rb
|
|
104
|
+
- lib/docktor_rails/result.rb
|
|
105
|
+
- lib/docktor_rails/version.rb
|
|
106
|
+
- sig/docktor_rails.rbs
|
|
107
|
+
homepage: https://github.com/fallS/docktor_rails
|
|
108
|
+
licenses:
|
|
109
|
+
- MIT
|
|
110
|
+
metadata:
|
|
111
|
+
homepage_uri: https://github.com/fallS/docktor_rails
|
|
112
|
+
source_code_uri: https://github.com/fallS/docktor_rails
|
|
113
|
+
changelog_uri: https://github.com/fallS/docktor_rails/blob/main/CHANGELOG.md
|
|
114
|
+
rubygems_mfa_required: 'true'
|
|
115
|
+
post_install_message:
|
|
116
|
+
rdoc_options: []
|
|
117
|
+
require_paths:
|
|
118
|
+
- lib
|
|
119
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
120
|
+
requirements:
|
|
121
|
+
- - ">="
|
|
122
|
+
- !ruby/object:Gem::Version
|
|
123
|
+
version: 3.0.0
|
|
124
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
125
|
+
requirements:
|
|
126
|
+
- - ">="
|
|
127
|
+
- !ruby/object:Gem::Version
|
|
128
|
+
version: '0'
|
|
129
|
+
requirements: []
|
|
130
|
+
rubygems_version: 3.3.7
|
|
131
|
+
signing_key:
|
|
132
|
+
specification_version: 4
|
|
133
|
+
summary: Preflight checks for Rails Docker/Compose dev environments
|
|
134
|
+
test_files: []
|