procd 1.0.2__tar.gz → 1.0.3__tar.gz
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.
- {procd-1.0.2 → procd-1.0.3}/PKG-INFO +1 -1
- {procd-1.0.2 → procd-1.0.3}/pyproject.toml +1 -1
- {procd-1.0.2 → procd-1.0.3}/src/procd/cli.py +90 -60
- {procd-1.0.2 → procd-1.0.3}/LICENSE +0 -0
- {procd-1.0.2 → procd-1.0.3}/README_PYPI.md +0 -0
- {procd-1.0.2 → procd-1.0.3}/src/procd/__init__.py +0 -0
- {procd-1.0.2 → procd-1.0.3}/src/procd/_expand.py +0 -0
- {procd-1.0.2 → procd-1.0.3}/src/procd/_logger.py +0 -0
- {procd-1.0.2 → procd-1.0.3}/src/procd/builtin.py +0 -0
- {procd-1.0.2 → procd-1.0.3}/src/procd/config.py +0 -0
- {procd-1.0.2 → procd-1.0.3}/src/procd/download.py +0 -0
- {procd-1.0.2 → procd-1.0.3}/src/procd/process.py +0 -0
- {procd-1.0.2 → procd-1.0.3}/src/procd/supervisor.py +0 -0
|
@@ -180,6 +180,10 @@ def cmd_up(args: argparse.Namespace) -> None:
|
|
|
180
180
|
conf = _load(args)
|
|
181
181
|
targets = _resolve_services(args.names, conf) if args.names else _default_services(conf)
|
|
182
182
|
|
|
183
|
+
if args.follow:
|
|
184
|
+
_up_follow(args, conf, targets)
|
|
185
|
+
return
|
|
186
|
+
|
|
183
187
|
if args.detach:
|
|
184
188
|
# detach: 従来通り _logger サブプロセスで JSONL に書き続ける
|
|
185
189
|
procs: list[tuple[str, subprocess.Popen]] = []
|
|
@@ -277,6 +281,78 @@ def cmd_up(args: argparse.Namespace) -> None:
|
|
|
277
281
|
shutdown()
|
|
278
282
|
|
|
279
283
|
|
|
284
|
+
def _up_follow(args: argparse.Namespace, conf: cfg.Config, targets: list[str]) -> None:
|
|
285
|
+
"""detach 起動してから jsonl ログをフォローし、終了時にそのサービスも停止する。"""
|
|
286
|
+
jsonl = process.jsonl_path()
|
|
287
|
+
log_start_pos = jsonl.stat().st_size if jsonl.exists() else 0
|
|
288
|
+
|
|
289
|
+
started_names: list[str] = []
|
|
290
|
+
for i, name in enumerate(targets):
|
|
291
|
+
if process.is_running(name):
|
|
292
|
+
print(f"[{name}] already running (pid {process.read_pid(name)})")
|
|
293
|
+
continue
|
|
294
|
+
svc = conf.services[name]
|
|
295
|
+
color = process.COLORS[i % len(process.COLORS)]
|
|
296
|
+
print(f"{color}[{name}]{process.RESET} Starting...")
|
|
297
|
+
process.start(name, svc, detach=True, project_dir=conf.project_dir, verbose=args.verbose)
|
|
298
|
+
started_names.append(name)
|
|
299
|
+
for name in started_names:
|
|
300
|
+
svc = conf.services[name]
|
|
301
|
+
i = list(conf.services.keys()).index(name)
|
|
302
|
+
color = process.COLORS[i % len(process.COLORS)]
|
|
303
|
+
result = process.wait_ready(name, svc, project_dir=conf.project_dir)
|
|
304
|
+
if result is None:
|
|
305
|
+
print(f"{color}[{name}]{process.RESET} Process exited immediately.", file=sys.stderr)
|
|
306
|
+
elif result:
|
|
307
|
+
print(f"{color}[{name}]{process.RESET} Ready (pid {process.read_pid(name)})")
|
|
308
|
+
else:
|
|
309
|
+
print(f"{color}[{name}]{process.RESET} Timed out waiting for ready", file=sys.stderr)
|
|
310
|
+
|
|
311
|
+
def _shutdown_started() -> None:
|
|
312
|
+
if not started_names:
|
|
313
|
+
return
|
|
314
|
+
print("\nStopping services...")
|
|
315
|
+
for name in reversed(started_names):
|
|
316
|
+
if process.stop(name):
|
|
317
|
+
print(f" Stopped {name}")
|
|
318
|
+
|
|
319
|
+
if not jsonl.exists():
|
|
320
|
+
if not started_names:
|
|
321
|
+
_err_console.print("[yellow]no logs found[/yellow]")
|
|
322
|
+
return
|
|
323
|
+
deadline = time.time() + 5
|
|
324
|
+
while not jsonl.exists() and time.time() < deadline:
|
|
325
|
+
time.sleep(0.1)
|
|
326
|
+
if not jsonl.exists():
|
|
327
|
+
process.LOG_DIR.mkdir(parents=True, exist_ok=True)
|
|
328
|
+
jsonl.touch()
|
|
329
|
+
|
|
330
|
+
def _fmt(entry: dict) -> str:
|
|
331
|
+
s = entry.get("s", "?")
|
|
332
|
+
color = process.COLORS[list(conf.services).index(s) % len(process.COLORS)] if s in conf.services else ""
|
|
333
|
+
ts = f" {entry.get('t','')[:23]} " if args.timestamp else ""
|
|
334
|
+
return f"{color}[{s}]{process.RESET}{ts} {entry.get('m','')}"
|
|
335
|
+
|
|
336
|
+
try:
|
|
337
|
+
with open(jsonl, encoding="utf-8") as f:
|
|
338
|
+
f.seek(log_start_pos)
|
|
339
|
+
while True:
|
|
340
|
+
line = f.readline()
|
|
341
|
+
if line:
|
|
342
|
+
try:
|
|
343
|
+
entry = json.loads(line)
|
|
344
|
+
except json.JSONDecodeError:
|
|
345
|
+
continue
|
|
346
|
+
print(_fmt(entry))
|
|
347
|
+
sys.stdout.flush()
|
|
348
|
+
else:
|
|
349
|
+
time.sleep(0.05)
|
|
350
|
+
except KeyboardInterrupt:
|
|
351
|
+
pass
|
|
352
|
+
finally:
|
|
353
|
+
_shutdown_started()
|
|
354
|
+
|
|
355
|
+
|
|
280
356
|
def cmd_down(args: argparse.Namespace) -> None:
|
|
281
357
|
conf = _load(args)
|
|
282
358
|
targets = _resolve_services(args.names, conf) if args.names else _default_services(conf)
|
|
@@ -337,59 +413,13 @@ def cmd_logs(args: argparse.Namespace) -> None:
|
|
|
337
413
|
ts = f" {entry.get('t','')[:23]} " if args.timestamp else ""
|
|
338
414
|
return f"{color}[{s}]{process.RESET}{ts} {entry.get('m','')}"
|
|
339
415
|
|
|
340
|
-
# --up: サービスを起動してからログをフォローし、終了時にサービスも停止する
|
|
341
|
-
started_names: list[str] = []
|
|
342
|
-
# 起動前のログ末尾位置を記録して古いログを表示しない
|
|
343
|
-
log_start_pos: int = jsonl.stat().st_size if jsonl.exists() else 0
|
|
344
|
-
|
|
345
|
-
if getattr(args, "up", None) is not None:
|
|
346
|
-
targets = _resolve_services(args.up, conf) if args.up else _default_services(conf)
|
|
347
|
-
for i, name in enumerate(targets):
|
|
348
|
-
if process.is_running(name):
|
|
349
|
-
print(f"[{name}] already running (pid {process.read_pid(name)})")
|
|
350
|
-
continue
|
|
351
|
-
svc = conf.services[name]
|
|
352
|
-
color = process.COLORS[i % len(process.COLORS)]
|
|
353
|
-
print(f"{color}[{name}]{process.RESET} Starting...")
|
|
354
|
-
process.start(name, svc, detach=True, project_dir=conf.project_dir, verbose=args.verbose)
|
|
355
|
-
started_names.append(name)
|
|
356
|
-
for name in started_names:
|
|
357
|
-
svc = conf.services[name]
|
|
358
|
-
i = list(conf.services.keys()).index(name)
|
|
359
|
-
color = process.COLORS[i % len(process.COLORS)]
|
|
360
|
-
result = process.wait_ready(name, svc, project_dir=conf.project_dir)
|
|
361
|
-
if result is None:
|
|
362
|
-
print(f"{color}[{name}]{process.RESET} Process exited immediately.", file=sys.stderr)
|
|
363
|
-
elif result:
|
|
364
|
-
print(f"{color}[{name}]{process.RESET} Ready (pid {process.read_pid(name)})")
|
|
365
|
-
else:
|
|
366
|
-
print(f"{color}[{name}]{process.RESET} Timed out waiting for ready", file=sys.stderr)
|
|
367
|
-
args.follow = True
|
|
368
|
-
|
|
369
|
-
def _shutdown_started() -> None:
|
|
370
|
-
if not started_names:
|
|
371
|
-
return
|
|
372
|
-
print("\nStopping services...")
|
|
373
|
-
for name in reversed(started_names):
|
|
374
|
-
if process.stop(name):
|
|
375
|
-
print(f" Stopped {name}")
|
|
376
|
-
|
|
377
416
|
if args.follow:
|
|
378
|
-
# --up でサービスを起動した場合はログファイルが存在するまで少し待つ
|
|
379
417
|
if not jsonl.exists():
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
return
|
|
383
|
-
deadline = time.time() + 5
|
|
384
|
-
while not jsonl.exists() and time.time() < deadline:
|
|
385
|
-
time.sleep(0.1)
|
|
386
|
-
if not jsonl.exists():
|
|
387
|
-
process.LOG_DIR.mkdir(parents=True, exist_ok=True)
|
|
388
|
-
jsonl.touch()
|
|
418
|
+
_err_console.print("[yellow]no logs found — run 'procd up -d' first[/yellow]")
|
|
419
|
+
return
|
|
389
420
|
try:
|
|
390
421
|
with open(jsonl, encoding="utf-8") as f:
|
|
391
|
-
#
|
|
392
|
-
f.seek(log_start_pos if started_names else 0, 0 if started_names else 2)
|
|
422
|
+
f.seek(0, 2) # 末尾からフォロー
|
|
393
423
|
while True:
|
|
394
424
|
line = f.readline()
|
|
395
425
|
if line:
|
|
@@ -401,8 +431,6 @@ def cmd_logs(args: argparse.Namespace) -> None:
|
|
|
401
431
|
time.sleep(0.05)
|
|
402
432
|
except KeyboardInterrupt:
|
|
403
433
|
pass
|
|
404
|
-
finally:
|
|
405
|
-
_shutdown_started()
|
|
406
434
|
else:
|
|
407
435
|
if not jsonl.exists():
|
|
408
436
|
return
|
|
@@ -589,10 +617,10 @@ def cmd_link(args: argparse.Namespace) -> None:
|
|
|
589
617
|
conf = _load(args)
|
|
590
618
|
project_dir = conf.project_dir
|
|
591
619
|
|
|
592
|
-
if args.
|
|
593
|
-
names = " ".join(args.
|
|
594
|
-
procd_sub = f"
|
|
595
|
-
shortcut_name = f"procd
|
|
620
|
+
if args.up_follow is not None:
|
|
621
|
+
names = " ".join(args.up_follow)
|
|
622
|
+
procd_sub = f"up --follow{' ' + names if names else ''}"
|
|
623
|
+
shortcut_name = f"procd up --follow{' ' + names if names else ''}"
|
|
596
624
|
elif args.up is not None:
|
|
597
625
|
names = " ".join(args.up)
|
|
598
626
|
procd_sub = f"up{' ' + names if names else ''}"
|
|
@@ -632,7 +660,11 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
632
660
|
# up
|
|
633
661
|
p = sub.add_parser("up", help="サービスを起動する")
|
|
634
662
|
p.add_argument("names", nargs="*", metavar="NAME")
|
|
635
|
-
p.
|
|
663
|
+
group = p.add_mutually_exclusive_group()
|
|
664
|
+
group.add_argument("-d", "--detach", action="store_true", help="バックグラウンド起動")
|
|
665
|
+
group.add_argument("-f", "--follow", action="store_true",
|
|
666
|
+
help="バックグラウンド起動してログをフォローし、終了時にサービスも停止する")
|
|
667
|
+
p.add_argument("-t", "--timestamp", action="store_true", help="--follow 時にタイムスタンプを表示する")
|
|
636
668
|
p.set_defaults(func=cmd_up)
|
|
637
669
|
|
|
638
670
|
# down
|
|
@@ -655,8 +687,6 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
655
687
|
p.add_argument("-f", "--follow", action="store_true", help="tail -f スタイルで追跡")
|
|
656
688
|
p.add_argument("-n", "--lines", type=int, default=200, metavar="N", help="表示する行数(デフォルト: 200)")
|
|
657
689
|
p.add_argument("-t", "--timestamp", action="store_true", help="タイムスタンプを表示する")
|
|
658
|
-
p.add_argument("--up", nargs="*", metavar="NAME",
|
|
659
|
-
help="サービスを起動してからログをフォローし、終了時にサービスも停止する")
|
|
660
690
|
p.set_defaults(func=cmd_logs)
|
|
661
691
|
|
|
662
692
|
# run
|
|
@@ -684,8 +714,8 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
684
714
|
group = p.add_mutually_exclusive_group(required=True)
|
|
685
715
|
group.add_argument("--up", nargs="*", metavar="NAME",
|
|
686
716
|
help="procd up のショートカット(NAME 省略で default グループ)")
|
|
687
|
-
group.add_argument("--
|
|
688
|
-
help="procd
|
|
717
|
+
group.add_argument("--up-follow", nargs="*", metavar="NAME", dest="up_follow",
|
|
718
|
+
help="procd up --follow のショートカット(NAME 省略で default グループ)")
|
|
689
719
|
group.add_argument("--run", metavar="NAME",
|
|
690
720
|
help="procd run <NAME> のショートカット")
|
|
691
721
|
p.add_argument("--icon", metavar="FILE", help="アイコンファイル (.ico / .icns)")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|