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.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +17 -0
  3. data/Gemfile.lock +1 -1
  4. data/README.md +15 -11
  5. data/config/coverage_baseline.json +350 -107
  6. data/docs/deployment.md +8 -0
  7. data/docs/gh-cli-workflow.md +5 -1
  8. data/docs/setup-validation-log.md +36 -0
  9. data/docs/setup.md +144 -0
  10. data/lib/generators/source_monitor/install/install_generator.rb +1 -1
  11. data/lib/source_monitor/setup/bundle_installer.rb +17 -0
  12. data/lib/source_monitor/setup/cli.rb +51 -0
  13. data/lib/source_monitor/setup/dependency_checker.rb +165 -0
  14. data/lib/source_monitor/setup/detectors.rb +67 -0
  15. data/lib/source_monitor/setup/gemfile_editor.rb +29 -0
  16. data/lib/source_monitor/setup/initializer_patcher.rb +59 -0
  17. data/lib/source_monitor/setup/install_generator.rb +22 -0
  18. data/lib/source_monitor/setup/migration_installer.rb +40 -0
  19. data/lib/source_monitor/setup/node_installer.rb +27 -0
  20. data/lib/source_monitor/setup/prompter.rb +34 -0
  21. data/lib/source_monitor/setup/requirements.rb +51 -0
  22. data/lib/source_monitor/setup/shell_runner.rb +14 -0
  23. data/lib/source_monitor/setup/verification/action_cable_verifier.rb +79 -0
  24. data/lib/source_monitor/setup/verification/printer.rb +24 -0
  25. data/lib/source_monitor/setup/verification/result.rb +68 -0
  26. data/lib/source_monitor/setup/verification/runner.rb +24 -0
  27. data/lib/source_monitor/setup/verification/solid_queue_verifier.rb +75 -0
  28. data/lib/source_monitor/setup/verification/telemetry_logger.rb +33 -0
  29. data/lib/source_monitor/setup/workflow.rb +99 -0
  30. data/lib/source_monitor/version.rb +1 -1
  31. data/lib/source_monitor.rb +19 -0
  32. data/lib/tasks/source_monitor_setup.rake +39 -0
  33. data/tasks/prd-setup-workflow-streamlining.md +68 -0
  34. data/tasks/tasks-setup-workflow-streamlining.md +51 -0
  35. metadata +25 -2
  36. data/docs/installation.md +0 -144
