rails-worktrees 0.1.0 → 0.1.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fc7ddb67fbc5fde18c04ab164229207c6f54284dc4bb5c1fb955f40544706cbb
4
- data.tar.gz: a4005286b6c1dbad990d94ca3999b1490f2532c981fa8e5a7fb9b5768e4f30d6
3
+ metadata.gz: 5c6da0f27411c99c20b5ab3496a4ac0672ba6a3833e429b33b53b5884d3a3c76
4
+ data.tar.gz: cb144472bad91868165d8a37de3ba12f0675255aabc2fa3df0d9e8d6c3d736ff
5
5
  SHA512:
6
- metadata.gz: 8eef6c43ad39e5fc38c3dbdc10fc2618fc07f0ccd65c5c25bef94ae3991aeff143687ee45a37983846cc7ec8fc4c031d39b2b4cd6abcdd37d31243bb705a24a0
7
- data.tar.gz: dfdeeb6c73a81e8c3c8147883b5ca752b79ff4a63e318db409792c6723c4fd84ae3f9effc93edc1d456f74fa22f326e954ac7239d044a27709436d1e462810b6
6
+ metadata.gz: 7d959fde216a23fa191dca31294ff776b0091e3f230db9191a87890e8736217c3182b773e2950b18cbc151c20f833b238b7f3a07e618a5a4aa997bad5191760e
7
+ data.tar.gz: 7abecada6524724a90bd993b3975ce8b20a05e5123c0a9e2f6a2e8bc3d5435d04590a2ba3df83561ec41004d03d83627d48659866290b78b5ce4ed3bebbabfb1
@@ -1 +1 @@
1
- {".":"0.1.0"}
1
+ {".":"0.1.1"}
data/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.1.1](https://github.com/asjer/rails-worktrees/compare/v0.1.0...v0.1.1) (2026-03-30)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * **bin/wt:** remove unnecessary frozen_string_literal comment ([e3752c8](https://github.com/asjer/rails-worktrees/commit/e3752c86e4003b80e401253e3c07bd0107ba1514))
9
+ * **ci:** update gem installation steps to prevent `Gemfile.lock` freeze ([e3c75e8](https://github.com/asjer/rails-worktrees/commit/e3c75e85528e575185adb417b6bd9efa995087f1))
10
+ * **frozen-string-literal:** remove unnecessary frozen_string_literal comments ([a8583d2](https://github.com/asjer/rails-worktrees/commit/a8583d2bab1097a6c6a1906a3d6f6092d7b8d867))
11
+ * **generator:** update generator command to use shorter namespace ([cef728e](https://github.com/asjer/rails-worktrees/commit/cef728e3642c36da054f0d53ab5d8a932b2511c4))
12
+ * **installation:** show setup instructions on boot when generator hasn't run ([af31a3f](https://github.com/asjer/rails-worktrees/commit/af31a3f3849408cf7098e2a4e90a9246c89b0bbe))
13
+
3
14
  ## 0.1.0 (2026-03-30)
4
15
 
5
16
 
data/README.md CHANGED
@@ -12,7 +12,7 @@
12
12
 
13
13
  ```bash
14
14
  bundle add rails-worktrees
15
- bin/rails generate rails:worktrees:install
15
+ bin/rails generate worktrees:install
16
16
  ```
17
17
 
18
18
  The installer adds:
@@ -141,7 +141,7 @@ This smoke test:
141
141
 
142
142
  - creates a temporary Rails app from a compatible Rails version
143
143
  - installs `rails-worktrees` from the current checkout path
144
- - runs `bin/rails generate rails:worktrees:install`
144
+ - runs `bin/rails generate worktrees:install`
145
145
  - verifies `bin/wt`, the generated initializer, the Procfile example, `config/database.yml` patching, and worktree `.env` bootstrapping
146
146
  - creates a temporary bare `origin` and confirms `bin/wt smoke-branch` creates a real worktree
147
147
 
data/Rakefile CHANGED
@@ -1,5 +1,3 @@
1
- # frozen_string_literal: true
2
-
3
1
  require 'bundler/gem_tasks'
4
2
  require 'rspec/core/rake_task'
5
3
 
data/exe/wt CHANGED
@@ -1,6 +1,4 @@
1
1
  #!/usr/bin/env ruby
2
- # frozen_string_literal: true
3
-
4
2
  lib = File.expand_path('../lib', __dir__)
5
3
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
6
4
 
@@ -1,5 +1,3 @@
1
- # frozen_string_literal: true
2
-
3
1
  module Rails
4
2
  module Worktrees
5
3
  module Generators
@@ -1,6 +1,4 @@
1
1
  #!/usr/bin/env ruby
2
- # frozen_string_literal: true
3
-
4
2
  ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
5
3
 
6
4
  require 'bundler/setup'
@@ -1,5 +1,3 @@
1
- # frozen_string_literal: true
2
-
3
1
  Rails::Worktrees.configure do |config|
4
2
  <% if options['conductor'] -%>
5
3
  config.workspace_root = <%= conductor_workspace_root %>
@@ -0,0 +1,134 @@
1
+ require 'open3'
2
+ require 'rails/generators'
3
+
4
+ require_relative '../../rails/worktrees/mise_follow_up'
5
+ require_relative '../../../rails/worktrees/database_config_updater'
6
+
7
+ module Worktrees
8
+ module Generators
9
+ # Installs the wt wrapper, configuration, and safe database.yml updates.
10
+ class InstallGenerator < ::Rails::Generators::Base
11
+ include ::Rails::Worktrees::Generators::MiseFollowUp
12
+
13
+ namespace 'worktrees:install'
14
+ desc 'Installs bin/wt, a Rails::Worktrees initializer, and updates config/database.yml when safe.'
15
+ source_root File.expand_path('../../rails/worktrees/templates', __dir__)
16
+ class_option :conductor, type: :boolean, default: false,
17
+ desc: 'Configure the installer for ~/Sites/conductor/workspaces'
18
+
19
+ FOLLOW_UP_TEMPLATE = <<~TEXT.freeze
20
+ ============================================
21
+ rails-worktrees installed successfully! 🚂
22
+
23
+ Installed:
24
+ %<installed>s
25
+
26
+ Get started:
27
+ $ bin/wt
28
+ $ bin/wt my-feature
29
+
30
+ Configure:
31
+ config/initializers/rails_worktrees.rb
32
+ %<notes>s
33
+ ============================================
34
+ TEXT
35
+
36
+ def create_bin_wrapper
37
+ template('bin/wt', 'bin/wt')
38
+ chmod('bin/wt', 0o755)
39
+ end
40
+
41
+ def create_initializer
42
+ template('rails_worktrees.rb.tt', 'config/initializers/rails_worktrees.rb')
43
+ end
44
+
45
+ def create_procfile_worktree_example
46
+ template('Procfile.dev.worktree.example.tt', 'Procfile.dev.worktree.example')
47
+ end
48
+
49
+ def update_database_configuration
50
+ unless File.exist?(database_config_path)
51
+ say_status(:skip, 'config/database.yml not found', :yellow)
52
+ @database_outcome = :not_found
53
+ return
54
+ end
55
+
56
+ result = database_update_result
57
+ @database_outcome = result.changed? ? :updated : :identical
58
+ announce_database_update(result)
59
+ end
60
+
61
+ def verify_installation
62
+ if git_repo?
63
+ say_status(:ok, 'git repository detected', :green)
64
+ else
65
+ say_status(:warning, 'run inside a git repository for bin/wt to work', :yellow)
66
+ end
67
+ end
68
+
69
+ def show_follow_up
70
+ say(follow_up_message)
71
+ end
72
+
73
+ private
74
+
75
+ def database_config_path
76
+ File.join(destination_root, 'config/database.yml')
77
+ end
78
+
79
+ def database_update_result
80
+ result = ::Rails::Worktrees::DatabaseConfigUpdater.new(
81
+ content: File.read(database_config_path)
82
+ ).call
83
+
84
+ File.write(database_config_path, result.content) if result.changed?
85
+
86
+ result
87
+ end
88
+
89
+ def follow_up_message
90
+ "\n#{format(FOLLOW_UP_TEMPLATE, installed: installed_items_text, notes: follow_up_notes_text)}"
91
+ end
92
+
93
+ def installed_items_text
94
+ items = [
95
+ ' • bin/wt',
96
+ ' • config/initializers/rails_worktrees.rb',
97
+ ' • Procfile.dev.worktree.example'
98
+ ]
99
+ items << database_follow_up_line if database_follow_up_line
100
+ items.join("\n")
101
+ end
102
+
103
+ def database_follow_up_line
104
+ case @database_outcome
105
+ when :updated
106
+ ' • config/database.yml (updated with WORKTREE_DATABASE_SUFFIX)'
107
+ when :not_found
108
+ ' • config/database.yml was not found — see README for manual setup'
109
+ end
110
+ end
111
+
112
+ def announce_database_update(result)
113
+ status = result.changed? ? :update : :identical
114
+ color = result.changed? ? :green : :blue
115
+
116
+ say_status(status, 'config/database.yml', color)
117
+ result.messages.each { |message| say(message) }
118
+ end
119
+
120
+ def git_repo?
121
+ _stdout_str, _stderr_str, status = Open3.capture3(
122
+ 'git', 'rev-parse', '--is-inside-work-tree', chdir: destination_root
123
+ )
124
+ status.success?
125
+ rescue Errno::ENOENT
126
+ false
127
+ end
128
+
129
+ def conductor_workspace_root
130
+ "File.expand_path('~/Sites/conductor/workspaces')"
131
+ end
132
+ end
133
+ end
134
+ end
@@ -1,5 +1,3 @@
1
- # frozen_string_literal: true
2
-
3
1
  module Rails
4
2
  module Worktrees
5
3
  # Shell entrypoint for the wt executable.
@@ -1,5 +1,3 @@
1
- # frozen_string_literal: true
2
-
3
1
  module Rails
4
2
  module Worktrees
5
3
  class Command
@@ -1,5 +1,3 @@
1
- # frozen_string_literal: true
2
-
3
1
  require 'open3'
4
2
 
5
3
  module Rails
@@ -1,5 +1,3 @@
1
- # frozen_string_literal: true
2
-
3
1
  module Rails
4
2
  module Worktrees
5
3
  class Command
@@ -1,5 +1,3 @@
1
- # frozen_string_literal: true
2
-
3
1
  module Rails
4
2
  module Worktrees
5
3
  class Command
@@ -1,5 +1,3 @@
1
- # frozen_string_literal: true
2
-
3
1
  module Rails
4
2
  module Worktrees
5
3
  class Command
@@ -1,5 +1,3 @@
1
- # frozen_string_literal: true
2
-
3
1
  require 'fileutils'
4
2
  require_relative 'command/environment_support'
5
3
  require_relative 'command/git_operations'
@@ -1,11 +1,9 @@
1
- # frozen_string_literal: true
2
-
3
1
  module Rails
4
2
  module Worktrees
5
3
  # Stores application-level settings for the wt command.
6
4
  class Configuration
7
5
  DEFAULT_BOOTSTRAP_ENV = true
8
- DEFAULT_BRANCH_PREFIX = '🚂'
6
+ DEFAULT_BRANCH_PREFIX = '🚂'.freeze
9
7
  DEFAULT_DEV_PORT_RANGE = (3000..3999)
10
8
  DEFAULT_USED_NAMES_DIRECTORY = File.join(
11
9
  ENV.fetch('XDG_STATE_HOME', File.join(Dir.home, '.local/state')),
@@ -1,5 +1,3 @@
1
- # frozen_string_literal: true
2
-
3
1
  module Rails
4
2
  module Worktrees
5
3
  # Safely patches common database.yml layouts for worktree suffixes.
@@ -10,7 +8,7 @@ module Rails
10
8
  end
11
9
  end
12
10
 
13
- SUFFIX_TEMPLATE = "<%= ENV.fetch('WORKTREE_DATABASE_SUFFIX', '') %>"
11
+ SUFFIX_TEMPLATE = "<%= ENV.fetch('WORKTREE_DATABASE_SUFFIX', '') %>".freeze
14
12
  SUPPORTED_ENVIRONMENTS = %w[development test].freeze
15
13
  DATABASE_LINE_PATTERN = /\A(\s*database:\s*)(.+?)(\s*(?:#.*)?\n?)\z/
16
14
  SECTION_PATTERN = /\A([A-Za-z0-9_]+):(?:\s|$)/
@@ -1,5 +1,3 @@
1
- # frozen_string_literal: true
2
-
3
1
  require 'pathname'
4
2
  require 'zlib'
5
3
 
@@ -14,7 +12,7 @@ module Rails
14
12
  attr_accessor :values
15
13
  end
16
14
 
17
- ENV_FILE_NAME = '.env'
15
+ ENV_FILE_NAME = '.env'.freeze
18
16
 
19
17
  def initialize(target_dir:, worktree_name:, configuration:)
20
18
  @target_dir = target_dir
@@ -1,8 +1,10 @@
1
- # frozen_string_literal: true
2
-
3
1
  module Rails
4
2
  module Worktrees
3
+ # Hooks install guidance into Rails boot when the generator has not run yet.
5
4
  class Railtie < ::Rails::Railtie
5
+ initializer 'rails_worktrees.installation_hint' do
6
+ Rails::Worktrees.warn_about_missing_installation
7
+ end
6
8
  end
7
9
  end
8
10
  end
@@ -1,7 +1,5 @@
1
- # frozen_string_literal: true
2
-
3
1
  module Rails
4
2
  module Worktrees
5
- VERSION = '0.1.0'
3
+ VERSION = '0.1.1'.freeze
6
4
  end
7
5
  end
@@ -1,4 +1,4 @@
1
- # frozen_string_literal: true
1
+ require 'pathname'
2
2
 
3
3
  require_relative 'worktrees/version'
4
4
  require_relative 'worktrees/configuration'
@@ -12,6 +12,13 @@ module Rails
12
12
  module Worktrees
13
13
  class Error < StandardError; end
14
14
 
15
+ INSTALL_GENERATOR_COMMAND = 'bin/rails generate worktrees:install'.freeze
16
+ INSTALL_GENERATOR_NAMES = %w[worktrees:install].freeze
17
+ REQUIRED_INSTALLATION_PATHS = [
18
+ 'bin/wt',
19
+ 'config/initializers/rails_worktrees.rb'
20
+ ].freeze
21
+
15
22
  class << self
16
23
  def configuration
17
24
  @configuration ||= Configuration.new
@@ -25,6 +32,95 @@ module Rails
25
32
  def reset_configuration!
26
33
  @configuration = Configuration.new
27
34
  end
35
+
36
+ def installation_complete?(root = resolve_root)
37
+ return false unless root
38
+
39
+ required_installation_paths(root).all?(&:exist?)
40
+ end
41
+
42
+ def missing_installation_message(root: resolve_root)
43
+ return generic_missing_installation_message unless root
44
+
45
+ detailed_missing_installation_message(normalize_root(root))
46
+ end
47
+
48
+ def warn_about_missing_installation(root: resolve_root, stderr: $stderr, argv: ARGV)
49
+ return unless missing_installation_warning_needed?(root: root, argv: argv)
50
+
51
+ stderr.puts(missing_installation_message(root: root))
52
+ end
53
+
54
+ private
55
+
56
+ def missing_installation_warning_needed?(root:, argv:)
57
+ return false unless root
58
+ return false if installation_complete?(root)
59
+ return false if install_generator_invocation?(argv)
60
+
61
+ true
62
+ end
63
+
64
+ def install_generator_invocation?(argv)
65
+ normalized_args = Array(argv).map(&:to_s)
66
+ generator_commands = %w[generate g]
67
+
68
+ INSTALL_GENERATOR_NAMES.any? do |generator_name|
69
+ normalized_args.include?(generator_name) ||
70
+ normalized_args.each_cons(2).any? do |left, right|
71
+ generator_commands.include?(left) && right == generator_name
72
+ end
73
+ end
74
+ end
75
+
76
+ def required_installation_paths(root)
77
+ root_path = normalize_root(root)
78
+
79
+ REQUIRED_INSTALLATION_PATHS.map { |relative_path| root_path.join(relative_path) }
80
+ end
81
+
82
+ def generic_missing_installation_message
83
+ <<~MSG
84
+
85
+ rails-worktrees is in your bundle, but the app installer has not run yet.
86
+
87
+ Run:
88
+ $ #{INSTALL_GENERATOR_COMMAND}
89
+
90
+ Docs: https://github.com/asjer/rails-worktrees
91
+ MSG
92
+ end
93
+
94
+ def detailed_missing_installation_message(root_path)
95
+ <<~MSG
96
+
97
+ rails-worktrees is in your bundle, but the app installer has not run yet.
98
+
99
+ Run:
100
+ $ #{INSTALL_GENERATOR_COMMAND}
101
+
102
+ Missing expected files under #{root_path}:
103
+ #{missing_installation_items_text(root_path)}
104
+
105
+ Docs: https://github.com/asjer/rails-worktrees
106
+ MSG
107
+ end
108
+
109
+ def missing_installation_items_text(root)
110
+ required_installation_paths(root).reject(&:exist?).map do |path|
111
+ " • #{path.relative_path_from(root)}"
112
+ end.join("\n")
113
+ end
114
+
115
+ def resolve_root
116
+ return unless defined?(Rails) && Rails.respond_to?(:root)
117
+
118
+ Rails.root
119
+ end
120
+
121
+ def normalize_root(root)
122
+ root.is_a?(Pathname) ? root : Pathname(root)
123
+ end
28
124
  end
29
125
  end
30
126
  end
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.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Asjer Querido
@@ -45,11 +45,11 @@ files:
45
45
  - Rakefile
46
46
  - exe/wt
47
47
  - lefthook.yml
48
- - lib/generators/rails/worktrees/install_generator.rb
49
48
  - lib/generators/rails/worktrees/mise_follow_up.rb
50
49
  - lib/generators/rails/worktrees/templates/Procfile.dev.worktree.example.tt
51
50
  - lib/generators/rails/worktrees/templates/bin/wt
52
51
  - lib/generators/rails/worktrees/templates/rails_worktrees.rb.tt
52
+ - lib/generators/worktrees/install/install_generator.rb
53
53
  - lib/rails/worktrees.rb
54
54
  - lib/rails/worktrees/cli.rb
55
55
  - lib/rails/worktrees/command.rb
@@ -77,7 +77,7 @@ metadata:
77
77
  rubygems_mfa_required: 'true'
78
78
  post_install_message: "\n============================================\n Thank you
79
79
  for installing rails-worktrees! \U0001F389\n\n Run the installer:\n $ bin/rails
80
- generate rails:worktrees:install\n\n Docs: https://github.com/asjer/rails-worktrees\n============================================\n\n"
80
+ generate worktrees:install\n\n Docs: https://github.com/asjer/rails-worktrees\n============================================\n\n"
81
81
  rdoc_options: []
82
82
  require_paths:
83
83
  - lib
@@ -1,137 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'open3'
4
- require 'rails/generators'
5
- require_relative 'mise_follow_up'
6
- require_relative '../../../rails/worktrees/database_config_updater'
7
-
8
- module Rails
9
- module Worktrees
10
- module Generators
11
- # Installs the wt wrapper, configuration, and safe database.yml updates.
12
- class InstallGenerator < ::Rails::Generators::Base
13
- include MiseFollowUp
14
-
15
- namespace 'rails:worktrees:install'
16
- desc 'Installs bin/wt, a Rails::Worktrees initializer, and updates config/database.yml when safe.'
17
- source_root File.expand_path('templates', __dir__)
18
- class_option :conductor, type: :boolean, default: false,
19
- desc: 'Configure the installer for ~/Sites/conductor/workspaces'
20
-
21
- FOLLOW_UP_TEMPLATE = <<~TEXT
22
- ============================================
23
- rails-worktrees installed successfully! 🚂
24
-
25
- Installed:
26
- %<installed>s
27
-
28
- Get started:
29
- $ bin/wt
30
- $ bin/wt my-feature
31
-
32
- Configure:
33
- config/initializers/rails_worktrees.rb
34
- %<notes>s
35
- ============================================
36
- TEXT
37
-
38
- def create_bin_wrapper
39
- template('bin/wt', 'bin/wt')
40
- chmod('bin/wt', 0o755)
41
- end
42
-
43
- def create_initializer
44
- template('rails_worktrees.rb.tt', 'config/initializers/rails_worktrees.rb')
45
- end
46
-
47
- def create_procfile_worktree_example
48
- template('Procfile.dev.worktree.example.tt', 'Procfile.dev.worktree.example')
49
- end
50
-
51
- def update_database_configuration
52
- unless File.exist?(database_config_path)
53
- say_status(:skip, 'config/database.yml not found', :yellow)
54
- @database_outcome = :not_found
55
- return
56
- end
57
-
58
- result = database_update_result
59
- @database_outcome = result.changed? ? :updated : :identical
60
- announce_database_update(result)
61
- end
62
-
63
- def verify_installation
64
- if git_repo?
65
- say_status(:ok, 'git repository detected', :green)
66
- else
67
- say_status(:warning, 'run inside a git repository for bin/wt to work', :yellow)
68
- end
69
- end
70
-
71
- def show_follow_up
72
- say(follow_up_message)
73
- end
74
-
75
- private
76
-
77
- def database_config_path
78
- File.join(destination_root, 'config/database.yml')
79
- end
80
-
81
- def database_update_result
82
- result = ::Rails::Worktrees::DatabaseConfigUpdater.new(
83
- content: File.read(database_config_path)
84
- ).call
85
-
86
- File.write(database_config_path, result.content) if result.changed?
87
-
88
- result
89
- end
90
-
91
- def follow_up_message
92
- "\n#{format(FOLLOW_UP_TEMPLATE, installed: installed_items_text, notes: follow_up_notes_text)}"
93
- end
94
-
95
- def installed_items_text
96
- items = [
97
- ' • bin/wt',
98
- ' • config/initializers/rails_worktrees.rb',
99
- ' • Procfile.dev.worktree.example'
100
- ]
101
- items << database_follow_up_line if database_follow_up_line
102
- items.join("\n")
103
- end
104
-
105
- def database_follow_up_line
106
- case @database_outcome
107
- when :updated
108
- ' • config/database.yml (updated with WORKTREE_DATABASE_SUFFIX)'
109
- when :not_found
110
- ' • config/database.yml was not found — see README for manual setup'
111
- end
112
- end
113
-
114
- def announce_database_update(result)
115
- status = result.changed? ? :update : :identical
116
- color = result.changed? ? :green : :blue
117
-
118
- say_status(status, 'config/database.yml', color)
119
- result.messages.each { |message| say(message) }
120
- end
121
-
122
- def git_repo?
123
- _stdout_str, _stderr_str, status = Open3.capture3(
124
- 'git', 'rev-parse', '--is-inside-work-tree', chdir: destination_root
125
- )
126
- status.success?
127
- rescue Errno::ENOENT
128
- false
129
- end
130
-
131
- def conductor_workspace_root
132
- "File.expand_path('~/Sites/conductor/workspaces')"
133
- end
134
- end
135
- end
136
- end
137
- end