source_monitor 0.1.1 → 0.1.2
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/CHANGELOG.md +17 -0
- data/Gemfile.lock +1 -1
- data/README.md +15 -11
- data/config/coverage_baseline.json +350 -107
- data/docs/deployment.md +8 -0
- data/docs/gh-cli-workflow.md +5 -1
- data/docs/setup-validation-log.md +36 -0
- data/docs/setup.md +144 -0
- data/lib/generators/source_monitor/install/install_generator.rb +1 -1
- data/lib/source_monitor/setup/bundle_installer.rb +17 -0
- data/lib/source_monitor/setup/cli.rb +51 -0
- data/lib/source_monitor/setup/dependency_checker.rb +165 -0
- data/lib/source_monitor/setup/detectors.rb +67 -0
- data/lib/source_monitor/setup/gemfile_editor.rb +29 -0
- data/lib/source_monitor/setup/initializer_patcher.rb +59 -0
- data/lib/source_monitor/setup/install_generator.rb +22 -0
- data/lib/source_monitor/setup/migration_installer.rb +40 -0
- data/lib/source_monitor/setup/node_installer.rb +27 -0
- data/lib/source_monitor/setup/prompter.rb +34 -0
- data/lib/source_monitor/setup/requirements.rb +51 -0
- data/lib/source_monitor/setup/shell_runner.rb +14 -0
- data/lib/source_monitor/setup/verification/action_cable_verifier.rb +79 -0
- data/lib/source_monitor/setup/verification/printer.rb +24 -0
- data/lib/source_monitor/setup/verification/result.rb +68 -0
- data/lib/source_monitor/setup/verification/runner.rb +24 -0
- data/lib/source_monitor/setup/verification/solid_queue_verifier.rb +75 -0
- data/lib/source_monitor/setup/verification/telemetry_logger.rb +33 -0
- data/lib/source_monitor/setup/workflow.rb +99 -0
- data/lib/source_monitor/version.rb +1 -1
- data/lib/source_monitor.rb +19 -0
- data/lib/tasks/source_monitor_setup.rake +39 -0
- data/tasks/prd-setup-workflow-streamlining.md +68 -0
- data/tasks/tasks-setup-workflow-streamlining.md +51 -0
- metadata +25 -2
- data/docs/installation.md +0 -144
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
require "thor"
|
|
2
|
+
|
|
3
|
+
module SourceMonitor
|
|
4
|
+
module Setup
|
|
5
|
+
class Prompter
|
|
6
|
+
def initialize(shell: Thor::Shell::Basic.new, auto_yes: false)
|
|
7
|
+
@shell = shell
|
|
8
|
+
@auto_yes = auto_yes
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def ask(question, default: nil)
|
|
12
|
+
return default if auto_yes
|
|
13
|
+
|
|
14
|
+
prompt = default ? "#{question} [#{default}]" : question
|
|
15
|
+
response = shell.ask(prompt).to_s.strip
|
|
16
|
+
response.empty? ? default : response
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def yes?(question, default: true)
|
|
20
|
+
return default if auto_yes
|
|
21
|
+
|
|
22
|
+
suffix = default ? "Y/n" : "y/N"
|
|
23
|
+
response = shell.ask("#{question} (#{suffix})").to_s.strip.downcase
|
|
24
|
+
return default if response.empty?
|
|
25
|
+
|
|
26
|
+
%w[y yes].include?(response)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
attr_reader :shell, :auto_yes
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
module SourceMonitor
|
|
2
|
+
module Setup
|
|
3
|
+
module Requirements
|
|
4
|
+
class Version
|
|
5
|
+
attr_reader :requirement
|
|
6
|
+
|
|
7
|
+
def initialize(spec)
|
|
8
|
+
@requirement = Gem::Requirement.new(spec)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def expected
|
|
12
|
+
requirement.to_s
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def normalize(value)
|
|
16
|
+
return if value.blank?
|
|
17
|
+
|
|
18
|
+
normalized = value.to_s.strip.sub(/^v/i, "")
|
|
19
|
+
Gem::Version.new(normalized)
|
|
20
|
+
rescue ArgumentError
|
|
21
|
+
nil
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def satisfied?(value)
|
|
25
|
+
version = normalize(value)
|
|
26
|
+
return false unless version
|
|
27
|
+
|
|
28
|
+
requirement.satisfied_by?(version)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
class Adapter
|
|
33
|
+
attr_reader :expected
|
|
34
|
+
|
|
35
|
+
def initialize(expected)
|
|
36
|
+
@expected = expected.to_s
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def normalize(value)
|
|
40
|
+
value&.to_s
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def satisfied?(value)
|
|
44
|
+
return false if value.blank?
|
|
45
|
+
|
|
46
|
+
value.to_s.casecmp(expected).zero?
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
module SourceMonitor
|
|
2
|
+
module Setup
|
|
3
|
+
module Verification
|
|
4
|
+
class ActionCableVerifier
|
|
5
|
+
def initialize(config: SourceMonitor.config.realtime, cable_config: default_cable_config, connection: default_connection)
|
|
6
|
+
@config = config
|
|
7
|
+
@cable_config = cable_config
|
|
8
|
+
@connection = connection
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def call
|
|
12
|
+
case adapter
|
|
13
|
+
when :solid_cable
|
|
14
|
+
verify_solid_cable
|
|
15
|
+
when :redis
|
|
16
|
+
verify_redis
|
|
17
|
+
else
|
|
18
|
+
warning_result("Realtime adapter #{adapter.inspect} is not recognized", "Set config.realtime.adapter to :solid_cable or :redis in the initializer")
|
|
19
|
+
end
|
|
20
|
+
rescue StandardError => e
|
|
21
|
+
error_result("Action Cable verification failed: #{e.message}", "Double-check Action Cable configuration and credentials")
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
attr_reader :config, :cable_config, :connection
|
|
27
|
+
|
|
28
|
+
def adapter
|
|
29
|
+
config.adapter
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def default_cable_config
|
|
33
|
+
ActionCable.server.config.cable
|
|
34
|
+
rescue StandardError
|
|
35
|
+
{}
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def default_connection
|
|
39
|
+
ActiveRecord::Base.connection
|
|
40
|
+
rescue StandardError
|
|
41
|
+
nil
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def verify_solid_cable
|
|
45
|
+
unless defined?(SolidCable)
|
|
46
|
+
return error_result("Solid Cable gem is not loaded", "Add `solid_cable` to your Gemfile or switch to the Redis adapter")
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
unless connection&.table_exists?("solid_cable_messages")
|
|
50
|
+
return error_result("Solid Cable tables are missing", "Run `rails solid_cable:install` or copy the engine migration that creates solid_cable_messages")
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
ok_result("Solid Cable tables detected and the gem is loaded")
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def verify_redis
|
|
57
|
+
url = config.redis_url.presence || cable_config[:url]
|
|
58
|
+
if url.blank?
|
|
59
|
+
return error_result("Redis adapter configured without a URL", "Set config.realtime.redis_url or supply :url in your cable.yml")
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
ok_result("Redis Action Cable configuration detected (#{url})")
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def ok_result(details)
|
|
66
|
+
Result.new(key: :action_cable, name: "Action Cable", status: :ok, details: details)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def warning_result(details, remediation)
|
|
70
|
+
Result.new(key: :action_cable, name: "Action Cable", status: :warning, details: details, remediation: remediation)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def error_result(details, remediation)
|
|
74
|
+
Result.new(key: :action_cable, name: "Action Cable", status: :error, details: details, remediation: remediation)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
module SourceMonitor
|
|
2
|
+
module Setup
|
|
3
|
+
module Verification
|
|
4
|
+
class Printer
|
|
5
|
+
def initialize(shell: Thor::Shell::Basic.new)
|
|
6
|
+
@shell = shell
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def print(summary)
|
|
10
|
+
shell.say("Verification summary (#{summary.overall_status.upcase}):")
|
|
11
|
+
summary.results.each do |result|
|
|
12
|
+
shell.say("- #{result.name}: #{result.status.upcase} - #{result.details}")
|
|
13
|
+
shell.say(" Remediation: #{result.remediation}") if result.remediation.present?
|
|
14
|
+
end
|
|
15
|
+
shell.say("JSON: #{summary.to_json}")
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
attr_reader :shell
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
|
|
3
|
+
module SourceMonitor
|
|
4
|
+
module Setup
|
|
5
|
+
module Verification
|
|
6
|
+
Result = Struct.new(
|
|
7
|
+
:key,
|
|
8
|
+
:name,
|
|
9
|
+
:status,
|
|
10
|
+
:details,
|
|
11
|
+
:remediation,
|
|
12
|
+
keyword_init: true
|
|
13
|
+
) do
|
|
14
|
+
def ok?
|
|
15
|
+
status == :ok
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def warning?
|
|
19
|
+
status == :warning
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def error?
|
|
23
|
+
status == :error
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def as_json(_options = nil)
|
|
27
|
+
{
|
|
28
|
+
key: key,
|
|
29
|
+
name: name,
|
|
30
|
+
status: status,
|
|
31
|
+
details: details,
|
|
32
|
+
remediation: remediation
|
|
33
|
+
}
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
class Summary
|
|
38
|
+
attr_reader :results
|
|
39
|
+
|
|
40
|
+
def initialize(results)
|
|
41
|
+
@results = results
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def overall_status
|
|
45
|
+
return :error if results.any?(&:error?)
|
|
46
|
+
return :warning if results.any?(&:warning?)
|
|
47
|
+
|
|
48
|
+
:ok
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def ok?
|
|
52
|
+
overall_status == :ok
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def to_h
|
|
56
|
+
{
|
|
57
|
+
overall_status: overall_status,
|
|
58
|
+
results: results.map(&:as_json)
|
|
59
|
+
}
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def to_json(*args)
|
|
63
|
+
to_h.to_json(*args)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
module SourceMonitor
|
|
2
|
+
module Setup
|
|
3
|
+
module Verification
|
|
4
|
+
class Runner
|
|
5
|
+
def initialize(verifiers: default_verifiers)
|
|
6
|
+
@verifiers = verifiers
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def call
|
|
10
|
+
results = verifiers.map { |verifier| verifier.call }
|
|
11
|
+
Summary.new(results)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
private
|
|
15
|
+
|
|
16
|
+
attr_reader :verifiers
|
|
17
|
+
|
|
18
|
+
def default_verifiers
|
|
19
|
+
[ SolidQueueVerifier.new, ActionCableVerifier.new ]
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
module SourceMonitor
|
|
2
|
+
module Setup
|
|
3
|
+
module Verification
|
|
4
|
+
class SolidQueueVerifier
|
|
5
|
+
DEFAULT_HEARTBEAT_THRESHOLD = 2.minutes
|
|
6
|
+
|
|
7
|
+
def initialize(process_relation: default_process_relation, connection: default_connection, clock: -> { Time.current })
|
|
8
|
+
@process_relation = process_relation
|
|
9
|
+
@connection = connection
|
|
10
|
+
@clock = clock
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def call
|
|
14
|
+
return missing_gem_result unless process_relation
|
|
15
|
+
return missing_tables_result unless tables_present?
|
|
16
|
+
|
|
17
|
+
recent = recent_workers?
|
|
18
|
+
|
|
19
|
+
if recent
|
|
20
|
+
ok_result("Solid Queue workers are reporting heartbeats")
|
|
21
|
+
else
|
|
22
|
+
warning_result("No Solid Queue workers have reported in the last #{DEFAULT_HEARTBEAT_THRESHOLD.inspect}", "Start a Solid Queue worker with `bin/rails solid_queue:start` and ensure it stays running")
|
|
23
|
+
end
|
|
24
|
+
rescue StandardError => e
|
|
25
|
+
error_result("Solid Queue verification failed: #{e.message}", "Verify Solid Queue migrations are up to date and workers can access the database")
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
attr_reader :process_relation, :connection, :clock
|
|
31
|
+
|
|
32
|
+
def default_process_relation
|
|
33
|
+
SolidQueue::Process if defined?(SolidQueue::Process)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def default_connection
|
|
37
|
+
SolidQueue::Process.connection if defined?(SolidQueue::Process)
|
|
38
|
+
rescue StandardError
|
|
39
|
+
nil
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def tables_present?
|
|
43
|
+
return false unless connection
|
|
44
|
+
|
|
45
|
+
connection.table_exists?(process_relation.table_name)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def recent_workers?
|
|
49
|
+
cutoff = clock.call - DEFAULT_HEARTBEAT_THRESHOLD
|
|
50
|
+
process_relation.where("last_heartbeat_at >= ?", cutoff).exists?
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def missing_gem_result
|
|
54
|
+
error_result("Solid Queue gem is not available", "Add `solid_queue` to your Gemfile and bundle install")
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def missing_tables_result
|
|
58
|
+
error_result("Solid Queue tables are missing", "Run `rails solid_queue:install` or copy the engine's Solid Queue migration")
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def ok_result(details)
|
|
62
|
+
Result.new(key: :solid_queue, name: "Solid Queue", status: :ok, details: details)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def warning_result(details, remediation)
|
|
66
|
+
Result.new(key: :solid_queue, name: "Solid Queue", status: :warning, details: details, remediation: remediation)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def error_result(details, remediation)
|
|
70
|
+
Result.new(key: :solid_queue, name: "Solid Queue", status: :error, details: details, remediation: remediation)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
require "fileutils"
|
|
2
|
+
require "pathname"
|
|
3
|
+
|
|
4
|
+
module SourceMonitor
|
|
5
|
+
module Setup
|
|
6
|
+
module Verification
|
|
7
|
+
class TelemetryLogger
|
|
8
|
+
def initialize(path: nil)
|
|
9
|
+
@path = Pathname.new(path || default_path)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def log(summary)
|
|
13
|
+
FileUtils.mkdir_p(path.dirname)
|
|
14
|
+
path.open("a") do |file|
|
|
15
|
+
file.puts({ timestamp: Time.current.iso8601, payload: summary.to_h }.to_json)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
attr_reader :path
|
|
22
|
+
|
|
23
|
+
def default_path
|
|
24
|
+
if defined?(Rails) && Rails.respond_to?(:root) && Rails.root
|
|
25
|
+
Rails.root.join("log", "source_monitor_setup.log")
|
|
26
|
+
else
|
|
27
|
+
Pathname.new("log/source_monitor_setup.log")
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
require "pathname"
|
|
2
|
+
|
|
3
|
+
module SourceMonitor
|
|
4
|
+
module Setup
|
|
5
|
+
class Workflow
|
|
6
|
+
DEFAULT_MOUNT_PATH = "/source_monitor".freeze
|
|
7
|
+
|
|
8
|
+
class RequirementError < StandardError
|
|
9
|
+
attr_reader :summary
|
|
10
|
+
|
|
11
|
+
def initialize(summary)
|
|
12
|
+
@summary = summary
|
|
13
|
+
super(build_message)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
private
|
|
17
|
+
|
|
18
|
+
def build_message
|
|
19
|
+
messages = summary.errors.map do |result|
|
|
20
|
+
"#{result.name}: #{result.remediation}".strip
|
|
21
|
+
end
|
|
22
|
+
"Setup requirements failed. #{messages.join(' ')}"
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def initialize(
|
|
27
|
+
dependency_checker: DependencyChecker.new,
|
|
28
|
+
prompter: Prompter.new,
|
|
29
|
+
gemfile_editor: GemfileEditor.new,
|
|
30
|
+
bundle_installer: BundleInstaller.new,
|
|
31
|
+
node_installer: NodeInstaller.new,
|
|
32
|
+
install_generator: InstallGenerator.new,
|
|
33
|
+
migration_installer: MigrationInstaller.new,
|
|
34
|
+
initializer_patcher: InitializerPatcher.new,
|
|
35
|
+
devise_detector: method(:default_devise_detector),
|
|
36
|
+
verifier: Verification::Runner.new
|
|
37
|
+
)
|
|
38
|
+
@dependency_checker = dependency_checker
|
|
39
|
+
@prompter = prompter
|
|
40
|
+
@gemfile_editor = gemfile_editor
|
|
41
|
+
@bundle_installer = bundle_installer
|
|
42
|
+
@node_installer = node_installer
|
|
43
|
+
@install_generator = install_generator
|
|
44
|
+
@migration_installer = migration_installer
|
|
45
|
+
@initializer_patcher = initializer_patcher
|
|
46
|
+
@devise_detector = devise_detector
|
|
47
|
+
@verifier = verifier
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def run
|
|
51
|
+
summary = dependency_checker.call
|
|
52
|
+
raise RequirementError, summary if summary.errors?
|
|
53
|
+
|
|
54
|
+
mount_path = prompter.ask("Mount SourceMonitor at which path?", default: DEFAULT_MOUNT_PATH)
|
|
55
|
+
|
|
56
|
+
gemfile_editor.ensure_entry
|
|
57
|
+
bundle_installer.install
|
|
58
|
+
node_installer.install_if_needed
|
|
59
|
+
install_generator.run(mount_path: mount_path)
|
|
60
|
+
migration_installer.install
|
|
61
|
+
initializer_patcher.ensure_navigation_hint(mount_path: mount_path)
|
|
62
|
+
|
|
63
|
+
if devise_available? && prompter.yes?("Wire Devise authentication hooks into SourceMonitor?", default: true)
|
|
64
|
+
initializer_patcher.ensure_devise_hooks
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
verifier.call
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
private
|
|
71
|
+
|
|
72
|
+
attr_reader :dependency_checker,
|
|
73
|
+
:prompter,
|
|
74
|
+
:gemfile_editor,
|
|
75
|
+
:bundle_installer,
|
|
76
|
+
:node_installer,
|
|
77
|
+
:install_generator,
|
|
78
|
+
:migration_installer,
|
|
79
|
+
:initializer_patcher,
|
|
80
|
+
:devise_detector,
|
|
81
|
+
:verifier
|
|
82
|
+
|
|
83
|
+
def devise_available?
|
|
84
|
+
!!devise_detector.call
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def default_devise_detector
|
|
88
|
+
Gem.loaded_specs.key?("devise") || gemfile_mentions_devise?
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def gemfile_mentions_devise?
|
|
92
|
+
gemfile = Pathname.new("Gemfile")
|
|
93
|
+
gemfile.exist? && gemfile.read.include?("devise")
|
|
94
|
+
rescue StandardError
|
|
95
|
+
false
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
data/lib/source_monitor.rb
CHANGED
|
@@ -81,6 +81,25 @@ require "source_monitor/scheduler"
|
|
|
81
81
|
require "source_monitor/items/item_creator"
|
|
82
82
|
require "source_monitor/health"
|
|
83
83
|
require "source_monitor/assets"
|
|
84
|
+
require "source_monitor/setup/requirements"
|
|
85
|
+
require "source_monitor/setup/shell_runner"
|
|
86
|
+
require "source_monitor/setup/detectors"
|
|
87
|
+
require "source_monitor/setup/dependency_checker"
|
|
88
|
+
require "source_monitor/setup/prompter"
|
|
89
|
+
require "source_monitor/setup/gemfile_editor"
|
|
90
|
+
require "source_monitor/setup/bundle_installer"
|
|
91
|
+
require "source_monitor/setup/node_installer"
|
|
92
|
+
require "source_monitor/setup/install_generator"
|
|
93
|
+
require "source_monitor/setup/migration_installer"
|
|
94
|
+
require "source_monitor/setup/initializer_patcher"
|
|
95
|
+
require "source_monitor/setup/verification/result"
|
|
96
|
+
require "source_monitor/setup/verification/solid_queue_verifier"
|
|
97
|
+
require "source_monitor/setup/verification/action_cable_verifier"
|
|
98
|
+
require "source_monitor/setup/verification/runner"
|
|
99
|
+
require "source_monitor/setup/verification/printer"
|
|
100
|
+
require "source_monitor/setup/verification/telemetry_logger"
|
|
101
|
+
require "source_monitor/setup/workflow"
|
|
102
|
+
require "source_monitor/setup/cli"
|
|
84
103
|
|
|
85
104
|
module SourceMonitor
|
|
86
105
|
class << self
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
namespace :source_monitor do
|
|
2
|
+
namespace :setup do
|
|
3
|
+
desc "Verify host dependencies before running the guided SourceMonitor installer"
|
|
4
|
+
task check: :environment do
|
|
5
|
+
summary = SourceMonitor::Setup::DependencyChecker.new.call
|
|
6
|
+
|
|
7
|
+
puts "SourceMonitor dependency check:" # rubocop:disable Rails/Output
|
|
8
|
+
summary.results.each do |result|
|
|
9
|
+
status = result.status.to_s.upcase
|
|
10
|
+
current = result.current ? result.current.to_s : "missing"
|
|
11
|
+
expected = result.expected || "n/a"
|
|
12
|
+
puts "- #{result.name}: #{status} (current: #{current}, required: #{expected})" # rubocop:disable Rails/Output
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
if summary.errors?
|
|
16
|
+
messages = summary.errors.map do |result|
|
|
17
|
+
"#{result.name}: #{result.remediation}"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
raise "SourceMonitor setup requirements failed. #{messages.join(' ')}"
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
desc "Verify queue workers, Action Cable, and telemetry hooks"
|
|
25
|
+
task verify: :environment do
|
|
26
|
+
summary = SourceMonitor::Setup::Verification::Runner.new.call
|
|
27
|
+
printer = SourceMonitor::Setup::Verification::Printer.new
|
|
28
|
+
printer.print(summary)
|
|
29
|
+
|
|
30
|
+
if ENV["SOURCE_MONITOR_SETUP_TELEMETRY"].present?
|
|
31
|
+
SourceMonitor::Setup::Verification::TelemetryLogger.new.log(summary)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
unless summary.ok?
|
|
35
|
+
raise "SourceMonitor setup verification failed. See output above for remediation steps."
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# Streamlined Setup Workflow for SourceMonitor Engine
|
|
2
|
+
|
|
3
|
+
## 1. Introduction / Overview
|
|
4
|
+
Teams currently follow a long, error-prone checklist to integrate the SourceMonitor engine into either brand-new or existing Rails 8 hosts. Manual gem edits, generator invocations, migration copying, authentication wiring, and Solid Queue/Solid Cable verification create setup drift and delayed feedback when something fails. This PRD defines a streamlined workflow that combines authoritative checklists with guided automation (scripts/generators) so users can reliably install, mount, and validate the engine with minimal switching between docs and terminals. The primary goal is to reduce manual tasks and error risk when onboarding SourceMonitor into any Rails app that meets the core prerequisites.
|
|
5
|
+
|
|
6
|
+
## 2. Goals
|
|
7
|
+
- Reduce time-to-first-successful SourceMonitor dashboard load (including background job + Action Cable verification) to under 15 minutes for both new and existing hosts.
|
|
8
|
+
- Provide a single guided setup command (or task) that performs/coordinates all feasible installation steps while surfacing next actions inline.
|
|
9
|
+
- Deliver updated documentation/checklists that stay in sync with the guided workflow and clearly call out remaining manual steps.
|
|
10
|
+
- Ensure Solid Queue/Solid Cable (with optional Redis Action Cable fallback) are validated automatically before marking setup complete.
|
|
11
|
+
- Instrument the workflow to capture pass/fail telemetry for each step (stored locally/logged) to aid support and future improvements.
|
|
12
|
+
|
|
13
|
+
## 3. User Stories
|
|
14
|
+
- **New Rails app maintainer:** “As a developer bootstrapping a fresh Rails 8.0.2.1+ app, I want a single command that adds SourceMonitor, runs the install generator, copies migrations, and verifies Solid Queue so I can start monitoring feeds without memorizing every prerequisite.”
|
|
15
|
+
- **Existing app maintainer:** “As a developer integrating SourceMonitor into an established Rails codebase, I want the installer to detect what’s already configured (Devise, Solid Queue migrations, Action Cable adapter) and only prompt me for missing pieces.”
|
|
16
|
+
- **Release engineer:** “As the person responsible for the release checklist, I want deterministic logs from the setup workflow so I can confirm every environment (staging, prod) passes the same validation steps.”
|
|
17
|
+
|
|
18
|
+
## 4. Functional Requirements
|
|
19
|
+
1. Provide a new CLI entry point (rails source_monitor:setup`) that orchestrates the onboarding steps end-to-end, supporting both fresh and existing hosts.
|
|
20
|
+
2. The setup command must:
|
|
21
|
+
- Detect prerequisite versions (Ruby ≥ 3.4.4, Rails ≥ 8.0.2.1, PostgreSQL, Node ≥ 18) and halt with actionable remediation steps when mismatched.
|
|
22
|
+
- Offer to append the `source_monitor` gem declaration if missing, then run `bundle install` (respecting rbenv) with progress output.
|
|
23
|
+
- Run `npm install` only if the host manages node tooling and dependencies changed.
|
|
24
|
+
- Invoke `source_monitor:install` with a user-confirmed mount path (default `/source_monitor`) and confirm the engine route is reachable.
|
|
25
|
+
- Copy migrations via `railties:install:migrations FROM=source_monitor`, deduplicate Solid Queue tables if already present, and run `db:migrate`.
|
|
26
|
+
- Create/patch `config/initializers/source_monitor.rb` with sensible defaults plus TODO comments for any unresolved configuration (Mission Control, custom queues, etc.).
|
|
27
|
+
- Offer guided prompts (Y/N) for optional Devise wiring, with snippets inserted only when the host uses Devise (detected via Bundler).
|
|
28
|
+
- Verify Solid Queue worker availability by optionally starting a transient worker or pinging existing processes, logging failures with remediation.
|
|
29
|
+
- Verify Action Cable by ensuring either Solid Cable tables exist or Redis adapter credentials are configured; provide auto-detection and optional smoke ping.
|
|
30
|
+
3. Emit a structured summary (JSON + human-readable table) at the end of the setup run showing which steps succeeded, which need manual follow-up, and links to docs.
|
|
31
|
+
4. Create an updated checklist document (living under `docs/setup.md`) that mirrors the automated steps, clarifies manual-only tasks (e.g., navigation link updates, Mission Control wiring), and references the new command.
|
|
32
|
+
5. Expose a reusable verification task (e.g., `rails source_monitor:verify_install`) that can be run in CI to confirm migrations, queues, and Action Cable remain healthy after upgrades.
|
|
33
|
+
6. Ensure the workflow supports Solid Queue/Solid Cable by default and optionally configures Redis for Action Cable when the host opts in (prompt + adapter switch helper).
|
|
34
|
+
7. Add minimal telemetry hooks (log lines or optional webhook) so support teams can request the last setup report when debugging customer installs.
|
|
35
|
+
8. Provide sample scripts or templates for integrating a basic Devise-backed system test (sign-in + visit mount path) but mark it as optional to keep scope aligned with must-have validations (engine + background job success).
|
|
36
|
+
9. Document how to roll back the setup (e.g., remove gem, initializer, route) to encourage experimentation without risk.
|
|
37
|
+
|
|
38
|
+
## 5. Non-Goals (Out of Scope)
|
|
39
|
+
- Supporting non-PostgreSQL databases or Rails versions below 8.0.2.1.
|
|
40
|
+
- Full automation of Devise or other authentication stacks (provide guidance and optional snippets only).
|
|
41
|
+
- Provisioning or managing Redis/Solid Queue infrastructure beyond verifying connectivity and migrations.
|
|
42
|
+
- Replacing the existing CI pipeline; the focus is on installation workflow, not release automation.
|
|
43
|
+
- Creating GUI installers or web-based setup wizards—command-line + documentation only.
|
|
44
|
+
|
|
45
|
+
## 6. Design Considerations
|
|
46
|
+
- Provide dry-run and verbose flags so advanced users can inspect actions without modifying their apps.
|
|
47
|
+
- Structure setup steps as discrete service objects to enable reuse in future generators and tests.
|
|
48
|
+
- Store mount path and adapter decisions in `config/source_monitor.yml` to keep the initializer tidy.
|
|
49
|
+
- When Redis is selected for Action Cable, generate template credentials entries and highlight security requirements.
|
|
50
|
+
|
|
51
|
+
## 7. Technical Considerations
|
|
52
|
+
- Rails generators for CLI interactions to reuse existing generator patterns and support prompts.
|
|
53
|
+
- Respect rbenv by invoking Ruby/Bundler via `ENV["RBENV_ROOT"]` shims (mirroring existing binstubs).
|
|
54
|
+
- Ensure idempotency: rerunning the setup command should detect completed steps and skip or re-verify without breaking the app.
|
|
55
|
+
- For migration deduplication, compare checksum filenames before copying to avoid duplicates in existing hosts.
|
|
56
|
+
- Telemetry/logging should default to local file output (e.g., `log/source_monitor_setup.log`) with an opt-in environment variable for remote reporting.
|
|
57
|
+
|
|
58
|
+
## 8. Success Metrics
|
|
59
|
+
- ≥80% of beta users report completing setup in ≤15 minutes (collected via follow-up survey/logs).
|
|
60
|
+
- Support tickets related to initial installation drop by 50% quarter-over-quarter.
|
|
61
|
+
- Automated verification task adopted in CI for at least 3 internal environments within two weeks of release.
|
|
62
|
+
- Zero critical setup regressions reported within the first month after launch (monitored via issue tracker).
|
|
63
|
+
|
|
64
|
+
## 9. Open Questions
|
|
65
|
+
- Should the setup command automatically create a nav link in host layouts, or simply print instructions?
|
|
66
|
+
- How should we detect whether `npm install` is necessary in hosts that vend their own asset pipeline (e.g., Propshaft, esbuild)?
|
|
67
|
+
- Do we need to version-gate the workflow for future Rails releases (8.1, 8.2), and how will we communicate incompatibilities?
|
|
68
|
+
- Is telemetry opt-in per run (flag) or opt-out (environment variable)?
|