yamine 0.14.3 → 0.15.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8af42a840ce756689e6c37140a3a8cdf402c51aa71b9819b88df624d6a6ffaae
4
- data.tar.gz: 0a51ab3de0e5605ea8b5152720f76957530722a1b65740dfebc0ab29d14337f9
3
+ metadata.gz: cb9ab4ec4d7e32278f307f3ebb9366b2a23ef7ec5b65016e6888c240ee90d951
4
+ data.tar.gz: 13f8a0d5340f03de8132101f5eb996b533aaf9b60a96b21bfbdddc6839ae4641
5
5
  SHA512:
6
- metadata.gz: e7823296acbadf8e6cc07079ff3f2d3ad56809d2de4973e6ed821124888cd2ee2144e267a754e585454fbc5c808c23fb9984e8da9880de4fda3b2b58bfcfff65
7
- data.tar.gz: 2642187bbffd601efb5272f557577014704a4415b76c45eefbfa177d3e337b62074d5b60f36525c7a25b174684af7ccc5da31bbd8abaf59a9cdfa9fb864fae96
6
+ metadata.gz: 10b5b826f5cd2c007e181feaf9f671e0b654a80b13f922c3251d72912987ecd294fa3cdc1d17fa7e5f58dbed22aab8961598844b711c896301e4cb7b46a59399
7
+ data.tar.gz: cb4962d0c9db1711963bbee31b59f3ca062613562714dfeebed81cdff5c896caf0fb3cdc16296e5245db0885da8debf03b6a1ec9b527e98bb79c3b5e4e46ff08
data/CHANGELOG.md CHANGED
@@ -1,5 +1,34 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.15.1] — 2026-09-22
4
+
5
+ ### Fixed
6
+
7
+ - **Stale merged worktrees now lose their branch too.** A worktree whose
8
+ directory was already gone was pruned from git, but its branch was
9
+ left behind — so `remove` and `clean` reported success while the
10
+ merged branch lingered, unlike a normal teardown. Stale entries now
11
+ delete a merged branch the same way (`git branch -d` after the
12
+ prune), and an unmerged branch is kept with the reason said out loud
13
+ unless `--force`. Regression coverage pins all three: stale + merged
14
+ deletes, stale + unmerged keeps, and `clean` doing the same sweep.
15
+
16
+ ## [0.15.0] — 2026-09-15
17
+
18
+ ### Changed
19
+
20
+ - **App logs now live in `log/development.log` — `log/yamine-*.log` is gone.**
21
+ Every process used to append to a per-process `log/yamine-#{name}.log`,
22
+ hijacking the Rails request log away from `log/development.log` and
23
+ making `tail -f log/development.log` go silent under yamine. Now
24
+ every process appends to the same shared file — the one Rails people
25
+ already expect — with `tail -F` surviving rotation. `yamine log` and
26
+ `yamine log -F` tail that file; the `--wait` failure payload's log tail
27
+ and `died_detail` now point at `log/development.log` too. Old
28
+ `log/yamine-*.log` files are cleaned up automatically on the next
29
+ boot. Interleaving matches the foreman/without-yamine experience; a
30
+ per-process opt-in can be added later if needed.
31
+
3
32
  ## [0.14.3] — 2026-09-15
4
33
 
5
34
  ### Fixed
data/README.md CHANGED
@@ -229,7 +229,7 @@ yamine db list|create|drop # per-worktree databases
229
229
  yamine worktree list|add|remove|clean # worktree lifecycle
230
230
  yamine stop # stop this app's backend + routes
231
231
  yamine restart # touch tmp/restart.txt (managed apps reboot)
232
- yamine log [-f] [n] # tail (or follow) this app's backend log
232
+ yamine log [-F] [n] # tail (or follow) log/development.log
233
233
  yamine proxy start|stop # control the proxy
234
234
  yamine service install|status|uninstall # root-owned OS startup service
