yamine 0.13.0 → 0.13.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6f7c7127a262069469056cf55f94b5785de8e1890bc0814d84453159e1a01096
4
- data.tar.gz: 0001bfbad73772c3caa7e615384a0c70dfa82732987174f55cbfbffac703dd15
3
+ metadata.gz: 122503332845c1bbee6034e7262b763d38c2d4c849723e9519dfc7e91f0739b6
4
+ data.tar.gz: 5ae28759ce2ba01f7c9239056ca49d0ceda6f0958e26a534561fdcab76a485f3
5
5
  SHA512:
6
- metadata.gz: 702d9b80482ac8f541f16ab5946b8916b0c23750774e398eabe85cd940a5d39306bdc188e010065e17ecf91081feb79b94d021a24c71186978ba8d1e4ede7dba
7
- data.tar.gz: f0f12d6cc5b32bcc46804ef3009a72ab4fac500f792b6d6ea39f05324857031b4cb0f06fe131ef291a61f2faf1d1fd87eb85520d6698b63a6a5b6dde394e637d
6
+ metadata.gz: 35f7dc98e1beaa8fcbf46dc2dbb735656789c193d2ed713e395fc038fa9a7801fcec6ea552c3affc67c82f0a3101b3200eb6126459f0588b723245318e257981
7
+ data.tar.gz: c3f54ddcc3cb3d0f6b99bca81770fc4addd61cadb22baf176c81042b9609edb079b69f2f41dd265378b08c44eb1a322dc00cffca580496741086750671950007
data/CHANGELOG.md CHANGED
@@ -1,5 +1,53 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.13.2] — 2026-09-12
4
+
5
+ ### Fixed
6
+
7
+ - **"Resolves" now means "resolves for every client", not "the system
8
+ resolver said yes".** `Hosts.resolves?` asked `Addrinfo.getaddrinfo`,
9
+ which on macOS answers any `.localhost` name unconditionally — so
10
+ doctor and boot reported every `.localhost` route as fully resolved
11
+ even with no `/etc/hosts` entry, while a client that resolves only
12
+ from the hosts file (a CGO-disabled Go binary; AnyGit is the case
13
+ that found it) could not resolve any of them. That invisible gap is
14
+ why `sudo yamine hosts sync` existed as a manual step at all.
15
+
16
+ The check is now state-based. `Hosts.hosts_entry?` reads the hosts
17
+ file directly — any loopback mapping counts, hand-added entries
18
+ included, not just yamine's managed block — and `Hosts.resolution`
19
+ classifies each route as `ok` (every client resolves it), `warn`
20
+ (browsers and curl fine via the RFC 6761 `.localhost` special case;
21
+ file-only resolvers cannot see it), or `fail` (nothing resolves it).
22
+ Doctor renders the warn class with its existing `[warn]` state instead
23
+ of a failure — the 0.7.0 lesson, kept by severity and wording rather
24
+ than by a probe — and boot names the two gaps separately.
25
+
26
+ `.localhost` names are classified on file absence alone: the system
27
+ resolver's unconditional yes carries no information, and acting on it
28
+ was the bug. getaddrinfo now runs only for custom-TLD names the file
29
+ does not answer (real DNS or `/etc/resolver` setups stay `ok`), so
30
+ the common doctor path does no probing at all.
31
+
32
+ ## [0.13.1] — 2026-09-12
33
+
34
+ ### Fixed
35
+
36
+ - **`worktree clean` no longer reports "could not drop — remove manually"
37
+ for a database that never existed.** `dropdb` fails on a missing
38
+ database, and the old code collapsed that into a generic failure. Now
39
+ `Database.drop` returns an honest three-state outcome — `:dropped`,
40
+ `:missing` (nothing to drop, not an error), `:failed` (server
41
+ unreachable or drop refused) — by splitting "server reachable" from
42
+ "database present". Clean prints each outcome (`dropped N`,
43
+ `N did not exist — nothing to drop`) plus a summary line, exits 1 only
44
+ when something really failed, and **keeps the claim of a failed drop**
45
+ so the next clean can retry it (the old code deleted all claims
46
+ regardless, losing track of databases it failed to drop).
47
+ - `db drop` and `db create` resolve the DATABASE_URL template the way
48
+ boot does (ENV, then config env.clear) instead of ENV-only, so they
49
+ work from a shell without the variable exported.
50
+
3
51
  ## [0.13.0] — 2026-09-11
