rails-worktrees 0.7.4 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,84 @@
1
+ require 'open3'
2
+ require 'tempfile'
3
+
4
+ module Rails
5
+ module Worktrees
6
+ # Runs teardown in the target app, without borrowing the caller's suffix or bundle.
7
+ class TeardownRunner
8
+ RUNTIME_KEYS = %w[PATH HOME USER LOGNAME SHELL LANG LANGUAGE LC_ALL LC_CTYPE TMPDIR TMP TEMP
9
+ GEM_HOME GEM_PATH LD_LIBRARY_PATH SSL_CERT_FILE SSL_CERT_DIR].freeze
10
+ RUBY_LOADER_KEYS = %w[RUBYOPT RUBYLIB].freeze
11
+ SUFFIX_KEY = 'WORKTREE_DATABASE_SUFFIX'.freeze
12
+ SCRIPT = File.expand_path('teardown_script.rb', __dir__).freeze
13
+
14
+ def initialize(target_dir:, env:, io:, use_current_env: false)
15
+ @target_dir = target_dir
16
+ @env = env.to_h
17
+ @stdout = io.fetch(:stdout)
18
+ @use_current_env = use_current_env
19
+ end
20
+
21
+ def call(dry_run: false)
22
+ env = runtime_env
23
+ validate_environment!(env)
24
+ Tempfile.create('rails-worktrees-teardown') do |plan|
25
+ run_teardown(env, plan.path, dry_run: dry_run)
26
+ end
27
+ rescue SystemCallError => e
28
+ raise Error, "Could not start teardown: #{e.message}"
29
+ end
30
+
31
+ private
32
+
33
+ def run_teardown(env, plan_path, dry_run:)
34
+ run(env.merge('RAILS_ENV' => 'test', 'RACK_ENV' => 'test'),
35
+ [rails_bin, 'runner', SCRIPT, '--export-test-configs', plan_path])
36
+ argv = [rails_bin, 'runner', SCRIPT, '--test-configs', plan_path]
37
+ argv << '--dry-run' if dry_run
38
+ run(env, argv)
39
+ end
40
+
41
+ def validate_environment!(env)
42
+ if env[SUFFIX_KEY].to_s.strip.empty?
43
+ raise Error, 'WORKTREE_DATABASE_SUFFIX must not be empty; refusing teardown.'
44
+ end
45
+ raise Error, "Expected #{@target_dir}/bin/rails for teardown." unless File.file?(rails_bin)
46
+ end
47
+
48
+ def run(env, argv)
49
+ Open3.popen2e(env, *argv, chdir: @target_dir, unsetenv_others: true) do |stdin, output, wait|
50
+ stdin.close
51
+ output.each_line { |line| @stdout.print(line) }
52
+ raise Error, 'Teardown failed; worktree and branch were kept.' unless wait.value.success?
53
+ end
54
+ end
55
+
56
+ def rails_bin = File.join(@target_dir, 'bin', 'rails')
57
+
58
+ def runtime_env
59
+ source = @use_current_env ? @env : @env.slice(*RUNTIME_KEYS)
60
+ env = source.reject { |key, _| key.start_with?('BUNDLE_', 'GIT_') || RUBY_LOADER_KEYS.include?(key) }
61
+ suffix = file_suffix
62
+ env[SUFFIX_KEY] = suffix unless suffix.nil?
63
+ env.merge('RAILS_ENV' => 'development', 'RACK_ENV' => 'development',
64
+ 'BUNDLE_GEMFILE' => File.join(@target_dir, 'Gemfile'))
65
+ end
66
+
67
+ def file_suffix
68
+ path = File.join(@target_dir, '.env')
69
+ return unless File.file?(path)
70
+
71
+ line = File.readlines(path, chomp: true).reverse.find do |entry|
72
+ entry.match?(/\A\s*(?:export\s+)?WORKTREE_DATABASE_SUFFIX\s*=/)
73
+ end
74
+ return unless line
75
+
76
+ value = line.split('=', 2).last.strip
77
+ match = value.match(/\A(?:'([^']*)'|"([^"]*)"|([^\s#'"$`]*))\s*(?:#.*)?\z/)
78
+ raise Error, 'Cannot safely read WORKTREE_DATABASE_SUFFIX from .env; use a literal value.' unless match
79
+
80
+ match.captures.compact.first
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,115 @@
1
+ require 'json'
2
+
3
+ # Executed by the target app's Rails runner, first in test, then in development.
4
+ module Rails
5
+ module Worktrees
6
+ # Validates the complete Rails database plan before applying any destructive effects.
7
+ class DatabaseTeardown
8
+ class SafetyError < StandardError; end
9
+
10
+ def call(test_plan:, export: false, dry_run: false)
11
+ suffix = ENV.fetch('WORKTREE_DATABASE_SUFFIX', '')
12
+ raise SafetyError, 'WORKTREE_DATABASE_SUFFIX must not be empty; refusing teardown.' if suffix.strip.empty?
13
+
14
+ return export_test_configs(test_plan, suffix) if export
15
+
16
+ development = database_configs('development')
17
+ databases = development + test_configs(test_plan, suffix)
18
+ validate_databases!(databases, suffix)
19
+ check_protection!(development)
20
+ stop_overmind(dry_run: dry_run)
21
+ drop_databases(databases, dry_run: dry_run)
22
+ end
23
+
24
+ private
25
+
26
+ def database_configs(environment)
27
+ unless defined?(ActiveRecord::Base)
28
+ raise SafetyError, 'ActiveRecord is required to inspect development/test databases.'
29
+ end
30
+
31
+ configs = ActiveRecord::Base.configurations.configs_for(env_name: environment)
32
+ raise SafetyError, "No database configurations found for #{environment}; refusing teardown." if configs.empty?
33
+
34
+ configs
35
+ end
36
+
37
+ def export_test_configs(path, suffix)
38
+ configs = database_configs('test')
39
+ validate_databases!(configs, suffix)
40
+ check_protection!(configs)
41
+ File.write(path, JSON.generate('suffix' => suffix, 'configs' => config_hash(configs)))
42
+ end
43
+
44
+ def test_configs(path, suffix)
45
+ plan = JSON.parse(File.read(path))
46
+ if plan['suffix'] != suffix
47
+ raise SafetyError,
48
+ 'Test environment changed WORKTREE_DATABASE_SUFFIX; refusing teardown.'
49
+ end
50
+
51
+ ActiveRecord::DatabaseConfigurations.new('test' => plan.fetch('configs')).configs_for(env_name: 'test')
52
+ end
53
+
54
+ def config_hash(configs)
55
+ configs.to_h { |config| [config.name, config.configuration_hash] }
56
+ end
57
+
58
+ def check_protection!(databases)
59
+ original = ActiveRecord::Base.configurations
60
+ existing = existing_databases(databases)
61
+ # Keep the resolved objects: rebuilding hashes would reapply environment URLs to explicit YAML URLs.
62
+ ActiveRecord::Base.configurations = existing
63
+ ENV.delete('DISABLE_DATABASE_ENVIRONMENT_CHECK')
64
+ existing.map(&:env_name).uniq.each do |environment|
65
+ ActiveRecord::Tasks::DatabaseTasks.check_protected_environments!(environment)
66
+ end
67
+ ensure
68
+ ActiveRecord::Base.configurations = original if original
69
+ end
70
+
71
+ def existing_databases(databases)
72
+ # SQLite opens would create absent files even in a dry-run. Absent files have no protected metadata.
73
+ databases.reject { |config| config.adapter == 'sqlite3' && !File.exist?(config.database) }
74
+ end
75
+
76
+ def validate_databases!(databases, suffix)
77
+ databases.each do |config|
78
+ # A suffix in a SQLite directory does not protect an unsuffixed database file.
79
+ next if File.basename(config.database.to_s).include?(suffix)
80
+
81
+ raise SafetyError,
82
+ "Database '#{config.database}' (#{config.env_name}/#{config.name}) does not contain " \
83
+ "WORKTREE_DATABASE_SUFFIX '#{suffix}'; refusing teardown."
84
+ end
85
+ end
86
+
87
+ def drop_databases(databases, dry_run:)
88
+ databases.each do |config|
89
+ puts "#{dry_run ? 'Would drop' : 'Dropping'} database '#{config.database}' " \
90
+ "(#{config.env_name}/#{config.name})"
91
+ ActiveRecord::Tasks::DatabaseTasks.drop(config) unless dry_run
92
+ end
93
+ end
94
+
95
+ def stop_overmind(dry_run:)
96
+ return unless File.exist?('.overmind.sock')
97
+
98
+ puts "#{dry_run ? 'Would stop' : 'Stopping'} Overmind at #{File.expand_path('.overmind.sock')}"
99
+ return if dry_run
100
+ return if system('overmind', 'quit', '--socket', '.overmind.sock')
101
+
102
+ raise SafetyError, 'Could not stop Overmind; databases were kept.'
103
+ end
104
+ end
105
+ end
106
+ end
107
+
108
+ begin
109
+ export = ARGV.first == '--export-test-configs'
110
+ Rails::Worktrees::DatabaseTeardown.new.call(test_plan: ARGV.fetch(1), export: export,
111
+ dry_run: ARGV.include?('--dry-run'))
112
+ rescue StandardError => e
113
+ warn "Teardown failed: #{e.message}"
114
+ exit 1
115
+ end
@@ -1,5 +1,5 @@
1
1
  module Rails