@@ -0,0 +1,36 @@
1
+ # Setup Workflow Validation Notes
2
+
3
+ Date: 2025-11-13
4
+ Engineer: Codex agent
5
+
6
+ ## Scenario A – Fresh Rails Host (cloned from dummy app)
7
+
8
+ Steps:
9
+ 1. Copied `test/dummy` to `tmp/setup_validation` to simulate a pristine host.
10
+ 2. Ran `../../bin/source_monitor install --yes` from the copied directory.
11
+
12
+ Findings:
13
+ - The first pass surfaced two actionable items:
14
+ - Gemfile duplication (`eval_gemfile '../../Gemfile'` already includes `source_monitor`). Deleted the appended line to unblock bundler.
15
+ - Final verification exited with `WARNING` because no Solid Queue workers were running yet. Inserted a synthetic `SolidQueue::Process` row via `bundle exec rails runner` to mimic a heartbeat before re-running verify.
16
+ - Subsequent `../../bin/source_monitor verify` returned:
17
+ ```
18
+ Verification summary (OK):
19
+ - Solid Queue: OK - Solid Queue workers are reporting heartbeats
20
+ - Action Cable: OK - Solid Cable tables detected and the gem is loaded
21
+ ```
22
+ - Re-running the installer after the fixes proved idempotent and completed with an `OK` summary.
23
+
24
+ ## Scenario B – Existing Host (same copy, post-install)
25
+
26
+ Steps:
27
+ 1. With the same working copy (representing an already-configured app), ran `../../bin/source_monitor install --yes` again.
28
+ 2. Verified that no files were changed beyond timestamp updates and the workflow skipped migration copies/Devise hooks as expected.
29
+
30
+ Findings:
31
+ - Second run finished cleanly with the same `OK` verification summary. Demonstrates safe re-entry for upgrades/CI.
32
+
33
+ ## Follow-ups
34
+
35
+ - The Gemfile duplication edge case only applies to internal test harnesses that `eval_gemfile '../../Gemfile'`. Production hosts should add the gem once, but we may consider enhancing `GemfileEditor` to detect `eval_gemfile` references to avoid duplicate insertion.
36
+ - A running (or synthetic) Solid Queue process is required for the verification step to return `OK`. Documented expectations in `docs/setup.md` and highlighted the remediation string emitted by the verifier.
data/docs/setup.md ADDED
@@ -0,0 +1,144 @@
1
+ # SourceMonitor Setup Workflow
2
+
3
+ This guide consolidates the new guided installer, verification commands, and rollback steps so teams can onboard the engine into either a fresh Rails host or an existing application without missing prerequisites.
4
+
5
+ ## Prerequisites
6
+
7
+ | Requirement | Minimum | Notes |
8
+ | --- | --- | --- |
9
+ | Ruby | 3.4.4 | Use rbenv and match the engine's `.ruby-version`. |
10
+ | Rails | 8.0.2.1 | Run `bin/rails about` inside the host to confirm. |
11
+ | PostgreSQL | 14+ | Required for Solid Queue tables and item storage. |
12
+ | Node.js | 18+ | Needed for Tailwind/esbuild assets when the host owns node tooling. |
13
+ | Background jobs | Solid Queue (>= 0.3, < 3.0) | Add `solid_queue` to the host Gemfile if not present. |
14
+ | Realtime | Solid Cable (>= 3.0) or Redis | Solid Cable is the default; Redis requires `config.realtime.adapter = :redis`. |
15
+
16
+ ## Guided Setup (Recommended)
17
+
18
+ 1. **Check prerequisites** (optional but fast):
19
+ ```bash
20
+ bin/rails source_monitor:setup:check
21
+ ```
22
+ This invokes the dependency checker added in Phase 10.04 and surfaces remediation text when versions or adapters are missing.
23
+
24
+ 2. **Run the guided installer:**
25
+ ```bash
26
+ bin/source_monitor install
27
+ ```
28
+ - Prompts for the mount path (defaults to `/source_monitor`).
29
+ - Ensures `gem "source_monitor"` is in the host Gemfile and runs `bundle install` via rbenv shims.
30
+ - Runs `npm install` when `package.json` exists.
31
+ - Executes `bin/rails generate source_monitor:install --mount-path=...`.
32
+ - Copies migrations, deduplicates duplicate Solid Queue migrations, and reruns `bin/rails db:migrate`.
33
+ - Updates `config/initializers/source_monitor.rb` with a navigation hint and, when desired, Devise hooks.
34
+ - Writes a verification report at the end (same as `bin/source_monitor verify`).
35
+
36
+ 3. **Start background workers:**
37
+ ```bash
38
+ bin/rails solid_queue:start
39
+ bin/jobs --recurring_schedule_file=config/recurring.yml # optional recurring scheduler
40
+ ```
41
+
42
+ 4. **Visit the dashboard** at the chosen mount path, create a source, and trigger “Fetch Now” to validate realtime updates and Solid Queue processing.
43
+
44
+ ### Fully Non-Interactive Install
45
+
46
+ Use `--yes` to accept defaults (mount path `/source_monitor`, Devise hooks enabled if Devise detected):
47
+ ```bash
48
+ bin/source_monitor install --yes
49
+ ```
50
+
51
+ ### Verification & Telemetry
52
+
53
+ - Re-run verification anytime:
54
+ ```bash
55
+ bin/source_monitor verify
56
+ ```
57
+ or
58
+ ```bash
59
+ bin/rails source_monitor:setup:verify
60
+ ```
61
+ - Results show human-friendly lines plus a JSON blob; exit status is non-zero when any check fails.
62
+ - To persist telemetry for support, set `SOURCE_MONITOR_SETUP_TELEMETRY=true`. Logs append to `log/source_monitor_setup.log`.
63
+
64
+ ## Manual Installation (Advanced)
65
+
66
+ Prefer to script each step or plug SourceMonitor into an existing deployment checklist? Use the manual flow below. It mirrors the guided CLI internals while keeping every command explicit.
67
+
68
+ ### Quick Reference
69
+
70
+ | Step | Command | Purpose |
71
+ | --- | --- | --- |
72
+ | 1 | `gem "source_monitor", github: "dchuk/source_monitor"` | Add the engine to your Gemfile (skip if already present) |
73
+ | 2 | `bundle install` | Install Ruby dependencies |
74
+ | 3 | `bin/rails generate source_monitor:install --mount-path=/source_monitor` | Mount the engine and create the initializer |
75
+ | 4 | `bin/rails railties:install:migrations FROM=source_monitor` | Copy engine migrations (idempotent) |
76
+ | 5 | `bin/rails db:migrate` | Apply schema updates, including Solid Queue tables |
77
+ | 6 | `bin/rails solid_queue:start` | Ensure jobs process via Solid Queue |
78
+ | 7 | `bin/jobs --recurring_schedule_file=config/recurring.yml` | Start recurring scheduler (optional but recommended) |
79
+ | 8 | `bin/source_monitor verify` | Confirm Solid Queue/Action Cable readiness and emit telemetry |
80
+
81
+ > Tip: You can drop these commands directly into your CI pipeline or release scripts. The CLI uses the same services, so mixing and matching is safe.
82
+
83
+ ### Step-by-step Details
84
+
85
+ 1. **Add the gem** to the host `Gemfile` (GitHub edge or released version) and run `bundle install`. If your host manages node tooling, run `npm install` also.
86
+ 2. **Install the engine** via `bin/rails generate source_monitor:install --mount-path=/source_monitor`. The generator mounts the engine, creates `config/initializers/source_monitor.rb`, and prints follow-up instructions. Re-running the generator is safe; it detects existing mounts/initializers.
87
+ 3. **Copy migrations** with `bin/rails railties:install:migrations FROM=source_monitor`. This brings in the SourceMonitor tables plus Solid Cable/Queue schema when needed. The command is idempotent—run it again after upgrading the gem.
88
+ 4. **Apply database changes** using `bin/rails db:migrate`. If your host already installed Solid Queue migrations manually, delete duplicate files before migrating.
89
+ 5. **Wire Action Cable** if necessary. SourceMonitor defaults to Solid Cable; confirm `ApplicationCable::Connection`/`Channel` exist and that `config/initializers/source_monitor.rb` uses the adapter you expect. To switch to Redis, set `config.realtime.adapter = :redis` and `config.realtime.redis_url`.
90
+ 6. **Start workers** with `bin/rails solid_queue:start` (or your process manager). Add a recurring process via `bin/jobs --recurring_schedule_file=config/recurring.yml` when you need fetch/scrape schedules.
91
+ 7. **Review the initializer** and tune queue names, HTTP timeouts, scraping adapters, retention limits, authentication hooks, and Mission Control integration. The [configuration reference](configuration.md) details every option.
92
+ 8. **Verify the install**: run `bin/source_monitor verify` to ensure Solid Queue workers and Action Cable are healthy, then visit the mount path to trigger a fetch manually. Enable telemetry if you want JSON logs recorded for support.
93
+
94
+ ### Host Compatibility Matrix
95
+
96
+ | Host Scenario | Status | Notes |
97
+ | --- | --- | --- |
98
+ | Rails 8 full-stack app | ✅ Supported | Use the guided workflow or the manual generator steps above |
99
+ | Rails 8 API-only app (`--api`) | ✅ Supported | Generator mounts engine; provide your own UI entry point if needed |
100
+ | Dedicated Solid Queue database | ✅ Supported | Run `bin/rails solid_queue:install` in the host app before copying SourceMonitor migrations |
101
+ | Redis-backed Action Cable | ✅ Supported | Set `config.realtime.adapter = :redis` and provide `config.realtime.redis_url`; existing `config/cable.yml` entries are preserved |
102
+
103
+ ## Rollback Steps
104
+
105
+ If you need to revert the integration in a host app:
106
+
107
+ 1. Remove `gem "source_monitor"` from the host Gemfile and rerun `bundle install`.
108
+ 2. Delete the engine initializer (`config/initializers/source_monitor.rb`) and any navigation links referencing the mount path.
109
+ 3. Remove the mount entry from `config/routes.rb` (the install generator adds a comment to help locate it).
110
+ 4. Drop SourceMonitor tables if they are no longer needed:
111
+ ```bash
112
+ bin/rails db:migrate:down VERSION=<timestamp> # repeat for each engine migration
113
+ ```
114
+ 5. Remove Solid Queue / Solid Cable migrations only if no other components rely on them.
115
+
116
+ Document each removal in the host application's changelog to keep future upgrades predictable.
117
+
118
+ ## Optional Devise System Test Template
119
+
120
+ Add a guardrail test in the host app (or dummy) to make sure authentication protects the dashboard after upgrades:
121
+
122
+ ```ruby
123
+ # test/system/source_monitor_setup_test.rb
124
+ require "application_system_test_case"
125
+
126
+ class SourceMonitorSetupTest < ApplicationSystemTestCase
127
+ test "signed in admin can reach SourceMonitor" do
128
+ user = users(:admin)
129
+ sign_in user
130
+
131
+ visit "/source_monitor"
132
+ assert_text "SourceMonitor Dashboard"
133
+ end
134
+ end
135
+ ```
136
+
137
+ - Swap `sign_in user` for your Devise helper (`login_as`, etc.).
138
+ - Use fixtures or factories that guarantee the user is authorized per the initializer’s `authorize_with` hook.
139
+
140
+ ## Additional Notes
141
+
142
+ - Re-running `bin/source_monitor install` is idempotent: if migrations already exist or Devise hooks are present, the workflow skips creation and only re-verifies prerequisites.
143
+ - The CLI wraps the same services the rake tasks use, so CI can call `bin/source_monitor verify` directly after migrations to catch worker/cable misconfigurations before deploying.
144
+ - Keep this document aligned with the PRD (`tasks/prd-setup-workflow-streamlining.md`) and active task list (`tasks/tasks-setup-workflow-streamlining.md`).
@@ -34,7 +34,7 @@ module SourceMonitor
34
34
 
