"""Génération des fichiers de lancement : ecosystem PM2 (.cjs) et plists launchd.""" import json import plistlib import shlex def _mem(v): if not v: return None if isinstance(v, str): return v return "%dM" % (int(v) // (1024 * 1024)) def _home(path, ctx): """PM2/launchd n'expandent pas « ~ » : chemin absolu obligatoire.""" if isinstance(path, str) and path.startswith("~"): return ctx["HOME"] + path[1:] return path def ecosystem(m, ctx): """Retourne le contenu JS d'un ecosystem.config.cjs pour les processus PM2 + tunnel ngrok.""" from .manifest import render apps = [] for p in m["processes"]: if p.get("manager", "pm2") != "pm2": continue a = { "name": p["name"], "script": _home(render(p["script"], ctx), ctx), "cwd": _home(render(p.get("cwd") or m["dir"], ctx), ctx), "exec_mode": "fork", "instances": 1, "autorestart": p.get("autorestart", True), "merge_logs": True, "time": True, "env": render(p.get("env") or {}, ctx), } if p.get("args"): args = p["args"] a["args"] = " ".join(shlex.quote(str(x)) for x in render(args, ctx)) if isinstance(args, list) else render(args, ctx) interp = p.get("interpreter") if interp and interp not in ("none", "N/A"): a["interpreter"] = render(interp, ctx) elif interp in ("none",) or (p["script"].endswith((".sh", ".mjs", ".js", ".py", ".cjs")) is False): a["interpreter"] = "none" if p.get("cron_restart"): a["cron_restart"] = p["cron_restart"] if p.get("max_memory_restart"): a["max_memory_restart"] = _mem(p["max_memory_restart"]) if p.get("restart_delay"): a["restart_delay"] = p["restart_delay"] if p.get("node_args"): a["node_args"] = p["node_args"] apps.append(a) if m.get("ngrok"): n = m["ngrok"] args = ["http", "--url=%s" % n["url"], str(n["port"]), "--log=stdout"] + list(n.get("extra_args") or []) apps.append({ "name": n["name"], "script": n.get("binary") or "/opt/homebrew/bin/ngrok", "args": " ".join(args), "interpreter": "none", "cwd": _home(render(m["dir"], ctx), ctx), "exec_mode": "fork", "autorestart": True, "restart_delay": 5000, "merge_logs": True, "time": True, }) head = "// généré par maclustr-dispatch — ne pas éditer à la main (app: %s, nœud: %s)\n" % (m["app"], ctx.get("NODE")) return head + "module.exports = { apps: %s };\n" % json.dumps(apps, indent=2, ensure_ascii=False) def plist(item, ctx): from .manifest import render d = { "Label": item["label"], "ProgramArguments": render(item["program_arguments"], ctx), "RunAtLoad": item.get("run_at_load", True), "KeepAlive": item.get("keep_alive", True), "StandardOutPath": render(item.get("stdout") or "{{HOME}}/Library/Logs/%s.log" % item["label"], ctx), "StandardErrorPath": render(item.get("stderr") or "{{HOME}}/Library/Logs/%s.err.log" % item["label"], ctx), } if item.get("working_directory"): d["WorkingDirectory"] = _home(render(item["working_directory"], ctx), ctx) env = dict(item.get("env") or {}) env.setdefault("HOME", ctx["HOME"]) env.setdefault("PATH", "/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin") d["EnvironmentVariables"] = render(env, ctx) if item.get("start_interval"): d["StartInterval"] = int(item["start_interval"]) if item.get("throttle_interval"): d["ThrottleInterval"] = int(item["throttle_interval"]) return plistlib.dumps(d).decode()