opaque_id 1.4.0 → 1.7.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.
data/docs/usage.md CHANGED
@@ -20,13 +20,13 @@ OpaqueId can be used independently of ActiveRecord for generating secure, random
20
20
  ### Basic Usage
21
21
 
22
22
  ```ruby
23
- # Generate a default opaque ID (21 characters, alphanumeric)
23
+ # Generate a default opaque ID (18 characters, slug-like)
24
24
  id = OpaqueId.generate
25
- # => "V1StGXR8_Z5jdHi6B-myT"
25
+ # => "izkpm55j334u8x9y2a"
26
26
 
27
27
  # Generate multiple IDs
28
28
  ids = 5.times.map { OpaqueId.generate }
29
- # => ["V1StGXR8_Z5jdHi6B-myT", "K8jH2mN9_pL3qR7sT1v", ...]
29
+ # => ["izkpm55j334u8x9y2a", "k8jh2mn9pl3qr7st1v", ...]
30
30
  ```
31
31
 
32
32
  ### Custom Parameters
@@ -34,7 +34,7 @@ ids = 5.times.map { OpaqueId.generate }
34
34
  ```ruby
35
35
  # Custom length
36
36
  id = OpaqueId.generate(size: 10)
37
- # => "V1StGXR8_Z5"
37
+ # => "izkpm55j334u"
38
38
 
39
39
  # Custom alphabet
40
40
  id = OpaqueId.generate(alphabet: OpaqueId::STANDARD_ALPHABET)
@@ -42,15 +42,19 @@ id = OpaqueId.generate(alphabet: OpaqueId::STANDARD_ALPHABET)
42
42
 
43
43
  # Both custom length and alphabet
44
44
  id = OpaqueId.generate(size: 15, alphabet: OpaqueId::STANDARD_ALPHABET)
45
- # => "V1StGXR8_Z5jdHi"
45
+ # => "V1StGXR8_Z5jdHi6B"
46
46
  ```
47
47
 
48
48
  ### Built-in Alphabets
49
49
 
50
50
  ```ruby
51
- # Alphanumeric alphabet (default) - A-Z, a-z, 0-9
51
+ # Slug-like alphabet (default) - 0-9, a-z
52
+ id = OpaqueId.generate(alphabet: OpaqueId::SLUG_LIKE_ALPHABET)
53
+ # => "izkpm55j334u8x9y2a"
54
+
55
+ # Alphanumeric alphabet - A-Z, a-z, 0-9
52
56
  id = OpaqueId.generate(alphabet: OpaqueId::ALPHANUMERIC_ALPHABET)
53
- # => "V1StGXR8_Z5jdHi6B-myT"
57
+ # => "V1StGXR8Z5jdHi6BmyT"
54
58
 
55
59
  # Standard alphabet - A-Z, a-z, 0-9, -, _
56
60
  id = OpaqueId.generate(alphabet: OpaqueId::STANDARD_ALPHABET)
@@ -73,7 +77,7 @@ id = OpaqueId.generate(size: 16, alphabet: hex_alphabet)
73
77
  # URL-safe characters
74
78
  url_safe = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"
75
79
  id = OpaqueId.generate(size: 12, alphabet: url_safe)
76
- # => "V1StGXR8_Z5j"
80
+ # => "izkpm55j334u8x"
77
81
  ```
78
82
 
79
83
  ## ActiveRecord Integration
@@ -94,7 +98,7 @@ end
94
98
  # Create a new user - opaque_id is automatically generated
95
99
  user = User.create!(name: "John Doe", email: "john@example.com")
96
100
  puts user.opaque_id
97
- # => "V1StGXR8_Z5jdHi6B-myT"
101
+ # => "izkpm55j334u8x9y2a"
98
102
 
99
103
  # The ID is generated before the record is saved
100
104
  user = User.new(name: "Jane Smith")
@@ -103,17 +107,17 @@ puts user.opaque_id
103
107
 
104
108
  user.save!
105
109
  puts user.opaque_id
106
- # => "K8jH2mN9_pL3qR7sT1v" (generated on save)
110
+ # => "k8jh2mn9pl3qr7st1va" (generated on save)
107
111
  ```
108
112
 
109
113
  ### Finder Methods
110
114
 
111
115
  ```ruby
112
116
  # Find by opaque_id (returns nil if not found)
