rails_app_version 1.4.0 → 1.5.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b4a38336feb27db4c69dc73aeb4358e7a5ca69e696bd197ba67bb152de74b1bb
4
- data.tar.gz: 45083bf090ffe8e0797ac4af3ad18340a1f7c1b4f8a0932e282054a8e663d5d8
3
+ metadata.gz: 409c9f1cf9552f299f410f29c6c5aec32f4690a521e2ff99a1f441a7cbfafc2e
4
+ data.tar.gz: 2859e8073f038fcbdd924ad612a6db4193e7e7944c7fcc3b15be3fb104414284
5
5
  SHA512:
6
- metadata.gz: 042bd639e93aa7f6d60cf1b8df524f66254eca8bc4b538e4877669beae575ef4fd3a3eb93d1d028f9078b35ba34bb44cf57a29245df4c4d957a7f6899e344161
7
- data.tar.gz: '0378b7c1bd0683a5dbe0095584231bd0ff5a42672fefa4a373e994b389c6942499aeab5983b35c6efcbad2e8ba8eb1efc794d11ce28928d92cf1cac2bb221dd5'
6
+ metadata.gz: a04505806cbece569d2dc2b03e55cf724782b0075386d6360acd9dcdf0a94e8b96fe5bd81c78d848bad3462f04afb609623bbb0dc106f23bedbb7ce0f8519ef8
7
+ data.tar.gz: 533d508373e430b6934e86ebb476195b5c8f064c1337cfe5964c7762ed47fa11e89e6a61df9e87a5569a170d64293aae42caa99a91aa717207019cc63280fb51
data/README.md CHANGED
@@ -4,6 +4,17 @@ Rails AppVersion provides an opinionated version and environment management for
4
4
  version and environment information throughout your application, it enables better error tracking, debugging, and
5
5
  deployment management.
6
6
 
