rails-worktrees 0.6.0 → 0.7.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 +33 -1
- data/lib/rails/worktrees/cli.rb +22 -2
- data/lib/rails/worktrees/command/environment_support.rb +1 -0
- data/lib/rails/worktrees/command/output.rb +9 -0
- data/lib/rails/worktrees/command/post_create_support.rb +7 -6
- data/lib/rails/worktrees/command/workspace_paths.rb +15 -3
- data/lib/rails/worktrees/command.rb +206 -15
- data/lib/rails/worktrees/env_bootstrapper.rb +49 -5
- data/lib/rails/worktrees/mise_environment.rb +87 -0
- data/lib/rails/worktrees/post_create_runner.rb +31 -7
- data/lib/rails/worktrees/version.rb +1 -1
- data/lib/rails/worktrees.rb +1 -0
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e86a789b2e56f07d95a626b67c9fafbf01862e9c8496043107914b4446e13702
|
|
4
|
+
data.tar.gz: 9f0dce854a6c7a20dfedb00dd181f7ecc5699ae6cf86ff61d24d60bce80be11e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 16513989c4364e4cf37d4eb0cf1fbde3fc95d8aa246223b5e9cca20da0035266d8f33e81a115d83b95b359ff93292796ba88889f06495c9d8b39fde11eb8d5b4
|
|
7
|
+
data.tar.gz: 1bcdfe219ef78dadeeaf7a8de8cc71662de4e0066b6bebf9941aa59d86c9349a38e3caf0671693dc321b54b672618e7b9b772994efb48511628f77718345aceb
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{".":"0.
|
|
1
|
+
{".":"0.7.0"}
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.7.0](https://github.com/asjer/rails-worktrees/compare/v0.6.0...v0.7.0) (2026-04-05)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* **wt:** add rerunnable setup workflow ([a609fcc](https://github.com/asjer/rails-worktrees/commit/a609fcc3c463da1d2ad9922b620f9c517dc6ed74))
|
|
9
|
+
|
|
3
10
|
## [0.6.0](https://github.com/asjer/rails-worktrees/compare/v0.5.1...v0.6.0) (2026-04-03)
|
|
4
11
|
|
|
5
12
|
|
data/README.md
CHANGED
|
@@ -42,8 +42,12 @@ With `--yolo`, the installer also:
|
|
|
42
42
|
```bash
|
|
43
43
|
bin/wt # auto-pick a name from bundled *.txt lists
|
|
44
44
|
bin/wt my-feature # use an explicit worktree name
|
|
45
|
+
bin/wt --skip-setup my-feature # create now, run setup later
|
|
45
46
|
bin/wt --dry-run my-feature # preview the full setup without changing anything
|
|
46
47
|
bin/wt --print-env my-feature # preview DEV_PORT and WORKTREE_DATABASE_SUFFIX
|
|
48
|
+
bin/wt setup # rerun setup for the current checkout/worktree
|
|
49
|
+
bin/wt setup my-feature # rerun setup for a managed worktree by name
|
|
50
|
+
bin/wt setup ../my-project.worktrees/my-feature # setup a specific checkout without cd-ing into it
|
|
47
51
|
bin/wt doctor # audit install/config drift and basic worktree health
|
|
48
52
|
bin/wt update --dry-run # preview safe maintenance fixes
|
|
49
53
|
bin/wt update # apply safe maintenance fixes for managed files
|
|
@@ -65,6 +69,7 @@ bin/ob --print-url '?from=nav' # print the resolved URL without opening a brows
|
|
|
65
69
|
| `-h`, `--help` | Show the help message |
|
|
66
70
|
| `-v`, `--version` | Show the script version |
|
|
67
71
|
| `--dry-run [name]` | Preview worktree creation or cleanup without changing anything |
|
|
72
|
+
| `--skip-setup` | Create a worktree without running setup steps |
|
|
68
73
|
| `--force` | Force branch deletion for `bin/wt remove` / `bin/wt delete` |
|
|
69
74
|
| `--env`, `--print-env <name>` | Preview `DEV_PORT` and `WORKTREE_DATABASE_SUFFIX` |
|
|
70
75
|
|
|
@@ -78,6 +83,7 @@ By default `bin/wt`:
|
|
|
78
83
|
- auto-picks names from bundled `.txt` files when no explicit name is given
|
|
79
84
|
- retires bundled names so they are not picked twice
|
|
80
85
|
- bootstraps a worktree-local `.env` with deterministic `DEV_PORT` and `WORKTREE_DATABASE_SUFFIX` values
|
|
86
|
+
- runs setup automatically after creation: credential linking, `bundle install`, `yarn install` when applicable, both `db:prepare` steps, test asset precompile, and a final `bin/rails assets:clobber`
|
|
81
87
|
|
|
82
88
|
```text
|
|
83
89
|
workspace/
|
|
@@ -90,6 +96,32 @@ workspace/
|
|
|
90
96
|
|
|
91
97
|
`WT_WORKSPACES_ROOT` or `config.workspace_root` overrides the destination root and uses the layout `<root>/<project>/<name>`.
|
|
92
98
|
|
|
99
|
+
### Setup command
|
|
100
|
+
|
|
101
|
+
`bin/wt setup` reruns setup for the **current checkout**. Run it from inside a linked worktree created by `bin/wt`, a worktree created manually with `git worktree`, or a checkout prepared by another tool.
|
|
102
|
+
|
|
103
|
+
If the checkout was created by `bin/wt`, you can also point at it by managed worktree name from the main app checkout:
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
bin/wt setup my-feature
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
If you do not want to `cd` first, pass an explicit checkout path:
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
bin/wt setup ../my-project.worktrees/my-feature
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
This is the recovery path when you want to create first and bootstrap later:
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
bin/wt --skip-setup my-feature
|
|
119
|
+
cd ../my-project.worktrees/my-feature
|
|
120
|
+
bin/wt setup
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
`bin/wt setup --dry-run` previews the same steps without changing files.
|
|
124
|
+
|
|
93
125
|
### Cleanup commands
|
|
94
126
|
|
|
95
127
|
`bin/wt` also supports cleanup commands for worktrees it manages:
|
|
@@ -181,7 +213,7 @@ If your `database.yml` is too custom to patch safely, the installer leaves it al
|
|
|
181
213
|
When `bin/wt` creates a worktree it writes a worktree-local `.env` with:
|
|
182
214
|
|
|
183
215
|
- `DEV_PORT` — deterministic port derived from the worktree name via CRC32, rotated through `dev_port_range`, skipping ports already claimed by peer worktrees
|
|
184
|
-
- `WORKTREE_DATABASE_SUFFIX` — derived from the worktree name
|
|
216
|
+
- `WORKTREE_DATABASE_SUFFIX` — derived from the best available worktree identity (managed name when known, otherwise the current branch or checkout basename). When a readable suffix is already claimed by a peer checkout, `bin/wt` appends a short `DEV_PORT`-based token to keep the databases isolated.
|
|
185
217
|
|
|
186
218
|
Existing `.env` values are never overwritten.
|
|
187
219
|
|
data/lib/rails/worktrees/cli.rb
CHANGED
|
@@ -3,7 +3,8 @@ 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].freeze
|
|
6
|
+
LOADER_IGNORED_FLAGS = %w[--dry-run --force --skip-setup].freeze
|
|
7
|
+
SETUP_SUBCOMMAND = 'setup'.freeze
|
|
7
8
|
|
|
8
9
|
def initialize(
|
|
9
10
|
argv: ARGV,
|
|
@@ -29,11 +30,17 @@ module Rails
|
|
|
29
30
|
private
|
|
30
31
|
|
|
31
32
|
def load_project_configuration(configuration)
|
|
32
|
-
::Rails::Worktrees::ProjectConfigurationLoader.new(root:
|
|
33
|
+
::Rails::Worktrees::ProjectConfigurationLoader.new(root: configuration_root, configuration: configuration).call
|
|
33
34
|
rescue StandardError, ScriptError => e
|
|
34
35
|
raise ::Rails::Worktrees::Error, "Failed to load worktrees configuration: #{e.class}: #{e.message}"
|
|
35
36
|
end
|
|
36
37
|
|
|
38
|
+
def configuration_root
|
|
39
|
+
return expand_setup_target_path if explicit_setup_path_target?
|
|
40
|
+
|
|
41
|
+
@cwd
|
|
42
|
+
end
|
|
43
|
+
|
|
37
44
|
def should_load_project_configuration?
|
|
38
45
|
argv_without_flags.empty? || !loader_optional_command?(argv_without_flags.first)
|
|
39
46
|
end
|
|
@@ -46,6 +53,19 @@ module Rails
|
|
|
46
53
|
LOADER_OPTIONAL_COMMANDS.include?(command)
|
|
47
54
|
end
|
|
48
55
|
|
|
56
|
+
def explicit_setup_path_target?
|
|
57
|
+
argv_without_flags.first == SETUP_SUBCOMMAND && argv_without_flags.length == 2 &&
|
|
58
|
+
path_like_setup_target?(argv_without_flags.last)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def expand_setup_target_path
|
|
62
|
+
File.expand_path(argv_without_flags.last, @cwd)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def path_like_setup_target?(value)
|
|
66
|
+
value.start_with?('/', '.', '~') || value.include?(File::SEPARATOR)
|
|
67
|
+
end
|
|
68
|
+
|
|
49
69
|
def command_for(configuration)
|
|
50
70
|
Command.new(argv: @argv, io: @io, env: @env, cwd: @cwd, configuration: configuration)
|
|
51
71
|
end
|
|
@@ -30,9 +30,11 @@ module Rails
|
|
|
30
30
|
Create and clean up Git worktrees for the current repository.
|
|
31
31
|
|
|
32
32
|
Usage: wt [worktree-name]
|
|
33
|
+
wt [--skip-setup] [worktree-name]
|
|
33
34
|
wt --dry-run [worktree-name]
|
|
34
35
|
wt --print-env <worktree-name>
|
|
35
36
|
wt doctor
|
|
37
|
+
wt setup [--dry-run] [path|name]
|
|
36
38
|
wt update [--dry-run]
|
|
37
39
|
wt remove [--dry-run] [--force] <worktree-name>
|
|
38
40
|
wt delete [--dry-run] [--force] <worktree-name>
|
|
@@ -42,6 +44,7 @@ module Rails
|
|
|
42
44
|
-h, --help Show this help message
|
|
43
45
|
-v, --version Show the script version
|
|
44
46
|
--dry-run [name] Preview worktree creation or cleanup without changing anything
|
|
47
|
+
--skip-setup Create the worktree without running setup steps
|
|
45
48
|
--force Delete an unmerged local branch with wt remove/delete
|
|
46
49
|
--env, --print-env <name> Preview DEV_PORT and WORKTREE_DATABASE_SUFFIX
|
|
47
50
|
|
|
@@ -49,8 +52,12 @@ module Rails
|
|
|
49
52
|
wt Auto-pick a name from a bundled *.txt list
|
|
50
53
|
wt my-feature Use an explicit worktree name
|
|
51
54
|
wt --dry-run my-feature
|
|
55
|
+
wt --skip-setup my-feature
|
|
52
56
|
wt --print-env my-feature
|
|
53
57
|
wt doctor
|
|
58
|
+
wt setup
|
|
59
|
+
wt setup my-feature
|
|
60
|
+
wt setup ../my-project.worktrees/my-feature
|
|
54
61
|
wt update --dry-run
|
|
55
62
|
wt remove my-feature
|
|
56
63
|
wt remove --force my-feature
|
|
@@ -61,6 +68,8 @@ module Rails
|
|
|
61
68
|
- when workspace_root or WT_WORKSPACES_ROOT is set, creates worktrees in <root>/<project>/<name>
|
|
62
69
|
- always uses the branch name #{@configuration.branch_prefix}/<name>
|
|
63
70
|
- bases new branches on the repository's origin default branch
|
|
71
|
+
- by default wt <name> both creates the worktree and runs setup automatically
|
|
72
|
+
- wt setup reruns setup for the current checkout, a managed worktree name, or a specific checkout path, including manually-created worktrees
|
|
64
73
|
- wt doctor audits install/config drift plus basic worktree health without changing files
|
|
65
74
|
- wt update applies safe file-based fixes for managed installer artifacts and config hints
|
|
66
75
|
- wt remove/delete can run from the main checkout or any sibling worktree, but never remove the worktree you're currently in
|
|
@@ -5,19 +5,20 @@ module Rails
|
|
|
5
5
|
module PostCreateSupport
|
|
6
6
|
private
|
|
7
7
|
|
|
8
|
-
def run_post_create_steps(context)
|
|
9
|
-
post_create_runner_for(context).call(dry_run: false)
|
|
8
|
+
def run_post_create_steps(context, bootstrapped_env: nil)
|
|
9
|
+
post_create_runner_for(context, bootstrapped_env: bootstrapped_env).call(dry_run: false)
|
|
10
10
|
end
|
|
11
11
|
|
|
12
|
-
def preview_post_create_steps(context)
|
|
13
|
-
post_create_runner_for(context).call(dry_run: true)
|
|
12
|
+
def preview_post_create_steps(context, bootstrapped_env: nil)
|
|
13
|
+
post_create_runner_for(context, bootstrapped_env: bootstrapped_env).call(dry_run: true)
|
|
14
14
|
end
|
|
15
15
|
|
|
16
|
-
def post_create_runner_for(context)
|
|
16
|
+
def post_create_runner_for(context, bootstrapped_env: nil)
|
|
17
17
|
PostCreateRunner.new(
|
|
18
18
|
target_dir: context[:target_dir],
|
|
19
|
-
peer_roots: peer_roots_excluding(context[:target_dir]),
|
|
19
|
+
peer_roots: context.fetch(:peer_roots) { peer_roots_excluding(context[:target_dir]) },
|
|
20
20
|
configuration: @configuration,
|
|
21
|
+
bootstrapped_env: bootstrapped_env,
|
|
21
22
|
io: { stdout: @stdout, stderr: @stderr }
|
|
22
23
|
)
|
|
23
24
|
end
|
|
@@ -7,7 +7,19 @@ module Rails
|
|
|
7
7
|
|
|
8
8
|
def resolve_repository_context
|
|
9
9
|
current_root = canonical_path(git_capture('rev-parse', '--show-toplevel').strip)
|
|
10
|
-
common_dir = expand_git_path(git_capture('rev-parse', '--git-common-dir').strip)
|
|
10
|
+
common_dir = expand_git_path(git_capture('rev-parse', '--git-common-dir').strip, base_dir: @cwd)
|
|
11
|
+
primary_root = primary_checkout_root_for(current_root, common_dir)
|
|
12
|
+
|
|
13
|
+
repository_context_for(current_root, primary_root)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def resolve_repository_context_for(path)
|
|
17
|
+
expanded_path = File.expand_path(path, @cwd)
|
|
18
|
+
current_root = canonical_path(git_capture('-C', expanded_path, 'rev-parse', '--show-toplevel').strip)
|
|
19
|
+
common_dir = expand_git_path(
|
|
20
|
+
git_capture('-C', expanded_path, 'rev-parse', '--git-common-dir').strip,
|
|
21
|
+
base_dir: expanded_path
|
|
22
|
+
)
|
|
11
23
|
primary_root = primary_checkout_root_for(current_root, common_dir)
|
|
12
24
|
|
|
13
25
|
repository_context_for(current_root, primary_root)
|
|
@@ -66,10 +78,10 @@ module Rails
|
|
|
66
78
|
canonical_path(File.dirname(common_dir))
|
|
67
79
|
end
|
|
68
80
|
|
|
69
|
-
def expand_git_path(path)
|
|
81
|
+
def expand_git_path(path, base_dir: @cwd)
|
|
70
82
|
return path if path.start_with?('/')
|
|
71
83
|
|
|
72
|
-
File.expand_path(path,
|
|
84
|
+
File.expand_path(path, base_dir)
|
|
73
85
|
end
|
|
74
86
|
|
|
75
87
|
def present_path?(path)
|
|
@@ -13,6 +13,7 @@ module Rails
|
|
|
13
13
|
class Command
|
|
14
14
|
REMOVE_SUBCOMMANDS = %w[remove delete].freeze
|
|
15
15
|
DOCTOR_SUBCOMMAND = 'doctor'.freeze
|
|
16
|
+
SETUP_SUBCOMMAND = 'setup'.freeze
|
|
16
17
|
UPDATE_SUBCOMMAND = 'update'.freeze
|
|
17
18
|
|
|
18
19
|
include GitOperations
|
|
@@ -49,9 +50,12 @@ module Rails
|
|
|
49
50
|
|
|
50
51
|
def force? = @force
|
|
51
52
|
|
|
53
|
+
def skip_setup? = @skip_setup
|
|
54
|
+
|
|
52
55
|
def extract_flags!
|
|
53
56
|
@dry_run = extract_flag!('--dry-run')
|
|
54
57
|
@force = extract_flag!('--force')
|
|
58
|
+
@skip_setup = extract_flag!('--skip-setup')
|
|
55
59
|
end
|
|
56
60
|
|
|
57
61
|
def extract_flag!(flag)
|
|
@@ -77,20 +81,37 @@ module Rails
|
|
|
77
81
|
@argv.first == DOCTOR_SUBCOMMAND
|
|
78
82
|
end
|
|
79
83
|
|
|
84
|
+
def setup_subcommand?
|
|
85
|
+
@argv.first == SETUP_SUBCOMMAND
|
|
86
|
+
end
|
|
87
|
+
|
|
80
88
|
def update_subcommand?
|
|
81
89
|
@argv.first == UPDATE_SUBCOMMAND
|
|
82
90
|
end
|
|
83
91
|
|
|
84
92
|
def execute_requested_command
|
|
85
|
-
|
|
86
|
-
return
|
|
87
|
-
return
|
|
88
|
-
return execute_prune_command if prune_subcommand?
|
|
89
|
-
return usage_error if @argv.length > 1 || force?
|
|
93
|
+
handler = requested_command_handler
|
|
94
|
+
return handler.call if handler
|
|
95
|
+
return usage_error if invalid_worktree_command?
|
|
90
96
|
|
|
91
97
|
execute_worktree_command
|
|
92
98
|
end
|
|
93
99
|
|
|
100
|
+
def requested_command_handler
|
|
101
|
+
case @argv.first
|
|
102
|
+
when DOCTOR_SUBCOMMAND then -> { execute_doctor_command }
|
|
103
|
+
when SETUP_SUBCOMMAND then -> { execute_setup_command }
|
|
104
|
+
when UPDATE_SUBCOMMAND then -> { execute_update_command }
|
|
105
|
+
when 'prune' then -> { execute_prune_command }
|
|
106
|
+
else
|
|
107
|
+
-> { execute_remove_command } if remove_subcommand?
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def invalid_worktree_command?
|
|
112
|
+
@argv.length > 1 || force?
|
|
113
|
+
end
|
|
114
|
+
|
|
94
115
|
def execute_worktree_command
|
|
95
116
|
require_git_repo
|
|
96
117
|
announce_dry_run if dry_run?
|
|
@@ -107,6 +128,16 @@ module Rails
|
|
|
107
128
|
finish(context)
|
|
108
129
|
end
|
|
109
130
|
|
|
131
|
+
def execute_setup_command
|
|
132
|
+
validate_setup_args!
|
|
133
|
+
announce_dry_run if dry_run?
|
|
134
|
+
|
|
135
|
+
context = resolve_setup_context(target_reference: setup_target_reference)
|
|
136
|
+
validate_setup_target!(context)
|
|
137
|
+
|
|
138
|
+
complete_setup_flow(context, success_message: 'Setup complete')
|
|
139
|
+
end
|
|
140
|
+
|
|
110
141
|
def execute_remove_command
|
|
111
142
|
require_git_repo
|
|
112
143
|
announce_dry_run if dry_run?
|
|
@@ -210,17 +241,26 @@ module Rails
|
|
|
210
241
|
def validate_prune_args!
|
|
211
242
|
raise Error, 'Usage: wt prune' unless @argv.length == 1
|
|
212
243
|
raise Error, 'The --force flag is only supported with wt remove.' if force?
|
|
244
|
+
raise Error, 'The --skip-setup flag is only supported when creating a worktree.' if skip_setup?
|
|
213
245
|
end
|
|
214
246
|
|
|
215
247
|
def validate_doctor_args!
|
|
216
248
|
raise Error, 'Usage: wt doctor' unless @argv.length == 1
|
|
217
249
|
raise Error, 'wt doctor does not support --dry-run.' if dry_run?
|
|
218
250
|
raise Error, 'The --force flag is only supported with wt remove.' if force?
|
|
251
|
+
raise Error, 'The --skip-setup flag is only supported when creating a worktree.' if skip_setup?
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
def validate_setup_args!
|
|
255
|
+
raise Error, 'Usage: wt setup [--dry-run] [path|name]' unless [1, 2].include?(@argv.length)
|
|
256
|
+
raise Error, 'The --force flag is only supported with wt remove.' if force?
|
|
257
|
+
raise Error, 'The --skip-setup flag is only supported when creating a worktree.' if skip_setup?
|
|
219
258
|
end
|
|
220
259
|
|
|
221
260
|
def validate_update_args!
|
|
222
261
|
raise Error, 'Usage: wt update [--dry-run]' unless @argv.length == 1
|
|
223
262
|
raise Error, 'The --force flag is only supported with wt remove.' if force?
|
|
263
|
+
raise Error, 'The --skip-setup flag is only supported when creating a worktree.' if skip_setup?
|
|
224
264
|
end
|
|
225
265
|
|
|
226
266
|
def announce_prune_candidates(candidates)
|
|
@@ -258,6 +298,60 @@ module Rails
|
|
|
258
298
|
)
|
|
259
299
|
end
|
|
260
300
|
|
|
301
|
+
def resolve_setup_context(target_reference: nil, repository: nil)
|
|
302
|
+
if setup_target_name?(target_reference)
|
|
303
|
+
return resolve_named_setup_context(target_reference, repository: repository)
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
resolve_path_setup_context(target_reference, repository: repository)
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
def resolve_path_setup_context(target_reference, repository: nil)
|
|
310
|
+
target_dir = resolved_setup_target_path(target_reference)
|
|
311
|
+
repository ||= target_reference ? resolve_repository_context_for(target_dir) : resolve_repository_context
|
|
312
|
+
branch_name = current_checkout_branch_at(target_dir)
|
|
313
|
+
|
|
314
|
+
repository.merge(
|
|
315
|
+
worktree_name: setup_identity_for(repository[:current_root], branch_name),
|
|
316
|
+
branch_name: display_branch_name(branch_name, target_dir: repository[:current_root]),
|
|
317
|
+
target_dir: repository[:current_root],
|
|
318
|
+
peer_roots: peer_roots_for(repository[:current_root])
|
|
319
|
+
)
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
def resolve_named_setup_context(worktree_name, repository: nil)
|
|
323
|
+
repository ||= resolve_repository_context
|
|
324
|
+
context = resolve_worktree_context(explicit_worktree_name: worktree_name, repository: repository)
|
|
325
|
+
branch_name = resolved_named_setup_branch_name(context)
|
|
326
|
+
|
|
327
|
+
context.merge(
|
|
328
|
+
branch_name: display_branch_name(branch_name, target_dir: context[:target_dir]),
|
|
329
|
+
peer_roots: peer_roots_for(context[:target_dir])
|
|
330
|
+
)
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
def resolved_named_setup_branch_name(context)
|
|
334
|
+
return context[:branch_name] unless File.directory?(context[:target_dir])
|
|
335
|
+
|
|
336
|
+
current_checkout_branch_at(context[:target_dir])
|
|
337
|
+
end
|
|
338
|
+
|
|
339
|
+
def setup_target_reference = @argv[1]
|
|
340
|
+
|
|
341
|
+
def setup_target_name?(target_reference)
|
|
342
|
+
!target_reference.nil? && !path_like_setup_target?(target_reference)
|
|
343
|
+
end
|
|
344
|
+
|
|
345
|
+
def resolved_setup_target_path(target_path)
|
|
346
|
+
return @cwd if target_path.nil?
|
|
347
|
+
|
|
348
|
+
File.expand_path(target_path, @cwd)
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
def path_like_setup_target?(target_reference)
|
|
352
|
+
target_reference.start_with?('/', '.', '~') || target_reference.include?(File::SEPARATOR)
|
|
353
|
+
end
|
|
354
|
+
|
|
261
355
|
def resolved_worktree_name(project_name, workspaces, explicit_worktree_name)
|
|
262
356
|
return validate_worktree_name(explicit_worktree_name) if explicit_worktree_name
|
|
263
357
|
|
|
@@ -285,27 +379,52 @@ module Rails
|
|
|
285
379
|
|
|
286
380
|
def finish(context)
|
|
287
381
|
settle_retired_name(context[:worktree_name], context[:project_name], dry_run: dry_run?)
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
382
|
+
complete_setup_flow(
|
|
383
|
+
context,
|
|
384
|
+
success_message: skip_setup? ? 'Worktree created' : 'Worktree ready',
|
|
385
|
+
run_post_create: !skip_setup?
|
|
386
|
+
)
|
|
293
387
|
end
|
|
294
388
|
|
|
295
|
-
def complete_dry_run_after_setup(context, bootstrap_result)
|
|
296
|
-
preview_post_create_steps(context)
|
|
389
|
+
def complete_dry_run_after_setup(context, bootstrap_result, run_post_create: true)
|
|
390
|
+
preview_post_create_steps(context, bootstrapped_env: bootstrap_result&.values) if run_post_create
|
|
391
|
+
info('Would skip setup steps; run `wt setup` inside the checkout when you are ready.') unless run_post_create
|
|
297
392
|
complete_dry_run(context, env_values: bootstrap_result&.values)
|
|
298
393
|
end
|
|
299
394
|
|
|
300
|
-
def complete_created_worktree(context, bootstrap_result)
|
|
301
|
-
|
|
395
|
+
def complete_created_worktree(context, bootstrap_result, success_message:, run_post_create: true)
|
|
396
|
+
unless run_post_create
|
|
397
|
+
return complete_created_worktree_without_setup(context, bootstrap_result, success_message)
|
|
398
|
+
end
|
|
399
|
+
|
|
400
|
+
result = run_post_create_steps(context, bootstrapped_env: bootstrap_result&.values)
|
|
302
401
|
return result unless result.zero?
|
|
303
402
|
|
|
304
|
-
success(
|
|
403
|
+
success(success_message)
|
|
305
404
|
print_context_summary(context, env_values: bootstrap_result&.values)
|
|
306
405
|
0
|
|
307
406
|
end
|
|
308
407
|
|
|
408
|
+
def complete_created_worktree_without_setup(context, bootstrap_result, success_message)
|
|
409
|
+
success(success_message)
|
|
410
|
+
print_context_summary(context, env_values: bootstrap_result&.values)
|
|
411
|
+
info('Setup skipped. Run `wt setup` inside the checkout when you are ready.')
|
|
412
|
+
0
|
|
413
|
+
end
|
|
414
|
+
|
|
415
|
+
def complete_setup_flow(context, success_message:, run_post_create: true)
|
|
416
|
+
bootstrap_result = bootstrap_worktree_environment(context)
|
|
417
|
+
|
|
418
|
+
return complete_dry_run_after_setup(context, bootstrap_result, run_post_create: run_post_create) if dry_run?
|
|
419
|
+
|
|
420
|
+
complete_created_worktree(
|
|
421
|
+
context,
|
|
422
|
+
bootstrap_result,
|
|
423
|
+
success_message: success_message,
|
|
424
|
+
run_post_create: run_post_create
|
|
425
|
+
)
|
|
426
|
+
end
|
|
427
|
+
|
|
309
428
|
def finish_reuse(context)
|
|
310
429
|
target, branch = context.values_at(:target_dir, :branch_name)
|
|
311
430
|
|
|
@@ -338,6 +457,78 @@ module Rails
|
|
|
338
457
|
"#{@configuration.branch_prefix}/#{worktree_name}"
|
|
339
458
|
end
|
|
340
459
|
|
|
460
|
+
def validate_setup_target!(context)
|
|
461
|
+
raise Error, "Setup target does not exist: #{context[:target_dir]}" unless File.directory?(context[:target_dir])
|
|
462
|
+
|
|
463
|
+
rails_bin = File.join(context[:target_dir], 'bin', 'rails')
|
|
464
|
+
|
|
465
|
+
unless File.exist?(rails_bin)
|
|
466
|
+
raise Error,
|
|
467
|
+
"Setup target does not look like a Rails app. Expected #{rails_bin} to exist before running wt setup."
|
|
468
|
+
end
|
|
469
|
+
|
|
470
|
+
return if registered_worktree_path_for_setup?(context[:target_dir])
|
|
471
|
+
|
|
472
|
+
warning(
|
|
473
|
+
'Current checkout is not registered in git worktree list; peer credential key discovery may be limited.'
|
|
474
|
+
)
|
|
475
|
+
end
|
|
476
|
+
|
|
477
|
+
def current_checkout_branch_at(target_dir)
|
|
478
|
+
git_capture('-C', target_dir, 'branch', '--show-current', allow_failure: true).strip
|
|
479
|
+
end
|
|
480
|
+
|
|
481
|
+
def registered_worktree_path_for_setup?(target_dir)
|
|
482
|
+
normalized_target = canonical_path(target_dir)
|
|
483
|
+
|
|
484
|
+
worktree_entries_for_checkout(target_dir).any? { |entry| entry[:path] == normalized_target }
|
|
485
|
+
end
|
|
486
|
+
|
|
487
|
+
def peer_roots_for(target_dir)
|
|
488
|
+
normalized_target = canonical_path(target_dir)
|
|
489
|
+
|
|
490
|
+
worktree_entries_for_checkout(target_dir)
|
|
491
|
+
.map { |entry| entry[:path] }
|
|
492
|
+
.reject { |path| path == normalized_target }
|
|
493
|
+
end
|
|
494
|
+
|
|
495
|
+
def worktree_entries_for_checkout(target_dir)
|
|
496
|
+
git_capture('-C', target_dir, 'worktree', 'list', '--porcelain', allow_failure: true)
|
|
497
|
+
.split("\n\n")
|
|
498
|
+
.filter_map do |block|
|
|
499
|
+
entry = parse_worktree_entry(block)
|
|
500
|
+
entry[:path] ? entry : nil
|
|
501
|
+
end
|
|
502
|
+
end
|
|
503
|
+
|
|
504
|
+
def setup_identity_for(target_dir, branch_name)
|
|
505
|
+
stripped_branch = worktree_identity_from_branch(branch_name)
|
|
506
|
+
return stripped_branch unless stripped_branch.empty?
|
|
507
|
+
|
|
508
|
+
File.basename(target_dir)
|
|
509
|
+
end
|
|
510
|
+
|
|
511
|
+
def worktree_identity_from_branch(branch_name)
|
|
512
|
+
branch_name = branch_name.to_s.strip
|
|
513
|
+
return '' if branch_name.empty?
|
|
514
|
+
|
|
515
|
+
worktree_name_for_branch(branch_name)
|
|
516
|
+
end
|
|
517
|
+
|
|
518
|
+
def display_branch_name(branch_name, target_dir: nil)
|
|
519
|
+
branch_name = branch_name.to_s.strip
|
|
520
|
+
return branch_name unless branch_name.empty?
|
|
521
|
+
|
|
522
|
+
git_args = if target_dir.to_s.strip.empty?
|
|
523
|
+
['rev-parse', '--short', 'HEAD']
|
|
524
|
+
else
|
|
525
|
+
['-C', target_dir, 'rev-parse', '--short', 'HEAD']
|
|
526
|
+
end
|
|
527
|
+
|
|
528
|
+
short_sha = git_capture(*git_args, allow_failure: true).strip
|
|
529
|
+
short_sha.empty? ? '(detached HEAD)' : "(detached HEAD at #{short_sha})"
|
|
530
|
+
end
|
|
531
|
+
|
|
341
532
|
def ensure_removable!(context, worktree_exists:, branch_exists:)
|
|
342
533
|
ensure_remove_target_exists!(context, worktree_exists: worktree_exists, branch_exists: branch_exists)
|
|
343
534
|
ensure_not_removing_protected_checkout!(context)
|
|
@@ -14,10 +14,11 @@ module Rails
|
|
|
14
14
|
|
|
15
15
|
ENV_FILE_NAME = '.env'.freeze
|
|
16
16
|
|
|
17
|
-
def initialize(target_dir:, worktree_name:, configuration:)
|
|
17
|
+
def initialize(target_dir:, worktree_name:, configuration:, peer_roots: nil)
|
|
18
18
|
@target_dir = target_dir
|
|
19
19
|
@worktree_name = worktree_name
|
|
20
20
|
@configuration = configuration
|
|
21
|
+
@peer_roots = peer_roots.nil? ? nil : Array(peer_roots).compact
|
|
21
22
|
end
|
|
22
23
|
|
|
23
24
|
def call(dry_run: false)
|
|
@@ -46,10 +47,12 @@ module Rails
|
|
|
46
47
|
def existing_env_lines = File.exist?(env_path) ? File.readlines(env_path, chomp: true) : []
|
|
47
48
|
|
|
48
49
|
def resolved_values(lines)
|
|
50
|
+
dev_port = (env_value(lines, 'DEV_PORT') || allocate_dev_port).to_s
|
|
51
|
+
|
|
49
52
|
{
|
|
50
|
-
'DEV_PORT' =>
|
|
53
|
+
'DEV_PORT' => dev_port,
|
|
51
54
|
'WORKTREE_DATABASE_SUFFIX' => env_value(lines, 'WORKTREE_DATABASE_SUFFIX') ||
|
|
52
|
-
|
|
55
|
+
allocate_worktree_database_suffix(dev_port: dev_port)
|
|
53
56
|
}
|
|
54
57
|
end
|
|
55
58
|
|
|
@@ -108,8 +111,7 @@ module Rails
|
|
|
108
111
|
end
|
|
109
112
|
|
|
110
113
|
def claimed_peer_ports
|
|
111
|
-
|
|
112
|
-
next unless File.directory?(path)
|
|
114
|
+
peer_paths.filter_map do |path|
|
|
113
115
|
next if File.expand_path(path) == File.expand_path(@target_dir)
|
|
114
116
|
|
|
115
117
|
port = env_value(peer_env_lines(path), 'DEV_PORT')
|
|
@@ -126,8 +128,21 @@ module Rails
|
|
|
126
128
|
|
|
127
129
|
def peers_root = File.dirname(@target_dir)
|
|
128
130
|
|
|
131
|
+
def peer_paths
|
|
132
|
+
return @peer_roots unless @peer_roots.nil?
|
|
133
|
+
|
|
134
|
+
Dir.glob(File.join(peers_root, '*')).select { |path| File.directory?(path) }
|
|
135
|
+
end
|
|
136
|
+
|
|
129
137
|
def configured_port_range = @configuration.dev_port_range
|
|
130
138
|
|
|
139
|
+
def allocate_worktree_database_suffix(dev_port:)
|
|
140
|
+
base_candidate = format_worktree_database_suffix(@worktree_name)
|
|
141
|
+
return base_candidate unless claimed_peer_suffixes.include?(base_candidate)
|
|
142
|
+
|
|
143
|
+
suffix_with_token(@worktree_name, dev_port.to_s)
|
|
144
|
+
end
|
|
145
|
+
|
|
131
146
|
def format_worktree_database_suffix(value)
|
|
132
147
|
suffix = value.downcase.gsub(/[^a-z0-9]+/, '_').gsub(/\A_+|_+\z/, '').squeeze('_')
|
|
133
148
|
suffix = suffix[0, @configuration.worktree_database_suffix_max_length]
|
|
@@ -136,6 +151,35 @@ module Rails
|
|
|
136
151
|
"_#{suffix}"
|
|
137
152
|
end
|
|
138
153
|
|
|
154
|
+
def suffix_with_token(value, token)
|
|
155
|
+
slug = normalized_suffix_slug(value)
|
|
156
|
+
normalized_token = token.to_s.downcase.gsub(/[^a-z0-9]+/, '')
|
|
157
|
+
max_length = @configuration.worktree_database_suffix_max_length
|
|
158
|
+
available_slug_length = max_length - normalized_token.length - 1
|
|
159
|
+
|
|
160
|
+
composed_slug = if available_slug_length.positive?
|
|
161
|
+
[slug[0, available_slug_length], normalized_token].reject(&:empty?).join('_')
|
|
162
|
+
else
|
|
163
|
+
normalized_token[0, max_length]
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
format_worktree_database_suffix(composed_slug)
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def normalized_suffix_slug(value)
|
|
170
|
+
slug = value.to_s.downcase.gsub(/[^a-z0-9]+/, '_').gsub(/\A_+|_+\z/, '').squeeze('_')
|
|
171
|
+
slug.empty? ? 'worktree' : slug
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def claimed_peer_suffixes
|
|
175
|
+
peer_paths.each_with_object(Set.new) do |path, suffixes|
|
|
176
|
+
next if File.expand_path(path) == File.expand_path(@target_dir)
|
|
177
|
+
|
|
178
|
+
suffix = env_value(peer_env_lines(path), 'WORKTREE_DATABASE_SUFFIX')
|
|
179
|
+
suffixes << suffix if suffix
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
|
|
139
183
|
def formatted_updates(updates) = updates.map { |key, value| "#{key}=#{value}" }.join(', ')
|
|
140
184
|
|
|
141
185
|
def display_path(path)
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
require 'json'
|
|
2
|
+
require 'open3'
|
|
3
|
+
|
|
4
|
+
module Rails
|
|
5
|
+
module Worktrees
|
|
6
|
+
# Resolves runtime environment variables from mise for setup subprocesses.
|
|
7
|
+
class MiseEnvironment
|
|
8
|
+
Result = Struct.new(:env, :messages)
|
|
9
|
+
|
|
10
|
+
CONFIG_FILES = %w[mise.toml .mise.toml].freeze
|
|
11
|
+
|
|
12
|
+
def initialize(target_dir:, env:)
|
|
13
|
+
@target_dir = target_dir
|
|
14
|
+
@env = env
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def call
|
|
18
|
+
return Result.new(env: {}, messages: []) unless mise_available?
|
|
19
|
+
|
|
20
|
+
messages = []
|
|
21
|
+
trust_config!(messages)
|
|
22
|
+
env = resolved_env
|
|
23
|
+
messages << '🧰 Activating mise toolchain...' if project_config_file || !env.empty?
|
|
24
|
+
|
|
25
|
+
Result.new(env: env, messages: messages.uniq)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def mise_available?
|
|
31
|
+
_stdout_str, _stderr_str, status = Open3.capture3(@env, 'mise', '--version', chdir: @target_dir)
|
|
32
|
+
status.success?
|
|
33
|
+
rescue Errno::ENOENT
|
|
34
|
+
false
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def trust_config!(messages)
|
|
38
|
+
config_file = project_config_file
|
|
39
|
+
return unless config_file
|
|
40
|
+
|
|
41
|
+
messages << '🔐 Trusting mise config...'
|
|
42
|
+
|
|
43
|
+
ensure_trusted!(config_file)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def ensure_trusted!(config_file)
|
|
47
|
+
_stdout_str, stderr_str, status = Open3.capture3(
|
|
48
|
+
@env,
|
|
49
|
+
'mise',
|
|
50
|
+
'trust',
|
|
51
|
+
config_file,
|
|
52
|
+
chdir: @target_dir
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
return if status.success?
|
|
56
|
+
|
|
57
|
+
raise Error, "mise trust failed: #{command_error(stderr_str)}"
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def resolved_env
|
|
61
|
+
@resolved_env ||= begin
|
|
62
|
+
stdout_str, stderr_str, status = Open3.capture3(@env, 'mise', 'env', '--json', chdir: @target_dir)
|
|
63
|
+
raise Error, "mise env --json failed: #{command_error(stderr_str)}" unless status.success?
|
|
64
|
+
|
|
65
|
+
JSON.parse(stdout_str).each_with_object({}) do |(key, value), env|
|
|
66
|
+
next if value.nil?
|
|
67
|
+
|
|
68
|
+
env[key] = value.to_s
|
|
69
|
+
end
|
|
70
|
+
rescue JSON::ParserError => e
|
|
71
|
+
raise Error, "mise env --json returned invalid JSON: #{e.message}"
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def project_config_file
|
|
76
|
+
@project_config_file ||= CONFIG_FILES
|
|
77
|
+
.map { |file_name| File.join(@target_dir, file_name) }
|
|
78
|
+
.find { |path| File.file?(path) }
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def command_error(stderr_str)
|
|
82
|
+
message = stderr_str.to_s.strip
|
|
83
|
+
message.empty? ? 'unknown error' : message
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
@@ -4,7 +4,7 @@ module Rails
|
|
|
4
4
|
module Worktrees
|
|
5
5
|
# Runs post-create setup steps in a newly created worktree.
|
|
6
6
|
# Steps run in order: credential linking, bundle install, yarn install,
|
|
7
|
-
# db:prepare (development), db:prepare (test), assets:precompile (test).
|
|
7
|
+
# db:prepare (development), db:prepare (test), assets:precompile (test), assets:clobber.
|
|
8
8
|
# rubocop:disable Metrics/ClassLength
|
|
9
9
|
class PostCreateRunner
|
|
10
10
|
STEPS = [
|
|
@@ -17,7 +17,9 @@ module Rails
|
|
|
17
17
|
{ id: :test_db_prepare, argv: %w[bin/rails db:prepare],
|
|
18
18
|
header: '🗄️ Preparing test database...', env: { 'RAILS_ENV' => 'test' } },
|
|
19
19
|
{ id: :test_assets_precompile, argv: %w[bin/rails assets:precompile],
|
|
20
|
-
header: '🎨 Precompiling test assets...', env: { 'RAILS_ENV' => 'test' } }
|
|
20
|
+
header: '🎨 Precompiling test assets...', env: { 'RAILS_ENV' => 'test' } },
|
|
21
|
+
{ id: :assets_clobber, argv: %w[bin/rails assets:clobber],
|
|
22
|
+
header: '🧹 Clobbering compiled assets...' }
|
|
21
23
|
].freeze
|
|
22
24
|
|
|
23
25
|
STEP_CONFIG = {
|
|
@@ -25,13 +27,15 @@ module Rails
|
|
|
25
27
|
yarn: :run_yarn_install,
|
|
26
28
|
db_prepare: :run_db_prepare,
|
|
27
29
|
test_db_prepare: :run_test_db_prepare,
|
|
28
|
-
test_assets_precompile: :run_test_assets_precompile
|
|
30
|
+
test_assets_precompile: :run_test_assets_precompile,
|
|
31
|
+
assets_clobber: :run_test_assets_precompile
|
|
29
32
|
}.freeze
|
|
30
33
|
|
|
31
|
-
def initialize(target_dir:, peer_roots:, configuration:, io:)
|
|
34
|
+
def initialize(target_dir:, peer_roots:, configuration:, io:, bootstrapped_env: nil)
|
|
32
35
|
@target_dir = target_dir
|
|
33
36
|
@peer_roots = peer_roots
|
|
34
37
|
@configuration = configuration
|
|
38
|
+
@bootstrapped_env = (bootstrapped_env || {}).transform_values(&:to_s)
|
|
35
39
|
@stdout = io.fetch(:stdout)
|
|
36
40
|
@stderr = io.fetch(:stderr)
|
|
37
41
|
end
|
|
@@ -39,9 +43,14 @@ module Rails
|
|
|
39
43
|
def call(dry_run: false)
|
|
40
44
|
return 0 if @configuration.post_create_command == false
|
|
41
45
|
|
|
46
|
+
@runtime_env = resolved_runtime_env(dry_run: dry_run)
|
|
47
|
+
|
|
42
48
|
return run_custom_command(dry_run: dry_run) if custom_command?
|
|
43
49
|
|
|
44
50
|
run_built_in_steps(dry_run: dry_run)
|
|
51
|
+
rescue Error => e
|
|
52
|
+
@stderr.puts("❌ #{e.message}")
|
|
53
|
+
1
|
|
45
54
|
end
|
|
46
55
|
|
|
47
56
|
private
|
|
@@ -118,7 +127,7 @@ module Rails
|
|
|
118
127
|
def capture_shell_command_exit_status(command)
|
|
119
128
|
exit_status = nil
|
|
120
129
|
|
|
121
|
-
Open3.popen2e(
|
|
130
|
+
Open3.popen2e(runtime_env, command, chdir: @target_dir) do |_stdin, output, wait_thread|
|
|
122
131
|
output.each_line { |line| @stdout.print(line) }
|
|
123
132
|
exit_status = wait_thread.value.exitstatus
|
|
124
133
|
end
|
|
@@ -143,7 +152,7 @@ module Rails
|
|
|
143
152
|
1
|
|
144
153
|
end
|
|
145
154
|
|
|
146
|
-
def
|
|
155
|
+
def shell_env
|
|
147
156
|
# Pass through only the essentials so subprocess tools work correctly.
|
|
148
157
|
ENV.to_h.slice('PATH', 'HOME', 'LANG', 'TERM', 'SHELL',
|
|
149
158
|
'BUNDLE_GEMFILE', 'BUNDLE_PATH', 'GEM_HOME', 'GEM_PATH',
|
|
@@ -152,6 +161,21 @@ module Rails
|
|
|
152
161
|
'DEV_PORT', 'WORKTREE_DATABASE_SUFFIX')
|
|
153
162
|
end
|
|
154
163
|
|
|
164
|
+
attr_reader :runtime_env
|
|
165
|
+
|
|
166
|
+
def resolved_runtime_env(dry_run:)
|
|
167
|
+
env = shell_env.merge(@bootstrapped_env)
|
|
168
|
+
return env if dry_run
|
|
169
|
+
|
|
170
|
+
env.merge(toolchain_env)
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def toolchain_env
|
|
174
|
+
result = MiseEnvironment.new(target_dir: @target_dir, env: shell_env.merge(@bootstrapped_env)).call
|
|
175
|
+
result.messages.each { |message| info(message) }
|
|
176
|
+
result.env
|
|
177
|
+
end
|
|
178
|
+
|
|
155
179
|
def yarn_lock_present?
|
|
156
180
|
File.exist?(File.join(@target_dir, 'yarn.lock'))
|
|
157
181
|
end
|
|
@@ -163,7 +187,7 @@ module Rails
|
|
|
163
187
|
end
|
|
164
188
|
|
|
165
189
|
def step_command(step)
|
|
166
|
-
[
|
|
190
|
+
[runtime_env.merge(step.fetch(:env, {})), *step.fetch(:argv)]
|
|
167
191
|
end
|
|
168
192
|
|
|
169
193
|
def info(message)
|
data/lib/rails/worktrees.rb
CHANGED
|
@@ -3,6 +3,7 @@ require 'pathname'
|
|
|
3
3
|
require_relative 'worktrees/version'
|
|
4
4
|
require_relative 'worktrees/configuration'
|
|
5
5
|
require_relative 'worktrees/application_configuration'
|
|
6
|
+
require_relative 'worktrees/mise_environment'
|
|
6
7
|
require_relative 'worktrees/env_bootstrapper'
|
|
7
8
|
require_relative 'worktrees/credential_key_linker'
|
|
8
9
|
require_relative 'worktrees/post_create_runner'
|
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.7.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Asjer Querido
|
|
@@ -70,6 +70,7 @@ files:
|
|
|
70
70
|
- lib/rails/worktrees/database_config_updater.rb
|
|
71
71
|
- lib/rails/worktrees/env_bootstrapper.rb
|
|
72
72
|
- lib/rails/worktrees/initializer_updater.rb
|
|
73
|
+
- lib/rails/worktrees/mise_environment.rb
|
|
73
74
|
- lib/rails/worktrees/mise_toml_updater.rb
|
|
74
75
|
- lib/rails/worktrees/names/cities.txt
|
|
75
76
|
- lib/rails/worktrees/post_create_runner.rb
|