35
35
  def print_next_steps
36
36
  say_status :info,
37
- "Next steps: review docs/installation.md for install walkthroughs and docs/troubleshooting.md for common fixes.",
37
+ "Next steps: review docs/setup.md for the guided + manual install walkthrough and docs/troubleshooting.md for common fixes.",
38
38
  :green
39
39
  end
40
40
 
@@ -0,0 +1,17 @@
1
+ module SourceMonitor
2
+ module Setup
3
+ class BundleInstaller
4
+ def initialize(shell: ShellRunner.new)
5
+ @shell = shell
6
+ end
7
+
8
+ def install
9
+ shell.run("bundle", "install")
10
+ end
11
+
12
+ private
13
+
14
+ attr_reader :shell
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,51 @@
1
+ require "thor"
2
+
3
+ module SourceMonitor
4
+ module Setup
5
+ class CLI < Thor
6
+ class_option :yes, type: :boolean, default: false, desc: "Accept all defaults"
7
+
8
+ desc "install", "Run the guided SourceMonitor setup workflow"
9
+ option :mount_path, type: :string, default: Workflow::DEFAULT_MOUNT_PATH
10
+ def install
11
+ workflow = Workflow.new(
12
+ prompter: Prompter.new(shell: shell, auto_yes: options[:yes])
13
+ )
14
+ summary = workflow.run
15
+ handle_summary(summary)
16
+ end
17
+
18
+ desc "verify", "Verify queue workers and Action Cable configuration"
19
+ def verify
20
+ summary = verification_runner.call
21
+ handle_summary(summary)
22
+ end
23
+
24
+ private
25
+
26
+ def handle_summary(summary)
27
+ printer.print(summary)
28
+ emit_telemetry(summary)
29
+ exit(1) unless summary.ok?
30
+ end
31
+
32
+ def printer
33
+ Verification::Printer.new(shell: shell)
34
+ end
35
+
36
+ def verification_runner
37
+ Verification::Runner.new
38
+ end
39
+
40
+ def emit_telemetry(summary)
41
+ return unless telemetry_enabled?
42
+
43
+ Verification::TelemetryLogger.new.log(summary)
44
+ end
45
+
46
+ def telemetry_enabled?
47
+ ENV["SOURCE_MONITOR_SETUP_TELEMETRY"].to_s.casecmp("true").zero?
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,165 @@
1
+ module SourceMonitor
2
+ module Setup
3
+ class DependencyChecker
4
+ Dependency = Struct.new(
5
+ :key,
6
+ :name,
7
+ :requirement,
8
+ :detector,
9
+ :remediation,
10
+ keyword_init: true
11
+ )
12
+
13
+ Result = Struct.new(
14
+ :key,
15
+ :name,
16
+ :status,
17
+ :current,
18
+ :expected,
19
+ :remediation,
20
+ keyword_init: true
21
+ ) do
22
+ def ok?
23
+ status == :ok
24
+ end
25
+
26
+ def warning?
27
+ status == :warning
28
+ end
29
+
30
+ def error?
31
+ status == :error
32
+ end
33
+
34
+ def missing?
35
+ status == :missing
36
+ end
37
+ end
38
+
39
+ class Summary
40
+ attr_reader :results
41
+
42
+ def initialize(results)
43
+ @results = results
44
+ end
45
+
46
+ def overall_status
47
+ return :error if errors?
48
+ return :warning if warnings?
49
+
50
+ :ok
51
+ end
52
+
53
+ def ok?
54
+ overall_status == :ok
55
+ end
56
+
57
+ def errors?
58
+ results.any? { |result| result.error? || result.missing? }
59
+ end
60
+
61
+ def warnings?
62
+ results.any?(&:warning?)
63
+ end
64
+
65
+ def errors
66
+ results.select { |result| result.error? || result.missing? }
67
+ end
68
+
69
+ def warnings
70
+ results.select(&:warning?)
71
+ end
72
+ end
73
+
74
+ def initialize(dependencies: default_dependencies)
75
+ @dependencies = dependencies
76
+ end
77
+
78
+ def call
79
+ Summary.new(@dependencies.map { |dependency| evaluate_dependency(dependency) })
80
+ end
81
+
82
+ private
83
+
84
+ def evaluate_dependency(dependency)
85
+ current = safe_detect { dependency.detector.call }
86
+ expected = requirement_expected(dependency.requirement)
87
+ status = classify_status(dependency.requirement, current)
88
+
89
+ Result.new(
90
+ key: dependency.key,
91
+ name: dependency.name,
92
+ status: status,
93
+ current: normalize(dependency.requirement, current),
94
+ expected: expected,
95
+ remediation: dependency.remediation
96
+ )
97
+ end
98
+
99
+ def safe_detect
100
+ yield
101
+ rescue StandardError
102
+ nil
103
+ end
104
+
105
+ def classify_status(requirement, current)
106
+ return :missing if current.nil?
107
+ return :ok if requirement.respond_to?(:satisfied?) && requirement.satisfied?(current)
108
+
109
+ :error
110
+ end
111
+
112
+ def normalize(requirement, value)
113
+ if requirement.respond_to?(:normalize)
114
+ requirement.normalize(value)
115
+ else
116
+ value
117
+ end
118
+ end
119
+
120
+ def requirement_expected(requirement)
121
+ requirement.respond_to?(:expected) ? requirement.expected : nil
122
+ end
123
+
124
+ def default_dependencies
125
+ [
126
+ Dependency.new(
127
+ key: :ruby,
128
+ name: "Ruby",
129
+ requirement: Requirements::Version.new(">= 3.4.4"),
130
+ detector: -> { Detectors.ruby_version },
131
+ remediation: "Upgrade Ruby to >= 3.4.4"
132
+ ),
133
+ Dependency.new(
134
+ key: :rails,
135
+ name: "Rails",
136
+ requirement: Requirements::Version.new(">= 8.0.2.1"),
137
+ detector: -> { Detectors.rails_version },
138
+ remediation: "Upgrade Rails to >= 8.0.2.1"
139
+ ),
140
+ Dependency.new(
141
+ key: :node,
142
+ name: "Node.js",
143
+ requirement: Requirements::Version.new(">= 18.0.0"),
144
+ detector: -> { Detectors.node_version },
145
+ remediation: "Install Node.js 18 or newer"
146
+ ),
147
+ Dependency.new(
148
+ key: :postgres,
149
+ name: "PostgreSQL Adapter",
150
+ requirement: Requirements::Adapter.new("postgresql"),
151
+ detector: -> { Detectors.postgres_adapter },
152
+ remediation: "Configure the host app to use PostgreSQL before installing SourceMonitor"
153
+ ),
154
+ Dependency.new(
155
+ key: :solid_queue,
156
+ name: "Solid Queue",
157
+ requirement: Requirements::Version.new([ ">= 0.3.0", "< 3.0" ]),
158
+ detector: -> { Detectors.solid_queue_version },
159
+ remediation: "Add the solid_queue gem (>= 0.3, < 3.0) to your host app"
160
+ )
161
+ ]
162
+ end
163
+ end
164
+ end
165
+ end
@@ -0,0 +1,67 @@
1
+ module SourceMonitor
2
+ module Setup
3
+ module Detectors
4
+ extend self
5
+
6
+ def ruby_version
7
+ Gem::Version.new(RUBY_VERSION)
8
+ end
9
+
10
+ def rails_version
11
+ return unless defined?(Rails)
12
+
13
+ Rails.gem_version
14
+ end
15
+
16
+ def node_version(shell: ShellRunner.new)
17
+ output = shell.run("node", "--version")
18
+ return if output.blank?
19
+
20
+ Gem::Version.new(output.strip.sub(/^v/i, ""))
21
+ rescue ArgumentError
22
+ nil
23
+ end
24
+
25
+ def postgres_adapter(config: nil, fallback: nil)
26
+ config ||= connection_db_config
27
+ fallback ||= primary_config
28
+
29
+ config_adapter(config) || config_adapter(fallback)
30
+ rescue StandardError
31
+ nil
32
+ end
33
+
34
+ def solid_queue_version
35
+ spec = Gem.loaded_specs["solid_queue"]
36
+ spec&.version
37
+ end
38
+
39
+ private
40
+
41
+ def connection_db_config
42
+ return unless defined?(ActiveRecord::Base)
43
+
44
+ ActiveRecord::Base.connection_db_config
45
+ rescue ActiveRecord::ConnectionNotEstablished
46
+ nil
47
+ end
48
+
49
+ def primary_config
50
+ return unless defined?(Rails)
51
+
52
+ ActiveRecord::Base.configurations.configs_for(env_name: Rails.env, name: "primary")
53
+ &.first
54
+ rescue StandardError
55
+ nil
56
+ end
57
+
58
+ def config_adapter(config)
59
+ return if config.nil?
60
+
61
+ config.respond_to?(:adapter) ? config.adapter : config[:adapter]
62
+ rescue StandardError
63
+ nil
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,29 @@
1
+ require "pathname"
2
+
3
+ module SourceMonitor
4
+ module Setup
5
+ class GemfileEditor
6
+ attr_reader :path
7
+
8
+ def initialize(path: "Gemfile")
9
+ @path = Pathname.new(path)
10
+ end
11
+
12
+ def ensure_entry
13
+ return false unless path.exist?
14
+
15
+ contents = path.read
16
+ return false if contents.match?(/gem\s+['"]source_monitor['"]/)
17
+
18
+ path.open("a") do |file|
19
+ file.write(<<~RUBY)
20
+
21
+ gem "source_monitor"
22
+ RUBY
23
+ end
24
+
25
+ true
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,59 @@
1
+ require "pathname"
2
+
3
+ module SourceMonitor
4
+ module Setup
5
+ class InitializerPatcher
6
+ AUTH_MARKER = "# ---- Authentication".freeze
7
+
8
+ def initialize(path: "config/initializers/source_monitor.rb")
9
+ @path = Pathname.new(path)
10
+ end
11
+
12
+ def ensure_navigation_hint(mount_path: Workflow::DEFAULT_MOUNT_PATH)
13
+ return false unless path.exist?
14
+
15
+ comment = "# Mount SourceMonitor at #{mount_path} so admins can find the dashboard."
16
+ contents = path.read
17
+ return false if contents.include?(comment)
18
+
19
+ updated = contents.sub("# SourceMonitor engine configuration.", "# SourceMonitor engine configuration.\n#{comment}")
20
+ path.write(updated)
21
+ true
22
+ end
23
+
24
+ def ensure_devise_hooks
25
+ return false unless path.exist?
26
+
27
+ contents = path.read
28
+ return false if contents.include?("config.authentication.authenticate_with :authenticate_user!")
29
+
30
+ snippet = indent(devise_snippet)
31
+ updated = if contents.include?(AUTH_MARKER)
32
+ contents.sub(AUTH_MARKER, "#{AUTH_MARKER}\n#{snippet}")
33
+ else
34
+ contents.sub(/SourceMonitor.configure do \|config\|/m, "\\0\n#{snippet}")
35
+ end
36
+
37
+ path.write(updated)
38
+ true
39
+ end
40
+
41
+ private
42
+
43
+ attr_reader :path
44
+
45
+ def indent(text)
46
+ text.lines.map { |line| line.strip.empty? ? "\n" : " #{line}" }.join
47
+ end
48
+
49
+ def devise_snippet
50
+ <<~RUBY
51
+ config.authentication.authenticate_with :authenticate_user!
52
+ config.authentication.authorize_with ->(controller) { controller.current_user&.respond_to?(:admin?) ? controller.current_user.admin? : true }
53
+ config.authentication.current_user_method = :current_user
54
+ config.authentication.user_signed_in_method = :user_signed_in?
55
+ RUBY
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,22 @@
1
+ module SourceMonitor
2
+ module Setup
3
+ class InstallGenerator
4
+ def initialize(shell: ShellRunner.new)
5
+ @shell = shell
6
+ end
7
+
8
+ def run(mount_path: Workflow::DEFAULT_MOUNT_PATH)
9
+ shell.run(
10
+ "bin/rails",
11
+ "generate",
12
+ "source_monitor:install",
13
+ "--mount-path=#{mount_path}"
14
+ )
15
+ end
16
+
17
+ private
18
+
19
+ attr_reader :shell
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,40 @@
1
+ require "fileutils"
2
+ require "pathname"
3
+
4
+ module SourceMonitor
5
+ module Setup
6
+ class MigrationInstaller
7
+ SOLID_QUEUE_PATTERN = "*_create_solid_queue_tables.rb"
8
+
9
+ def initialize(shell: ShellRunner.new, migrations_path: "db/migrate")
10
+ @shell = shell
11
+ @migrations_path = Pathname.new(migrations_path)
12
+ end
13
+
14
+ def install
15
+ copy_migrations
16
+ deduplicate_solid_queue
17
+ run_migrations
18
+ end
19
+
20
+ private
21
+
22
+ attr_reader :shell, :migrations_path
23
+
24
+ def copy_migrations
25
+ shell.run("bin/rails", "railties:install:migrations", "FROM=source_monitor")
26
+ end
27
+
28
+ def deduplicate_solid_queue
29
+ files = Dir[migrations_path.join(SOLID_QUEUE_PATTERN).to_s].sort
30
+ return if files.size <= 1
31
+
32
+ files[1..].each { |path| FileUtils.rm_f(path) }
33
+ end
34
+
35
+ def run_migrations
36
+ shell.run("bin/rails", "db:migrate")
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,27 @@
1
+ require "pathname"
2
+
3
+ module SourceMonitor
4
+ module Setup
5
+ class NodeInstaller
6
+ def initialize(root: Pathname.pwd.to_s, shell: ShellRunner.new)
7
+ @root = Pathname.new(root)
8
+ @shell = shell
9
+ end
10
+
11
+ def install_if_needed
12
+ return false unless package_json?
13
+
14
+ shell.run("npm", "install")
15
+ true
16
+ end
17
+
18
+ private
19
+
20
+ attr_reader :root, :shell
21
+
22
+ def package_json?
23
+ root.join("package.json").exist?
24
+ end
25
+ end
26
+ end
27
+ end