rails-worktrees 0.2.1 → 0.3.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 +15 -0
- data/README.md +41 -3
- data/exe/ob +14 -0
- data/lib/generators/rails/worktrees/templates/bin/ob +6 -0
- data/lib/generators/worktrees/install/install_generator.rb +44 -6
- data/lib/rails/worktrees/browser_command.rb +264 -0
- data/lib/rails/worktrees/version.rb +1 -1
- data/lib/rails/worktrees.rb +1 -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: 46ec0bdd9361eabe4925e37760bcd5d050c8f71072ec93cfdb427e244186ea8e
|
|
4
|
+
data.tar.gz: 965a2ea5c6d454739f833cf0a534a4b195903531d159e2fb25f5eff9bb71083c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: fa02fa0f6786fb3865326f8cef1e4beb804a25411cc7b5d15676236424b23a5cd4e5b55bd21550f1b1e3f422384f1c06662ff05b9d40e291e931b1bd09775af2
|
|
7
|
+
data.tar.gz: 43269ef67990a1a4986f407a96696b9b01faabf8a64930e676c3c505a214d38b228ba3dae5e9ffa76fc74dc59d96bbdad3eafceb42c99236c64a23954fbefced
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{".":"0.
|
|
1
|
+
{".":"0.3.0"}
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.3.0](https://github.com/asjer/rails-worktrees/compare/v0.2.2...v0.3.0) (2026-03-30)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* **ob:** add `bin/ob` cli for opening `localhost:$DEV_PORT` routes in the browser ([1c94ade](https://github.com/asjer/rails-worktrees/commit/1c94adebd338cfe6e344095f3d6df089570f1e82))
|
|
9
|
+
|
|
10
|
+
## [0.2.2](https://github.com/asjer/rails-worktrees/compare/v0.2.1...v0.2.2) (2026-03-30)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Bug Fixes
|
|
14
|
+
|
|
15
|
+
* **yolo:** skip generating `Procfile.dev.worktree.example` in --yolo mode ([b2320be](https://github.com/asjer/rails-worktrees/commit/b2320be92c84bfdcbb4f781fb34fc079df09f0b1))
|
|
16
|
+
|
|
3
17
|
## [0.2.1](https://github.com/asjer/rails-worktrees/compare/v0.2.0...v0.2.1) (2026-03-30)
|
|
4
18
|
|
|
5
19
|
|
|
@@ -35,6 +49,7 @@
|
|
|
35
49
|
## [Unreleased]
|
|
36
50
|
|
|
37
51
|
- Add a gem-managed `wt` CLI for creating Rails worktrees.
|
|
52
|
+
- Add an optional gem-managed `ob` CLI plus generated `bin/ob` wrapper for opening `localhost:$DEV_PORT` routes.
|
|
38
53
|
- Add a Rails installer generator that creates `bin/wt` and `config/initializers/rails_worktrees.rb`.
|
|
39
54
|
- Add conservative `config/database.yml` patching for common development/test database names.
|
|
40
55
|
- Add a manual-dispatch GitHub Actions workflow for the disposable Rails smoke test.
|
data/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Rails::Worktrees
|
|
2
2
|
|
|
3
|
-
`rails-worktrees` adds
|
|
3
|
+
`rails-worktrees` adds Rails-friendly `bin/wt` and `bin/ob` commands for creating Git worktrees and opening the right local browser URL for each worktree.
|
|
4
4
|
|
|
5
5
|
## Requirements
|
|
6
6
|
|
|
@@ -13,6 +13,8 @@
|
|
|
13
13
|
```bash
|
|
14
14
|
bundle add rails-worktrees
|
|
15
15
|
bin/rails generate worktrees:install
|
|
16
|
+
# or, to also generate bin/ob without the yolo follow-ups:
|
|
17
|
+
bin/rails generate worktrees:install --browser
|
|
16
18
|
# or, to apply the common Procfile.dev + Puma + mise follow-ups automatically:
|
|
17
19
|
bin/rails generate worktrees:install --yolo
|
|
18
20
|
```
|
|
@@ -20,12 +22,15 @@ bin/rails generate worktrees:install --yolo
|
|
|
20
22
|
The installer adds:
|
|
21
23
|
|
|
22
24
|
- `bin/wt` — a thin wrapper that executes the gem-owned CLI
|
|
25
|
+
- `bin/ob` — an optional browser helper generated by `--browser` or `--yolo`
|
|
23
26
|
- `config/initializers/rails_worktrees.rb` — optional configuration
|
|
24
|
-
- `Procfile.dev.worktree.example` — a copy-paste helper for `${DEV_PORT:-3000}` in `Procfile.dev`
|
|
27
|
+
- `Procfile.dev.worktree.example` — a copy-paste helper for `${DEV_PORT:-3000}` in `Procfile.dev` on regular installs
|
|
25
28
|
- a safe update to `config/database.yml` for common development/test database names
|
|
26
29
|
|
|
27
30
|
With `--yolo`, the installer also:
|
|
28
31
|
|
|
32
|
+
- generates `bin/ob`
|
|
33
|
+
- skips `Procfile.dev.worktree.example`
|
|
29
34
|
- replaces the existing `web:` entry in `Procfile.dev` with the DEV_PORT-aware command when `Procfile.dev` already exists
|
|
30
35
|
- updates `config/puma.rb` to use `port ENV['DEV_PORT'] || ENV.fetch('PORT', 3000)` when it still uses a supported default `PORT` binding
|
|
31
36
|
- updates `mise.toml` or `.mise.toml` to load `.env` from `[env]` when either file already exists
|
|
@@ -37,6 +42,11 @@ bin/wt # auto-pick a name from bundled *.txt lists
|
|
|
37
42
|
bin/wt my-feature # use an explicit worktree name
|
|
38
43
|
bin/wt --dry-run my-feature # preview the full setup without changing anything
|
|
39
44
|
bin/wt --print-env my-feature # preview DEV_PORT and WORKTREE_DATABASE_SUFFIX
|
|
45
|
+
|
|
46
|
+
bin/ob # open http://localhost:$DEV_PORT/
|
|
47
|
+
bin/ob contact # open http://localhost:$DEV_PORT/contact
|
|
48
|
+
bin/ob '/contact?from=nav' # open a local route with query params
|
|
49
|
+
bin/ob --print-url '?from=nav' # print the resolved URL without opening a browser
|
|
40
50
|
```
|
|
41
51
|
|
|
42
52
|
### Options
|
|
@@ -123,6 +133,33 @@ When `bin/wt` creates a worktree it writes a worktree-local `.env` with:
|
|
|
123
133
|
|
|
124
134
|
Existing `.env` values are never overwritten.
|
|
125
135
|
|
|
136
|
+
### Browser helper
|
|
137
|
+
|
|
138
|
+
If you install with `--browser` or `--yolo`, the installer generates `bin/ob`.
|
|
139
|
+
|
|
140
|
+
`bin/ob` reads `DEV_PORT` from the current app or worktree's `.env`, falls back to `ENV['DEV_PORT']`, then falls back to `3000`, and opens `http://localhost:$DEV_PORT/...` in your default browser.
|
|
141
|
+
|
|
142
|
+
Route examples:
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
bin/ob
|
|
146
|
+
bin/ob contact
|
|
147
|
+
bin/ob admin/users
|
|
148
|
+
bin/ob '/contact?from=footer'
|
|
149
|
+
bin/ob --print-url '?from=footer'
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
`bin/ob` accepts a single optional local route argument. Query-only routes such as `?from=footer` resolve to the app root, and full URLs such as `https://example.com` are intentionally rejected.
|
|
153
|
+
|
|
154
|
+
If `DEV_PORT` is missing from `.env`, `bin/ob` tells you when it falls back to `ENV['DEV_PORT']` or `3000`.
|
|
155
|
+
|
|
156
|
+
Browser opening currently uses:
|
|
157
|
+
|
|
158
|
+
- `open` on macOS
|
|
159
|
+
- `xdg-open` on Linux and other Unix-like environments that provide it
|
|
160
|
+
|
|
161
|
+
For scripts and debugging, `bin/ob --print-url [route]` prints the resolved localhost URL without opening a browser.
|
|
162
|
+
|
|
126
163
|
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:
|
|
127
164
|
|
|
128
165
|
```text
|
|
@@ -131,6 +168,7 @@ web: env RUBY_DEBUG_OPEN=true bin/rails server -b 0.0.0.0 -p ${DEV_PORT:-3000}
|
|
|
131
168
|
|
|
132
169
|
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
170
|
|
|
171
|
+
- skip generating `Procfile.dev.worktree.example`
|
|
134
172
|
- replace the existing `web:` entry in `Procfile.dev`
|
|
135
173
|
- update `config/puma.rb` to `port ENV['DEV_PORT'] || ENV.fetch('PORT', 3000)` when it still uses a supported default `PORT` binding
|
|
136
174
|
- add `_.file = ".env"` to the `[env]` section of `mise.toml` or `.mise.toml`
|
|
@@ -158,7 +196,7 @@ This smoke test:
|
|
|
158
196
|
- creates a temporary Rails app from a compatible Rails version
|
|
159
197
|
- installs `rails-worktrees` from the current checkout path
|
|
160
198
|
- 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
|
|
199
|
+
- verifies `bin/wt`, `bin/ob`, the generated initializer, that `--yolo` skips the Procfile example, yolo updates to `Procfile.dev`, `config/puma.rb`, and `mise.toml`, `config/database.yml` patching, and worktree `.env` bootstrapping
|
|
162
200
|
- creates a temporary bare `origin` and confirms `bin/wt smoke-branch` creates a real worktree
|
|
163
201
|
|
|
164
202
|
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.
|
data/exe/ob
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
lib = File.expand_path('../lib', __dir__)
|
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
|
+
|
|
5
|
+
require 'rails/worktrees'
|
|
6
|
+
|
|
7
|
+
exit(
|
|
8
|
+
Rails::Worktrees::BrowserCommand.new(
|
|
9
|
+
argv: ARGV,
|
|
10
|
+
io: { stdin: $stdin, stdout: $stdout, stderr: $stderr },
|
|
11
|
+
env: ENV,
|
|
12
|
+
cwd: Dir.pwd
|
|
13
|
+
).run
|
|
14
|
+
)
|
|
@@ -17,12 +17,24 @@ module Worktrees
|
|
|
17
17
|
include ::Rails::Worktrees::Generators::PumaFollowUp
|
|
18
18
|
|
|
19
19
|
namespace 'worktrees:install'
|
|
20
|
-
desc
|
|
20
|
+
desc [
|
|
21
|
+
'Installs bin/wt, optional bin/ob, a Rails::Worktrees initializer,',
|
|
22
|
+
'and updates config/database.yml when safe.'
|
|
23
|
+
].join(' ')
|
|
21
24
|
source_root File.expand_path('../../rails/worktrees/templates', __dir__)
|
|
22
25
|
class_option :conductor, type: :boolean, default: false,
|
|
23
26
|
desc: 'Configure the installer for ~/Sites/conductor/workspaces'
|
|
24
|
-
class_option :
|
|
25
|
-
|
|
27
|
+
class_option :browser,
|
|
28
|
+
type: :boolean,
|
|
29
|
+
default: false,
|
|
30
|
+
desc: 'Generate bin/ob to open localhost:$DEV_PORT routes for this app/worktree'
|
|
31
|
+
class_option :yolo,
|
|
32
|
+
type: :boolean,
|
|
33
|
+
default: false,
|
|
34
|
+
desc: [
|
|
35
|
+
'Apply common Procfile.dev, config/puma.rb, and mise .env',
|
|
36
|
+
'follow-up edits when safe; also generate bin/ob'
|
|
37
|
+
].join(' ')
|
|
26
38
|
|
|
27
39
|
FOLLOW_UP_TEMPLATE = <<~TEXT.freeze
|
|
28
40
|
============================================
|
|
@@ -46,11 +58,20 @@ module Worktrees
|
|
|
46
58
|
chmod('bin/wt', 0o755)
|
|
47
59
|
end
|
|
48
60
|
|
|
61
|
+
def create_browser_wrapper
|
|
62
|
+
return unless install_browser_wrapper?
|
|
63
|
+
|
|
64
|
+
template('bin/ob', 'bin/ob')
|
|
65
|
+
chmod('bin/ob', 0o755)
|
|
66
|
+
end
|
|
67
|
+
|
|
49
68
|
def create_initializer
|
|
50
69
|
template('rails_worktrees.rb.tt', 'config/initializers/rails_worktrees.rb')
|
|
51
70
|
end
|
|
52
71
|
|
|
53
72
|
def create_procfile_worktree_example
|
|
73
|
+
return if options[:yolo]
|
|
74
|
+
|
|
54
75
|
template('Procfile.dev.worktree.example.tt', 'Procfile.dev.worktree.example')
|
|
55
76
|
end
|
|
56
77
|
|
|
@@ -122,15 +143,16 @@ module Worktrees
|
|
|
122
143
|
end
|
|
123
144
|
|
|
124
145
|
def follow_up_notes_text
|
|
125
|
-
[super, puma_follow_up_notes_text].join
|
|
146
|
+
[super, browser_follow_up_notes_text, puma_follow_up_notes_text].join
|
|
126
147
|
end
|
|
127
148
|
|
|
128
149
|
def installed_items_text
|
|
129
150
|
items = [
|
|
130
151
|
' • bin/wt',
|
|
131
|
-
' • config/initializers/rails_worktrees.rb'
|
|
132
|
-
' • Procfile.dev.worktree.example'
|
|
152
|
+
' • config/initializers/rails_worktrees.rb'
|
|
133
153
|
]
|
|
154
|
+
items << ' • bin/ob' if install_browser_wrapper?
|
|
155
|
+
items << ' • Procfile.dev.worktree.example' unless options[:yolo]
|
|
134
156
|
items << database_follow_up_line if database_follow_up_line
|
|
135
157
|
items.join("\n")
|
|
136
158
|
end
|
|
@@ -209,6 +231,22 @@ module Worktrees
|
|
|
209
231
|
say('Skipped mise yolo update because no supported mise config file was found.')
|
|
210
232
|
end
|
|
211
233
|
|
|
234
|
+
def browser_follow_up_notes_text
|
|
235
|
+
return '' unless install_browser_wrapper?
|
|
236
|
+
|
|
237
|
+
[
|
|
238
|
+
'',
|
|
239
|
+
' Open browser:',
|
|
240
|
+
' $ bin/ob',
|
|
241
|
+
' $ bin/ob contact',
|
|
242
|
+
" $ bin/ob --print-url '?from=nav'"
|
|
243
|
+
].join("\n")
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
def install_browser_wrapper?
|
|
247
|
+
options[:yolo] || options[:browser]
|
|
248
|
+
end
|
|
249
|
+
|
|
212
250
|
def git_repo?
|
|
213
251
|
_stdout_str, _stderr_str, status = Open3.capture3(
|
|
214
252
|
'git', 'rev-parse', '--is-inside-work-tree', chdir: destination_root
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
require 'rbconfig'
|
|
2
|
+
require 'uri'
|
|
3
|
+
|
|
4
|
+
module Rails
|
|
5
|
+
module Worktrees
|
|
6
|
+
# Opens the current app/worktree in a browser using the local DEV_PORT.
|
|
7
|
+
# rubocop:disable Metrics/ClassLength
|
|
8
|
+
class BrowserCommand
|
|
9
|
+
APP_ROOT_ENV_KEY = 'RAILS_WORKTREES_APP_ROOT'.freeze
|
|
10
|
+
ENV_FILE_NAME = '.env'.freeze
|
|
11
|
+
|
|
12
|
+
def initialize(argv:, io:, env:, cwd:, host_os: RbConfig::CONFIG['host_os'])
|
|
13
|
+
@argv = argv.dup
|
|
14
|
+
@stdin = io.fetch(:stdin)
|
|
15
|
+
@stdout = io.fetch(:stdout)
|
|
16
|
+
@stderr = io.fetch(:stderr)
|
|
17
|
+
@env = env
|
|
18
|
+
@cwd = cwd
|
|
19
|
+
@host_os = host_os
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def run
|
|
23
|
+
meta_command_result = handle_meta_command
|
|
24
|
+
return meta_command_result unless meta_command_result.nil?
|
|
25
|
+
return usage_error if @argv.length > 1
|
|
26
|
+
|
|
27
|
+
url = build_url(@argv.first)
|
|
28
|
+
open_browser(url)
|
|
29
|
+
@stdout.puts("🌐 Opening #{url}")
|
|
30
|
+
0
|
|
31
|
+
rescue Error => e
|
|
32
|
+
@stderr.puts("Error: #{e.message}")
|
|
33
|
+
1
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
def handle_meta_command
|
|
39
|
+
case @argv.first
|
|
40
|
+
when '-h', '--help'
|
|
41
|
+
@stdout.print(usage)
|
|
42
|
+
0
|
|
43
|
+
when '-v', '--version'
|
|
44
|
+
@stdout.puts("ob #{Rails::Worktrees::VERSION}")
|
|
45
|
+
0
|
|
46
|
+
when '--url', '--print-url'
|
|
47
|
+
print_url_command
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def usage_error
|
|
52
|
+
@stderr.print(usage)
|
|
53
|
+
1
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def usage
|
|
57
|
+
<<~USAGE
|
|
58
|
+
ob #{::Rails::Worktrees::VERSION}
|
|
59
|
+
Open the current app/worktree in your browser using DEV_PORT.
|
|
60
|
+
|
|
61
|
+
Usage: ob [route]
|
|
62
|
+
ob --print-url [route]
|
|
63
|
+
|
|
64
|
+
Options:
|
|
65
|
+
-h, --help Show this help message
|
|
66
|
+
-v, --version Show the script version
|
|
67
|
+
--url, --print-url Print the resolved URL without opening a browser
|
|
68
|
+
|
|
69
|
+
Quick start:
|
|
70
|
+
ob
|
|
71
|
+
ob contact
|
|
72
|
+
ob '/contact?ref=nav'
|
|
73
|
+
ob --print-url '?from=nav'
|
|
74
|
+
|
|
75
|
+
Route rules:
|
|
76
|
+
- route is optional; the default is /
|
|
77
|
+
- values like contact and admin/users become /contact and /admin/users
|
|
78
|
+
- query-only values like ?from=nav resolve to /?from=nav
|
|
79
|
+
- full URLs are rejected; ob only opens localhost routes
|
|
80
|
+
- DEV_PORT comes from .env first, then ENV['DEV_PORT'], then 3000
|
|
81
|
+
USAGE
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def build_url(route)
|
|
85
|
+
raise Error, 'ob only accepts local routes, not full URLs' if full_url?(route)
|
|
86
|
+
|
|
87
|
+
path, query, fragment = route_components(route)
|
|
88
|
+
uri_options = { host: 'localhost', port: resolved_dev_port.to_i, path: path, query: query, fragment: fragment }
|
|
89
|
+
URI::HTTP.build(**uri_options).to_s
|
|
90
|
+
rescue URI::Error => e
|
|
91
|
+
raise Error, "Invalid route: #{e.message}"
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def route_components(route)
|
|
95
|
+
raw_route = route.to_s.strip
|
|
96
|
+
route_without_fragment, fragment = raw_route.split('#', 2)
|
|
97
|
+
path_part, query = route_without_fragment.to_s.split('?', 2)
|
|
98
|
+
|
|
99
|
+
[normalized_path(path_part), presence(query), presence(fragment)]
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def normalized_path(path_part)
|
|
103
|
+
cleaned = path_part.to_s
|
|
104
|
+
return '/' if cleaned.empty?
|
|
105
|
+
|
|
106
|
+
cleaned.start_with?('/') ? cleaned : "/#{cleaned}"
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def resolved_dev_port
|
|
110
|
+
info = dev_port_resolution
|
|
111
|
+
note_port_fallback(info[:message])
|
|
112
|
+
|
|
113
|
+
port = info.fetch(:port)
|
|
114
|
+
raise Error, "DEV_PORT must be numeric, got #{port.inspect}" unless port.match?(/\A\d+\z/)
|
|
115
|
+
raise Error, "DEV_PORT must be between 1 and 65535, got #{port.inspect}" unless (1..65_535).cover?(port.to_i)
|
|
116
|
+
|
|
117
|
+
port.to_i
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def print_url_command
|
|
121
|
+
return usage_error if @argv.length > 2
|
|
122
|
+
|
|
123
|
+
@stdout.puts(build_url(@argv[1]))
|
|
124
|
+
0
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def dev_port_from_env_file
|
|
128
|
+
return unless File.file?(env_path)
|
|
129
|
+
|
|
130
|
+
lines = File.readlines(env_path, chomp: true)
|
|
131
|
+
env_value(lines, 'DEV_PORT')
|
|
132
|
+
rescue StandardError => e
|
|
133
|
+
raise Error, "Could not read #{env_path}: #{e.message}"
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def env_value(lines, key)
|
|
137
|
+
line = lines.reverse.find { |entry| entry.start_with?("#{key}=") }
|
|
138
|
+
value = line&.split('=', 2)&.last
|
|
139
|
+
value unless value&.empty?
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def dev_port_resolution
|
|
143
|
+
env_file_port = dev_port_from_env_file
|
|
144
|
+
return { port: env_file_port, message: nil } if env_file_port
|
|
145
|
+
|
|
146
|
+
env_file_fallback_resolution
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def env_file_fallback_resolution
|
|
150
|
+
env_port = presence(@env['DEV_PORT'])
|
|
151
|
+
return existing_env_file_resolution(env_port) if File.file?(env_path)
|
|
152
|
+
|
|
153
|
+
missing_env_file_resolution(env_port)
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def existing_env_file_resolution(env_port)
|
|
157
|
+
return env_resolution_from_env_var(env_port) if env_port
|
|
158
|
+
|
|
159
|
+
{
|
|
160
|
+
port: '3000',
|
|
161
|
+
message: [
|
|
162
|
+
"#{env_path} does not define DEV_PORT",
|
|
163
|
+
"and ENV['DEV_PORT'] is unset; falling back to localhost:3000."
|
|
164
|
+
].join(' ')
|
|
165
|
+
}
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def missing_env_file_resolution(env_port)
|
|
169
|
+
return missing_env_file_resolution_from_env_var(env_port) if env_port
|
|
170
|
+
|
|
171
|
+
{
|
|
172
|
+
port: '3000',
|
|
173
|
+
message: [
|
|
174
|
+
"#{env_path} was not found",
|
|
175
|
+
"and ENV['DEV_PORT'] is unset; falling back to localhost:3000."
|
|
176
|
+
].join(' ')
|
|
177
|
+
}
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def env_resolution_from_env_var(env_port)
|
|
181
|
+
{
|
|
182
|
+
port: env_port,
|
|
183
|
+
message: "#{env_path} does not define DEV_PORT; falling back to ENV['DEV_PORT']=#{env_port}."
|
|
184
|
+
}
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def missing_env_file_resolution_from_env_var(env_port)
|
|
188
|
+
{
|
|
189
|
+
port: env_port,
|
|
190
|
+
message: "#{env_path} was not found; falling back to ENV['DEV_PORT']=#{env_port}."
|
|
191
|
+
}
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def note_port_fallback(message)
|
|
195
|
+
return unless message
|
|
196
|
+
return if @port_fallback_noted
|
|
197
|
+
|
|
198
|
+
@stderr.puts("Info: #{message}")
|
|
199
|
+
@port_fallback_noted = true
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
def open_browser(url)
|
|
203
|
+
command = opener_command
|
|
204
|
+
raise Error, "Could not find a browser opener for #{@host_os.inspect}" unless command
|
|
205
|
+
raise Error, "Failed to open browser with #{command}" unless run_opener(command, url)
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
def opener_command
|
|
209
|
+
opener_candidates.find { |command| command_available?(command) }
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
def run_opener(command, url)
|
|
213
|
+
system(command, url)
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
def command_available?(command)
|
|
217
|
+
@env.fetch('PATH', '').split(File::PATH_SEPARATOR).any? do |directory|
|
|
218
|
+
candidate = File.join(directory, command)
|
|
219
|
+
File.file?(candidate) && File.executable?(candidate)
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
def opener_candidates
|
|
224
|
+
return ['open'] if @host_os.match?(/darwin/i)
|
|
225
|
+
|
|
226
|
+
['xdg-open']
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
def env_path = File.join(app_root, ENV_FILE_NAME)
|
|
230
|
+
|
|
231
|
+
def app_root
|
|
232
|
+
@app_root ||= begin
|
|
233
|
+
explicit_root = presence(@env[APP_ROOT_ENV_KEY])
|
|
234
|
+
explicit_root ? File.expand_path(explicit_root) : discover_app_root(File.expand_path(@cwd))
|
|
235
|
+
end
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
def discover_app_root(start_dir)
|
|
239
|
+
current = start_dir
|
|
240
|
+
|
|
241
|
+
loop do
|
|
242
|
+
return current if File.file?(File.join(current, 'Gemfile'))
|
|
243
|
+
|
|
244
|
+
parent = File.dirname(current)
|
|
245
|
+
break if parent == current
|
|
246
|
+
|
|
247
|
+
current = parent
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
start_dir
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
def full_url?(route)
|
|
254
|
+
route.to_s.match?(%r{\A[a-z][a-z0-9+\-.]*://}i)
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
def presence(value)
|
|
258
|
+
value = value.to_s
|
|
259
|
+
value.empty? ? nil : value
|
|
260
|
+
end
|
|
261
|
+
end
|
|
262
|
+
# rubocop:enable Metrics/ClassLength
|
|
263
|
+
end
|
|
264
|
+
end
|
data/lib/rails/worktrees.rb
CHANGED
|
@@ -5,6 +5,7 @@ require_relative 'worktrees/configuration'
|
|
|
5
5
|
require_relative 'worktrees/env_bootstrapper'
|
|
6
6
|
require_relative 'worktrees/command'
|
|
7
7
|
require_relative 'worktrees/cli'
|
|
8
|
+
require_relative 'worktrees/browser_command'
|
|
8
9
|
require_relative 'worktrees/database_config_updater'
|
|
9
10
|
require_relative 'worktrees/procfile_updater'
|
|
10
11
|
require_relative 'worktrees/mise_toml_updater'
|
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.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Asjer Querido
|
|
@@ -34,6 +34,7 @@ description: Rails::Worktrees is a Ruby gem intended to support working with git
|
|
|
34
34
|
email:
|
|
35
35
|
- asjer@johnyontherun.com
|
|
36
36
|
executables:
|
|
37
|
+
- ob
|
|
37
38
|
- wt
|
|
38
39
|
extensions: []
|
|
39
40
|
extra_rdoc_files: []
|
|
@@ -43,15 +44,18 @@ files:
|
|
|
43
44
|
- LICENSE.txt
|
|
44
45
|
- README.md
|
|
45
46
|
- Rakefile
|
|
47
|
+
- exe/ob
|
|
46
48
|
- exe/wt
|
|
47
49
|
- lefthook.yml
|
|
48
50
|
- lib/generators/rails/worktrees/mise_follow_up.rb
|
|
49
51
|
- lib/generators/rails/worktrees/puma_follow_up.rb
|
|
50
52
|
- lib/generators/rails/worktrees/templates/Procfile.dev.worktree.example.tt
|
|
53
|
+
- lib/generators/rails/worktrees/templates/bin/ob
|
|
51
54
|
- lib/generators/rails/worktrees/templates/bin/wt
|
|
52
55
|
- lib/generators/rails/worktrees/templates/rails_worktrees.rb.tt
|
|
53
56
|
- lib/generators/worktrees/install/install_generator.rb
|
|
54
57
|
- lib/rails/worktrees.rb
|
|
58
|
+
- lib/rails/worktrees/browser_command.rb
|
|
55
59
|
- lib/rails/worktrees/cli.rb
|
|
56
60
|
- lib/rails/worktrees/command.rb
|
|
57
61
|
- lib/rails/worktrees/command/environment_support.rb
|