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