113
- user = User.find_by_opaque_id("V1StGXR8_Z5jdHi6B-myT")
117
+ user = User.find_by_opaque_id("izkpm55j334u8x9y2a")
114
118
 
115
119
  # Find by opaque_id (raises exception if not found)
116
- user = User.find_by_opaque_id!("V1StGXR8_Z5jdHi6B-myT")
120
+ user = User.find_by_opaque_id!("izkpm55j334u8x9y2a")
117
121
  # => ActiveRecord::RecordNotFound if not found
118
122
 
119
123
  # Use in scopes
@@ -123,7 +127,7 @@ class User < ApplicationRecord
123
127
  scope :by_opaque_id, ->(id) { where(opaque_id: id) }
124
128
  end
125
129
 
126
- users = User.by_opaque_id("V1StGXR8_Z5jdHi6B-myT")
130
+ users = User.by_opaque_id("izkpm55j334u8x9y2a")
127
131
  ```
128
132
 
129
133
  ### Custom Column Names
@@ -139,9 +143,9 @@ end
139
143
  # Now the methods use the custom column name
140
144
  user = User.create!(name: "John Doe")
141
145
  puts user.public_id
142
- # => "V1StGXR8_Z5jdHi6B-myT"
146
+ # => "izkpm55j334u8x9y2a"
143
147
 
144
- user = User.find_by_public_id("V1StGXR8_Z5jdHi6B-myT")
148
+ user = User.find_by_public_id("izkpm55j334u8x9y2a")
145
149
  ```
146
150
 
147
151
  ## Rails Generator
@@ -198,7 +202,7 @@ end
198
202
  # Create an order
199
203
  order = Order.create!(user_id: 1, total: 99.99)
200
204
  puts order.opaque_id
201
- # => "V1StGXR8_Z5j"
205
+ # => "izkpm55j334u8x"
202
206
 
203
207
  # Use in URLs
204
208
  order_url(order.opaque_id)
@@ -259,7 +263,7 @@ end
259
263
  # Create a user
260
264
  user = User.create!(name: "John Doe", email: "john@example.com")
261
265
  puts user.opaque_id
262
- # => "V1StGXR8_Z5jdHi6B-myT" (starts with letter)
266
+ # => "izkpm55j334u8x9y2a" (starts with letter)
263
267
 
264
268
  # Use in user profiles
265
269
  user_url(user.opaque_id)