2
2
  module Worktrees
3
- VERSION = '0.7.4'.freeze
3
+ VERSION = '0.9.0'.freeze
4
4
  end
5
5
  end
@@ -6,7 +6,9 @@ require_relative 'worktrees/application_configuration'
6
6
  require_relative 'worktrees/mise_environment'
7
7
  require_relative 'worktrees/env_bootstrapper'
8
8
  require_relative 'worktrees/credential_key_linker'
9
+ require_relative 'worktrees/playwright_browser_check'
9
10
  require_relative 'worktrees/post_create_runner'
11
+ require_relative 'worktrees/teardown_runner'
10
12
  require_relative 'worktrees/command'
11
13
  require_relative 'worktrees/cli'
12
14
  require_relative 'worktrees/browser_command'
@@ -15,6 +17,8 @@ require_relative 'worktrees/initializer_updater'
15
17
  require_relative 'worktrees/procfile_updater'
16
18
  require_relative 'worktrees/mise_toml_updater'
17
19
  require_relative 'worktrees/puma_config_updater'
20
+ require_relative 'worktrees/pi_worktree_config_path'
21
+ require_relative 'worktrees/pi_worktree_config_updater'
18
22
  require_relative 'worktrees/project_configuration_loader'
19
23
  require_relative 'worktrees/project_maintenance'
