Repository files navigation

Semverve

Build StatusLanguage: RubyGem VersionLicense: MIT

Rake tasks for handling the tedium surrounding maintaining a version number in your Ruby project, with gusto!

About

Maintaining a gem version is not hard, but there are so many little pieces that are easy to forget. How many times have you had changes where the code was ready, the tests were green, the PR was merged, you go to push the gem, and you realize you forgot to bump the version? Then comes the tiny follow-up PR that forces you to waste CI minutes for a two-line change, you submit it, and... oh, no! You still have references to the old version number in your documentation! Rinse and repeat until you finally remember all the things.

Semverve is meant to make that tedium boring in the best way. It provides a small set of Rake tasks for reading the current version, generating a version file, incrementing patch/minor/major versions, setting an exact version, and checking the places where version numbers tend to drift, like .gemspec files and documentation.

In a nutshell, rake semverve:increment:patch updates the "patch" level in your configured version.rb file, :minor updates the "minor" level, etc.. Calling rake semverve:check checks whether the surrounding project still agrees with that version. It can catch stale README references, safe code literals, .gemspec drift, and a stale Gemfile.lock entry. If you want Semverve to do the mechanical cleanup, the matching *:fix tasks can update safe references and run bundle lock for generated lockfile drift. Specific findings can be skipped with magic comments, similar to RuboCop and RDoc.

You can view the documentation here.

For release history and upgrading notes, see CHANGELOG.md.

Installation

Add the gem to your Gemfile:

gem"semverve"

Then add this to your Rakefile:

require"semverve/task"Semverve::Task.new

This defines:

rake semverve:current
rake semverve:increment:patch
rake semverve:increment:minor
rake semverve:increment:major
rake semverve:generate
rake 'semverve:set[1.2.3]'
rake semverve:check
rake semverve:fix
rake semverve:check:references
rake semverve:fix:references
rake semverve:check:code
rake semverve:fix:code
rake semverve:check:package_metadata
rake semverve:fix:package_metadata
rake semverve:check:rails_config_metadata
rake semverve:fix:rails_config_metadata
rake semverve:check:rubygems
rake semverve:check:release

Configuration

By default, Semverve reads the single .gemspec in the project root, uses spec.name as the gem name, and manages lib/<gem_name>/version.rb.

For a conventional gem, this may be all you need:

require"semverve/task"Semverve::Task.new

That installs the semverve:* Rake tasks. If you want to change any defaults, configure Semverve from the task block in your Rakefile:

require"semverve/task"Semverve::Task.newdo |config|
config.format=:moduleconfig.bundle_lock=trueconfig.version_file="lib/my_gem/version.rb"config.module_name="MyGem"config.version_checks=[:doc_references,:code_references,:package_metadata]config.release_checks=[:rubygems]config.rubygems_host="https://rubygems.org"config.version_code_reference_files.append("lib/**/*.rb")config.version_doc_reference_files.append("doc/**/*.md")config.version_match_mode=:non_currentend

The core defaults are equivalent to:

Semverve::Task.newdo |config|
config.adapter=nilconfig.format=:moduleconfig.bundle_lock=falseconfig.root=Dir.pwdconfig.version_checks=[:doc_references,:code_references,:package_metadata]config.release_checks=[]config.rubygems_host="https://rubygems.org"config.task_namespace=:semverveconfig.version_match_mode=:olderconfig.version_code_reference_files=Rake::FileList[]config.version_code_reference_pattern=/^\s*(?:(?:[A-Z]\w*::)*(?:[A-Z]\w*VERSION[A-Z0-9_]*|VERSION)|(?:[a-z_]\w*|self)\.version)\s*=\s*(?<quote>["'])(?<version>\d+\.\d+\.\d+)\k<quote>/config.version_doc_reference_files=Rake::FileList["README*","**/README*"].exclude(".git/**/*","coverage/**/*","tmp/**/*","vendor/**/*")end

The empty version_code_reference_files default only applies to arbitrary code literal scanning. rake semverve:check still checks the resolved .gemspec version and matching Gemfile.lock entry through its default package metadata check. Release checks are empty by default because they may make network requests and are intended for release pipelines rather than every local or pull-request run.

Set config.task_namespace in the task block to use a shorter or project-specific namespace:

require"semverve/task"Semverve::Task.newdo |config|
config.task_namespace=:versionend

That installs tasks such as version:current, version:increment:patch, and version:check instead of semverve:*.

Semverve tasks use Rake task arguments for values:

rake 'semverve:set[1.2.3]'
rake 'semverve:generate[1.0.0,simple]'
rake 'semverve:generate[simple]'
rake 'semverve:generate[force]'

Quote task invocations that include square brackets. Shells such as zsh may otherwise treat brackets as glob patterns before Rake sees them. Flag syntax such as rake semverve:set --version 1.2.3 is not used because --version is already a Rake option; Semverve stays within Rake's native argument syntax instead.

The gem name, module name, and version-file path are inferred by default:

config.gem_name# spec.name from the single .gemspecconfig.module_name# camelized gem name, such as "MyGem"config.version_file# lib/<gem_name>/version.rb

Override them when your project does something unusual:

Semverve::Task.newdo |config|
config.gem_name="my-gem"config.module_name="MyGem"config.version_file="lib/my_gem/version.rb"end

Framework adapters

Framework adapters provide app-oriented defaults without requiring a gemspec or package identity. config.adapter is the preferred API; config.preset remains supported as a backward-compatible alias.

Rails apps

Rails applications do not need gem-style version files, but an application version can still be useful for release notes, support/debug screens, deployment metadata, or API output.

When Rails is loaded, Semverve's Railtie installs the same semverve:* Rake tasks for bin/rails/rails automatically. To use Rails-style defaults, set the Rails adapter:

Semverve::Task.newdo |config|
config.adapter=:railsend

config.preset = :rails is still accepted for existing setups.

The Rails adapter uses Rails.root, stores the version in config/version.rb, uses the :simple format, and infers the module name from your Rails application module when possible. Its default checks are app-oriented: documentation references, configured code literals, and optional Rails config metadata. It does not run package metadata checks unless you opt in.

Generate the file with:

bin/rails semverve:generate

If your app keeps the version somewhere else, override the path:

Semverve::Task.newdo |config|
config.adapter=:railsconfig.version_file="config/releases/version.rb"end

Rails support is only an adapter and a Railtie; Semverve does not require a dummy app, a Rails plugin layout, or a Rails dependency.

Rails config metadata is optional. When present, Semverve checks safe literals in config/application.rb, config/environments/*.rb, and config/initializers/**/*.rb:

config.x.version="1.2.2"Rails.application.config.x.version="1.2.2"

Dynamic assignments are treated as self-managed and left alone:

config.x.version=Storefront::VERSION

Rails engines or apps that publish gems can opt into package metadata checks by setting config.gem_name and including :package_metadata in config.version_checks. Deployment and container metadata, such as Docker, Kamal, and Helm, are intentionally left for future adapter support.

Sinatra apps

Sinatra applications can use the Sinatra adapter for app-style defaults:

Semverve::Task.newdo |config|
config.adapter=:sinatraend

The Sinatra adapter stores the version in config/version.rb, uses the :simple format, infers the module name from the project directory, and checks documentation references plus configured code literals by default. It does not infer a package name from config/version.rb and does not run package metadata checks unless you opt in.

Formats

The default :module format stores MAJOR, MINOR, and PATCH constants under a Version module and exposes a top-level VERSION constant.

moduleMyGemmoduleVersionMAJOR=0MINOR=1PATCH=0module_functiondefto_a[MAJOR,MINOR,PATCH]enddefto_sto_a.join(".")endendVERSION=Version.to_send

The :simple format stores only:

moduleMyGemVERSION="1.0.0"end

Generating

Generate the default module format:

rake semverve:generate

Generate a specific version or format:

rake 'semverve:generate[1.0.0,simple]'
rake 'semverve:generate[simple]'

Generation fails if the target file already exists. To replace it:

rake 'semverve:generate[force]'

semverve:generate accepts optional tokens for version, format, and force. Token order does not matter: semantic versions set the generated version, module or simple sets the format, and force overwrites an existing version file.

rake 'semverve:generate[1.0.0,force]'
rake 'semverve:generate[simple,force]'
rake 'semverve:generate[1.0.0,simple,force]'

Bootstrapping a new gem

If your gemspec dynamically requires the generated version file, a brand-new project can fail before semverve:generate has a chance to run:

require_relative"lib/my_gem/version"

Rake loads the Rakefile before it can invoke any task. If loading the Rakefile also loads bundler/gem_tasks, Bundler evaluates the gemspec, and an unconditional require_relative can crash because lib/my_gem/version.rb does not exist yet.

Prefer guarding Bundler's gem tasks while running semverve:generate:

require"semverve/task"unlessARGV.any?{ |arg| arg.start_with?("semverve:generate")}require"bundler/gem_tasks"endSemverve::Task.new

Then generate the version file:

rake semverve:generate

After generation, you can keep the guard or switch back to your normal dynamic gemspec loading style.

Semverve's metadata inference does not evaluate the gemspec for generation; it reads the literal spec.name, so either bootstrap pattern still allows semverve:generate to infer lib/my_gem/version.rb.

Alternatively, make the gemspec tolerate the missing file during bootstrap:

version_path=File.expand_path("lib/my_gem/version",__dir__)requireversion_pathifFile.file?("#{version_path}.rb")Gem::Specification.newdo |spec|
spec.name="my_gem"spec.version=defined?(MyGem::VERSION) ? MyGem::VERSION : "0.0.0"semverveend

Incrementing

rake semverve:increment:patch
rake semverve:increment:minor
rake semverve:increment:major

Patch increments only patch. Minor increments minor and resets patch to 0. Major increments major and resets minor and patch to 0.

Successful increments print the version change:

Updating to version 2.0.2 (was 2.0.1)

Set config.bundle_lock = true to run bundle lock after increments to update your gem's version in Gemfile.lock.

Setting

Set an exact version without incrementing:

rake 'semverve:set[1.2.3]'

Successful updates print the version change:

Updating to version 1.2.3 (was 1.2.2)

Setting the current version again does not rewrite the version file:

Version is already 1.2.3

Setting a lower version prints a warning but still updates the file:

Warning: updating to version 1.9.9, which is lower than the current version 2.0.1.
Updating to version 1.9.9 (was 2.0.1)

This will also run bundle lock on success if you have config.bundle_lock = true in your config.

Checking version references, code, and metadata

Run every version check with:

rake semverve:check

This task is designed for normal CI. It uses local project files, prints parseable findings, and exits non-zero when it finds drift.

By default, gem/package projects check:

  • README version references, plus any configured docs or comment files
  • configured code files for safe version literals
  • the gemspec version and Gemfile.lock entry

Rails adapter projects instead check README references, configured code literals, and optional Rails config metadata.

Findings are printed in parseable formats and the task exits non-zero:

README.md:12:24: version reference 1.2.2 -> 1.2.3
lib/my_gem/constants.rb:1:16: code version literal 1.2.2 -> 1.2.3
my_gem.gemspec:3:18: gemspec version 1.2.2 -> 1.2.3
Gemfile.lock:4:13: locked version 1.2.2 -> 1.2.3

Run every available fix:

rake semverve:fix

Choose which surfaces the umbrella check and fix tasks run with config.version_checks:

Semverve::Task.newdo |config|
config.version_checks=[:doc_references,:package_metadata]end

The allowed values come from Semverve's check registry. Built-in checks are :doc_references, :code_references, and :package_metadata; framework adapters can add their own checks, such as Rails' :rails_config_metadata.

Use focused tasks when you want only one surface:

rake semverve:check:references
rake semverve:fix:references
rake semverve:check:code
rake semverve:fix:code
rake semverve:check:package_metadata
rake semverve:fix:package_metadata
rake semverve:check:rails_config_metadata
rake semverve:fix:rails_config_metadata

semverve:fix:package_metadata rewrites literal gemspec versions when safe and runs bundle lock for Gemfile.lock drift. semverve:fix:rails_config_metadata rewrites safe Rails config version literals.

Pass a semantic version when you want to check or fix only that exact version in doc references and code literals:

rake 'semverve:check[1.2.2]'
rake 'semverve:fix:references[1.2.2]'

Package metadata and adapter-owned metadata checks still compare metadata to the current version. If you target the current version, check tasks list reference/code matches but fix tasks are no-ops because the text is already current.

Extension API

Semverve exposes small public objects for framework adapters and version checks. These APIs are intentionally local registration APIs; Semverve does not yet autoload third-party adapter gems.

Register a framework adapter with Semverve::Adapters.register. An adapter must expose name, defaults(configuration), and checks. It can also expose infer_package_name? to control whether app-style version files should be treated as package names.

Register a check with Semverve::VersionChecks.register, or return adapter-owned checks from an adapter's checks method. A check object should expose:

  • name and task_name
  • check_description, fix_description, finding_label, and fix_label
  • clean_message, targetable?, and exact_target_fix_noop_notice?
  • findings(configuration, current_version, include_ignored:, target_version:)
  • fix(configuration, current_version, target_version:)

Checks should return Semverve::Finding objects from findings and a Semverve::FixResult from fix. Semverve::VersionMatchPolicy and Semverve::VersionLiteralRewriter are available for checks that need Semverve's standard stale-version matching or named-capture literal rewriting.

For example:

classMyConfigVersionCheck < Semverve::VersionChecks::Checkdefname=:my_config_metadatadeftask_name=:my_config_metadatadefcheck_description="Check app config metadata for version mismatches"deffix_description="Fix safe app config metadata version mismatches"deffinding_label="app config version"defclean_message="App config metadata is current."deffindings(configuration,current_version,include_ignored: false,target_version: nil)[]enddeffix(configuration,current_version,target_version: nil)Semverve::FixResult.new(changed_files: [],replacement_count: 0)endend

Version references

Version references are prose-like references to versions. These are usually in README files, docs, guides, changelogs, or comments. By default, Semverve scans README files throughout the repo:

Semverve::Task.newdo |config|
config.version_doc_reference_files=Rake::FileList["README*","**/README*"].exclude(".git/**/*","coverage/**/*","tmp/**/*","vendor/**/*")end

Add docs or Ruby comments without replacing the README defaults:

Semverve::Task.newdo |config|
config.version_doc_reference_files.append("doc/**/*.md","lib/**/*.rb")end

Replace the defaults entirely:

Semverve::Task.newdo |config|
config.version_doc_reference_files=Rake::FileList["guides/**/*.md"]end

Ruby files are scanned only in comments. Text files with .md, .markdown, .txt, .rdoc, and .adoc extensions are scanned as full text.

The default version match mode is :older, which flags only semantic versions lower than the current version in doc references and code literals:

Semverve::Task.newdo |config|
config.version_match_mode=:olderend

Use :non_current when every doc reference and code literal should match the current version:

Semverve::Task.newdo |config|
config.version_match_mode=:non_currentend

Ignore an intentional reference with semverve:ignore-version-reference on the same line or the preceding nonblank line.

This migration note intentionally mentions 1.0.0. <!-- semverve:ignore-version-reference -->

Prefer ignore markers when possible because they move with the ignored text. Configured ignores are for edge cases where a marker would render as unwanted content, such as inside a Markdown code block. They depend on line numbers, so they can drift when the file changes:

Semverve::Task.newdo |config|
config.version_reference_ignores={"README.md"=>{42=>["1.0.0"],45=>"2.0.0"}}end

Configured ignores apply to documentation references and code literals. They are exact to the version string, so other stale versions on the same line are still reported. Fix tasks leave configured ignores unchanged.

Audit ignored references by setting SEMVERVE_REPORT_IGNORED=true when running check tasks:

SEMVERVE_REPORT_IGNORED=true rake semverve:check
SEMVERVE_REPORT_IGNORED=true rake 'semverve:check[1.2.2]'

Code version literals

Code scanning is opt-in to avoid false positives. This is for arbitrary project code, not package metadata. The default is:

Semverve::Task.newdo |config|
config.version_code_reference_files=Rake::FileList[]end

Append files when you want Semverve to check safe code literals:

Semverve::Task.newdo |config|
config.version_code_reference_files.append("lib/**/*.rb")end

Or replace the list entirely:

Semverve::Task.newdo |config|
config.version_code_reference_files=Rake::FileList["lib/**/*.rb","*.gemspec"]end

Ruby code checks only obvious version assignments/constants, such as:

APP_VERSION="1.2.2"spec.version="1.2.2"

Ignore an intentional code literal with semverve:ignore-version-reference on the same line or the preceding nonblank line.

Set SEMVERVE_REPORT_IGNORED=true with semverve:check or semverve:check:code to report ignored stale literals without changing semverve:fix behavior.

The default pattern is Ruby-oriented. Semverve does not inspect file extensions or parse other languages for code literals; non-Ruby files are scanned as plain text with the same pattern. If a JavaScript, Python, or other source file uses a different version-literal shape, configure a custom pattern before adding those files.

Arbitrary string examples are ignored.

If your project has a different safe version-literal shape, provide your own pattern:

Semverve::Task.newdo |config|
config.version_code_reference_files.append("lib/**/*.rb")config.version_code_reference_pattern=/release ["'](?<version>\d+\.\d+\.\d+)["']/end

With that pattern, this line:

release"1.2.2"

matches the full release "1.2.2" text, but only 1.2.2 is captured as version. If rake semverve:fix:code is updating references to 1.2.3, the line becomes:

release"1.2.3"

The custom value must be a Regexp and must include a named capture called version. Semverve replaces only that capture when running rake semverve:fix:code, and the captured value still has to parse as a semantic version.

Package metadata

Package metadata checks are part of rake semverve:check by default for gem/package projects. They compare the current version file against:

  • the resolved .gemspec version
  • the matching Gemfile.lock entry, when a lockfile exists

Package metadata always requires an exact match, regardless of config.version_match_mode.

No file-list configuration is needed for these checks. Semverve resolves the gemspec from the project root and reads Gemfile.lock when one exists.

Dynamic gemspec versions work as expected:

require_relative"lib/my_gem/version"Gem::Specification.newdo |spec|
spec.name="my_gem"spec.version=MyGem::VERSIONend

Literal gemspec versions can be fixed automatically:

Gem::Specification.newdo |spec|
spec.name="my_gem"spec.version="1.2.2"end

rake semverve:fix:package_metadata updates safe literal gemspec assignments and runs bundle lock when the lockfile has drifted.

Rails config metadata

Rails config metadata checks are part of rake semverve:check when the Rails adapter is active. They scan config/application.rb, config/environments/*.rb, and config/initializers/**/*.rb for optional Rails config literals:

config.x.version="1.2.2"Rails.application.config.x.version="1.2.2"

These checks always require an exact match with the current Semverve version. rake semverve:fix:rails_config_metadata rewrites only those safe string literals. Dynamic assignments, including config.x.version = Storefront::VERSION, are considered self-managed and ignored.

Checking release readiness

Release checks are separate from rake semverve:check. They are useful in CI, but they are meant for release workflows, tag builds, or pre-publish jobs rather than every pull request.

Enable the RubyGems published-version check:

Semverve::Task.newdo |config|
config.release_checks=[:rubygems]end

Then run:

rake semverve:check:release

With :rubygems enabled, semverve:check:release asks the configured RubyGems-compatible host whether the current version already exists. If it does, the task exits non-zero with a message like:

my_gem 1.2.3 already exists on https://rubygems.org.

If all configured release checks pass, it prints:

Release checks passed.

You can also run the RubyGems check directly without changing config.release_checks:

rake semverve:check:rubygems

When the current version is not published, the focused task prints:

my_gem 1.2.3 is not published on https://rubygems.org.

Use config.rubygems_host for a private RubyGems-compatible server:

Semverve::Task.newdo |config|
config.release_checks=[:rubygems]config.rubygems_host="https://gems.example.com"end

The published-version check treats a missing gem as unpublished. It fails closed on registry errors, malformed responses, and network failures because release pipelines should not silently publish after an inconclusive preflight.

For ordinary CI, keep using the local checks:

bundle exec rake test semverve:check

For release CI, run the release check before building or pushing:

bundle exec rake semverve:check:release build

Vim

While there's no official vim support (yet), you can add the following to ~/.vim/plugin/semverve.vim.

command!-bang SemverveAudit call<SID>semverve_audit(<bang>0)
function!s:semverve_audit(report_ignored) abortletl:old_efm= &errorformattrylet &errorformat='%f:%l:%c:%m'letl:string=""if!a:report_ignoredletl:string.='SEMVERVE_REPORT_IGNORED=true 'endifletl:string.='bundle exec rake semverve:check 2>/dev/null'cexprsystemlist(l:string)
ifv:shell_error!=0copenelseccloseecho'Semverve checks passed.'endiffinallylet &errorformat=l:old_efmendtryendfunction

You can then call :SemverveAudit, which will call bundle exec rake semverve:check, and :SemverveAudit! which will call the same command with SEMVERVE_REPORT_IGNORED=true, and populate and open the quickfix list if any offenses are found.

Reporting Bugs and Requesting Features

If you have an idea or find a bug, please create an issue. Just make sure the topic doesn't already exist. Better yet, you can always submit a Pull Request.

Support this project

I love knowing when people find my work useful. Any kind of support is very much appreciated!

About

🏷️ Rake tasks for incrementing gem versions and finding and updating version references in documentation and code.

Resources

Contributing

Stars

1 star

Watchers

1 watching

Forks

Releases

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Repository files navigation

Semverve

Build StatusLanguage: RubyGem VersionLicense: MIT

Rake tasks for handling the tedium surrounding maintaining a version number in your Ruby project, with gusto!

About

Maintaining a gem version is not hard, but there are so many little pieces that are easy to forget. How many times have you had changes where the code was ready, the tests were green, the PR was merged, you go to push the gem, and you realize you forgot to bump the version? Then comes the tiny follow-up PR that forces you to waste CI minutes for a two-line change, you submit it, and... oh, no! You still have references to the old version number in your documentation! Rinse and repeat until you finally remember all the things.

Semverve is meant to make that tedium boring in the best way. It provides a small set of Rake tasks for reading the current version, generating a version file, incrementing patch/minor/major versions, setting an exact version, and checking the places where version numbers tend to drift, like .gemspec files and documentation.

In a nutshell, rake semverve:increment:patch updates the "patch" level in your configured version.rb file, :minor updates the "minor" level, etc.. Calling rake semverve:check checks whether the surrounding project still agrees with that version. It can catch stale README references, safe code literals, .gemspec drift, and a stale Gemfile.lock entry. If you want Semverve to do the mechanical cleanup, the matching *:fix tasks can update safe references and run bundle lock for generated lockfile drift. Specific findings can be skipped with magic comments, similar to RuboCop and RDoc.

You can view the documentation here.

For release history and upgrading notes, see CHANGELOG.md.

Installation

Add the gem to your Gemfile:

gem"semverve"

Then add this to your Rakefile:

require"semverve/task"Semverve::Task.new

This defines:

rake semverve:current
rake semverve:increment:patch
rake semverve:increment:minor
rake semverve:increment:major
rake semverve:generate
rake 'semverve:set[1.2.3]'
rake semverve:check
rake semverve:fix
rake semverve:check:references
rake semverve:fix:references
rake semverve:check:code
rake semverve:fix:code
rake semverve:check:package_metadata
rake semverve:fix:package_metadata
rake semverve:check:rails_config_metadata
rake semverve:fix:rails_config_metadata
rake semverve:check:rubygems
rake semverve:check:release

Configuration

By default, Semverve reads the single .gemspec in the project root, uses spec.name as the gem name, and manages lib/<gem_name>/version.rb.

For a conventional gem, this may be all you need:

require"semverve/task"Semverve::Task.new

That installs the semverve:* Rake tasks. If you want to change any defaults, configure Semverve from the task block in your Rakefile:

require"semverve/task"Semverve::Task.newdo |config|
config.format=:moduleconfig.bundle_lock=trueconfig.version_file="lib/my_gem/version.rb"config.module_name="MyGem"config.version_checks=[:doc_references,:code_references,:package_metadata]config.release_checks=[:rubygems]config.rubygems_host="https://rubygems.org"config.version_code_reference_files.append("lib/**/*.rb")config.version_doc_reference_files.append("doc/**/*.md")config.version_match_mode=:non_currentend

The core defaults are equivalent to:

Semverve::Task.newdo |config|
config.adapter=nilconfig.format=:moduleconfig.bundle_lock=falseconfig.root=Dir.pwdconfig.version_checks=[:doc_references,:code_references,:package_metadata]config.release_checks=[]config.rubygems_host="https://rubygems.org"config.task_namespace=:semverveconfig.version_match_mode=:olderconfig.version_code_reference_files=Rake::FileList[]config.version_code_reference_pattern=/^\s*(?:(?:[A-Z]\w*::)*(?:[A-Z]\w*VERSION[A-Z0-9_]*|VERSION)|(?:[a-z_]\w*|self)\.version)\s*=\s*(?<quote>["'])(?<version>\d+\.\d+\.\d+)\k<quote>/config.version_doc_reference_files=Rake::FileList["README*","**/README*"].exclude(".git/**/*","coverage/**/*","tmp/**/*","vendor/**/*")end

The empty version_code_reference_files default only applies to arbitrary code literal scanning. rake semverve:check still checks the resolved .gemspec version and matching Gemfile.lock entry through its default package metadata check. Release checks are empty by default because they may make network requests and are intended for release pipelines rather than every local or pull-request run.

Set config.task_namespace in the task block to use a shorter or project-specific namespace:

require"semverve/task"Semverve::Task.newdo |config|
config.task_namespace=:versionend

That installs tasks such as version:current, version:increment:patch, and version:check instead of semverve:*.

Semverve tasks use Rake task arguments for values:

rake 'semverve:set[1.2.3]'
rake 'semverve:generate[1.0.0,simple]'
rake 'semverve:generate[simple]'
rake 'semverve:generate[force]'

Quote task invocations that include square brackets. Shells such as zsh may otherwise treat brackets as glob patterns before Rake sees them. Flag syntax such as rake semverve:set --version 1.2.3 is not used because --version is already a Rake option; Semverve stays within Rake's native argument syntax instead.

The gem name, module name, and version-file path are inferred by default:

config.gem_name# spec.name from the single .gemspecconfig.module_name# camelized gem name, such as "MyGem"config.version_file# lib/<gem_name>/version.rb

Override them when your project does something unusual:

Semverve::Task.newdo |config|
config.gem_name="my-gem"config.module_name="MyGem"config.version_file="lib/my_gem/version.rb"end

Framework adapters

Framework adapters provide app-oriented defaults without requiring a gemspec or package identity. config.adapter is the preferred API; config.preset remains supported as a backward-compatible alias.

Rails apps

Rails applications do not need gem-style version files, but an application version can still be useful for release notes, support/debug screens, deployment metadata, or API output.

When Rails is loaded, Semverve's Railtie installs the same semverve:* Rake tasks for bin/rails/rails automatically. To use Rails-style defaults, set the Rails adapter:

Semverve::Task.newdo |config|
config.adapter=:railsend

config.preset = :rails is still accepted for existing setups.

The Rails adapter uses Rails.root, stores the version in config/version.rb, uses the :simple format, and infers the module name from your Rails application module when possible. Its default checks are app-oriented: documentation references, configured code literals, and optional Rails config metadata. It does not run package metadata checks unless you opt in.

Generate the file with:

bin/rails semverve:generate

If your app keeps the version somewhere else, override the path:

Semverve::Task.newdo |config|
config.adapter=:railsconfig.version_file="config/releases/version.rb"end

Rails support is only an adapter and a Railtie; Semverve does not require a dummy app, a Rails plugin layout, or a Rails dependency.

Rails config metadata is optional. When present, Semverve checks safe literals in config/application.rb, config/environments/*.rb, and config/initializers/**/*.rb:

config.x.version="1.2.2"Rails.application.config.x.version="1.2.2"

Dynamic assignments are treated as self-managed and left alone:

config.x.version=Storefront::VERSION

Rails engines or apps that publish gems can opt into package metadata checks by setting config.gem_name and including :package_metadata in config.version_checks. Deployment and container metadata, such as Docker, Kamal, and Helm, are intentionally left for future adapter support.

Sinatra apps

Sinatra applications can use the Sinatra adapter for app-style defaults:

Semverve::Task.newdo |config|
config.adapter=:sinatraend

The Sinatra adapter stores the version in config/version.rb, uses the :simple format, infers the module name from the project directory, and checks documentation references plus configured code literals by default. It does not infer a package name from config/version.rb and does not run package metadata checks unless you opt in.

Formats

The default :module format stores MAJOR, MINOR, and PATCH constants under a Version module and exposes a top-level VERSION constant.

moduleMyGemmoduleVersionMAJOR=0MINOR=1PATCH=0module_functiondefto_a[MAJOR,MINOR,PATCH]enddefto_sto_a.join(".")endendVERSION=Version.to_send

The :simple format stores only:

moduleMyGemVERSION="1.0.0"end

Generating

Generate the default module format:

rake semverve:generate

Generate a specific version or format:

rake 'semverve:generate[1.0.0,simple]'
rake 'semverve:generate[simple]'

Generation fails if the target file already exists. To replace it:

rake 'semverve:generate[force]'

semverve:generate accepts optional tokens for version, format, and force. Token order does not matter: semantic versions set the generated version, module or simple sets the format, and force overwrites an existing version file.

rake 'semverve:generate[1.0.0,force]'
rake 'semverve:generate[simple,force]'
rake 'semverve:generate[1.0.0,simple,force]'

Bootstrapping a new gem

If your gemspec dynamically requires the generated version file, a brand-new project can fail before semverve:generate has a chance to run:

require_relative"lib/my_gem/version"

Rake loads the Rakefile before it can invoke any task. If loading the Rakefile also loads bundler/gem_tasks, Bundler evaluates the gemspec, and an unconditional require_relative can crash because lib/my_gem/version.rb does not exist yet.

Prefer guarding Bundler's gem tasks while running semverve:generate:

require"semverve/task"unlessARGV.any?{ |arg| arg.start_with?("semverve:generate")}require"bundler/gem_tasks"endSemverve::Task.new

Then generate the version file:

rake semverve:generate

After generation, you can keep the guard or switch back to your normal dynamic gemspec loading style.

Semverve's metadata inference does not evaluate the gemspec for generation; it reads the literal spec.name, so either bootstrap pattern still allows semverve:generate to infer lib/my_gem/version.rb.

Alternatively, make the gemspec tolerate the missing file during bootstrap:

version_path=File.expand_path("lib/my_gem/version",__dir__)requireversion_pathifFile.file?("#{version_path}.rb")Gem::Specification.newdo |spec|
spec.name="my_gem"spec.version=defined?(MyGem::VERSION) ? MyGem::VERSION : "0.0.0"semverveend

Incrementing

rake semverve:increment:patch
rake semverve:increment:minor
rake semverve:increment:major

Patch increments only patch. Minor increments minor and resets patch to 0. Major increments major and resets minor and patch to 0.

Successful increments print the version change:

Updating to version 2.0.2 (was 2.0.1)

Set config.bundle_lock = true to run bundle lock after increments to update your gem's version in Gemfile.lock.

Setting

Set an exact version without incrementing:

rake 'semverve:set[1.2.3]'

Successful updates print the version change:

Updating to version 1.2.3 (was 1.2.2)

Setting the current version again does not rewrite the version file:

Version is already 1.2.3

Setting a lower version prints a warning but still updates the file:

Warning: updating to version 1.9.9, which is lower than the current version 2.0.1.
Updating to version 1.9.9 (was 2.0.1)

This will also run bundle lock on success if you have config.bundle_lock = true in your config.

Checking version references, code, and metadata

Run every version check with:

rake semverve:check

This task is designed for normal CI. It uses local project files, prints parseable findings, and exits non-zero when it finds drift.

By default, gem/package projects check:

  • README version references, plus any configured docs or comment files
  • configured code files for safe version literals
  • the gemspec version and Gemfile.lock entry

Rails adapter projects instead check README references, configured code literals, and optional Rails config metadata.

Findings are printed in parseable formats and the task exits non-zero:

README.md:12:24: version reference 1.2.2 -> 1.2.3
lib/my_gem/constants.rb:1:16: code version literal 1.2.2 -> 1.2.3
my_gem.gemspec:3:18: gemspec version 1.2.2 -> 1.2.3
Gemfile.lock:4:13: locked version 1.2.2 -> 1.2.3

Run every available fix:

rake semverve:fix

Choose which surfaces the umbrella check and fix tasks run with config.version_checks:

Semverve::Task.newdo |config|
config.version_checks=[:doc_references,:package_metadata]end

The allowed values come from Semverve's check registry. Built-in checks are :doc_references, :code_references, and :package_metadata; framework adapters can add their own checks, such as Rails' :rails_config_metadata.

Use focused tasks when you want only one surface:

rake semverve:check:references
rake semverve:fix:references
rake semverve:check:code
rake semverve:fix:code
rake semverve:check:package_metadata
rake semverve:fix:package_metadata
rake semverve:check:rails_config_metadata
rake semverve:fix:rails_config_metadata

semverve:fix:package_metadata rewrites literal gemspec versions when safe and runs bundle lock for Gemfile.lock drift. semverve:fix:rails_config_metadata rewrites safe Rails config version literals.

Pass a semantic version when you want to check or fix only that exact version in doc references and code literals:

rake 'semverve:check[1.2.2]'
rake 'semverve:fix:references[1.2.2]'

Package metadata and adapter-owned metadata checks still compare metadata to the current version. If you target the current version, check tasks list reference/code matches but fix tasks are no-ops because the text is already current.

Extension API

Semverve exposes small public objects for framework adapters and version checks. These APIs are intentionally local registration APIs; Semverve does not yet autoload third-party adapter gems.

Register a framework adapter with Semverve::Adapters.register. An adapter must expose name, defaults(configuration), and checks. It can also expose infer_package_name? to control whether app-style version files should be treated as package names.

Register a check with Semverve::VersionChecks.register, or return adapter-owned checks from an adapter's checks method. A check object should expose:

  • name and task_name
  • check_description, fix_description, finding_label, and fix_label
  • clean_message, targetable?, and exact_target_fix_noop_notice?
  • findings(configuration, current_version, include_ignored:, target_version:)
  • fix(configuration, current_version, target_version:)

Checks should return Semverve::Finding objects from findings and a Semverve::FixResult from fix. Semverve::VersionMatchPolicy and Semverve::VersionLiteralRewriter are available for checks that need Semverve's standard stale-version matching or named-capture literal rewriting.

For example:

classMyConfigVersionCheck < Semverve::VersionChecks::Checkdefname=:my_config_metadatadeftask_name=:my_config_metadatadefcheck_description="Check app config metadata for version mismatches"deffix_description="Fix safe app config metadata version mismatches"deffinding_label="app config version"defclean_message="App config metadata is current."deffindings(configuration,current_version,include_ignored: false,target_version: nil)[]enddeffix(configuration,current_version,target_version: nil)Semverve::FixResult.new(changed_files: [],replacement_count: 0)endend

Version references

Version references are prose-like references to versions. These are usually in README files, docs, guides, changelogs, or comments. By default, Semverve scans README files throughout the repo:

Semverve::Task.newdo |config|
config.version_doc_reference_files=Rake::FileList["README*","**/README*"].exclude(".git/**/*","coverage/**/*","tmp/**/*","vendor/**/*")end

Add docs or Ruby comments without replacing the README defaults:

Semverve::Task.newdo |config|
config.version_doc_reference_files.append("doc/**/*.md","lib/**/*.rb")end

Replace the defaults entirely:

Semverve::Task.newdo |config|
config.version_doc_reference_files=Rake::FileList["guides/**/*.md"]end

Ruby files are scanned only in comments. Text files with .md, .markdown, .txt, .rdoc, and .adoc extensions are scanned as full text.

The default version match mode is :older, which flags only semantic versions lower than the current version in doc references and code literals:

Semverve::Task.newdo |config|
config.version_match_mode=:olderend

Use :non_current when every doc reference and code literal should match the current version:

Semverve::Task.newdo |config|
config.version_match_mode=:non_currentend

Ignore an intentional reference with semverve:ignore-version-reference on the same line or the preceding nonblank line.

This migration note intentionally mentions 1.0.0. <!-- semverve:ignore-version-reference -->

Prefer ignore markers when possible because they move with the ignored text. Configured ignores are for edge cases where a marker would render as unwanted content, such as inside a Markdown code block. They depend on line numbers, so they can drift when the file changes:

Semverve::Task.newdo |config|
config.version_reference_ignores={"README.md"=>{42=>["1.0.0"],45=>"2.0.0"}}end

Configured ignores apply to documentation references and code literals. They are exact to the version string, so other stale versions on the same line are still reported. Fix tasks leave configured ignores unchanged.

Audit ignored references by setting SEMVERVE_REPORT_IGNORED=true when running check tasks:

SEMVERVE_REPORT_IGNORED=true rake semverve:check
SEMVERVE_REPORT_IGNORED=true rake 'semverve:check[1.2.2]'

Code version literals

Code scanning is opt-in to avoid false positives. This is for arbitrary project code, not package metadata. The default is:

Semverve::Task.newdo |config|
config.version_code_reference_files=Rake::FileList[]end

Append files when you want Semverve to check safe code literals:

Semverve::Task.newdo |config|
config.version_code_reference_files.append("lib/**/*.rb")end

Or replace the list entirely:

Semverve::Task.newdo |config|
config.version_code_reference_files=Rake::FileList["lib/**/*.rb","*.gemspec"]end

Ruby code checks only obvious version assignments/constants, such as:

APP_VERSION="1.2.2"spec.version="1.2.2"

Ignore an intentional code literal with semverve:ignore-version-reference on the same line or the preceding nonblank line.

Set SEMVERVE_REPORT_IGNORED=true with semverve:check or semverve:check:code to report ignored stale literals without changing semverve:fix behavior.

The default pattern is Ruby-oriented. Semverve does not inspect file extensions or parse other languages for code literals; non-Ruby files are scanned as plain text with the same pattern. If a JavaScript, Python, or other source file uses a different version-literal shape, configure a custom pattern before adding those files.

Arbitrary string examples are ignored.

If your project has a different safe version-literal shape, provide your own pattern:

Semverve::Task.newdo |config|
config.version_code_reference_files.append("lib/**/*.rb")config.version_code_reference_pattern=/release ["'](?<version>\d+\.\d+\.\d+)["']/end

With that pattern, this line:

release"1.2.2"

matches the full release "1.2.2" text, but only 1.2.2 is captured as version. If rake semverve:fix:code is updating references to 1.2.3, the line becomes:

release"1.2.3"

The custom value must be a Regexp and must include a named capture called version. Semverve replaces only that capture when running rake semverve:fix:code, and the captured value still has to parse as a semantic version.

Package metadata

Package metadata checks are part of rake semverve:check by default for gem/package projects. They compare the current version file against:

  • the resolved .gemspec version
  • the matching Gemfile.lock entry, when a lockfile exists

Package metadata always requires an exact match, regardless of config.version_match_mode.

No file-list configuration is needed for these checks. Semverve resolves the gemspec from the project root and reads Gemfile.lock when one exists.

Dynamic gemspec versions work as expected:

require_relative"lib/my_gem/version"Gem::Specification.newdo |spec|
spec.name="my_gem"spec.version=MyGem::VERSIONend

Literal gemspec versions can be fixed automatically:

Gem::Specification.newdo |spec|
spec.name="my_gem"spec.version="1.2.2"end

rake semverve:fix:package_metadata updates safe literal gemspec assignments and runs bundle lock when the lockfile has drifted.

Rails config metadata

Rails config metadata checks are part of rake semverve:check when the Rails adapter is active. They scan config/application.rb, config/environments/*.rb, and config/initializers/**/*.rb for optional Rails config literals:

config.x.version="1.2.2"Rails.application.config.x.version="1.2.2"

These checks always require an exact match with the current Semverve version. rake semverve:fix:rails_config_metadata rewrites only those safe string literals. Dynamic assignments, including config.x.version = Storefront::VERSION, are considered self-managed and ignored.

Checking release readiness

Release checks are separate from rake semverve:check. They are useful in CI, but they are meant for release workflows, tag builds, or pre-publish jobs rather than every pull request.

Enable the RubyGems published-version check:

Semverve::Task.newdo |config|
config.release_checks=[:rubygems]end

Then run:

rake semverve:check:release

With :rubygems enabled, semverve:check:release asks the configured RubyGems-compatible host whether the current version already exists. If it does, the task exits non-zero with a message like:

my_gem 1.2.3 already exists on https://rubygems.org.

If all configured release checks pass, it prints:

Release checks passed.

You can also run the RubyGems check directly without changing config.release_checks:

rake semverve:check:rubygems

When the current version is not published, the focused task prints:

my_gem 1.2.3 is not published on https://rubygems.org.

Use config.rubygems_host for a private RubyGems-compatible server:

Semverve::Task.newdo |config|
config.release_checks=[:rubygems]config.rubygems_host="https://gems.example.com"end

The published-version check treats a missing gem as unpublished. It fails closed on registry errors, malformed responses, and network failures because release pipelines should not silently publish after an inconclusive preflight.

For ordinary CI, keep using the local checks:

bundle exec rake test semverve:check

For release CI, run the release check before building or pushing:

bundle exec rake semverve:check:release build

Vim

While there's no official vim support (yet), you can add the following to ~/.vim/plugin/semverve.vim.

command!-bang SemverveAudit call<SID>semverve_audit(<bang>0)
function!s:semverve_audit(report_ignored) abortletl:old_efm= &errorformattrylet &errorformat='%f:%l:%c:%m'letl:string=""if!a:report_ignoredletl:string.='SEMVERVE_REPORT_IGNORED=true 'endifletl:string.='bundle exec rake semverve:check 2>/dev/null'cexprsystemlist(l:string)
ifv:shell_error!=0copenelseccloseecho'Semverve checks passed.'endiffinallylet &errorformat=l:old_efmendtryendfunction

You can then call :SemverveAudit, which will call bundle exec rake semverve:check, and :SemverveAudit! which will call the same command with SEMVERVE_REPORT_IGNORED=true, and populate and open the quickfix list if any offenses are found.

Reporting Bugs and Requesting Features

If you have an idea or find a bug, please create an issue. Just make sure the topic doesn't already exist. Better yet, you can always submit a Pull Request.

Support this project

I love knowing when people find my work useful. Any kind of support is very much appreciated!

About

🏷️ Rake tasks for incrementing gem versions and finding and updating version references in documentation and code.

Resources

Contributing

Stars

1 star

Watchers

1 watching

Forks

Releases

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Semverve

Build StatusLanguage: RubyGem VersionLicense: MIT

Rake tasks for handling the tedium surrounding maintaining a version number in your Ruby project, with gusto!

About

Maintaining a gem version is not hard, but there are so many little pieces that are easy to forget. How many times have you had changes where the code was ready, the tests were green, the PR was merged, you go to push the gem, and you realize you forgot to bump the version? Then comes the tiny follow-up PR that forces you to waste CI minutes for a two-line change, you submit it, and... oh, no! You still have references to the old version number in your documentation! Rinse and repeat until you finally remember all the things.

Semverve is meant to make that tedium boring in the best way. It provides a small set of Rake tasks for reading the current version, generating a version file, incrementing patch/minor/major versions, setting an exact version, and checking the places where version numbers tend to drift, like .gemspec files and documentation.

In a nutshell, rake semverve:increment:patch updates the "patch" level in your configured version.rb file, :minor updates the "minor" level, etc.. Calling rake semverve:check checks whether the surrounding project still agrees with that version. It can catch stale README references, safe code literals, .gemspec drift, and a stale Gemfile.lock entry. If you want Semverve to do the mechanical cleanup, the matching *:fix tasks can update safe references and run bundle lock for generated lockfile drift. Specific findings can be skipped with magic comments, similar to RuboCop and RDoc.

You can view the documentation here.

For release history and upgrading notes, see CHANGELOG.md.

Installation

Add the gem to your Gemfile:

gem"semverve"

Then add this to your Rakefile:

require"semverve/task"Semverve::Task.new

This defines:

rake semverve:current
rake semverve:increment:patch
rake semverve:increment:minor
rake semverve:increment:major
rake semverve:generate
rake 'semverve:set[1.2.3]'
rake semverve:check
rake semverve:fix
rake semverve:check:references
rake semverve:fix:references
rake semverve:check:code
rake semverve:fix:code
rake semverve:check:package_metadata
rake semverve:fix:package_metadata
rake semverve:check:rails_config_metadata
rake semverve:fix:rails_config_metadata
rake semverve:check:rubygems
rake semverve:check:release

Configuration

By default, Semverve reads the single .gemspec in the project root, uses spec.name as the gem name, and manages lib/<gem_name>/version.rb.

For a conventional gem, this may be all you need:

require"semverve/task"Semverve::Task.new

That installs the semverve:* Rake tasks. If you want to change any defaults, configure Semverve from the task block in your Rakefile:

require"semverve/task"Semverve::Task.newdo |config|
config.format=:moduleconfig.bundle_lock=trueconfig.version_file="lib/my_gem/version.rb"config.module_name="MyGem"config.version_checks=[:doc_references,:code_references,:package_metadata]config.release_checks=[:rubygems]config.rubygems_host="https://rubygems.org"config.version_code_reference_files.append("lib/**/*.rb")config.version_doc_reference_files.append("doc/**/*.md")config.version_match_mode=:non_currentend

The core defaults are equivalent to:

Semverve::Task.newdo |config|
config.adapter=nilconfig.format=:moduleconfig.bundle_lock=falseconfig.root=Dir.pwdconfig.version_checks=[:doc_references,:code_references,:package_metadata]config.release_checks=[]config.rubygems_host="https://rubygems.org"config.task_namespace=:semverveconfig.version_match_mode=:olderconfig.version_code_reference_files=Rake::FileList[]config.version_code_reference_pattern=/^\s*(?:(?:[A-Z]\w*::)*(?:[A-Z]\w*VERSION[A-Z0-9_]*|VERSION)|(?:[a-z_]\w*|self)\.version)\s*=\s*(?<quote>["'])(?<version>\d+\.\d+\.\d+)\k<quote>/config.version_doc_reference_files=Rake::FileList["README*","**/README*"].exclude(".git/**/*","coverage/**/*","tmp/**/*","vendor/**/*")end

The empty version_code_reference_files default only applies to arbitrary code literal scanning. rake semverve:check still checks the resolved .gemspec version and matching Gemfile.lock entry through its default package metadata check. Release checks are empty by default because they may make network requests and are intended for release pipelines rather than every local or pull-request run.

Set config.task_namespace in the task block to use a shorter or project-specific namespace:

require"semverve/task"Semverve::Task.newdo |config|
config.task_namespace=:versionend

That installs tasks such as version:current, version:increment:patch, and version:check instead of semverve:*.

Semverve tasks use Rake task arguments for values:

rake 'semverve:set[1.2.3]'
rake 'semverve:generate[1.0.0,simple]'
rake 'semverve:generate[simple]'
rake 'semverve:generate[force]'

Quote task invocations that include square brackets. Shells such as zsh may otherwise treat brackets as glob patterns before Rake sees them. Flag syntax such as rake semverve:set --version 1.2.3 is not used because --version is already a Rake option; Semverve stays within Rake's native argument syntax instead.

The gem name, module name, and version-file path are inferred by default:

config.gem_name# spec.name from the single .gemspecconfig.module_name# camelized gem name, such as "MyGem"config.version_file# lib/<gem_name>/version.rb

Override them when your project does something unusual:

Semverve::Task.newdo |config|
config.gem_name="my-gem"config.module_name="MyGem"config.version_file="lib/my_gem/version.rb"end

Framework adapters

Framework adapters provide app-oriented defaults without requiring a gemspec or package identity. config.adapter is the preferred API; config.preset remains supported as a backward-compatible alias.

Rails apps

Rails applications do not need gem-style version files, but an application version can still be useful for release notes, support/debug screens, deployment metadata, or API output.

When Rails is loaded, Semverve's Railtie installs the same semverve:* Rake tasks for bin/rails/rails automatically. To use Rails-style defaults, set the Rails adapter:

Semverve::Task.newdo |config|
config.adapter=:railsend

config.preset = :rails is still accepted for existing setups.

The Rails adapter uses Rails.root, stores the version in config/version.rb, uses the :simple format, and infers the module name from your Rails application module when possible. Its default checks are app-oriented: documentation references, configured code literals, and optional Rails config metadata. It does not run package metadata checks unless you opt in.

Generate the file with:

bin/rails semverve:generate

If your app keeps the version somewhere else, override the path:

Semverve::Task.newdo |config|
config.adapter=:railsconfig.version_file="config/releases/version.rb"end

Rails support is only an adapter and a Railtie; Semverve does not require a dummy app, a Rails plugin layout, or a Rails dependency.

Rails config metadata is optional. When present, Semverve checks safe literals in config/application.rb, config/environments/*.rb, and config/initializers/**/*.rb:

config.x.version="1.2.2"Rails.application.config.x.version="1.2.2"

Dynamic assignments are treated as self-managed and left alone:

config.x.version=Storefront::VERSION

Rails engines or apps that publish gems can opt into package metadata checks by setting config.gem_name and including :package_metadata in config.version_checks. Deployment and container metadata, such as Docker, Kamal, and Helm, are intentionally left for future adapter support.

Sinatra apps

Sinatra applications can use the Sinatra adapter for app-style defaults:

Semverve::Task.newdo |config|
config.adapter=:sinatraend

The Sinatra adapter stores the version in config/version.rb, uses the :simple format, infers the module name from the project directory, and checks documentation references plus configured code literals by default. It does not infer a package name from config/version.rb and does not run package metadata checks unless you opt in.

Formats

The default :module format stores MAJOR, MINOR, and PATCH constants under a Version module and exposes a top-level VERSION constant.

moduleMyGemmoduleVersionMAJOR=0MINOR=1PATCH=0module_functiondefto_a[MAJOR,MINOR,PATCH]enddefto_sto_a.join(".")endendVERSION=Version.to_send

The :simple format stores only:

moduleMyGemVERSION="1.0.0"end

Generating

Generate the default module format:

rake semverve:generate

Generate a specific version or format:

rake 'semverve:generate[1.0.0,simple]'
rake 'semverve:generate[simple]'

Generation fails if the target file already exists. To replace it:

rake 'semverve:generate[force]'

semverve:generate accepts optional tokens for version, format, and force. Token order does not matter: semantic versions set the generated version, module or simple sets the format, and force overwrites an existing version file.

rake 'semverve:generate[1.0.0,force]'
rake 'semverve:generate[simple,force]'
rake 'semverve:generate[1.0.0,simple,force]'

Bootstrapping a new gem

If your gemspec dynamically requires the generated version file, a brand-new project can fail before semverve:generate has a chance to run:

require_relative"lib/my_gem/version"

Rake loads the Rakefile before it can invoke any task. If loading the Rakefile also loads bundler/gem_tasks, Bundler evaluates the gemspec, and an unconditional require_relative can crash because lib/my_gem/version.rb does not exist yet.

Prefer guarding Bundler's gem tasks while running semverve:generate:

require"semverve/task"unlessARGV.any?{ |arg| arg.start_with?("semverve:generate")}require"bundler/gem_tasks"endSemverve::Task.new

Then generate the version file:

rake semverve:generate

After generation, you can keep the guard or switch back to your normal dynamic gemspec loading style.

Semverve's metadata inference does not evaluate the gemspec for generation; it reads the literal spec.name, so either bootstrap pattern still allows semverve:generate to infer lib/my_gem/version.rb.

Alternatively, make the gemspec tolerate the missing file during bootstrap:

version_path=File.expand_path("lib/my_gem/version",__dir__)requireversion_pathifFile.file?("#{version_path}.rb")Gem::Specification.newdo |spec|
spec.name="my_gem"spec.version=defined?(MyGem::VERSION) ? MyGem::VERSION : "0.0.0"semverveend

Incrementing

rake semverve:increment:patch
rake semverve:increment:minor
rake semverve:increment:major

Patch increments only patch. Minor increments minor and resets patch to 0. Major increments major and resets minor and patch to 0.

Successful increments print the version change:

Updating to version 2.0.2 (was 2.0.1)

Set config.bundle_lock = true to run bundle lock after increments to update your gem's version in Gemfile.lock.

Setting

Set an exact version without incrementing:

rake 'semverve:set[1.2.3]'

Successful updates print the version change:

Updating to version 1.2.3 (was 1.2.2)

Setting the current version again does not rewrite the version file:

Version is already 1.2.3

Setting a lower version prints a warning but still updates the file:

Warning: updating to version 1.9.9, which is lower than the current version 2.0.1.
Updating to version 1.9.9 (was 2.0.1)

This will also run bundle lock on success if you have config.bundle_lock = true in your config.

Checking version references, code, and metadata

Run every version check with:

rake semverve:check

This task is designed for normal CI. It uses local project files, prints parseable findings, and exits non-zero when it finds drift.

By default, gem/package projects check:

  • README version references, plus any configured docs or comment files
  • configured code files for safe version literals
  • the gemspec version and Gemfile.lock entry

Rails adapter projects instead check README references, configured code literals, and optional Rails config metadata.

Findings are printed in parseable formats and the task exits non-zero:

README.md:12:24: version reference 1.2.2 -> 1.2.3
lib/my_gem/constants.rb:1:16: code version literal 1.2.2 -> 1.2.3
my_gem.gemspec:3:18: gemspec version 1.2.2 -> 1.2.3
Gemfile.lock:4:13: locked version 1.2.2 -> 1.2.3

Run every available fix:

rake semverve:fix

Choose which surfaces the umbrella check and fix tasks run with config.version_checks:

Semverve::Task.newdo |config|
config.version_checks=[:doc_references,:package_metadata]end

The allowed values come from Semverve's check registry. Built-in checks are :doc_references, :code_references, and :package_metadata; framework adapters can add their own checks, such as Rails' :rails_config_metadata.

Use focused tasks when you want only one surface:

rake semverve:check:references
rake semverve:fix:references
rake semverve:check:code
rake semverve:fix:code
rake semverve:check:package_metadata
rake semverve:fix:package_metadata
rake semverve:check:rails_config_metadata
rake semverve:fix:rails_config_metadata

semverve:fix:package_metadata rewrites literal gemspec versions when safe and runs bundle lock for Gemfile.lock drift. semverve:fix:rails_config_metadata rewrites safe Rails config version literals.

Pass a semantic version when you want to check or fix only that exact version in doc references and code literals:

rake 'semverve:check[1.2.2]'
rake 'semverve:fix:references[1.2.2]'

Package metadata and adapter-owned metadata checks still compare metadata to the current version. If you target the current version, check tasks list reference/code matches but fix tasks are no-ops because the text is already current.

Extension API

Semverve exposes small public objects for framework adapters and version checks. These APIs are intentionally local registration APIs; Semverve does not yet autoload third-party adapter gems.

Register a framework adapter with Semverve::Adapters.register. An adapter must expose name, defaults(configuration), and checks. It can also expose infer_package_name? to control whether app-style version files should be treated as package names.

Register a check with Semverve::VersionChecks.register, or return adapter-owned checks from an adapter's checks method. A check object should expose:

  • name and task_name
  • check_description, fix_description, finding_label, and fix_label
  • clean_message, targetable?, and exact_target_fix_noop_notice?
  • findings(configuration, current_version, include_ignored:, target_version:)
  • fix(configuration, current_version, target_version:)

Checks should return Semverve::Finding objects from findings and a Semverve::FixResult from fix. Semverve::VersionMatchPolicy and Semverve::VersionLiteralRewriter are available for checks that need Semverve's standard stale-version matching or named-capture literal rewriting.

For example:

classMyConfigVersionCheck < Semverve::VersionChecks::Checkdefname=:my_config_metadatadeftask_name=:my_config_metadatadefcheck_description="Check app config metadata for version mismatches"deffix_description="Fix safe app config metadata version mismatches"deffinding_label="app config version"defclean_message="App config metadata is current."deffindings(configuration,current_version,include_ignored: false,target_version: nil)[]enddeffix(configuration,current_version,target_version: nil)Semverve::FixResult.new(changed_files: [],replacement_count: 0)endend

Version references

Version references are prose-like references to versions. These are usually in README files, docs, guides, changelogs, or comments. By default, Semverve scans README files throughout the repo:

Semverve::Task.newdo |config|
config.version_doc_reference_files=Rake::FileList["README*","**/README*"].exclude(".git/**/*","coverage/**/*","tmp/**/*","vendor/**/*")end

Add docs or Ruby comments without replacing the README defaults:

Semverve::Task.newdo |config|
config.version_doc_reference_files.append("doc/**/*.md","lib/**/*.rb")end

Replace the defaults entirely:

Semverve::Task.newdo |config|
config.version_doc_reference_files=Rake::FileList["guides/**/*.md"]end

Ruby files are scanned only in comments. Text files with .md, .markdown, .txt, .rdoc, and .adoc extensions are scanned as full text.

The default version match mode is :older, which flags only semantic versions lower than the current version in doc references and code literals:

Semverve::Task.newdo |config|
config.version_match_mode=:olderend

Use :non_current when every doc reference and code literal should match the current version:

Semverve::Task.newdo |config|
config.version_match_mode=:non_currentend

Ignore an intentional reference with semverve:ignore-version-reference on the same line or the preceding nonblank line.

This migration note intentionally mentions 1.0.0. <!-- semverve:ignore-version-reference -->

Prefer ignore markers when possible because they move with the ignored text. Configured ignores are for edge cases where a marker would render as unwanted content, such as inside a Markdown code block. They depend on line numbers, so they can drift when the file changes:

Semverve::Task.newdo |config|
config.version_reference_ignores={"README.md"=>{42=>["1.0.0"],45=>"2.0.0"}}end

Configured ignores apply to documentation references and code literals. They are exact to the version string, so other stale versions on the same line are still reported. Fix tasks leave configured ignores unchanged.

Audit ignored references by setting SEMVERVE_REPORT_IGNORED=true when running check tasks:

SEMVERVE_REPORT_IGNORED=true rake semverve:check
SEMVERVE_REPORT_IGNORED=true rake 'semverve:check[1.2.2]'

Code version literals

Code scanning is opt-in to avoid false positives. This is for arbitrary project code, not package metadata. The default is:

Semverve::Task.newdo |config|
config.version_code_reference_files=Rake::FileList[]end

Append files when you want Semverve to check safe code literals:

Semverve::Task.newdo |config|
config.version_code_reference_files.append("lib/**/*.rb")end

Or replace the list entirely:

Semverve::Task.newdo |config|
config.version_code_reference_files=Rake::FileList["lib/**/*.rb","*.gemspec"]end

Ruby code checks only obvious version assignments/constants, such as:

APP_VERSION="1.2.2"spec.version="1.2.2"

Ignore an intentional code literal with semverve:ignore-version-reference on the same line or the preceding nonblank line.

Set SEMVERVE_REPORT_IGNORED=true with semverve:check or semverve:check:code to report ignored stale literals without changing semverve:fix behavior.

The default pattern is Ruby-oriented. Semverve does not inspect file extensions or parse other languages for code literals; non-Ruby files are scanned as plain text with the same pattern. If a JavaScript, Python, or other source file uses a different version-literal shape, configure a custom pattern before adding those files.

Arbitrary string examples are ignored.

If your project has a different safe version-literal shape, provide your own pattern:

Semverve::Task.newdo |config|
config.version_code_reference_files.append("lib/**/*.rb")config.version_code_reference_pattern=/release ["'](?<version>\d+\.\d+\.\d+)["']/end

With that pattern, this line:

release"1.2.2"

matches the full release "1.2.2" text, but only 1.2.2 is captured as version. If rake semverve:fix:code is updating references to 1.2.3, the line becomes:

release"1.2.3"

The custom value must be a Regexp and must include a named capture called version. Semverve replaces only that capture when running rake semverve:fix:code, and the captured value still has to parse as a semantic version.

Package metadata

Package metadata checks are part of rake semverve:check by default for gem/package projects. They compare the current version file against:

  • the resolved .gemspec version
  • the matching Gemfile.lock entry, when a lockfile exists

Package metadata always requires an exact match, regardless of config.version_match_mode.

No file-list configuration is needed for these checks. Semverve resolves the gemspec from the project root and reads Gemfile.lock when one exists.

Dynamic gemspec versions work as expected:

require_relative"lib/my_gem/version"Gem::Specification.newdo |spec|
spec.name="my_gem"spec.version=MyGem::VERSIONend

Literal gemspec versions can be fixed automatically:

Gem::Specification.newdo |spec|
spec.name="my_gem"spec.version="1.2.2"end

rake semverve:fix:package_metadata updates safe literal gemspec assignments and runs bundle lock when the lockfile has drifted.

Rails config metadata

Rails config metadata checks are part of rake semverve:check when the Rails adapter is active. They scan config/application.rb, config/environments/*.rb, and config/initializers/**/*.rb for optional Rails config literals:

config.x.version="1.2.2"Rails.application.config.x.version="1.2.2"

These checks always require an exact match with the current Semverve version. rake semverve:fix:rails_config_metadata rewrites only those safe string literals. Dynamic assignments, including config.x.version = Storefront::VERSION, are considered self-managed and ignored.

Checking release readiness

Release checks are separate from rake semverve:check. They are useful in CI, but they are meant for release workflows, tag builds, or pre-publish jobs rather than every pull request.

Enable the RubyGems published-version check:

Semverve::Task.newdo |config|
config.release_checks=[:rubygems]end

Then run:

rake semverve:check:release

With :rubygems enabled, semverve:check:release asks the configured RubyGems-compatible host whether the current version already exists. If it does, the task exits non-zero with a message like:

my_gem 1.2.3 already exists on https://rubygems.org.

If all configured release checks pass, it prints:

Release checks passed.

You can also run the RubyGems check directly without changing config.release_checks:

rake semverve:check:rubygems

When the current version is not published, the focused task prints:

my_gem 1.2.3 is not published on https://rubygems.org.

Use config.rubygems_host for a private RubyGems-compatible server:

Semverve::Task.newdo |config|
config.release_checks=[:rubygems]config.rubygems_host="https://gems.example.com"end

The published-version check treats a missing gem as unpublished. It fails closed on registry errors, malformed responses, and network failures because release pipelines should not silently publish after an inconclusive preflight.

For ordinary CI, keep using the local checks:

bundle exec rake test semverve:check

For release CI, run the release check before building or pushing:

bundle exec rake semverve:check:release build

Vim

While there's no official vim support (yet), you can add the following to ~/.vim/plugin/semverve.vim.

command!-bang SemverveAudit call<SID>semverve_audit(<bang>0)
function!s:semverve_audit(report_ignored) abortletl:old_efm= &errorformattrylet &errorformat='%f:%l:%c:%m'letl:string=""if!a:report_ignoredletl:string.='SEMVERVE_REPORT_IGNORED=true 'endifletl:string.='bundle exec rake semverve:check 2>/dev/null'cexprsystemlist(l:string)
ifv:shell_error!=0copenelseccloseecho'Semverve checks passed.'endiffinallylet &errorformat=l:old_efmendtryendfunction

You can then call :SemverveAudit, which will call bundle exec rake semverve:check, and :SemverveAudit! which will call the same command with SEMVERVE_REPORT_IGNORED=true, and populate and open the quickfix list if any offenses are found.

Reporting Bugs and Requesting Features

If you have an idea or find a bug, please create an issue. Just make sure the topic doesn't already exist. Better yet, you can always submit a Pull Request.

Support this project

I love knowing when people find my work useful. Any kind of support is very much appreciated!

About

🏷️ Rake tasks for incrementing gem versions and finding and updating version references in documentation and code.

Resources

Contributing

Stars

1 star

Watchers

1 watching

Forks

Releases

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Semverve

Build StatusLanguage: RubyGem VersionLicense: MIT

Rake tasks for handling the tedium surrounding maintaining a version number in your Ruby project, with gusto!

About

Maintaining a gem version is not hard, but there are so many little pieces that are easy to forget. How many times have you had changes where the code was ready, the tests were green, the PR was merged, you go to push the gem, and you realize you forgot to bump the version? Then comes the tiny follow-up PR that forces you to waste CI minutes for a two-line change, you submit it, and... oh, no! You still have references to the old version number in your documentation! Rinse and repeat until you finally remember all the things.

Semverve is meant to make that tedium boring in the best way. It provides a small set of Rake tasks for reading the current version, generating a version file, incrementing patch/minor/major versions, setting an exact version, and checking the places where version numbers tend to drift, like .gemspec files and documentation.

In a nutshell, rake semverve:increment:patch updates the "patch" level in your configured version.rb file, :minor updates the "minor" level, etc.. Calling rake semverve:check checks whether the surrounding project still agrees with that version. It can catch stale README references, safe code literals, .gemspec drift, and a stale Gemfile.lock entry. If you want Semverve to do the mechanical cleanup, the matching *:fix tasks can update safe references and run bundle lock for generated lockfile drift. Specific findings can be skipped with magic comments, similar to RuboCop and RDoc.

You can view the documentation here.

For release history and upgrading notes, see CHANGELOG.md.

Installation

Add the gem to your Gemfile:

gem"semverve"

Then add this to your Rakefile:

require"semverve/task"Semverve::Task.new

This defines:

rake semverve:current
rake semverve:increment:patch
rake semverve:increment:minor
rake semverve:increment:major
rake semverve:generate
rake 'semverve:set[1.2.3]'
rake semverve:check
rake semverve:fix
rake semverve:check:references
rake semverve:fix:references
rake semverve:check:code
rake semverve:fix:code
rake semverve:check:package_metadata
rake semverve:fix:package_metadata
rake semverve:check:rails_config_metadata
rake semverve:fix:rails_config_metadata
rake semverve:check:rubygems
rake semverve:check:release

Configuration

By default, Semverve reads the single .gemspec in the project root, uses spec.name as the gem name, and manages lib/<gem_name>/version.rb.

For a conventional gem, this may be all you need:

require"semverve/task"Semverve::Task.new

That installs the semverve:* Rake tasks. If you want to change any defaults, configure Semverve from the task block in your Rakefile:

require"semverve/task"Semverve::Task.newdo |config|
config.format=:moduleconfig.bundle_lock=trueconfig.version_file="lib/my_gem/version.rb"config.module_name="MyGem"config.version_checks=[:doc_references,:code_references,:package_metadata]config.release_checks=[:rubygems]config.rubygems_host="https://rubygems.org"config.version_code_reference_files.append("lib/**/*.rb")config.version_doc_reference_files.append("doc/**/*.md")config.version_match_mode=:non_currentend

The core defaults are equivalent to:

Semverve::Task.newdo |config|
config.adapter=nilconfig.format=:moduleconfig.bundle_lock=falseconfig.root=Dir.pwdconfig.version_checks=[:doc_references,:code_references,:package_metadata]config.release_checks=[]config.rubygems_host="https://rubygems.org"config.task_namespace=:semverveconfig.version_match_mode=:olderconfig.version_code_reference_files=Rake::FileList[]config.version_code_reference_pattern=/^\s*(?:(?:[A-Z]\w*::)*(?:[A-Z]\w*VERSION[A-Z0-9_]*|VERSION)|(?:[a-z_]\w*|self)\.version)\s*=\s*(?<quote>["'])(?<version>\d+\.\d+\.\d+)\k<quote>/config.version_doc_reference_files=Rake::FileList["README*","**/README*"].exclude(".git/**/*","coverage/**/*","tmp/**/*","vendor/**/*")end

The empty version_code_reference_files default only applies to arbitrary code literal scanning. rake semverve:check still checks the resolved .gemspec version and matching Gemfile.lock entry through its default package metadata check. Release checks are empty by default because they may make network requests and are intended for release pipelines rather than every local or pull-request run.

Set config.task_namespace in the task block to use a shorter or project-specific namespace:

require"semverve/task"Semverve::Task.newdo |config|
config.task_namespace=:versionend

That installs tasks such as version:current, version:increment:patch, and version:check instead of semverve:*.

Semverve tasks use Rake task arguments for values:

rake 'semverve:set[1.2.3]'
rake 'semverve:generate[1.0.0,simple]'
rake 'semverve:generate[simple]'
rake 'semverve:generate[force]'

Quote task invocations that include square brackets. Shells such as zsh may otherwise treat brackets as glob patterns before Rake sees them. Flag syntax such as rake semverve:set --version 1.2.3 is not used because --version is already a Rake option; Semverve stays within Rake's native argument syntax instead.

The gem name, module name, and version-file path are inferred by default:

config.gem_name# spec.name from the single .gemspecconfig.module_name# camelized gem name, such as "MyGem"config.version_file# lib/<gem_name>/version.rb

Override them when your project does something unusual:

Semverve::Task.newdo |config|
config.gem_name="my-gem"config.module_name="MyGem"config.version_file="lib/my_gem/version.rb"end

Framework adapters

Framework adapters provide app-oriented defaults without requiring a gemspec or package identity. config.adapter is the preferred API; config.preset remains supported as a backward-compatible alias.

Rails apps

Rails applications do not need gem-style version files, but an application version can still be useful for release notes, support/debug screens, deployment metadata, or API output.

When Rails is loaded, Semverve's Railtie installs the same semverve:* Rake tasks for bin/rails/rails automatically. To use Rails-style defaults, set the Rails adapter:

Semverve::Task.newdo |config|
config.adapter=:railsend

config.preset = :rails is still accepted for existing setups.

The Rails adapter uses Rails.root, stores the version in config/version.rb, uses the :simple format, and infers the module name from your Rails application module when possible. Its default checks are app-oriented: documentation references, configured code literals, and optional Rails config metadata. It does not run package metadata checks unless you opt in.

Generate the file with:

bin/rails semverve:generate

If your app keeps the version somewhere else, override the path:

Semverve::Task.newdo |config|
config.adapter=:railsconfig.version_file="config/releases/version.rb"end

Rails support is only an adapter and a Railtie; Semverve does not require a dummy app, a Rails plugin layout, or a Rails dependency.

Rails config metadata is optional. When present, Semverve checks safe literals in config/application.rb, config/environments/*.rb, and config/initializers/**/*.rb:

config.x.version="1.2.2"Rails.application.config.x.version="1.2.2"

Dynamic assignments are treated as self-managed and left alone:

config.x.version=Storefront::VERSION

Rails engines or apps that publish gems can opt into package metadata checks by setting config.gem_name and including :package_metadata in config.version_checks. Deployment and container metadata, such as Docker, Kamal, and Helm, are intentionally left for future adapter support.

Sinatra apps

Sinatra applications can use the Sinatra adapter for app-style defaults:

Semverve::Task.newdo |config|
config.adapter=:sinatraend

The Sinatra adapter stores the version in config/version.rb, uses the :simple format, infers the module name from the project directory, and checks documentation references plus configured code literals by default. It does not infer a package name from config/version.rb and does not run package metadata checks unless you opt in.

Formats

The default :module format stores MAJOR, MINOR, and PATCH constants under a Version module and exposes a top-level VERSION constant.

moduleMyGemmoduleVersionMAJOR=0MINOR=1PATCH=0module_functiondefto_a[MAJOR,MINOR,PATCH]enddefto_sto_a.join(".")endendVERSION=Version.to_send

The :simple format stores only:

moduleMyGemVERSION="1.0.0"end

Generating

Generate the default module format:

rake semverve:generate

Generate a specific version or format:

rake 'semverve:generate[1.0.0,simple]'
rake 'semverve:generate[simple]'

Generation fails if the target file already exists. To replace it:

rake 'semverve:generate[force]'

semverve:generate accepts optional tokens for version, format, and force. Token order does not matter: semantic versions set the generated version, module or simple sets the format, and force overwrites an existing version file.

rake 'semverve:generate[1.0.0,force]'
rake 'semverve:generate[simple,force]'
rake 'semverve:generate[1.0.0,simple,force]'

Bootstrapping a new gem

If your gemspec dynamically requires the generated version file, a brand-new project can fail before semverve:generate has a chance to run:

require_relative"lib/my_gem/version"

Rake loads the Rakefile before it can invoke any task. If loading the Rakefile also loads bundler/gem_tasks, Bundler evaluates the gemspec, and an unconditional require_relative can crash because lib/my_gem/version.rb does not exist yet.

Prefer guarding Bundler's gem tasks while running semverve:generate:

require"semverve/task"unlessARGV.any?{ |arg| arg.start_with?("semverve:generate")}require"bundler/gem_tasks"endSemverve::Task.new

Then generate the version file:

rake semverve:generate

After generation, you can keep the guard or switch back to your normal dynamic gemspec loading style.

Semverve's metadata inference does not evaluate the gemspec for generation; it reads the literal spec.name, so either bootstrap pattern still allows semverve:generate to infer lib/my_gem/version.rb.

Alternatively, make the gemspec tolerate the missing file during bootstrap:

version_path=File.expand_path("lib/my_gem/version",__dir__)requireversion_pathifFile.file?("#{version_path}.rb")Gem::Specification.newdo |spec|
spec.name="my_gem"spec.version=defined?(MyGem::VERSION) ? MyGem::VERSION : "0.0.0"semverveend

Incrementing

rake semverve:increment:patch
rake semverve:increment:minor
rake semverve:increment:major

Patch increments only patch. Minor increments minor and resets patch to 0. Major increments major and resets minor and patch to 0.

Successful increments print the version change:

Updating to version 2.0.2 (was 2.0.1)

Set config.bundle_lock = true to run bundle lock after increments to update your gem's version in Gemfile.lock.

Setting

Set an exact version without incrementing:

rake 'semverve:set[1.2.3]'

Successful updates print the version change:

Updating to version 1.2.3 (was 1.2.2)

Setting the current version again does not rewrite the version file:

Version is already 1.2.3

Setting a lower version prints a warning but still updates the file:

Warning: updating to version 1.9.9, which is lower than the current version 2.0.1.
Updating to version 1.9.9 (was 2.0.1)

This will also run bundle lock on success if you have config.bundle_lock = true in your config.

Checking version references, code, and metadata

Run every version check with:

rake semverve:check

This task is designed for normal CI. It uses local project files, prints parseable findings, and exits non-zero when it finds drift.

By default, gem/package projects check:

  • README version references, plus any configured docs or comment files
  • configured code files for safe version literals
  • the gemspec version and Gemfile.lock entry

Rails adapter projects instead check README references, configured code literals, and optional Rails config metadata.

Findings are printed in parseable formats and the task exits non-zero:

README.md:12:24: version reference 1.2.2 -> 1.2.3
lib/my_gem/constants.rb:1:16: code version literal 1.2.2 -> 1.2.3
my_gem.gemspec:3:18: gemspec version 1.2.2 -> 1.2.3
Gemfile.lock:4:13: locked version 1.2.2 -> 1.2.3

Run every available fix:

rake semverve:fix

Choose which surfaces the umbrella check and fix tasks run with config.version_checks:

Semverve::Task.newdo |config|
config.version_checks=[:doc_references,:package_metadata]end

The allowed values come from Semverve's check registry. Built-in checks are :doc_references, :code_references, and :package_metadata; framework adapters can add their own checks, such as Rails' :rails_config_metadata.

Use focused tasks when you want only one surface:

rake semverve:check:references
rake semverve:fix:references
rake semverve:check:code
rake semverve:fix:code
rake semverve:check:package_metadata
rake semverve:fix:package_metadata
rake semverve:check:rails_config_metadata
rake semverve:fix:rails_config_metadata

semverve:fix:package_metadata rewrites literal gemspec versions when safe and runs bundle lock for Gemfile.lock drift. semverve:fix:rails_config_metadata rewrites safe Rails config version literals.

Pass a semantic version when you want to check or fix only that exact version in doc references and code literals:

rake 'semverve:check[1.2.2]'
rake 'semverve:fix:references[1.2.2]'

Package metadata and adapter-owned metadata checks still compare metadata to the current version. If you target the current version, check tasks list reference/code matches but fix tasks are no-ops because the text is already current.

Extension API

Semverve exposes small public objects for framework adapters and version checks. These APIs are intentionally local registration APIs; Semverve does not yet autoload third-party adapter gems.

Register a framework adapter with Semverve::Adapters.register. An adapter must expose name, defaults(configuration), and checks. It can also expose infer_package_name? to control whether app-style version files should be treated as package names.

Register a check with Semverve::VersionChecks.register, or return adapter-owned checks from an adapter's checks method. A check object should expose:

  • name and task_name
  • check_description, fix_description, finding_label, and fix_label
  • clean_message, targetable?, and exact_target_fix_noop_notice?
  • findings(configuration, current_version, include_ignored:, target_version:)
  • fix(configuration, current_version, target_version:)

Checks should return Semverve::Finding objects from findings and a Semverve::FixResult from fix. Semverve::VersionMatchPolicy and Semverve::VersionLiteralRewriter are available for checks that need Semverve's standard stale-version matching or named-capture literal rewriting.

For example:

classMyConfigVersionCheck < Semverve::VersionChecks::Checkdefname=:my_config_metadatadeftask_name=:my_config_metadatadefcheck_description="Check app config metadata for version mismatches"deffix_description="Fix safe app config metadata version mismatches"deffinding_label="app config version"defclean_message="App config metadata is current."deffindings(configuration,current_version,include_ignored: false,target_version: nil)[]enddeffix(configuration,current_version,target_version: nil)Semverve::FixResult.new(changed_files: [],replacement_count: 0)endend

Version references

Version references are prose-like references to versions. These are usually in README files, docs, guides, changelogs, or comments. By default, Semverve scans README files throughout the repo:

Semverve::Task.newdo |config|
config.version_doc_reference_files=Rake::FileList["README*","**/README*"].exclude(".git/**/*","coverage/**/*","tmp/**/*","vendor/**/*")end

Add docs or Ruby comments without replacing the README defaults:

Semverve::Task.newdo |config|
config.version_doc_reference_files.append("doc/**/*.md","lib/**/*.rb")end

Replace the defaults entirely:

Semverve::Task.newdo |config|
config.version_doc_reference_files=Rake::FileList["guides/**/*.md"]end

Ruby files are scanned only in comments. Text files with .md, .markdown, .txt, .rdoc, and .adoc extensions are scanned as full text.

The default version match mode is :older, which flags only semantic versions lower than the current version in doc references and code literals:

Semverve::Task.newdo |config|
config.version_match_mode=:olderend

Use :non_current when every doc reference and code literal should match the current version:

Semverve::Task.newdo |config|
config.version_match_mode=:non_currentend

Ignore an intentional reference with semverve:ignore-version-reference on the same line or the preceding nonblank line.

This migration note intentionally mentions 1.0.0. <!-- semverve:ignore-version-reference -->

Prefer ignore markers when possible because they move with the ignored text. Configured ignores are for edge cases where a marker would render as unwanted content, such as inside a Markdown code block. They depend on line numbers, so they can drift when the file changes:

Semverve::Task.newdo |config|
config.version_reference_ignores={"README.md"=>{42=>["1.0.0"],45=>"2.0.0"}}end

Configured ignores apply to documentation references and code literals. They are exact to the version string, so other stale versions on the same line are still reported. Fix tasks leave configured ignores unchanged.

Audit ignored references by setting SEMVERVE_REPORT_IGNORED=true when running check tasks:

SEMVERVE_REPORT_IGNORED=true rake semverve:check
SEMVERVE_REPORT_IGNORED=true rake 'semverve:check[1.2.2]'

Code version literals

Code scanning is opt-in to avoid false positives. This is for arbitrary project code, not package metadata. The default is:

Semverve::Task.newdo |config|
config.version_code_reference_files=Rake::FileList[]end

Append files when you want Semverve to check safe code literals:

Semverve::Task.newdo |config|
config.version_code_reference_files.append("lib/**/*.rb")end

Or replace the list entirely:

Semverve::Task.newdo |config|
config.version_code_reference_files=Rake::FileList["lib/**/*.rb","*.gemspec"]end

Ruby code checks only obvious version assignments/constants, such as:

APP_VERSION="1.2.2"spec.version="1.2.2"

Ignore an intentional code literal with semverve:ignore-version-reference on the same line or the preceding nonblank line.

Set SEMVERVE_REPORT_IGNORED=true with semverve:check or semverve:check:code to report ignored stale literals without changing semverve:fix behavior.

The default pattern is Ruby-oriented. Semverve does not inspect file extensions or parse other languages for code literals; non-Ruby files are scanned as plain text with the same pattern. If a JavaScript, Python, or other source file uses a different version-literal shape, configure a custom pattern before adding those files.

Arbitrary string examples are ignored.

If your project has a different safe version-literal shape, provide your own pattern:

Semverve::Task.newdo |config|
config.version_code_reference_files.append("lib/**/*.rb")config.version_code_reference_pattern=/release ["'](?<version>\d+\.\d+\.\d+)["']/end

With that pattern, this line:

release"1.2.2"

matches the full release "1.2.2" text, but only 1.2.2 is captured as version. If rake semverve:fix:code is updating references to 1.2.3, the line becomes:

release"1.2.3"

The custom value must be a Regexp and must include a named capture called version. Semverve replaces only that capture when running rake semverve:fix:code, and the captured value still has to parse as a semantic version.

Package metadata

Package metadata checks are part of rake semverve:check by default for gem/package projects. They compare the current version file against:

  • the resolved .gemspec version
  • the matching Gemfile.lock entry, when a lockfile exists

Package metadata always requires an exact match, regardless of config.version_match_mode.

No file-list configuration is needed for these checks. Semverve resolves the gemspec from the project root and reads Gemfile.lock when one exists.

Dynamic gemspec versions work as expected:

require_relative"lib/my_gem/version"Gem::Specification.newdo |spec|
spec.name="my_gem"spec.version=MyGem::VERSIONend

Literal gemspec versions can be fixed automatically:

Gem::Specification.newdo |spec|
spec.name="my_gem"spec.version="1.2.2"end

rake semverve:fix:package_metadata updates safe literal gemspec assignments and runs bundle lock when the lockfile has drifted.

Rails config metadata

Rails config metadata checks are part of rake semverve:check when the Rails adapter is active. They scan config/application.rb, config/environments/*.rb, and config/initializers/**/*.rb for optional Rails config literals:

config.x.version="1.2.2"Rails.application.config.x.version="1.2.2"

These checks always require an exact match with the current Semverve version. rake semverve:fix:rails_config_metadata rewrites only those safe string literals. Dynamic assignments, including config.x.version = Storefront::VERSION, are considered self-managed and ignored.

Checking release readiness

Release checks are separate from rake semverve:check. They are useful in CI, but they are meant for release workflows, tag builds, or pre-publish jobs rather than every pull request.

Enable the RubyGems published-version check:

Semverve::Task.newdo |config|
config.release_checks=[:rubygems]end

Then run:

rake semverve:check:release

With :rubygems enabled, semverve:check:release asks the configured RubyGems-compatible host whether the current version already exists. If it does, the task exits non-zero with a message like:

my_gem 1.2.3 already exists on https://rubygems.org.

If all configured release checks pass, it prints:

Release checks passed.

You can also run the RubyGems check directly without changing config.release_checks:

rake semverve:check:rubygems

When the current version is not published, the focused task prints:

my_gem 1.2.3 is not published on https://rubygems.org.

Use config.rubygems_host for a private RubyGems-compatible server:

Semverve::Task.newdo |config|
config.release_checks=[:rubygems]config.rubygems_host="https://gems.example.com"end

The published-version check treats a missing gem as unpublished. It fails closed on registry errors, malformed responses, and network failures because release pipelines should not silently publish after an inconclusive preflight.

For ordinary CI, keep using the local checks:

bundle exec rake test semverve:check

For release CI, run the release check before building or pushing:

bundle exec rake semverve:check:release build

Vim

While there's no official vim support (yet), you can add the following to ~/.vim/plugin/semverve.vim.

command!-bang SemverveAudit call<SID>semverve_audit(<bang>0)
function!s:semverve_audit(report_ignored) abortletl:old_efm= &errorformattrylet &errorformat='%f:%l:%c:%m'letl:string=""if!a:report_ignoredletl:string.='SEMVERVE_REPORT_IGNORED=true 'endifletl:string.='bundle exec rake semverve:check 2>/dev/null'cexprsystemlist(l:string)
ifv:shell_error!=0copenelseccloseecho'Semverve checks passed.'endiffinallylet &errorformat=l:old_efmendtryendfunction

You can then call :SemverveAudit, which will call bundle exec rake semverve:check, and :SemverveAudit! which will call the same command with SEMVERVE_REPORT_IGNORED=true, and populate and open the quickfix list if any offenses are found.

Reporting Bugs and Requesting Features

If you have an idea or find a bug, please create an issue. Just make sure the topic doesn't already exist. Better yet, you can always submit a Pull Request.

Support this project

I love knowing when people find my work useful. Any kind of support is very much appreciated!

About

🏷️ Rake tasks for incrementing gem versions and finding and updating version references in documentation and code.

Resources

Contributing

Stars

1 star

Watchers

1 watching

Forks

Releases

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Repository files navigation

Semverve

Build StatusLanguage: RubyGem VersionLicense: MIT

Rake tasks for handling the tedium surrounding maintaining a version number in your Ruby project, with gusto!

About

Maintaining a gem version is not hard, but there are so many little pieces that are easy to forget. How many times have you had changes where the code was ready, the tests were green, the PR was merged, you go to push the gem, and you realize you forgot to bump the version? Then comes the tiny follow-up PR that forces you to waste CI minutes for a two-line change, you submit it, and... oh, no! You still have references to the old version number in your documentation! Rinse and repeat until you finally remember all the things.

Semverve is meant to make that tedium boring in the best way. It provides a small set of Rake tasks for reading the current version, generating a version file, incrementing patch/minor/major versions, setting an exact version, and checking the places where version numbers tend to drift, like .gemspec files and documentation.

In a nutshell, rake semverve:increment:patch updates the "patch" level in your configured version.rb file, :minor updates the "minor" level, etc.. Calling rake semverve:check checks whether the surrounding project still agrees with that version. It can catch stale README references, safe code literals, .gemspec drift, and a stale Gemfile.lock entry. If you want Semverve to do the mechanical cleanup, the matching *:fix tasks can update safe references and run bundle lock for generated lockfile drift. Specific findings can be skipped with magic comments, similar to RuboCop and RDoc.

You can view the documentation here.

For release history and upgrading notes, see CHANGELOG.md.

Installation

Add the gem to your Gemfile:

gem"semverve"

Then add this to your Rakefile:

require"semverve/task"Semverve::Task.new

This defines:

rake semverve:current
rake semverve:increment:patch
rake semverve:increment:minor
rake semverve:increment:major
rake semverve:generate
rake 'semverve:set[1.2.3]'
rake semverve:check
rake semverve:fix
rake semverve:check:references
rake semverve:fix:references
rake semverve:check:code
rake semverve:fix:code
rake semverve:check:package_metadata
rake semverve:fix:package_metadata
rake semverve:check:rails_config_metadata
rake semverve:fix:rails_config_metadata
rake semverve:check:rubygems
rake semverve:check:release

Configuration

By default, Semverve reads the single .gemspec in the project root, uses spec.name as the gem name, and manages lib/<gem_name>/version.rb.

For a conventional gem, this may be all you need:

require"semverve/task"Semverve::Task.new

That installs the semverve:* Rake tasks. If you want to change any defaults, configure Semverve from the task block in your Rakefile:

require"semverve/task"Semverve::Task.newdo |config|
config.format=:moduleconfig.bundle_lock=trueconfig.version_file="lib/my_gem/version.rb"config.module_name="MyGem"config.version_checks=[:doc_references,:code_references,:package_metadata]config.release_checks=[:rubygems]config.rubygems_host="https://rubygems.org"config.version_code_reference_files.append("lib/**/*.rb")config.version_doc_reference_files.append("doc/**/*.md")config.version_match_mode=:non_currentend

The core defaults are equivalent to:

Semverve::Task.newdo |config|
config.adapter=nilconfig.format=:moduleconfig.bundle_lock=falseconfig.root=Dir.pwdconfig.version_checks=[:doc_references,:code_references,:package_metadata]config.release_checks=[]config.rubygems_host="https://rubygems.org"config.task_namespace=:semverveconfig.version_match_mode=:olderconfig.version_code_reference_files=Rake::FileList[]config.version_code_reference_pattern=/^\s*(?:(?:[A-Z]\w*::)*(?:[A-Z]\w*VERSION[A-Z0-9_]*|VERSION)|(?:[a-z_]\w*|self)\.version)\s*=\s*(?<quote>["'])(?<version>\d+\.\d+\.\d+)\k<quote>/config.version_doc_reference_files=Rake::FileList["README*","**/README*"].exclude(".git/**/*","coverage/**/*","tmp/**/*","vendor/**/*")end

The empty version_code_reference_files default only applies to arbitrary code literal scanning. rake semverve:check still checks the resolved .gemspec version and matching Gemfile.lock entry through its default package metadata check. Release checks are empty by default because they may make network requests and are intended for release pipelines rather than every local or pull-request run.

Set config.task_namespace in the task block to use a shorter or project-specific namespace:

require"semverve/task"Semverve::Task.newdo |config|
config.task_namespace=:versionend

That installs tasks such as version:current, version:increment:patch, and version:check instead of semverve:*.

Semverve tasks use Rake task arguments for values:

rake 'semverve:set[1.2.3]'
rake 'semverve:generate[1.0.0,simple]'
rake 'semverve:generate[simple]'
rake 'semverve:generate[force]'

Quote task invocations that include square brackets. Shells such as zsh may otherwise treat brackets as glob patterns before Rake sees them. Flag syntax such as rake semverve:set --version 1.2.3 is not used because --version is already a Rake option; Semverve stays within Rake's native argument syntax instead.

The gem name, module name, and version-file path are inferred by default:

config.gem_name# spec.name from the single .gemspecconfig.module_name# camelized gem name, such as "MyGem"config.version_file# lib/<gem_name>/version.rb

Override them when your project does something unusual:

Semverve::Task.newdo |config|
config.gem_name="my-gem"config.module_name="MyGem"config.version_file="lib/my_gem/version.rb"end

Framework adapters

Framework adapters provide app-oriented defaults without requiring a gemspec or package identity. config.adapter is the preferred API; config.preset remains supported as a backward-compatible alias.

Rails apps

Rails applications do not need gem-style version files, but an application version can still be useful for release notes, support/debug screens, deployment metadata, or API output.

When Rails is loaded, Semverve's Railtie installs the same semverve:* Rake tasks for bin/rails/rails automatically. To use Rails-style defaults, set the Rails adapter:

Semverve::Task.newdo |config|
config.adapter=:railsend

config.preset = :rails is still accepted for existing setups.

The Rails adapter uses Rails.root, stores the version in config/version.rb, uses the :simple format, and infers the module name from your Rails application module when possible. Its default checks are app-oriented: documentation references, configured code literals, and optional Rails config metadata. It does not run package metadata checks unless you opt in.

Generate the file with:

bin/rails semverve:generate

If your app keeps the version somewhere else, override the path:

Semverve::Task.newdo |config|
config.adapter=:railsconfig.version_file="config/releases/version.rb"end

Rails support is only an adapter and a Railtie; Semverve does not require a dummy app, a Rails plugin layout, or a Rails dependency.

Rails config metadata is optional. When present, Semverve checks safe literals in config/application.rb, config/environments/*.rb, and config/initializers/**/*.rb:

config.x.version="1.2.2"Rails.application.config.x.version="1.2.2"

Dynamic assignments are treated as self-managed and left alone:

config.x.version=Storefront::VERSION

Rails engines or apps that publish gems can opt into package metadata checks by setting config.gem_name and including :package_metadata in config.version_checks. Deployment and container metadata, such as Docker, Kamal, and Helm, are intentionally left for future adapter support.

Sinatra apps

Sinatra applications can use the Sinatra adapter for app-style defaults:

Semverve::Task.newdo |config|
config.adapter=:sinatraend

The Sinatra adapter stores the version in config/version.rb, uses the :simple format, infers the module name from the project directory, and checks documentation references plus configured code literals by default. It does not infer a package name from config/version.rb and does not run package metadata checks unless you opt in.

Formats

The default :module format stores MAJOR, MINOR, and PATCH constants under a Version module and exposes a top-level VERSION constant.

moduleMyGemmoduleVersionMAJOR=0MINOR=1PATCH=0module_functiondefto_a[MAJOR,MINOR,PATCH]enddefto_sto_a.join(".")endendVERSION=Version.to_send

The :simple format stores only:

moduleMyGemVERSION="1.0.0"end

Generating

Generate the default module format:

rake semverve:generate

Generate a specific version or format:

rake 'semverve:generate[1.0.0,simple]'
rake 'semverve:generate[simple]'

Generation fails if the target file already exists. To replace it:

rake 'semverve:generate[force]'

semverve:generate accepts optional tokens for version, format, and force. Token order does not matter: semantic versions set the generated version, module or simple sets the format, and force overwrites an existing version file.

rake 'semverve:generate[1.0.0,force]'
rake 'semverve:generate[simple,force]'
rake 'semverve:generate[1.0.0,simple,force]'

Bootstrapping a new gem

If your gemspec dynamically requires the generated version file, a brand-new project can fail before semverve:generate has a chance to run:

require_relative"lib/my_gem/version"

Rake loads the Rakefile before it can invoke any task. If loading the Rakefile also loads bundler/gem_tasks, Bundler evaluates the gemspec, and an unconditional require_relative can crash because lib/my_gem/version.rb does not exist yet.

Prefer guarding Bundler's gem tasks while running semverve:generate:

require"semverve/task"unlessARGV.any?{ |arg| arg.start_with?("semverve:generate")}require"bundler/gem_tasks"endSemverve::Task.new

Then generate the version file:

rake semverve:generate

After generation, you can keep the guard or switch back to your normal dynamic gemspec loading style.

Semverve's metadata inference does not evaluate the gemspec for generation; it reads the literal spec.name, so either bootstrap pattern still allows semverve:generate to infer lib/my_gem/version.rb.

Alternatively, make the gemspec tolerate the missing file during bootstrap:

version_path=File.expand_path("lib/my_gem/version",__dir__)requireversion_pathifFile.file?("#{version_path}.rb")Gem::Specification.newdo |spec|
spec.name="my_gem"spec.version=defined?(MyGem::VERSION) ? MyGem::VERSION : "0.0.0"semverveend

Incrementing

rake semverve:increment:patch
rake semverve:increment:minor
rake semverve:increment:major

Patch increments only patch. Minor increments minor and resets patch to 0. Major increments major and resets minor and patch to 0.

Successful increments print the version change:

Updating to version 2.0.2 (was 2.0.1)

Set config.bundle_lock = true to run bundle lock after increments to update your gem's version in Gemfile.lock.

Setting

Set an exact version without incrementing:

rake 'semverve:set[1.2.3]'

Successful updates print the version change:

Updating to version 1.2.3 (was 1.2.2)

Setting the current version again does not rewrite the version file:

Version is already 1.2.3

Setting a lower version prints a warning but still updates the file:

Warning: updating to version 1.9.9, which is lower than the current version 2.0.1.
Updating to version 1.9.9 (was 2.0.1)

This will also run bundle lock on success if you have config.bundle_lock = true in your config.

Checking version references, code, and metadata

Run every version check with:

rake semverve:check

This task is designed for normal CI. It uses local project files, prints parseable findings, and exits non-zero when it finds drift.

By default, gem/package projects check:

  • README version references, plus any configured docs or comment files
  • configured code files for safe version literals
  • the gemspec version and Gemfile.lock entry

Rails adapter projects instead check README references, configured code literals, and optional Rails config metadata.

Findings are printed in parseable formats and the task exits non-zero:

README.md:12:24: version reference 1.2.2 -> 1.2.3
lib/my_gem/constants.rb:1:16: code version literal 1.2.2 -> 1.2.3
my_gem.gemspec:3:18: gemspec version 1.2.2 -> 1.2.3
Gemfile.lock:4:13: locked version 1.2.2 -> 1.2.3

Run every available fix:

rake semverve:fix

Choose which surfaces the umbrella check and fix tasks run with config.version_checks:

Semverve::Task.newdo |config|
config.version_checks=[:doc_references,:package_metadata]end

The allowed values come from Semverve's check registry. Built-in checks are :doc_references, :code_references, and :package_metadata; framework adapters can add their own checks, such as Rails' :rails_config_metadata.

Use focused tasks when you want only one surface:

rake semverve:check:references
rake semverve:fix:references
rake semverve:check:code
rake semverve:fix:code
rake semverve:check:package_metadata
rake semverve:fix:package_metadata
rake semverve:check:rails_config_metadata
rake semverve:fix:rails_config_metadata

semverve:fix:package_metadata rewrites literal gemspec versions when safe and runs bundle lock for Gemfile.lock drift. semverve:fix:rails_config_metadata rewrites safe Rails config version literals.

Pass a semantic version when you want to check or fix only that exact version in doc references and code literals:

rake 'semverve:check[1.2.2]'
rake 'semverve:fix:references[1.2.2]'

Package metadata and adapter-owned metadata checks still compare metadata to the current version. If you target the current version, check tasks list reference/code matches but fix tasks are no-ops because the text is already current.

Extension API

Semverve exposes small public objects for framework adapters and version checks. These APIs are intentionally local registration APIs; Semverve does not yet autoload third-party adapter gems.

Register a framework adapter with Semverve::Adapters.register. An adapter must expose name, defaults(configuration), and checks. It can also expose infer_package_name? to control whether app-style version files should be treated as package names.

Register a check with Semverve::VersionChecks.register, or return adapter-owned checks from an adapter's checks method. A check object should expose:

  • name and task_name
  • check_description, fix_description, finding_label, and fix_label
  • clean_message, targetable?, and exact_target_fix_noop_notice?
  • findings(configuration, current_version, include_ignored:, target_version:)
  • fix(configuration, current_version, target_version:)

Checks should return Semverve::Finding objects from findings and a Semverve::FixResult from fix. Semverve::VersionMatchPolicy and Semverve::VersionLiteralRewriter are available for checks that need Semverve's standard stale-version matching or named-capture literal rewriting.

For example:

classMyConfigVersionCheck < Semverve::VersionChecks::Checkdefname=:my_config_metadatadeftask_name=:my_config_metadatadefcheck_description="Check app config metadata for version mismatches"deffix_description="Fix safe app config metadata version mismatches"deffinding_label="app config version"defclean_message="App config metadata is current."deffindings(configuration,current_version,include_ignored: false,target_version: nil)[]enddeffix(configuration,current_version,target_version: nil)Semverve::FixResult.new(changed_files: [],replacement_count: 0)endend

Version references

Version references are prose-like references to versions. These are usually in README files, docs, guides, changelogs, or comments. By default, Semverve scans README files throughout the repo:

Semverve::Task.newdo |config|
config.version_doc_reference_files=Rake::FileList["README*","**/README*"].exclude(".git/**/*","coverage/**/*","tmp/**/*","vendor/**/*")end

Add docs or Ruby comments without replacing the README defaults:

Semverve::Task.newdo |config|
config.version_doc_reference_files.append("doc/**/*.md","lib/**/*.rb")end

Replace the defaults entirely:

Semverve::Task.newdo |config|
config.version_doc_reference_files=Rake::FileList["guides/**/*.md"]end

Ruby files are scanned only in comments. Text files with .md, .markdown, .txt, .rdoc, and .adoc extensions are scanned as full text.

The default version match mode is :older, which flags only semantic versions lower than the current version in doc references and code literals:

Semverve::Task.newdo |config|
config.version_match_mode=:olderend

Use :non_current when every doc reference and code literal should match the current version:

Semverve::Task.newdo |config|
config.version_match_mode=:non_currentend

Ignore an intentional reference with semverve:ignore-version-reference on the same line or the preceding nonblank line.

This migration note intentionally mentions 1.0.0. <!-- semverve:ignore-version-reference -->

Prefer ignore markers when possible because they move with the ignored text. Configured ignores are for edge cases where a marker would render as unwanted content, such as inside a Markdown code block. They depend on line numbers, so they can drift when the file changes:

Semverve::Task.newdo |config|
config.version_reference_ignores={"README.md"=>{42=>["1.0.0"],45=>"2.0.0"}}end

Configured ignores apply to documentation references and code literals. They are exact to the version string, so other stale versions on the same line are still reported. Fix tasks leave configured ignores unchanged.

Audit ignored references by setting SEMVERVE_REPORT_IGNORED=true when running check tasks:

SEMVERVE_REPORT_IGNORED=true rake semverve:check
SEMVERVE_REPORT_IGNORED=true rake 'semverve:check[1.2.2]'

Code version literals

Code scanning is opt-in to avoid false positives. This is for arbitrary project code, not package metadata. The default is:

Semverve::Task.newdo |config|
config.version_code_reference_files=Rake::FileList[]end

Append files when you want Semverve to check safe code literals:

Semverve::Task.newdo |config|
config.version_code_reference_files.append("lib/**/*.rb")end

Or replace the list entirely:

Semverve::Task.newdo |config|
config.version_code_reference_files=Rake::FileList["lib/**/*.rb","*.gemspec"]end

Ruby code checks only obvious version assignments/constants, such as:

APP_VERSION="1.2.2"spec.version="1.2.2"

Ignore an intentional code literal with semverve:ignore-version-reference on the same line or the preceding nonblank line.

Set SEMVERVE_REPORT_IGNORED=true with semverve:check or semverve:check:code to report ignored stale literals without changing semverve:fix behavior.

The default pattern is Ruby-oriented. Semverve does not inspect file extensions or parse other languages for code literals; non-Ruby files are scanned as plain text with the same pattern. If a JavaScript, Python, or other source file uses a different version-literal shape, configure a custom pattern before adding those files.

Arbitrary string examples are ignored.

If your project has a different safe version-literal shape, provide your own pattern:

Semverve::Task.newdo |config|
config.version_code_reference_files.append("lib/**/*.rb")config.version_code_reference_pattern=/release ["'](?<version>\d+\.\d+\.\d+)["']/end

With that pattern, this line:

release"1.2.2"

matches the full release "1.2.2" text, but only 1.2.2 is captured as version. If rake semverve:fix:code is updating references to 1.2.3, the line becomes:

release"1.2.3"

The custom value must be a Regexp and must include a named capture called version. Semverve replaces only that capture when running rake semverve:fix:code, and the captured value still has to parse as a semantic version.

Package metadata

Package metadata checks are part of rake semverve:check by default for gem/package projects. They compare the current version file against:

  • the resolved .gemspec version
  • the matching Gemfile.lock entry, when a lockfile exists

Package metadata always requires an exact match, regardless of config.version_match_mode.

No file-list configuration is needed for these checks. Semverve resolves the gemspec from the project root and reads Gemfile.lock when one exists.

Dynamic gemspec versions work as expected:

require_relative"lib/my_gem/version"Gem::Specification.newdo |spec|
spec.name="my_gem"spec.version=MyGem::VERSIONend

Literal gemspec versions can be fixed automatically:

Gem::Specification.newdo |spec|
spec.name="my_gem"spec.version="1.2.2"end

rake semverve:fix:package_metadata updates safe literal gemspec assignments and runs bundle lock when the lockfile has drifted.

Rails config metadata

Rails config metadata checks are part of rake semverve:check when the Rails adapter is active. They scan config/application.rb, config/environments/*.rb, and config/initializers/**/*.rb for optional Rails config literals:

config.x.version="1.2.2"Rails.application.config.x.version="1.2.2"

These checks always require an exact match with the current Semverve version. rake semverve:fix:rails_config_metadata rewrites only those safe string literals. Dynamic assignments, including config.x.version = Storefront::VERSION, are considered self-managed and ignored.

Checking release readiness

Release checks are separate from rake semverve:check. They are useful in CI, but they are meant for release workflows, tag builds, or pre-publish jobs rather than every pull request.

Enable the RubyGems published-version check:

Semverve::Task.newdo |config|
config.release_checks=[:rubygems]end

Then run:

rake semverve:check:release

With :rubygems enabled, semverve:check:release asks the configured RubyGems-compatible host whether the current version already exists. If it does, the task exits non-zero with a message like:

my_gem 1.2.3 already exists on https://rubygems.org.

If all configured release checks pass, it prints:

Release checks passed.

You can also run the RubyGems check directly without changing config.release_checks:

rake semverve:check:rubygems

When the current version is not published, the focused task prints:

my_gem 1.2.3 is not published on https://rubygems.org.

Use config.rubygems_host for a private RubyGems-compatible server:

Semverve::Task.newdo |config|
config.release_checks=[:rubygems]config.rubygems_host="https://gems.example.com"end

The published-version check treats a missing gem as unpublished. It fails closed on registry errors, malformed responses, and network failures because release pipelines should not silently publish after an inconclusive preflight.

For ordinary CI, keep using the local checks:

bundle exec rake test semverve:check

For release CI, run the release check before building or pushing:

bundle exec rake semverve:check:release build

Vim

While there's no official vim support (yet), you can add the following to ~/.vim/plugin/semverve.vim.

command!-bang SemverveAudit call<SID>semverve_audit(<bang>0)
function!s:semverve_audit(report_ignored) abortletl:old_efm= &errorformattrylet &errorformat='%f:%l:%c:%m'letl:string=""if!a:report_ignoredletl:string.='SEMVERVE_REPORT_IGNORED=true 'endifletl:string.='bundle exec rake semverve:check 2>/dev/null'cexprsystemlist(l:string)
ifv:shell_error!=0copenelseccloseecho'Semverve checks passed.'endiffinallylet &errorformat=l:old_efmendtryendfunction

You can then call :SemverveAudit, which will call bundle exec rake semverve:check, and :SemverveAudit! which will call the same command with SEMVERVE_REPORT_IGNORED=true, and populate and open the quickfix list if any offenses are found.

Reporting Bugs and Requesting Features

If you have an idea or find a bug, please create an issue. Just make sure the topic doesn't already exist. Better yet, you can always submit a Pull Request.

Support this project

I love knowing when people find my work useful. Any kind of support is very much appreciated!

About

🏷️ Rake tasks for incrementing gem versions and finding and updating version references in documentation and code.

Resources

Contributing

Stars

1 star

Watchers

1 watching

Forks

Releases

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Semverve

Build StatusLanguage: RubyGem VersionLicense: MIT

Rake tasks for handling the tedium surrounding maintaining a version number in your Ruby project, with gusto!

About

Maintaining a gem version is not hard, but there are so many little pieces that are easy to forget. How many times have you had changes where the code was ready, the tests were green, the PR was merged, you go to push the gem, and you realize you forgot to bump the version? Then comes the tiny follow-up PR that forces you to waste CI minutes for a two-line change, you submit it, and... oh, no! You still have references to the old version number in your documentation! Rinse and repeat until you finally remember all the things.

Semverve is meant to make that tedium boring in the best way. It provides a small set of Rake tasks for reading the current version, generating a version file, incrementing patch/minor/major versions, setting an exact version, and checking the places where version numbers tend to drift, like .gemspec files and documentation.

In a nutshell, rake semverve:increment:patch updates the "patch" level in your configured version.rb file, :minor updates the "minor" level, etc.. Calling rake semverve:check checks whether the surrounding project still agrees with that version. It can catch stale README references, safe code literals, .gemspec drift, and a stale Gemfile.lock entry. If you want Semverve to do the mechanical cleanup, the matching *:fix tasks can update safe references and run bundle lock for generated lockfile drift. Specific findings can be skipped with magic comments, similar to RuboCop and RDoc.

You can view the documentation here.

For release history and upgrading notes, see CHANGELOG.md.

Installation

Add the gem to your Gemfile:

gem"semverve"

Then add this to your Rakefile:

require"semverve/task"Semverve::Task.new

This defines:

rake semverve:current
rake semverve:increment:patch
rake semverve:increment:minor
rake semverve:increment:major
rake semverve:generate
rake 'semverve:set[1.2.3]'
rake semverve:check
rake semverve:fix
rake semverve:check:references
rake semverve:fix:references
rake semverve:check:code
rake semverve:fix:code
rake semverve:check:package_metadata
rake semverve:fix:package_metadata
rake semverve:check:rails_config_metadata
rake semverve:fix:rails_config_metadata
rake semverve:check:rubygems
rake semverve:check:release

Configuration

By default, Semverve reads the single .gemspec in the project root, uses spec.name as the gem name, and manages lib/<gem_name>/version.rb.

For a conventional gem, this may be all you need:

require"semverve/task"Semverve::Task.new

That installs the semverve:* Rake tasks. If you want to change any defaults, configure Semverve from the task block in your Rakefile:

require"semverve/task"Semverve::Task.newdo |config|
config.format=:moduleconfig.bundle_lock=trueconfig.version_file="lib/my_gem/version.rb"config.module_name="MyGem"config.version_checks=[:doc_references,:code_references,:package_metadata]config.release_checks=[:rubygems]config.rubygems_host="https://rubygems.org"config.version_code_reference_files.append("lib/**/*.rb")config.version_doc_reference_files.append("doc/**/*.md")config.version_match_mode=:non_currentend

The core defaults are equivalent to:

Semverve::Task.newdo |config|
config.adapter=nilconfig.format=:moduleconfig.bundle_lock=falseconfig.root=Dir.pwdconfig.version_checks=[:doc_references,:code_references,:package_metadata]config.release_checks=[]config.rubygems_host="https://rubygems.org"config.task_namespace=:semverveconfig.version_match_mode=:olderconfig.version_code_reference_files=Rake::FileList[]config.version_code_reference_pattern=/^\s*(?:(?:[A-Z]\w*::)*(?:[A-Z]\w*VERSION[A-Z0-9_]*|VERSION)|(?:[a-z_]\w*|self)\.version)\s*=\s*(?<quote>["'])(?<version>\d+\.\d+\.\d+)\k<quote>/config.version_doc_reference_files=Rake::FileList["README*","**/README*"].exclude(".git/**/*","coverage/**/*","tmp/**/*","vendor/**/*")end

The empty version_code_reference_files default only applies to arbitrary code literal scanning. rake semverve:check still checks the resolved .gemspec version and matching Gemfile.lock entry through its default package metadata check. Release checks are empty by default because they may make network requests and are intended for release pipelines rather than every local or pull-request run.

Set config.task_namespace in the task block to use a shorter or project-specific namespace:

require"semverve/task"Semverve::Task.newdo |config|
config.task_namespace=:versionend

That installs tasks such as version:current, version:increment:patch, and version:check instead of semverve:*.

Semverve tasks use Rake task arguments for values:

rake 'semverve:set[1.2.3]'
rake 'semverve:generate[1.0.0,simple]'
rake 'semverve:generate[simple]'
rake 'semverve:generate[force]'

Quote task invocations that include square brackets. Shells such as zsh may otherwise treat brackets as glob patterns before Rake sees them. Flag syntax such as rake semverve:set --version 1.2.3 is not used because --version is already a Rake option; Semverve stays within Rake's native argument syntax instead.

The gem name, module name, and version-file path are inferred by default:

config.gem_name# spec.name from the single .gemspecconfig.module_name# camelized gem name, such as "MyGem"config.version_file# lib/<gem_name>/version.rb

Override them when your project does something unusual:

Semverve::Task.newdo |config|
config.gem_name="my-gem"config.module_name="MyGem"config.version_file="lib/my_gem/version.rb"end

Framework adapters

Framework adapters provide app-oriented defaults without requiring a gemspec or package identity. config.adapter is the preferred API; config.preset remains supported as a backward-compatible alias.

Rails apps

Rails applications do not need gem-style version files, but an application version can still be useful for release notes, support/debug screens, deployment metadata, or API output.

When Rails is loaded, Semverve's Railtie installs the same semverve:* Rake tasks for bin/rails/rails automatically. To use Rails-style defaults, set the Rails adapter:

Semverve::Task.newdo |config|
config.adapter=:railsend

config.preset = :rails is still accepted for existing setups.

The Rails adapter uses Rails.root, stores the version in config/version.rb, uses the :simple format, and infers the module name from your Rails application module when possible. Its default checks are app-oriented: documentation references, configured code literals, and optional Rails config metadata. It does not run package metadata checks unless you opt in.

Generate the file with:

bin/rails semverve:generate

If your app keeps the version somewhere else, override the path:

Semverve::Task.newdo |config|
config.adapter=:railsconfig.version_file="config/releases/version.rb"end

Rails support is only an adapter and a Railtie; Semverve does not require a dummy app, a Rails plugin layout, or a Rails dependency.

Rails config metadata is optional. When present, Semverve checks safe literals in config/application.rb, config/environments/*.rb, and config/initializers/**/*.rb:

config.x.version="1.2.2"Rails.application.config.x.version="1.2.2"

Dynamic assignments are treated as self-managed and left alone:

config.x.version=Storefront::VERSION

Rails engines or apps that publish gems can opt into package metadata checks by setting config.gem_name and including :package_metadata in config.version_checks. Deployment and container metadata, such as Docker, Kamal, and Helm, are intentionally left for future adapter support.

Sinatra apps

Sinatra applications can use the Sinatra adapter for app-style defaults:

Semverve::Task.newdo |config|
config.adapter=:sinatraend

The Sinatra adapter stores the version in config/version.rb, uses the :simple format, infers the module name from the project directory, and checks documentation references plus configured code literals by default. It does not infer a package name from config/version.rb and does not run package metadata checks unless you opt in.

Formats

The default :module format stores MAJOR, MINOR, and PATCH constants under a Version module and exposes a top-level VERSION constant.

moduleMyGemmoduleVersionMAJOR=0MINOR=1PATCH=0module_functiondefto_a[MAJOR,MINOR,PATCH]enddefto_sto_a.join(".")endendVERSION=Version.to_send

The :simple format stores only:

moduleMyGemVERSION="1.0.0"end

Generating

Generate the default module format:

rake semverve:generate

Generate a specific version or format:

rake 'semverve:generate[1.0.0,simple]'
rake 'semverve:generate[simple]'

Generation fails if the target file already exists. To replace it:

rake 'semverve:generate[force]'

semverve:generate accepts optional tokens for version, format, and force. Token order does not matter: semantic versions set the generated version, module or simple sets the format, and force overwrites an existing version file.

rake 'semverve:generate[1.0.0,force]'
rake 'semverve:generate[simple,force]'
rake 'semverve:generate[1.0.0,simple,force]'

Bootstrapping a new gem

If your gemspec dynamically requires the generated version file, a brand-new project can fail before semverve:generate has a chance to run:

require_relative"lib/my_gem/version"

Rake loads the Rakefile before it can invoke any task. If loading the Rakefile also loads bundler/gem_tasks, Bundler evaluates the gemspec, and an unconditional require_relative can crash because lib/my_gem/version.rb does not exist yet.

Prefer guarding Bundler's gem tasks while running semverve:generate:

require"semverve/task"unlessARGV.any?{ |arg| arg.start_with?("semverve:generate")}require"bundler/gem_tasks"endSemverve::Task.new

Then generate the version file:

rake semverve:generate

After generation, you can keep the guard or switch back to your normal dynamic gemspec loading style.

Semverve's metadata inference does not evaluate the gemspec for generation; it reads the literal spec.name, so either bootstrap pattern still allows semverve:generate to infer lib/my_gem/version.rb.

Alternatively, make the gemspec tolerate the missing file during bootstrap:

version_path=File.expand_path("lib/my_gem/version",__dir__)requireversion_pathifFile.file?("#{version_path}.rb")Gem::Specification.newdo |spec|
spec.name="my_gem"spec.version=defined?(MyGem::VERSION) ? MyGem::VERSION : "0.0.0"semverveend

Incrementing

rake semverve:increment:patch
rake semverve:increment:minor
rake semverve:increment:major

Patch increments only patch. Minor increments minor and resets patch to 0. Major increments major and resets minor and patch to 0.

Successful increments print the version change:

Updating to version 2.0.2 (was 2.0.1)

Set config.bundle_lock = true to run bundle lock after increments to update your gem's version in Gemfile.lock.

Setting

Set an exact version without incrementing:

rake 'semverve:set[1.2.3]'

Successful updates print the version change:

Updating to version 1.2.3 (was 1.2.2)

Setting the current version again does not rewrite the version file:

Version is already 1.2.3

Setting a lower version prints a warning but still updates the file:

Warning: updating to version 1.9.9, which is lower than the current version 2.0.1.
Updating to version 1.9.9 (was 2.0.1)

This will also run bundle lock on success if you have config.bundle_lock = true in your config.

Checking version references, code, and metadata

Run every version check with:

rake semverve:check

This task is designed for normal CI. It uses local project files, prints parseable findings, and exits non-zero when it finds drift.

By default, gem/package projects check:

  • README version references, plus any configured docs or comment files
  • configured code files for safe version literals
  • the gemspec version and Gemfile.lock entry

Rails adapter projects instead check README references, configured code literals, and optional Rails config metadata.

Findings are printed in parseable formats and the task exits non-zero:

README.md:12:24: version reference 1.2.2 -> 1.2.3
lib/my_gem/constants.rb:1:16: code version literal 1.2.2 -> 1.2.3
my_gem.gemspec:3:18: gemspec version 1.2.2 -> 1.2.3
Gemfile.lock:4:13: locked version 1.2.2 -> 1.2.3

Run every available fix:

rake semverve:fix

Choose which surfaces the umbrella check and fix tasks run with config.version_checks:

Semverve::Task.newdo |config|
config.version_checks=[:doc_references,:package_metadata]end

The allowed values come from Semverve's check registry. Built-in checks are :doc_references, :code_references, and :package_metadata; framework adapters can add their own checks, such as Rails' :rails_config_metadata.

Use focused tasks when you want only one surface:

rake semverve:check:references
rake semverve:fix:references
rake semverve:check:code
rake semverve:fix:code
rake semverve:check:package_metadata
rake semverve:fix:package_metadata
rake semverve:check:rails_config_metadata
rake semverve:fix:rails_config_metadata

semverve:fix:package_metadata rewrites literal gemspec versions when safe and runs bundle lock for Gemfile.lock drift. semverve:fix:rails_config_metadata rewrites safe Rails config version literals.

Pass a semantic version when you want to check or fix only that exact version in doc references and code literals:

rake 'semverve:check[1.2.2]'
rake 'semverve:fix:references[1.2.2]'

Package metadata and adapter-owned metadata checks still compare metadata to the current version. If you target the current version, check tasks list reference/code matches but fix tasks are no-ops because the text is already current.

Extension API

Semverve exposes small public objects for framework adapters and version checks. These APIs are intentionally local registration APIs; Semverve does not yet autoload third-party adapter gems.

Register a framework adapter with Semverve::Adapters.register. An adapter must expose name, defaults(configuration), and checks. It can also expose infer_package_name? to control whether app-style version files should be treated as package names.

Register a check with Semverve::VersionChecks.register, or return adapter-owned checks from an adapter's checks method. A check object should expose:

  • name and task_name
  • check_description, fix_description, finding_label, and fix_label
  • clean_message, targetable?, and exact_target_fix_noop_notice?
  • findings(configuration, current_version, include_ignored:, target_version:)
  • fix(configuration, current_version, target_version:)

Checks should return Semverve::Finding objects from findings and a Semverve::FixResult from fix. Semverve::VersionMatchPolicy and Semverve::VersionLiteralRewriter are available for checks that need Semverve's standard stale-version matching or named-capture literal rewriting.

For example:

classMyConfigVersionCheck < Semverve::VersionChecks::Checkdefname=:my_config_metadatadeftask_name=:my_config_metadatadefcheck_description="Check app config metadata for version mismatches"deffix_description="Fix safe app config metadata version mismatches"deffinding_label="app config version"defclean_message="App config metadata is current."deffindings(configuration,current_version,include_ignored: false,target_version: nil)[]enddeffix(configuration,current_version,target_version: nil)Semverve::FixResult.new(changed_files: [],replacement_count: 0)endend

Version references

Version references are prose-like references to versions. These are usually in README files, docs, guides, changelogs, or comments. By default, Semverve scans README files throughout the repo:

Semverve::Task.newdo |config|
config.version_doc_reference_files=Rake::FileList["README*","**/README*"].exclude(".git/**/*","coverage/**/*","tmp/**/*","vendor/**/*")end

Add docs or Ruby comments without replacing the README defaults:

Semverve::Task.newdo |config|
config.version_doc_reference_files.append("doc/**/*.md","lib/**/*.rb")end

Replace the defaults entirely:

Semverve::Task.newdo |config|
config.version_doc_reference_files=Rake::FileList["guides/**/*.md"]end

Ruby files are scanned only in comments. Text files with .md, .markdown, .txt, .rdoc, and .adoc extensions are scanned as full text.

The default version match mode is :older, which flags only semantic versions lower than the current version in doc references and code literals:

Semverve::Task.newdo |config|
config.version_match_mode=:olderend

Use :non_current when every doc reference and code literal should match the current version:

Semverve::Task.newdo |config|
config.version_match_mode=:non_currentend

Ignore an intentional reference with semverve:ignore-version-reference on the same line or the preceding nonblank line.

This migration note intentionally mentions 1.0.0. <!-- semverve:ignore-version-reference -->

Prefer ignore markers when possible because they move with the ignored text. Configured ignores are for edge cases where a marker would render as unwanted content, such as inside a Markdown code block. They depend on line numbers, so they can drift when the file changes:

Semverve::Task.newdo |config|
config.version_reference_ignores={"README.md"=>{42=>["1.0.0"],45=>"2.0.0"}}end

Configured ignores apply to documentation references and code literals. They are exact to the version string, so other stale versions on the same line are still reported. Fix tasks leave configured ignores unchanged.

Audit ignored references by setting SEMVERVE_REPORT_IGNORED=true when running check tasks:

SEMVERVE_REPORT_IGNORED=true rake semverve:check
SEMVERVE_REPORT_IGNORED=true rake 'semverve:check[1.2.2]'

Code version literals

Code scanning is opt-in to avoid false positives. This is for arbitrary project code, not package metadata. The default is:

Semverve::Task.newdo |config|
config.version_code_reference_files=Rake::FileList[]end

Append files when you want Semverve to check safe code literals:

Semverve::Task.newdo |config|
config.version_code_reference_files.append("lib/**/*.rb")end

Or replace the list entirely:

Semverve::Task.newdo |config|
config.version_code_reference_files=Rake::FileList["lib/**/*.rb","*.gemspec"]end

Ruby code checks only obvious version assignments/constants, such as:

APP_VERSION="1.2.2"spec.version="1.2.2"

Ignore an intentional code literal with semverve:ignore-version-reference on the same line or the preceding nonblank line.

Set SEMVERVE_REPORT_IGNORED=true with semverve:check or semverve:check:code to report ignored stale literals without changing semverve:fix behavior.

The default pattern is Ruby-oriented. Semverve does not inspect file extensions or parse other languages for code literals; non-Ruby files are scanned as plain text with the same pattern. If a JavaScript, Python, or other source file uses a different version-literal shape, configure a custom pattern before adding those files.

Arbitrary string examples are ignored.

If your project has a different safe version-literal shape, provide your own pattern:

Semverve::Task.newdo |config|
config.version_code_reference_files.append("lib/**/*.rb")config.version_code_reference_pattern=/release ["'](?<version>\d+\.\d+\.\d+)["']/end

With that pattern, this line:

release"1.2.2"

matches the full release "1.2.2" text, but only 1.2.2 is captured as version. If rake semverve:fix:code is updating references to 1.2.3, the line becomes:

release"1.2.3"

The custom value must be a Regexp and must include a named capture called version. Semverve replaces only that capture when running rake semverve:fix:code, and the captured value still has to parse as a semantic version.

Package metadata

Package metadata checks are part of rake semverve:check by default for gem/package projects. They compare the current version file against:

  • the resolved .gemspec version
  • the matching Gemfile.lock entry, when a lockfile exists

Package metadata always requires an exact match, regardless of config.version_match_mode.

No file-list configuration is needed for these checks. Semverve resolves the gemspec from the project root and reads Gemfile.lock when one exists.

Dynamic gemspec versions work as expected:

require_relative"lib/my_gem/version"Gem::Specification.newdo |spec|
spec.name="my_gem"spec.version=MyGem::VERSIONend

Literal gemspec versions can be fixed automatically:

Gem::Specification.newdo |spec|
spec.name="my_gem"spec.version="1.2.2"end

rake semverve:fix:package_metadata updates safe literal gemspec assignments and runs bundle lock when the lockfile has drifted.

Rails config metadata

Rails config metadata checks are part of rake semverve:check when the Rails adapter is active. They scan config/application.rb, config/environments/*.rb, and config/initializers/**/*.rb for optional Rails config literals:

config.x.version="1.2.2"Rails.application.config.x.version="1.2.2"

These checks always require an exact match with the current Semverve version. rake semverve:fix:rails_config_metadata rewrites only those safe string literals. Dynamic assignments, including config.x.version = Storefront::VERSION, are considered self-managed and ignored.

Checking release readiness

Release checks are separate from rake semverve:check. They are useful in CI, but they are meant for release workflows, tag builds, or pre-publish jobs rather than every pull request.

Enable the RubyGems published-version check:

Semverve::Task.newdo |config|
config.release_checks=[:rubygems]end

Then run:

rake semverve:check:release

With :rubygems enabled, semverve:check:release asks the configured RubyGems-compatible host whether the current version already exists. If it does, the task exits non-zero with a message like:

my_gem 1.2.3 already exists on https://rubygems.org.

If all configured release checks pass, it prints:

Release checks passed.

You can also run the RubyGems check directly without changing config.release_checks:

rake semverve:check:rubygems

When the current version is not published, the focused task prints:

my_gem 1.2.3 is not published on https://rubygems.org.

Use config.rubygems_host for a private RubyGems-compatible server:

Semverve::Task.newdo |config|
config.release_checks=[:rubygems]config.rubygems_host="https://gems.example.com"end

The published-version check treats a missing gem as unpublished. It fails closed on registry errors, malformed responses, and network failures because release pipelines should not silently publish after an inconclusive preflight.

For ordinary CI, keep using the local checks:

bundle exec rake test semverve:check

For release CI, run the release check before building or pushing:

bundle exec rake semverve:check:release build

Vim

While there's no official vim support (yet), you can add the following to ~/.vim/plugin/semverve.vim.

command!-bang SemverveAudit call<SID>semverve_audit(<bang>0)
function!s:semverve_audit(report_ignored) abortletl:old_efm= &errorformattrylet &errorformat='%f:%l:%c:%m'letl:string=""if!a:report_ignoredletl:string.='SEMVERVE_REPORT_IGNORED=true 'endifletl:string.='bundle exec rake semverve:check 2>/dev/null'cexprsystemlist(l:string)
ifv:shell_error!=0copenelseccloseecho'Semverve checks passed.'endiffinallylet &errorformat=l:old_efmendtryendfunction

You can then call :SemverveAudit, which will call bundle exec rake semverve:check, and :SemverveAudit! which will call the same command with SEMVERVE_REPORT_IGNORED=true, and populate and open the quickfix list if any offenses are found.

Reporting Bugs and Requesting Features

If you have an idea or find a bug, please create an issue. Just make sure the topic doesn't already exist. Better yet, you can always submit a Pull Request.

Support this project

I love knowing when people find my work useful. Any kind of support is very much appreciated!

About

🏷️ Rake tasks for incrementing gem versions and finding and updating version references in documentation and code.

Resources

Contributing

Stars

1 star

Watchers

1 watching

Forks

Releases

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Semverve

Build StatusLanguage: RubyGem VersionLicense: MIT

Rake tasks for handling the tedium surrounding maintaining a version number in your Ruby project, with gusto!

About

Maintaining a gem version is not hard, but there are so many little pieces that are easy to forget. How many times have you had changes where the code was ready, the tests were green, the PR was merged, you go to push the gem, and you realize you forgot to bump the version? Then comes the tiny follow-up PR that forces you to waste CI minutes for a two-line change, you submit it, and... oh, no! You still have references to the old version number in your documentation! Rinse and repeat until you finally remember all the things.

Semverve is meant to make that tedium boring in the best way. It provides a small set of Rake tasks for reading the current version, generating a version file, incrementing patch/minor/major versions, setting an exact version, and checking the places where version numbers tend to drift, like .gemspec files and documentation.

In a nutshell, rake semverve:increment:patch updates the "patch" level in your configured version.rb file, :minor updates the "minor" level, etc.. Calling rake semverve:check checks whether the surrounding project still agrees with that version. It can catch stale README references, safe code literals, .gemspec drift, and a stale Gemfile.lock entry. If you want Semverve to do the mechanical cleanup, the matching *:fix tasks can update safe references and run bundle lock for generated lockfile drift. Specific findings can be skipped with magic comments, similar to RuboCop and RDoc.

You can view the documentation here.

For release history and upgrading notes, see CHANGELOG.md.

Installation

Add the gem to your Gemfile:

gem"semverve"

Then add this to your Rakefile:

require"semverve/task"Semverve::Task.new

This defines:

rake semverve:current
rake semverve:increment:patch
rake semverve:increment:minor
rake semverve:increment:major
rake semverve:generate
rake 'semverve:set[1.2.3]'
rake semverve:check
rake semverve:fix
rake semverve:check:references
rake semverve:fix:references
rake semverve:check:code
rake semverve:fix:code
rake semverve:check:package_metadata
rake semverve:fix:package_metadata
rake semverve:check:rails_config_metadata
rake semverve:fix:rails_config_metadata
rake semverve:check:rubygems
rake semverve:check:release

Configuration

By default, Semverve reads the single .gemspec in the project root, uses spec.name as the gem name, and manages lib/<gem_name>/version.rb.

For a conventional gem, this may be all you need:

require"semverve/task"Semverve::Task.new

That installs the semverve:* Rake tasks. If you want to change any defaults, configure Semverve from the task block in your Rakefile:

require"semverve/task"Semverve::Task.newdo |config|
config.format=:moduleconfig.bundle_lock=trueconfig.version_file="lib/my_gem/version.rb"config.module_name="MyGem"config.version_checks=[:doc_references,:code_references,:package_metadata]config.release_checks=[:rubygems]config.rubygems_host="https://rubygems.org"config.version_code_reference_files.append("lib/**/*.rb")config.version_doc_reference_files.append("doc/**/*.md")config.version_match_mode=:non_currentend

The core defaults are equivalent to:

Semverve::Task.newdo |config|
config.adapter=nilconfig.format=:moduleconfig.bundle_lock=falseconfig.root=Dir.pwdconfig.version_checks=[:doc_references,:code_references,:package_metadata]config.release_checks=[]config.rubygems_host="https://rubygems.org"config.task_namespace=:semverveconfig.version_match_mode=:olderconfig.version_code_reference_files=Rake::FileList[]config.version_code_reference_pattern=/^\s*(?:(?:[A-Z]\w*::)*(?:[A-Z]\w*VERSION[A-Z0-9_]*|VERSION)|(?:[a-z_]\w*|self)\.version)\s*=\s*(?<quote>["'])(?<version>\d+\.\d+\.\d+)\k<quote>/config.version_doc_reference_files=Rake::FileList["README*","**/README*"].exclude(".git/**/*","coverage/**/*","tmp/**/*","vendor/**/*")end

The empty version_code_reference_files default only applies to arbitrary code literal scanning. rake semverve:check still checks the resolved .gemspec version and matching Gemfile.lock entry through its default package metadata check. Release checks are empty by default because they may make network requests and are intended for release pipelines rather than every local or pull-request run.

Set config.task_namespace in the task block to use a shorter or project-specific namespace:

require"semverve/task"Semverve::Task.newdo |config|
config.task_namespace=:versionend

That installs tasks such as version:current, version:increment:patch, and version:check instead of semverve:*.

Semverve tasks use Rake task arguments for values:

rake 'semverve:set[1.2.3]'
rake 'semverve:generate[1.0.0,simple]'
rake 'semverve:generate[simple]'
rake 'semverve:generate[force]'

Quote task invocations that include square brackets. Shells such as zsh may otherwise treat brackets as glob patterns before Rake sees them. Flag syntax such as rake semverve:set --version 1.2.3 is not used because --version is already a Rake option; Semverve stays within Rake's native argument syntax instead.

The gem name, module name, and version-file path are inferred by default:

config.gem_name# spec.name from the single .gemspecconfig.module_name# camelized gem name, such as "MyGem"config.version_file# lib/<gem_name>/version.rb

Override them when your project does something unusual:

Semverve::Task.newdo |config|
config.gem_name="my-gem"config.module_name="MyGem"config.version_file="lib/my_gem/version.rb"end

Framework adapters

Framework adapters provide app-oriented defaults without requiring a gemspec or package identity. config.adapter is the preferred API; config.preset remains supported as a backward-compatible alias.

Rails apps

Rails applications do not need gem-style version files, but an application version can still be useful for release notes, support/debug screens, deployment metadata, or API output.

When Rails is loaded, Semverve's Railtie installs the same semverve:* Rake tasks for bin/rails/rails automatically. To use Rails-style defaults, set the Rails adapter:

Semverve::Task.newdo |config|
config.adapter=:railsend

config.preset = :rails is still accepted for existing setups.

The Rails adapter uses Rails.root, stores the version in config/version.rb, uses the :simple format, and infers the module name from your Rails application module when possible. Its default checks are app-oriented: documentation references, configured code literals, and optional Rails config metadata. It does not run package metadata checks unless you opt in.

Generate the file with:

bin/rails semverve:generate

If your app keeps the version somewhere else, override the path:

Semverve::Task.newdo |config|
config.adapter=:railsconfig.version_file="config/releases/version.rb"end

Rails support is only an adapter and a Railtie; Semverve does not require a dummy app, a Rails plugin layout, or a Rails dependency.

Rails config metadata is optional. When present, Semverve checks safe literals in config/application.rb, config/environments/*.rb, and config/initializers/**/*.rb:

config.x.version="1.2.2"Rails.application.config.x.version="1.2.2"

Dynamic assignments are treated as self-managed and left alone:

config.x.version=Storefront::VERSION

Rails engines or apps that publish gems can opt into package metadata checks by setting config.gem_name and including :package_metadata in config.version_checks. Deployment and container metadata, such as Docker, Kamal, and Helm, are intentionally left for future adapter support.

Sinatra apps

Sinatra applications can use the Sinatra adapter for app-style defaults:

Semverve::Task.newdo |config|
config.adapter=:sinatraend

The Sinatra adapter stores the version in config/version.rb, uses the :simple format, infers the module name from the project directory, and checks documentation references plus configured code literals by default. It does not infer a package name from config/version.rb and does not run package metadata checks unless you opt in.

Formats

The default :module format stores MAJOR, MINOR, and PATCH constants under a Version module and exposes a top-level VERSION constant.

moduleMyGemmoduleVersionMAJOR=0MINOR=1PATCH=0module_functiondefto_a[MAJOR,MINOR,PATCH]enddefto_sto_a.join(".")endendVERSION=Version.to_send

The :simple format stores only:

moduleMyGemVERSION="1.0.0"end

Generating

Generate the default module format:

rake semverve:generate

Generate a specific version or format:

rake 'semverve:generate[1.0.0,simple]'
rake 'semverve:generate[simple]'

Generation fails if the target file already exists. To replace it:

rake 'semverve:generate[force]'

semverve:generate accepts optional tokens for version, format, and force. Token order does not matter: semantic versions set the generated version, module or simple sets the format, and force overwrites an existing version file.

rake 'semverve:generate[1.0.0,force]'
rake 'semverve:generate[simple,force]'
rake 'semverve:generate[1.0.0,simple,force]'

Bootstrapping a new gem

If your gemspec dynamically requires the generated version file, a brand-new project can fail before semverve:generate has a chance to run:

require_relative"lib/my_gem/version"

Rake loads the Rakefile before it can invoke any task. If loading the Rakefile also loads bundler/gem_tasks, Bundler evaluates the gemspec, and an unconditional require_relative can crash because lib/my_gem/version.rb does not exist yet.

Prefer guarding Bundler's gem tasks while running semverve:generate:

require"semverve/task"unlessARGV.any?{ |arg| arg.start_with?("semverve:generate")}require"bundler/gem_tasks"endSemverve::Task.new

Then generate the version file:

rake semverve:generate

After generation, you can keep the guard or switch back to your normal dynamic gemspec loading style.

Semverve's metadata inference does not evaluate the gemspec for generation; it reads the literal spec.name, so either bootstrap pattern still allows semverve:generate to infer lib/my_gem/version.rb.

Alternatively, make the gemspec tolerate the missing file during bootstrap:

version_path=File.expand_path("lib/my_gem/version",__dir__)requireversion_pathifFile.file?("#{version_path}.rb")Gem::Specification.newdo |spec|
spec.name="my_gem"spec.version=defined?(MyGem::VERSION) ? MyGem::VERSION : "0.0.0"semverveend

Incrementing

rake semverve:increment:patch
rake semverve:increment:minor
rake semverve:increment:major

Patch increments only patch. Minor increments minor and resets patch to 0. Major increments major and resets minor and patch to 0.

Successful increments print the version change:

Updating to version 2.0.2 (was 2.0.1)

Set config.bundle_lock = true to run bundle lock after increments to update your gem's version in Gemfile.lock.

Setting

Set an exact version without incrementing:

rake 'semverve:set[1.2.3]'

Successful updates print the version change:

Updating to version 1.2.3 (was 1.2.2)

Setting the current version again does not rewrite the version file:

Version is already 1.2.3

Setting a lower version prints a warning but still updates the file:

Warning: updating to version 1.9.9, which is lower than the current version 2.0.1.
Updating to version 1.9.9 (was 2.0.1)

This will also run bundle lock on success if you have config.bundle_lock = true in your config.

Checking version references, code, and metadata

Run every version check with:

rake semverve:check

This task is designed for normal CI. It uses local project files, prints parseable findings, and exits non-zero when it finds drift.

By default, gem/package projects check:

  • README version references, plus any configured docs or comment files
  • configured code files for safe version literals
  • the gemspec version and Gemfile.lock entry

Rails adapter projects instead check README references, configured code literals, and optional Rails config metadata.

Findings are printed in parseable formats and the task exits non-zero:

README.md:12:24: version reference 1.2.2 -> 1.2.3
lib/my_gem/constants.rb:1:16: code version literal 1.2.2 -> 1.2.3
my_gem.gemspec:3:18: gemspec version 1.2.2 -> 1.2.3
Gemfile.lock:4:13: locked version 1.2.2 -> 1.2.3

Run every available fix:

rake semverve:fix

Choose which surfaces the umbrella check and fix tasks run with config.version_checks:

Semverve::Task.newdo |config|
config.version_checks=[:doc_references,:package_metadata]end

The allowed values come from Semverve's check registry. Built-in checks are :doc_references, :code_references, and :package_metadata; framework adapters can add their own checks, such as Rails' :rails_config_metadata.

Use focused tasks when you want only one surface:

rake semverve:check:references
rake semverve:fix:references
rake semverve:check:code
rake semverve:fix:code
rake semverve:check:package_metadata
rake semverve:fix:package_metadata
rake semverve:check:rails_config_metadata
rake semverve:fix:rails_config_metadata

semverve:fix:package_metadata rewrites literal gemspec versions when safe and runs bundle lock for Gemfile.lock drift. semverve:fix:rails_config_metadata rewrites safe Rails config version literals.

Pass a semantic version when you want to check or fix only that exact version in doc references and code literals:

rake 'semverve:check[1.2.2]'
rake 'semverve:fix:references[1.2.2]'

Package metadata and adapter-owned metadata checks still compare metadata to the current version. If you target the current version, check tasks list reference/code matches but fix tasks are no-ops because the text is already current.

Extension API

Semverve exposes small public objects for framework adapters and version checks. These APIs are intentionally local registration APIs; Semverve does not yet autoload third-party adapter gems.

Register a framework adapter with Semverve::Adapters.register. An adapter must expose name, defaults(configuration), and checks. It can also expose infer_package_name? to control whether app-style version files should be treated as package names.

Register a check with Semverve::VersionChecks.register, or return adapter-owned checks from an adapter's checks method. A check object should expose:

  • name and task_name
  • check_description, fix_description, finding_label, and fix_label
  • clean_message, targetable?, and exact_target_fix_noop_notice?
  • findings(configuration, current_version, include_ignored:, target_version:)
  • fix(configuration, current_version, target_version:)

Checks should return Semverve::Finding objects from findings and a Semverve::FixResult from fix. Semverve::VersionMatchPolicy and Semverve::VersionLiteralRewriter are available for checks that need Semverve's standard stale-version matching or named-capture literal rewriting.

For example:

classMyConfigVersionCheck < Semverve::VersionChecks::Checkdefname=:my_config_metadatadeftask_name=:my_config_metadatadefcheck_description="Check app config metadata for version mismatches"deffix_description="Fix safe app config metadata version mismatches"deffinding_label="app config version"defclean_message="App config metadata is current."deffindings(configuration,current_version,include_ignored: false,target_version: nil)[]enddeffix(configuration,current_version,target_version: nil)Semverve::FixResult.new(changed_files: [],replacement_count: 0)endend

Version references

Version references are prose-like references to versions. These are usually in README files, docs, guides, changelogs, or comments. By default, Semverve scans README files throughout the repo:

Semverve::Task.newdo |config|
config.version_doc_reference_files=Rake::FileList["README*","**/README*"].exclude(".git/**/*","coverage/**/*","tmp/**/*","vendor/**/*")end

Add docs or Ruby comments without replacing the README defaults:

Semverve::Task.newdo |config|
config.version_doc_reference_files.append("doc/**/*.md","lib/**/*.rb")end

Replace the defaults entirely:

Semverve::Task.newdo |config|
config.version_doc_reference_files=Rake::FileList["guides/**/*.md"]end

Ruby files are scanned only in comments. Text files with .md, .markdown, .txt, .rdoc, and .adoc extensions are scanned as full text.

The default version match mode is :older, which flags only semantic versions lower than the current version in doc references and code literals:

Semverve::Task.newdo |config|
config.version_match_mode=:olderend

Use :non_current when every doc reference and code literal should match the current version:

Semverve::Task.newdo |config|
config.version_match_mode=:non_currentend

Ignore an intentional reference with semverve:ignore-version-reference on the same line or the preceding nonblank line.

This migration note intentionally mentions 1.0.0. <!-- semverve:ignore-version-reference -->

Prefer ignore markers when possible because they move with the ignored text. Configured ignores are for edge cases where a marker would render as unwanted content, such as inside a Markdown code block. They depend on line numbers, so they can drift when the file changes:

Semverve::Task.newdo |config|
config.version_reference_ignores={"README.md"=>{42=>["1.0.0"],45=>"2.0.0"}}end

Configured ignores apply to documentation references and code literals. They are exact to the version string, so other stale versions on the same line are still reported. Fix tasks leave configured ignores unchanged.

Audit ignored references by setting SEMVERVE_REPORT_IGNORED=true when running check tasks:

SEMVERVE_REPORT_IGNORED=true rake semverve:check
SEMVERVE_REPORT_IGNORED=true rake 'semverve:check[1.2.2]'

Code version literals

Code scanning is opt-in to avoid false positives. This is for arbitrary project code, not package metadata. The default is:

Semverve::Task.newdo |config|
config.version_code_reference_files=Rake::FileList[]end

Append files when you want Semverve to check safe code literals:

Semverve::Task.newdo |config|
config.version_code_reference_files.append("lib/**/*.rb")end

Or replace the list entirely:

Semverve::Task.newdo |config|
config.version_code_reference_files=Rake::FileList["lib/**/*.rb","*.gemspec"]end

Ruby code checks only obvious version assignments/constants, such as:

APP_VERSION="1.2.2"spec.version="1.2.2"

Ignore an intentional code literal with semverve:ignore-version-reference on the same line or the preceding nonblank line.

Set SEMVERVE_REPORT_IGNORED=true with semverve:check or semverve:check:code to report ignored stale literals without changing semverve:fix behavior.

The default pattern is Ruby-oriented. Semverve does not inspect file extensions or parse other languages for code literals; non-Ruby files are scanned as plain text with the same pattern. If a JavaScript, Python, or other source file uses a different version-literal shape, configure a custom pattern before adding those files.

Arbitrary string examples are ignored.

If your project has a different safe version-literal shape, provide your own pattern:

Semverve::Task.newdo |config|
config.version_code_reference_files.append("lib/**/*.rb")config.version_code_reference_pattern=/release ["'](?<version>\d+\.\d+\.\d+)["']/end

With that pattern, this line:

release"1.2.2"

matches the full release "1.2.2" text, but only 1.2.2 is captured as version. If rake semverve:fix:code is updating references to 1.2.3, the line becomes:

release"1.2.3"

The custom value must be a Regexp and must include a named capture called version. Semverve replaces only that capture when running rake semverve:fix:code, and the captured value still has to parse as a semantic version.

Package metadata

Package metadata checks are part of rake semverve:check by default for gem/package projects. They compare the current version file against:

  • the resolved .gemspec version
  • the matching Gemfile.lock entry, when a lockfile exists

Package metadata always requires an exact match, regardless of config.version_match_mode.

No file-list configuration is needed for these checks. Semverve resolves the gemspec from the project root and reads Gemfile.lock when one exists.

Dynamic gemspec versions work as expected:

require_relative"lib/my_gem/version"Gem::Specification.newdo |spec|
spec.name="my_gem"spec.version=MyGem::VERSIONend

Literal gemspec versions can be fixed automatically:

Gem::Specification.newdo |spec|
spec.name="my_gem"spec.version="1.2.2"end

rake semverve:fix:package_metadata updates safe literal gemspec assignments and runs bundle lock when the lockfile has drifted.

Rails config metadata

Rails config metadata checks are part of rake semverve:check when the Rails adapter is active. They scan config/application.rb, config/environments/*.rb, and config/initializers/**/*.rb for optional Rails config literals:

config.x.version="1.2.2"Rails.application.config.x.version="1.2.2"

These checks always require an exact match with the current Semverve version. rake semverve:fix:rails_config_metadata rewrites only those safe string literals. Dynamic assignments, including config.x.version = Storefront::VERSION, are considered self-managed and ignored.

Checking release readiness

Release checks are separate from rake semverve:check. They are useful in CI, but they are meant for release workflows, tag builds, or pre-publish jobs rather than every pull request.

Enable the RubyGems published-version check:

Semverve::Task.newdo |config|
config.release_checks=[:rubygems]end

Then run:

rake semverve:check:release

With :rubygems enabled, semverve:check:release asks the configured RubyGems-compatible host whether the current version already exists. If it does, the task exits non-zero with a message like:

my_gem 1.2.3 already exists on https://rubygems.org.

If all configured release checks pass, it prints:

Release checks passed.

You can also run the RubyGems check directly without changing config.release_checks:

rake semverve:check:rubygems

When the current version is not published, the focused task prints:

my_gem 1.2.3 is not published on https://rubygems.org.

Use config.rubygems_host for a private RubyGems-compatible server:

Semverve::Task.newdo |config|
config.release_checks=[:rubygems]config.rubygems_host="https://gems.example.com"end

The published-version check treats a missing gem as unpublished. It fails closed on registry errors, malformed responses, and network failures because release pipelines should not silently publish after an inconclusive preflight.

For ordinary CI, keep using the local checks:

bundle exec rake test semverve:check

For release CI, run the release check before building or pushing:

bundle exec rake semverve:check:release build

Vim

While there's no official vim support (yet), you can add the following to ~/.vim/plugin/semverve.vim.

command!-bang SemverveAudit call<SID>semverve_audit(<bang>0)
function!s:semverve_audit(report_ignored) abortletl:old_efm= &errorformattrylet &errorformat='%f:%l:%c:%m'letl:string=""if!a:report_ignoredletl:string.='SEMVERVE_REPORT_IGNORED=true 'endifletl:string.='bundle exec rake semverve:check 2>/dev/null'cexprsystemlist(l:string)
ifv:shell_error!=0copenelseccloseecho'Semverve checks passed.'endiffinallylet &errorformat=l:old_efmendtryendfunction

You can then call :SemverveAudit, which will call bundle exec rake semverve:check, and :SemverveAudit! which will call the same command with SEMVERVE_REPORT_IGNORED=true, and populate and open the quickfix list if any offenses are found.

Reporting Bugs and Requesting Features

If you have an idea or find a bug, please create an issue. Just make sure the topic doesn't already exist. Better yet, you can always submit a Pull Request.

Support this project

I love knowing when people find my work useful. Any kind of support is very much appreciated!

About

🏷️ Rake tasks for incrementing gem versions and finding and updating version references in documentation and code.

Resources

Contributing

Stars

1 star

Watchers

1 watching

Forks

Releases

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Repository files navigation

Semverve

Build StatusLanguage: RubyGem VersionLicense: MIT

Rake tasks for handling the tedium surrounding maintaining a version number in your Ruby project, with gusto!

About

Maintaining a gem version is not hard, but there are so many little pieces that are easy to forget. How many times have you had changes where the code was ready, the tests were green, the PR was merged, you go to push the gem, and you realize you forgot to bump the version? Then comes the tiny follow-up PR that forces you to waste CI minutes for a two-line change, you submit it, and... oh, no! You still have references to the old version number in your documentation! Rinse and repeat until you finally remember all the things.

Semverve is meant to make that tedium boring in the best way. It provides a small set of Rake tasks for reading the current version, generating a version file, incrementing patch/minor/major versions, setting an exact version, and checking the places where version numbers tend to drift, like .gemspec files and documentation.

In a nutshell, rake semverve:increment:patch updates the "patch" level in your configured version.rb file, :minor updates the "minor" level, etc.. Calling rake semverve:check checks whether the surrounding project still agrees with that version. It can catch stale README references, safe code literals, .gemspec drift, and a stale Gemfile.lock entry. If you want Semverve to do the mechanical cleanup, the matching *:fix tasks can update safe references and run bundle lock for generated lockfile drift. Specific findings can be skipped with magic comments, similar to RuboCop and RDoc.

You can view the documentation here.

For release history and upgrading notes, see CHANGELOG.md.

Installation

Add the gem to your Gemfile:

gem"semverve"

Then add this to your Rakefile:

require"semverve/task"Semverve::Task.new

This defines:

rake semverve:current
rake semverve:increment:patch
rake semverve:increment:minor
rake semverve:increment:major
rake semverve:generate
rake 'semverve:set[1.2.3]'
rake semverve:check
rake semverve:fix
rake semverve:check:references
rake semverve:fix:references
rake semverve:check:code
rake semverve:fix:code
rake semverve:check:package_metadata
rake semverve:fix:package_metadata
rake semverve:check:rails_config_metadata
rake semverve:fix:rails_config_metadata
rake semverve:check:rubygems
rake semverve:check:release

Configuration

By default, Semverve reads the single .gemspec in the project root, uses spec.name as the gem name, and manages lib/<gem_name>/version.rb.

For a conventional gem, this may be all you need:

require"semverve/task"Semverve::Task.new

That installs the semverve:* Rake tasks. If you want to change any defaults, configure Semverve from the task block in your Rakefile:

require"semverve/task"Semverve::Task.newdo |config|
config.format=:moduleconfig.bundle_lock=trueconfig.version_file="lib/my_gem/version.rb"config.module_name="MyGem"config.version_checks=[:doc_references,:code_references,:package_metadata]config.release_checks=[:rubygems]config.rubygems_host="https://rubygems.org"config.version_code_reference_files.append("lib/**/*.rb")config.version_doc_reference_files.append("doc/**/*.md")config.version_match_mode=:non_currentend

The core defaults are equivalent to:

Semverve::Task.newdo |config|
config.adapter=nilconfig.format=:moduleconfig.bundle_lock=falseconfig.root=Dir.pwdconfig.version_checks=[:doc_references,:code_references,:package_metadata]config.release_checks=[]config.rubygems_host="https://rubygems.org"config.task_namespace=:semverveconfig.version_match_mode=:olderconfig.version_code_reference_files=Rake::FileList[]config.version_code_reference_pattern=/^\s*(?:(?:[A-Z]\w*::)*(?:[A-Z]\w*VERSION[A-Z0-9_]*|VERSION)|(?:[a-z_]\w*|self)\.version)\s*=\s*(?<quote>["'])(?<version>\d+\.\d+\.\d+)\k<quote>/config.version_doc_reference_files=Rake::FileList["README*","**/README*"].exclude(".git/**/*","coverage/**/*","tmp/**/*","vendor/**/*")end

The empty version_code_reference_files default only applies to arbitrary code literal scanning. rake semverve:check still checks the resolved .gemspec version and matching Gemfile.lock entry through its default package metadata check. Release checks are empty by default because they may make network requests and are intended for release pipelines rather than every local or pull-request run.

Set config.task_namespace in the task block to use a shorter or project-specific namespace:

require"semverve/task"Semverve::Task.newdo |config|
config.task_namespace=:versionend

That installs tasks such as version:current, version:increment:patch, and version:check instead of semverve:*.

Semverve tasks use Rake task arguments for values:

rake 'semverve:set[1.2.3]'
rake 'semverve:generate[1.0.0,simple]'
rake 'semverve:generate[simple]'
rake 'semverve:generate[force]'

Quote task invocations that include square brackets. Shells such as zsh may otherwise treat brackets as glob patterns before Rake sees them. Flag syntax such as rake semverve:set --version 1.2.3 is not used because --version is already a Rake option; Semverve stays within Rake's native argument syntax instead.

The gem name, module name, and version-file path are inferred by default:

config.gem_name# spec.name from the single .gemspecconfig.module_name# camelized gem name, such as "MyGem"config.version_file# lib/<gem_name>/version.rb

Override them when your project does something unusual:

Semverve::Task.newdo |config|
config.gem_name="my-gem"config.module_name="MyGem"config.version_file="lib/my_gem/version.rb"end

Framework adapters

Framework adapters provide app-oriented defaults without requiring a gemspec or package identity. config.adapter is the preferred API; config.preset remains supported as a backward-compatible alias.

Rails apps

Rails applications do not need gem-style version files, but an application version can still be useful for release notes, support/debug screens, deployment metadata, or API output.

When Rails is loaded, Semverve's Railtie installs the same semverve:* Rake tasks for bin/rails/rails automatically. To use Rails-style defaults, set the Rails adapter:

Semverve::Task.newdo |config|
config.adapter=:railsend

config.preset = :rails is still accepted for existing setups.

The Rails adapter uses Rails.root, stores the version in config/version.rb, uses the :simple format, and infers the module name from your Rails application module when possible. Its default checks are app-oriented: documentation references, configured code literals, and optional Rails config metadata. It does not run package metadata checks unless you opt in.

Generate the file with:

bin/rails semverve:generate

If your app keeps the version somewhere else, override the path:

Semverve::Task.newdo |config|
config.adapter=:railsconfig.version_file="config/releases/version.rb"end

Rails support is only an adapter and a Railtie; Semverve does not require a dummy app, a Rails plugin layout, or a Rails dependency.

Rails config metadata is optional. When present, Semverve checks safe literals in config/application.rb, config/environments/*.rb, and config/initializers/**/*.rb:

config.x.version="1.2.2"Rails.application.config.x.version="1.2.2"

Dynamic assignments are treated as self-managed and left alone:

config.x.version=Storefront::VERSION

Rails engines or apps that publish gems can opt into package metadata checks by setting config.gem_name and including :package_metadata in config.version_checks. Deployment and container metadata, such as Docker, Kamal, and Helm, are intentionally left for future adapter support.

Sinatra apps

Sinatra applications can use the Sinatra adapter for app-style defaults:

Semverve::Task.newdo |config|
config.adapter=:sinatraend

The Sinatra adapter stores the version in config/version.rb, uses the :simple format, infers the module name from the project directory, and checks documentation references plus configured code literals by default. It does not infer a package name from config/version.rb and does not run package metadata checks unless you opt in.

Formats

The default :module format stores MAJOR, MINOR, and PATCH constants under a Version module and exposes a top-level VERSION constant.

moduleMyGemmoduleVersionMAJOR=0MINOR=1PATCH=0module_functiondefto_a[MAJOR,MINOR,PATCH]enddefto_sto_a.join(".")endendVERSION=Version.to_send

The :simple format stores only:

moduleMyGemVERSION="1.0.0"end

Generating

Generate the default module format:

rake semverve:generate

Generate a specific version or format:

rake 'semverve:generate[1.0.0,simple]'
rake 'semverve:generate[simple]'

Generation fails if the target file already exists. To replace it:

rake 'semverve:generate[force]'

semverve:generate accepts optional tokens for version, format, and force. Token order does not matter: semantic versions set the generated version, module or simple sets the format, and force overwrites an existing version file.

rake 'semverve:generate[1.0.0,force]'
rake 'semverve:generate[simple,force]'
rake 'semverve:generate[1.0.0,simple,force]'

Bootstrapping a new gem

If your gemspec dynamically requires the generated version file, a brand-new project can fail before semverve:generate has a chance to run:

require_relative"lib/my_gem/version"

Rake loads the Rakefile before it can invoke any task. If loading the Rakefile also loads bundler/gem_tasks, Bundler evaluates the gemspec, and an unconditional require_relative can crash because lib/my_gem/version.rb does not exist yet.

Prefer guarding Bundler's gem tasks while running semverve:generate:

require"semverve/task"unlessARGV.any?{ |arg| arg.start_with?("semverve:generate")}require"bundler/gem_tasks"endSemverve::Task.new

Then generate the version file:

rake semverve:generate

After generation, you can keep the guard or switch back to your normal dynamic gemspec loading style.

Semverve's metadata inference does not evaluate the gemspec for generation; it reads the literal spec.name, so either bootstrap pattern still allows semverve:generate to infer lib/my_gem/version.rb.

Alternatively, make the gemspec tolerate the missing file during bootstrap:

version_path=File.expand_path("lib/my_gem/version",__dir__)requireversion_pathifFile.file?("#{version_path}.rb")Gem::Specification.newdo |spec|
spec.name="my_gem"spec.version=defined?(MyGem::VERSION) ? MyGem::VERSION : "0.0.0"semverveend

Incrementing

rake semverve:increment:patch
rake semverve:increment:minor
rake semverve:increment:major

Patch increments only patch. Minor increments minor and resets patch to 0. Major increments major and resets minor and patch to 0.

Successful increments print the version change:

Updating to version 2.0.2 (was 2.0.1)

Set config.bundle_lock = true to run bundle lock after increments to update your gem's version in Gemfile.lock.

Setting

Set an exact version without incrementing:

rake 'semverve:set[1.2.3]'

Successful updates print the version change:

Updating to version 1.2.3 (was 1.2.2)

Setting the current version again does not rewrite the version file:

Version is already 1.2.3

Setting a lower version prints a warning but still updates the file:

Warning: updating to version 1.9.9, which is lower than the current version 2.0.1.
Updating to version 1.9.9 (was 2.0.1)

This will also run bundle lock on success if you have config.bundle_lock = true in your config.

Checking version references, code, and metadata

Run every version check with:

rake semverve:check

This task is designed for normal CI. It uses local project files, prints parseable findings, and exits non-zero when it finds drift.

By default, gem/package projects check:

  • README version references, plus any configured docs or comment files
  • configured code files for safe version literals
  • the gemspec version and Gemfile.lock entry

Rails adapter projects instead check README references, configured code literals, and optional Rails config metadata.

Findings are printed in parseable formats and the task exits non-zero:

README.md:12:24: version reference 1.2.2 -> 1.2.3
lib/my_gem/constants.rb:1:16: code version literal 1.2.2 -> 1.2.3
my_gem.gemspec:3:18: gemspec version 1.2.2 -> 1.2.3
Gemfile.lock:4:13: locked version 1.2.2 -> 1.2.3

Run every available fix:

rake semverve:fix

Choose which surfaces the umbrella check and fix tasks run with config.version_checks:

Semverve::Task.newdo |config|
config.version_checks=[:doc_references,:package_metadata]end

The allowed values come from Semverve's check registry. Built-in checks are :doc_references, :code_references, and :package_metadata; framework adapters can add their own checks, such as Rails' :rails_config_metadata.

Use focused tasks when you want only one surface:

rake semverve:check:references
rake semverve:fix:references
rake semverve:check:code
rake semverve:fix:code
rake semverve:check:package_metadata
rake semverve:fix:package_metadata
rake semverve:check:rails_config_metadata
rake semverve:fix:rails_config_metadata

semverve:fix:package_metadata rewrites literal gemspec versions when safe and runs bundle lock for Gemfile.lock drift. semverve:fix:rails_config_metadata rewrites safe Rails config version literals.

Pass a semantic version when you want to check or fix only that exact version in doc references and code literals:

rake 'semverve:check[1.2.2]'
rake 'semverve:fix:references[1.2.2]'

Package metadata and adapter-owned metadata checks still compare metadata to the current version. If you target the current version, check tasks list reference/code matches but fix tasks are no-ops because the text is already current.

Extension API

Semverve exposes small public objects for framework adapters and version checks. These APIs are intentionally local registration APIs; Semverve does not yet autoload third-party adapter gems.

Register a framework adapter with Semverve::Adapters.register. An adapter must expose name, defaults(configuration), and checks. It can also expose infer_package_name? to control whether app-style version files should be treated as package names.

Register a check with Semverve::VersionChecks.register, or return adapter-owned checks from an adapter's checks method. A check object should expose:

  • name and task_name
  • check_description, fix_description, finding_label, and fix_label
  • clean_message, targetable?, and exact_target_fix_noop_notice?
  • findings(configuration, current_version, include_ignored:, target_version:)
  • fix(configuration, current_version, target_version:)

Checks should return Semverve::Finding objects from findings and a Semverve::FixResult from fix. Semverve::VersionMatchPolicy and Semverve::VersionLiteralRewriter are available for checks that need Semverve's standard stale-version matching or named-capture literal rewriting.

For example:

classMyConfigVersionCheck < Semverve::VersionChecks::Checkdefname=:my_config_metadatadeftask_name=:my_config_metadatadefcheck_description="Check app config metadata for version mismatches"deffix_description="Fix safe app config metadata version mismatches"deffinding_label="app config version"defclean_message="App config metadata is current."deffindings(configuration,current_version,include_ignored: false,target_version: nil)[]enddeffix(configuration,current_version,target_version: nil)Semverve::FixResult.new(changed_files: [],replacement_count: 0)endend

Version references

Version references are prose-like references to versions. These are usually in README files, docs, guides, changelogs, or comments. By default, Semverve scans README files throughout the repo:

Semverve::Task.newdo |config|
config.version_doc_reference_files=Rake::FileList["README*","**/README*"].exclude(".git/**/*","coverage/**/*","tmp/**/*","vendor/**/*")end

Add docs or Ruby comments without replacing the README defaults:

Semverve::Task.newdo |config|
config.version_doc_reference_files.append("doc/**/*.md","lib/**/*.rb")end

Replace the defaults entirely:

Semverve::Task.newdo |config|
config.version_doc_reference_files=Rake::FileList["guides/**/*.md"]end

Ruby files are scanned only in comments. Text files with .md, .markdown, .txt, .rdoc, and .adoc extensions are scanned as full text.

The default version match mode is :older, which flags only semantic versions lower than the current version in doc references and code literals:

Semverve::Task.newdo |config|
config.version_match_mode=:olderend

Use :non_current when every doc reference and code literal should match the current version:

Semverve::Task.newdo |config|
config.version_match_mode=:non_currentend

Ignore an intentional reference with semverve:ignore-version-reference on the same line or the preceding nonblank line.

This migration note intentionally mentions 1.0.0. <!-- semverve:ignore-version-reference -->

Prefer ignore markers when possible because they move with the ignored text. Configured ignores are for edge cases where a marker would render as unwanted content, such as inside a Markdown code block. They depend on line numbers, so they can drift when the file changes:

Semverve::Task.newdo |config|
config.version_reference_ignores={"README.md"=>{42=>["1.0.0"],45=>"2.0.0"}}end

Configured ignores apply to documentation references and code literals. They are exact to the version string, so other stale versions on the same line are still reported. Fix tasks leave configured ignores unchanged.

Audit ignored references by setting SEMVERVE_REPORT_IGNORED=true when running check tasks:

SEMVERVE_REPORT_IGNORED=true rake semverve:check
SEMVERVE_REPORT_IGNORED=true rake 'semverve:check[1.2.2]'

Code version literals

Code scanning is opt-in to avoid false positives. This is for arbitrary project code, not package metadata. The default is:

Semverve::Task.newdo |config|
config.version_code_reference_files=Rake::FileList[]end

Append files when you want Semverve to check safe code literals:

Semverve::Task.newdo |config|
config.version_code_reference_files.append("lib/**/*.rb")end

Or replace the list entirely:

Semverve::Task.newdo |config|
config.version_code_reference_files=Rake::FileList["lib/**/*.rb","*.gemspec"]end

Ruby code checks only obvious version assignments/constants, such as:

APP_VERSION="1.2.2"spec.version="1.2.2"

Ignore an intentional code literal with semverve:ignore-version-reference on the same line or the preceding nonblank line.

Set SEMVERVE_REPORT_IGNORED=true with semverve:check or semverve:check:code to report ignored stale literals without changing semverve:fix behavior.

The default pattern is Ruby-oriented. Semverve does not inspect file extensions or parse other languages for code literals; non-Ruby files are scanned as plain text with the same pattern. If a JavaScript, Python, or other source file uses a different version-literal shape, configure a custom pattern before adding those files.

Arbitrary string examples are ignored.

If your project has a different safe version-literal shape, provide your own pattern:

Semverve::Task.newdo |config|
config.version_code_reference_files.append("lib/**/*.rb")config.version_code_reference_pattern=/release ["'](?<version>\d+\.\d+\.\d+)["']/end

With that pattern, this line:

release"1.2.2"

matches the full release "1.2.2" text, but only 1.2.2 is captured as version. If rake semverve:fix:code is updating references to 1.2.3, the line becomes:

release"1.2.3"

The custom value must be a Regexp and must include a named capture called version. Semverve replaces only that capture when running rake semverve:fix:code, and the captured value still has to parse as a semantic version.

Package metadata

Package metadata checks are part of rake semverve:check by default for gem/package projects. They compare the current version file against:

  • the resolved .gemspec version
  • the matching Gemfile.lock entry, when a lockfile exists

Package metadata always requires an exact match, regardless of config.version_match_mode.

No file-list configuration is needed for these checks. Semverve resolves the gemspec from the project root and reads Gemfile.lock when one exists.

Dynamic gemspec versions work as expected:

require_relative"lib/my_gem/version"Gem::Specification.newdo |spec|
spec.name="my_gem"spec.version=MyGem::VERSIONend

Literal gemspec versions can be fixed automatically:

Gem::Specification.newdo |spec|
spec.name="my_gem"spec.version="1.2.2"end

rake semverve:fix:package_metadata updates safe literal gemspec assignments and runs bundle lock when the lockfile has drifted.

Rails config metadata

Rails config metadata checks are part of rake semverve:check when the Rails adapter is active. They scan config/application.rb, config/environments/*.rb, and config/initializers/**/*.rb for optional Rails config literals:

config.x.version="1.2.2"Rails.application.config.x.version="1.2.2"

These checks always require an exact match with the current Semverve version. rake semverve:fix:rails_config_metadata rewrites only those safe string literals. Dynamic assignments, including config.x.version = Storefront::VERSION, are considered self-managed and ignored.

Checking release readiness

Release checks are separate from rake semverve:check. They are useful in CI, but they are meant for release workflows, tag builds, or pre-publish jobs rather than every pull request.

Enable the RubyGems published-version check:

Semverve::Task.newdo |config|
config.release_checks=[:rubygems]end

Then run:

rake semverve:check:release

With :rubygems enabled, semverve:check:release asks the configured RubyGems-compatible host whether the current version already exists. If it does, the task exits non-zero with a message like:

my_gem 1.2.3 already exists on https://rubygems.org.

If all configured release checks pass, it prints:

Release checks passed.

You can also run the RubyGems check directly without changing config.release_checks:

rake semverve:check:rubygems

When the current version is not published, the focused task prints:

my_gem 1.2.3 is not published on https://rubygems.org.

Use config.rubygems_host for a private RubyGems-compatible server:

Semverve::Task.newdo |config|
config.release_checks=[:rubygems]config.rubygems_host="https://gems.example.com"end

The published-version check treats a missing gem as unpublished. It fails closed on registry errors, malformed responses, and network failures because release pipelines should not silently publish after an inconclusive preflight.

For ordinary CI, keep using the local checks:

bundle exec rake test semverve:check

For release CI, run the release check before building or pushing:

bundle exec rake semverve:check:release build

Vim

While there's no official vim support (yet), you can add the following to ~/.vim/plugin/semverve.vim.

command!-bang SemverveAudit call<SID>semverve_audit(<bang>0)
function!s:semverve_audit(report_ignored) abortletl:old_efm= &errorformattrylet &errorformat='%f:%l:%c:%m'letl:string=""if!a:report_ignoredletl:string.='SEMVERVE_REPORT_IGNORED=true 'endifletl:string.='bundle exec rake semverve:check 2>/dev/null'cexprsystemlist(l:string)
ifv:shell_error!=0copenelseccloseecho'Semverve checks passed.'endiffinallylet &errorformat=l:old_efmendtryendfunction

You can then call :SemverveAudit, which will call bundle exec rake semverve:check, and :SemverveAudit! which will call the same command with SEMVERVE_REPORT_IGNORED=true, and populate and open the quickfix list if any offenses are found.

Reporting Bugs and Requesting Features

If you have an idea or find a bug, please create an issue. Just make sure the topic doesn't already exist. Better yet, you can always submit a Pull Request.

Support this project

I love knowing when people find my work useful. Any kind of support is very much appreciated!

About

🏷️ Rake tasks for incrementing gem versions and finding and updating version references in documentation and code.

Resources

Contributing

Stars

1 star

Watchers

1 watching

Forks

Releases

Contributors

Languages