rails-worktrees 0.1.1 → 0.2.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 +7 -0
- data/README.md +15 -3
- data/lib/generators/rails/worktrees/mise_follow_up.rb +14 -4
- data/lib/generators/worktrees/install/install_generator.rb +69 -0
- data/lib/rails/worktrees/mise_toml_updater.rb +82 -0
- data/lib/rails/worktrees/procfile_updater.rb +69 -0
- data/lib/rails/worktrees/version.rb +1 -1
- data/lib/rails/worktrees.rb +2 -0
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5cf1debd660652a5429a2d5485e944006685268b423866a96e15af71f9cb3471
|
|
4
|
+
data.tar.gz: 14f6a3f5efaa6b829026b3d4d085c9b55d09236cb7b610bafa8f8834a534fb5b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e16d066bfb55c97b959e92b4c25d1b2ac08404a728cadf077852a937a1a4c00ea17ca0c08dcfecc571654f836ba0aeccc6f81b08b6a96f7a4443093681d3b25d
|
|
7
|
+
data.tar.gz: f7fdc3e954463fed68f62934d153fbe4b086fb86f4be97c8f8e172b54c0bcc4d402587b99c2b171cc024f7ef53be14e950da311399e49ef0a491d696ac15dc64
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{".":"0.
|
|
1
|
+
{".":"0.2.0"}
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.2.0](https://github.com/asjer/rails-worktrees/compare/v0.1.1...v0.2.0) (2026-03-30)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* add `--yolo` mode to install common follow-ups without manual edits ([1ec82ec](https://github.com/asjer/rails-worktrees/commit/1ec82ec83726b5ca9cbaee39697c607fdb825f26))
|
|
9
|
+
|
|
3
10
|
## [0.1.1](https://github.com/asjer/rails-worktrees/compare/v0.1.0...v0.1.1) (2026-03-30)
|
|
4
11
|
|
|
5
12
|
|
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 + mise follow-ups automatically:
|
|
17
|
+
bin/rails generate worktrees:install --yolo
|
|
16
18
|
```
|
|
17
19
|
|
|
18
20
|
The installer adds:
|
|
@@ -22,6 +24,11 @@ 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 `mise.toml` or `.mise.toml` to load `.env` from `[env]` when either file already exists
|
|
31
|
+
|
|
25
32
|
## Usage
|
|
26
33
|
|
|
27
34
|
```bash
|
|
@@ -115,12 +122,17 @@ When `bin/wt` creates a worktree it writes a worktree-local `.env` with:
|
|
|
115
122
|
|
|
116
123
|
Existing `.env` values are never overwritten.
|
|
117
124
|
|
|
118
|
-
|
|
125
|
+
By default, the installer does **not** edit your `Procfile.dev` or `mise` config. It generates `Procfile.dev.worktree.example` with a ready-to-copy line:
|
|
119
126
|
|
|
120
127
|
```text
|
|
121
128
|
web: env RUBY_DEBUG_OPEN=true bin/rails server -b 0.0.0.0 -p ${DEV_PORT:-3000}
|
|
122
129
|
```
|
|
123
130
|
|
|
131
|
+
If you run `bin/rails generate worktrees:install --yolo`, the installer applies the two common follow-ups for you when the files already exist:
|
|
132
|
+
|
|
133
|
+
- replace the existing `web:` entry in `Procfile.dev`
|
|
134
|
+
- add `_.file = ".env"` to the `[env]` section of `mise.toml` or `.mise.toml`
|
|
135
|
+
|
|
124
136
|
Use a project-local env loader like `mise` with `_.file = ".env"` to keep values scoped per-worktree.
|
|
125
137
|
|
|
126
138
|
## Development
|
|
@@ -141,8 +153,8 @@ This smoke test:
|
|
|
141
153
|
|
|
142
154
|
- creates a temporary Rails app from a compatible Rails version
|
|
143
155
|
- 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
|
|
156
|
+
- runs `bin/rails generate worktrees:install --yolo`
|
|
157
|
+
- verifies `bin/wt`, the generated initializer, the Procfile example, yolo updates to `Procfile.dev` and `mise.toml`, `config/database.yml` patching, and worktree `.env` bootstrapping
|
|
146
158
|
- creates a temporary bare `origin` and confirms `bin/wt smoke-branch` creates a real worktree
|
|
147
159
|
|
|
148
160
|
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
|
|
@@ -3,10 +3,13 @@ require 'rails/generators'
|
|
|
3
3
|
|
|
4
4
|
require_relative '../../rails/worktrees/mise_follow_up'
|
|
5
5
|
require_relative '../../../rails/worktrees/database_config_updater'
|
|
6
|
+
require_relative '../../../rails/worktrees/procfile_updater'
|
|
7
|
+
require_relative '../../../rails/worktrees/mise_toml_updater'
|
|
6
8
|
|
|
7
9
|
module Worktrees
|
|
8
10
|
module Generators
|
|
9
11
|
# Installs the wt wrapper, configuration, and safe database.yml updates.
|
|
12
|
+
# rubocop:disable Metrics/ClassLength
|
|
10
13
|
class InstallGenerator < ::Rails::Generators::Base
|
|
11
14
|
include ::Rails::Worktrees::Generators::MiseFollowUp
|
|
12
15
|
|
|
@@ -15,6 +18,8 @@ module Worktrees
|
|
|
15
18
|
source_root File.expand_path('../../rails/worktrees/templates', __dir__)
|
|
16
19
|
class_option :conductor, type: :boolean, default: false,
|
|
17
20
|
desc: 'Configure the installer for ~/Sites/conductor/workspaces'
|
|
21
|
+
class_option :yolo, type: :boolean, default: false,
|
|
22
|
+
desc: 'Apply common Procfile.dev and mise .env follow-up edits when safe'
|
|
18
23
|
|
|
19
24
|
FOLLOW_UP_TEMPLATE = <<~TEXT.freeze
|
|
20
25
|
============================================
|
|
@@ -46,6 +51,13 @@ module Worktrees
|
|
|
46
51
|
template('Procfile.dev.worktree.example.tt', 'Procfile.dev.worktree.example')
|
|
47
52
|
end
|
|
48
53
|
|
|
54
|
+
def apply_yolo_follow_ups
|
|
55
|
+
return unless options[:yolo]
|
|
56
|
+
|
|
57
|
+
update_procfile
|
|
58
|
+
update_mise_toml
|
|
59
|
+
end
|
|
60
|
+
|
|
49
61
|
def update_database_configuration
|
|
50
62
|
unless File.exist?(database_config_path)
|
|
51
63
|
say_status(:skip, 'config/database.yml not found', :yellow)
|
|
@@ -76,6 +88,17 @@ module Worktrees
|
|
|
76
88
|
File.join(destination_root, 'config/database.yml')
|
|
77
89
|
end
|
|
78
90
|
|
|
91
|
+
def procfile_path
|
|
92
|
+
File.join(destination_root, 'Procfile.dev')
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def mise_toml_paths
|
|
96
|
+
[
|
|
97
|
+
File.join(destination_root, 'mise.toml'),
|
|
98
|
+
File.join(destination_root, '.mise.toml')
|
|
99
|
+
]
|
|
100
|
+
end
|
|
101
|
+
|
|
79
102
|
def database_update_result
|
|
80
103
|
result = ::Rails::Worktrees::DatabaseConfigUpdater.new(
|
|
81
104
|
content: File.read(database_config_path)
|
|
@@ -117,6 +140,51 @@ module Worktrees
|
|
|
117
140
|
result.messages.each { |message| say(message) }
|
|
118
141
|
end
|
|
119
142
|
|
|
143
|
+
def update_procfile
|
|
144
|
+
unless File.exist?(procfile_path)
|
|
145
|
+
say_status(:skip, 'Procfile.dev not found', :yellow)
|
|
146
|
+
say('Skipped Procfile.dev yolo update because the file does not exist yet.')
|
|
147
|
+
return
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
result = ::Rails::Worktrees::ProcfileUpdater.new(content: File.read(procfile_path)).call
|
|
151
|
+
File.write(procfile_path, result.content) if result.changed?
|
|
152
|
+
announce_updater_result('Procfile.dev', result)
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def update_mise_toml
|
|
156
|
+
path = first_mise_toml_path
|
|
157
|
+
return announce_missing_mise_toml unless path
|
|
158
|
+
|
|
159
|
+
result = ::Rails::Worktrees::MiseTomlUpdater.new(
|
|
160
|
+
content: File.read(path),
|
|
161
|
+
file_name: File.basename(path)
|
|
162
|
+
).call
|
|
163
|
+
|
|
164
|
+
File.write(path, result.content) if result.changed?
|
|
165
|
+
announce_updater_result(File.basename(path), result)
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def announce_updater_result(path, result)
|
|
169
|
+
status, color = case result.status
|
|
170
|
+
when :updated then %i[update green]
|
|
171
|
+
when :identical then %i[identical blue]
|
|
172
|
+
else %i[skip yellow]
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
say_status(status, path, color)
|
|
176
|
+
result.messages.each { |message| say(message) }
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def first_mise_toml_path
|
|
180
|
+
mise_toml_paths.find { |candidate| File.file?(candidate) }
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def announce_missing_mise_toml
|
|
184
|
+
say_status(:skip, 'mise.toml/.mise.toml not found', :yellow)
|
|
185
|
+
say('Skipped mise yolo update because no supported mise config file was found.')
|
|
186
|
+
end
|
|
187
|
+
|
|
120
188
|
def git_repo?
|
|
121
189
|
_stdout_str, _stderr_str, status = Open3.capture3(
|
|
122
190
|
'git', 'rev-parse', '--is-inside-work-tree', chdir: destination_root
|
|
@@ -130,5 +198,6 @@ module Worktrees
|
|
|
130
198
|
"File.expand_path('~/Sites/conductor/workspaces')"
|
|
131
199
|
end
|
|
132
200
|
end
|
|
201
|
+
# rubocop:enable Metrics/ClassLength
|
|
133
202
|
end
|
|
134
203
|
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
|
data/lib/rails/worktrees.rb
CHANGED
|
@@ -6,6 +6,8 @@ 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'
|
|
9
11
|
|
|
10
12
|
module Rails
|
|
11
13
|
# 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.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Asjer Querido
|
|
@@ -61,7 +61,9 @@ files:
|
|
|
61
61
|
- lib/rails/worktrees/configuration.rb
|
|
62
62
|
- lib/rails/worktrees/database_config_updater.rb
|
|
63
63
|
- lib/rails/worktrees/env_bootstrapper.rb
|
|
64
|
+
- lib/rails/worktrees/mise_toml_updater.rb
|
|
64
65
|
- lib/rails/worktrees/names/cities.txt
|
|
66
|
+
- lib/rails/worktrees/procfile_updater.rb
|
|
65
67
|
- lib/rails/worktrees/railtie.rb
|
|
66
68
|
- lib/rails/worktrees/version.rb
|
|
67
69
|
- mise.toml
|