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.
- checksums.yaml +4 -4
- data/.release-please-manifest.json +1 -1
- data/CHANGELOG.md +21 -0
- data/README.md +102 -6
- data/lib/generators/rails/worktrees/templates/bin/wt +3 -3
- data/lib/generators/rails/worktrees/templates/rails_worktrees.rb.tt +8 -0
- data/lib/generators/worktrees/install/install_generator.rb +59 -1
- data/lib/rails/worktrees/cli.rb +20 -3
- data/lib/rails/worktrees/command/in_repo_worktrees.rb +85 -0
- data/lib/rails/worktrees/command/name_picking.rb +2 -1
- data/lib/rails/worktrees/command/output.rb +15 -5
- data/lib/rails/worktrees/command/workspace_paths.rb +40 -9
- data/lib/rails/worktrees/command.rb +58 -9
- data/lib/rails/worktrees/configuration.rb +6 -2
- data/lib/rails/worktrees/pi_worktree_config_path.rb +44 -0
- data/lib/rails/worktrees/pi_worktree_config_updater.rb +123 -0
- data/lib/rails/worktrees/playwright_browser_check.rb +56 -0
- data/lib/rails/worktrees/post_create_runner.rb +45 -4
- data/lib/rails/worktrees/project_maintenance.rb +30 -3
- data/lib/rails/worktrees/teardown_runner.rb +84 -0
- data/lib/rails/worktrees/teardown_script.rb +115 -0
- data/lib/rails/worktrees/version.rb +1 -1
- data/lib/rails/worktrees.rb +4 -0
- data/sig/rails/worktrees.rbs +1 -0
- metadata +7 -1
|
@@ -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
|
data/lib/rails/worktrees.rb
CHANGED
|
@@ -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
|
|
data/sig/rails/worktrees.rbs
CHANGED
|
@@ -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.
|
|
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
|