rails-worktrees 0.7.4 → 0.8.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4eed6634afda26cca90bd9f6e2540a7ace8f865ec53f97a283fc873e137cffe8
4
- data.tar.gz: 6a2541a2a026af4ed0ccd8e14f3d1ceac78e0da7c5e4d1fc01c1fb6d4b34e0ad
3
+ metadata.gz: f0a2cb7e1644d4ec842daf14fcdf96d6b53d810e548d908a6ea651df4ebaface
4
+ data.tar.gz: 05ae0690974fed85a0862b0fc296681d7de1b037c03d973bef746a1872b62f41
5
5
  SHA512:
6
- metadata.gz: 0a8cf44f39ec11d98faa0d7e3b18badb5b1c4a8dcb3c31c67a0f26d4442656554cb5658275ccc96df69364d5a46572ab2408f57ea08deb31a0568bf284daf2da
7
- data.tar.gz: 588ba219467bee9baa350d06d44a8eaf56fc1eab7e361bb1ff233cd7b9efa1e0e7041ad9f96d90d1bd059ae378789388caf0d4b7c2556cd29b6f7c28cfc63c07
6
+ metadata.gz: d23f7e9e0e070624ebff11c04f050a88e11ea43b61b893139d337c3df992a4ac3642db5344c491923ee36e94b6864d903dec72cbe8bbbf16c899dd3714bad6c7
7
+ data.tar.gz: 919e046dd0c73ad18ae90a9dc6745dbe5484f00766c8c58302f4981fa44b9e0a730455fa9eb5c7c07b929d02abb1c448e150e587495941ac4c8a9dcc20c5dc73
@@ -1 +1 @@
1
- {".":"0.7.4"}
1
+ {".":"0.8.0"}
data/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.8.0](https://github.com/asjer/rails-worktrees/compare/v0.7.4...v0.8.0) (2026-10-24)
4
+
5
+
6
+ ### Features
7
+
8
+ * **pi:** add opt-in pi-worktree setup hook generation ([ccaa0ea](https://github.com/asjer/rails-worktrees/commit/ccaa0ead9fe31a01ac2c8016a51fd60b1611daef)), closes [#53](https://github.com/asjer/rails-worktrees/issues/53)
9
+
3
10
  ## [0.7.4](https://github.com/asjer/rails-worktrees/compare/v0.7.3...v0.7.4) (2026-08-15)
4
11
 
5
12
 
data/README.md CHANGED
@@ -17,6 +17,8 @@ bin/rails generate worktrees:install
17
17
  bin/rails generate worktrees:install --browser
18
18
  # or, to apply the common Procfile.dev + Puma + mise follow-ups automatically:
19
19
  bin/rails generate worktrees:install --yolo
20
+ # optionally, to let the pi-worktree extension run bin/wt setup in new Pi worktrees:
21
+ bin/rails generate worktrees:install --pi
20
22
  ```
21
23
 
22
24
  The generated initializer writes app config under `Rails.application.config.x.rails_worktrees`, so the file stays safe to load even in environments like `test` where the gem is only bundled for `:development`. When `rails-worktrees` is loaded, the gem applies those settings for both the CLI and in-process Rails usage.
@@ -28,6 +30,7 @@ The installer adds:
28
30
  - `config/initializers/rails_worktrees.rb` — optional configuration
29
31
  - `Procfile.dev.worktree.example` — a copy-paste helper for `${DEV_PORT:-3000}` in `Procfile.dev` on regular installs
30
32
  - a safe update to `config/database.yml` for common development/test database names
33
+ - `.pi/worktree.json` — only with `--pi`; see [Pi worktrees](#pi-worktrees-pi-worktree-extension)
31
34
 
32
35
  With `--yolo`, the installer also:
33
36
 
@@ -51,6 +54,7 @@ bin/wt setup ../my-project.worktrees/my-feature # setup a specific checkout with
51
54
  bin/wt doctor # audit install/config drift and basic worktree health
52
55
  bin/wt update --dry-run # preview safe maintenance fixes
53
56
  bin/wt update # apply safe maintenance fixes for managed files
57
+ bin/wt update --pi # also add the pi-worktree hook in .pi/worktree.json
54
58
  bin/wt remove my-feature # remove a worktree and delete its local branch
55
59
  bin/wt delete my-feature # alias for `bin/wt remove`
56
60
  bin/wt remove --force my-feature # also delete an unmerged local branch
@@ -72,6 +76,7 @@ bin/ob --print-url '?from=nav' # print the resolved URL without opening a brows
72
76
  | `--skip-setup` | Create a worktree without running setup steps |
73
77
  | `--force` | Force branch deletion for `bin/wt remove` / `bin/wt delete` |
74
78
  | `--env`, `--print-env <name>` | Preview `DEV_PORT` and `WORKTREE_DATABASE_SUFFIX` |
79
+ | `--pi` | With `bin/wt update`, create or merge `.pi/worktree.json` for the pi-worktree extension |
75
80
 
76
81
  ### Default behavior
77
82
 
@@ -160,6 +165,41 @@ bin/wt prune --dry-run
160
165
 
161
166
  `bin/wt update` is intentionally conservative: it only applies safe file-based fixes for managed templates and supported config shapes. It does **not** delete branches, remove worktrees, or rewrite ambiguous custom files automatically.
162
167
 
168
+ `bin/wt doctor` and `bin/wt update` only look at `.pi/worktree.json` once it already runs `bin/wt setup`, or when you pass `bin/wt update --pi`.
169
+
170
+ ### Pi worktrees (pi-worktree extension)
171
+
172
+ If you create worktrees from [Pi](https://github.com/badlogic/pi-mono) with `pi --worktree` or `/worktree`, rails-worktrees can configure them to run `bin/wt setup` automatically. This is opt-in:
173
+
174
+ ```bash
175
+ bin/rails generate worktrees:install --pi
176
+ # or, in an app that already has rails-worktrees installed:
177
+ bin/wt update --pi
178
+ ```
179
+
180
+ Both commands create or update `.pi/worktree.json`:
181
+
182
+ ```json
183
+ {
184
+ "linkEnvFiles": false,
185
+ "postCreate": [
186
+ "bin/wt setup"
187
+ ]
188
+ }
189
+ ```
190
+
191
+ `.pi/worktree.json` is read by the [pi-worktree](https://www.npmjs.com/package/pi-worktree) npm extension (`pi install pi-worktree`), **not** by Pi core. Without that extension, the file has no effect. pi-worktree places worktrees in `.worktrees/` inside the repository by default, so add that directory to `.gitignore`.
192
+
193
+ `linkEnvFiles: false` matters. By default, pi-worktree symlinks the main checkout's gitignored `.env*` files into a new worktree before `postCreate` runs. `bin/wt setup` keeps existing `.env` values, so every Pi worktree would then reuse the main checkout's `DEV_PORT` and `WORKTREE_DATABASE_SUFFIX`, and setup would write through the symlink into the main checkout. With linking disabled, each worktree gets its own `.env` and database identity.
194
+
195
+ Existing configuration is merged, not replaced:
196
+
197
+ - other keys (`dir`, `branchPrefix`, `preRemove`, …) and custom `postCreate` hooks are preserved; `bin/wt setup` is appended once and never duplicated
198
+ - `linkEnvFiles` is only added when it is absent; an explicit `"linkEnvFiles": true`, invalid JSON, or a non-list `postCreate` is left untouched with a message explaining what to change by hand
199
+ - no `preRemove` hook is added, and the worktree directory and branch prefix stay under your control
200
+
201
+ pi-worktree runs each `postCreate` step with a 120-second timeout. If a first `bundle install` or `db:prepare` takes longer, run `bin/wt setup` again inside the worktree.
202
+
163
203
  ### Updating old `bin/wt` wrappers
164
204
 
165
205
  Versions before 0.7.1 generated `bin/wt` as a Ruby shebang wrapper. On hosts where Ruby is provided by mise but not already on `PATH`, that legacy wrapper can fail with `/usr/bin/env: ruby: No such file or directory` before rails-worktrees can activate mise.
@@ -80,7 +80,7 @@ def rails_worktrees_bootstrap_safe_invocation?(argv)
80
80
  return true if %w[-h --help -v --version].include?(args.first)
81
81
  return true if %w[--env --print-env].include?(args.first)
82
82
 
83
- command_args = args.reject { |arg| %w[--dry-run --skip-setup --env --print-env].include?(arg) }
83
+ command_args = args.reject { |arg| %w[--dry-run --skip-setup --pi --env --print-env].include?(arg) }
84
84
  command = command_args.first
85
85
 
86
86
  return true if %w[setup doctor update].include?(command)
@@ -7,6 +7,7 @@ require_relative '../../../rails/worktrees/database_config_updater'
7
7
  require_relative '../../../rails/worktrees/procfile_updater'
8
8
  require_relative '../../../rails/worktrees/mise_toml_updater'
9
9
  require_relative '../../../rails/worktrees/puma_config_updater'
10
+ require_relative '../../../rails/worktrees/pi_worktree_config_updater'
10
11
 
11
12
  module Worktrees
12
13
  module Generators
@@ -35,8 +36,13 @@ module Worktrees
35
36
  'Apply common Procfile.dev, config/puma.rb, and mise .env',
36
37
  'follow-up edits when safe; also generate bin/ob'
37
38
  ].join(' ')
39
+ class_option :pi,
40
+ type: :boolean,
41
+ default: false,
42
+ desc: 'Generate .pi/worktree.json so the pi-worktree extension runs bin/wt setup in new worktrees'
38
43
 
39
44
  FILE_ENCODING = 'UTF-8'.freeze
45
+ PI_WORKTREE_CONFIG = ::Rails::Worktrees::PiWorktreeConfigUpdater::RELATIVE_PATH
40
46
 
41
47
  FOLLOW_UP_TEMPLATE = <<~TEXT.freeze
42
48
  ============================================
@@ -97,6 +103,21 @@ module Worktrees
97
103
  announce_database_update(result)
98
104
  end
99
105
 
106
+ def configure_pi_worktree
107
+ return unless options[:pi]
108
+
109
+ path = pi_worktree_config_path
110
+ unsafe_reason = ::Rails::Worktrees::PiWorktreeConfigPath.conflict(destination_root)
111
+ return announce_unsafe_pi_worktree_config(unsafe_reason) if unsafe_reason
112
+
113
+ result = ::Rails::Worktrees::PiWorktreeConfigUpdater.new(
114
+ content: File.exist?(path) ? File.read(path, encoding: FILE_ENCODING) : nil
115
+ ).call
116
+ write_pi_worktree_config(path, result.content) if result.changed?
117
+ @pi_worktree_outcome = result.status
118
+ announce_updater_result(PI_WORKTREE_CONFIG, result)
119
+ end
120
+
100
121
  def verify_installation
101
122
  if git_repo?
102
123
  say_status(:ok, 'git repository detected', :green)
@@ -130,6 +151,21 @@ module Worktrees
130
151
  ]
131
152
  end
132
153
 
154
+ def pi_worktree_config_path
155
+ File.join(destination_root, PI_WORKTREE_CONFIG)
156
+ end
157
+
158
+ def write_pi_worktree_config(path, content)
159
+ FileUtils.mkdir_p(File.dirname(path))
160
+ File.write(path, content)
161
+ end
162
+
163
+ def announce_unsafe_pi_worktree_config(reason)
164
+ @pi_worktree_outcome = :conflict
165
+ say_status(:skip, PI_WORKTREE_CONFIG, :yellow)
166
+ say("#{PI_WORKTREE_CONFIG} #{reason}; leaving it unchanged. Add the bin/wt setup hook manually.")
167
+ end
168
+
133
169
  def database_update_result
134
170
  result = ::Rails::Worktrees::DatabaseConfigUpdater.new(
135
171
  content: File.read(database_config_path, encoding: FILE_ENCODING)
@@ -145,7 +181,7 @@ module Worktrees
145
181
  end
146
182
 
147
183
  def follow_up_notes_text
148
- [super, browser_follow_up_notes_text, puma_follow_up_notes_text].join
184
+ [super, browser_follow_up_notes_text, puma_follow_up_notes_text, pi_worktree_follow_up_notes_text].join
149
185
  end
150
186
 
151
187
  def installed_items_text
@@ -156,9 +192,31 @@ module Worktrees
156
192
  items << ' • bin/ob' if install_browser_wrapper?
157
193
  items << ' • Procfile.dev.worktree.example' unless options[:yolo]
158
194
  items << database_follow_up_line if database_follow_up_line
195
+ items << pi_worktree_follow_up_line if pi_worktree_follow_up_line
159
196
  items.join("\n")
160
197
  end
161
198
 
199
+ def pi_worktree_follow_up_line
200
+ case @pi_worktree_outcome
201
+ when :updated, :identical
202
+ " • #{PI_WORKTREE_CONFIG} (pi-worktree postCreate runs bin/wt setup)"
203
+ when :conflict
204
+ " • #{PI_WORKTREE_CONFIG} needs manual review — see the message above"
205
+ end
206
+ end
207
+
208
+ def pi_worktree_follow_up_notes_text
209
+ return '' unless options[:pi]
210
+
211
+ [
212
+ '',
213
+ ' Pi worktrees:',
214
+ " #{PI_WORKTREE_CONFIG} is read by the pi-worktree extension",
215
+ ' ($ pi install pi-worktree); it is not native Pi core configuration.',
216
+ ' Add the worktree directory (default .worktrees/) to .gitignore.'
217
+ ].join("\n")
218
+ end
219
+
162
220
  def database_follow_up_line
163
221
  case @database_outcome
164
222
  when :updated
@@ -3,7 +3,7 @@ module Rails
3
3
  # Shell entrypoint for the wt executable.
4
4
  class CLI
5
5
  LOADER_OPTIONAL_COMMANDS = %w[doctor update -h --help -v --version].freeze
6
- LOADER_IGNORED_FLAGS = %w[--dry-run --force --skip-setup].freeze
6
+ LOADER_IGNORED_FLAGS = %w[--dry-run --force --skip-setup --pi].freeze
7
7
  SETUP_SUBCOMMAND = 'setup'.freeze
8
8
 
9
9
  def initialize(
@@ -35,7 +35,7 @@ module Rails
35
35
  wt --print-env <worktree-name>
36
36
  wt doctor
37
37
  wt setup [--dry-run] [path|name]
38
- wt update [--dry-run]
38
+ wt update [--dry-run] [--pi]
39
39
  wt remove [--dry-run] [--force] <worktree-name>
40
40
  wt delete [--dry-run] [--force] <worktree-name>
41
41
  wt prune [--dry-run]
@@ -47,6 +47,7 @@ module Rails
47
47
  --skip-setup Create the worktree without running setup steps
48
48
  --force Delete an unmerged local branch with wt remove/delete
49
49
  --env, --print-env <name> Preview DEV_PORT and WORKTREE_DATABASE_SUFFIX
50
+ --pi With wt update, add the pi-worktree bin/wt setup hook
50
51
 
51
52
  Quick start:
52
53
  wt Auto-pick a name from a bundled *.txt list
@@ -72,6 +73,7 @@ module Rails
72
73
  - wt setup reruns setup for the current checkout, a managed worktree name, or a specific checkout path, including manually-created worktrees
73
74
  - wt doctor audits install/config drift plus basic worktree health without changing files
74
75
  - wt update applies safe file-based fixes for managed installer artifacts and config hints
76
+ - wt update --pi also writes .pi/worktree.json for the pi-worktree extension (not Pi core)
75
77
  - wt remove/delete can run from the main checkout or any sibling worktree, but never remove the worktree you're currently in
76
78
  - wt prune removes merged worktrees created by wt while skipping the main checkout and the checkout you're in
77
79
  - auto-discovers bundled *.txt files from #{@configuration.name_sources_path}
@@ -15,6 +15,7 @@ module Rails
15
15
  DOCTOR_SUBCOMMAND = 'doctor'.freeze
16
16
  SETUP_SUBCOMMAND = 'setup'.freeze
17
17
  UPDATE_SUBCOMMAND = 'update'.freeze
18
+ PI_FLAG_EXEMPT_META_COMMANDS = %w[-h --help -v --version].freeze
18
19
 
19
20
  include GitOperations
20
21
  include EnvironmentSupport
@@ -35,6 +36,7 @@ module Rails
35
36
  end
36
37
 
37
38
  def run
39
+ validate_pi_flag!
38
40
  meta_command_result = handle_meta_command
39
41
  return meta_command_result unless meta_command_result.nil?
40
42
 
@@ -52,10 +54,13 @@ module Rails
52
54
 
53
55
  def skip_setup? = @skip_setup
54
56
 
57
+ def pi? = @pi
58
+
55
59
  def extract_flags!
56
60
  @dry_run = extract_flag!('--dry-run')
57
61
  @force = extract_flag!('--force')
58
62
  @skip_setup = extract_flag!('--skip-setup')
63
+ @pi = extract_flag!('--pi')
59
64
  end
60
65
 
61
66
  def extract_flag!(flag)
@@ -89,6 +94,13 @@ module Rails
89
94
  @argv.first == UPDATE_SUBCOMMAND
90
95
  end
91
96
 
97
+ def validate_pi_flag!
98
+ return unless pi?
99
+ return if update_subcommand? || PI_FLAG_EXEMPT_META_COMMANDS.include?(@argv.first)
100
+
101
+ raise Error, 'The --pi flag is only supported with wt update.'
102
+ end
103
+
92
104
  def execute_requested_command
93
105
  handler = requested_command_handler
94
106
  return handler.call if handler
@@ -259,7 +271,7 @@ module Rails
259
271
  end
260
272
 
261
273
  def validate_update_args!
262
- raise Error, 'Usage: wt update [--dry-run] [path]' unless [1, 2].include?(@argv.length)
274
+ raise Error, 'Usage: wt update [--dry-run] [--pi] [path]' unless [1, 2].include?(@argv.length)
263
275
  raise Error, 'The --force flag is only supported with wt remove.' if force?
264
276
  raise Error, 'The --skip-setup flag is only supported when creating a worktree.' if skip_setup?
265
277
  end
@@ -638,7 +650,7 @@ module Rails
638
650
  end
639
651
 
640
652
  def project_maintenance_report(root)
641
- ::Rails::Worktrees::ProjectMaintenance.new(root: root).call
653
+ ::Rails::Worktrees::ProjectMaintenance.new(root: root, pi_worktree: pi?).call
642
654
  end
643
655
 
644
656
  def doctor_worktree_checks(repository)
@@ -0,0 +1,44 @@
1
+ module Rails
2
+ module Worktrees
3
+ # Decides whether .pi/worktree.json under an app root can be safely read and written in place.
4
+ class PiWorktreeConfigPath
5
+ RELATIVE_PATH = '.pi/worktree.json'.freeze
6
+
7
+ def self.conflict(root)
8
+ new(root).conflict
9
+ end
10
+
11
+ def initialize(root)
12
+ @root = root
13
+ @path = File.join(root, RELATIVE_PATH)
14
+ @parent = File.dirname(@path)
15
+ end
16
+
17
+ # Returns why the config path is unsafe, or nil when it can be used.
18
+ def conflict
19
+ return 'is a symlink' if File.symlink?(@path)
20
+ return 'is not a regular file' if File.exist?(@path) && !File.file?(@path)
21
+ return "cannot be created because #{File.basename(@parent)} is not a directory" if parent_not_directory?
22
+ return if within_root?(nearest_existing_realpath(@parent))
23
+
24
+ 'resolves outside the app through a symlinked directory'
25
+ end
26
+
27
+ private
28
+
29
+ def parent_not_directory?
30
+ (File.exist?(@parent) || File.symlink?(@parent)) && !File.directory?(@parent)
31
+ end
32
+
33
+ def nearest_existing_realpath(path)
34
+ path = File.dirname(path) until File.exist?(path)
35
+ File.realpath(path)
36
+ end
37
+
38
+ def within_root?(path)
39
+ root = File.realpath(@root)
40
+ path == root || path.start_with?("#{root}#{File::SEPARATOR}")
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,123 @@
1
+ require 'json'
2
+ require_relative 'pi_worktree_config_path'
3
+
4
+ module Rails
5
+ module Worktrees
6
+ # Safely merges the bin/wt setup hook into the pi-worktree extension's .pi/worktree.json.
7
+ class PiWorktreeConfigUpdater
8
+ Result = Struct.new(:content, :changed, :status, :messages) do
9
+ def changed?
10
+ changed
11
+ end
12
+ end
13
+
14
+ RELATIVE_PATH = PiWorktreeConfigPath::RELATIVE_PATH
15
+ SETUP_COMMAND = 'bin/wt setup'.freeze
16
+ # Horizontal whitespace only: a newline would make the shell run two separate commands.
17
+ SETUP_COMMAND_PATTERN = %r{\A[ \t]*(?:\./)?bin/wt[ \t]+setup[ \t]*\z}
18
+
19
+ def self.configured?(content)
20
+ return false if content.nil? || !content.valid_encoding?
21
+
22
+ config = JSON.parse(content)
23
+ config.is_a?(Hash) && setup_hook?(config['postCreate'])
24
+ rescue JSON::ParserError
25
+ false
26
+ end
27
+
28
+ def self.setup_hook?(commands)
29
+ return false unless commands.is_a?(Array)
30
+
31
+ commands.any? { |command| command.is_a?(String) && command.match?(SETUP_COMMAND_PATTERN) }
32
+ end
33
+
34
+ def initialize(content:)
35
+ @content = content
36
+ end
37
+
38
+ def call
39
+ config = parse_config
40
+ return config if config.is_a?(Result)
41
+
42
+ conflict = conflict_message(config)
43
+ return conflict_result(conflict) if conflict
44
+
45
+ merge_setup_hook(config)
46
+ end
47
+
48
+ private
49
+
50
+ def parse_config
51
+ return {} if @content.nil?
52
+ unless @content.valid_encoding?
53
+ return conflict_result("#{RELATIVE_PATH} is not valid UTF-8; leaving it unchanged.")
54
+ end
55
+ return {} if @content.strip.empty?
56
+
57
+ config = JSON.parse(@content)
58
+ return config if config.is_a?(Hash)
59
+
60
+ conflict_result("#{RELATIVE_PATH} must contain a JSON object; leaving it unchanged.")
61
+ rescue JSON::ParserError => e
62
+ conflict_result("#{RELATIVE_PATH} is not valid JSON (#{e.message.lines.first.strip}); leaving it unchanged.")
63
+ end
64
+
65
+ def conflict_message(config)
66
+ if config.key?('linkEnvFiles') && config['linkEnvFiles'] != false
67
+ return [
68
+ "#{RELATIVE_PATH} sets \"linkEnvFiles\": #{JSON.generate(config['linkEnvFiles'])}, so pi-worktree " \
69
+ "would symlink the main checkout's .env into new worktrees before bin/wt setup runs.",
70
+ 'Set "linkEnvFiles": false yourself to give each worktree its own DEV_PORT and ' \
71
+ 'WORKTREE_DATABASE_SUFFIX, then rerun this command; the file was left unchanged.'
72
+ ]
73
+ end
74
+
75
+ return if !config.key?('postCreate') || string_list?(config['postCreate'])
76
+
77
+ "#{RELATIVE_PATH} postCreate must be a list of shell commands; leaving it unchanged."
78
+ end
79
+
80
+ def merge_setup_hook(config)
81
+ updated = config.dup
82
+ messages = [add_setup_hook(updated), disable_env_file_linking(updated)].compact
83
+ return identical_result if messages.empty?
84
+
85
+ Result.new("#{JSON.pretty_generate(ordered_new_config(config, updated))}\n", true, :updated, messages)
86
+ end
87
+
88
+ def add_setup_hook(config)
89
+ return if self.class.setup_hook?(config['postCreate'])
90
+
91
+ config['postCreate'] = Array(config['postCreate']) + [SETUP_COMMAND]
92
+ "Added \"#{SETUP_COMMAND}\" to postCreate in #{RELATIVE_PATH}."
93
+ end
94
+
95
+ def disable_env_file_linking(config)
96
+ return if config.key?('linkEnvFiles')
97
+
98
+ config['linkEnvFiles'] = false
99
+ "Disabled linkEnvFiles in #{RELATIVE_PATH} so each worktree keeps its own .env."
100
+ end
101
+
102
+ # Fresh configs list linkEnvFiles first so the safety switch is visible above the hooks.
103
+ def ordered_new_config(original, updated)
104
+ return updated unless original.empty?
105
+
106
+ { 'linkEnvFiles' => updated['linkEnvFiles'], 'postCreate' => updated['postCreate'] }
107
+ end
108
+
109
+ def string_list?(value)
110
+ value.is_a?(Array) && value.all?(String)
111
+ end
112
+
113
+ def identical_result
114
+ Result.new(@content, false, :identical,
115
+ ["#{RELATIVE_PATH} already runs bin/wt setup with env-file linking disabled."])
116
+ end
117
+
118
+ def conflict_result(messages)
119
+ Result.new(@content, false, :conflict, Array(messages))
120
+ end
121
+ end
122
+ end
123
+ end
@@ -50,8 +50,9 @@ module Rails
50
50
  TEMPLATE_ROOT = File.expand_path('../../generators/rails/worktrees/templates', __dir__)
51
51
  FILE_ENCODING = 'UTF-8'.freeze
52
52
 
53
- def initialize(root:)
53
+ def initialize(root:, pi_worktree: false)
54
54
  @root = root
55
+ @pi_worktree = pi_worktree
55
56
  end
56
57
 
57
58
  def call
@@ -60,7 +61,7 @@ module Rails
60
61
 
61
62
  private
62
63
 
63
- attr_reader :root
64
+ attr_reader :root, :pi_worktree
64
65
 
65
66
  def maintenance_checks
66
67
  [
@@ -70,7 +71,8 @@ module Rails
70
71
  optional_updater_check(procfile_config) { |content| ProcfileUpdater.new(content: content).call },
71
72
  optional_updater_check(puma_config) { |content| PumaConfigUpdater.new(content: content).call },
72
73
  mise_check,
73
- template_file_check(browser_wrapper_config)
74
+ template_file_check(browser_wrapper_config),
75
+ pi_worktree_check
74
76
  ]
75
77
  end
76
78
 
@@ -263,6 +265,31 @@ module Rails
263
265
  updater_result_check(config, mise_updater_result(relative_path))
264
266
  end
265
267
 
268
+ # Only audit .pi/worktree.json when explicitly requested or when it already runs bin/wt setup,
269
+ # so configs owned purely by other pi-worktree users stay untouched.
270
+ def pi_worktree_check
271
+ relative_path = PiWorktreeConfigUpdater::RELATIVE_PATH
272
+ path = absolute_path(relative_path)
273
+ content = File.file?(path) ? read_text(path) : nil
274
+ return unless pi_worktree || PiWorktreeConfigUpdater.configured?(content)
275
+
276
+ config = {
277
+ identifier: :pi_worktree,
278
+ category: :config,
279
+ relative_path: relative_path,
280
+ identical_headline: "#{relative_path} already runs bin/wt setup with env-file linking disabled.",
281
+ fixable_headline: "#{relative_path} can be updated automatically.",
282
+ warning_headline: "#{relative_path} needs manual review."
283
+ }
284
+ path_conflict = PiWorktreeConfigPath.conflict(root)
285
+ if path_conflict
286
+ return warning_check(config, config.fetch(:warning_headline),
287
+ ["#{relative_path} #{path_conflict}; leaving it unchanged."])
288
+ end
289
+
290
+ updater_result_check(config, PiWorktreeConfigUpdater.new(content: content).call)
291
+ end
292
+
266
293
  def mise_updater_result(relative_path)
267
294
  MiseTomlUpdater.new(
268
295
  content: read_text(absolute_path(relative_path)),
@@ -1,5 +1,5 @@
1
1
  module Rails
2
2
  module Worktrees
3
- VERSION = '0.7.4'.freeze
3
+ VERSION = '0.8.0'.freeze
4
4
  end
5
5
  end
@@ -15,6 +15,8 @@ require_relative 'worktrees/initializer_updater'
15
15
  require_relative 'worktrees/procfile_updater'
16
16
  require_relative 'worktrees/mise_toml_updater'
17
17
  require_relative 'worktrees/puma_config_updater'
18
+ require_relative 'worktrees/pi_worktree_config_path'
19
+ require_relative 'worktrees/pi_worktree_config_updater'
18
20
  require_relative 'worktrees/project_configuration_loader'
19
21
  require_relative 'worktrees/project_maintenance'
20
22
 
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.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Asjer Querido
@@ -73,6 +73,8 @@ files:
73
73
  - lib/rails/worktrees/mise_environment.rb
74
74
  - lib/rails/worktrees/mise_toml_updater.rb
75
75
  - lib/rails/worktrees/names/cities.txt
76
+ - lib/rails/worktrees/pi_worktree_config_path.rb
77
+ - lib/rails/worktrees/pi_worktree_config_updater.rb
76
78
  - lib/rails/worktrees/post_create_runner.rb
77
79
  - lib/rails/worktrees/procfile_updater.rb
78
80
  - lib/rails/worktrees/project_configuration_loader.rb