0ead67f85b
* Bump the development-dependencies group across 1 directory with 3 updates Bumps the development-dependencies group with 3 updates in the / directory: [brakeman](https://github.com/presidentbeef/brakeman), [faker](https://github.com/faker-ruby/faker) and [selenium-webdriver](https://github.com/SeleniumHQ/selenium). Updates `brakeman` from 7.1.2 to 8.0.1 - [Release notes](https://github.com/presidentbeef/brakeman/releases) - [Changelog](https://github.com/presidentbeef/brakeman/blob/main/CHANGES.md) - [Commits](https://github.com/presidentbeef/brakeman/compare/v7.1.2...v8.0.1) Updates `faker` from 3.5.3 to 3.6.0 - [Release notes](https://github.com/faker-ruby/faker/releases) - [Changelog](https://github.com/faker-ruby/faker/blob/main/CHANGELOG.md) - [Commits](https://github.com/faker-ruby/faker/compare/v3.5.3...v3.6.0) Updates `selenium-webdriver` from 4.39.0 to 4.40.0 - [Release notes](https://github.com/SeleniumHQ/selenium/releases) - [Changelog](https://github.com/SeleniumHQ/selenium/blob/trunk/rb/CHANGES) - [Commits](https://github.com/SeleniumHQ/selenium/compare/selenium-4.39.0...selenium-4.40.0) --- updated-dependencies: - dependency-name: brakeman dependency-version: 8.0.1 dependency-type: direct:development update-type: version-update:semver-major dependency-group: development-dependencies - dependency-name: faker dependency-version: 3.6.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: development-dependencies - dependency-name: selenium-webdriver dependency-version: 4.40.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: development-dependencies ... Signed-off-by: dependabot[bot] <support@github.com> * Auto-sync Gemfile.saas.lock on Dependabot PRs Dependabot can't update custom-named Gemfiles, so Gemfile.saas.lock drifts whenever it bumps shared gems in Gemfile.lock. Add `bin/bundle-drift forward` to push oss lockfile changes into the saas lockfile (the reverse of `correct`), and a GitHub Actions workflow that runs it automatically on Dependabot bundler branches. * Update brakeman ignore fingerprint for 8.0.1 Brakeman 8.0.1 changed how it computes warning fingerprints, so the existing ignore entry for the safe_constantize warning in Notifier became obsolete. Update to the new fingerprint. --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Jeremy Daer <jeremy@37signals.com>
182 lines
5.3 KiB
Ruby
Executable File
182 lines
5.3 KiB
Ruby
Executable File
#!/usr/bin/env ruby
|
|
# Checks that Gemfile.lock and Gemfile.saas.lock are in sync for shared dependencies.
|
|
# Since Gemfile.saas evals Gemfile, shared gems should have identical versions.
|
|
#
|
|
# Usage:
|
|
# bin/bundle-drift [check] # check for drift (default subcommand)
|
|
# bin/bundle-drift correct # restore alignment (Gemfile.saas.lock is authoritative)
|
|
# bin/bundle-drift forward # push Gemfile.lock changes into Gemfile.saas.lock
|
|
require "bundler"
|
|
require "fileutils"
|
|
|
|
GEMFILE_LOCK = "Gemfile.lock"
|
|
GEMFILE_SAAS_LOCK = "Gemfile.saas.lock"
|
|
|
|
class GemfileDriftChecker
|
|
def initialize
|
|
@oss_lockfile = parse_lockfile(GEMFILE_LOCK)
|
|
@saas_lockfile = parse_lockfile(GEMFILE_SAAS_LOCK)
|
|
end
|
|
|
|
def check
|
|
find_drift.tap do
|
|
report it
|
|
end
|
|
end
|
|
|
|
private
|
|
def parse_lockfile(path)
|
|
Bundler::LockfileParser.new(File.read(path))
|
|
end
|
|
|
|
def find_drift
|
|
oss_specs, saas_specs = specs_hash(@oss_lockfile), specs_hash(@saas_lockfile)
|
|
shared_gems = oss_specs.keys & saas_specs.keys
|
|
|
|
shared_gems.filter_map do |name|
|
|
oss_version, saas_version = oss_specs[name], saas_specs[name]
|
|
if oss_version != saas_version
|
|
{ name: name, oss: oss_version, saas: saas_version }
|
|
end
|
|
end.sort_by { |d| d[:name] }
|
|
end
|
|
|
|
def specs_hash(lockfile)
|
|
lockfile.specs.to_h { |spec| [ spec.name, spec.version.to_s ] }
|
|
end
|
|
|
|
def report(drift)
|
|
if drift.empty?
|
|
puts "✓ Gemfile.lock and Gemfile.saas.lock are in sync"
|
|
else
|
|
puts "✗ Gemfile lock files have drifted!\n\n"
|
|
|
|
name_width = [ drift.map { |d| d[:name].length }.max, "Gem".length ].max
|
|
oss_width = [ drift.map { |d| d[:oss].length }.max, "Gemfile.lock".length ].max
|
|
saas_width = [ drift.map { |d| d[:saas].length }.max, "Gemfile.saas.lock".length ].max
|
|
|
|
puts " #{"Gem".ljust(name_width)} #{"Gemfile.lock".ljust(oss_width)} Gemfile.saas.lock"
|
|
puts " #{"-" * name_width} #{"-" * oss_width} #{"-" * saas_width}"
|
|
|
|
drift.each do |d|
|
|
puts " #{d[:name].ljust(name_width)} #{d[:oss].ljust(oss_width)} #{d[:saas]}"
|
|
end
|
|
|
|
puts "\nRun 'bin/bundle-drift correct' to restore alignment."
|
|
end
|
|
end
|
|
end
|
|
|
|
class GemfileDriftCorrector
|
|
def correct
|
|
drift = GemfileDriftChecker.new.check
|
|
return puts "\nNothing to correct." if drift.empty?
|
|
|
|
puts "\nRestoring alignment (Gemfile.saas.lock is authoritative)...\n\n"
|
|
|
|
# Save original for diff
|
|
original_content = File.read(GEMFILE_LOCK)
|
|
|
|
# Seed Gemfile.lock with Gemfile.saas.lock - Bundler will use these as version hints
|
|
FileUtils.cp(GEMFILE_SAAS_LOCK, GEMFILE_LOCK)
|
|
|
|
# Re-lock: Bundler prunes SaaS-only gems while preserving shared versions
|
|
puts "▸ Re-locking Gemfile (seeded from Gemfile.saas.lock)"
|
|
unless system("BUNDLE_GEMFILE=Gemfile bundle lock")
|
|
File.write(GEMFILE_LOCK, original_content)
|
|
abort("Failed to lock Gemfile. Restored original.")
|
|
end
|
|
|
|
puts "\n▸ Verifying alignment"
|
|
new_drift = GemfileDriftChecker.new.check
|
|
|
|
if new_drift.empty?
|
|
puts "\n✓ Lock files are now in sync"
|
|
show_diff(original_content, File.read(GEMFILE_LOCK))
|
|
else
|
|
puts "\n✗ Lock files still have drift after correction."
|
|
puts " Bundler couldn't resolve to matching versions."
|
|
puts " Restoring original Gemfile.lock."
|
|
File.write(GEMFILE_LOCK, original_content)
|
|
exit 1
|
|
end
|
|
end
|
|
|
|
private
|
|
def show_diff(original, corrected)
|
|
require "tempfile"
|
|
|
|
Tempfile.create("gemfile-lock-original") do |f|
|
|
f.write(original)
|
|
f.flush
|
|
|
|
diff = `diff -u #{f.path} #{GEMFILE_LOCK} 2>/dev/null`
|
|
unless diff.empty?
|
|
puts "\nChanges made to Gemfile.lock:"
|
|
puts diff
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
class GemfileDriftForwarder
|
|
def forward
|
|
drift = GemfileDriftChecker.new.check
|
|
return puts "\nNothing to forward." if drift.empty?
|
|
|
|
puts "\nForwarding Gemfile.lock versions into Gemfile.saas.lock...\n\n"
|
|
|
|
original_content = File.read(GEMFILE_SAAS_LOCK)
|
|
patched_content = original_content.dup
|
|
|
|
drift.each do |d|
|
|
old_entry = "#{d[:name]} (#{d[:saas]})"
|
|
new_entry = "#{d[:name]} (#{d[:oss]})"
|
|
puts " #{old_entry} → #{new_entry}"
|
|
patched_content.gsub!(old_entry, new_entry)
|
|
end
|
|
|
|
File.write(GEMFILE_SAAS_LOCK, patched_content)
|
|
|
|
puts "\n▸ Verifying alignment"
|
|
new_drift = GemfileDriftChecker.new.check
|
|
|
|
if new_drift.empty?
|
|
puts "\n✓ Lock files are now in sync"
|
|
show_diff(original_content, patched_content)
|
|
else
|
|
puts "\n✗ Lock files still have drift after forwarding."
|
|
puts " Restoring original Gemfile.saas.lock."
|
|
File.write(GEMFILE_SAAS_LOCK, original_content)
|
|
exit 1
|
|
end
|
|
end
|
|
|
|
private
|
|
def show_diff(original, patched)
|
|
require "tempfile"
|
|
|
|
Tempfile.create("gemfile-saas-lock-original") do |f|
|
|
f.write(original)
|
|
f.flush
|
|
|
|
diff = `diff -u #{f.path} #{GEMFILE_SAAS_LOCK} 2>/dev/null`
|
|
unless diff.empty?
|
|
puts "\nChanges made to Gemfile.saas.lock:"
|
|
puts diff
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
case command = ARGV[0] || "check"
|
|
when "check"
|
|
exit 1 unless GemfileDriftChecker.new.check.empty?
|
|
when "correct"
|
|
GemfileDriftCorrector.new.correct
|
|
when "forward"
|
|
GemfileDriftForwarder.new.forward
|
|
else
|
|
abort "Usage: bin/bundle-drift [check|correct|forward]"
|
|
end
|