diff --git a/.gitignore b/.gitignore index 1e8e95069cfe..b73f4b80d19a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ # Dependency installations and lockfiles -Gemfile.lock +/Gemfile.lock .bundle/ /*/vendor/bundle/ /*/lib/bundler/man/ diff --git a/seed_lockfiles.rb b/seed_lockfiles.rb new file mode 100644 index 000000000000..b2f9ed58558e --- /dev/null +++ b/seed_lockfiles.rb @@ -0,0 +1,112 @@ +#!/usr/bin/env ruby +# seed_lockfiles.rb +# +# Usage: +# ruby seed_lockfiles.rb --continue --size 15 --push + +require 'optparse' +require 'fileutils' + +options = { + batch_index: 0, + batch_size: 15, + auto_continue: false, + push: false +} + +OptionParser.new do |opts| + opts.banner = "Usage: ruby seed_lockfiles.rb [options]" + + opts.on("-s", "--size N", Integer, "Number of libraries in the batch (default: 15)") do |s| + options[:batch_size] = s + end + + opts.on("-i", "--index N", Integer, "Manual batch index (0-indexed)") do |i| + options[:batch_index] = i + end + + opts.on("-c", "--continue", "Automatically resume from the first unseeded library") do + options[:auto_continue] = true + end + + opts.on("-p", "--push", "Create a branch, commit (but do not push automatically to allow review)") do + options[:push] = true + end +end.parse! + +# 1. Discover all libraries +all_gems = Dir.glob("*/").map { |d| d.chomp('/') }.select do |dir| + File.exist?(File.join(dir, "#{dir}.gemspec")) +end.sort + +# 2. Filter unseeded & eligible libraries +unseeded_gems = all_gems.select do |dir| + lockfile_path = File.join(dir, "Gemfile.lock") + + # Skip if it already has a lockfile physically present + next false if File.exist?(lockfile_path) + + # Ask Git directly if this path is ignored + is_ignored = system("git check-ignore -q #{lockfile_path}") + + next false if is_ignored + + true +end + +puts "šŸ” [Seeder] Found #{unseeded_gems.size} eligible libraries (no lockfile AND not .gitignored)" + +if unseeded_gems.empty? + puts "šŸŽ‰ All eligible libraries have been seeded!" + exit 0 +end + +if options[:auto_continue] + puts "ā­ļø [Seeder] --continue flag detected." + options[:batch_index] = 0 +end + +start_offset = options[:batch_index] * options[:batch_size] +batch_gems = unseeded_gems[start_offset, options[:batch_size]] + +if batch_gems.nil? || batch_gems.empty? + puts "āŒ [Seeder] Batch index #{options[:batch_index]} is out of bounds!" + exit 1 +end + +branch_name = "chore/seed-lockfiles-#{Time.now.to_i}" +puts "šŸ“¦ [Seeder] Batch targets #{batch_gems.size} gems:" +batch_gems.each { |g| puts " - #{g}" } + +# 3. Create branch if requested +if options[:push] + system("git checkout main && git pull", exception: true) + system("git checkout -b #{branch_name}", exception: true) +end + +# 4. Generate lockfiles SEQUENTIALLY +batch_gems.each_with_index do |gem_name, i| + puts "ā³ [#{i + 1}/#{batch_gems.size}] Seeding #{gem_name}..." + + Dir.chdir(gem_name) do + # Run bundle lock synchronously + system("bundle lock", exception: true) + end + + # Stage the file sequentially so we safely avoid Git index lock conflicts + system("git add #{gem_name}/Gemfile.lock", exception: true) if options[:push] +end + +# 5. Commit if requested +if options[:push] + commit_msg = "chore(lockfiles): seed Gemfile.lock for #{batch_gems.size} libraries\n\nGems included in this batch:\n" + batch_gems.map { |g| "- #{g}" }.join("\n") + File.write(".git/COMMIT_EDITMSG", commit_msg) + system("git commit -F .git/COMMIT_EDITMSG", exception: true) + File.delete(".git/COMMIT_EDITMSG") + + puts "\nāœ… Batch committed to local branch #{branch_name}!" + puts "šŸ‘‰ You can now review the commit and push it:" + puts " git push -u origin #{branch_name}" +else + puts "\nāœ… Batch seeded locally. Run with --push to automatically branch and commit." +end