rails-worktrees 0.2.2 → 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 +8 -0
- data/README.md +38 -2
- data/exe/ob +14 -0
- data/lib/generators/rails/worktrees/templates/bin/ob +6 -0
- data/lib/generators/worktrees/install/install_generator.rb +40 -4
- 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,12 @@
|
|
|
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
|
+
|
|
3
10
|
## [0.2.2](https://github.com/asjer/rails-worktrees/compare/v0.2.1...v0.2.2) (2026-03-30)
|
|
4
11
|
|
|
5
12
|
|
|
@@ -42,6 +49,7 @@
|
|
|
42
49
|
## [Unreleased]
|
|
43
50
|
|
|
44
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.
|
|
45
53
|
- Add a Rails installer generator that creates `bin/wt` and `config/initializers/rails_worktrees.rb`.
|
|
46
54
|
- Add conservative `config/database.yml` patching for common development/test database names.
|
|
47
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,14 @@ 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
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`
|
|
29
33
|
- skips `Procfile.dev.worktree.example`
|
|
30
34
|
- replaces the existing `web:` entry in `Procfile.dev` with the DEV_PORT-aware command when `Procfile.dev` already exists
|
|
31
35
|
- updates `config/puma.rb` to use `port ENV['DEV_PORT'] || ENV.fetch('PORT', 3000)` when it still uses a supported default `PORT` binding
|
|
@@ -38,6 +42,11 @@ bin/wt # auto-pick a name from bundled *.txt lists
|
|
|
38
42
|
bin/wt my-feature # use an explicit worktree name
|
|
39
43
|
bin/wt --dry-run my-feature # preview the full setup without changing anything
|
|
40
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
|
|
41
50
|
```
|
|
42
51
|
|
|
43
52
|
### Options
|
|
@@ -124,6 +133,33 @@ When `bin/wt` creates a worktree it writes a worktree-local `.env` with:
|
|
|
124
133
|
|
|
125
134
|
Existing `.env` values are never overwritten.
|
|
126
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
|
+
|
|
127
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:
|
|
128
164
|
|
|
129
165
|
```text
|
|
@@ -160,7 +196,7 @@ This smoke test:
|
|
|
160
196
|
- creates a temporary Rails app from a compatible Rails version
|
|
161
197
|
- installs `rails-worktrees` from the current checkout path
|
|
162
198
|
- runs `bin/rails generate worktrees:install --yolo`
|
|
163
|
-
- verifies `bin/wt`, 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
|
|
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
|
|
164
200
|
- creates a temporary bare `origin` and confirms `bin/wt smoke-branch` creates a real worktree
|
|
165
201
|
|
|
166
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,6 +58,13 @@ 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
|
|
@@ -124,7 +143,7 @@ module Worktrees
|
|
|
124
143
|
end
|
|
125
144
|
|
|
126
145
|
def follow_up_notes_text
|
|
127
|
-
[super, puma_follow_up_notes_text].join
|
|
146
|
+
[super, browser_follow_up_notes_text, puma_follow_up_notes_text].join
|
|
128
147
|
end
|
|
129
148
|
|
|
130
149
|
def installed_items_text
|
|
@@ -132,6 +151,7 @@ module Worktrees
|
|
|
132
151
|
' • bin/wt',
|
|
133
152
|
' • config/initializers/rails_worktrees.rb'
|
|
134
153
|
]
|
|
154
|
+
items << ' • bin/ob' if install_browser_wrapper?
|
|
135
155
|
items << ' • Procfile.dev.worktree.example' unless options[:yolo]
|
|
136
156
|
items << database_follow_up_line if database_follow_up_line
|
|
137
157
|
items.join("\n")
|
|
@@ -211,6 +231,22 @@ module Worktrees
|
|
|
211
231
|
say('Skipped mise yolo update because no supported mise config file was found.')
|
|
212
232
|
end
|
|
213
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
|
+
|
|
214
250
|
def git_repo?
|
|
215
251
|
_stdout_str, _stderr_str, status = Open3.capture3(
|
|
216
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
|