4
52
 
5
53
  ### Changed
data/README.md CHANGED
@@ -122,7 +122,9 @@ destination pattern). Naming a variant also prefixes the hostname — see
122
122
  below for how that differs from a worktree's automatic prefix.
123
123
 
124
124
  `.localhost` resolves to loopback natively in Chrome, Firefox, and Edge —
125
- no DNS server, no `/etc/resolver`. Safari may need `yamine hosts sync`.
125
+ no DNS server, no `/etc/resolver`. Safari, custom TLDs, and resolvers
126
+ that read only `/etc/hosts` (CGO-disabled Go binaries are the common
127
+ case) need `yamine hosts sync`.
126
128
 
127
129
  ## Hostname shape
128
130
 
@@ -144,8 +144,10 @@ yamine prune # clear stale routes from crashed sessions
144
144
  yamine start --json # boot readiness payload (pass/fail + log tail)
145
145
  ```
146
146
 
147
- If a hostname does not resolve: `yamine hosts sync`. If the browser
148
- warns about TLS: `yamine trust`.
147
+ If a hostname does not resolve — or boot/doctor report it as not in
148
+ `/etc/hosts` and you use clients that read only that file (CGO-disabled
149
+ Go binaries): `yamine hosts sync`. If the browser warns about TLS:
150
+ `yamine trust`.
149
151
 
150
152
  ## When NOT to use yamine
151
153
 
@@ -157,7 +157,7 @@ module Yamine
157
157
  end
158
158
 
159
159
  say opts
160
- ctx.report_unresolved(routes_registered.flat_map { |r| r[:hostnames] })
160
+ ctx.report_resolution_gaps(routes_registered.flat_map { |r| r[:hostnames] })
161
161
 
162
162
  # Supervisor: exit when ANY child dies (loud cleanup). pid => name
163
163
  # so the message names the casualty instead of "a process".
@@ -546,13 +546,7 @@ module Yamine
546
546
  end
547
547
 
548
548
  def database_template_from_config(resolved)
549
- top = resolved.env.is_a?(Hash) ? resolved.env["clear"] : nil
550
- from_top = top.is_a?(Hash) ? top["DATABASE_URL"] : nil
551
- return from_top if from_top && !from_top.to_s.strip.empty?
552
-
553
- env = resolved.processes.values.map { |e| e["env"] || {} }
554
- clear = env.map { |e| e["clear"] || {} }.reduce({}, :merge)
555
- clear["DATABASE_URL"]
549
+ Database.template_for(resolved)
556
550
  end
557
551
 
558
552
  # Schema-load on first boot uses the app's own command when declared
@@ -35,11 +35,22 @@ module Yamine
35
35
  ProxyControl.proxy_tls(store)
36
36
  end
37
37
 
38
- def report_unresolved(hostnames)
39
- missing = Hosts.unresolved(hostnames)
40
- return if missing.empty?
38
+ # Boot-time DNS report. Two distinct gaps, named separately: a fail
39
+ # resolves nowhere; a warn resolves in browsers and curl (the RFC
40
+ # 6761 .localhost special case) but not for clients that read only
41
+ # /etc/hosts — which is exactly the gap `hosts sync` closes.
42
+ def report_resolution_gaps(hostnames)
43
+ groups = Hosts.resolution(hostnames)
44
+ return if groups[:fail].empty? && groups[:warn].empty?
41
45
 
42
- warn "Warning: #{missing.join(", ")} will not resolve. Run: yamine hosts sync"
46
+ unless groups[:fail].empty?
47
+ warn "Warning: #{groups[:fail].join(", ")} will not resolve. Run: yamine hosts sync"
48
+ end
49
+ unless groups[:warn].empty?
50
+ warn "Warning: #{groups[:warn].join(", ")} not in /etc/hosts — browsers and curl " \
51
+ "resolve them anyway, but clients that only read /etc/hosts (CGO-disabled Go " \
52
+ "binaries) cannot. Run: yamine hosts sync if you use such clients."
53
+ end
43
54
  end
44
55
 
45
56
  def wait_for_exit(pid, timeout:)
@@ -893,9 +893,9 @@ module Yamine
893
893
  resolved = Resolver.resolve(Dir.pwd)
894
894
  name = Database.name_for(Dir.pwd, env: boot_env_name,
895
895
  state_dir: ctx.store.dir)
896
- template = ENV["DATABASE_URL"]
896
+ template = drop_template
897
897
  unless template && !template.strip.empty?
898
- $stderr.puts "Error: set DATABASE_URL (template with host/user; database segment is replaced)."
898
+ $stderr.puts "Error: no DATABASE_URL template found (ENV or env.clear in config/local.yml)."
899
899
  exit 1
900
900
  end
901
901
  if Database.ensure_exists(name, template)
@@ -917,38 +917,41 @@ module Yamine
917
917
  $stderr.puts "Error: worktree #{info["dir"]} still exists. Use --force to drop #{name} anyway."
918
918
  exit 1
919
919
  end
920
- if drop_database(ctx, name)
920
+ template = drop_template
921
+ unless template && !template.strip.empty?
922
+ $stderr.puts "Error: no DATABASE_URL template found (ENV or env.clear in config/local.yml) — cannot reach the server to drop #{name}."
923
+ exit 1
924
+ end
925
+ case Database.drop(name, template)
926
+ when :dropped
921
927
  map.delete(name)
922
928
  Database.save_map(ctx.store.dir, map)
923
929
  puts "Dropped #{name}."
924
- else
925
- $stderr.puts "Error: could not drop #{name}."
930
+ when :missing
931
+ map.delete(name)
932
+ Database.save_map(ctx.store.dir, map)
933
+ puts "#{name} did not exist — nothing to drop (claim removed)."
934
+ when :failed
935
+ $stderr.puts "Error: could not drop #{name} — is the database server running? The claim is kept."
926
936
  exit 1
927
937
  end
928
938
  end
929
939
 
930
- def drop_database(_ctx, name)
931
- template = ENV["DATABASE_URL"]
932
- return false unless template && !template.strip.empty?
940
+ # Template for reaching the database server outside of boot:
941
+ # ENV first, then the app's config (env.clear). Runs from the
942
+ # main checkout, so the config's own template is the usual source.
943
+ def drop_template
944
+ env = ENV["DATABASE_URL"]
945
+ return env if env && !env.strip.empty?
933
946
 
934
- case Database.adapter_for(template)
935
- when :postgres
936
- uri = URI.parse(template)
937
- _out, status = Open3.capture2("dropdb", name, env: Database.pg_env(uri))
938
- status.success?
939
- when :mysql
940
- uri = URI.parse(template)
941
- args = ["-h", uri.host || "127.0.0.1", "-u",
942
- URI.decode_www_form_component(uri.user || "root")]
943
- _out, status = Open3.capture2("mysqladmin", *args, "drop", name, "-f")
944
- status.success?
945
- else
946
- false
947
- end
948
- rescue SystemCallError
949
- false
947
+ config = Config.load(Dir.pwd)
948
+ return nil unless config
949
+
950
+ Database.template_for(TemplateSource.new(config.env_config, config.processes))
950
951
  end
951
952
 
953
+ TemplateSource = Struct.new(:env, :processes)
954
+
952
955
  def boot_env_name
953
956
  ENV.fetch("RAILS_ENV", "development")
954
957
  end
@@ -974,16 +977,31 @@ module Yamine
974
977
  puts "No orphaned worktree databases."
975
978
  return
976
979
  end
980
+ template = drop_template
981
+ unless template && !template.strip.empty?
982
+ $stderr.puts "Error: no DATABASE_URL template found (ENV or env.clear in config/local.yml) — cannot drop the orphaned databases."
983
+ exit 1
984
+ end
985
+ dropped, missing, failed = [], [], []
977
986
  orphaned.each do |name, _info|
978
- if drop_database(ctx, name)
979
- puts "Dropped orphaned #{name}."
980
- else
981
- $stderr.puts "Could not drop #{name} — remove manually."
987
+ case Database.drop(name, template)
988
+ when :dropped then dropped << name
989
+ when :missing then missing << name
990
+ when :failed then failed << name
982
991
  end
983
992
  end
993
+ dropped.each { |n| puts " dropped #{n}" }
994
+ missing.each { |n| puts " #{n} did not exist — nothing to drop" }
995
+ failed.each { |n| warn " could not drop #{n} — is the server running? Claim kept for a retry." }
996
+ # Forget only the claims we actually handled; a failed drop
997
+ # stays recorded so the next clean can retry it.
984
998
  map = Database.load_map(ctx.store.dir)
985
- orphaned.each_key { |name| map.delete(name) }
999
+ (dropped + missing).each { |n| map.delete(n) }
986
1000
  Database.save_map(ctx.store.dir, map)
1001
+ summary = "Cleaned #{orphaned.length} orphaned claim(s): #{dropped.length} dropped, #{missing.length} already gone"
1002
+ summary += ", #{failed.length} failed" if failed.any?
1003
+ puts "#{summary}."
1004
+ exit 1 if failed.any?
987
1005
  else
988
1006
  raise Error, "Usage: yamine worktree [list|clean]"
989
1007
  end
@@ -100,6 +100,28 @@ module Yamine
100
100
  nil
101
101
  end
102
102
 
103
+ # The DATABASE_URL template per-worktree names derive from: ENV
104
+ # first, then the config's top-level env.clear, then per-process
105
+ # env.clear (configs written before top-level env existed). Shared
106
+ # by boot, `db create/drop`, and `worktree clean` so they all agree.
107
+ # Takes anything responding to .env (resolver Result / Config) —
108
+ # pass env_config for a bare Config.
109
+ def template_for(resolved)
110
+ source = if resolved.respond_to?(:env)
111
+ resolved.env
112
+ else
113
+ resolved.env_config
114
+ end
115
+ top = source.is_a?(Hash) ? source["clear"] : nil
116
+ from_top = top.is_a?(Hash) ? top["DATABASE_URL"] : nil
117
+ return from_top if from_top && !from_top.to_s.strip.empty?
118
+
119
+ processes = resolved.respond_to?(:processes) ? resolved.processes : {}
120
+ env = processes.values.map { |e| e["env"] || {} }
121
+ clear = env.map { |e| e["clear"] || {} }.reduce({}, :merge)
122
+ clear["DATABASE_URL"]
123
+ end
124
+
103
125
  # Databases whose worktree dirs no longer exist (for `worktree clean`).
104
126
  def orphaned(state_dir)
105
127
  load_map(state_dir).select { |_, v| !File.directory?(v["dir"]) }
@@ -146,6 +168,73 @@ module Yamine
146
168
  end
147
169
  end
148
170
 
171
+ # Drop a database with an honest outcome. Returns :dropped,
172
+ # :missing (the database did not exist — nothing to do, NOT an
173
+ # error), or :failed (server unreachable, drop refused). The
174
+ # distinction matters: `worktree clean` used to print "could not
175
+ # drop — remove manually" for a database that never existed,
176
+ # sending the user hunting for locks and permissions that were
177
+ # never the problem.
178
+ def drop(name, database_url)
179
+ case adapter_for(database_url)
180
+ when :postgres then pg_drop(name, database_url)
181
+ when :mysql then mysql_drop(name, database_url)
182
+ else :failed
183
+ end
184
+ end
185
+
186
+ def pg_drop(name, database_url)
187
+ reachable, exists = pg_state(name, database_url)
188
+ return :missing if reachable && !exists
189
+ return :failed unless reachable
190
+
191
+ uri = URI.parse(database_url)
192
+ _out, status = Open3.capture2("dropdb", name, env: pg_env(uri))
193
+ return :dropped if status.success?
194
+
195
+ reachable, exists = pg_state(name, database_url)
196
+ reachable && exists ? :failed : :dropped
197
+ rescue SystemCallError
198
+ :failed
199
+ end
200
+
201
+ def mysql_drop(name, database_url)
202
+ reachable, exists = mysql_state(name, database_url)
203
+ return :missing if reachable && !exists
204
+ return :failed unless reachable
205
+
206
+ uri = URI.parse(database_url)
207
+ _out, status = Open3.capture2("mysqladmin", *mysql_args(uri), "drop", name, "-f")
208
+ return :dropped if status.success?
209
+
210
+ reachable, exists = mysql_state(name, database_url)
211
+ reachable && exists ? :failed : :dropped
212
+ rescue SystemCallError
213
+ :failed
214
+ end
215
+
216
+ # [server_reachable, database_exists] — the split that lets drop
217
+ # tell "nothing to drop" apart from "could not reach the server".
218
+ def pg_state(name, database_url)
219
+ uri = URI.parse(database_url)
220
+ out, status = Open3.capture2(pg_env(uri), "psql", "-lqt")
221
+ return [false, false] unless status.success?
222
+
223
+ [true, out.split("\n").any? { |l| l.split("|").first.to_s.strip == name }]
224
+ rescue SystemCallError
225
+ [false, false]
226
+ end
227
+
228
+ def mysql_state(name, database_url)
229
+ uri = URI.parse(database_url)
230
+ out, status = Open3.capture2("mysql", *mysql_args(uri), "-e", "SHOW DATABASES;")
231
+ return [false, false] unless status.success?
232
+
233
+ [true, out.split("\n").include?(name)]
234
+ rescue SystemCallError
235
+ [false, false]
236
+ end
237
+
149
238
  # Create the database if missing. Uses createdb/mysqladmin when
150
239
  # available; returns true/false, never raises (boot reports, not dies).
151
240
  def ensure_exists(name, database_url)
data/lib/yamine/doctor.rb CHANGED
@@ -160,12 +160,24 @@ module Yamine
160
160
  hostnames = store.load_routes.map { |r| r["hostname"] }
161
161
  return Check.new(name: "dns", ok: true, message: "no routes to resolve") if hostnames.empty?
162
162
 
163
- missing = Hosts.unresolved(hostnames)
164
- if missing.empty?
163
+ groups = Hosts.resolution(hostnames)
164
+ problems = []
165
+ problems << "#{groups[:fail].join(", ")} do not resolve — run: yamine hosts sync" unless groups[:fail].empty?
166
+ unless groups[:warn].empty?
167
+ problems << "#{groups[:warn].join(", ")} not in /etc/hosts — browsers and curl " \
168
+ "resolve them anyway, but clients that only read the hosts file " \
169
+ "(CGO-disabled Go binaries) cannot — run: yamine hosts sync if you use such clients"
170
+ end
171
+
172
+ if problems.empty?
165
173
  Check.new(name: "dns", ok: true, message: "all #{hostnames.length} hostname(s) resolve")
174
+ elsif groups[:fail].empty?
175
+ # Only the file-only-resolver gap: most clients are fine, so this
176
+ # is a warn, not a failure — failing here sent browser-only users
177
+ # to an elevated hosts write they did not need (the 0.7.0 lesson).
178
+ Check.new(name: "dns", ok: true, warn: true, message: problems.join("; "))
166
179
  else
167
- Check.new(name: "dns", ok: false,
168
- message: "#{missing.join(", ")} do not resolve — run: yamine hosts sync")
180
+ Check.new(name: "dns", ok: false, message: problems.join("; "))
169
181
  end
170
182
  end
171
183
 
data/lib/yamine/hosts.rb CHANGED
@@ -4,9 +4,11 @@ require "socket"
4
4
  require "timeout"
5
5
 
6
6
  module Yamine
7
- # /etc/hosts sync for Safari + custom TLDs (.localhost resolves natively
8
- # in Chrome/Firefox/Edge; Safari uses the system resolver).
9
- # Same managed-block approach as portless hosts.ts.
7
+ # /etc/hosts sync for the client classes that cannot resolve the routes
8
+ # on their own: custom TLDs, and resolvers that read only the hosts file
9
+ # (a CGO-disabled Go binary is the common case — no system resolver, no
10
+ # RFC 6761 special-casing). Same managed-block approach as portless
11
+ # hosts.ts.
10
12
  module Hosts
11
13
  BEGIN_MARKER = "# --- yamine begin ---"
12
14
  END_MARKER = "# --- yamine end ---"
@@ -44,14 +46,13 @@ module Yamine
44
46
  # Bring the managed block to exactly these hostnames. Idempotent: when
45
47
  # the block already matches, this is a no-op returning true.
46
48
  #
47
- # The short-circuit is load-bearing, not an optimization. /etc/hosts is
49
+ # The sync short-circuit is load-bearing, not an optimization. /etc/hosts is
48
50
  # root-owned on a normal machine, so a rewrite fails without sudo — and
49
51
  # `setup` and every boot's workstation check run this. Without the
50
52
  # guard, a machine whose hosts file was already correct got a
51
53
  # "could not write /etc/hosts (try sudo yamine hosts sync)" warning on
52
54
  # every run, sending people to an elevated write for a file that
53
- # needed nothing. (Chrome/Firefox/Edge resolve *.localhost natively;
54
- # /etc/hosts only matters for Safari and custom TLDs.)
55
+ # needed nothing.
55
56
  def sync(hostnames, path = PATH)
56
57
  return true if synced?(hostnames, path)
57
58
 
@@ -85,26 +86,60 @@ module Yamine
85
86
  false
86
87
  end
87
88
 
88
- # Does the hostname resolve for the programs that will actually use
89
- # it — browsers, curl, the app's own HTTP clients? That question can
90
- # only be answered by the SYSTEM resolver (getaddrinfo), which reads
91
- # nsswitch (so /etc/hosts counts) and implements the RFC 6761
92
- # special-use TLDs.
89
+ # Loopback mappings that count as "this name reaches the proxy".
90
+ LOOPBACK = %w[127.0.0.1 ::1].freeze
91
+
92
+ # Does the hosts file map this hostname to loopback? That is the one
93
+ # resolution source every client shares — browsers and curl, the
94
+ # system resolver, and resolvers that read only the file (a
95
+ # CGO-disabled Go binary is the common case). The whole file is
96
+ # searched, not just the managed block, so a hand-added entry counts;
97
+ # only loopback mappings count, since the proxy serves on loopback
98
+ # and any other address points the name away from yamine.
99
+ def hosts_entry?(hostname, path = PATH)
100
+ target = hostname.to_s.downcase
101
+ File.foreach(path) do |line|
102
+ parts = line.split("#", 2).first.to_s.split
103
+ next unless parts.length > 1 && LOOPBACK.include?(parts.first.downcase)
104
+ return true if parts[1..].any? { |name| name.downcase == target }
105
+ end
106
+ false
107
+ rescue SystemCallError
108
+ false
109
+ end
110
+
111
+ # Where one hostname stands for the clients that will actually use
112
+ # it — a question with three answers no single resolver probe can
113
+ # produce:
114
+ #
115
+ # :ok — every client resolves it: mapped in the hosts file, or
116
+ # found by the system resolver without it (real DNS or an
117
+ # /etc/resolver entry for a custom TLD).
118
+ # :warn — browsers and curl handle it (the RFC 6761 .localhost
119
+ # special case), but file-only resolvers cannot see it.
120
+ # :fail — nothing resolves it.
93
121
  #
94
- # Ruby's Resolv is a pure-Ruby DNS client: it never sees /etc/hosts
95
- # and knows nothing about RFC 6761, so it reports "no address for
96
- # myapp.localhost" on a machine where .localhost resolves perfectly.
97
- # That false negative sent users to `sudo yamine hosts sync` — an
98
- # elevated write to /etc/hosts — to fix a problem they did not have.
99
- def resolves?(hostname)
122
+ # .localhost names are classified :warn on file absence alone. The
123
+ # system resolver answers any .localhost unconditionally, so probing
124
+ # it carries no information — and acting on that probe is how doctor
125
+ # and boot once reported every .localhost as fully resolved while a
126
+ # CGO-disabled Go binary could not resolve any of them.
127
+ def classification(hostname, path = PATH)
128
+ return :ok if hosts_entry?(hostname, path)
129
+ return :warn if hostname.to_s.downcase.end_with?(".localhost")
130
+
100
131
  Timeout.timeout(2) { Addrinfo.getaddrinfo(hostname, nil) }
101
- true
132
+ :ok
102
133
  rescue SocketError, SystemCallError, Timeout::Error
103
- false
134
+ :fail
104
135
  end
105
136
 
106
- def unresolved(hostnames)
107
- hostnames.reject { |h| resolves?(h) }
137
+ # Partition hostnames into { ok:, warn:, fail: } for the consumers
138
+ # that report one line per state (doctor, boot).
139
+ def resolution(hostnames, path = PATH)
140
+ groups = { ok: [], warn: [], fail: [] }
141
+ hostnames.each { |h| groups[classification(h, path)] << h }
142
+ groups
108
143
  end
109
144
  end
110
145
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Yamine
4
- VERSION = "0.13.0"
4
+ VERSION = "0.13.2"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: yamine
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.13.0
4
+ version: 0.13.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kaka Ruto