From a06bad13f703665c82a3155c9412eb70b8889aa5 Mon Sep 17 00:00:00 2001 From: Torrey Payne <11740989+torreypayne@users.noreply.github.com> Date: Thu, 30 Jul 2026 18:44:18 +0000 Subject: [PATCH 1/5] chore(script): add automated threaded lockfile seeder --- seed_lockfiles.rb | 117 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100755 seed_lockfiles.rb diff --git a/seed_lockfiles.rb b/seed_lockfiles.rb new file mode 100755 index 000000000000..bb88afbc99b4 --- /dev/null +++ b/seed_lockfiles.rb @@ -0,0 +1,117 @@ +#!/usr/bin/env ruby +# seed_lockfiles.rb +# +# Usage: +# ruby seed_lockfiles.rb --continue --size 25 --push + +require 'optparse' +require 'fileutils' +require 'thread' + +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 libraries +unseeded_gems = all_gems.select do |dir| + !File.exist?(File.join(dir, "Gemfile.lock")) +end + +puts "šŸ” [Seeder] Found #{unseeded_gems.size} libraries without a Gemfile.lock" + +if unseeded_gems.empty? + puts "šŸŽ‰ All 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." + +# 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 CONCURRENTLY +# We use a thread-safe Queue and a Mutex to prevent terminal print interleaving +queue = Queue.new +batch_gems.each { |g| queue << g } +STDOUT_MUTEX = Mutex.new + +platforms = %w[ruby x86_64-linux x86_64-darwin arm64-darwin x64-mingw-ucrt x64-mingw32] +platform_args = platforms.map { |p| "--add-platform #{p}" }.join(" ") + +workers = 8.times.map do + Thread.new do + loop do + # Non-blocking pop; raises ThreadError when the queue is identically empty + gem_name = queue.pop(true) rescue break + + STDOUT_MUTEX.synchronize { puts "ā³ Seeding #{gem_name}..." } + + # Run bundle lock with all requested generic, Mac, and Windows platforms + system("cd #{gem_name} && bundle lock #{platform_args}", exception: true) + + system("git add #{gem_name}/Gemfile.lock", exception: true) if options[:push] + end + end +end + +# Await completion of all threads +workers.each(&:join) + +# 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 From 14b3e00331455ac5eeb77d34a786fd35d1a413f9 Mon Sep 17 00:00:00 2001 From: Torrey Payne <11740989+torreypayne@users.noreply.github.com> Date: Thu, 30 Jul 2026 19:03:34 +0000 Subject: [PATCH 2/5] chore(script): drop multithreading, require Gemfile, and apply gitignore filters --- seed_lockfiles.rb | 56 +++++++++++++++++++++++------------------------ 1 file changed, 27 insertions(+), 29 deletions(-) mode change 100755 => 100644 seed_lockfiles.rb diff --git a/seed_lockfiles.rb b/seed_lockfiles.rb old mode 100755 new mode 100644 index bb88afbc99b4..996b2f4d7204 --- a/seed_lockfiles.rb +++ b/seed_lockfiles.rb @@ -2,11 +2,10 @@ # seed_lockfiles.rb # # Usage: -# ruby seed_lockfiles.rb --continue --size 25 --push +# ruby seed_lockfiles.rb --continue --size 15 --push require 'optparse' require 'fileutils' -require 'thread' options = { batch_index: 0, @@ -40,15 +39,25 @@ File.exist?(File.join(dir, "#{dir}.gemspec")) end.sort -# 2. Filter unseeded libraries +# 2. Filter unseeded & eligible libraries unseeded_gems = all_gems.select do |dir| - !File.exist?(File.join(dir, "Gemfile.lock")) + # Skip if it already has a lockfile physically present + next false if File.exist?(File.join(dir, "Gemfile.lock")) + + # Skip if Gemfile.lock is still listed inside this gem's .gitignore + gitignore_path = File.join(dir, ".gitignore") + if File.exist?(gitignore_path) + is_ignored = File.readlines(gitignore_path).any? { |line| line.strip == "Gemfile.lock" } + next false if is_ignored + end + + true end -puts "šŸ” [Seeder] Found #{unseeded_gems.size} libraries without a Gemfile.lock" +puts "šŸ” [Seeder] Found #{unseeded_gems.size} eligible libraries (no lockfile AND not .gitignored)" if unseeded_gems.empty? - puts "šŸŽ‰ All libraries have been seeded!" + puts "šŸŽ‰ All eligible libraries have been seeded!" exit 0 end @@ -66,7 +75,8 @@ end branch_name = "chore/seed-lockfiles-#{Time.now.to_i}" -puts "šŸ“¦ [Seeder] Batch targets #{batch_gems.size} gems." +puts "šŸ“¦ [Seeder] Batch targets #{batch_gems.size} gems:" +batch_gems.each { |g| puts " - #{g}" } # 3. Create branch if requested if options[:push] @@ -74,33 +84,21 @@ system("git checkout -b #{branch_name}", exception: true) end -# 4. Generate lockfiles CONCURRENTLY -# We use a thread-safe Queue and a Mutex to prevent terminal print interleaving -queue = Queue.new -batch_gems.each { |g| queue << g } -STDOUT_MUTEX = Mutex.new - +# 4. Generate lockfiles SEQUENTIALLY platforms = %w[ruby x86_64-linux x86_64-darwin arm64-darwin x64-mingw-ucrt x64-mingw32] platform_args = platforms.map { |p| "--add-platform #{p}" }.join(" ") -workers = 8.times.map do - Thread.new do - loop do - # Non-blocking pop; raises ThreadError when the queue is identically empty - gem_name = queue.pop(true) rescue break - - STDOUT_MUTEX.synchronize { puts "ā³ Seeding #{gem_name}..." } - - # Run bundle lock with all requested generic, Mac, and Windows platforms - system("cd #{gem_name} && bundle lock #{platform_args}", exception: true) - - system("git add #{gem_name}/Gemfile.lock", exception: true) if options[:push] - end +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 #{platform_args}", exception: true) end -end -# Await completion of all threads -workers.each(&:join) + # 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] From 1943d7b3d28da209e985469a86219ee3ee706e24 Mon Sep 17 00:00:00 2001 From: Torrey Payne <11740989+torreypayne@users.noreply.github.com> Date: Thu, 30 Jul 2026 22:39:06 +0000 Subject: [PATCH 3/5] chore(script): drop explicit bundle lock platforms --- seed_lockfiles.rb | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/seed_lockfiles.rb b/seed_lockfiles.rb index 996b2f4d7204..2d6f08b6c116 100644 --- a/seed_lockfiles.rb +++ b/seed_lockfiles.rb @@ -85,15 +85,12 @@ end # 4. Generate lockfiles SEQUENTIALLY -platforms = %w[ruby x86_64-linux x86_64-darwin arm64-darwin x64-mingw-ucrt x64-mingw32] -platform_args = platforms.map { |p| "--add-platform #{p}" }.join(" ") - 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 #{platform_args}", exception: true) + system("bundle lock", exception: true) end # Stage the file sequentially so we safely avoid Git index lock conflicts From c8c42b11898d5ec40ed256b86352d89c1f7d1809 Mon Sep 17 00:00:00 2001 From: Torrey Payne <11740989+torreypayne@users.noreply.github.com> Date: Thu, 30 Jul 2026 23:05:51 +0000 Subject: [PATCH 4/5] chore(script): use native git check-ignore for robust filtering --- seed_lockfiles.rb | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/seed_lockfiles.rb b/seed_lockfiles.rb index 2d6f08b6c116..b2f9ed58558e 100644 --- a/seed_lockfiles.rb +++ b/seed_lockfiles.rb @@ -41,15 +41,15 @@ # 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?(File.join(dir, "Gemfile.lock")) + next false if File.exist?(lockfile_path) - # Skip if Gemfile.lock is still listed inside this gem's .gitignore - gitignore_path = File.join(dir, ".gitignore") - if File.exist?(gitignore_path) - is_ignored = File.readlines(gitignore_path).any? { |line| line.strip == "Gemfile.lock" } - next false if is_ignored - end + # Ask Git directly if this path is ignored + is_ignored = system("git check-ignore -q #{lockfile_path}") + + next false if is_ignored true end From 12b0460907175d07807601bc52c5fe4cf79fc1f5 Mon Sep 17 00:00:00 2001 From: Torrey Payne <11740989+torreypayne@users.noreply.github.com> Date: Thu, 30 Jul 2026 23:09:00 +0000 Subject: [PATCH 5/5] chore: strictly bind root Gemfile.lock ignore to root directory --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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/