source_monitor 0.4.0 → 0.5.1
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/.claude/skills/sm-host-setup/SKILL.md +3 -1
- data/.claude/skills/sm-upgrade/SKILL.md +102 -0
- data/.claude/skills/sm-upgrade/reference/upgrade-workflow.md +92 -0
- data/.claude/skills/sm-upgrade/reference/version-history.md +68 -0
- data/.gitignore +1 -0
- data/.vbw-planning/SHIPPED.md +35 -0
- data/.vbw-planning/milestones/generator-enhancements/SHIPPED.md +40 -0
- data/.vbw-planning/milestones/upgrade-assurance/REQUIREMENTS.md +80 -0
- data/.vbw-planning/milestones/upgrade-assurance/ROADMAP.md +75 -0
- data/.vbw-planning/milestones/upgrade-assurance/STATE.md +29 -0
- data/.vbw-planning/milestones/upgrade-assurance/phases/01-upgrade-command/01-VERIFICATION.md +144 -0
- data/.vbw-planning/milestones/upgrade-assurance/phases/01-upgrade-command/PLAN-01-SUMMARY.md +43 -0
- data/.vbw-planning/milestones/upgrade-assurance/phases/01-upgrade-command/PLAN-01.md +405 -0
- data/.vbw-planning/milestones/upgrade-assurance/phases/02-config-deprecation/PLAN-01-SUMMARY.md +27 -0
- data/.vbw-planning/milestones/upgrade-assurance/phases/02-config-deprecation/PLAN-01.md +303 -0
- data/.vbw-planning/milestones/upgrade-assurance/phases/03-upgrade-skill-docs/03-VERIFICATION.md +380 -0
- data/.vbw-planning/milestones/upgrade-assurance/phases/03-upgrade-skill-docs/PLAN-01-SUMMARY.md +36 -0
- data/.vbw-planning/milestones/upgrade-assurance/phases/03-upgrade-skill-docs/PLAN-01.md +652 -0
- data/CHANGELOG.md +25 -0
- data/CLAUDE.md +5 -3
- data/Gemfile.lock +4 -4
- data/VERSION +1 -1
- data/docs/upgrade.md +140 -0
- data/lib/source_monitor/configuration/deprecation_registry.rb +237 -0
- data/lib/source_monitor/configuration.rb +8 -0
- data/lib/source_monitor/setup/cli.rb +7 -0
- data/lib/source_monitor/setup/skills_installer.rb +1 -0
- data/lib/source_monitor/setup/upgrade_command.rb +59 -0
- data/lib/source_monitor/setup/verification/pending_migrations_verifier.rb +92 -0
- data/lib/source_monitor/setup/verification/runner.rb +1 -1
- data/lib/source_monitor/version.rb +1 -1
- data/lib/source_monitor.rb +3 -0
- metadata +44 -25
- /data/.vbw-planning/{REQUIREMENTS.md → milestones/generator-enhancements/REQUIREMENTS.md} +0 -0
- /data/.vbw-planning/{ROADMAP.md → milestones/generator-enhancements/ROADMAP.md} +0 -0
- /data/.vbw-planning/{STATE.md → milestones/generator-enhancements/STATE.md} +0 -0
- /data/.vbw-planning/{phases → milestones/generator-enhancements/phases}/01-generator-steps/01-CONTEXT.md +0 -0
- /data/.vbw-planning/{phases → milestones/generator-enhancements/phases}/01-generator-steps/01-VERIFICATION.md +0 -0
- /data/.vbw-planning/{phases → milestones/generator-enhancements/phases}/01-generator-steps/PLAN-01-SUMMARY.md +0 -0
- /data/.vbw-planning/{phases → milestones/generator-enhancements/phases}/01-generator-steps/PLAN-01.md +0 -0
- /data/.vbw-planning/{phases → milestones/generator-enhancements/phases}/02-verification/02-VERIFICATION.md +0 -0
- /data/.vbw-planning/{phases → milestones/generator-enhancements/phases}/02-verification/PLAN-01-SUMMARY.md +0 -0
- /data/.vbw-planning/{phases → milestones/generator-enhancements/phases}/02-verification/PLAN-01.md +0 -0
- /data/.vbw-planning/{phases → milestones/generator-enhancements/phases}/03-docs-alignment/03-VERIFICATION.md +0 -0
- /data/.vbw-planning/{phases → milestones/generator-enhancements/phases}/03-docs-alignment/PLAN-01-SUMMARY.md +0 -0
- /data/.vbw-planning/{phases → milestones/generator-enhancements/phases}/03-docs-alignment/PLAN-01.md +0 -0
- /data/.vbw-planning/{phases → milestones/generator-enhancements/phases}/04-dashboard-ux/04-VERIFICATION.md +0 -0
- /data/.vbw-planning/{phases → milestones/generator-enhancements/phases}/04-dashboard-ux/PLAN-01-SUMMARY.md +0 -0
- /data/.vbw-planning/{phases → milestones/generator-enhancements/phases}/04-dashboard-ux/PLAN-01.md +0 -0
- /data/.vbw-planning/{phases → milestones/generator-enhancements/phases}/05-active-storage-images/05-VERIFICATION.md +0 -0
- /data/.vbw-planning/{phases → milestones/generator-enhancements/phases}/05-active-storage-images/PLAN-01-SUMMARY.md +0 -0
- /data/.vbw-planning/{phases → milestones/generator-enhancements/phases}/05-active-storage-images/PLAN-01.md +0 -0
- /data/.vbw-planning/{phases → milestones/generator-enhancements/phases}/05-active-storage-images/PLAN-02-SUMMARY.md +0 -0
- /data/.vbw-planning/{phases → milestones/generator-enhancements/phases}/05-active-storage-images/PLAN-02.md +0 -0
- /data/.vbw-planning/{phases → milestones/generator-enhancements/phases}/06-netflix-feed-fix/06-VERIFICATION.md +0 -0
- /data/.vbw-planning/{phases → milestones/generator-enhancements/phases}/06-netflix-feed-fix/PLAN-01-SUMMARY.md +0 -0
- /data/.vbw-planning/{phases → milestones/generator-enhancements/phases}/06-netflix-feed-fix/PLAN-01.md +0 -0
data/CHANGELOG.md
CHANGED
|
@@ -15,6 +15,31 @@ All notable changes to this project are documented below. The format follows [Ke
|
|
|
15
15
|
|
|
16
16
|
- No unreleased changes yet.
|
|
17
17
|
|
|
18
|
+
## [0.5.1] - 2026-02-13
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
|
|
22
|
+
- Bumped puma from 7.1.0 to 7.2.0 (17% faster HTTP parsing, `workers :auto`, GC-compactible C extension).
|
|
23
|
+
- Bumped solid_queue from 1.2.4 to 1.3.1 (async mode, bug fixes).
|
|
24
|
+
- Bumped turbo-rails from 2.0.20 to 2.0.23 (broadcast suppression fix, navigator clobbering fix).
|
|
25
|
+
|
|
26
|
+
## [0.5.0] - 2026-02-13
|
|
27
|
+
|
|
28
|
+
### Added
|
|
29
|
+
|
|
30
|
+
- `bin/source_monitor upgrade` command: detects version changes since last install, copies new migrations, re-runs the generator, runs verification, and reports what changed. Uses a `.source_monitor_version` marker file for version tracking.
|
|
31
|
+
- `PendingMigrationsVerifier` checks for unmigrated SourceMonitor tables in the verification suite, integrated into both `bin/source_monitor verify` and the upgrade flow.
|
|
32
|
+
- Configuration deprecation framework: engine developers can register deprecated config options with `DeprecationRegistry.register`. At boot time, stale options trigger `:warning` (renamed) or `:error` (removed) messages with actionable replacement paths.
|
|
33
|
+
- `sm-upgrade` AI skill guides agents through post-update workflows: CHANGELOG parsing, running the upgrade command, interpreting verification results, and handling deprecation warnings.
|
|
34
|
+
- `docs/upgrade.md` versioned upgrade guide with general steps, version-specific notes (0.1.x through 0.4.x), and troubleshooting.
|
|
35
|
+
- `sm-host-setup` skill cross-references the upgrade workflow.
|
|
36
|
+
|
|
37
|
+
### Testing
|
|
38
|
+
|
|
39
|
+
- 1,003 tests, 0 failures (up from 973 in 0.4.0).
|
|
40
|
+
- RuboCop: 397 files, 0 offenses.
|
|
41
|
+
- Brakeman: 0 warnings.
|
|
42
|
+
|
|
18
43
|
## [0.4.0] - 2026-02-12
|
|
19
44
|
|
|
20
45
|
### Added
|
data/CLAUDE.md
CHANGED
|
@@ -4,9 +4,10 @@
|
|
|
4
4
|
|
|
5
5
|
## Active Context
|
|
6
6
|
|
|
7
|
-
**Milestone:** none
|
|
8
|
-
**Last shipped:**
|
|
9
|
-
**
|
|
7
|
+
**Milestone:** (none active)
|
|
8
|
+
**Last shipped:** upgrade-assurance (2026-02-13) -- 3 phases, 14 tasks, 12 commits
|
|
9
|
+
**Previous:** generator-enhancements (2026-02-12) -- v0.4.0
|
|
10
|
+
**Next action:** /vbw:vibe to start a new milestone
|
|
10
11
|
|
|
11
12
|
## Key Decisions
|
|
12
13
|
|
|
@@ -192,6 +193,7 @@ Engine-specific skills (`sm-*` prefix). Consumer skills install by default; cont
|
|
|
192
193
|
| `sm-event-handler` | Lifecycle callbacks (after_item_created, etc.) |
|
|
193
194
|
| `sm-model-extension` | Extend engine models from host app |
|
|
194
195
|
| `sm-dashboard-widget` | Dashboard queries, presenters, Turbo broadcasts |
|
|
196
|
+
| `sm-upgrade` | Gem upgrade workflow with CHANGELOG parsing |
|
|
195
197
|
|
|
196
198
|
### Contributor Skills (opt-in)
|
|
197
199
|
|
data/Gemfile.lock
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
source_monitor (0.
|
|
4
|
+
source_monitor (0.5.1)
|
|
5
5
|
cssbundling-rails (~> 1.4)
|
|
6
6
|
faraday (~> 2.9)
|
|
7
7
|
faraday-follow_redirects (~> 0.4)
|
|
@@ -241,7 +241,7 @@ GEM
|
|
|
241
241
|
date
|
|
242
242
|
stringio
|
|
243
243
|
public_suffix (6.0.2)
|
|
244
|
-
puma (7.
|
|
244
|
+
puma (7.2.0)
|
|
245
245
|
nio4r (~> 2.0)
|
|
246
246
|
raabro (1.4.0)
|
|
247
247
|
racc (1.8.1)
|
|
@@ -349,7 +349,7 @@ GEM
|
|
|
349
349
|
activejob (>= 7.2)
|
|
350
350
|
activerecord (>= 7.2)
|
|
351
351
|
railties (>= 7.2)
|
|
352
|
-
solid_queue (1.
|
|
352
|
+
solid_queue (1.3.1)
|
|
353
353
|
activejob (>= 7.1)
|
|
354
354
|
activerecord (>= 7.1)
|
|
355
355
|
concurrent-ruby (>= 1.3.1)
|
|
@@ -362,7 +362,7 @@ GEM
|
|
|
362
362
|
thor (1.5.0)
|
|
363
363
|
timeout (0.6.0)
|
|
364
364
|
tsort (0.2.0)
|
|
365
|
-
turbo-rails (2.0.
|
|
365
|
+
turbo-rails (2.0.23)
|
|
366
366
|
actionpack (>= 7.1.0)
|
|
367
367
|
railties (>= 7.1.0)
|
|
368
368
|
tzinfo (2.0.6)
|
data/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.5.1
|
data/docs/upgrade.md
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# SourceMonitor Upgrade Guide
|
|
2
|
+
|
|
3
|
+
This guide covers upgrading SourceMonitor to a new gem version in your host Rails application.
|
|
4
|
+
|
|
5
|
+
## General Upgrade Steps
|
|
6
|
+
|
|
7
|
+
1. Review the [CHANGELOG](../CHANGELOG.md) for changes between your current and target versions
|
|
8
|
+
2. Update your Gemfile version constraint and run `bundle update source_monitor`
|
|
9
|
+
3. Run the upgrade command: `bin/source_monitor upgrade`
|
|
10
|
+
4. Apply database migrations if new ones were copied: `bin/rails db:migrate`
|
|
11
|
+
5. Address any deprecation warnings in your initializer (see Deprecation Handling below)
|
|
12
|
+
6. Run verification: `bin/source_monitor verify`
|
|
13
|
+
7. Restart your web server and background workers
|
|
14
|
+
|
|
15
|
+
## Quick Upgrade (Most Cases)
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
# 1. Update the gem
|
|
19
|
+
bundle update source_monitor
|
|
20
|
+
|
|
21
|
+
# 2. Run the upgrade command (handles migrations, generator, verification)
|
|
22
|
+
bin/source_monitor upgrade
|
|
23
|
+
|
|
24
|
+
# 3. Migrate if needed
|
|
25
|
+
bin/rails db:migrate
|
|
26
|
+
|
|
27
|
+
# 4. Restart
|
|
28
|
+
# (restart web server and Solid Queue workers)
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Deprecation Handling
|
|
32
|
+
|
|
33
|
+
When upgrading, you may see deprecation warnings in your Rails log:
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
[SourceMonitor] DEPRECATION: 'http.old_option' was deprecated in v0.5.0 and replaced by 'http.new_option'.
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
To resolve:
|
|
40
|
+
1. Open `config/initializers/source_monitor.rb`
|
|
41
|
+
2. Find the deprecated option (e.g., `config.http.old_option = value`)
|
|
42
|
+
3. Replace with the new option from the warning message (e.g., `config.http.new_option = value`)
|
|
43
|
+
4. Restart and verify the warning is gone
|
|
44
|
+
|
|
45
|
+
If a removed option raises an error (`SourceMonitor::DeprecatedOptionError`), you must update the initializer before the app can boot.
|
|
46
|
+
|
|
47
|
+
## Version-Specific Notes
|
|
48
|
+
|
|
49
|
+
### Upgrading to 0.4.0 (from 0.3.x)
|
|
50
|
+
|
|
51
|
+
**Released:** 2026-02-12
|
|
52
|
+
|
|
53
|
+
**What changed:**
|
|
54
|
+
- Install generator now auto-patches `Procfile.dev` with a Solid Queue `jobs:` entry
|
|
55
|
+
- Install generator now patches `config/queue.yml` dispatcher with `recurring_schedule: config/recurring.yml`
|
|
56
|
+
- Active Storage image download feature added (opt-in)
|
|
57
|
+
- SSL certificate configuration added to HTTP settings
|
|
58
|
+
- Enhanced verification messages for SolidQueue and RecurringSchedule verifiers
|
|
59
|
+
|
|
60
|
+
**Upgrade steps:**
|
|
61
|
+
```bash
|
|
62
|
+
bundle update source_monitor
|
|
63
|
+
bin/source_monitor upgrade
|
|
64
|
+
bin/rails db:migrate
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
**Notes:**
|
|
68
|
+
- No breaking changes. All existing configuration remains valid.
|
|
69
|
+
- Re-running the generator (`bin/rails generate source_monitor:install`) will add missing `Procfile.dev` and `queue.yml` entries without overwriting existing config.
|
|
70
|
+
- New optional features: `config.images.download_to_active_storage = true`, `config.http.ssl_ca_file`, `config.http.ssl_ca_path`, `config.http.ssl_verify`.
|
|
71
|
+
|
|
72
|
+
### Upgrading to 0.3.0 (from 0.2.x)
|
|
73
|
+
|
|
74
|
+
**Released:** 2026-02-10
|
|
75
|
+
|
|
76
|
+
**What changed:**
|
|
77
|
+
- Internal refactoring: FeedFetcher, Configuration, ImportSessionsController, and ItemCreator extracted into smaller modules
|
|
78
|
+
- Eager requires replaced with Ruby autoload
|
|
79
|
+
- Skills system added (14 `sm-*` Claude Code skills)
|
|
80
|
+
|
|
81
|
+
**Upgrade steps:**
|
|
82
|
+
```bash
|
|
83
|
+
bundle update source_monitor
|
|
84
|
+
bin/source_monitor upgrade
|
|
85
|
+
bin/rails db:migrate
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
**Notes:**
|
|
89
|
+
- No breaking changes to the public API.
|
|
90
|
+
- If you referenced internal classes directly (e.g., `SourceMonitor::FeedFetcher` internals), verify your code against the new module structure.
|
|
91
|
+
- Optionally install AI skills: `bin/rails source_monitor:skills:install`
|
|
92
|
+
|
|
93
|
+
### Upgrading to 0.2.0 (from 0.1.x)
|
|
94
|
+
|
|
95
|
+
**Released:** 2025-11-25
|
|
96
|
+
|
|
97
|
+
**What changed:**
|
|
98
|
+
- OPML import wizard with multi-step flow
|
|
99
|
+
- New `ImportHistory` model and associated migrations
|
|
100
|
+
|
|
101
|
+
**Upgrade steps:**
|
|
102
|
+
```bash
|
|
103
|
+
bundle update source_monitor
|
|
104
|
+
bin/rails railties:install:migrations FROM=source_monitor
|
|
105
|
+
bin/rails db:migrate
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
**Notes:**
|
|
109
|
+
- New database tables required. Run migrations after updating.
|
|
110
|
+
- No configuration changes needed.
|
|
111
|
+
|
|
112
|
+
## Troubleshooting
|
|
113
|
+
|
|
114
|
+
### "Already up to date" but I expected changes
|
|
115
|
+
- Verify the gem version actually changed: `bundle show source_monitor`
|
|
116
|
+
- Check `Gemfile.lock` for the resolved version
|
|
117
|
+
- If the `.source_monitor_version` marker was manually edited, delete it and re-run upgrade
|
|
118
|
+
|
|
119
|
+
### Migrations fail with duplicate timestamps
|
|
120
|
+
- Remove the duplicate migration file from `db/migrate/` (keep the newer one)
|
|
121
|
+
- Re-run `bin/rails db:migrate`
|
|
122
|
+
|
|
123
|
+
### Deprecation error prevents boot
|
|
124
|
+
- Read the error message for the replacement option
|
|
125
|
+
- Update your initializer before restarting
|
|
126
|
+
- If unsure which option to use, consult [Configuration Reference](configuration.md)
|
|
127
|
+
|
|
128
|
+
### Verification failures after upgrade
|
|
129
|
+
- **PendingMigrations:** Run `bin/rails db:migrate`
|
|
130
|
+
- **SolidQueue:** Ensure workers are running. Check `Procfile.dev` for a `jobs:` entry.
|
|
131
|
+
- **RecurringSchedule:** Re-run `bin/rails generate source_monitor:install` to patch `config/queue.yml`
|
|
132
|
+
- **ActionCable:** Configure Solid Cable or Redis adapter
|
|
133
|
+
|
|
134
|
+
For additional help, see [Troubleshooting](troubleshooting.md).
|
|
135
|
+
|
|
136
|
+
## See Also
|
|
137
|
+
- [Setup Guide](setup.md) -- Initial installation
|
|
138
|
+
- [Configuration Reference](configuration.md) -- All configuration options
|
|
139
|
+
- [Troubleshooting](troubleshooting.md) -- Common issues and fixes
|
|
140
|
+
- [CHANGELOG](../CHANGELOG.md) -- Full version history
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SourceMonitor
|
|
4
|
+
class DeprecatedOptionError < StandardError; end
|
|
5
|
+
|
|
6
|
+
class Configuration
|
|
7
|
+
# Registry for deprecated configuration options.
|
|
8
|
+
#
|
|
9
|
+
# Engine developers register deprecations at boot time via the DSL:
|
|
10
|
+
#
|
|
11
|
+
# SourceMonitor::Configuration::DeprecationRegistry.register(
|
|
12
|
+
# "http.old_proxy_url",
|
|
13
|
+
# removed_in: "0.5.0",
|
|
14
|
+
# replacement: "http.proxy",
|
|
15
|
+
# severity: :warning,
|
|
16
|
+
# message: "Use config.http.proxy instead"
|
|
17
|
+
# )
|
|
18
|
+
#
|
|
19
|
+
# When a host app's initializer accesses a deprecated option, the
|
|
20
|
+
# trapping method fires automatically:
|
|
21
|
+
# - :warning -- logs via Rails.logger.warn and forwards to replacement
|
|
22
|
+
# - :error -- raises SourceMonitor::DeprecatedOptionError
|
|
23
|
+
#
|
|
24
|
+
class DeprecationRegistry
|
|
25
|
+
# Maps settings accessor names (as used on Configuration) to their classes.
|
|
26
|
+
SETTINGS_CLASSES = {
|
|
27
|
+
"http" => "HTTPSettings",
|
|
28
|
+
"fetching" => "FetchingSettings",
|
|
29
|
+
"health" => "HealthSettings",
|
|
30
|
+
"scraping" => "ScrapingSettings",
|
|
31
|
+
"retention" => "RetentionSettings",
|
|
32
|
+
"realtime" => "RealtimeSettings",
|
|
33
|
+
"authentication" => "AuthenticationSettings",
|
|
34
|
+
"images" => "ImagesSettings",
|
|
35
|
+
"scrapers" => "ScraperRegistry",
|
|
36
|
+
"events" => "Events",
|
|
37
|
+
"models" => "Models"
|
|
38
|
+
}.freeze
|
|
39
|
+
|
|
40
|
+
class << self
|
|
41
|
+
# Register a deprecated configuration option.
|
|
42
|
+
#
|
|
43
|
+
# @param path [String] dot-notation path, e.g. "http.old_proxy_url" or "old_queue_prefix"
|
|
44
|
+
# @param removed_in [String] version in which the option was deprecated
|
|
45
|
+
# @param replacement [String, nil] dot-notation path to the replacement option
|
|
46
|
+
# @param severity [:warning, :error] :warning logs + forwards, :error raises
|
|
47
|
+
# @param message [String, nil] additional migration guidance
|
|
48
|
+
def register(path, removed_in:, replacement: nil, severity: :warning, message: nil)
|
|
49
|
+
segments = path.split(".")
|
|
50
|
+
source_prefix = nil
|
|
51
|
+
if segments.length == 1
|
|
52
|
+
target_class = Configuration
|
|
53
|
+
option_name = segments.first
|
|
54
|
+
else
|
|
55
|
+
source_prefix = segments.first
|
|
56
|
+
option_name = segments.last
|
|
57
|
+
class_name = SETTINGS_CLASSES[source_prefix]
|
|
58
|
+
raise ArgumentError, "Unknown settings accessor: #{source_prefix}" unless class_name
|
|
59
|
+
|
|
60
|
+
target_class = Configuration.const_get(class_name)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
deprecation_message = build_message(path, removed_in, replacement, message)
|
|
64
|
+
|
|
65
|
+
if target_class.method_defined?(:"#{option_name}=") || target_class.method_defined?(option_name.to_sym)
|
|
66
|
+
warn "[SourceMonitor] DeprecationRegistry: '#{path}' already exists on #{target_class.name}. " \
|
|
67
|
+
"Skipping trap definition -- the option is not yet removed/renamed."
|
|
68
|
+
entries[path] = { path: path, removed_in: removed_in, replacement: replacement,
|
|
69
|
+
severity: severity, message: deprecation_message, skipped: true }
|
|
70
|
+
return
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
define_trap_methods(target_class, option_name, deprecation_message, severity, replacement,
|
|
74
|
+
source_prefix: source_prefix)
|
|
75
|
+
|
|
76
|
+
entries[path] = { path: path, removed_in: removed_in, replacement: replacement,
|
|
77
|
+
severity: severity, message: deprecation_message, skipped: false }
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Remove all registered deprecation traps and clear state.
|
|
81
|
+
# Essential for test isolation.
|
|
82
|
+
def clear!
|
|
83
|
+
defined_methods.each do |target_class, method_name|
|
|
84
|
+
target_class.remove_method(method_name) if target_class.method_defined?(method_name)
|
|
85
|
+
rescue NameError
|
|
86
|
+
# Method was already removed or never defined; ignore.
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
@entries = {}
|
|
90
|
+
@defined_methods = []
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Returns a duplicate of the entries hash for inspection.
|
|
94
|
+
def entries
|
|
95
|
+
@entries ||= {}
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Check if a path is registered.
|
|
99
|
+
def registered?(path)
|
|
100
|
+
entries.key?(path)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# No-op hook for future "default changed" checks.
|
|
104
|
+
# Called by Configuration#check_deprecations! after the configure block.
|
|
105
|
+
def check_defaults!(_config)
|
|
106
|
+
# Reserved for future use. Phases may add checks like:
|
|
107
|
+
# "option X changed its default from A to B in version Y"
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
private
|
|
111
|
+
|
|
112
|
+
def defined_methods
|
|
113
|
+
@defined_methods ||= []
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def build_message(path, removed_in, replacement, extra_message)
|
|
117
|
+
parts = +"[SourceMonitor] DEPRECATION: '#{path}' was deprecated in v#{removed_in}"
|
|
118
|
+
parts << " and replaced by '#{replacement}'" if replacement
|
|
119
|
+
parts << ". #{extra_message}" if extra_message
|
|
120
|
+
parts << "." unless parts.end_with?(".")
|
|
121
|
+
parts.freeze
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def define_trap_methods(target_class, option_name, deprecation_message, severity, replacement, source_prefix: nil)
|
|
125
|
+
writer_name = :"#{option_name}="
|
|
126
|
+
reader_name = option_name.to_sym
|
|
127
|
+
|
|
128
|
+
case severity
|
|
129
|
+
when :warning
|
|
130
|
+
define_warning_writer(target_class, writer_name, deprecation_message, replacement, source_prefix)
|
|
131
|
+
define_warning_reader(target_class, reader_name, deprecation_message, replacement, source_prefix)
|
|
132
|
+
when :error
|
|
133
|
+
define_error_method(target_class, writer_name, deprecation_message)
|
|
134
|
+
define_error_method(target_class, reader_name, deprecation_message)
|
|
135
|
+
else
|
|
136
|
+
raise ArgumentError, "Unknown severity: #{severity}. Must be :warning or :error."
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
defined_methods.push([ target_class, writer_name ], [ target_class, reader_name ])
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def define_warning_writer(target_class, writer_name, deprecation_message, replacement, source_prefix)
|
|
143
|
+
replacement_writer = replacement_setter_for(replacement, source_prefix)
|
|
144
|
+
|
|
145
|
+
target_class.define_method(writer_name) do |value|
|
|
146
|
+
Rails.logger.warn(deprecation_message)
|
|
147
|
+
if replacement_writer
|
|
148
|
+
resolve_replacement_target(replacement_writer[:target]).public_send(
|
|
149
|
+
replacement_writer[:setter], value
|
|
150
|
+
)
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def define_warning_reader(target_class, reader_name, deprecation_message, replacement, source_prefix)
|
|
156
|
+
replacement_reader = replacement_getter_for(replacement, source_prefix)
|
|
157
|
+
|
|
158
|
+
target_class.define_method(reader_name) do
|
|
159
|
+
Rails.logger.warn(deprecation_message)
|
|
160
|
+
if replacement_reader
|
|
161
|
+
resolve_replacement_target(replacement_reader[:target]).public_send(
|
|
162
|
+
replacement_reader[:getter]
|
|
163
|
+
)
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def define_error_method(target_class, method_name, deprecation_message)
|
|
169
|
+
target_class.define_method(method_name) do |*|
|
|
170
|
+
raise SourceMonitor::DeprecatedOptionError, deprecation_message
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# Parse replacement path into target accessor chain and setter name.
|
|
175
|
+
# When source_prefix matches the replacement prefix, the target is nil
|
|
176
|
+
# (replacement is on the same settings class).
|
|
177
|
+
#
|
|
178
|
+
# "http.proxy" with source_prefix "http" => { target: nil, setter: "proxy=" }
|
|
179
|
+
# "queue_namespace" => { target: nil, setter: "queue_namespace=" }
|
|
180
|
+
# "http.proxy" with source_prefix nil => { target: :http, setter: "proxy=" }
|
|
181
|
+
def replacement_setter_for(replacement, source_prefix = nil)
|
|
182
|
+
return nil unless replacement
|
|
183
|
+
|
|
184
|
+
segments = replacement.split(".")
|
|
185
|
+
if segments.length == 1
|
|
186
|
+
{ target: nil, setter: :"#{segments.first}=" }
|
|
187
|
+
elsif source_prefix && segments.first == source_prefix
|
|
188
|
+
{ target: nil, setter: :"#{segments.last}=" }
|
|
189
|
+
else
|
|
190
|
+
{ target: segments.first.to_sym, setter: :"#{segments.last}=" }
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
# Parse replacement path into target accessor chain and getter name.
|
|
195
|
+
def replacement_getter_for(replacement, source_prefix = nil)
|
|
196
|
+
return nil unless replacement
|
|
197
|
+
|
|
198
|
+
segments = replacement.split(".")
|
|
199
|
+
if segments.length == 1
|
|
200
|
+
{ target: nil, getter: segments.first.to_sym }
|
|
201
|
+
elsif source_prefix && segments.first == source_prefix
|
|
202
|
+
{ target: nil, getter: segments.last.to_sym }
|
|
203
|
+
else
|
|
204
|
+
{ target: segments.first.to_sym, getter: segments.last.to_sym }
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
# Add a helper method to settings classes and Configuration for resolving
|
|
213
|
+
# replacement targets. This allows "http.proxy" to resolve as self.http.proxy
|
|
214
|
+
# from within a Configuration instance, or as self.proxy from within an
|
|
215
|
+
# HTTPSettings instance.
|
|
216
|
+
module SourceMonitor
|
|
217
|
+
class Configuration
|
|
218
|
+
private
|
|
219
|
+
|
|
220
|
+
def resolve_replacement_target(accessor)
|
|
221
|
+
accessor ? public_send(accessor) : self
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
# Add the same helper to all settings classes so forwarding works
|
|
227
|
+
# when the deprecated method is defined on a nested settings class
|
|
228
|
+
# and the replacement is on the same class (e.g. "http.old_proxy" -> "http.proxy").
|
|
229
|
+
SourceMonitor::Configuration::DeprecationRegistry::SETTINGS_CLASSES.each_value do |class_name|
|
|
230
|
+
klass = SourceMonitor::Configuration.const_get(class_name)
|
|
231
|
+
unless klass.method_defined?(:resolve_replacement_target, false)
|
|
232
|
+
klass.define_method(:resolve_replacement_target) do |accessor|
|
|
233
|
+
accessor ? public_send(accessor) : self
|
|
234
|
+
end
|
|
235
|
+
klass.send(:private, :resolve_replacement_target)
|
|
236
|
+
end
|
|
237
|
+
end
|
|
@@ -14,6 +14,7 @@ require "source_monitor/configuration/events"
|
|
|
14
14
|
require "source_monitor/configuration/validation_definition"
|
|
15
15
|
require "source_monitor/configuration/model_definition"
|
|
16
16
|
require "source_monitor/configuration/models"
|
|
17
|
+
require "source_monitor/configuration/deprecation_registry"
|
|
17
18
|
|
|
18
19
|
module SourceMonitor
|
|
19
20
|
class Configuration
|
|
@@ -85,5 +86,12 @@ module SourceMonitor
|
|
|
85
86
|
raise ArgumentError, "unknown queue role #{role.inspect}"
|
|
86
87
|
end
|
|
87
88
|
end
|
|
89
|
+
|
|
90
|
+
# Post-configure hook for deprecation validation.
|
|
91
|
+
# Delegates to DeprecationRegistry.check_defaults! for future
|
|
92
|
+
# "default changed" checks. Currently a no-op.
|
|
93
|
+
def check_deprecations!
|
|
94
|
+
DeprecationRegistry.check_defaults!(self)
|
|
95
|
+
end
|
|
88
96
|
end
|
|
89
97
|
end
|
|
@@ -23,6 +23,13 @@ module SourceMonitor
|
|
|
23
23
|
handle_summary(summary)
|
|
24
24
|
end
|
|
25
25
|
|
|
26
|
+
desc "upgrade", "Upgrade SourceMonitor after a gem version change"
|
|
27
|
+
def upgrade
|
|
28
|
+
command = UpgradeCommand.new
|
|
29
|
+
summary = command.call
|
|
30
|
+
handle_summary(summary)
|
|
31
|
+
end
|
|
32
|
+
|
|
26
33
|
private
|
|
27
34
|
|
|
28
35
|
def handle_summary(summary)
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SourceMonitor
|
|
4
|
+
module Setup
|
|
5
|
+
class UpgradeCommand
|
|
6
|
+
def initialize(
|
|
7
|
+
migration_installer: MigrationInstaller.new,
|
|
8
|
+
install_generator: InstallGenerator.new,
|
|
9
|
+
verifier: Verification::Runner.new,
|
|
10
|
+
version_file: File.join(Dir.pwd, ".source_monitor_version"),
|
|
11
|
+
current_version: SourceMonitor::VERSION
|
|
12
|
+
)
|
|
13
|
+
@migration_installer = migration_installer
|
|
14
|
+
@install_generator = install_generator
|
|
15
|
+
@verifier = verifier
|
|
16
|
+
@version_file = version_file
|
|
17
|
+
@current_version = current_version
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def call
|
|
21
|
+
stored = read_stored_version
|
|
22
|
+
|
|
23
|
+
if stored == current_version
|
|
24
|
+
return up_to_date_summary
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
migration_installer.install
|
|
28
|
+
install_generator.run
|
|
29
|
+
summary = verifier.call
|
|
30
|
+
write_version_marker
|
|
31
|
+
summary
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
attr_reader :migration_installer, :install_generator, :verifier, :version_file, :current_version
|
|
37
|
+
|
|
38
|
+
def read_stored_version
|
|
39
|
+
return nil unless File.exist?(version_file)
|
|
40
|
+
|
|
41
|
+
File.read(version_file).strip
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def write_version_marker
|
|
45
|
+
File.write(version_file, current_version)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def up_to_date_summary
|
|
49
|
+
result = Verification::Result.new(
|
|
50
|
+
key: :upgrade,
|
|
51
|
+
name: "Upgrade",
|
|
52
|
+
status: :ok,
|
|
53
|
+
details: "Already up to date (v#{current_version})"
|
|
54
|
+
)
|
|
55
|
+
Verification::Summary.new([ result ])
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SourceMonitor
|
|
4
|
+
module Setup
|
|
5
|
+
module Verification
|
|
6
|
+
class PendingMigrationsVerifier
|
|
7
|
+
MIGRATION_TIMESTAMP_PATTERN = /\A\d+_/
|
|
8
|
+
|
|
9
|
+
def initialize(
|
|
10
|
+
engine_migrations_path: default_engine_migrations_path,
|
|
11
|
+
host_migrations_path: default_host_migrations_path,
|
|
12
|
+
connection: default_connection
|
|
13
|
+
)
|
|
14
|
+
@engine_migrations_path = engine_migrations_path
|
|
15
|
+
@host_migrations_path = host_migrations_path
|
|
16
|
+
@connection = connection
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def call
|
|
20
|
+
engine_names = source_monitor_migration_names(engine_migrations_path)
|
|
21
|
+
return ok_result("No SourceMonitor engine migrations found") if engine_names.empty?
|
|
22
|
+
|
|
23
|
+
host_names = migration_names(host_migrations_path)
|
|
24
|
+
missing = engine_names - host_names
|
|
25
|
+
|
|
26
|
+
if missing.any?
|
|
27
|
+
warning_result(
|
|
28
|
+
"#{missing.size} SourceMonitor migration(s) not copied to host: #{missing.join(', ')}",
|
|
29
|
+
"Run `bin/source_monitor upgrade` or `bin/rails railties:install:migrations FROM=source_monitor`"
|
|
30
|
+
)
|
|
31
|
+
elsif connection.migration_context.needs_migration?
|
|
32
|
+
warning_result(
|
|
33
|
+
"All SourceMonitor migrations are copied but some migrations are pending",
|
|
34
|
+
"Run `bin/rails db:migrate` to apply pending migrations"
|
|
35
|
+
)
|
|
36
|
+
else
|
|
37
|
+
ok_result("All SourceMonitor migrations are present and up to date")
|
|
38
|
+
end
|
|
39
|
+
rescue StandardError => e
|
|
40
|
+
error_result(
|
|
41
|
+
"Migration verification failed: #{e.message}",
|
|
42
|
+
"Check database connectivity and migration file permissions"
|
|
43
|
+
)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private
|
|
47
|
+
|
|
48
|
+
attr_reader :engine_migrations_path, :host_migrations_path, :connection
|
|
49
|
+
|
|
50
|
+
def default_engine_migrations_path
|
|
51
|
+
SourceMonitor::Engine.root.join("db/migrate")
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def default_host_migrations_path
|
|
55
|
+
Rails.root.join("db/migrate")
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def default_connection
|
|
59
|
+
ActiveRecord::Base.connection
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def source_monitor_migration_names(path)
|
|
63
|
+
migration_names(path).select { |name| name.include?("source_monitor") }
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def migration_names(path)
|
|
67
|
+
return [] unless File.directory?(path.to_s)
|
|
68
|
+
|
|
69
|
+
Dir.children(path.to_s)
|
|
70
|
+
.select { |f| f.end_with?(".rb") }
|
|
71
|
+
.map { |f| strip_timestamp(f) }
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def strip_timestamp(filename)
|
|
75
|
+
filename.sub(MIGRATION_TIMESTAMP_PATTERN, "").delete_suffix(".rb")
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def ok_result(details)
|
|
79
|
+
Result.new(key: :pending_migrations, name: "Pending Migrations", status: :ok, details: details)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def warning_result(details, remediation)
|
|
83
|
+
Result.new(key: :pending_migrations, name: "Pending Migrations", status: :warning, details: details, remediation: remediation)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def error_result(details, remediation)
|
|
87
|
+
Result.new(key: :pending_migrations, name: "Pending Migrations", status: :error, details: details, remediation: remediation)
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
@@ -18,7 +18,7 @@ module SourceMonitor
|
|
|
18
18
|
attr_reader :verifiers
|
|
19
19
|
|
|
20
20
|
def default_verifiers
|
|
21
|
-
[ SolidQueueVerifier.new, RecurringScheduleVerifier.new, ActionCableVerifier.new ]
|
|
21
|
+
[ PendingMigrationsVerifier.new, SolidQueueVerifier.new, RecurringScheduleVerifier.new, ActionCableVerifier.new ]
|
|
22
22
|
end
|
|
23
23
|
end
|
|
24
24
|
end
|