yamine 0.12.0 → 0.13.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 +46 -4
- data/README.md +20 -4
- data/lib/ask/skills/yamine/SKILL.md +11 -0
- data/lib/yamine/cli/boot.rb +3 -1
- data/lib/yamine/cli/routes.rb +14 -4
- data/lib/yamine/config.rb +35 -4
- data/lib/yamine/proxy.rb +27 -3
- data/lib/yamine/resolver.rb +2 -1
- data/lib/yamine/route_store.rb +7 -1
- data/lib/yamine/runner.rb +4 -4
- data/lib/yamine/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6f7c7127a262069469056cf55f94b5785de8e1890bc0814d84453159e1a01096
|
|
4
|
+
data.tar.gz: 0001bfbad73772c3caa7e615384a0c70dfa82732987174f55cbfbffac703dd15
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 702d9b80482ac8f541f16ab5946b8916b0c23750774e398eabe85cd940a5d39306bdc188e010065e17ecf91081feb79b94d021a24c71186978ba8d1e4ede7dba
|
|
7
|
+
data.tar.gz: f0f12d6cc5b32bcc46804ef3009a72ab4fac500f792b6d6ea39f05324857031b4cb0f06fe131ef291a61f2faf1d1fd87eb85520d6698b63a6a5b6dde394e637d
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,47 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.13.0] — 2026-09-11
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
|
|
7
|
+
- **The subdomain fallback is now opt-in: `proxy.subdomains`.** A route
|
|
8
|
+
used to answer its own subdomains unconditionally, so *any* label under
|
|
9
|
+
a live app silently resolved to that app. The expensive case is a
|
|
10
|
+
worktree: once `<branch>.myapp.localhost` became the worktree URL, the
|
|
11
|
+
same hostname with that worktree **stopped** answered as the main
|
|
12
|
+
checkout — HTTP 200, a real app, the wrong code, and nothing to
|
|
13
|
+
distinguish it from the right answer. You had to notice the branch
|
|
14
|
+
was running to know you were looking at someone else's build.
|
|
15
|
+
|
|
16
|
+
Unregistered hostnames now 404 instead, and the page names the parent
|
|
17
|
+
app and its directory: "`myapp.localhost` is running in `/code/myapp`.
|
|
18
|
+
If `my-branch.myapp.localhost` is a worktree or branch, start it there
|
|
19
|
+
(`yamine start`)." The failure states its own fix.
|
|
20
|
+
|
|
21
|
+
Opting in is one line per app that genuinely wants it:
|
|
22
|
+
|
|
23
|
+
```yaml
|
|
24
|
+
proxy:
|
|
25
|
+
subdomains: true # this app answers *.myapp.localhost
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
`yamine alias <name> <port> --wildcard` is the same opt-in for a
|
|
29
|
+
single ad-hoc route. `yamine status` now reports which mode an app is
|
|
30
|
+
in, since it changes what an unregistered label resolves to.
|
|
31
|
+
|
|
32
|
+
Nothing depended on the ambient behavior: no sibling app config used
|
|
33
|
+
subdomains, and the only artifact was one test, now the opt-in test.
|
|
34
|
+
Route files written before this need no migration — the flag is absent
|
|
35
|
+
unless set, and absent means exact hostname only.
|
|
36
|
+
|
|
37
|
+
### Fixed
|
|
38
|
+
|
|
39
|
+
- **`proxy: false` on a process named `web` no longer fails validation.**
|
|
40
|
+
The EXAMPLE can only name one class per key (`true` → TrueClass), so a
|
|
41
|
+
literal `false` was compared as falseclass and rejected with the
|
|
42
|
+
self-contradictory "expected a boolean, got falseclass". Booleans are
|
|
43
|
+
now checked as booleans.
|
|
44
|
+
|
|
3
45
|
## [0.12.0] — 2026-09-11
|
|
4
46
|
|
|
5
47
|
### Fixed
|
|
@@ -84,10 +126,10 @@
|
|
|
84
126
|
|
|
85
127
|
- **Ruby, git, and curl now trust the local CA — so an app can call
|
|
86
128
|
another app at `https://<name>.localhost` with no configuration.** The
|
|
87
|
-
gap surfaced when anywaye (a Rails app) tried to reach
|
|
88
|
-
|
|
89
|
-
CA was trusted everywhere a human looks (Safari, Chrome,
|
|
90
|
-
nowhere a child process looks — only Node was covered, via
|
|
129
|
+
gap surfaced when anywaye (a Rails app) tried to reach a second
|
|
130
|
+
checkout of anymark at its `.localhost` URL: `certificate verify
|
|
131
|
+
failed`. The CA was trusted everywhere a human looks (Safari, Chrome,
|
|
132
|
+
curl) and nowhere a child process looks — only Node was covered, via
|
|
91
133
|
`NODE_EXTRA_CA_CERTS`.
|
|
92
134
|
|
|
93
135
|
The naive fix is wrong in a way that shows up much later. `SSL_CERT_FILE`
|
data/README.md
CHANGED
|
@@ -91,6 +91,7 @@ of truth for service name, proxy TLD/host, processes, and env:
|
|
|
91
91
|
service: myapp
|
|
92
92
|
proxy:
|
|
93
93
|
tld: localhost
|
|
94
|
+
subdomains: false # opt in to answering *.myapp.localhost
|
|
94
95
|
processes:
|
|
95
96
|
web:
|
|
96
97
|
cmd: bundle exec puma -b tcp://127.0.0.1:$PORT config.ru
|
|
@@ -162,10 +163,25 @@ yamine --variant demo # -> https://demo.myapp.localhos
|
|
|
162
163
|
yamine --tld preview.example.com # your own domain (OAuth parity)
|
|
163
164
|
```
|
|
164
165
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
166
|
+
## Subdomains are opt-in
|
|
167
|
+
|
|
168
|
+
A route answers its exact hostname. `*.myapp.localhost` reaches
|
|
169
|
+
`myapp.localhost` only if that app asked for it:
|
|
170
|
+
|
|
171
|
+
```yaml
|
|
172
|
+
proxy:
|
|
173
|
+
subdomains: true # this app answers its own subdomains
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
yamine alias tenant1 4001 --wildcard # one route, its subdomains
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
Off is the useful default. An unregistered label under a live app is far
|
|
181
|
+
more likely to be a worktree whose stack is stopped than a tenant, and
|
|
182
|
+
handing that label to the parent app means HTTP 200 with the wrong code.
|
|
183
|
+
Instead the request 404s and names the parent app, its directory, and how
|
|
184
|
+
to start it. `yamine status` reports which mode an app is in.
|
|
169
185
|
|
|
170
186
|
## Commands
|
|
171
187
|
|
|
@@ -98,6 +98,17 @@ Only an explicit variant ever looks for an overlay file — a branch name
|
|
|
98
98
|
that happens to match one on disk is ignored. `yamine status` shows
|
|
99
99
|
`variant:` and `overlay:` separately.
|
|
100
100
|
|
|
101
|
+
## Subdomains are opt-in
|
|
102
|
+
|
|
103
|
+
A route answers its exact hostname. `*.myapp.localhost` reaches the app
|
|
104
|
+
only if it asked (`proxy.subdomains: true` in `config/local.yml`, or
|
|
105
|
+
`yamine alias <name> <port> --wildcard` for one route).
|
|
106
|
+
|
|
107
|
+
So a worktree whose stack is not running gets a 404 that names the parent
|
|
108
|
+
app, its directory, and `yamine start` — not the parent app's code. If
|
|
109
|
+
you hit a `.localhost` URL that loads but looks wrong, check you started
|
|
110
|
+
the worktree you think you did; `yamine status` in it prints the URL.
|
|
111
|
+
|
|
101
112
|
## First time on a machine
|
|
102
113
|
|
|
103
114
|
Run `yamine start` — it does the one-shot CA trust, port 443, and
|
data/lib/yamine/cli/boot.rb
CHANGED
|
@@ -243,6 +243,7 @@ module Yamine
|
|
|
243
243
|
url: item[:url], dir: Dir.pwd, command: item[:command],
|
|
244
244
|
port: item[:port], force: opts[:force],
|
|
245
245
|
rails_dev_host: item[:hostname], database_url: db_url,
|
|
246
|
+
subdomains: resolved.subdomains,
|
|
246
247
|
extra_env: build_env(resolved, item[:entry], proc_name: item[:name]))
|
|
247
248
|
routes_registered << { hostnames: item[:hostnames], app: app }
|
|
248
249
|
|
|
@@ -281,7 +282,8 @@ module Yamine
|
|
|
281
282
|
if failed.empty?
|
|
282
283
|
apps.each do |name, slot|
|
|
283
284
|
runner.adopt(slot[:item][:hostname], slot[:app], force: opts[:force],
|
|
284
|
-
spec: { "dir" => File.expand_path(Dir.pwd), "proc" => name }
|
|
285
|
+
spec: { "dir" => File.expand_path(Dir.pwd), "proc" => name },
|
|
286
|
+
subdomains: resolved.subdomains)
|
|
285
287
|
routes_registered << { hostnames: slot[:item][:hostnames], app: slot[:app] }
|
|
286
288
|
say opts, " -> #{slot[:item][:url]}"
|
|
287
289
|
end
|
data/lib/yamine/cli/routes.rb
CHANGED
|
@@ -147,14 +147,20 @@ module Yamine
|
|
|
147
147
|
puts "Removed alias #{hostname}."
|
|
148
148
|
return
|
|
149
149
|
end
|
|
150
|
-
name, port_or_url = args
|
|
151
|
-
raise Error, "Usage: yamine alias <name> <port|url>" unless name && port_or_url
|
|
150
|
+
name, port_or_url = args.reject { |a| a.start_with?("--") }
|
|
151
|
+
raise Error, "Usage: yamine alias <name> <port|url> [--wildcard]" unless name && port_or_url
|
|
152
152
|
|
|
153
153
|
hostname = alias_hostname(name)
|
|
154
154
|
target = port_or_url.match?(/\A\d+\z/) ? "127.0.0.1:#{port_or_url}" : port_or_url
|
|
155
155
|
force = args.include?("--force")
|
|
156
|
-
|
|
157
|
-
|
|
156
|
+
# --wildcard is the per-route escape hatch for the tenant case:
|
|
157
|
+
# this alias answers its own subdomains. Config `proxy.subdomains`
|
|
158
|
+
# is the same opt-in for a whole app at boot.
|
|
159
|
+
wildcard = args.include?("--wildcard")
|
|
160
|
+
ctx.store.add_route(hostname, target, 0, kind: "tcp", force: force,
|
|
161
|
+
subdomains: wildcard)
|
|
162
|
+
suffix = wildcard ? " (and its subdomains)" : ""
|
|
163
|
+
puts "#{hostname} -> #{target}#{suffix}"
|
|
158
164
|
end
|
|
159
165
|
|
|
160
166
|
# A name containing dots is treated as a full hostname (any TLD);
|
|
@@ -282,6 +288,7 @@ module Yamine
|
|
|
282
288
|
host: resolved.host, host_source: resolved.sources[:host],
|
|
283
289
|
variant: resolved.variant, variant_source: resolved.sources[:variant],
|
|
284
290
|
overlay: resolved.overlay, overlay_source: resolved.sources[:overlay],
|
|
291
|
+
subdomains: resolved.subdomains,
|
|
285
292
|
urls: urls,
|
|
286
293
|
processes: resolved.processes.keys,
|
|
287
294
|
framework: Framework.detect(Dir.pwd).to_s
|
|
@@ -302,6 +309,9 @@ module Yamine
|
|
|
302
309
|
# explicit --variant / YAMINE_VARIANT ever sets it, so say so
|
|
303
310
|
# rather than letting the two look like one setting.
|
|
304
311
|
puts "overlay: #{payload[:overlay] ? "#{payload[:overlay]} (merged)" : "(none)"}"
|
|
312
|
+
# Off by default; say when it is on, because it changes what an
|
|
313
|
+
# unregistered label under this app resolves to.
|
|
314
|
+
puts "subdomains: #{payload[:subdomains] ? "own subdomains resolve here" : "exact hostname only"}"
|
|
305
315
|
puts "processes: #{payload[:processes].join(", ")}"
|
|
306
316
|
puts "urls:"
|
|
307
317
|
urls.each { |u| puts " #{u}" }
|
data/lib/yamine/config.rb
CHANGED
|
@@ -51,7 +51,12 @@ module Yamine
|
|
|
51
51
|
"service" => "myapp",
|
|
52
52
|
"proxy" => {
|
|
53
53
|
"tld" => "localhost",
|
|
54
|
-
"host" => "myapp.local.example.com"
|
|
54
|
+
"host" => "myapp.local.example.com",
|
|
55
|
+
# Opt this app into answering its own subdomains. Off by default:
|
|
56
|
+
# an unregistered label under a live app is far more likely to be
|
|
57
|
+
# a stopped worktree than a tenant, and resolving it to the wrong
|
|
58
|
+
# app is worse than not resolving it.
|
|
59
|
+
"subdomains" => false
|
|
55
60
|
},
|
|
56
61
|
# db: false opts out of per-worktree databases entirely (exotic
|
|
57
62
|
# setups: manual establish_connection, shared staging DB, ...).
|
|
@@ -178,6 +183,14 @@ module Yamine
|
|
|
178
183
|
data[key]
|
|
179
184
|
end
|
|
180
185
|
|
|
186
|
+
# Whether this app answers its own subdomains. Off unless asked: the
|
|
187
|
+
# ambient fallback used to hand any unregistered label to whichever
|
|
188
|
+
# app owned the parent name, which reads as the right app running the
|
|
189
|
+
# wrong code — the worktree case that motivated the opt-in.
|
|
190
|
+
def subdomains?
|
|
191
|
+
proxy_config["subdomains"] == true
|
|
192
|
+
end
|
|
193
|
+
|
|
181
194
|
private
|
|
182
195
|
|
|
183
196
|
def load_secrets
|
|
@@ -274,6 +287,9 @@ module Yamine
|
|
|
274
287
|
if value["tld"] && !Sanitize.valid_tld?(value["tld"].downcase)
|
|
275
288
|
raise ConfigError, "#{context}: invalid tld #{value["tld"].inspect}"
|
|
276
289
|
end
|
|
290
|
+
if value.key?("subdomains") && ![true, false].include?(value["subdomains"])
|
|
291
|
+
raise ConfigError, "#{context}: subdomains must be a boolean"
|
|
292
|
+
end
|
|
277
293
|
end
|
|
278
294
|
|
|
279
295
|
def validate_db(value, context)
|
|
@@ -364,13 +380,28 @@ module Yamine
|
|
|
364
380
|
elsif example_value.is_a?(Array) && value.is_a?(Array)
|
|
365
381
|
validate_array_of!(value, example_value.first.class) unless example_value.empty?
|
|
366
382
|
elsif !example_value.nil?
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
383
|
+
# Booleans are one type, but the example can only name one class
|
|
384
|
+
# (TrueClass for `true`), so a literal `false` compared as
|
|
385
|
+
# falseclass and was rejected: `proxy: false` on a process
|
|
386
|
+
# raised "expected a boolean, got falseclass". Ask the value
|
|
387
|
+
# what it is instead of the example.
|
|
388
|
+
if boolean?(example_value)
|
|
389
|
+
unless boolean?(value)
|
|
390
|
+
raise ConfigError, "#{current_context}: expected a boolean, got #{value.class.name.downcase}"
|
|
391
|
+
end
|
|
392
|
+
else
|
|
393
|
+
expected = type_description(example_value.class)
|
|
394
|
+
unless value.is_a?(example_value.class) || (example_value.is_a?(String) && value.is_a?(String))
|
|
395
|
+
raise ConfigError, "#{current_context}: expected #{expected}, got #{value.class.name.downcase}"
|
|
396
|
+
end
|
|
370
397
|
end
|
|
371
398
|
end
|
|
372
399
|
end
|
|
373
400
|
|
|
401
|
+
def boolean?(value)
|
|
402
|
+
value == true || value == false
|
|
403
|
+
end
|
|
404
|
+
|
|
374
405
|
def validate_array_of!(array, type)
|
|
375
406
|
array.each_with_index do |value, index|
|
|
376
407
|
with_context(index) do
|
data/lib/yamine/proxy.rb
CHANGED
|
@@ -106,6 +106,17 @@ module Yamine
|
|
|
106
106
|
end
|
|
107
107
|
|
|
108
108
|
# Pure request-routing core, tested without sockets.
|
|
109
|
+
#
|
|
110
|
+
# Exact hostname first, then an opted-in wildcard. The wildcard is
|
|
111
|
+
# deliberately not ambient: a route only answers its own subdomains
|
|
112
|
+
# when it registered with `subdomains: true` (config
|
|
113
|
+
# `proxy.subdomains`, or `yamine alias --wildcard`).
|
|
114
|
+
#
|
|
115
|
+
# It used to be unconditional, which meant every unregistered label
|
|
116
|
+
# under any live app silently resolved to that app. The expensive
|
|
117
|
+
# case is a worktree: `<branch>.myapp.localhost` answered as the main
|
|
118
|
+
# checkout — a wrong-but-working app, indistinguishable from the
|
|
119
|
+
# right one. An unregistered hostname now 404s, and says so.
|
|
109
120
|
def route(authority, routes)
|
|
110
121
|
host = Hostname.strip_port(authority)
|
|
111
122
|
return nil if host.empty? || host.bytesize > MAX_HOSTNAME_BYTES
|
|
@@ -113,8 +124,7 @@ module Yamine
|
|
|
113
124
|
exact = routes.find { |r| r["hostname"] == host }
|
|
114
125
|
return exact if exact
|
|
115
126
|
|
|
116
|
-
|
|
117
|
-
wildcard
|
|
127
|
+
routes.find { |r| r["subdomains"] && host.end_with?(".#{r["hostname"]}") }
|
|
118
128
|
end
|
|
119
129
|
|
|
120
130
|
def check_hops(headers)
|
|
@@ -452,9 +462,23 @@ module Yamine
|
|
|
452
462
|
return respond(sock, 404, "<h1>Not Found</h1>")
|
|
453
463
|
end
|
|
454
464
|
|
|
465
|
+
# A label in front of a live app is almost always a worktree or a
|
|
466
|
+
# branch whose stack is not running. Name the parent it would fall
|
|
467
|
+
# under and where it lives, so "the app loaded but it's the wrong
|
|
468
|
+
# code" becomes "that worktree isn't running" without a search.
|
|
469
|
+
parent = routes.find { |r| bare.end_with?(".#{r["hostname"]}") }
|
|
470
|
+
hint = if parent
|
|
471
|
+
dir = parent.dig("spec", "dir")
|
|
472
|
+
where = dir ? " in #{escape(dir)}" : ""
|
|
473
|
+
"<p><strong>#{escape(parent["hostname"])}</strong> is running#{where}.</p>" \
|
|
474
|
+
"<p>If #{escape(bare)} is a worktree or branch, start it there " \
|
|
475
|
+
"(<code>yamine start</code>), or open " \
|
|
476
|
+
"<strong>#{escape(parent["hostname"])}</strong> instead.</p>"
|
|
477
|
+
end
|
|
478
|
+
|
|
455
479
|
items = routes.map { |r| "<li>#{escape(r["hostname"])}</li>" }.join
|
|
456
480
|
body = "<h1>No app registered for #{escape(bare)}</h1>" \
|
|
457
|
-
"<ul>#{items}</ul>"
|
|
481
|
+
"#{hint}<ul>#{items}</ul>"
|
|
458
482
|
respond(sock, 404, body)
|
|
459
483
|
end
|
|
460
484
|
|
data/lib/yamine/resolver.rb
CHANGED
|
@@ -9,7 +9,7 @@ module Yamine
|
|
|
9
9
|
module_function
|
|
10
10
|
|
|
11
11
|
Result = Struct.new(:app, :tld, :host, :processes, :secrets,
|
|
12
|
-
:sources, :variant, :overlay, :db, :env, keyword_init: true)
|
|
12
|
+
:sources, :variant, :overlay, :subdomains, :db, :env, keyword_init: true)
|
|
13
13
|
|
|
14
14
|
# Two axes, and keeping them apart is the whole point:
|
|
15
15
|
#
|
|
@@ -69,6 +69,7 @@ module Yamine
|
|
|
69
69
|
sources: sources,
|
|
70
70
|
variant: variant_name,
|
|
71
71
|
overlay: overlay,
|
|
72
|
+
subdomains: config.subdomains?,
|
|
72
73
|
db: config.data["db"],
|
|
73
74
|
env: config.env_config
|
|
74
75
|
)
|
data/lib/yamine/route_store.rb
CHANGED
|
@@ -76,7 +76,12 @@ module Yamine
|
|
|
76
76
|
# `agent` records the owner (defaults to Agent.name); foreign-owned
|
|
77
77
|
# live routes can only be taken with force: true, and the conflict
|
|
78
78
|
# error names the other agent plus its worktree dir.
|
|
79
|
-
|
|
79
|
+
#
|
|
80
|
+
# `subdomains` opts this route into answering its own subdomains.
|
|
81
|
+
# Absent (the default, and what every pre-existing route file has)
|
|
82
|
+
# means exact hostname only — see Proxy#route for why.
|
|
83
|
+
def add_route(hostname, target, pid, kind:, force: false, spec: nil, agent: nil,
|
|
84
|
+
subdomains: false)
|
|
80
85
|
agent ||= Agent.name
|
|
81
86
|
killed = nil
|
|
82
87
|
with_lock do
|
|
@@ -101,6 +106,7 @@ module Yamine
|
|
|
101
106
|
entry = { "hostname" => hostname, "target" => target, "kind" => kind,
|
|
102
107
|
"pid" => pid, "agent" => agent }
|
|
103
108
|
entry["spec"] = spec if spec
|
|
109
|
+
entry["subdomains"] = true if subdomains
|
|
104
110
|
routes << entry
|
|
105
111
|
save_routes(routes)
|
|
106
112
|
end
|
data/lib/yamine/runner.rb
CHANGED
|
@@ -99,7 +99,7 @@ module Yamine
|
|
|
99
99
|
# win over it, because those describe the boot rather than the app.
|
|
100
100
|
def boot_run(name:, hostname:, url:, dir:, command:, port: nil, force: false,
|
|
101
101
|
rails_dev_host: nil, register: true, database_url: nil, spec: nil,
|
|
102
|
-
extra_env: nil)
|
|
102
|
+
extra_env: nil, subdomains: false)
|
|
103
103
|
port ||= Ports.find_free
|
|
104
104
|
env = child_env(dir, url: url, port: port, rails_dev_host: rails_dev_host,
|
|
105
105
|
database_url: database_url, extra_env: extra_env)
|
|
@@ -111,7 +111,7 @@ module Yamine
|
|
|
111
111
|
begin
|
|
112
112
|
spec ||= { "dir" => File.expand_path(dir), "proc" => name }
|
|
113
113
|
@store.add_route(hostname, target, Process.pid, kind: "tcp",
|
|
114
|
-
force: force, spec: spec)
|
|
114
|
+
force: force, spec: spec, subdomains: subdomains)
|
|
115
115
|
write_backend_pid(hostname, pid)
|
|
116
116
|
rescue StandardError
|
|
117
117
|
# Registration refused (quota, conflict): the backend is
|
|
@@ -140,9 +140,9 @@ module Yamine
|
|
|
140
140
|
end
|
|
141
141
|
|
|
142
142
|
# Register an already-spawned backend: route + sidecar, together.
|
|
143
|
-
def adopt(hostname, app, force: false, spec: nil)
|
|
143
|
+
def adopt(hostname, app, force: false, spec: nil, subdomains: false)
|
|
144
144
|
@store.add_route(hostname, app.target, Process.pid, kind: app.kind,
|
|
145
|
-
force: force, spec: spec)
|
|
145
|
+
force: force, spec: spec, subdomains: subdomains)
|
|
146
146
|
write_backend_pid(hostname, app.pid)
|
|
147
147
|
nil
|
|
148
148
|
end
|
data/lib/yamine/version.rb
CHANGED