7
+ > [!NOTE]
8
+ > **Part of this gem has been upstreamed into Rails.** Rails 8.2 ships `Rails.application.revision`, which resolves the
9
+ > deploy revision from `ENV["REVISION"]`, the `REVISION` file, then `git rev-parse HEAD`. This gem defers to it rather
10
+ > than reimplementing it. See [Revision resolution](#revision-resolution).
11
+ >
12
+ > The middleware has **not** been upstreamed, and is the main reason to still reach for this gem. It advertises the
13
+ > running version and environment on every response (`X-App-Version`, `X-App-Environment`), which lets external
14
+ > agents (uptime monitors, deploy verifiers, load balancers, LLM agents poking at an endpoint) tell *when a server was
15
+ > deployed* without shell access to it. `Rails.application.revision` is only readable from inside the process; the
16
+ > header is readable from anywhere.
17
+
7
18
  ## Why Use Rails AppVersion?
8
19
 
9
20
  Version and environment tracking are important for modern web applications, particularly when debugging issues in
@@ -17,8 +28,15 @@ production. Rails AppVersion helps you:
17
28
 
18
29
  ### Error Reporting Integration Example
19
30
 
31
+ Using [Lapsoss](https://github.com/seuros/lapsoss) - a vendor-neutral error reporting gem:
32
+
20
33
  ```ruby
21
- Sentry.init do |config|
34
+ # config/initializers/lapsoss.rb
35
+ Lapsoss.configure do |config|
36
+ # Works with any service - switch vendors without code changes
37
+ config.use_telebugs(dsn: ENV['TELEBUGS_DSN'])
38
+
39
+ # Set release and environment from Rails AppVersion
22
40
  config.release = Rails.application.version.to_s
23
41
  config.environment = Rails.application.env
24
42
  end
@@ -88,12 +106,25 @@ The default configuration file is located at `config/app_version.yml`:
88
106
  shared:
89
107
  # Attempts to read from VERSION file, falls back to '0.0.0'
90
108
  version: <%= Rails.root.join('VERSION').read.strip rescue '0.0.0' %>
91
- # Attempts to read from REVISION file, then tries git commit hash, finally falls back to '0'
92
- revision: <%= Rails.root.join('REVISION').read.strip rescue (`git rev-parse HEAD`.strip rescue '0') %>
109
+ # Blank on purpose. See "Revision resolution" below
110
+ revision:
93
111
  show_revision: <%= Rails.env.local? %>
94
112
  environment: <%= ENV.fetch('RAILS_APP_ENV', Rails.env) %>
95
113
  ```
96
114
 
115
+ #### Revision resolution
116
+
117
+ Rails 8.2 resolves the deploy revision itself via `Rails.application.revision`, checking `ENV["REVISION"]`, then the
118
+ `REVISION` file, then `git rev-parse HEAD`. This gem defers to it rather than reimplementing that lookup, so the
119
+ resolution order is:
120
+
121
+ 1. An explicit `revision:` in `config/app_version.yml`, if set
122
+ 2. `Rails.application.revision` (Rails 8.2+)
123
+ 3. On Rails 8.0/8.1 only: `ENV["REVISION"]`, then the `REVISION` file
124
+
125
+ On Rails 8.0 and 8.1 there is no `git rev-parse` fallback: deploy a `REVISION` file or set `ENV["REVISION"]` rather
126
+ than shelling out on every boot. Step 3 goes away once Rails 8.2 is the minimum supported version.
127
+
97
128
  You can customize this configuration for different environments, though we recommend maintaining version information in
98
129
  the VERSION file:
99
130
 
@@ -195,22 +226,82 @@ The gem automatically displays version and environment information when you star
195
226
 
196
227
  ```
197
228
  Welcome to the Rails console!
198
- Ruby version: 3.2.0
229
+ Ruby version: 3.4.0
199
230
  Application environment: staging
200
231
  Application version: 1.2.3
201
232
  To exit, press `Ctrl + D`.
202
233
  ```
203
234
 
235
+ ## Requirements
236
+
237
+ - Ruby >= 3.4
238
+ - Rails >= 8.0
239
+
240
+ CI runs the suite against Ruby 3.4 and 4.0, on Rails 8.0, 8.1 and `rails/rails@main`.
241
+
204
242
  ## Version Format
205
243
 
206
244
  Rails AppVersion supports several version formats:
207
245
 
208
246
  - Standard versions: "1.2.3" (major.minor.patch)
209
247
  - Short versions: "1.2" (major.minor)
210
- - Pre-release versions: "2.0.0-alpha" (with pre-release identifier)
248
+ - Pre-release versions: "2.0.0-alpha", "2.0.0.alpha" or "2.0.0.pre.alpha" (all expose `pre == "alpha"`)
249
+
250
+ Parsing is delegated to `Gem::Version`, so comparison operators behave exactly as they do for gem versions.
251
+ Malformed strings raise `ArgumentError` rather than silently parsing as `0`.
211
252
 
212
- Version strings are parsed according to Semantic Versioning principles and maintain compatibility with `Gem::Version`
213
- for comparison operations.
253
+ ## Using with release-please
254
+ To have release-please automatically update a plain VERSION file in your repository:
255
+ 1. Add the inline marker to your VERSION file:
256
+ ```
257
+ 1.0.0
258
+ ```
259
+
260
+ 2. Configure release-please
261
+
262
+ Add the following to .github/release-please-config.json:
263
+ ```json
264
+ {
265
+ "packages": {
266
+ ".": {
267
+ "release-type": "simple",
268
+ "component": "App Name",
269
+ "version-file": "VERSION"
270
+ }
271
+ }
272
+ }
273
+ ```
274
+
275
+ 3. Add the following to .github/release-please-manifest.json:
276
+ ```json
277
+ {
278
+ ".": "1.0.0"
279
+ }
280
+ ```
281
+
282
+ 4. Add the following to .github/workflows/release-please.yml:
283
+ ```yaml
284
+ name: release-please
285
+
286
+ on:
287
+ push:
288
+ branches:
289
+ - main
290
+
291
+ permissions:
292
+ contents: write
293
+ pull-requests: write
294
+
295
+ jobs:
296
+ release-please:
297
+ runs-on: ubuntu-latest
298
+ steps:
299
+ - uses: googleapis/release-please-action@v4
300
+ with:
301
+ token: ${{ secrets.RELEASE_PLEASE_TOKEN }}
302
+ config-file: .github/release-please-config.json
303
+ manifest-file: .github/.release-please-manifest.json
304
+ ```
214
305
 
215
306
  ## Contributing
216
307
 
@@ -1,6 +1,9 @@
1
1
  shared:
2
2
  version: <%= Rails.root.join('VERSION').read.strip rescue '0.0.0' %>
3
- revision: <%= Rails.root.join('REVISION').read.strip rescue (`git rev-parse HEAD`.strip rescue '0') %>
3
+ # Blank on purpose. Rails resolves the deploy revision itself from
4
+ # ENV["REVISION"], the REVISION file, then `git rev-parse HEAD`.
5
+ # Set a value here only to override that.
6
+ revision:
4
7
  show_revision: <%= Rails.env.local? %>
5
8
  environment: <%= ENV.fetch('RAILS_APP_ENV', Rails.env) %>
6
9
  middleware:
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsAppVersion
4
+ # Mixed into Rails::Application so `Rails.application.version`,
5
+ # `.env` and `.app_config` read straight off the railtie instance that
6
+ # parsed app_version.yml.
7
+ module AppInfo
8
+ def version = app_version_railtie.version
9
+ def env = app_version_railtie.env
10
+ def app_config = app_version_railtie.app_config
11
+
12
+ private
13
+
14
+ def app_version_railtie
15
+ @app_version_railtie ||= railties.find { |railtie| railtie.is_a?(RailsAppVersion::Railtie) }
16
+ end
17
+ end
18
+ end
@@ -1,21 +1,31 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RailsAppVersion
4
- Rails::Application.include AppEnvironment
5
- Rails::Application.include AppVersion
4
+ Rails::Application.include AppInfo
5
+
6
6
  class Railtie < ::Rails::Railtie
7
- CONFIG_FILE = "app_version.yml".freeze
7
+ CONFIG_FILE = "app_version.yml"
8
8
 
9
9
  class << self
10
10
  def root
11
- @root ||= Pathname.new(File.expand_path(File.expand_path("../../", __dir__)))
11
+ @root ||= Pathname.new(File.expand_path("../..", __dir__))
12
+ end
13
+
14
+ def print_console_banner
15
+ # rubocop:disable Rails/Output
16
+ puts "Welcome to the Rails console!"
17
+ puts "Ruby version: #{RUBY_VERSION}"
18
+ puts "Application environment: #{Rails.application.env}"
19
+ puts "Application version: #{Rails.application.version.full}"
20
+ puts "To exit, press `Ctrl + D`."
21
+ # rubocop:enable Rails/Output
12
22
  end
13
23
  end
14
24
 
15
25
  attr_reader :app_config, :version, :env
16
26
 
17
27
  rake_tasks do
18
- load File.expand_path("../../tasks/app_version_tasks.rake", __FILE__)
28
+ load File.expand_path("../tasks/app_version_tasks.rake", __dir__)
19
29
  end
20
30
 
21
31
  console do
@@ -24,48 +34,53 @@ module RailsAppVersion
24
34
 
25
35
  initializer "rails_app_version.fetch_config" do |app|
26
36
  @app_config = load_config(app)
27
- @version = Version.create(@app_config[:version], @app_config[:revision])
37
+ @version = Version.create(@app_config[:version], revision_for(app, @app_config))
28
38
  @env = ActiveSupport::StringInquirer.new(@app_config.fetch(:environment, Rails.env))
29
39
  end
30
40
 
31
41
  initializer "rails_app_version.middleware" do |app|
32
- # Add the middleware to the stack if enabled
33
- if @app_config.dig(:middleware, :enabled)
34
- options = @app_config.dig(:middleware, :options) || {}
35
- app.middleware.insert_before Rails::Rack::Logger, AppInfoMiddleware, options
36
- end
42
+ next unless @app_config.dig(:middleware, :enabled)
43
+
44
+ options = @app_config.dig(:middleware, :options) || {}
45
+ app.middleware.insert_before Rails::Rack::Logger, AppInfoMiddleware, options
37
46
  end
38
47
 
39
48
  private
40
49
 
50
+ # An explicit revision in app_version.yml always wins. Otherwise defer to
51
+ # Rails::Application#revision (8.2+), which already resolves
52
+ # ENV["REVISION"], the REVISION file, then `git rev-parse HEAD`. There is
53
+ # no reason for this gem to reimplement that.
54
+ def revision_for(app, config)
55
+ return config[:revision] if config[:revision].present?
56
+ return app.revision if app.respond_to?(:revision)
57
+
58
+ # Rails < 8.2 has no native lookup. Deliberately no `git rev-parse`
59
+ # fallback: ship a REVISION file or set ENV["REVISION"] instead of
60
+ # shelling out on every boot. Remove once 8.2 is the minimum.
61
+ ENV["REVISION"].presence || read_revision_file(app)
62
+ end
63
+
64
+ def read_revision_file(app)
65
+ app.root.join("REVISION").read.strip.presence
66
+ rescue SystemCallError
67
+ nil
68
+ end
69
+
70
+ # Falls back to the config shipped with the gem when the host app has no
71
+ # config/app_version.yml. `rake app:version:config` installs one.
41
72
  def load_config(app)
42
73
  app.config_for(:app_version, env: Rails.env)
43
74
  rescue StandardError => e
44
- Rails.logger.warn("Could not load app_version.yml: #{e.message}. Using default configuration.")
45
- load_default_config
75
+ Rails.logger&.warn("Could not load #{CONFIG_FILE}: #{e.message}. Using default configuration.")
76
+ default_config
46
77
  end
47
78
 
48
- def load_default_config
49
- yaml_path = Railtie.root.join("config", CONFIG_FILE)
50
- all_configs = parse_yaml_config(yaml_path)
51
- all_configs[:shared] || {}
52
- end
53
-
54
- def parse_yaml_config(path)
79
+ def default_config
80
+ path = Railtie.root.join("config", CONFIG_FILE)
55
81
  return {} unless File.exist?(path)
56
82
 
57
- require "erb"
58
- ActiveSupport::ConfigurationFile.parse(path).deep_symbolize_keys
59
- end
60
-
61
- def self.print_console_banner
62
- # rubocop:disable Rails/Output
63
- puts "Welcome to the Rails console!"
64
- puts "Ruby version: #{RUBY_VERSION}"
65
- puts "Application environment: #{Rails.application.env}"
66
- puts "Application version: #{Rails.application.version&.full}"
67
- puts "To exit, press `Ctrl + D`."
68
- # rubocop:enable Rails/Output
83
+ ActiveSupport::ConfigurationFile.parse(path).deep_symbolize_keys[:shared] || {}
69
84
  end
70
85
  end
71
86
  end
@@ -1,73 +1,61 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RailsAppVersion
4
- VERSION = "1.4.0"
5
-
4
+ VERSION = "1.5.0"
5
+
6
+ # A Gem::Version with named segments, an optional VCS revision and cache-key
7
+ # helpers.
8
+ #
9
+ # Segment parsing is delegated to Gem::Version, so "2.0.0-alpha",
10
+ # "2.0.0.alpha" and "2.0.0.pre.alpha" all describe the same prerelease, and
11
+ # malformed strings raise instead of silently parsing as 0.
6
12
  class Version < Gem::Version
7
- attr_reader :major, :minor, :patch, :pre, :revision
13
+ # Deploy scripts write "0" to REVISION when no SHA is available.
14
+ BLANK_REVISION = "0"
15
+
16
+ attr_accessor :revision
8
17
 
9
18
  def self.create(version_string, revision = nil)
10
- new(version_string).tap { |v| v.set_revision(revision) }
11
- end
19
+ raise ArgumentError, "Version string cannot be nil or empty" if version_string.to_s.strip.empty?
12
20
 
13
- def set_revision(revision)
14
- @revision = revision
21
+ new(version_string).tap { |version| version.revision = revision }
15
22
  end
16
23
 
17
- def full
18
- return to_s unless revision
19
- return to_s if revision.to_s == "0"
20
- "#{self} (#{short_revision})"
21
- end
24
+ def major = numeric_segments[0] || 0
25
+ def minor = numeric_segments[1] || 0
26
+ def patch = numeric_segments[2]
22
27
 
23
- def to_cache_key
24
- parts = [ major, minor ]
25
- parts << patch if has_patch?
26
- parts << pre if prerelease?
27
- parts.join("-")
28
- end
28
+ # The prerelease label, without the "pre" marker Gem::Version inserts:
29
+ # "2.0.0-alpha" => "alpha", "1.2.3" => nil.
30
+ def pre
31
+ return @pre if defined?(@pre)
29
32
 
30
- def short_revision
31
- revision.to_s.slice(0, 8).presence
33
+ @pre = segments.drop(numeric_segments.size).reject { |segment| segment == "pre" }.join(".").presence
32
34
  end
33
35
 
34
- def prerelease?
35
- !@pre.nil?
36
+ def full
37
+ short_revision ? "#{self} (#{short_revision})" : to_s
36
38
  end
37
39
 
38
- def production_ready?
39
- !prerelease? && major.positive?
40
- end
40
+ def short_revision
41
+ return if revision.to_s == BLANK_REVISION
41
42
 
42
- def has_patch?
43
- !@patch.nil?
43
+ revision.to_s.slice(0, 8).presence
44
44
  end
45
45
 
46
- protected
47
-
48
- def initialize(version)
49
- super
50
- parse_version(version)
46
+ def to_cache_key
47
+ parts = [ major, minor ]
48
+ parts << patch if patch
49
+ parts << pre if prerelease?
50
+ parts.join("-")
51
51
  end
52
52
 
53
- private
54
-
55
- def parse_version(version_string)
56
- if version_string.nil? || version_string.empty?
57
- raise ArgumentError, "Version string cannot be nil or empty"
58
- end
59
-
60
- parts = version_string.split(".")
61
- pre_parts = parts.last.split("-")
53
+ def production_ready? = !prerelease? && major.positive?
62
54
 
63
- if pre_parts.length > 1
64
- parts[-1] = pre_parts[0]
65
- @pre = pre_parts[1]
66
- end
55
+ private
67
56
 
68
- @major = parts[0].to_i
69
- @minor = parts[1]&.to_i || 0
70
- @patch = parts[2]&.to_i
57
+ def numeric_segments
58
+ @numeric_segments ||= segments.take_while { |segment| segment.is_a?(Integer) }
71
59
  end
72
60
  end
73
61
  end
@@ -2,10 +2,9 @@
2
2
 
3
3
  require "rails"
4
4
  require "rails/application"
5
- require "rails_app_version/version"
6
- require "rails_app_version/app_version"
7
- require "rails_app_version/app_environment"
8
5
  require "action_controller/railtie"
6
+ require "rails_app_version/version"
7
+ require "rails_app_version/app_info"
9
8
  require "rails_app_version/railtie"
10
9
 
11
10
  module RailsAppVersion
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rails_app_version
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.4.0
4
+ version: 1.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Abdelkader Boudih
@@ -15,20 +15,14 @@ dependencies:
15
15
  requirements:
16
16
  - - ">="
17
17
  - !ruby/object:Gem::Version
18
- version: '7.0'
19
- - - "<"
20
- - !ruby/object:Gem::Version
21
- version: '8.2'
18
+ version: '8.0'
22
19
  type: :runtime
23
20
  prerelease: false
24
21
  version_requirements: !ruby/object:Gem::Requirement
25
22
  requirements:
26
23
  - - ">="
27
24
  - !ruby/object:Gem::Version
28
- version: '7.0'
29
- - - "<"
30
- - !ruby/object:Gem::Version
31
- version: '8.2'
25
+ version: '8.0'
32
26
  - !ruby/object:Gem::Dependency
33
27
  name: dotenv-rails
34
28
  requirement: !ruby/object:Gem::Requirement
@@ -68,9 +62,8 @@ files:
68
62
  - Rakefile
69
63
  - config/app_version.yml
70
64
  - lib/rails_app_version.rb
71
- - lib/rails_app_version/app_environment.rb
65
+ - lib/rails_app_version/app_info.rb
72
66
  - lib/rails_app_version/app_info_middleware.rb
73
- - lib/rails_app_version/app_version.rb
74
67
  - lib/rails_app_version/railtie.rb
75
68
  - lib/rails_app_version/version.rb
76
69
  - lib/tasks/app_version_tasks.rake
@@ -88,14 +81,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
88
81
  requirements:
89
82
  - - ">="
90
83
  - !ruby/object:Gem::Version
91
- version: 3.2.0
84
+ version: 3.4.0
92
85
  required_rubygems_version: !ruby/object:Gem::Requirement
93
86
  requirements:
94
87
  - - ">="
95
88
  - !ruby/object:Gem::Version
96
89
  version: '0'
97
90
  requirements: []
98
- rubygems_version: 3.6.9
91
+ rubygems_version: 4.0.10
99
92
  specification_version: 4
100
93
  summary: Get the version of your Rails app
101
94
  test_files: []
@@ -1,15 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module RailsAppVersion
4
- module AppEnvironment
5
- extend ActiveSupport::Concern
6
-
7
- included do
8
- def env
9
- @env ||= railties.find do |railtie|
10
- railtie.is_a?(RailsAppVersion::Railtie)
11
- end.env
12
- end
13
- end
14
- end
15
- end
@@ -1,15 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module RailsAppVersion
4
- module AppVersion
5
- extend ActiveSupport::Concern
6
-
7
- included do
8
- def version
9
- @version ||= railties.find do |railtie|
10
- railtie.is_a?(RailsAppVersion::Railtie)
11
- end.version
12
- end
13
- end
14
- end
15
- end