e11y 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rspec +1 -0
- data/.rubocop.yml +20 -0
- data/CHANGELOG.md +151 -13
- data/README.md +1138 -104
- data/RELEASE.md +254 -0
- data/Rakefile +377 -0
- data/benchmarks/OPTIMIZATION.md +246 -0
- data/benchmarks/README.md +103 -0
- data/benchmarks/allocation_profiling.rb +253 -0
- data/benchmarks/e11y_benchmarks.rb +447 -0
- data/benchmarks/ruby_baseline_allocations.rb +175 -0
- data/benchmarks/run_all.rb +9 -21
- data/docs/00-ICP-AND-TIMELINE.md +2 -2
- data/docs/ADR-001-architecture.md +1 -1
- data/docs/ADR-004-adapter-architecture.md +247 -0
- data/docs/ADR-009-cost-optimization.md +231 -115
- data/docs/ADR-017-multi-rails-compatibility.md +103 -0
- data/docs/ADR-INDEX.md +99 -0
- data/docs/CONTRIBUTING.md +312 -0
- data/docs/IMPLEMENTATION_PLAN.md +1 -1
- data/docs/QUICK-START.md +0 -6
- data/docs/use_cases/UC-019-retention-based-routing.md +584 -0
- data/e11y.gemspec +28 -17
- data/lib/e11y/adapters/adaptive_batcher.rb +3 -0
- data/lib/e11y/adapters/audit_encrypted.rb +10 -4
- data/lib/e11y/adapters/base.rb +15 -0
- data/lib/e11y/adapters/file.rb +4 -1
- data/lib/e11y/adapters/in_memory.rb +6 -0
- data/lib/e11y/adapters/loki.rb +9 -0
- data/lib/e11y/adapters/otel_logs.rb +11 -9
- data/lib/e11y/adapters/sentry.rb +9 -0
- data/lib/e11y/adapters/yabeda.rb +54 -10
- data/lib/e11y/buffers.rb +8 -8
- data/lib/e11y/console.rb +52 -60
- data/lib/e11y/event/base.rb +75 -10
- data/lib/e11y/event/value_sampling_config.rb +10 -4
- data/lib/e11y/events/rails/http/request.rb +1 -1
- data/lib/e11y/instruments/active_job.rb +6 -3
- data/lib/e11y/instruments/rails_instrumentation.rb +51 -28
- data/lib/e11y/instruments/sidekiq.rb +7 -7
- data/lib/e11y/logger/bridge.rb +24 -54
- data/lib/e11y/metrics/cardinality_protection.rb +257 -12
- data/lib/e11y/metrics/cardinality_tracker.rb +17 -0
- data/lib/e11y/metrics/registry.rb +6 -2
- data/lib/e11y/metrics/relabeling.rb +0 -56
- data/lib/e11y/metrics.rb +6 -1
- data/lib/e11y/middleware/audit_signing.rb +12 -9
- data/lib/e11y/middleware/pii_filter.rb +18 -10
- data/lib/e11y/middleware/request.rb +10 -4
- data/lib/e11y/middleware/routing.rb +117 -90
- data/lib/e11y/middleware/sampling.rb +47 -28
- data/lib/e11y/middleware/trace_context.rb +40 -11
- data/lib/e11y/middleware/validation.rb +20 -2
- data/lib/e11y/middleware/versioning.rb +1 -1
- data/lib/e11y/pii.rb +7 -7
- data/lib/e11y/railtie.rb +24 -20
- data/lib/e11y/reliability/circuit_breaker.rb +3 -0
- data/lib/e11y/reliability/dlq/file_storage.rb +16 -5
- data/lib/e11y/reliability/dlq/filter.rb +3 -0
- data/lib/e11y/reliability/retry_handler.rb +4 -0
- data/lib/e11y/sampling/error_spike_detector.rb +16 -5
- data/lib/e11y/sampling/load_monitor.rb +13 -4
- data/lib/e11y/self_monitoring/reliability_monitor.rb +3 -0
- data/lib/e11y/version.rb +1 -1
- data/lib/e11y.rb +86 -9
- metadata +83 -38
- data/docs/use_cases/UC-019-tiered-storage-migration.md +0 -562
- data/lib/e11y/middleware/pii_filtering.rb +0 -280
- data/lib/e11y/middleware/slo.rb +0 -168
data/RELEASE.md
ADDED
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
# Release Instructions for e11y
|
|
2
|
+
|
|
3
|
+
## Quick Release (Automated)
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
# 1. Bump version (updates VERSION + CHANGELOG)
|
|
7
|
+
rake release:bump
|
|
8
|
+
# Enter new version when prompted, e.g., 0.2.0
|
|
9
|
+
|
|
10
|
+
# 2. Review and commit
|
|
11
|
+
git diff
|
|
12
|
+
git add -A
|
|
13
|
+
git commit -m "Bump version to 0.2.0"
|
|
14
|
+
|
|
15
|
+
# 3. Full release (test + build + tag + push + publish)
|
|
16
|
+
rake release:full
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
For more control, see [Step-by-Step Release](#step-by-step-release) below.
|
|
20
|
+
|
|
21
|
+
## Pre-Release Checklist
|
|
22
|
+
|
|
23
|
+
- [ ] All changes documented in CHANGELOG.md under `[Unreleased]`
|
|
24
|
+
- [ ] Version bumped: `rake release:bump`
|
|
25
|
+
- [ ] All tests passing: `rake spec`
|
|
26
|
+
- [ ] Changes committed
|
|
27
|
+
- [ ] Git tag created
|
|
28
|
+
- [ ] Published to RubyGems.org
|
|
29
|
+
- [ ] GitHub release created
|
|
30
|
+
|
|
31
|
+
## Step-by-Step Release
|
|
32
|
+
|
|
33
|
+
### Step 0: Bump Version
|
|
34
|
+
|
|
35
|
+
First, update version and CHANGELOG:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
rake release:bump
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
This will:
|
|
42
|
+
1. Prompt for new version (e.g., 0.2.0)
|
|
43
|
+
2. Update `lib/e11y/version.rb`
|
|
44
|
+
3. Convert `[Unreleased]` → `[0.2.0] - YYYY-MM-DD` in CHANGELOG
|
|
45
|
+
4. Add new empty `[Unreleased]` section
|
|
46
|
+
|
|
47
|
+
Commit the changes:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
git add -A
|
|
51
|
+
git commit -m "Bump version to 0.2.0"
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Step 1: Prepare Release
|
|
55
|
+
|
|
56
|
+
Run tests, build gem, create tag:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
rake release:prep
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
This will:
|
|
63
|
+
- ✅ Check git status (fails if uncommitted changes)
|
|
64
|
+
- ✅ Run full test suite
|
|
65
|
+
- ✅ Build gem file
|
|
66
|
+
- ✅ Create annotated git tag
|
|
67
|
+
|
|
68
|
+
Or manually:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
# Run all tests
|
|
72
|
+
bundle exec rspec
|
|
73
|
+
|
|
74
|
+
# Build gem
|
|
75
|
+
gem build e11y.gemspec
|
|
76
|
+
|
|
77
|
+
# Create and push tag
|
|
78
|
+
git tag -a v0.2.0 -m "Release v0.2.0"
|
|
79
|
+
git push origin main
|
|
80
|
+
git push origin v0.2.0
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Step 2: Push to GitHub
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
rake release:git_push
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
This will:
|
|
90
|
+
- ✅ Verify tag exists
|
|
91
|
+
- ✅ Push commits to origin/main
|
|
92
|
+
- ✅ Push tag to origin
|
|
93
|
+
- ✅ Show GitHub release URL
|
|
94
|
+
|
|
95
|
+
### Step 3: Publish to RubyGems.org
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
rake release:gem_push
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
This will:
|
|
102
|
+
- ✅ Verify gem file exists
|
|
103
|
+
- ✅ Prompt for confirmation
|
|
104
|
+
- ✅ Push gem to RubyGems (requires MFA)
|
|
105
|
+
- ✅ Show verification URL
|
|
106
|
+
|
|
107
|
+
Or manually:
|
|
108
|
+
|
|
109
|
+
### Prerequisites
|
|
110
|
+
|
|
111
|
+
1. **RubyGems Account**: Create account at https://rubygems.org/sign_up
|
|
112
|
+
2. **API Key**: Get your API key from https://rubygems.org/profile/edit
|
|
113
|
+
3. **MFA Enabled**: This gem requires MFA (configured in gemspec)
|
|
114
|
+
|
|
115
|
+
### Publish Command
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
# Sign in to RubyGems (one-time setup)
|
|
119
|
+
gem signin
|
|
120
|
+
|
|
121
|
+
# Push the gem (requires MFA)
|
|
122
|
+
gem push e11y-0.1.0.gem
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Expected output:
|
|
126
|
+
```
|
|
127
|
+
Pushing gem to https://rubygems.org...
|
|
128
|
+
Enter your RubyGems.org credentials.
|
|
129
|
+
Username: [your_username]
|
|
130
|
+
Password: [your_password]
|
|
131
|
+
MFA Code: [6-digit code from authenticator app]
|
|
132
|
+
Successfully registered gem: e11y (0.1.0)
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Verify Publication
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
# Check gem is available
|
|
139
|
+
gem search e11y --remote
|
|
140
|
+
|
|
141
|
+
# Install from RubyGems
|
|
142
|
+
gem install e11y
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## Step 3: Create GitHub Release
|
|
146
|
+
|
|
147
|
+
1. Go to https://github.com/arturseletskiy/e11y/releases/new
|
|
148
|
+
2. Choose tag: `v0.1.0`
|
|
149
|
+
3. Release title: `v0.1.0 - First Production Release`
|
|
150
|
+
4. Description: Use content from CHANGELOG.md (see below)
|
|
151
|
+
5. Attach binary: Upload `e11y-0.1.0.gem`
|
|
152
|
+
6. Click "Publish release"
|
|
153
|
+
|
|
154
|
+
### GitHub Release Notes Template
|
|
155
|
+
|
|
156
|
+
```markdown
|
|
157
|
+
# 🎉 E11y 0.1.0 - First Production Release
|
|
158
|
+
|
|
159
|
+
Production-ready observability gem for Ruby on Rails with zero-config SLO tracking, request-scoped buffering, and high-performance event streaming.
|
|
160
|
+
|
|
161
|
+
## 🚀 Highlights
|
|
162
|
+
|
|
163
|
+
- **Zero-Config SLO Tracking** - Automatic Service Level Objectives for HTTP and background jobs
|
|
164
|
+
- **100K+ events/sec** - Benchmark-validated performance (p99 <50μs latency)
|
|
165
|
+
- **99%+ Test Coverage** - 1409 test examples, battle-tested
|
|
166
|
+
- **16 ADRs** - Fully documented architecture decisions
|
|
167
|
+
- **Cardinality Protection** - 4-layer defense against metric explosions
|
|
168
|
+
- **Production-Ready** - Reliability layer with retry, circuit breaker, DLQ
|
|
169
|
+
|
|
170
|
+
## 📦 Installation
|
|
171
|
+
|
|
172
|
+
```ruby
|
|
173
|
+
# Gemfile
|
|
174
|
+
gem 'e11y', '~> 1.0'
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
```bash
|
|
178
|
+
bundle install
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
## 📚 Documentation
|
|
182
|
+
|
|
183
|
+
- **Quick Start**: [README.md](https://github.com/arturseletskiy/e11y#quick-start)
|
|
184
|
+
- **Architecture**: [docs/ADR-INDEX.md](https://github.com/arturseletskiy/e11y/blob/main/docs/ADR-INDEX.md)
|
|
185
|
+
- **Benchmarks**: [benchmarks/README.md](https://github.com/arturseletskiy/e11y/blob/main/benchmarks/README.md)
|
|
186
|
+
|
|
187
|
+
## 🔥 What's New
|
|
188
|
+
|
|
189
|
+
See [CHANGELOG.md](https://github.com/arturseletskiy/e11y/blob/main/CHANGELOG.md) for full details.
|
|
190
|
+
|
|
191
|
+
### Core Features (Phase 1-2)
|
|
192
|
+
- Event System with dry-schema validation
|
|
193
|
+
- Pipeline Architecture (middleware-based)
|
|
194
|
+
- 7 Adapters: Stdout, File, InMemory, Loki, Sentry, OpenTelemetry, Yabeda
|
|
195
|
+
- 3 Buffer Types: RingBuffer, RequestScopedBuffer, AdaptiveBuffer
|
|
196
|
+
- Reliability: Retry, Circuit Breaker, Dead Letter Queue
|
|
197
|
+
|
|
198
|
+
### SLO Tracking (Phase 3)
|
|
199
|
+
- Event-Driven SLOs for HTTP/jobs
|
|
200
|
+
- Stratified Sampling (latency-aware)
|
|
201
|
+
- Error Spike Detection
|
|
202
|
+
- Value-Based Sampling
|
|
203
|
+
|
|
204
|
+
### Rails Integration (Phase 4)
|
|
205
|
+
- Auto-instrumentation for Rails events
|
|
206
|
+
- HTTP request tracking
|
|
207
|
+
- Background job tracking (ActiveJob, Sidekiq)
|
|
208
|
+
- Database query events
|
|
209
|
+
- Logger bridge (Rails.logger → E11y)
|
|
210
|
+
|
|
211
|
+
### Scale & Performance (Phase 5)
|
|
212
|
+
- Cardinality Protection (4-layer defense)
|
|
213
|
+
- Tiered Storage (hot/warm/cold)
|
|
214
|
+
- Benchmarked: 100K events/sec, p99 <50μs
|
|
215
|
+
|
|
216
|
+
## ⚡ Performance
|
|
217
|
+
|
|
218
|
+
| Scale | Latency (p99) | Throughput | Memory |
|
|
219
|
+
|-------|--------------|------------|---------|
|
|
220
|
+
| Small (1K/s) | 47μs | 107K/s | 1.95 MB |
|
|
221
|
+
| Medium (10K/s) | 33μs | 110K/s | 19.49 MB |
|
|
222
|
+
| Large (100K/s) | 26μs | 109K/s | 194.93 MB |
|
|
223
|
+
|
|
224
|
+
## 🙏 Credits
|
|
225
|
+
|
|
226
|
+
Built with patterns from Devise, Sidekiq, Puma, Dry-rb, Yabeda, and Sentry.
|
|
227
|
+
|
|
228
|
+
---
|
|
229
|
+
|
|
230
|
+
**Full Changelog**: https://github.com/arturseletskiy/e11y/blob/main/CHANGELOG.md
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
## Post-Release Tasks
|
|
234
|
+
|
|
235
|
+
- [ ] Announce on Twitter/social media
|
|
236
|
+
- [ ] Update README badge with latest version
|
|
237
|
+
- [ ] Monitor RubyGems download stats
|
|
238
|
+
- [ ] Monitor GitHub issues for bug reports
|
|
239
|
+
|
|
240
|
+
## Rollback Procedure (if needed)
|
|
241
|
+
|
|
242
|
+
If critical bug found after release:
|
|
243
|
+
|
|
244
|
+
```bash
|
|
245
|
+
# Yank the gem (makes it unavailable for new installs)
|
|
246
|
+
gem yank e11y -v 0.1.0
|
|
247
|
+
|
|
248
|
+
# Fix the issue, bump to 1.0.1, and re-release
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
## Support
|
|
252
|
+
|
|
253
|
+
- **Issues**: https://github.com/arturseletskiy/e11y/issues
|
|
254
|
+
- **Discussions**: https://github.com/arturseletskiy/e11y/discussions
|
data/Rakefile
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
#
|
|
4
|
+
# E11y Gem Rakefile
|
|
5
|
+
#
|
|
6
|
+
# Quick reference:
|
|
7
|
+
# rake # Run tests and rubocop
|
|
8
|
+
# rake release:bump # Bump version and update CHANGELOG (interactive)
|
|
9
|
+
# rake release:full # Complete release workflow (prep + git_push + gem_push)
|
|
10
|
+
# rake release:prep # Run tests, build gem, create tag
|
|
11
|
+
# rake release:git_push # Push to GitHub
|
|
12
|
+
# rake release:gem_push # Publish to RubyGems
|
|
13
|
+
# rake spec:all # Run all test suites
|
|
14
|
+
# rake spec:unit # Run unit tests only (fast)
|
|
15
|
+
# rake spec:integration # Run integration tests
|
|
16
|
+
#
|
|
17
|
+
# See RELEASE.md for detailed release instructions
|
|
18
|
+
|
|
3
19
|
require "bundler/gem_tasks"
|
|
4
20
|
require "rspec/core/rake_task"
|
|
5
21
|
require "rubocop/rake_task"
|
|
@@ -10,6 +26,82 @@ RuboCop::RakeTask.new
|
|
|
10
26
|
|
|
11
27
|
task default: %i[spec rubocop]
|
|
12
28
|
|
|
29
|
+
# Test suite tasks
|
|
30
|
+
namespace :spec do
|
|
31
|
+
desc "Run unit tests only (fast, no Rails/integrations)"
|
|
32
|
+
task :unit do
|
|
33
|
+
sh "bundle exec rspec spec/e11y spec/e11y_spec.rb spec/zeitwerk_spec.rb"
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
desc "Run integration tests (requires Rails, bundle install --with integration)"
|
|
37
|
+
task :integration do
|
|
38
|
+
# Run integration tests with explicit file patterns to avoid loading all specs
|
|
39
|
+
# This prevents test pollution from unit test files
|
|
40
|
+
sh "INTEGRATION=true bundle exec rspec " \
|
|
41
|
+
"spec/integration/*.rb " \
|
|
42
|
+
"spec/e11y/adapters/*_spec.rb " \
|
|
43
|
+
"spec/e11y/instruments/*_spec.rb " \
|
|
44
|
+
"--tag integration"
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
desc "Run railtie integration tests (separate Rails app instance)"
|
|
48
|
+
task :railtie do
|
|
49
|
+
sh "bundle exec rspec spec/e11y/railtie_integration_spec.rb --tag railtie_integration"
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
desc "Run all tests (unit + integration + railtie, ~1729 examples)"
|
|
53
|
+
task :all do
|
|
54
|
+
puts "\n#{'=' * 80}"
|
|
55
|
+
puts "Running UNIT tests (spec/e11y + top-level specs)..."
|
|
56
|
+
puts "#{'=' * 80}\n"
|
|
57
|
+
Rake::Task["spec:unit"].invoke
|
|
58
|
+
|
|
59
|
+
puts "\n#{'=' * 80}"
|
|
60
|
+
puts "Running INTEGRATION tests (spec/integration)..."
|
|
61
|
+
puts "#{'=' * 80}\n"
|
|
62
|
+
Rake::Task["spec:integration"].invoke
|
|
63
|
+
|
|
64
|
+
puts "\n#{'=' * 80}"
|
|
65
|
+
puts "Running RAILTIE tests (Rails initialization)..."
|
|
66
|
+
puts "#{'=' * 80}\n"
|
|
67
|
+
Rake::Task["spec:railtie"].invoke
|
|
68
|
+
|
|
69
|
+
puts "\n#{'=' * 80}"
|
|
70
|
+
puts "✅ All test suites completed!"
|
|
71
|
+
puts "#{'=' * 80}\n"
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
desc "Run tests with coverage report"
|
|
75
|
+
task :coverage do
|
|
76
|
+
sh "COVERAGE=true bundle exec rspec"
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
desc "Run integration tests with coverage"
|
|
80
|
+
task :coverage_integration do
|
|
81
|
+
sh "COVERAGE=true INTEGRATION=true bundle exec rspec spec/integration/"
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
desc "Run benchmark tests (performance tests, slow)"
|
|
85
|
+
task :benchmark do
|
|
86
|
+
sh "bundle exec rspec spec/e11y --tag benchmark"
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
desc "Run ALL tests including benchmarks (very slow)"
|
|
90
|
+
task :everything do
|
|
91
|
+
puts "\n#{'=' * 80}"
|
|
92
|
+
puts "Running ALL tests (unit + integration + railtie + benchmarks)"
|
|
93
|
+
puts "#{'=' * 80}\n"
|
|
94
|
+
Rake::Task["spec:unit"].invoke
|
|
95
|
+
Rake::Task["spec:integration"].invoke
|
|
96
|
+
Rake::Task["spec:railtie"].invoke
|
|
97
|
+
Rake::Task["spec:benchmark"].invoke
|
|
98
|
+
|
|
99
|
+
puts "\n#{'=' * 80}"
|
|
100
|
+
puts "✅ All test suites including benchmarks completed!"
|
|
101
|
+
puts "#{'=' * 80}\n"
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
13
105
|
# Custom tasks
|
|
14
106
|
namespace :e11y do
|
|
15
107
|
desc "Start interactive console"
|
|
@@ -35,3 +127,288 @@ namespace :e11y do
|
|
|
35
127
|
sh "bundle exec brakeman --no-pager"
|
|
36
128
|
end
|
|
37
129
|
end
|
|
130
|
+
|
|
131
|
+
# Custom release automation (extends bundler/gem_tasks)
|
|
132
|
+
# Note: bundler/gem_tasks provides: release, release:guard_clean, release:rubygem_push, etc.
|
|
133
|
+
# Our tasks provide more control and visibility
|
|
134
|
+
namespace :release do
|
|
135
|
+
desc "Bump version and update CHANGELOG (interactive)"
|
|
136
|
+
task :bump do
|
|
137
|
+
require_relative "lib/e11y/version"
|
|
138
|
+
current_version = E11y::VERSION
|
|
139
|
+
|
|
140
|
+
puts "\n#{'=' * 80}"
|
|
141
|
+
puts "📝 Version Bump"
|
|
142
|
+
puts "#{'=' * 80}\n"
|
|
143
|
+
puts "Current version: #{current_version}"
|
|
144
|
+
puts "\nEnter new version (e.g., 0.2.0, 1.0.0):"
|
|
145
|
+
|
|
146
|
+
new_version = $stdin.gets.chomp.strip
|
|
147
|
+
|
|
148
|
+
if new_version.empty?
|
|
149
|
+
puts "❌ Error: Version cannot be empty"
|
|
150
|
+
exit 1
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
unless new_version.match?(/^\d+\.\d+\.\d+$/)
|
|
154
|
+
puts "❌ Error: Invalid version format. Use semantic versioning (e.g., 0.2.0)"
|
|
155
|
+
exit 1
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
if new_version == current_version
|
|
159
|
+
puts "⚠️ Warning: New version is the same as current version"
|
|
160
|
+
puts "Continue anyway? (y/N)"
|
|
161
|
+
response = $stdin.gets.chomp.downcase
|
|
162
|
+
exit 0 unless response == "y" || response == "yes"
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
puts "\n[1/3] Updating lib/e11y/version.rb..."
|
|
166
|
+
version_file = "lib/e11y/version.rb"
|
|
167
|
+
version_content = File.read(version_file)
|
|
168
|
+
updated_version_content = version_content.gsub(
|
|
169
|
+
/VERSION = "#{Regexp.escape(current_version)}"/,
|
|
170
|
+
"VERSION = \"#{new_version}\""
|
|
171
|
+
)
|
|
172
|
+
File.write(version_file, updated_version_content)
|
|
173
|
+
puts "✅ Updated: #{current_version} → #{new_version}"
|
|
174
|
+
|
|
175
|
+
puts "\n[2/3] Updating CHANGELOG.md..."
|
|
176
|
+
changelog_file = "CHANGELOG.md"
|
|
177
|
+
changelog_content = File.read(changelog_file)
|
|
178
|
+
|
|
179
|
+
# Check if there's an [Unreleased] section
|
|
180
|
+
if changelog_content.include?("## [Unreleased]")
|
|
181
|
+
# Replace [Unreleased] with version and date
|
|
182
|
+
today = Time.now.strftime("%Y-%m-%d")
|
|
183
|
+
updated_changelog = changelog_content.sub(
|
|
184
|
+
/## \[Unreleased\]/,
|
|
185
|
+
"## [#{new_version}] - #{today}"
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
# Add new [Unreleased] section at the top
|
|
189
|
+
updated_changelog = updated_changelog.sub(
|
|
190
|
+
/(## \[#{Regexp.escape(new_version)}\] - #{today})/,
|
|
191
|
+
"## [Unreleased]\n\n### Added\n\n### Changed\n\n### Fixed\n\n### Deprecated\n\n### Removed\n\n### Security\n\n\\1"
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
File.write(changelog_file, updated_changelog)
|
|
195
|
+
puts "✅ Updated CHANGELOG.md:"
|
|
196
|
+
puts " - [Unreleased] → [#{new_version}] - #{today}"
|
|
197
|
+
puts " - Added new [Unreleased] section"
|
|
198
|
+
else
|
|
199
|
+
# No [Unreleased] section, just add version entry
|
|
200
|
+
today = Time.now.strftime("%Y-%m-%d")
|
|
201
|
+
|
|
202
|
+
# Find where to insert (after the header, before first version)
|
|
203
|
+
if changelog_content =~ /(## \[\d+\.\d+\.\d+\])/
|
|
204
|
+
updated_changelog = changelog_content.sub(
|
|
205
|
+
/(## \[\d+\.\d+\.\d+\])/,
|
|
206
|
+
"## [Unreleased]\n\n### Added\n\n### Changed\n\n### Fixed\n\n### Deprecated\n\n### Removed\n\n### Security\n\n## [#{new_version}] - #{today}\n\n### Added\n- Version bump\n\n\\1"
|
|
207
|
+
)
|
|
208
|
+
else
|
|
209
|
+
# No previous versions, add after header
|
|
210
|
+
header_end = changelog_content.index("\n\n") || 0
|
|
211
|
+
header = changelog_content[0..header_end]
|
|
212
|
+
rest = changelog_content[header_end + 1..-1] || ""
|
|
213
|
+
updated_changelog = "#{header}\n## [#{new_version}] - #{today}\n\n### Added\n- Initial release\n\n#{rest}"
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
File.write(changelog_file, updated_changelog)
|
|
217
|
+
puts "✅ Added version [#{new_version}] - #{today} to CHANGELOG.md"
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
puts "\n[3/3] Summary"
|
|
221
|
+
puts "✅ Version bumped: #{current_version} → #{new_version}"
|
|
222
|
+
puts "✅ Files updated:"
|
|
223
|
+
puts " - lib/e11y/version.rb"
|
|
224
|
+
puts " - CHANGELOG.md"
|
|
225
|
+
|
|
226
|
+
puts "\n#{'=' * 80}"
|
|
227
|
+
puts "Next steps:"
|
|
228
|
+
puts " 1. Review changes: git diff"
|
|
229
|
+
puts " 2. Commit changes: git add -A && git commit -m 'Bump version to #{new_version}'"
|
|
230
|
+
puts " 3. Release: rake release:prep"
|
|
231
|
+
puts "#{'=' * 80}\n"
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
desc "Prepare release: run tests, build gem, create git tag (safe)"
|
|
235
|
+
task :prep do
|
|
236
|
+
require_relative "lib/e11y/version"
|
|
237
|
+
version = E11y::VERSION
|
|
238
|
+
|
|
239
|
+
puts "\n#{'=' * 80}"
|
|
240
|
+
puts "📦 Preparing release for e11y v#{version}"
|
|
241
|
+
puts "#{'=' * 80}\n"
|
|
242
|
+
|
|
243
|
+
# Step 1: Check git status
|
|
244
|
+
puts "\n[1/5] Checking git status..."
|
|
245
|
+
unless system("git diff-index --quiet HEAD --")
|
|
246
|
+
puts "❌ Error: You have uncommitted changes. Please commit them first."
|
|
247
|
+
exit 1
|
|
248
|
+
end
|
|
249
|
+
puts "✅ Git working directory is clean"
|
|
250
|
+
|
|
251
|
+
# Step 2: Run tests
|
|
252
|
+
puts "\n[2/5] Running tests..."
|
|
253
|
+
unless system("bundle exec rspec")
|
|
254
|
+
puts "❌ Error: Tests failed. Please fix them before releasing."
|
|
255
|
+
exit 1
|
|
256
|
+
end
|
|
257
|
+
puts "✅ All tests passed"
|
|
258
|
+
|
|
259
|
+
# Step 3: Build gem
|
|
260
|
+
puts "\n[3/5] Building gem..."
|
|
261
|
+
unless system("gem build e11y.gemspec")
|
|
262
|
+
puts "❌ Error: Failed to build gem"
|
|
263
|
+
exit 1
|
|
264
|
+
end
|
|
265
|
+
puts "✅ Gem built: e11y-#{version}.gem"
|
|
266
|
+
|
|
267
|
+
# Step 4: Create git tag
|
|
268
|
+
puts "\n[4/5] Creating git tag..."
|
|
269
|
+
tag_name = "v#{version}"
|
|
270
|
+
tag_message = "Release v#{version}"
|
|
271
|
+
|
|
272
|
+
if system("git rev-parse #{tag_name} >/dev/null 2>&1")
|
|
273
|
+
puts "⚠️ Warning: Tag #{tag_name} already exists"
|
|
274
|
+
else
|
|
275
|
+
unless system("git tag -a #{tag_name} -m '#{tag_message}'")
|
|
276
|
+
puts "❌ Error: Failed to create git tag"
|
|
277
|
+
exit 1
|
|
278
|
+
end
|
|
279
|
+
puts "✅ Git tag created: #{tag_name}"
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
# Step 5: Summary
|
|
283
|
+
puts "\n[5/5] Release preparation complete!"
|
|
284
|
+
puts "\n#{'=' * 80}"
|
|
285
|
+
puts "📦 Release v#{version} is ready!"
|
|
286
|
+
puts "#{'=' * 80}\n"
|
|
287
|
+
puts "Next steps:"
|
|
288
|
+
puts " 1. Review CHANGELOG.md"
|
|
289
|
+
puts " 2. Push to GitHub:"
|
|
290
|
+
puts " git push origin main"
|
|
291
|
+
puts " git push origin #{tag_name}"
|
|
292
|
+
puts " 3. Publish to RubyGems:"
|
|
293
|
+
puts " rake release:publish"
|
|
294
|
+
puts "\n"
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
desc "Publish gem to RubyGems.org (requires authentication, safe)"
|
|
298
|
+
task :gem_push do
|
|
299
|
+
require_relative "lib/e11y/version"
|
|
300
|
+
version = E11y::VERSION
|
|
301
|
+
gem_file = "e11y-#{version}.gem"
|
|
302
|
+
|
|
303
|
+
puts "\n#{'=' * 80}"
|
|
304
|
+
puts "📤 Publishing e11y v#{version} to RubyGems.org"
|
|
305
|
+
puts "#{'=' * 80}\n"
|
|
306
|
+
|
|
307
|
+
unless File.exist?(gem_file)
|
|
308
|
+
puts "❌ Error: Gem file not found: #{gem_file}"
|
|
309
|
+
puts "Run 'rake release:prep' first"
|
|
310
|
+
exit 1
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
puts "This will publish #{gem_file} to RubyGems.org"
|
|
314
|
+
puts "You will be prompted for your RubyGems credentials and MFA code."
|
|
315
|
+
puts "\nContinue? (y/N)"
|
|
316
|
+
|
|
317
|
+
response = $stdin.gets.chomp.downcase
|
|
318
|
+
unless %w[y yes].include?(response)
|
|
319
|
+
puts "❌ Publication cancelled"
|
|
320
|
+
exit 0
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
unless system("gem push #{gem_file}")
|
|
324
|
+
puts "\n❌ Error: Failed to publish gem"
|
|
325
|
+
puts "Make sure you have:"
|
|
326
|
+
puts " 1. RubyGems account (https://rubygems.org/sign_up)"
|
|
327
|
+
puts " 2. Signed in: gem signin"
|
|
328
|
+
puts " 3. MFA enabled on your account"
|
|
329
|
+
exit 1
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
puts "\n✅ Successfully published e11y v#{version} to RubyGems.org!"
|
|
333
|
+
puts "\nVerify: https://rubygems.org/gems/e11y/versions/#{version}"
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
desc "Push git changes and tag to GitHub (safe)"
|
|
337
|
+
task :git_push do
|
|
338
|
+
require_relative "lib/e11y/version"
|
|
339
|
+
version = E11y::VERSION
|
|
340
|
+
tag_name = "v#{version}"
|
|
341
|
+
|
|
342
|
+
puts "\n#{'=' * 80}"
|
|
343
|
+
puts "🚀 Pushing to GitHub"
|
|
344
|
+
puts "#{'=' * 80}\n"
|
|
345
|
+
|
|
346
|
+
unless system("git rev-parse #{tag_name} >/dev/null 2>&1")
|
|
347
|
+
puts "❌ Error: Tag #{tag_name} does not exist"
|
|
348
|
+
puts "Run 'rake release:prep' first"
|
|
349
|
+
exit 1
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
puts "[1/2] Pushing commits to origin/main..."
|
|
353
|
+
unless system("git push origin main")
|
|
354
|
+
puts "❌ Error: Failed to push commits"
|
|
355
|
+
exit 1
|
|
356
|
+
end
|
|
357
|
+
puts "✅ Commits pushed"
|
|
358
|
+
|
|
359
|
+
puts "\n[2/2] Pushing tag #{tag_name}..."
|
|
360
|
+
unless system("git push origin #{tag_name}")
|
|
361
|
+
puts "❌ Error: Failed to push tag"
|
|
362
|
+
exit 1
|
|
363
|
+
end
|
|
364
|
+
puts "✅ Tag pushed"
|
|
365
|
+
|
|
366
|
+
puts "\n✅ Successfully pushed to GitHub!"
|
|
367
|
+
puts "\nCreate GitHub release: https://github.com/arturseletskiy/e11y/releases/new?tag=#{tag_name}"
|
|
368
|
+
end
|
|
369
|
+
|
|
370
|
+
desc "Complete release workflow: prep, git_push, and gem_push (interactive)"
|
|
371
|
+
task :full do
|
|
372
|
+
Rake::Task["release:prep"].invoke
|
|
373
|
+
|
|
374
|
+
puts "\n#{'=' * 80}"
|
|
375
|
+
puts "Ready to push to GitHub and publish to RubyGems?"
|
|
376
|
+
puts "=" * 80
|
|
377
|
+
puts "This will:"
|
|
378
|
+
puts " 1. Push commits and tag to GitHub"
|
|
379
|
+
puts " 2. Publish gem to RubyGems.org"
|
|
380
|
+
puts "\nContinue? (y/N)"
|
|
381
|
+
|
|
382
|
+
response = $stdin.gets.chomp.downcase
|
|
383
|
+
unless %w[y yes].include?(response)
|
|
384
|
+
puts "\n⏸️ Release prepared but not published"
|
|
385
|
+
puts "To continue later, run:"
|
|
386
|
+
puts " rake release:git_push # Push to GitHub"
|
|
387
|
+
puts " rake release:gem_push # Publish to RubyGems"
|
|
388
|
+
exit 0
|
|
389
|
+
end
|
|
390
|
+
|
|
391
|
+
Rake::Task["release:git_push"].invoke
|
|
392
|
+
Rake::Task["release:gem_push"].invoke
|
|
393
|
+
|
|
394
|
+
puts "\n#{'=' * 80}"
|
|
395
|
+
puts "🎉 Release complete!"
|
|
396
|
+
puts "=" * 80
|
|
397
|
+
puts "\nPost-release tasks:"
|
|
398
|
+
puts " 1. Create GitHub release: https://github.com/arturseletskiy/e11y/releases/new"
|
|
399
|
+
puts " 2. Verify on RubyGems: https://rubygems.org/gems/e11y"
|
|
400
|
+
puts " 3. Update README badges"
|
|
401
|
+
puts " 4. Announce on social media"
|
|
402
|
+
puts "\n"
|
|
403
|
+
end
|
|
404
|
+
|
|
405
|
+
desc "Clean up built gems"
|
|
406
|
+
task :clean do
|
|
407
|
+
puts "🧹 Cleaning up gem files..."
|
|
408
|
+
FileList["*.gem"].each do |gem_file|
|
|
409
|
+
File.delete(gem_file)
|
|
410
|
+
puts " Deleted: #{gem_file}"
|
|
411
|
+
end
|
|
412
|
+
puts "✅ Clean complete"
|
|
413
|
+
end
|
|
414
|
+
end
|