@@ -273,7 +277,7 @@ user_url(user.opaque_id)
273
277
  ```ruby
274
278
  # Generate multiple IDs at once
275
279
  ids = 100.times.map { OpaqueId.generate }
276
- # => ["V1StGXR8_Z5jdHi6B-myT", "K8jH2mN9_pL3qR7sT1v", ...]
280
+ # => ["izkpm55j334u8x9y2a", "k8jh2mn9pl3qr7st1va", ...]
277
281
 
278
282
  # Use in bulk operations
279
283
  users_data = ids.map.with_index do |id, index|
@@ -15,8 +15,12 @@ module OpaqueId
15
15
  class_option :column_name, type: :string, default: 'opaque_id',
16
16
  desc: 'Name of the column to add'
17
17
 
18
+ class_option :limit, type: :numeric, default: 21,
19
+ desc: 'Column limit for the opaque_id string (default: 21)'
20
+
18
21
  def create_migration_file
19
22
  if model_name.present?
23
+ validate_limit_option
20
24
  table_name = model_name.tableize
21
25
  migration_template 'migration.rb.tt',
22
26
  "db/migrate/add_opaque_id_to_#{table_name}.rb"
@@ -26,11 +30,20 @@ module OpaqueId
26
30
  say 'Usage: rails generate opaque_id:install ModelName', :red
27
31
  say 'Example: rails generate opaque_id:install User', :green
28
32
  say 'Example: rails generate opaque_id:install Post --column-name=public_id', :green
33
+ say 'Example: rails generate opaque_id:install Post --limit=25', :green
29
34
  end
30
35
  end
31
36
 
32
37
  private
33
38
 
39
+ def validate_limit_option
40
+ # Default OpaqueId length is 18, so limit should be at least that
41
+ return unless options[:limit] < 18
42
+
43
+ say "Warning: Column limit (#{options[:limit]}) is less than the default OpaqueId length (18).", :yellow
44
+ say 'Consider using --limit=21 or higher to avoid truncation issues.', :yellow
45
+ end
46
+
34
47
  def add_include_to_model
35
48
  model_path = "app/models/#{model_name.underscore}.rb"
36
49
 
@@ -1,6 +1,6 @@
1
- class AddOpaqueIdTo<%= table_name.camelize %> < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
1
+ class AddOpaqueIdTo<%= model_name.tableize.camelize %> < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
2
2
  def change
3
- add_column :<%= table_name %>, :<%= options[:column_name] %>, :string
4
- add_index :<%= table_name %>, :<%= options[:column_name] %>, unique: true
3
+ add_column :<%= model_name.tableize %>, :<%= options[:column_name] %>, :string, limit: <%= options[:limit] %>
4
+ add_index :<%= model_name.tableize %>, :<%= options[:column_name] %>, unique: true
5
5
  end
6
6
  end
@@ -30,7 +30,7 @@ module OpaqueId
30
30
  end
31
31
 
32
32
  def opaque_id_length
33
- @opaque_id_length ||= 21
33
+ @opaque_id_length ||= 18
34
34
  end
35
35
 
36
36
  def opaque_id_length=(value)
@@ -38,7 +38,7 @@ module OpaqueId
38
38
  end
39
39
 
40
40
  def opaque_id_alphabet
41
- @opaque_id_alphabet ||= OpaqueId::ALPHANUMERIC_ALPHABET
41
+ @opaque_id_alphabet ||= OpaqueId::SLUG_LIKE_ALPHABET
42
42
  end
43
43
 
44
44
  def opaque_id_alphabet=(value)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module OpaqueId
4
- VERSION = '1.4.0'
4
+ VERSION = '1.7.0'
5
5
  end
data/lib/opaque_id.rb CHANGED
@@ -9,6 +9,9 @@ module OpaqueId
9
9
  class GenerationError < Error; end
10
10
  class ConfigurationError < Error; end
11
11
 
12
+ # Slug-like alphabet for URL-safe, double-click selectable IDs (36 characters)
13
+ SLUG_LIKE_ALPHABET = '0123456789abcdefghijklmnopqrstuvwxyz'
14
+
12
15
  # Standard URL-safe alphabet (64 characters)
13
16
  STANDARD_ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_'
14
17
 
@@ -17,7 +20,7 @@ module OpaqueId
17
20
 
18
21
  class << self
19
22
  # Generate a cryptographically secure random ID
20
- def generate(size: 21, alphabet: ALPHANUMERIC_ALPHABET)
23
+ def generate(size: 18, alphabet: SLUG_LIKE_ALPHABET)
21
24
  raise ConfigurationError, 'Size must be positive' unless size.positive?
22
25
  raise ConfigurationError, 'Alphabet cannot be empty' if alphabet.nil? || alphabet.empty?
23
26
 
@@ -3,7 +3,8 @@
3
3
  "packages": {
4
4
  ".": {
5
5
  "package-name": "opaque_id",
6
- "version-file": "lib/opaque_id/version.rb"
6
+ "version-file": "lib/opaque_id/version.rb",
7
+ "tag-template": "v${version}"
7
8
  }
8
9
  },
9
10
  "changelog-sections": [
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: opaque_id
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.4.0
4
+ version: 1.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joe Nyaggah
@@ -140,6 +140,7 @@ files:
140
140
  - docs/assets/css/custom.scss
141
141
  - docs/assets/images/favicon.svg
142
142
  - docs/assets/images/og-image.svg
143
+ - docs/benchmarks.md
143
144
  - docs/configuration.md
144
145
  - docs/development.md
145
146
  - docs/getting-started.md
@@ -156,14 +157,6 @@ files:
156
157
  - lib/opaque_id/model.rb
157
158
  - lib/opaque_id/version.rb
158
159
  - release-please-config.json
159
- - tasks/0001-prd-opaque-id-gem.md
160
- - tasks/0002-prd-publishing-release-automation.md
161
- - tasks/0003-prd-documentation-site.md
162
- - tasks/references/opaque_gem_requirements.md
163
- - tasks/references/original_identifiable_concern_and_nanoid.md
164
- - tasks/tasks-0001-prd-opaque-id-gem.md
165
- - tasks/tasks-0002-prd-publishing-release-automation.md
166
- - tasks/tasks-0003-prd-documentation-site.md
167
160
  homepage: https://github.com/nyaggah/opaque_id
168
161
  licenses:
169
162
  - MIT
@@ -1,202 +0,0 @@
1
- # Product Requirements Document: OpaqueId Ruby Gem
2
-
3
- ## Introduction/Overview
4
-
5
- The OpaqueId gem is a Ruby library that generates cryptographically secure, collision-free opaque identifiers for ActiveRecord models. This gem replaces the existing `nanoid.rb` dependency by implementing the same functionality using Ruby's built-in `SecureRandom` methods, eliminating external dependencies while maintaining the same security and performance characteristics.
6
-
7
- The primary problem this gem solves is preventing the exposure of incremental database IDs in public URLs and APIs, which can reveal business metrics, enable enumeration attacks, and expose internal system architecture. Instead, it provides opaque, non-sequential identifiers that are URL-friendly and cryptographically secure.
8
-
9
- ## Goals
10
-
11
- 1. **Replace nanoid.rb dependency** - Implement equivalent functionality using Ruby's built-in SecureRandom
12
- 2. **Maintain security standards** - Provide cryptographically secure ID generation with unbiased distribution
13
- 3. **Ensure performance** - Achieve 1M+ IDs/sec for 64-character alphabets, 180K+ IDs/sec for 36-character alphabets
14
- 4. **Simplify integration** - Provide seamless ActiveRecord integration with minimal configuration
15
- 5. **Enable wide adoption** - Create comprehensive documentation accessible to all Rails developers
16
- 6. **Ensure reliability** - Implement robust collision detection and retry logic
17
- 7. **Maintain compatibility** - Support Rails 8.0+ and Ruby 3.2+ environments
18
-
19
- ## User Stories
20
-
21
- ### Primary Users: Rails Developers
22
-
23
- **As a Rails developer**, I want to generate opaque IDs for my models so that I can expose public identifiers without revealing database structure.
24
-
25
- **As a Rails developer**, I want to easily integrate opaque ID generation into my existing models so that I don't have to manually implement ID generation logic.
26
-
27
- **As a Rails developer**, I want to configure ID generation parameters (length, alphabet, column name) so that I can customize the behavior for different use cases.
28
-
29
- **As a Rails developer**, I want to find records by their opaque ID so that I can build public-facing APIs and URLs.
30
-
31
- **As a Rails developer**, I want the gem to handle collision detection automatically so that I don't have to worry about duplicate IDs.
32
-
33
- **As a Rails developer**, I want comprehensive documentation and examples so that I can quickly understand and implement the gem in my project.
34
-
35
- ### Secondary Users: Security-Conscious Teams
36
-
37
- **As a security-conscious developer**, I want cryptographically secure ID generation so that my public identifiers cannot be predicted or enumerated.
38
-
39
- **As a security-conscious developer**, I want unbiased character distribution so that my IDs have maximum entropy and cannot be analyzed for patterns.
40
-
41
- ## Functional Requirements
42
-
43
- ### Core ID Generation
44
-
45
- 1. The system must generate cryptographically secure random IDs using Ruby's `SecureRandom`
46
- 2. The system must implement rejection sampling algorithm to ensure unbiased character distribution
47
- 3. The system must provide optimized fast path for 64-character alphabets using bitwise operations
48
- 4. The system must support custom alphabet configurations
49
- 5. The system must support custom ID length configurations
50
- 6. The system must provide two predefined alphabets: ALPHANUMERIC_ALPHABET (36 chars) and STANDARD_ALPHABET (64 chars)
51
-
52
- ### ActiveRecord Integration
53
-
54
- 7. The system must provide `OpaqueId::Model` concern for easy ActiveRecord integration
55
- 8. The system must automatically generate opaque IDs on model creation via `before_create` callback
56
- 9. The system must provide `find_by_opaque_id` and `find_by_opaque_id!` class methods
57
- 10. The system must support custom column names via `opaque_id_column` configuration
58
- 11. The system must implement collision detection with configurable retry attempts
59
- 12. The system must raise appropriate errors when collision resolution fails
60
-
61
- ### Rails Generator (Optional Convenience Tool)
62
-
63
- 13. The system must provide optional Rails generator `opaque_id:install` for creating migrations and updating models
64
- 14. The system must generate migration files that add opaque_id column with unique index
65
- 15. The system must automatically add `include OpaqueId::Model` to the corresponding model file
66
- 16. The system must support custom column names via generator options
67
- 17. The system must require explicit table name argument and show clear usage instructions when run without arguments
68
- 18. The system must work with any existing table (new or existing models)
69
- 19. The system must handle edge cases gracefully (missing model file, already included, different class names)
70
-
71
- ### Configuration Options
72
-
73
- 20. The system must support `opaque_id_column` configuration (default: `:opaque_id`)
74
- 21. The system must support `opaque_id_length` configuration (default: `18`)
75
- 22. The system must support `opaque_id_alphabet` configuration (default: `ALPHANUMERIC_ALPHABET`)
76
- 23. The system must support `opaque_id_require_letter_start` configuration (default: `true`)
77
- 24. The system must support `opaque_id_purge_chars` configuration (default: `nil`)
78
- 25. The system must support `opaque_id_max_retry` configuration (default: `1000`)
79
-
80
- ### Error Handling
81
-
82
- 26. The system must raise `OpaqueId::ConfigurationError` for invalid size or empty alphabet
83
- 27. The system must raise `OpaqueId::GenerationError` when collision resolution fails
84
- 28. The system must provide clear error messages for debugging
85
-
86
- ### Standalone Usage
87
-
88
- 29. The system must provide `OpaqueId.generate` method for standalone ID generation
89
- 30. The system must support all configuration options in standalone generation
90
- 31. The system must maintain the same security and performance characteristics in standalone mode
91
-
92
- ## Non-Goals (Out of Scope)
93
-
94
- 1. **Other ORM Support** - Will not support Mongoid, Sequel, or other ORMs in initial release
95
- 2. **Non-Rails Usage** - Will not provide standalone Ruby usage without ActiveRecord dependency
96
- 3. **Custom Algorithms** - Will not implement alternative ID generation algorithms beyond rejection sampling
97
- 4. **Database Migrations** - Will not provide automatic database migration for existing records
98
- 5. **ID Validation** - Will not provide built-in ID format validation (users can implement their own)
99
- 6. **Bulk Generation** - Will not provide optimized bulk ID generation methods
100
- 7. **ID Prefixes/Suffixes** - Will not support adding prefixes or suffixes to generated IDs
101
- 8. **Custom Random Sources** - Will not support custom random number generators beyond SecureRandom
102
- 9. **Interactive Generator Mode** - Will not provide interactive prompts for generator arguments
103
- 10. **Backward Compatibility** - Will not maintain compatibility with existing `public_id` implementations
104
-
105
- ## Design Considerations
106
-
107
- ### API Design
108
-
109
- - Follow Rails conventions for ActiveRecord concerns and generators
110
- - Use descriptive method names that clearly indicate functionality
111
- - Provide both safe (`find_by_opaque_id`) and unsafe (`find_by_opaque_id!`) lookup methods
112
- - Use class-level configuration options for easy customization
113
- - Follow Devise-style generator pattern for seamless integration
114
-
115
- ### Performance Optimization
116
-
117
- - Implement fast path for 64-character alphabets using bitwise operations (`byte & 63`)
118
- - Use rejection sampling with optimal mask calculation for unbiased distribution
119
- - Pre-allocate string capacity to avoid memory reallocation during generation
120
- - Batch random byte generation to minimize SecureRandom calls
121
-
122
- ### Security Considerations
123
-
124
- - Use only cryptographically secure random number generation
125
- - Implement proper rejection sampling to avoid modulo bias
126
- - Provide sufficient entropy through configurable alphabet sizes
127
- - Ensure IDs cannot be predicted or enumerated
128
-
129
- ## Technical Considerations
130
-
131
- ### Dependencies
132
-
133
- - **ActiveRecord**: >= 6.0 (targeting Rails 8.0+ compatibility)
134
- - **ActiveSupport**: >= 6.0 (for concern functionality)
135
- - **Ruby**: >= 3.2 (for modern Ruby features and performance)
136
-
137
- ### Testing Framework
138
-
139
- - Use **Minitest** instead of RSpec for consistency with Rails conventions
140
- - Implement comprehensive unit tests for all public methods
141
- - Include statistical tests for character distribution uniformity
142
- - Add performance benchmarks to ensure performance requirements are met
143
- - Test edge cases including collision scenarios and error conditions
144
-
145
- ### File Structure
146
-
147
- ```
148
- lib/
149
- ├── opaque_id.rb # Main module with generator
150
- ├── opaque_id/
151
- │ ├── version.rb # Version constant
152
- │ └── model.rb # ActiveRecord concern
153
- └── generators/
154
- └── opaque_id/
155
- ├── install_generator.rb # Migration generator
156
- └── templates/
157
- └── migration.rb.tt # Migration template
158
- ```
159
-
160
- ### Error Classes
161
-
162
- - `OpaqueId::Error` - Base error class
163
- - `OpaqueId::GenerationError` - ID generation failures
164
- - `OpaqueId::ConfigurationError` - Invalid configuration
165
-
166
- ## Success Metrics
167
-
168
- ### Performance Metrics
169
-
170
- - **Standard alphabet (64 chars)**: Achieve ~1.2M IDs/sec generation rate
171
- - **Alphanumeric alphabet (36 chars)**: Achieve ~180K IDs/sec generation rate
172
- - **Custom alphabet (20 chars)**: Achieve ~150K IDs/sec generation rate
173
-
174
- ### Quality Metrics
175
-
176
- - **Test Coverage**: Achieve 95%+ code coverage
177
- - **Documentation**: Provide comprehensive README with examples
178
- - **Error Handling**: All error conditions properly tested and documented
179
-
180
- ### Adoption Metrics
181
-
182
- - **Gem Downloads**: Target 1000+ downloads in first month
183
- - **GitHub Stars**: Target 50+ stars within 6 months
184
- - **Community Feedback**: Positive reception from Rails community
185
-
186
- ## Open Questions
187
-
188
- 1. **Version Strategy**: Should we follow semantic versioning strictly, or use a different versioning strategy for a utility gem?
189
-
190
- 2. **Backward Compatibility**: How should we handle potential breaking changes in future versions, especially regarding default configurations?
191
-
192
- 3. **Performance Testing**: Should we include automated performance regression testing in CI/CD, or rely on manual benchmarking?
193
-
194
- 4. **Documentation Hosting**: Should we create a dedicated documentation site, or rely on GitHub README and inline documentation?
195
-
196
- 5. **Community Contributions**: What level of community contribution should we expect, and how should we structure the project to encourage contributions?
197
-
198
- 6. **Integration Testing**: Should we test against multiple Rails versions in CI, or focus on the target Rails 8.0+ range?
199
-
200
- 7. **Security Auditing**: Should we implement any security auditing tools or processes for the random number generation?
201
-
202
- 8. **Migration Path**: How should we help users migrate from nanoid.rb to opaque_id, if at all?
@@ -1,206 +0,0 @@
1
- # Product Requirements Document: Publishing & Release Automation
2
-
3
- ## Introduction/Overview
4
-
5
- This PRD outlines the implementation of a fully automated publishing and release system for the OpaqueId Ruby gem. The system will automatically version, test, and publish releases to RubyGems.org based on conventional commits, eliminating manual release processes and ensuring consistent, professional releases.
6
-
7
- **Problem**: Manual release processes are error-prone, time-consuming, and inconsistent. Without automated versioning and publishing, releases can be delayed, version numbers can be inconsistent, and changelogs can be incomplete.
8
-
9
- **Goal**: Implement a fully automated CI/CD pipeline that handles versioning, testing, changelog generation, and publishing based on conventional commits and the state of the main branch.
10
-
11
- ## Goals
12
-
13
- 1. **Automated Versioning**: Automatically determine version bumps (patch/minor/major) based on conventional commit types
14
- 2. **Automated Publishing**: Automatically publish to RubyGems.org when changes are ready
15
- 3. **Quality Gates**: Ensure all tests pass, code quality standards are met, and security checks pass before release
16
- 4. **Automated Changelog**: Generate changelog entries from conventional commits
17
- 5. **Dependency Management**: Automatically check for and update dependencies weekly
18
- 6. **Commit Standardization**: Enforce conventional commit format for consistent messaging
19
- 7. **Security**: Use RubyGems trusted publishing for secure, keyless releases
20
-
21
- ## User Stories
22
-
23
- ### As a Developer
24
-
25
- - **US1**: I want my commits to automatically trigger appropriate version bumps so that I don't have to manually manage version numbers
26
- - **US2**: I want releases to be published automatically when I push to main so that I don't have to remember to manually publish
27
- - **US3**: I want commit message validation so that I follow consistent formatting standards
28
- - **US4**: I want automated dependency updates so that my gem stays secure and up-to-date
29
-
30
- ### As a Maintainer
31
-
32
- - **US5**: I want automated testing and quality checks before release so that I can trust the published code
33
- - **US6**: I want automated changelog generation so that users know what changed in each release
34
- - **US7**: I want security scanning before release so that vulnerabilities are caught early
35
-
36
- ### As a User
37
-
38
- - **US8**: I want consistent, predictable releases so that I can trust the gem's stability
39
- - **US9**: I want clear changelogs so that I understand what changed between versions
40
-
41
- ## Functional Requirements
42
-
43
- ### 1. Conventional Commits Integration
44
-
45
- 1.1. **Commit Message Validation**: Enforce conventional commit format (feat:, fix:, docs:, etc.) on all commits
46
- 1.2. **Commit Linting**: Use commitlint or similar tool to validate commit message format
47
- 1.3. **Pre-commit Hooks**: Automatically validate commit messages before they are accepted
48
- 1.4. **Commit Types**: Support standard types: feat, fix, docs, style, refactor, perf, test, chore, ci, build
49
-
50
- ### 2. Automated Versioning
51
-
52
- 2.1. **Semantic Versioning**: Use semantic versioning (MAJOR.MINOR.PATCH) based on conventional commits
53
- 2.2. **Version Bump Logic**:
54
-
55
- - `feat:` commits → MINOR version bump
56
- - `fix:` commits → PATCH version bump
57
- - `BREAKING CHANGE:` or `!` → MAJOR version bump
58
- - Other types → no version bump
59
- 2.3. **Version Detection**: Automatically detect when version should be bumped based on unreleased commits
60
- 2.4. **Version File Update**: Automatically update `lib/opaque_id/version.rb` with new version
61
-
62
- ### 3. GitHub Actions Workflow
63
-
64
- 3.1. **Single Workflow**: Update existing `main.yml` to include release automation
65
- 3.2. **Trigger Conditions**: Run on push to main branch when unreleased changes exist
66
- 3.3. **Workflow Steps**:
67
-
68
- - Checkout code
69
- - Setup Ruby environment
70
- - Install dependencies
71
- - Run tests
72
- - Run RuboCop
73
- - Security audit
74
- - Determine version bump
75
- - Update version file
76
- - Generate changelog
77
- - Create git tag
78
- - Publish to RubyGems.org
79
- - Create GitHub release
80
-
81
- ### 4. Quality Gates
82
-
83
- 4.1. **Test Execution**: Run full test suite before any release
84
- 4.2. **Code Quality**: Run RuboCop and fail if violations exist
85
- 4.3. **Security Scanning**: Run `bundle audit` to check for vulnerable dependencies
86
- 4.4. **Dependency Check**: Ensure all dependencies are up-to-date and secure
87
-
88
- ### 5. Changelog Generation
89
-
90
- 5.1. **Automated Generation**: Generate changelog from conventional commits since last release
91
- 5.2. **Changelog Format**: Use conventional changelog format with categorized sections
92
- 5.3. **Changelog Sections**: Features, Bug Fixes, Breaking Changes, Documentation, etc.
93
- 5.4. **Changelog Update**: Automatically update `CHANGELOG.md` with new entries
94
-
95
- ### 6. RubyGems Publishing
96
-
97
- 6.1. **Trusted Publishing**: Use RubyGems trusted publishing (no API keys required)
98
- 6.2. **MFA Enforcement**: Ensure MFA is required for publishing (already configured)
99
- 6.3. **Build Process**: Build gem package before publishing
100
- 6.4. **Publish Process**: Automatically push to RubyGems.org when all checks pass
101
-
102
- ### 7. Dependabot Integration
103
-
104
- 7.1. **Weekly Updates**: Check for dependency updates every Monday at 9 AM
105
- 7.2. **Update Types**: Check for both direct and indirect dependency updates
106
- 7.3. **Security Updates**: Prioritize security-related updates
107
- 7.4. **Update Strategy**: Create pull requests for dependency updates
108
-
109
- ### 8. Git Tag Management
110
-
111
- 8.1. **Automatic Tagging**: Create git tags for each release (e.g., `v1.2.3`)
112
- 8.2. **Tag Format**: Use semantic versioning format with `v` prefix
113
- 8.3. **Tag Push**: Automatically push tags to GitHub repository
114
- 8.4. **GitHub Release**: Create GitHub release with changelog and tag
115
-
116
- ## Non-Goals (Out of Scope)
117
-
118
- 1. **Manual Release Override**: No manual release triggers or overrides
119
- 2. **Multiple Branch Support**: Only support releases from main branch
120
- 3. **Pre-release Versions**: No support for alpha/beta/rc versions initially
121
- 4. **Monorepo Support**: Single gem repository only
122
- 5. **Custom Versioning Logic**: No custom versioning rules beyond conventional commits
123
- 6. **Rollback Automation**: No automatic rollback of failed releases
124
-
125
- ## Technical Considerations
126
-
127
- ### Dependencies
128
-
129
- - **Release Please**: Google's tool for PR-based versioning and changelog generation
130
- - **Bundler's release tasks**: Built-in rake tasks for building and tagging (already present)
131
- - **commitlint**: For commit message validation (optional)
132
- - **bundle-audit**: For security scanning
133
-
134
- ### GitHub Actions
135
-
136
- - **googleapis/release-please-action**: For creating release PRs and versioning
137
- - **rubygems/release-gem**: For RubyGems publishing with trusted publishing
138
- - **actions/checkout**: For code checkout
139
- - **ruby/setup-ruby**: For Ruby environment setup
140
-
141
- ### Configuration Files
142
-
143
- - **release-please-config.json**: Release Please configuration
144
- - **dependabot.yml**: Dependency update configuration
145
- - **.github/workflows/release-please.yml**: Release PR workflow
146
- - **.github/workflows/publish.yml**: Publishing workflow
147
-
148
- ### RubyGems Trusted Publishing
149
-
150
- - Configure trusted publishing on RubyGems.org
151
- - Use GitHub OIDC for authentication
152
- - No API keys or secrets required
153
-
154
- ## Success Metrics
155
-
156
- 1. **Release Automation**: 100% of releases are automated (no manual intervention)
157
- 2. **Release Frequency**: Ability to release multiple times per day if needed
158
- 3. **Quality Gates**: 0% of releases with failing tests or security issues
159
- 4. **Commit Compliance**: 100% of commits follow conventional commit format
160
- 5. **Dependency Currency**: Dependencies updated within 7 days of availability
161
- 6. **Release Time**: From commit to published gem in under 10 minutes
162
-
163
- ## Open Questions
164
-
165
- 1. **Commit Message Enforcement**: Should we use pre-commit hooks or GitHub Actions validation?
166
- 2. **Changelog Format**: Should we use conventional-changelog or custom format?
167
- 3. **Version Bump Strategy**: Should we batch multiple commits into single releases?
168
- 4. **Rollback Strategy**: How should we handle failed releases or rollbacks?
169
- 5. **Notification Strategy**: Should we notify maintainers of successful/failed releases?
170
- 6. **Branch Protection**: Should we require status checks before merging to main?
171
-
172
- ## Implementation Priority
173
-
174
- ### Phase 1: Core Automation
175
-
176
- - Conventional commits validation
177
- - Automated versioning
178
- - Basic GitHub Actions workflow
179
- - RubyGems publishing
180
-
181
- ### Phase 2: Quality & Security
182
-
183
- - Quality gates (tests, RuboCop, security)
184
- - Automated changelog generation
185
- - Dependabot integration
186
-
187
- ### Phase 3: Polish & Monitoring
188
-
189
- - Enhanced notifications
190
- - Release monitoring
191
- - Performance optimization
192
-
193
- ## Acceptance Criteria
194
-
195
- - [ ] All commits follow conventional commit format
196
- - [ ] Version numbers are automatically bumped based on commit types
197
- - [ ] Releases are automatically published to RubyGems.org
198
- - [ ] Changelog is automatically generated from commits
199
- - [ ] All tests pass before release
200
- - [ ] RuboCop passes before release
201
- - [ ] Security audit passes before release
202
- - [ ] Dependencies are automatically updated weekly
203
- - [ ] Git tags are automatically created for releases
204
- - [ ] GitHub releases are automatically created
205
- - [ ] RubyGems trusted publishing is configured
206
- - [ ] No manual intervention required for releases