235
235
  yamine hosts sync|clean # manage /etc/hosts entries
@@ -276,9 +276,10 @@ old fire-and-forget path.
276
276
 
277
277
  ## Log rotation
278
278
 
279
- `proxy.log` and per-app backend logs rotate at 5MB
280
- (`YAMINE_LOG_MAX_BYTES`), keeping one generation. `doctor` warns
281
- when the state dir passes 100MB.
279
+ `proxy.log` rotates at 5MB (`YAMINE_LOG_MAX_BYTES`), keeping one
280
+ generation. `log/development.log` is rotated too — use
281
+ `tail -F log/development.log` (or `yamine log -F`) so rotation doesn't
282
+ lose the tail. `doctor` warns when the state dir passes 100MB.
282
283
 
283
284
  ## Supervision
284
285
 
@@ -39,7 +39,7 @@ yamine start --json # machine-readable wait result (--wait default)
39
39
  yamine # same as start
40
40
  yamine stop # stop this app's backend + routes
41
41
  yamine status # show service, processes, and URLs
42
- yamine log [-f] # tail the web process log
42
+ yamine log [-F] # tail log/development.log (every process)
43
43
  ```
44
44
 
45
45
  `$PORT` and `YAMINE_URL` are injected per process; HTTP processes get
@@ -268,7 +268,7 @@ module Yamine
268
268
  children << { name: item[:name], pid: app.pid }
269
269
  apps[item[:name]] = {
270
270
  item: item,
271
- log_path: File.join(Dir.pwd, "log", "yamine-#{item[:name]}.log"),
271
+ log_path: File.join(Dir.pwd, "log", "development.log"),
272
272
  app: app
273
273
  }
274
274
  end
@@ -330,7 +330,7 @@ module Yamine
330
330
  def tail_for(failure, apps)
331
331
  name = failure[:name]
332
332
  slot = apps[name]
333
- path = slot ? File.expand_path(File.join(Dir.pwd, "log", "yamine-#{name}.log")) : nil
333
+ path = slot ? File.expand_path(File.join(Dir.pwd, "log", "development.log")) : nil
334
334
  lines = path && File.file?(path) ? File.readlines(path).last(20).join : "(no log file)"
335
335
  { path: path, tail: lines }
336
336
  rescue SystemCallError
@@ -580,7 +580,7 @@ module Yamine
580
580
  if dead
581
581
  pid, name = dead
582
582
  $stderr.puts "\n[#{name}] exited (pid #{pid}) — stopping the whole tree."
583
- log = File.join(Dir.pwd, "log", "yamine-#{name}.log")
583
+ log = File.join(Dir.pwd, "log", "development.log")
584
584
  $stderr.puts " log: #{log}" if File.file?(log)
585
585
  reporter&.note("#{name} exited; cleaning up routes")
586
586
  cleanup_routes(ctx, hostnames)
@@ -249,12 +249,13 @@ module Yamine
249
249
  puts "Touched #{path} — managed app restarts on next request."
250
250
  end
251
251
 
252
- # Tail the app log (default 50 lines); --follow streams.
253
- def log(ctx, args)
252
+ # Tail the shared app log (default 50 lines); --follow streams.
253
+ # Every process writes log/development.log, so `yamine log` and
254
+ # `yamine log -F` tail the one file `tail -F` survives rotation.
255
+ def log(_ctx, args)
254
256
  follow = args.delete("--follow") || args.delete("-f")
255
257
  lines = (args.first || 50).to_i
256
- resolved = Resolver.resolve(Dir.pwd)
257
- path = File.expand_path(File.join(Dir.pwd, "log", "yamine-#{resolved.app}.log"))
258
+ path = File.expand_path(File.join(Dir.pwd, "log", "development.log"))
258
259
  unless File.file?(path)
259
260
  puts "No log at #{path} yet."
260
261
  return
@@ -210,7 +210,7 @@ module Yamine
210
210
  end
211
211
 
212
212
  unless entry.exists?
213
- abandon(ctx, repo, entry)
213
+ abandon(ctx, repo, entry, force: opts[:force])
214
214
  return
215
215
  end
216
216
 
@@ -251,6 +251,7 @@ module Yamine
251
251
 
252
252
  failures = 0
253
253
  changed = false
254
+ stale = []
254
255
  plan.each do |step|
255
256
  case step[0]
256
257
  when :drop_db
@@ -266,12 +267,14 @@ module Yamine
266
267
  failures += 1
267
268
  end
268
269
  when :prune
270
+ stale << step[1]
269
271
  changed = true
270
272
  when :keep
271
273
  puts " kept #{step[1]} — #{step[2]}"
272
274
  end
273
275
  end
274
276
  Worktrees.prune(repo) if repo
277
+ stale.each { |entry| delete_stale_branch(repo, entry) } if repo
275
278
  resync_hosts(ctx) if repo && changed
276
279
  ran = plan.count { |s| %i[drop_db teardown prune].include?(s[0]) }
277
280
  summary = "Cleaned #{ran} item(s)"
@@ -285,7 +288,7 @@ module Yamine
285
288
  case action
286
289
  when :drop_db then "drop database #{subject} (#{reason} is gone)"
287
290
  when :teardown then "remove worktree #{subject.path} (#{reason})"
288
- when :prune then "prune stale git entry #{subject}"
291
+ when :prune then "prune stale git entry #{subject.path}"
289
292
  when :keep then "keep #{subject} — #{reason}"
290
293
  end
291
294
  end
@@ -303,7 +306,7 @@ module Yamine
303
306
  next if entry.bare || entry.main?
304
307
 
305
308
  unless entry.exists?
306
- plan << Plan.new(:prune, entry.path)
309
+ plan << Plan.new(:prune, entry)
307
310
  next
308
311
  end
309
312
  if Worktrees.dirty?(entry.path, ignore: LOCAL_CONFIG_FILES)
@@ -370,8 +373,10 @@ module Yamine
370
373
  end
371
374
 
372
375
  # A worktree whose directory is already gone: nothing to stop or
373
- # remove, just its database claim and the stale git entry.
374
- def abandon(ctx, repo, entry)
376
+ # remove, just its database claim, the stale git entry, and — when
377
+ # the branch is merged — the branch itself, matching what a normal
378
+ # removal would have done.
379
+ def abandon(ctx, repo, entry, force: false)
375
380
  map = Database.load_map(ctx.store.dir)
376
381
  claim = claim_for(map, entry.path)
377
382
  if claim
@@ -379,6 +384,26 @@ module Yamine
379
384
  end
380
385
  Worktrees.prune(repo)
381
386
  puts " pruned #{entry.path} (directory was already gone)"
387
+ delete_stale_branch(repo, entry, force: force)
388
+ end
389
+
390
+ # Prune first so git will allow the delete; an unmerged branch is
391
+ # kept unless force, exactly as teardown does for a live worktree.
392
+ def delete_stale_branch(repo, entry, force: false)
393
+ return unless entry.branch && Worktrees.branch_exists?(repo, entry.branch)
394
+
395
+ merged = Worktrees.merged?(repo, entry.branch,
396
+ into: Worktrees.default_branch(repo))
397
+ if force || merged
398
+ flag = force ? "-D" : "-d"
399
+ _out, status = Open3.capture2("git", "branch", flag, entry.branch,
400
+ chdir: repo, err: File::NULL)
401
+ if status&.success?
402
+ puts " deleted branch #{entry.branch}"
403
+ return
404
+ end
405
+ end
406
+ puts " kept branch #{entry.branch} (git refuses to delete an unmerged branch)"
382
407
  end
383
408
 
384
409
  # The teardown shared by `remove` and `clean`. Returns true when
data/lib/yamine/cli.rb CHANGED
@@ -104,7 +104,7 @@ module Yamine
104
104
  yamine kamal <variant> Preview-deploy snippet for Kamal
105
105
  yamine stop Stop this app's backend + routes
106
106
  yamine restart Touch tmp/restart.txt
107
- yamine log [-f] [n] Tail (or follow) this app's backend log
107
+ yamine log [-F] [n] Tail (or follow) log/development.log
108
108
 
109
109
  Flags: --variant, --tld, --force, --app-port, --wait (default), --no-wait, --json, --branch
110
110
  Env: YAMINE_VARIANT/TLD/PORT/STATE_DIR/AGENT, YAMINE_BRANCH=1
@@ -189,13 +189,14 @@ module Yamine
189
189
  end
190
190
 
191
191
  # Why a process that died before answering died: the recognized
192
- # fatal line when there is one, else point at its log.
192
+ # fatal line when there is one, else point at the shared log
193
+ # (every process appends to log/development.log).
193
194
  def died_detail(name, slot)
194
195
  reason = fatal_line(slot[:log_path])
195
196
  if reason
196
- "process exited: #{reason} (log/yamine-#{name}.log)"
197
+ "process exited: #{reason} (log/development.log)"
197
198
  else
198
- "process exited before becoming healthy — see log/yamine-#{name}.log"
199
+ "process exited before becoming healthy — see log/development.log"
199
200
  end
200
201
  end
201
202
 
data/lib/yamine/runner.rb CHANGED
@@ -91,8 +91,10 @@ module Yamine
91
91
 
92
92
  # Run an arbitrary command with PORT + YAMINE_URL. Returns App.
93
93
  # register: false spawns without a route (background processes).
94
- # Child output goes to log/yamine-<name>.log so `--wait` failure
95
- # payloads can tail it (same file the managed path uses).
94
+ # Child output goes to log/development.log (every process appends
95
+ # to the same file) so `tail -f log/development.log` is the same
96
+ # view yamine's failure payloads use. Interleaving matches the
97
+ # foreman/without-yamine experience.
96
98
  #
97
99
  # extra_env is the config's env.clear/env.secret for this process —
98
100
  # yamine's own keys (PORT, YAMINE_URL, DATABASE_URL, the TLS vars)
@@ -221,6 +223,14 @@ module Yamine
221
223
 
222
224
  private
223
225
 
226
+ def cleanup_legacy_logs(dir)
227
+ Dir.glob(File.expand_path(File.join(dir, "log", "yamine-*.log*"))).each do |path|
228
+ FileUtils.rm_f(path)
229
+ rescue SystemCallError
230
+ nil
231
+ end
232
+ end
233
+
224
234
  def socket_command(dir, socket_path)
225
235
  socket = "unix://#{socket_path}"
226
236
  config_ru = File.join(dir, "config.ru")
@@ -288,12 +298,15 @@ module Yamine
288
298
  end
289
299
  end
290
300
 
291
- # Log path for a process — public so the --wait failure payload
292
- # can tail the right file for the phase that failed.
293
- def log_path(dir, name)
294
- path = File.expand_path(File.join(dir, "log", "yamine-#{name}.log"))
301
+ # Log path for any process — every process appends to
302
+ # log/development.log so `tail -F` is the one view for humans
303
+ # and for --wait failure tails. Keep the two-arg signature so
304
+ # existing call sites don't need to change.
305
+ def log_path(dir, _name = nil)
306
+ path = File.expand_path(File.join(dir, "log", "development.log"))
295
307
  FileUtils.mkdir_p(File.dirname(path))
296
308
  Log.rotate(path)
309
+ cleanup_legacy_logs(dir)
297
310
  path
298
311
  end
299
312
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Yamine
4
- VERSION = "0.14.3"
4
+ VERSION = "0.15.1"
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.14.3
4
+ version: 0.15.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kaka Ruto