20
24
 
@@ -8,6 +8,7 @@ module Rails
8
8
  class Configuration
9
9
  attr_accessor bootstrap_env: bool
10
10
  attr_accessor workspace_root: String?
11
+ attr_accessor worktrees_dir: String?
11
12
  attr_accessor branch_prefix: String
12
13
  attr_accessor dev_port_range: ::Range[Integer]
13
14
  attr_accessor name_sources_path: String
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rails-worktrees
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.4
4
+ version: 0.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Asjer Querido
@@ -61,6 +61,7 @@ files:
61
61
  - lib/rails/worktrees/command.rb
62
62
  - lib/rails/worktrees/command/environment_support.rb
63
63
  - lib/rails/worktrees/command/git_operations.rb
64
+ - lib/rails/worktrees/command/in_repo_worktrees.rb
64
65
  - lib/rails/worktrees/command/name_picking.rb
65
66
  - lib/rails/worktrees/command/output.rb
66
67
  - lib/rails/worktrees/command/post_create_support.rb
@@ -73,12 +74,17 @@ files:
73
74
  - lib/rails/worktrees/mise_environment.rb
74
75
  - lib/rails/worktrees/mise_toml_updater.rb
75
76
  - lib/rails/worktrees/names/cities.txt
77
+ - lib/rails/worktrees/pi_worktree_config_path.rb
78
+ - lib/rails/worktrees/pi_worktree_config_updater.rb
79
+ - lib/rails/worktrees/playwright_browser_check.rb
76
80
  - lib/rails/worktrees/post_create_runner.rb
77
81
  - lib/rails/worktrees/procfile_updater.rb
78
82
  - lib/rails/worktrees/project_configuration_loader.rb
79
83
  - lib/rails/worktrees/project_maintenance.rb
80
84
  - lib/rails/worktrees/puma_config_updater.rb
81
85
  - lib/rails/worktrees/railtie.rb
86
+ - lib/rails/worktrees/teardown_runner.rb
87
+ - lib/rails/worktrees/teardown_script.rb
82
88
  - lib/rails/worktrees/version.rb
83
89
  - mise.toml
84
90
  - release-please-config.json