yamine 0.13.0 → 0.13.1
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 +19 -0
- data/lib/yamine/cli/boot.rb +1 -7
- data/lib/yamine/cli/system.rb +47 -29
- data/lib/yamine/database.rb +89 -0
- 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: 43ea000326072db8f0173360b3ef58a6cce132e20f372b65f0e777bafbb97e9d
|
|
4
|
+
data.tar.gz: 3e878d2cae8afffd1dca255a006a5863e12dbd4471a4b6214656dda5bc97d37f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 272dae53dfac6b0521c09c00ae096cdc10620dcabf8064a286a0fe25813a61738b3fefc4bb34cf8fdfdd8dd1467a0064ec4e8ceb6ec7b262a8ed638b66101bda
|
|
7
|
+
data.tar.gz: e16540d1830850c50a54451ec5f0bfb0ad87fd770b111a0b4276aad9a6916dadeeed4c106b61392e5fc5794e6276f003f718a5e53135885f36cc142944d1a12f
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.13.1] — 2026-09-12
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- **`worktree clean` no longer reports "could not drop — remove manually"
|
|
8
|
+
for a database that never existed.** `dropdb` fails on a missing
|
|
9
|
+
database, and the old code collapsed that into a generic failure. Now
|
|
10
|
+
`Database.drop` returns an honest three-state outcome — `:dropped`,
|
|
11
|
+
`:missing` (nothing to drop, not an error), `:failed` (server
|
|
12
|
+
unreachable or drop refused) — by splitting "server reachable" from
|
|
13
|
+
"database present". Clean prints each outcome (`dropped N`,
|
|
14
|
+
`N did not exist — nothing to drop`) plus a summary line, exits 1 only
|
|
15
|
+
when something really failed, and **keeps the claim of a failed drop**
|
|
16
|
+
so the next clean can retry it (the old code deleted all claims
|
|
17
|
+
regardless, losing track of databases it failed to drop).
|
|
18
|
+
- `db drop` and `db create` resolve the DATABASE_URL template the way
|
|
19
|
+
boot does (ENV, then config env.clear) instead of ENV-only, so they
|
|
20
|
+
work from a shell without the variable exported.
|
|
21
|
+
|
|
3
22
|
## [0.13.0] — 2026-09-11
|
|
4
23
|
|
|
5
24
|
### Changed
|
data/lib/yamine/cli/boot.rb
CHANGED
|
@@ -546,13 +546,7 @@ module Yamine
|
|
|
546
546
|
end
|
|
547
547
|
|
|
548
548
|
def database_template_from_config(resolved)
|
|
549
|
-
|
|
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
|
data/lib/yamine/cli/system.rb
CHANGED
|
@@ -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 =
|
|
896
|
+
template = drop_template
|
|
897
897
|
unless template && !template.strip.empty?
|
|
898
|
-
$stderr.puts "Error:
|
|
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
|
-
|
|
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
|
-
|
|
925
|
-
|
|
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
|
-
|
|
931
|
-
|
|
932
|
-
|
|
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
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
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
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
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
|
-
|
|
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
|
data/lib/yamine/database.rb
CHANGED
|
@@ -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/version.rb
CHANGED