brunch 1.0.0 → 1.1.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/CHANGELOG.md +8 -0
- data/README.md +40 -2
- data/lib/brunch/cli.rb +9 -3
- data/lib/brunch/compose_ports.rb +65 -0
- data/lib/brunch/managers.rb +26 -4
- data/lib/brunch/version.rb +1 -1
- data/lib/brunch/worktree_mode.rb +101 -15
- data/lib/brunch.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: bfbfbc36f9114715c3b656aeeb831010e8e75e1ccc15d8c7229813b4489ceac6
|
|
4
|
+
data.tar.gz: 6a98d0331e7d758bacafad88344dd92a0a105b60c06ead342d35fb928e36cc0f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 64bbfe38e607cf1cf8cc1e9ce042e104653a4ac0958fdeca87ff6e40e4febf0f09e287d9c55f6cb7ce75c17a3062ad6edb1cd159f18407d3a52415192360c52b
|
|
7
|
+
data.tar.gz: ea493ddaa2e3431665a0a0a0d1c42f99a3013c9ab5ccafc98b170a98dfddd20188a7bd53dc6384f99a1469a5aba1b52298b1edd9881287e4d8c3365d246719c6
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.1.0 - 2026-10-16
|
|
4
|
+
|
|
5
|
+
- Add opt-in `ports: auto` for Docker Compose and Podman Compose. Brunch now
|
|
6
|
+
renders a private Compose configuration and assigns every published host port
|
|
7
|
+
independently for each worktree, without modifying the repository file.
|
|
8
|
+
- Add `brunch ports` and `brunch port SERVICE CONTAINER_PORT` for discovering
|
|
9
|
+
the complete set of assigned host ports.
|
|
10
|
+
|
|
3
11
|
## 1.0.0 - 2026-10-16
|
|
4
12
|
|
|
5
13
|
- Recover automatically when a stopped Compose environment cannot resume: Brunch
|
data/README.md
CHANGED
|
@@ -96,6 +96,44 @@ preferred_port: 4000
|
|
|
96
96
|
|
|
97
97
|
Port assignments are persisted per worktree in `.git/brunch/state.json`.
|
|
98
98
|
|
|
99
|
+
### Existing Compose projects with several ports
|
|
100
|
+
|
|
101
|
+
By default Brunch manages the `BRUNCH_PORT` variable shown above. To run an
|
|
102
|
+
existing Compose project that publishes several fixed host ports, opt in with:
|
|
103
|
+
|
|
104
|
+
```yaml
|
|
105
|
+
ports: auto
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Brunch renders the current Compose configuration, reserves every published
|
|
109
|
+
host port, and writes a private generated configuration under `.git/brunch/`.
|
|
110
|
+
It never changes the repository's `compose.yaml`. The first worktree keeps the
|
|
111
|
+
declared ports whenever they are free; additional worktrees receive free ports
|
|
112
|
+
for every published service port.
|
|
113
|
+
|
|
114
|
+
For example, this requires no variables in the Compose file:
|
|
115
|
+
|
|
116
|
+
```yaml
|
|
117
|
+
services:
|
|
118
|
+
web:
|
|
119
|
+
ports:
|
|
120
|
+
- "3000:3000"
|
|
121
|
+
- "5173:5173"
|
|
122
|
+
mail:
|
|
123
|
+
ports:
|
|
124
|
+
- "8025:8025"
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
`brunch ports` displays all assigned mappings. Query one mapping with:
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
brunch port web 3000
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Only `ports:` entries that publish a host port are managed; internal service
|
|
134
|
+
ports and `expose:` remain unchanged. `BRUNCH_PORT` continues to contain the
|
|
135
|
+
first published port for compatibility with existing scripts.
|
|
136
|
+
|
|
99
137
|
## Getting started
|
|
100
138
|
|
|
101
139
|
Commit `brunch.yml` and the application configuration first. The repository must contain at least one commit.
|
|
@@ -185,12 +223,12 @@ Then:
|
|
|
185
223
|
cd ../feature-a
|
|
186
224
|
|
|
187
225
|
brunch port
|
|
188
|
-
brunch
|
|
226
|
+
brunch ports
|
|
189
227
|
```
|
|
190
228
|
|
|
191
229
|
`brunch port` prints the current worktree's port.
|
|
192
230
|
|
|
193
|
-
`brunch
|
|
231
|
+
`brunch ports` groups the current and previously activated branches under each
|
|
194
232
|
worktree. Stopped branches appear in gray and retain their worktree's port for
|
|
195
233
|
the next activation.
|
|
196
234
|
|
data/lib/brunch/cli.rb
CHANGED
|
@@ -10,7 +10,7 @@ module Brunch
|
|
|
10
10
|
include WorktreeMode
|
|
11
11
|
|
|
12
12
|
PORT_RANGE = (1024..49_151)
|
|
13
|
-
CONFIGURATION_KEYS = %w[manager compose_file command commands preferred_port].freeze
|
|
13
|
+
CONFIGURATION_KEYS = %w[manager compose_file command commands preferred_port ports].freeze
|
|
14
14
|
COMMAND_KEYS = %w[create start stop remove status health logs].freeze
|
|
15
15
|
|
|
16
16
|
class ConfigurationError < StandardError; end
|
|
@@ -90,7 +90,8 @@ module Brunch
|
|
|
90
90
|
path = File.join(root, "brunch.yml")
|
|
91
91
|
configured = File.file?(path) ? YAML.safe_load_file(path, permitted_classes: [], aliases: false) || {} : {}
|
|
92
92
|
validate_configuration!(configured)
|
|
93
|
-
{ "manager" => "docker_compose", "compose_file" => "compose.yaml", "preferred_port" => 3000
|
|
93
|
+
{ "manager" => "docker_compose", "compose_file" => "compose.yaml", "preferred_port" => 3000,
|
|
94
|
+
"ports" => "manual" }.merge(configured)
|
|
94
95
|
rescue Psych::Exception => e
|
|
95
96
|
raise ConfigurationError, "Invalid brunch.yml: #{e.message}"
|
|
96
97
|
end
|
|
@@ -101,7 +102,8 @@ module Brunch
|
|
|
101
102
|
unknown = configured.keys - CONFIGURATION_KEYS
|
|
102
103
|
raise ConfigurationError, "Unknown brunch.yml key(s): #{unknown.join(', ')}." unless unknown.empty?
|
|
103
104
|
|
|
104
|
-
value = { "manager" => "docker_compose", "compose_file" => "compose.yaml", "preferred_port" => 3000
|
|
105
|
+
value = { "manager" => "docker_compose", "compose_file" => "compose.yaml", "preferred_port" => 3000,
|
|
106
|
+
"ports" => "manual" }.merge(configured)
|
|
105
107
|
unless %w[docker_compose podman_compose local_process command].include?(value["manager"])
|
|
106
108
|
raise ConfigurationError, "Unknown Brunch manager: #{value['manager']}."
|
|
107
109
|
end
|
|
@@ -111,6 +113,10 @@ module Brunch
|
|
|
111
113
|
if %w[docker_compose podman_compose].include?(value["manager"]) && (!value["compose_file"].is_a?(String) || value["compose_file"].empty?)
|
|
112
114
|
raise ConfigurationError, "compose_file must be a non-empty string."
|
|
113
115
|
end
|
|
116
|
+
raise ConfigurationError, "ports must be manual or auto." unless %w[manual auto].include?(value["ports"])
|
|
117
|
+
if value["ports"] == "auto" && !%w[docker_compose podman_compose].include?(value["manager"])
|
|
118
|
+
raise ConfigurationError, "ports: auto requires a Docker Compose or Podman Compose manager."
|
|
119
|
+
end
|
|
114
120
|
if value["manager"] == "local_process" && (!value["command"].is_a?(String) || value["command"].empty?)
|
|
115
121
|
raise ConfigurationError, "local_process manager requires a non-empty command."
|
|
116
122
|
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Brunch
|
|
4
|
+
# Reads the resolved Compose model produced by `compose config` and changes
|
|
5
|
+
# only published host ports. Keeping this separate from the source YAML lets
|
|
6
|
+
# Brunch use existing Compose projects without asking them to add variables.
|
|
7
|
+
module ComposePorts
|
|
8
|
+
module_function
|
|
9
|
+
|
|
10
|
+
def extract(document)
|
|
11
|
+
services = document.fetch("services", {})
|
|
12
|
+
raise CLI::ConfigurationError, "Compose config must contain a services mapping." unless services.is_a?(Hash)
|
|
13
|
+
|
|
14
|
+
services.flat_map do |service, definition|
|
|
15
|
+
next [] unless definition.is_a?(Hash)
|
|
16
|
+
|
|
17
|
+
Array(definition["ports"]).filter_map do |port|
|
|
18
|
+
published_mapping(service, port)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def rewrite(document, assignments)
|
|
24
|
+
indexed = assignments.to_h { |assignment| [key(assignment), assignment.fetch("host")] }
|
|
25
|
+
document.fetch("services", {}).each do |service, definition|
|
|
26
|
+
next unless definition.is_a?(Hash)
|
|
27
|
+
|
|
28
|
+
definition["ports"] = Array(definition["ports"]).map do |port|
|
|
29
|
+
mapping = published_mapping(service, port)
|
|
30
|
+
next port unless mapping
|
|
31
|
+
|
|
32
|
+
port.merge("published" => indexed.fetch(key(mapping)))
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
document
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def key(mapping)
|
|
39
|
+
[mapping.fetch("service"), mapping.fetch("target"), mapping.fetch("protocol"), mapping.fetch("host_ip")]
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def published_mapping(service, port)
|
|
43
|
+
raise CLI::ConfigurationError, "Unsupported Compose port for #{service}; use Compose long syntax with ports: auto." unless port.is_a?(Hash)
|
|
44
|
+
return unless port.key?("published") && !port["published"].to_s.empty?
|
|
45
|
+
|
|
46
|
+
published = integer(port["published"], "published host port for #{service}")
|
|
47
|
+
target = integer(port["target"], "container port for #{service}")
|
|
48
|
+
protocol = port.fetch("protocol", "tcp")
|
|
49
|
+
raise CLI::ConfigurationError, "Unsupported protocol #{protocol.inspect} for #{service}." unless %w[tcp udp].include?(protocol)
|
|
50
|
+
|
|
51
|
+
{ "service" => service.to_s, "target" => target, "protocol" => protocol,
|
|
52
|
+
"host_ip" => port.fetch("host_ip", "").to_s, "published" => published }
|
|
53
|
+
end
|
|
54
|
+
private_class_method :published_mapping
|
|
55
|
+
|
|
56
|
+
def integer(value, description)
|
|
57
|
+
Integer(value).tap do |port|
|
|
58
|
+
raise CLI::ConfigurationError, "Invalid #{description}: #{value.inspect}." unless CLI::PORT_RANGE.cover?(port)
|
|
59
|
+
end
|
|
60
|
+
rescue ArgumentError, TypeError
|
|
61
|
+
raise CLI::ConfigurationError, "Invalid #{description}: #{value.inspect}."
|
|
62
|
+
end
|
|
63
|
+
private_class_method :integer
|
|
64
|
+
end
|
|
65
|
+
end
|
data/lib/brunch/managers.rb
CHANGED
|
@@ -98,6 +98,13 @@ module Brunch
|
|
|
98
98
|
compose(entry, "logs", *arguments)
|
|
99
99
|
end
|
|
100
100
|
|
|
101
|
+
def render_compose(entry)
|
|
102
|
+
output, error, status = Open3.capture3(environment(entry), *source_compose_command(entry, "config"), **compose_options(entry))
|
|
103
|
+
return [output, nil] if status.success?
|
|
104
|
+
|
|
105
|
+
[nil, error]
|
|
106
|
+
end
|
|
107
|
+
|
|
101
108
|
def port_available?(port)
|
|
102
109
|
ports, = Open3.capture2(*ports_command)
|
|
103
110
|
return false if ports.match?(/:#{port}->/)
|
|
@@ -125,11 +132,19 @@ module Brunch
|
|
|
125
132
|
|
|
126
133
|
def compose_command(entry, *arguments)
|
|
127
134
|
directory = compose_directory(entry)
|
|
128
|
-
compose_file = File.join(directory, entry.fetch("compose_file"))
|
|
135
|
+
compose_file = entry.fetch("rendered_compose", File.join(directory, entry.fetch("compose_file")))
|
|
129
136
|
["docker", "compose", "--project-name", entry.fetch("project"), "--project-directory", directory, "--file",
|
|
130
137
|
compose_file, *arguments]
|
|
131
138
|
end
|
|
132
139
|
|
|
140
|
+
def source_compose_command(entry, *arguments)
|
|
141
|
+
directory = entry.fetch("snapshot")
|
|
142
|
+
["docker", "compose", "--project-name", entry.fetch("project"), "--project-directory", directory, "--file",
|
|
143
|
+
File.join(directory, entry.fetch("compose_file")), *arguments]
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def compose_options(_entry) = {}
|
|
147
|
+
|
|
133
148
|
def compose_directory(entry)
|
|
134
149
|
source = entry.fetch("snapshot")
|
|
135
150
|
File.file?(File.join(source, entry.fetch("compose_file"))) ? source : entry.fetch("control", source)
|
|
@@ -138,7 +153,7 @@ module Brunch
|
|
|
138
153
|
def ports_command = ["docker", "ps", "--format", "{{.Ports}}"]
|
|
139
154
|
|
|
140
155
|
def compose(entry, *arguments)
|
|
141
|
-
system(environment(entry), *compose_command(entry, *arguments))
|
|
156
|
+
system(environment(entry), *compose_command(entry, *arguments), **compose_options(entry))
|
|
142
157
|
end
|
|
143
158
|
end
|
|
144
159
|
|
|
@@ -159,14 +174,21 @@ module Brunch
|
|
|
159
174
|
|
|
160
175
|
def compose_command(entry, *arguments)
|
|
161
176
|
directory = compose_directory(entry)
|
|
162
|
-
compose_file = File.join(directory, entry.fetch("compose_file"))
|
|
177
|
+
compose_file = entry.fetch("rendered_compose", File.join(directory, entry.fetch("compose_file")))
|
|
163
178
|
["podman", "compose", "-p", entry.fetch("project"), "-f", compose_file, *arguments]
|
|
164
179
|
end
|
|
165
180
|
|
|
181
|
+
def source_compose_command(entry, *arguments)
|
|
182
|
+
directory = entry.fetch("snapshot")
|
|
183
|
+
["podman", "compose", "-p", entry.fetch("project"), "-f", File.join(directory, entry.fetch("compose_file")), *arguments]
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def compose_options(entry) = { chdir: entry.fetch("snapshot") }
|
|
187
|
+
|
|
166
188
|
def ports_command = ["podman", "ps", "--format", "{{.Ports}}"]
|
|
167
189
|
|
|
168
190
|
def compose(entry, *arguments)
|
|
169
|
-
system(environment(entry), *compose_command(entry, *arguments),
|
|
191
|
+
system(environment(entry), *compose_command(entry, *arguments), **compose_options(entry))
|
|
170
192
|
end
|
|
171
193
|
end
|
|
172
194
|
|
data/lib/brunch/version.rb
CHANGED
data/lib/brunch/worktree_mode.rb
CHANGED
|
@@ -24,14 +24,16 @@ module Brunch
|
|
|
24
24
|
in ["branch-settled", ref, token] then settle_deleted_branch(ref, token)
|
|
25
25
|
in ["cleanup"] | ["register", _] then worktree_cleanup
|
|
26
26
|
in ["status"] then worktree_status
|
|
27
|
-
in ["list"] then worktree_ports
|
|
27
|
+
in ["list"] | ["ports"] then worktree_ports
|
|
28
28
|
in ["watch"] then worktree_watch
|
|
29
29
|
in ["port"] then worktree_port
|
|
30
|
+
in ["port", service, target] then worktree_port(service, target)
|
|
30
31
|
in ["logs", *arguments] then worktree_logs(arguments)
|
|
31
32
|
in ["run", "--", *command] if !command.empty? then worktree_run(command)
|
|
32
33
|
in ["doctor"] then worktree_doctor
|
|
33
34
|
else
|
|
34
|
-
warn "Usage: brunch {activate|start <current-ref>|stop|restart|status|list|watch|
|
|
35
|
+
warn "Usage: brunch {activate|start <current-ref>|stop|restart|status|list|ports|watch|" \
|
|
36
|
+
"port [service container-port]|logs [args]|run -- <cmd>|doctor|cleanup}"
|
|
35
37
|
return 64
|
|
36
38
|
end
|
|
37
39
|
0
|
|
@@ -87,12 +89,72 @@ module Brunch
|
|
|
87
89
|
end
|
|
88
90
|
|
|
89
91
|
def worktree_port_for(id, data, manager, base: preferred_port)
|
|
90
|
-
reserved = data
|
|
92
|
+
reserved = reserved_ports(data)
|
|
91
93
|
(base..CLI::PORT_RANGE.end).chain(CLI::PORT_RANGE.begin...base).find do |port|
|
|
92
94
|
!reserved.include?(port) && manager.port_available?(port)
|
|
93
95
|
end || abort("No free port for worktree #{id}.")
|
|
94
96
|
end
|
|
95
97
|
|
|
98
|
+
def reserved_ports(data)
|
|
99
|
+
assigned_ports(data).uniq
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def assigned_ports(data)
|
|
103
|
+
data.fetch("worktrees", {}).values.flat_map do |record|
|
|
104
|
+
[record["port"], *Array(record["ports"]).map { |mapping| mapping["host"] }]
|
|
105
|
+
end.compact
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def auto_ports?(config) = config.fetch("ports") == "auto"
|
|
109
|
+
|
|
110
|
+
def reserve_compose_ports(id, data, manager, mappings)
|
|
111
|
+
record = data.fetch("worktrees").fetch(id)
|
|
112
|
+
known = Array(record["ports"])
|
|
113
|
+
all_reserved = reserved_ports(data)
|
|
114
|
+
assignments = mappings.map do |mapping|
|
|
115
|
+
existing = known.find { |candidate| ComposePorts.key(candidate) == ComposePorts.key(mapping) }
|
|
116
|
+
next existing if existing
|
|
117
|
+
|
|
118
|
+
preferred = mapping.fetch("published")
|
|
119
|
+
host = if known.empty? && record["port"] == preferred
|
|
120
|
+
preferred
|
|
121
|
+
elsif !all_reserved.include?(preferred) && manager.port_available?(preferred)
|
|
122
|
+
preferred
|
|
123
|
+
else
|
|
124
|
+
worktree_port_for(id, data, manager, base: preferred)
|
|
125
|
+
end
|
|
126
|
+
assignment = mapping.merge("host" => host)
|
|
127
|
+
known << assignment
|
|
128
|
+
all_reserved << host
|
|
129
|
+
assignment
|
|
130
|
+
end
|
|
131
|
+
record["ports"] = known
|
|
132
|
+
record["port"] = assignments.first.fetch("host") unless assignments.empty?
|
|
133
|
+
assignments
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def render_compose_ports(path, entry, manager)
|
|
137
|
+
rendered, error = manager.render_compose(entry)
|
|
138
|
+
abort "Could not render #{entry.fetch('compose_file')}: #{error.strip}" unless rendered
|
|
139
|
+
|
|
140
|
+
document = YAML.safe_load(rendered, permitted_classes: [], aliases: false)
|
|
141
|
+
mappings = ComposePorts.extract(document)
|
|
142
|
+
assignments = nil
|
|
143
|
+
with_state_lock do |data|
|
|
144
|
+
assignments = reserve_compose_ports(worktree_identity(path), data, manager, mappings)
|
|
145
|
+
end
|
|
146
|
+
ComposePorts.rewrite(document, assignments)
|
|
147
|
+
target = File.join(entry.fetch("control"), "brunch-compose.yaml")
|
|
148
|
+
FileUtils.mkdir_p(File.dirname(target))
|
|
149
|
+
File.write(target, YAML.dump(document))
|
|
150
|
+
entry["ports"] = assignments
|
|
151
|
+
entry["port"] = assignments.first.fetch("host") unless assignments.empty?
|
|
152
|
+
entry["rendered_compose"] = target
|
|
153
|
+
entry
|
|
154
|
+
rescue Psych::Exception => e
|
|
155
|
+
raise CLI::ConfigurationError, "Could not parse rendered Compose configuration: #{e.message}"
|
|
156
|
+
end
|
|
157
|
+
|
|
96
158
|
def worktree_activate(path, force: false)
|
|
97
159
|
path = File.realpath(path)
|
|
98
160
|
id = worktree_identity(path)
|
|
@@ -150,13 +212,16 @@ module Brunch
|
|
|
150
212
|
if %w[docker_compose podman_compose].include?(config.fetch("manager"))
|
|
151
213
|
source_file = File.join(path, compose_file)
|
|
152
214
|
abort "Missing #{compose_file} in #{path}." unless File.file?(source_file)
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
215
|
+
unless auto_ports?(config)
|
|
216
|
+
destination = File.join(control, compose_file)
|
|
217
|
+
FileUtils.mkdir_p(File.dirname(destination))
|
|
218
|
+
FileUtils.cp(source_file, destination)
|
|
219
|
+
end
|
|
156
220
|
end
|
|
157
221
|
entry = { "source_type" => "worktree", "snapshot" => path, "control" => control,
|
|
158
222
|
"ref" => ref, "head" => head, "port" => port, "project" => project,
|
|
159
223
|
"compose_file" => compose_file, "configuration" => config, "status" => "starting" }
|
|
224
|
+
entry = render_compose_ports(path, entry, manager) if auto_ports?(config)
|
|
160
225
|
with_state_lock do |data|
|
|
161
226
|
record = data.fetch("worktrees").fetch(id)
|
|
162
227
|
record.fetch("environments")[ref] = entry
|
|
@@ -167,7 +232,7 @@ module Brunch
|
|
|
167
232
|
with_state_lock do |data|
|
|
168
233
|
data.fetch("worktrees").fetch(id).fetch("environments")[ref] = entry.merge("status" => "running")
|
|
169
234
|
end
|
|
170
|
-
puts "Started #{ref} in #{path} at http://127.0.0.1:#{port}"
|
|
235
|
+
puts "Started #{ref} in #{path} at http://127.0.0.1:#{entry.fetch('port')}"
|
|
171
236
|
end
|
|
172
237
|
end
|
|
173
238
|
|
|
@@ -405,7 +470,8 @@ module Brunch
|
|
|
405
470
|
record, current = current_worktree_entry(data)
|
|
406
471
|
return puts "No Brunch environment in this worktree." unless current
|
|
407
472
|
|
|
408
|
-
puts "#{current.fetch('ref')} (#{record.fetch('path')}): #{worktree_manager(current).status(current)}, http://127.0.0.1:#{
|
|
473
|
+
puts "#{current.fetch('ref')} (#{record.fetch('path')}): #{worktree_manager(current).status(current)}, http://127.0.0.1:#{current.fetch('port')}"
|
|
474
|
+
print_port_mappings(current)
|
|
409
475
|
end
|
|
410
476
|
|
|
411
477
|
def worktree_ports
|
|
@@ -422,11 +488,11 @@ module Brunch
|
|
|
422
488
|
prefix = index == branches.length - 1 ? " └─" : " ├─"
|
|
423
489
|
running = ref == record["current"] && entry["status"] == "running"
|
|
424
490
|
unless running
|
|
425
|
-
puts color("#{prefix} ○ #{ref} #{entry
|
|
491
|
+
puts color("#{prefix} ○ #{ref} #{port_label(entry)}", 90)
|
|
426
492
|
next
|
|
427
493
|
end
|
|
428
494
|
|
|
429
|
-
puts "#{prefix} #{color("● #{ref}", 32)} #{entry
|
|
495
|
+
puts "#{prefix} #{color("● #{ref}", 32)} #{port_label(entry)}"
|
|
430
496
|
end
|
|
431
497
|
end
|
|
432
498
|
end
|
|
@@ -445,11 +511,31 @@ module Brunch
|
|
|
445
511
|
puts "\nStopped watching."
|
|
446
512
|
end
|
|
447
513
|
|
|
448
|
-
def worktree_port
|
|
449
|
-
|
|
450
|
-
raise CLI::OperationError, "No Brunch environment in this worktree." unless
|
|
514
|
+
def worktree_port(service = nil, target = nil)
|
|
515
|
+
_record, entry = current_worktree_entry(state)
|
|
516
|
+
raise CLI::OperationError, "No Brunch environment in this worktree." unless entry
|
|
517
|
+
|
|
518
|
+
return puts entry.fetch("port") unless service
|
|
519
|
+
|
|
520
|
+
mapping = Array(entry["ports"]).find do |candidate|
|
|
521
|
+
candidate["service"] == service && candidate["target"].to_s == target
|
|
522
|
+
end
|
|
523
|
+
raise CLI::OperationError, "No published port for #{service}:#{target}." unless mapping
|
|
524
|
+
|
|
525
|
+
puts mapping.fetch("host")
|
|
526
|
+
end
|
|
527
|
+
|
|
528
|
+
def port_label(entry)
|
|
529
|
+
mappings = Array(entry["ports"])
|
|
530
|
+
return entry.fetch("port").to_s if mappings.empty?
|
|
451
531
|
|
|
452
|
-
|
|
532
|
+
mappings.map { |mapping| "#{mapping.fetch('service')}:#{mapping.fetch('target')}/#{mapping.fetch('protocol')}→#{mapping.fetch('host')}" }.join(", ")
|
|
533
|
+
end
|
|
534
|
+
|
|
535
|
+
def print_port_mappings(entry)
|
|
536
|
+
Array(entry["ports"]).each do |mapping|
|
|
537
|
+
puts " #{mapping.fetch('service')}:#{mapping.fetch('target')}/#{mapping.fetch('protocol')} → 127.0.0.1:#{mapping.fetch('host')}"
|
|
538
|
+
end
|
|
453
539
|
end
|
|
454
540
|
|
|
455
541
|
def worktree_logs(arguments)
|
|
@@ -469,7 +555,7 @@ module Brunch
|
|
|
469
555
|
|
|
470
556
|
def worktree_doctor
|
|
471
557
|
data = state
|
|
472
|
-
ports = data
|
|
558
|
+
ports = assigned_ports(data)
|
|
473
559
|
paths = worktree_paths
|
|
474
560
|
hooks_dir = git_output("rev-parse", "--git-path", "hooks")
|
|
475
561
|
checks = {
|
data/lib/brunch.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: brunch
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Maciej Ciemborowicz
|
|
@@ -94,6 +94,7 @@ files:
|
|
|
94
94
|
- lib/brunch.rb
|
|
95
95
|
- lib/brunch/branch_settlement.rb
|
|
96
96
|
- lib/brunch/cli.rb
|
|
97
|
+
- lib/brunch/compose_ports.rb
|
|
97
98
|
- lib/brunch/hooks.rb
|
|
98
99
|
- lib/brunch/managers.rb
|
|
99
100
|
- lib/brunch/version.rb
|