rails-worktrees 0.1.1 → 0.2.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 +4 -4
- data/.release-please-manifest.json +1 -1
- data/CHANGELOG.md +14 -0
- data/README.md +19 -3
- data/lib/generators/rails/worktrees/mise_follow_up.rb +14 -4
- data/lib/generators/rails/worktrees/puma_follow_up.rb +45 -0
- data/lib/generators/worktrees/install/install_generator.rb +93 -0
- data/lib/rails/worktrees/mise_toml_updater.rb +82 -0
- data/lib/rails/worktrees/procfile_updater.rb +69 -0
- data/lib/rails/worktrees/puma_config_updater.rb +91 -0
- data/lib/rails/worktrees/version.rb +1 -1
- data/lib/rails/worktrees.rb +3 -0
- metadata +5 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 971ba5f8f872a37a91d2e8c1755bf838db8bf67a767dbfba1b105b727440d9b7
|
|
4
|
+
data.tar.gz: dd0441f61442afffc29b7ab5f70968abd0c35c59e2731247cc55ddeae6e18265
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1e77b1961db51bcaca110efe1b8c9c6c96e848b523f30e158cc55c94550023fb85289b124ec859abd6957542ae0fcc4e20a63f793985b87ea5a03e2302b9748a
|
|
7
|
+
data.tar.gz: 5955435a25990d85b834e9c22d3c37d9621cab21a69dac6c6b9e038e763bc8346a8f6a23886f37098285cd6f055496417f85b34f8351e9f5b4d3e6115b429753
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{".":"0.
|
|
1
|
+
{".":"0.2.1"}
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.2.1](https://github.com/asjer/rails-worktrees/compare/v0.2.0...v0.2.1) (2026-03-30)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* **puma:** add missing `DEV_PORT` support to config/puma.rb ([3fd71a0](https://github.com/asjer/rails-worktrees/commit/3fd71a04d79732bed4cf626951abe06ddbc01153))
|
|
9
|
+
|
|
10
|
+
## [0.2.0](https://github.com/asjer/rails-worktrees/compare/v0.1.1...v0.2.0) (2026-03-30)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Features
|
|
14
|
+
|
|
15
|
+
* add `--yolo` mode to install common follow-ups without manual edits ([1ec82ec](https://github.com/asjer/rails-worktrees/commit/1ec82ec83726b5ca9cbaee39697c607fdb825f26))
|
|
16
|
+
|
|
3
17
|
## [0.1.1](https://github.com/asjer/rails-worktrees/compare/v0.1.0...v0.1.1) (2026-03-30)
|
|
4
18
|
|
|
5
19
|
|
data/README.md
CHANGED
|
@@ -13,6 +13,8 @@
|
|
|
13
13
|
```bash
|
|
14
14
|
bundle add rails-worktrees
|
|
15
15
|
bin/rails generate worktrees:install
|
|
16
|
+
# or, to apply the common Procfile.dev + Puma + mise follow-ups automatically:
|
|
17
|
+
bin/rails generate worktrees:install --yolo
|
|
16
18
|
```
|
|
17
19
|
|
|
18
20
|
The installer adds:
|
|
@@ -22,6 +24,12 @@ The installer adds:
|
|
|
22
24
|
- `Procfile.dev.worktree.example` — a copy-paste helper for `${DEV_PORT:-3000}` in `Procfile.dev`
|
|
23
25
|
- a safe update to `config/database.yml` for common development/test database names
|
|
24
26
|
|
|
27
|
+
With `--yolo`, the installer also:
|
|
28
|
+
|
|
29
|
+
- replaces the existing `web:` entry in `Procfile.dev` with the DEV_PORT-aware command when `Procfile.dev` already exists
|
|
30
|
+
- updates `config/puma.rb` to use `port ENV['DEV_PORT'] || ENV.fetch('PORT', 3000)` when it still uses a supported default `PORT` binding
|
|
31
|
+
- updates `mise.toml` or `.mise.toml` to load `.env` from `[env]` when either file already exists
|
|
32
|
+
|
|
25
33
|
## Usage
|
|
26
34
|
|
|
27
35
|
```bash
|
|
@@ -115,12 +123,20 @@ When `bin/wt` creates a worktree it writes a worktree-local `.env` with:
|
|
|
115
123
|
|
|
116
124
|
Existing `.env` values are never overwritten.
|
|
117
125
|
|
|
118
|
-
|
|
126
|
+
By default, the installer does **not** edit your `Procfile.dev`, `config/puma.rb`, or `mise` config. It generates `Procfile.dev.worktree.example` with a ready-to-copy line:
|
|
119
127
|
|
|
120
128
|
```text
|
|
121
129
|
web: env RUBY_DEBUG_OPEN=true bin/rails server -b 0.0.0.0 -p ${DEV_PORT:-3000}
|
|
122
130
|
```
|
|
123
131
|
|
|
132
|
+
If you run `bin/rails generate worktrees:install --yolo`, the installer applies the three common follow-ups for you when the files already exist:
|
|
133
|
+
|
|
134
|
+
- replace the existing `web:` entry in `Procfile.dev`
|
|
135
|
+
- update `config/puma.rb` to `port ENV['DEV_PORT'] || ENV.fetch('PORT', 3000)` when it still uses a supported default `PORT` binding
|
|
136
|
+
- add `_.file = ".env"` to the `[env]` section of `mise.toml` or `.mise.toml`
|
|
137
|
+
|
|
138
|
+
On a regular install, the follow-up message also suggests the same `config/puma.rb` edit when Puma still uses the default `PORT` binding.
|
|
139
|
+
|
|
124
140
|
Use a project-local env loader like `mise` with `_.file = ".env"` to keep values scoped per-worktree.
|
|
125
141
|
|
|
126
142
|
## Development
|
|
@@ -141,8 +157,8 @@ This smoke test:
|
|
|
141
157
|
|
|
142
158
|
- creates a temporary Rails app from a compatible Rails version
|
|
143
159
|
- installs `rails-worktrees` from the current checkout path
|
|
144
|
-
- runs `bin/rails generate worktrees:install`
|
|
145
|
-
- verifies `bin/wt`, the generated initializer, the Procfile example, `config/database.yml` patching, and worktree `.env` bootstrapping
|
|
160
|
+
- runs `bin/rails generate worktrees:install --yolo`
|
|
161
|
+
- verifies `bin/wt`, the generated initializer, the Procfile example, yolo updates to `Procfile.dev`, `config/puma.rb`, and `mise.toml`, `config/database.yml` patching, and worktree `.env` bootstrapping
|
|
146
162
|
- creates a temporary bare `origin` and confirms `bin/wt smoke-branch` creates a real worktree
|
|
147
163
|
|
|
148
164
|
By default, the script cleans up all temp directories after the run. Set `KEEP_SMOKE_TEST_ARTIFACTS=1` to keep them around for debugging, or set `RAILS_WORKTREES_SMOKE_RAILS_VERSION` to try a different compatible Rails version.
|
|
@@ -11,7 +11,8 @@ module Rails
|
|
|
11
11
|
[
|
|
12
12
|
'',
|
|
13
13
|
' Tip:',
|
|
14
|
-
|
|
14
|
+
" Detected #{File.basename(mise_toml_path)}. To auto-load the worktree-local .env when",
|
|
15
|
+
' you enter a worktree,',
|
|
15
16
|
' consider adding:',
|
|
16
17
|
' [env]',
|
|
17
18
|
' _.file = ".env"'
|
|
@@ -19,15 +20,24 @@ module Rails
|
|
|
19
20
|
end
|
|
20
21
|
|
|
21
22
|
def suggest_mise_env_file?
|
|
22
|
-
|
|
23
|
+
mise_toml_path && !mise_env_file_configured?
|
|
23
24
|
end
|
|
24
25
|
|
|
25
26
|
def mise_env_file_configured?
|
|
26
|
-
|
|
27
|
+
return false unless mise_toml_path
|
|
28
|
+
|
|
29
|
+
File.read(mise_toml_path).match?(/^\s*_.file\s*=\s*["']\.env["']\s*(?:#.*)?\s*$/)
|
|
27
30
|
end
|
|
28
31
|
|
|
29
32
|
def mise_toml_path
|
|
30
|
-
File.
|
|
33
|
+
@mise_toml_path ||= mise_toml_paths.find { |path| File.file?(path) }
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def mise_toml_paths
|
|
37
|
+
[
|
|
38
|
+
File.join(destination_root, 'mise.toml'),
|
|
39
|
+
File.join(destination_root, '.mise.toml')
|
|
40
|
+
]
|
|
31
41
|
end
|
|
32
42
|
end
|
|
33
43
|
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
module Rails
|
|
2
|
+
module Worktrees
|
|
3
|
+
module Generators
|
|
4
|
+
# Detects config/puma.rb setups that should prefer the worktree-local DEV_PORT.
|
|
5
|
+
module PumaFollowUp
|
|
6
|
+
private
|
|
7
|
+
|
|
8
|
+
def puma_follow_up_notes_text
|
|
9
|
+
return '' unless suggest_puma_dev_port_update?
|
|
10
|
+
|
|
11
|
+
[
|
|
12
|
+
'',
|
|
13
|
+
' Tip:',
|
|
14
|
+
' Detected config/puma.rb. To bind Puma to the worktree-local DEV_PORT,',
|
|
15
|
+
' consider changing the port line to:',
|
|
16
|
+
" #{::Rails::Worktrees::PumaConfigUpdater::STANDARD_PORT_LINE}"
|
|
17
|
+
].join("\n")
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def suggest_puma_dev_port_update?
|
|
21
|
+
existing_puma_config_path && puma_update_result.status == :updated
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def puma_update_result
|
|
25
|
+
return missing_puma_update_result unless existing_puma_config_path
|
|
26
|
+
|
|
27
|
+
::Rails::Worktrees::PumaConfigUpdater.new(content: File.read(existing_puma_config_path)).call
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def missing_puma_update_result
|
|
31
|
+
::Rails::Worktrees::PumaConfigUpdater::Result.new(nil, false, :skip, [])
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def puma_config_path
|
|
35
|
+
File.join(destination_root, 'config/puma.rb')
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def existing_puma_config_path
|
|
39
|
+
path = puma_config_path
|
|
40
|
+
File.file?(path) ? path : nil
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -2,19 +2,27 @@ require 'open3'
|
|
|
2
2
|
require 'rails/generators'
|
|
3
3
|
|
|
4
4
|
require_relative '../../rails/worktrees/mise_follow_up'
|
|
5
|
+
require_relative '../../rails/worktrees/puma_follow_up'
|
|
5
6
|
require_relative '../../../rails/worktrees/database_config_updater'
|
|
7
|
+
require_relative '../../../rails/worktrees/procfile_updater'
|
|
8
|
+
require_relative '../../../rails/worktrees/mise_toml_updater'
|
|
9
|
+
require_relative '../../../rails/worktrees/puma_config_updater'
|
|
6
10
|
|
|
7
11
|
module Worktrees
|
|
8
12
|
module Generators
|
|
9
13
|
# Installs the wt wrapper, configuration, and safe database.yml updates.
|
|
14
|
+
# rubocop:disable Metrics/ClassLength
|
|
10
15
|
class InstallGenerator < ::Rails::Generators::Base
|
|
11
16
|
include ::Rails::Worktrees::Generators::MiseFollowUp
|
|
17
|
+
include ::Rails::Worktrees::Generators::PumaFollowUp
|
|
12
18
|
|
|
13
19
|
namespace 'worktrees:install'
|
|
14
20
|
desc 'Installs bin/wt, a Rails::Worktrees initializer, and updates config/database.yml when safe.'
|
|
15
21
|
source_root File.expand_path('../../rails/worktrees/templates', __dir__)
|
|
16
22
|
class_option :conductor, type: :boolean, default: false,
|
|
17
23
|
desc: 'Configure the installer for ~/Sites/conductor/workspaces'
|
|
24
|
+
class_option :yolo, type: :boolean, default: false,
|
|
25
|
+
desc: 'Apply common Procfile.dev, config/puma.rb, and mise .env follow-up edits when safe'
|
|
18
26
|
|
|
19
27
|
FOLLOW_UP_TEMPLATE = <<~TEXT.freeze
|
|
20
28
|
============================================
|
|
@@ -46,6 +54,14 @@ module Worktrees
|
|
|
46
54
|
template('Procfile.dev.worktree.example.tt', 'Procfile.dev.worktree.example')
|
|
47
55
|
end
|
|
48
56
|
|
|
57
|
+
def apply_yolo_follow_ups
|
|
58
|
+
return unless options[:yolo]
|
|
59
|
+
|
|
60
|
+
update_procfile
|
|
61
|
+
update_puma_config
|
|
62
|
+
update_mise_toml
|
|
63
|
+
end
|
|
64
|
+
|
|
49
65
|
def update_database_configuration
|
|
50
66
|
unless File.exist?(database_config_path)
|
|
51
67
|
say_status(:skip, 'config/database.yml not found', :yellow)
|
|
@@ -76,6 +92,21 @@ module Worktrees
|
|
|
76
92
|
File.join(destination_root, 'config/database.yml')
|
|
77
93
|
end
|
|
78
94
|
|
|
95
|
+
def procfile_path
|
|
96
|
+
File.join(destination_root, 'Procfile.dev')
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def puma_config_path
|
|
100
|
+
File.join(destination_root, 'config/puma.rb')
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def mise_toml_paths
|
|
104
|
+
[
|
|
105
|
+
File.join(destination_root, 'mise.toml'),
|
|
106
|
+
File.join(destination_root, '.mise.toml')
|
|
107
|
+
]
|
|
108
|
+
end
|
|
109
|
+
|
|
79
110
|
def database_update_result
|
|
80
111
|
result = ::Rails::Worktrees::DatabaseConfigUpdater.new(
|
|
81
112
|
content: File.read(database_config_path)
|
|
@@ -90,6 +121,10 @@ module Worktrees
|
|
|
90
121
|
"\n#{format(FOLLOW_UP_TEMPLATE, installed: installed_items_text, notes: follow_up_notes_text)}"
|
|
91
122
|
end
|
|
92
123
|
|
|
124
|
+
def follow_up_notes_text
|
|
125
|
+
[super, puma_follow_up_notes_text].join
|
|
126
|
+
end
|
|
127
|
+
|
|
93
128
|
def installed_items_text
|
|
94
129
|
items = [
|
|
95
130
|
' • bin/wt',
|
|
@@ -117,6 +152,63 @@ module Worktrees
|
|
|
117
152
|
result.messages.each { |message| say(message) }
|
|
118
153
|
end
|
|
119
154
|
|
|
155
|
+
def update_procfile
|
|
156
|
+
unless File.exist?(procfile_path)
|
|
157
|
+
say_status(:skip, 'Procfile.dev not found', :yellow)
|
|
158
|
+
say('Skipped Procfile.dev yolo update because the file does not exist yet.')
|
|
159
|
+
return
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
result = ::Rails::Worktrees::ProcfileUpdater.new(content: File.read(procfile_path)).call
|
|
163
|
+
File.write(procfile_path, result.content) if result.changed?
|
|
164
|
+
announce_updater_result('Procfile.dev', result)
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def update_puma_config
|
|
168
|
+
unless File.exist?(puma_config_path)
|
|
169
|
+
say_status(:skip, 'config/puma.rb not found', :yellow)
|
|
170
|
+
say('Skipped config/puma.rb yolo update because the file does not exist yet.')
|
|
171
|
+
return
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
result = ::Rails::Worktrees::PumaConfigUpdater.new(content: File.read(puma_config_path)).call
|
|
175
|
+
File.write(puma_config_path, result.content) if result.changed?
|
|
176
|
+
announce_updater_result('config/puma.rb', result)
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def update_mise_toml
|
|
180
|
+
path = first_mise_toml_path
|
|
181
|
+
return announce_missing_mise_toml unless path
|
|
182
|
+
|
|
183
|
+
result = ::Rails::Worktrees::MiseTomlUpdater.new(
|
|
184
|
+
content: File.read(path),
|
|
185
|
+
file_name: File.basename(path)
|
|
186
|
+
).call
|
|
187
|
+
|
|
188
|
+
File.write(path, result.content) if result.changed?
|
|
189
|
+
announce_updater_result(File.basename(path), result)
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def announce_updater_result(path, result)
|
|
193
|
+
status, color = case result.status
|
|
194
|
+
when :updated then %i[update green]
|
|
195
|
+
when :identical then %i[identical blue]
|
|
196
|
+
else %i[skip yellow]
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
say_status(status, path, color)
|
|
200
|
+
result.messages.each { |message| say(message) }
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def first_mise_toml_path
|
|
204
|
+
mise_toml_paths.find { |candidate| File.file?(candidate) }
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
def announce_missing_mise_toml
|
|
208
|
+
say_status(:skip, 'mise.toml/.mise.toml not found', :yellow)
|
|
209
|
+
say('Skipped mise yolo update because no supported mise config file was found.')
|
|
210
|
+
end
|
|
211
|
+
|
|
120
212
|
def git_repo?
|
|
121
213
|
_stdout_str, _stderr_str, status = Open3.capture3(
|
|
122
214
|
'git', 'rev-parse', '--is-inside-work-tree', chdir: destination_root
|
|
@@ -130,5 +222,6 @@ module Worktrees
|
|
|
130
222
|
"File.expand_path('~/Sites/conductor/workspaces')"
|
|
131
223
|
end
|
|
132
224
|
end
|
|
225
|
+
# rubocop:enable Metrics/ClassLength
|
|
133
226
|
end
|
|
134
227
|
end
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
module Rails
|
|
2
|
+
module Worktrees
|
|
3
|
+
# Safely updates mise config to load the worktree-local .env.
|
|
4
|
+
class MiseTomlUpdater
|
|
5
|
+
Result = Struct.new(:content, :changed, :status, :messages) do
|
|
6
|
+
def changed?
|
|
7
|
+
changed
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
ENV_SECTION_PATTERN = /\A\s*\[env\]\s*(?:#.*)?\z/
|
|
12
|
+
ENV_FILE_PATTERN = /\A\s*_.file\s*=\s*["']\.env["']\s*(?:#.*)?\z/
|
|
13
|
+
SECTION_PATTERN = /\A\s*\[[^\]]+\]\s*(?:#.*)?\z/
|
|
14
|
+
ENV_FILE_ENTRY = '_.file = ".env"'.freeze
|
|
15
|
+
|
|
16
|
+
def initialize(content:, file_name:)
|
|
17
|
+
@content = content
|
|
18
|
+
@file_name = file_name
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def call
|
|
22
|
+
lines = @content.lines(chomp: true)
|
|
23
|
+
return update_existing_env_section(lines) if env_section_present?(lines)
|
|
24
|
+
|
|
25
|
+
append_env_section(lines)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def update_existing_env_section(lines)
|
|
31
|
+
env_section_start, env_section_end = env_section_bounds(lines)
|
|
32
|
+
return identical_result if env_file_configured?(lines, env_section_start, env_section_end)
|
|
33
|
+
|
|
34
|
+
updated_lines = lines.dup
|
|
35
|
+
updated_lines.insert(env_section_end, ENV_FILE_ENTRY)
|
|
36
|
+
updated_result(rebuild_content(updated_lines, trailing_newline: @content.end_with?("\n")))
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def append_env_section(lines)
|
|
40
|
+
updated_lines = lines.dup
|
|
41
|
+
updated_lines << '' if updated_lines.any? && !updated_lines.last.empty?
|
|
42
|
+
updated_lines << '[env]'
|
|
43
|
+
updated_lines << ENV_FILE_ENTRY
|
|
44
|
+
|
|
45
|
+
updated_result(rebuild_content(updated_lines, trailing_newline: true))
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def updated_result(content)
|
|
49
|
+
Result.new(content, true, :updated, ["Configured #{@file_name} to load .env from [env]."])
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def identical_result
|
|
53
|
+
Result.new(@content, false, :identical, ["#{@file_name} already loads .env from [env]."])
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def env_section_bounds(lines)
|
|
57
|
+
start_index = lines.find_index { |line| line.match?(ENV_SECTION_PATTERN) }
|
|
58
|
+
return [nil, nil] unless start_index
|
|
59
|
+
|
|
60
|
+
end_index = ((start_index + 1)...lines.length).find do |index|
|
|
61
|
+
lines[index].match?(SECTION_PATTERN)
|
|
62
|
+
end || lines.length
|
|
63
|
+
[start_index, end_index]
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def env_section_present?(lines)
|
|
67
|
+
env_section_bounds(lines).first
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def env_file_configured?(lines, start_index, end_index)
|
|
71
|
+
lines[(start_index + 1)...end_index].any? { |line| line.match?(ENV_FILE_PATTERN) }
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def rebuild_content(lines, trailing_newline:)
|
|
75
|
+
content = lines.join("\n")
|
|
76
|
+
return content if content.empty? || !trailing_newline
|
|
77
|
+
|
|
78
|
+
"#{content}\n"
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
module Rails
|
|
2
|
+
module Worktrees
|
|
3
|
+
# Safely updates Procfile.dev to use the worktree-local DEV_PORT.
|
|
4
|
+
class ProcfileUpdater
|
|
5
|
+
Result = Struct.new(:content, :changed, :status, :messages) do
|
|
6
|
+
def changed?
|
|
7
|
+
changed
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
STANDARD_WEB_ENTRY = 'web: env RUBY_DEBUG_OPEN=true bin/rails server -b 0.0.0.0 -p ${DEV_PORT:-3000}'.freeze
|
|
12
|
+
WEB_ENTRY_PATTERN = /\Aweb:\s*.*\z/
|
|
13
|
+
|
|
14
|
+
def initialize(content:)
|
|
15
|
+
@content = content
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def call
|
|
19
|
+
lines = @content.lines(chomp: true)
|
|
20
|
+
web_entry_indexes = web_entry_indexes(lines)
|
|
21
|
+
return skip_result if web_entry_indexes.empty?
|
|
22
|
+
|
|
23
|
+
updated_content = rebuild_content(
|
|
24
|
+
replace_web_entries(lines, web_entry_indexes),
|
|
25
|
+
trailing_newline: @content.end_with?("\n")
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
build_result(updated_content)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
def updated_result(content)
|
|
34
|
+
Result.new(content, true, :updated, ['Updated Procfile.dev web entry to use DEV_PORT.'])
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def identical_result(content)
|
|
38
|
+
Result.new(content, false, :identical, ['Procfile.dev already uses the DEV_PORT-aware web entry.'])
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def skip_result
|
|
42
|
+
Result.new(@content, false, :skip, ['No web: entry found in Procfile.dev; update it manually if needed.'])
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def web_entry_indexes(lines)
|
|
46
|
+
lines.each_index.select { |index| lines[index].match?(WEB_ENTRY_PATTERN) }
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def replace_web_entries(lines, web_entry_indexes)
|
|
50
|
+
lines.dup.tap do |updated_lines|
|
|
51
|
+
web_entry_indexes.each { |index| updated_lines[index] = STANDARD_WEB_ENTRY }
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def build_result(content)
|
|
56
|
+
return identical_result(content) if content == @content
|
|
57
|
+
|
|
58
|
+
updated_result(content)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def rebuild_content(lines, trailing_newline:)
|
|
62
|
+
content = lines.join("\n")
|
|
63
|
+
return content if content.empty? || !trailing_newline
|
|
64
|
+
|
|
65
|
+
"#{content}\n"
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
module Rails
|
|
2
|
+
module Worktrees
|
|
3
|
+
# Safely updates config/puma.rb to bind Puma to the worktree-local DEV_PORT.
|
|
4
|
+
class PumaConfigUpdater
|
|
5
|
+
Result = Struct.new(:content, :changed, :status, :messages) do
|
|
6
|
+
def changed?
|
|
7
|
+
changed
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
STANDARD_PORT_LINE = "port ENV['DEV_PORT'] || ENV.fetch('PORT', 3000)".freeze
|
|
12
|
+
CURRENT_PORT_PATTERN = /\A\s*port\s+ENV\.fetch\(["']PORT["'],\s*3000\)\s*(?:#.*)?\z/
|
|
13
|
+
LEGACY_PORT_PATTERN = /\A\s*port\s+ENV\.fetch\(["']PORT["']\)\s*\{\s*3000\s*\}\s*(?:#.*)?\z/
|
|
14
|
+
PORT_LINE_PATTERN = /\A\s*port\s+/
|
|
15
|
+
DEV_PORT_ACCESS_PATTERN = /ENV\[(["'])DEV_PORT\1\]/
|
|
16
|
+
DEV_PORT_FETCH_PATTERN = /ENV\.fetch\((["'])DEV_PORT\1(?:\s*,|\s*\))/
|
|
17
|
+
|
|
18
|
+
def initialize(content:)
|
|
19
|
+
@content = content
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def call
|
|
23
|
+
lines = @content.lines(chomp: true)
|
|
24
|
+
return identical_result(@content) if dev_port_configured?(lines)
|
|
25
|
+
|
|
26
|
+
port_line_indexes = supported_port_line_indexes(lines)
|
|
27
|
+
return skip_result if port_line_indexes.empty?
|
|
28
|
+
|
|
29
|
+
updated_content = rebuild_content(
|
|
30
|
+
replace_port_lines(lines, port_line_indexes),
|
|
31
|
+
trailing_newline: @content.end_with?("\n")
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
updated_result(updated_content)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
def updated_result(content)
|
|
40
|
+
Result.new(content, true, :updated, ['Updated config/puma.rb to prefer DEV_PORT before PORT.'])
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def identical_result(content)
|
|
44
|
+
Result.new(content, false, :identical, ['config/puma.rb already uses DEV_PORT-aware port binding.'])
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def skip_result
|
|
48
|
+
Result.new(
|
|
49
|
+
@content,
|
|
50
|
+
false,
|
|
51
|
+
:skip,
|
|
52
|
+
['No supported Puma port binding found in config/puma.rb; update it manually if needed.']
|
|
53
|
+
)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def dev_port_configured?(lines)
|
|
57
|
+
lines.any? do |line|
|
|
58
|
+
line.match?(PORT_LINE_PATTERN) &&
|
|
59
|
+
(line.match?(DEV_PORT_ACCESS_PATTERN) || line.match?(DEV_PORT_FETCH_PATTERN))
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def supported_port_line_indexes(lines)
|
|
64
|
+
lines.each_index.select { |index| supported_port_line?(lines[index]) }
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def supported_port_line?(line)
|
|
68
|
+
line.match?(CURRENT_PORT_PATTERN) || line.match?(LEGACY_PORT_PATTERN)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def replace_port_lines(lines, port_line_indexes)
|
|
72
|
+
lines.dup.tap do |updated_lines|
|
|
73
|
+
port_line_indexes.each do |index|
|
|
74
|
+
original_line = lines[index]
|
|
75
|
+
indent = original_line[/\A\s*/]
|
|
76
|
+
trailing_comment = original_line[/(\s*#.*)\z/, 1].to_s
|
|
77
|
+
|
|
78
|
+
updated_lines[index] = "#{indent}#{STANDARD_PORT_LINE}#{trailing_comment}"
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def rebuild_content(lines, trailing_newline:)
|
|
84
|
+
content = lines.join("\n")
|
|
85
|
+
return content if content.empty? || !trailing_newline
|
|
86
|
+
|
|
87
|
+
"#{content}\n"
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
data/lib/rails/worktrees.rb
CHANGED
|
@@ -6,6 +6,9 @@ require_relative 'worktrees/env_bootstrapper'
|
|
|
6
6
|
require_relative 'worktrees/command'
|
|
7
7
|
require_relative 'worktrees/cli'
|
|
8
8
|
require_relative 'worktrees/database_config_updater'
|
|
9
|
+
require_relative 'worktrees/procfile_updater'
|
|
10
|
+
require_relative 'worktrees/mise_toml_updater'
|
|
11
|
+
require_relative 'worktrees/puma_config_updater'
|
|
9
12
|
|
|
10
13
|
module Rails
|
|
11
14
|
# Rails-specific git worktree helpers and installer support.
|
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.2.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Asjer Querido
|
|
@@ -46,6 +46,7 @@ files:
|
|
|
46
46
|
- exe/wt
|
|
47
47
|
- lefthook.yml
|
|
48
48
|
- lib/generators/rails/worktrees/mise_follow_up.rb
|
|
49
|
+
- lib/generators/rails/worktrees/puma_follow_up.rb
|
|
49
50
|
- lib/generators/rails/worktrees/templates/Procfile.dev.worktree.example.tt
|
|
50
51
|
- lib/generators/rails/worktrees/templates/bin/wt
|
|
51
52
|
- lib/generators/rails/worktrees/templates/rails_worktrees.rb.tt
|
|
@@ -61,7 +62,10 @@ files:
|
|
|
61
62
|
- lib/rails/worktrees/configuration.rb
|
|
62
63
|
- lib/rails/worktrees/database_config_updater.rb
|
|
63
64
|
- lib/rails/worktrees/env_bootstrapper.rb
|
|
65
|
+
- lib/rails/worktrees/mise_toml_updater.rb
|
|
64
66
|
- lib/rails/worktrees/names/cities.txt
|
|
67
|
+
- lib/rails/worktrees/procfile_updater.rb
|
|
68
|
+
- lib/rails/worktrees/puma_config_updater.rb
|
|
65
69
|
- lib/rails/worktrees/railtie.rb
|
|
66
70
|
- lib/rails/worktrees/version.rb
|
|
67
71
|
- mise.toml
|