elit 3.6.7 → 3.6.8

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.
Files changed (42) hide show
  1. package/Cargo.lock +1 -1
  2. package/Cargo.toml +1 -1
  3. package/README.md +20 -1
  4. package/dist/cli.cjs +2496 -303
  5. package/dist/cli.mjs +2501 -308
  6. package/dist/config.d.ts +6 -6
  7. package/dist/{contracts-BeW9k0yZ.d.ts → contracts-_0p1-15U.d.ts} +1 -1
  8. package/dist/coverage.d.ts +1 -1
  9. package/dist/dev-build.d.ts +3 -3
  10. package/dist/http.cjs +160 -41
  11. package/dist/http.d.ts +5 -11
  12. package/dist/http.js +160 -41
  13. package/dist/http.mjs +160 -41
  14. package/dist/https.cjs +194 -46
  15. package/dist/https.d.ts +3 -6
  16. package/dist/https.js +194 -46
  17. package/dist/https.mjs +194 -46
  18. package/dist/pm-node-shared-listener-bootstrap.cjs +75 -0
  19. package/dist/pm.cjs +2101 -134
  20. package/dist/pm.d.ts +83 -8
  21. package/dist/pm.js +2095 -158
  22. package/dist/pm.mjs +2091 -139
  23. package/dist/preview-build.d.ts +3 -3
  24. package/dist/server.cjs +417 -168
  25. package/dist/server.d.ts +4 -4
  26. package/dist/server.js +453 -179
  27. package/dist/server.mjs +417 -168
  28. package/dist/smtp-server.js +37 -12
  29. package/dist/test-reporter.d.ts +1 -1
  30. package/dist/test-runtime.d.ts +1 -1
  31. package/dist/{types-tJn88E1N.d.ts → types-BayMVo_k.d.ts} +39 -3
  32. package/dist/{types-CIhpN1-K.d.ts → types-C70T-42Z.d.ts} +1 -1
  33. package/dist/{types-DAisuVr5.d.ts → types-DPOgoGs-.d.ts} +7 -1
  34. package/dist/{state-DvEkDehk.d.ts → types-fiLday0L.d.ts} +96 -92
  35. package/dist/types.d.ts +4 -0
  36. package/dist/{websocket-XfyK23zD.d.ts → websocket-BLBEAnhp.d.ts} +1 -1
  37. package/dist/ws.d.ts +3 -3
  38. package/dist/wss.cjs +194 -46
  39. package/dist/wss.d.ts +3 -3
  40. package/dist/wss.js +194 -46
  41. package/dist/wss.mjs +194 -46
  42. package/package.json +11 -6
package/dist/pm.js CHANGED
@@ -1677,7 +1677,7 @@ is not a problem with esbuild. You need to fix your environment instead.
1677
1677
  let latestResultPromise;
1678
1678
  let provideLatestResult;
1679
1679
  if (isContext)
1680
- requestCallbacks["on-end"] = (id, request2) => new Promise((resolve6) => {
1680
+ requestCallbacks["on-end"] = (id, request2) => new Promise((resolve7) => {
1681
1681
  buildResponseToResult(request2, (err, result, onEndErrors, onEndWarnings) => {
1682
1682
  const response = {
1683
1683
  errors: onEndErrors,
@@ -1687,7 +1687,7 @@ is not a problem with esbuild. You need to fix your environment instead.
1687
1687
  latestResultPromise = void 0;
1688
1688
  provideLatestResult = void 0;
1689
1689
  sendResponse(id, response);
1690
- resolve6();
1690
+ resolve7();
1691
1691
  });
1692
1692
  });
1693
1693
  sendRequest(refs, request, (error, response) => {
@@ -1704,10 +1704,10 @@ is not a problem with esbuild. You need to fix your environment instead.
1704
1704
  let didDispose = false;
1705
1705
  const result = {
1706
1706
  rebuild: () => {
1707
- if (!latestResultPromise) latestResultPromise = new Promise((resolve6, reject) => {
1707
+ if (!latestResultPromise) latestResultPromise = new Promise((resolve7, reject) => {
1708
1708
  let settlePromise;
1709
1709
  provideLatestResult = (err, result2) => {
1710
- if (!settlePromise) settlePromise = () => err ? reject(err) : resolve6(result2);
1710
+ if (!settlePromise) settlePromise = () => err ? reject(err) : resolve7(result2);
1711
1711
  };
1712
1712
  const triggerAnotherBuild = () => {
1713
1713
  const request2 = {
@@ -1728,7 +1728,7 @@ is not a problem with esbuild. You need to fix your environment instead.
1728
1728
  });
1729
1729
  return latestResultPromise;
1730
1730
  },
1731
- watch: (options2 = {}) => new Promise((resolve6, reject) => {
1731
+ watch: (options2 = {}) => new Promise((resolve7, reject) => {
1732
1732
  if (!streamIn.hasFS) throw new Error(`Cannot use the "watch" API in this environment`);
1733
1733
  const keys = {};
1734
1734
  const delay2 = getFlag(options2, keys, "delay", mustBeInteger);
@@ -1740,10 +1740,10 @@ is not a problem with esbuild. You need to fix your environment instead.
1740
1740
  if (delay2) request2.delay = delay2;
1741
1741
  sendRequest(refs, request2, (error2) => {
1742
1742
  if (error2) reject(new Error(error2));
1743
- else resolve6(void 0);
1743
+ else resolve7(void 0);
1744
1744
  });
1745
1745
  }),
1746
- serve: (options2 = {}) => new Promise((resolve6, reject) => {
1746
+ serve: (options2 = {}) => new Promise((resolve7, reject) => {
1747
1747
  if (!streamIn.hasFS) throw new Error(`Cannot use the "serve" API in this environment`);
1748
1748
  const keys = {};
1749
1749
  const port = getFlag(options2, keys, "port", mustBeValidPortNumber);
@@ -1781,28 +1781,28 @@ is not a problem with esbuild. You need to fix your environment instead.
1781
1781
  sendResponse(id, {});
1782
1782
  };
1783
1783
  }
1784
- resolve6(response2);
1784
+ resolve7(response2);
1785
1785
  });
1786
1786
  }),
1787
- cancel: () => new Promise((resolve6) => {
1788
- if (didDispose) return resolve6();
1787
+ cancel: () => new Promise((resolve7) => {
1788
+ if (didDispose) return resolve7();
1789
1789
  const request2 = {
1790
1790
  command: "cancel",
1791
1791
  key: buildKey
1792
1792
  };
1793
1793
  sendRequest(refs, request2, () => {
1794
- resolve6();
1794
+ resolve7();
1795
1795
  });
1796
1796
  }),
1797
- dispose: () => new Promise((resolve6) => {
1798
- if (didDispose) return resolve6();
1797
+ dispose: () => new Promise((resolve7) => {
1798
+ if (didDispose) return resolve7();
1799
1799
  didDispose = true;
1800
1800
  const request2 = {
1801
1801
  command: "dispose",
1802
1802
  key: buildKey
1803
1803
  };
1804
1804
  sendRequest(refs, request2, () => {
1805
- resolve6();
1805
+ resolve7();
1806
1806
  scheduleOnDisposeCallbacks();
1807
1807
  refs.unref();
1808
1808
  });
@@ -1841,7 +1841,7 @@ is not a problem with esbuild. You need to fix your environment instead.
1841
1841
  onLoad: []
1842
1842
  };
1843
1843
  i++;
1844
- let resolve6 = (path3, options = {}) => {
1844
+ let resolve7 = (path3, options = {}) => {
1845
1845
  if (!isSetupDone) throw new Error('Cannot call "resolve" before plugin setup has completed');
1846
1846
  if (typeof path3 !== "string") throw new Error(`The path to resolve must be a string`);
1847
1847
  let keys2 = /* @__PURE__ */ Object.create(null);
@@ -1885,7 +1885,7 @@ is not a problem with esbuild. You need to fix your environment instead.
1885
1885
  };
1886
1886
  let promise = setup({
1887
1887
  initialOptions,
1888
- resolve: resolve6,
1888
+ resolve: resolve7,
1889
1889
  onStart(callback) {
1890
1890
  let registeredText = `This error came from the "onStart" callback registered here:`;
1891
1891
  let registeredNote = extractCallerV8(new Error(registeredText), streamIn, "onStart");
@@ -2772,46 +2772,46 @@ More information: The file containing the code for esbuild's JavaScript API (${_
2772
2772
  }
2773
2773
  };
2774
2774
  longLivedService = {
2775
- build: (options) => new Promise((resolve6, reject) => {
2775
+ build: (options) => new Promise((resolve7, reject) => {
2776
2776
  service.buildOrContext({
2777
2777
  callName: "build",
2778
2778
  refs,
2779
2779
  options,
2780
2780
  isTTY: isTTY(),
2781
2781
  defaultWD,
2782
- callback: (err, res) => err ? reject(err) : resolve6(res)
2782
+ callback: (err, res) => err ? reject(err) : resolve7(res)
2783
2783
  });
2784
2784
  }),
2785
- context: (options) => new Promise((resolve6, reject) => service.buildOrContext({
2785
+ context: (options) => new Promise((resolve7, reject) => service.buildOrContext({
2786
2786
  callName: "context",
2787
2787
  refs,
2788
2788
  options,
2789
2789
  isTTY: isTTY(),
2790
2790
  defaultWD,
2791
- callback: (err, res) => err ? reject(err) : resolve6(res)
2791
+ callback: (err, res) => err ? reject(err) : resolve7(res)
2792
2792
  })),
2793
- transform: (input, options) => new Promise((resolve6, reject) => service.transform({
2793
+ transform: (input, options) => new Promise((resolve7, reject) => service.transform({
2794
2794
  callName: "transform",
2795
2795
  refs,
2796
2796
  input,
2797
2797
  options: options || {},
2798
2798
  isTTY: isTTY(),
2799
2799
  fs: fsAsync,
2800
- callback: (err, res) => err ? reject(err) : resolve6(res)
2800
+ callback: (err, res) => err ? reject(err) : resolve7(res)
2801
2801
  })),
2802
- formatMessages: (messages, options) => new Promise((resolve6, reject) => service.formatMessages({
2802
+ formatMessages: (messages, options) => new Promise((resolve7, reject) => service.formatMessages({
2803
2803
  callName: "formatMessages",
2804
2804
  refs,
2805
2805
  messages,
2806
2806
  options,
2807
- callback: (err, res) => err ? reject(err) : resolve6(res)
2807
+ callback: (err, res) => err ? reject(err) : resolve7(res)
2808
2808
  })),
2809
- analyzeMetafile: (metafile, options) => new Promise((resolve6, reject) => service.analyzeMetafile({
2809
+ analyzeMetafile: (metafile, options) => new Promise((resolve7, reject) => service.analyzeMetafile({
2810
2810
  callName: "analyzeMetafile",
2811
2811
  refs,
2812
2812
  metafile: typeof metafile === "string" ? metafile : JSON.stringify(metafile),
2813
2813
  options,
2814
- callback: (err, res) => err ? reject(err) : resolve6(res)
2814
+ callback: (err, res) => err ? reject(err) : resolve7(res)
2815
2815
  }))
2816
2816
  };
2817
2817
  return longLivedService;
@@ -2889,13 +2889,13 @@ error: ${text}`);
2889
2889
  worker.postMessage(msg);
2890
2890
  let status = Atomics.wait(sharedBufferView, 0, 0);
2891
2891
  if (status !== "ok" && status !== "not-equal") throw new Error("Internal error: Atomics.wait() failed: " + status);
2892
- let { message: { id: id2, resolve: resolve6, reject, properties } } = worker_threads2.receiveMessageOnPort(mainPort);
2892
+ let { message: { id: id2, resolve: resolve7, reject, properties } } = worker_threads2.receiveMessageOnPort(mainPort);
2893
2893
  if (id !== id2) throw new Error(`Internal error: Expected id ${id} but got id ${id2}`);
2894
2894
  if (reject) {
2895
2895
  applyProperties(reject, properties);
2896
2896
  throw reject;
2897
2897
  }
2898
- return resolve6;
2898
+ return resolve7;
2899
2899
  };
2900
2900
  worker.unref();
2901
2901
  return {
@@ -2986,6 +2986,7 @@ error: ${text}`);
2986
2986
  var DEFAULT_MAX_RESTARTS = 10;
2987
2987
  var DEFAULT_WATCH_DEBOUNCE = 250;
2988
2988
  var DEFAULT_MIN_UPTIME = 0;
2989
+ var DEFAULT_PM_LISTEN_TIMEOUT = 3e3;
2989
2990
  var DEFAULT_HEALTHCHECK_GRACE_PERIOD = 5e3;
2990
2991
  var DEFAULT_HEALTHCHECK_INTERVAL = 1e4;
2991
2992
  var DEFAULT_HEALTHCHECK_TIMEOUT = 3e3;
@@ -2993,6 +2994,11 @@ error: ${text}`);
2993
2994
  var DEFAULT_LOG_LINES = 40;
2994
2995
  var DEFAULT_PM_STOP_POLL_MS = 100;
2995
2996
  var DEFAULT_PM_STOP_GRACE_PERIOD_MS = 5e3;
2997
+ var DEFAULT_PM_KILL_TIMEOUT = DEFAULT_PM_STOP_GRACE_PERIOD_MS;
2998
+ var DEFAULT_PM_EXP_BACKOFF_MAX_DELAY = 15e3;
2999
+ var DEFAULT_PM_MEMORY_CHECK_INTERVAL = 500;
3000
+ var DEFAULT_PM_RESTART_WINDOW = 0;
3001
+ var DEFAULT_PM_PROXY_STRATEGY = "proxy";
2996
3002
  var PM_WAPK_ONLINE_STDIN_SHUTDOWN_ENV = "ELIT_PM_WAPK_ONLINE_STDIN_SHUTDOWN";
2997
3003
  var PM_WAPK_ONLINE_SHUTDOWN_COMMAND = "__ELIT_PM_WAPK_ONLINE_SHUTDOWN__";
2998
3004
  var PM_WAPK_ONLINE_SHUTDOWN_TIMEOUT_MS = 8e3;
@@ -3030,6 +3036,32 @@ error: ${text}`);
3030
3036
  }
3031
3037
  throw new Error(`${optionName} must be one of: always, on-failure, never`);
3032
3038
  }
3039
+ function normalizePmMemoryAction(value, optionName = "--memory-action") {
3040
+ if (value === void 0 || value === null || value === "") {
3041
+ return void 0;
3042
+ }
3043
+ if (typeof value !== "string") {
3044
+ throw new Error(`${optionName} must be one of: restart, stop`);
3045
+ }
3046
+ const action = value.trim().toLowerCase();
3047
+ if (action === "restart" || action === "stop") {
3048
+ return action;
3049
+ }
3050
+ throw new Error(`${optionName} must be one of: restart, stop`);
3051
+ }
3052
+ function normalizePmProxyStrategy(value, optionName = "--proxy-strategy") {
3053
+ if (value === void 0 || value === null || value === "") {
3054
+ return void 0;
3055
+ }
3056
+ if (typeof value !== "string") {
3057
+ throw new Error(`${optionName} must be one of: proxy, inherit`);
3058
+ }
3059
+ const strategy = value.trim().toLowerCase();
3060
+ if (strategy === "proxy" || strategy === "inherit") {
3061
+ return strategy;
3062
+ }
3063
+ throw new Error(`${optionName} must be one of: proxy, inherit`);
3064
+ }
3033
3065
  function normalizeIntegerOption(value, optionName, min = 0) {
3034
3066
  const parsed = Number.parseInt(value, 10);
3035
3067
  if (!Number.isFinite(parsed) || parsed < min) {
@@ -3037,6 +3069,30 @@ error: ${text}`);
3037
3069
  }
3038
3070
  return parsed;
3039
3071
  }
3072
+ function normalizePmMemoryLimit(value, optionName = "--max-memory") {
3073
+ if (value === void 0 || value === null || value === "") {
3074
+ return void 0;
3075
+ }
3076
+ if (typeof value === "number") {
3077
+ const normalized2 = Math.trunc(value);
3078
+ if (!Number.isFinite(normalized2) || normalized2 < 1) {
3079
+ throw new Error(`${optionName} must be a number >= 1 or a size like 256M.`);
3080
+ }
3081
+ return normalized2;
3082
+ }
3083
+ if (typeof value !== "string") {
3084
+ throw new Error(`${optionName} must be a number >= 1 or a size like 256M.`);
3085
+ }
3086
+ const normalized = value.trim();
3087
+ const match = /^(\d+)(b|kb|mb|gb|tb|k|m|g|t)?$/i.exec(normalized);
3088
+ if (!match) {
3089
+ throw new Error(`${optionName} must be a number >= 1 or a size like 256M.`);
3090
+ }
3091
+ const amount = normalizeIntegerOption(match[1] ?? "", optionName, 1);
3092
+ const unit = (match[2] ?? "b").toLowerCase();
3093
+ const multiplier = unit === "b" ? 1 : unit === "k" || unit === "kb" ? 1024 : unit === "m" || unit === "mb" ? 1024 ** 2 : unit === "g" || unit === "gb" ? 1024 ** 3 : 1024 ** 4;
3094
+ return amount * multiplier;
3095
+ }
3040
3096
  function normalizeNonEmptyString(value) {
3041
3097
  if (typeof value !== "string") {
3042
3098
  return void 0;
@@ -3313,6 +3369,30 @@ error: ${text}`);
3313
3369
  }
3314
3370
  return value;
3315
3371
  }
3372
+ function normalizePmProxyConfig(value, optionName = "pm proxy") {
3373
+ if (value === void 0 || value === null) {
3374
+ return void 0;
3375
+ }
3376
+ if (typeof value !== "object") {
3377
+ throw new Error(`${optionName} must be an object with at least a port.`);
3378
+ }
3379
+ const candidate = value;
3380
+ const hasAnyValue = candidate.port !== void 0 || candidate.host !== void 0 || candidate.targetHost !== void 0 || candidate.envVar !== void 0;
3381
+ if (!hasAnyValue) {
3382
+ return void 0;
3383
+ }
3384
+ if (candidate.port === void 0 || candidate.port === null || candidate.port === "") {
3385
+ throw new Error(`${optionName}.port is required.`);
3386
+ }
3387
+ const port = typeof candidate.port === "number" ? normalizeIntegerOption(String(Math.trunc(candidate.port)), `${optionName}.port`, 1) : normalizeIntegerOption(String(candidate.port), `${optionName}.port`, 1);
3388
+ return {
3389
+ port,
3390
+ strategy: normalizePmProxyStrategy(candidate.strategy, `${optionName}.strategy`) ?? DEFAULT_PM_PROXY_STRATEGY,
3391
+ host: normalizeNonEmptyString(candidate.host),
3392
+ targetHost: normalizeNonEmptyString(candidate.targetHost),
3393
+ envVar: normalizeNonEmptyString(candidate.envVar)
3394
+ };
3395
+ }
3316
3396
 
3317
3397
  // src/cli/pm/records.ts
3318
3398
  var import_node_fs2 = __require("fs");
@@ -3345,18 +3425,31 @@ error: ${text}`);
3345
3425
  function toSavedAppDefinition(record) {
3346
3426
  return {
3347
3427
  name: record.name,
3428
+ baseName: record.baseName,
3429
+ instanceIndex: record.instanceIndex,
3430
+ instances: record.instances,
3348
3431
  type: record.type,
3349
3432
  cwd: record.cwd,
3350
3433
  runtime: record.runtime,
3351
3434
  env: record.env,
3435
+ proxy: record.proxy,
3352
3436
  script: record.script,
3353
3437
  file: record.file,
3354
3438
  wapk: record.wapk,
3355
3439
  password: record.password,
3356
3440
  wapkRun: record.wapkRun,
3357
3441
  restartPolicy: record.restartPolicy,
3442
+ maxMemoryBytes: record.maxMemoryBytes,
3443
+ memoryAction: record.memoryAction,
3444
+ cronRestart: record.cronRestart,
3445
+ expBackoffRestartDelay: record.expBackoffRestartDelay,
3446
+ expBackoffRestartMaxDelay: record.expBackoffRestartMaxDelay,
3447
+ restartWindow: record.restartWindow,
3448
+ waitReady: record.waitReady,
3449
+ listenTimeout: record.listenTimeout,
3358
3450
  autorestart: record.autorestart,
3359
3451
  restartDelay: record.restartDelay,
3452
+ killTimeout: record.killTimeout,
3360
3453
  maxRestarts: record.maxRestarts,
3361
3454
  minUptime: record.minUptime,
3362
3455
  watch: record.watch,
@@ -3423,6 +3516,9 @@ error: ${text}`);
3423
3516
  }
3424
3517
  return listPmRecordMatches(paths).find((match) => match.record.name === nameOrId);
3425
3518
  }
3519
+ function findPmGroupMatches(paths, baseName) {
3520
+ return listPmRecordMatches(paths).filter((match) => match.record.baseName === baseName).sort((left, right) => left.record.instanceIndex - right.record.instanceIndex);
3521
+ }
3426
3522
  function isProcessAlive(pid) {
3427
3523
  if (!pid || pid <= 0) {
3428
3524
  return false;
@@ -3472,11 +3568,22 @@ error: ${text}`);
3472
3568
  runtime: record.runtime,
3473
3569
  cwd: record.cwd,
3474
3570
  env: record.env,
3571
+ proxy: record.proxy,
3572
+ instances: record.instances,
3475
3573
  autorestart: record.autorestart,
3476
3574
  restartDelay: record.restartDelay,
3575
+ killTimeout: record.killTimeout,
3477
3576
  maxRestarts: record.maxRestarts,
3478
3577
  password: record.password,
3479
3578
  restartPolicy: record.restartPolicy,
3579
+ maxMemory: record.maxMemoryBytes,
3580
+ memoryAction: record.memoryAction,
3581
+ cronRestart: record.cronRestart,
3582
+ expBackoffRestartDelay: record.expBackoffRestartDelay,
3583
+ expBackoffRestartMaxDelay: record.expBackoffRestartMaxDelay,
3584
+ restartWindow: record.restartWindow,
3585
+ waitReady: record.waitReady,
3586
+ listenTimeout: record.listenTimeout,
3480
3587
  minUptime: record.minUptime,
3481
3588
  watch: record.watch,
3482
3589
  watchPaths: record.watchPaths,
@@ -3495,11 +3602,22 @@ error: ${text}`);
3495
3602
  runtime: app.runtime,
3496
3603
  cwd: app.cwd,
3497
3604
  env: app.env,
3605
+ proxy: app.proxy,
3606
+ instances: app.instances,
3498
3607
  autorestart: app.autorestart,
3499
3608
  restartDelay: app.restartDelay,
3609
+ killTimeout: app.killTimeout,
3500
3610
  maxRestarts: app.maxRestarts,
3501
3611
  password: app.password,
3502
3612
  restartPolicy: app.restartPolicy,
3613
+ maxMemory: app.maxMemoryBytes,
3614
+ memoryAction: app.memoryAction,
3615
+ cronRestart: app.cronRestart,
3616
+ expBackoffRestartDelay: app.expBackoffRestartDelay,
3617
+ expBackoffRestartMaxDelay: app.expBackoffRestartMaxDelay,
3618
+ restartWindow: app.restartWindow,
3619
+ waitReady: app.waitReady,
3620
+ listenTimeout: app.listenTimeout,
3503
3621
  minUptime: app.minUptime,
3504
3622
  watch: app.watch,
3505
3623
  watchPaths: app.watchPaths,
@@ -3511,6 +3629,170 @@ error: ${text}`);
3511
3629
 
3512
3630
  // src/cli/pm/config.ts
3513
3631
  var import_node_path4 = __require("path");
3632
+
3633
+ // src/cli/pm/schedule.ts
3634
+ var PM_EVERY_PATTERN = /^@every\s+(\d+)(ms|s|m|h|d)$/i;
3635
+ function parsePositiveInteger(value, optionName, min = 0) {
3636
+ const parsed = Number.parseInt(value, 10);
3637
+ if (!Number.isFinite(parsed) || parsed < min) {
3638
+ throw new Error(`${optionName} must be a number >= ${min}`);
3639
+ }
3640
+ return parsed;
3641
+ }
3642
+ function fillRange(values, start, end, step, min, max, optionName) {
3643
+ if (!Number.isInteger(start) || !Number.isInteger(end) || start < min || end > max || start > end) {
3644
+ throw new Error(`${optionName} contains an invalid range: ${start}-${end}`);
3645
+ }
3646
+ if (!Number.isInteger(step) || step < 1) {
3647
+ throw new Error(`${optionName} contains an invalid step: ${step}`);
3648
+ }
3649
+ for (let current = start; current <= end; current += step) {
3650
+ values.add(current);
3651
+ }
3652
+ }
3653
+ function parseCronSegment(segment, values, min, max, optionName) {
3654
+ const normalized = segment.trim();
3655
+ if (!normalized) {
3656
+ throw new Error(`${optionName} contains an empty field segment.`);
3657
+ }
3658
+ const [rangeToken, stepToken] = normalized.split("/");
3659
+ if (normalized.split("/").length > 2) {
3660
+ throw new Error(`${optionName} contains an invalid step segment: ${normalized}`);
3661
+ }
3662
+ const step = stepToken === void 0 ? 1 : parsePositiveInteger(stepToken, optionName, 1);
3663
+ if (rangeToken === "*") {
3664
+ fillRange(values, min, max, step, min, max, optionName);
3665
+ return;
3666
+ }
3667
+ if (rangeToken.includes("-")) {
3668
+ const [startToken, endToken] = rangeToken.split("-");
3669
+ if (!startToken || !endToken) {
3670
+ throw new Error(`${optionName} contains an invalid range: ${normalized}`);
3671
+ }
3672
+ const start2 = parsePositiveInteger(startToken, optionName, min);
3673
+ const end = parsePositiveInteger(endToken, optionName, min);
3674
+ fillRange(values, start2, end, step, min, max, optionName);
3675
+ return;
3676
+ }
3677
+ const start = parsePositiveInteger(rangeToken, optionName, min);
3678
+ fillRange(values, start, stepToken === void 0 ? start : max, step, min, max, optionName);
3679
+ }
3680
+ function parseCronField(token, min, max, optionName) {
3681
+ const normalized = token.trim();
3682
+ if (!normalized) {
3683
+ throw new Error(`${optionName} contains an empty field.`);
3684
+ }
3685
+ if (normalized === "*") {
3686
+ const values2 = /* @__PURE__ */ new Set();
3687
+ fillRange(values2, min, max, 1, min, max, optionName);
3688
+ return {
3689
+ values: values2,
3690
+ wildcard: true
3691
+ };
3692
+ }
3693
+ const values = /* @__PURE__ */ new Set();
3694
+ for (const segment of normalized.split(",")) {
3695
+ parseCronSegment(segment, values, min, max, optionName);
3696
+ }
3697
+ if (values.size === 0) {
3698
+ throw new Error(`${optionName} must match at least one value.`);
3699
+ }
3700
+ return {
3701
+ values,
3702
+ wildcard: false
3703
+ };
3704
+ }
3705
+ function matchesCronField(field, value) {
3706
+ if (!field) {
3707
+ return true;
3708
+ }
3709
+ return field.wildcard || field.values.has(value);
3710
+ }
3711
+ function matchesCronDate(schedule, candidate) {
3712
+ if (schedule.kind !== "cron") {
3713
+ return false;
3714
+ }
3715
+ const secondMatches = schedule.hasSeconds ? matchesCronField(schedule.seconds, candidate.getSeconds()) : true;
3716
+ const minuteMatches = matchesCronField(schedule.minutes, candidate.getMinutes());
3717
+ const hourMatches = matchesCronField(schedule.hours, candidate.getHours());
3718
+ const monthMatches = matchesCronField(schedule.months, candidate.getMonth() + 1);
3719
+ const dayOfMonthMatches = matchesCronField(schedule.daysOfMonth, candidate.getDate());
3720
+ const dayOfWeekMatches = matchesCronField(schedule.daysOfWeek, candidate.getDay());
3721
+ const domWildcard = schedule.daysOfMonth?.wildcard ?? true;
3722
+ const dowWildcard = schedule.daysOfWeek?.wildcard ?? true;
3723
+ const dayMatches = domWildcard && dowWildcard ? true : domWildcard ? dayOfWeekMatches : dowWildcard ? dayOfMonthMatches : dayOfMonthMatches || dayOfWeekMatches;
3724
+ return secondMatches && minuteMatches && hourMatches && monthMatches && dayMatches;
3725
+ }
3726
+ function parsePmRestartSchedule(expression, optionName = "--cron-restart") {
3727
+ const normalized = expression.trim();
3728
+ const everyMatch = PM_EVERY_PATTERN.exec(normalized);
3729
+ if (everyMatch) {
3730
+ const [, valueText, unit] = everyMatch;
3731
+ const value = parsePositiveInteger(valueText ?? "", optionName, 1);
3732
+ const multiplier = unit?.toLowerCase() === "ms" ? 1 : unit?.toLowerCase() === "s" ? 1e3 : unit?.toLowerCase() === "m" ? 6e4 : unit?.toLowerCase() === "h" ? 36e5 : 864e5;
3733
+ return {
3734
+ expression: normalized,
3735
+ kind: "every",
3736
+ intervalMs: value * multiplier,
3737
+ hasSeconds: true
3738
+ };
3739
+ }
3740
+ const fields = normalized.split(/\s+/).filter((field) => field.length > 0);
3741
+ if (fields.length !== 5 && fields.length !== 6) {
3742
+ throw new Error(`${optionName} must be a 5-field cron expression or @every <duration>.`);
3743
+ }
3744
+ const hasSeconds = fields.length === 6;
3745
+ const offset = hasSeconds ? 1 : 0;
3746
+ return {
3747
+ expression: normalized,
3748
+ kind: "cron",
3749
+ hasSeconds,
3750
+ seconds: hasSeconds ? parseCronField(fields[0] ?? "", 0, 59, optionName) : void 0,
3751
+ minutes: parseCronField(fields[0 + offset] ?? "", 0, 59, optionName),
3752
+ hours: parseCronField(fields[1 + offset] ?? "", 0, 23, optionName),
3753
+ daysOfMonth: parseCronField(fields[2 + offset] ?? "", 1, 31, optionName),
3754
+ months: parseCronField(fields[3 + offset] ?? "", 1, 12, optionName),
3755
+ daysOfWeek: parseCronField(fields[4 + offset] ?? "", 0, 6, optionName)
3756
+ };
3757
+ }
3758
+ function normalizePmRestartSchedule(value, optionName = "--cron-restart") {
3759
+ if (value === void 0 || value === null || value === "") {
3760
+ return void 0;
3761
+ }
3762
+ if (typeof value !== "string") {
3763
+ throw new Error(`${optionName} must be a cron string or @every <duration>.`);
3764
+ }
3765
+ const normalized = value.trim();
3766
+ if (!normalized) {
3767
+ throw new Error(`${optionName} must be a non-empty cron string or @every <duration>.`);
3768
+ }
3769
+ parsePmRestartSchedule(normalized, optionName);
3770
+ return normalized;
3771
+ }
3772
+ function resolveNextPmScheduleOccurrence(schedule, after = /* @__PURE__ */ new Date()) {
3773
+ if (schedule.kind === "every") {
3774
+ return new Date(after.getTime() + (schedule.intervalMs ?? 0));
3775
+ }
3776
+ const stepMs = schedule.hasSeconds ? 1e3 : 6e4;
3777
+ const candidate = new Date(after.getTime());
3778
+ if (schedule.hasSeconds) {
3779
+ candidate.setMilliseconds(0);
3780
+ candidate.setSeconds(candidate.getSeconds() + 1);
3781
+ } else {
3782
+ candidate.setSeconds(0, 0);
3783
+ candidate.setMinutes(candidate.getMinutes() + 1);
3784
+ }
3785
+ const maxIterations = schedule.hasSeconds ? 2678400 : 527040;
3786
+ for (let index = 0; index < maxIterations; index++) {
3787
+ if (matchesCronDate(schedule, candidate)) {
3788
+ return new Date(candidate.getTime());
3789
+ }
3790
+ candidate.setTime(candidate.getTime() + stepMs);
3791
+ }
3792
+ return void 0;
3793
+ }
3794
+
3795
+ // src/cli/pm/config.ts
3514
3796
  function parsePmTarget(parsed, workspaceRoot) {
3515
3797
  if (parsed.script) {
3516
3798
  return { script: parsed.script };
@@ -3553,6 +3835,20 @@ error: ${text}`);
3553
3835
  }
3554
3836
  return "process";
3555
3837
  }
3838
+ function formatPmInstanceName(baseName, instanceIndex) {
3839
+ return instanceIndex <= 1 ? baseName : `${baseName}:${instanceIndex}`;
3840
+ }
3841
+ function expandPmInstanceDefinitions(definition, targetInstances = definition.instances) {
3842
+ return Array.from({ length: targetInstances }, (_, index) => {
3843
+ const instanceIndex = index + 1;
3844
+ return {
3845
+ ...definition,
3846
+ name: formatPmInstanceName(definition.baseName, instanceIndex),
3847
+ instanceIndex,
3848
+ instances: targetInstances
3849
+ };
3850
+ });
3851
+ }
3556
3852
  function countDefinedTargets(app) {
3557
3853
  return [app.script, app.file, app.wapk].filter(Boolean).length;
3558
3854
  }
@@ -3590,8 +3886,25 @@ error: ${text}`);
3590
3886
  throw new Error("A pm app must define exactly one of script, file, or wapk.");
3591
3887
  }
3592
3888
  const name = defaultProcessName({ script, file, wapk }, parsed.name ?? base?.name);
3889
+ const instances = parsed.instances ?? base?.instances ?? 1;
3890
+ if (!Number.isInteger(instances) || instances < 1) {
3891
+ throw new Error("pm instances must be a number >= 1.");
3892
+ }
3893
+ const proxy = normalizePmProxyConfig(
3894
+ parsed.proxy ? { ...base?.proxy ?? {}, ...parsed.proxy } : base?.proxy,
3895
+ parsed.proxy ? "pm proxy" : "pm.apps[].proxy"
3896
+ );
3897
+ if (proxy?.strategy === "inherit" && instances > 1) {
3898
+ throw new Error('pm proxy strategy "inherit" currently supports only one managed instance per app.');
3899
+ }
3593
3900
  const mergedWapkRun = mergePmWapkRunConfig(base?.wapkRun, parsed.wapkRun);
3594
3901
  const runtime2 = normalizePmRuntime(parsed.runtime ?? mergedWapkRun?.runtime ?? base?.runtime, "--runtime");
3902
+ const maxMemoryBytes = parsed.maxMemoryBytes ?? normalizePmMemoryLimit(base?.maxMemory, "pm.apps[].maxMemory");
3903
+ const memoryAction = parsed.memoryAction ?? normalizePmMemoryAction(base?.memoryAction, "pm.apps[].memoryAction") ?? "restart";
3904
+ const cronRestart = parsed.cronRestart ?? normalizePmRestartSchedule(base?.cronRestart, "pm.apps[].cronRestart");
3905
+ const expBackoffRestartDelay = parsed.expBackoffRestartDelay ?? (base?.expBackoffRestartDelay === void 0 ? void 0 : normalizeIntegerOption(String(base.expBackoffRestartDelay), "pm.apps[].expBackoffRestartDelay", 1));
3906
+ const expBackoffRestartMaxDelay = parsed.expBackoffRestartMaxDelay ?? (base?.expBackoffRestartMaxDelay === void 0 ? void 0 : normalizeIntegerOption(String(base.expBackoffRestartMaxDelay), "pm.apps[].expBackoffRestartMaxDelay", 1));
3907
+ const restartWindow = parsed.restartWindow ?? (base?.restartWindow === void 0 ? void 0 : normalizeIntegerOption(String(base.restartWindow), "pm.apps[].restartWindow", 1));
3595
3908
  let restartPolicy = normalizePmRestartPolicy(parsed.restartPolicy ?? base?.restartPolicy, "--restart-policy") ?? (base?.autorestart ?? true ? "always" : "never");
3596
3909
  if (parsed.autorestart === false) {
3597
3910
  restartPolicy = "never";
@@ -3611,6 +3924,7 @@ error: ${text}`);
3611
3924
  timeout: parsed.healthCheckTimeout,
3612
3925
  maxFailures: parsed.healthCheckMaxFailures
3613
3926
  } : base?.healthCheck);
3927
+ const waitReady = parsed.waitReady ?? base?.waitReady ?? false;
3614
3928
  const password = parsed.password ?? mergedWapkRun?.password ?? base?.password;
3615
3929
  const wapkRun = stripPmWapkSourceFromRunConfig(mergedWapkRun);
3616
3930
  if (password && !wapk) {
@@ -3619,8 +3933,14 @@ error: ${text}`);
3619
3933
  if (wapkRun && !wapk) {
3620
3934
  throw new Error("WAPK run options are only supported when starting a WAPK app.");
3621
3935
  }
3936
+ if (waitReady && !healthCheck) {
3937
+ throw new Error("--wait-ready requires --health-url or pm.apps[].healthCheck.");
3938
+ }
3622
3939
  return {
3623
- name,
3940
+ name: formatPmInstanceName(name, 1),
3941
+ baseName: name,
3942
+ instanceIndex: 1,
3943
+ instances,
3624
3944
  type: script ? "script" : wapk ? "wapk" : "file",
3625
3945
  source,
3626
3946
  cwd: resolvedCwd,
@@ -3635,9 +3955,19 @@ error: ${text}`);
3635
3955
  wapkRun,
3636
3956
  autorestart,
3637
3957
  restartDelay: parsed.restartDelay ?? base?.restartDelay ?? DEFAULT_RESTART_DELAY,
3958
+ proxy,
3959
+ killTimeout: parsed.killTimeout ?? base?.killTimeout ?? DEFAULT_PM_KILL_TIMEOUT,
3638
3960
  maxRestarts: parsed.maxRestarts ?? base?.maxRestarts ?? DEFAULT_MAX_RESTARTS,
3639
3961
  password,
3640
3962
  restartPolicy,
3963
+ maxMemoryBytes,
3964
+ memoryAction,
3965
+ cronRestart,
3966
+ expBackoffRestartDelay,
3967
+ expBackoffRestartMaxDelay,
3968
+ restartWindow,
3969
+ waitReady,
3970
+ listenTimeout: parsed.listenTimeout ?? base?.listenTimeout ?? DEFAULT_PM_LISTEN_TIMEOUT,
3641
3971
  minUptime: parsed.minUptime ?? base?.minUptime ?? DEFAULT_MIN_UPTIME,
3642
3972
  watch: watch2,
3643
3973
  watchPaths: watch2 ? normalizeResolvedWatchPaths(configuredWatchPaths, resolvedCwd, script ? "script" : wapk ? "wapk" : "file", file, wapk) : [],
@@ -3649,16 +3979,17 @@ error: ${text}`);
3649
3979
  function resolvePmStartDefinitions(parsed, config, workspaceRoot) {
3650
3980
  const configApps = getConfiguredPmApps(config);
3651
3981
  const selection = resolveStartSelection(configApps, parsed, workspaceRoot);
3982
+ const expandDefinitions = (definitions) => definitions.flatMap((definition) => expandPmInstanceDefinitions(definition));
3652
3983
  if (selection.startAll) {
3653
3984
  if (configApps.length === 0) {
3654
3985
  throw new Error("No pm apps configured in elit.config.* and no start target was provided.");
3655
3986
  }
3656
- return configApps.map((app) => resolvePmAppDefinition(app, { ...parsed, name: app.name }, workspaceRoot, "config"));
3987
+ return expandDefinitions(configApps.map((app) => resolvePmAppDefinition(app, { ...parsed, name: app.name }, workspaceRoot, "config")));
3657
3988
  }
3658
3989
  if (selection.selected) {
3659
- return [resolvePmAppDefinition(selection.selected, parsed, workspaceRoot, "config")];
3990
+ return expandDefinitions([resolvePmAppDefinition(selection.selected, parsed, workspaceRoot, "config")]);
3660
3991
  }
3661
- return [resolvePmAppDefinition(void 0, parsed, workspaceRoot, "cli")];
3992
+ return expandDefinitions([resolvePmAppDefinition(void 0, parsed, workspaceRoot, "cli")]);
3662
3993
  }
3663
3994
  function parsePmStartArgs(args) {
3664
3995
  const parsed = {
@@ -3731,6 +4062,39 @@ error: ${text}`);
3731
4062
  parsed.env[key] = value;
3732
4063
  break;
3733
4064
  }
4065
+ case "--instances":
4066
+ parsed.instances = normalizeIntegerOption(readRequiredValue(args, ++index, "--instances"), "--instances", 1);
4067
+ break;
4068
+ case "--proxy-port":
4069
+ parsed.proxy = {
4070
+ ...parsed.proxy,
4071
+ port: normalizeIntegerOption(readRequiredValue(args, ++index, "--proxy-port"), "--proxy-port", 1)
4072
+ };
4073
+ break;
4074
+ case "--proxy-strategy":
4075
+ parsed.proxy = {
4076
+ ...parsed.proxy,
4077
+ strategy: normalizePmProxyStrategy(readRequiredValue(args, ++index, "--proxy-strategy"), "--proxy-strategy")
4078
+ };
4079
+ break;
4080
+ case "--proxy-host":
4081
+ parsed.proxy = {
4082
+ ...parsed.proxy,
4083
+ host: readRequiredValue(args, ++index, "--proxy-host")
4084
+ };
4085
+ break;
4086
+ case "--proxy-target-host":
4087
+ parsed.proxy = {
4088
+ ...parsed.proxy,
4089
+ targetHost: readRequiredValue(args, ++index, "--proxy-target-host")
4090
+ };
4091
+ break;
4092
+ case "--proxy-env":
4093
+ parsed.proxy = {
4094
+ ...parsed.proxy,
4095
+ envVar: readRequiredValue(args, ++index, "--proxy-env")
4096
+ };
4097
+ break;
3734
4098
  case "--password":
3735
4099
  parsed.password = readRequiredValue(args, ++index, "--password");
3736
4100
  break;
@@ -3781,6 +4145,30 @@ error: ${text}`);
3781
4145
  case "--restart-policy":
3782
4146
  parsed.restartPolicy = normalizePmRestartPolicy(readRequiredValue(args, ++index, "--restart-policy"));
3783
4147
  break;
4148
+ case "--max-memory":
4149
+ parsed.maxMemoryBytes = normalizePmMemoryLimit(readRequiredValue(args, ++index, "--max-memory"), "--max-memory");
4150
+ break;
4151
+ case "--memory-action":
4152
+ parsed.memoryAction = normalizePmMemoryAction(readRequiredValue(args, ++index, "--memory-action"), "--memory-action");
4153
+ break;
4154
+ case "--cron-restart":
4155
+ parsed.cronRestart = normalizePmRestartSchedule(readRequiredValue(args, ++index, "--cron-restart"), "--cron-restart");
4156
+ break;
4157
+ case "--exp-backoff-restart-delay":
4158
+ parsed.expBackoffRestartDelay = normalizeIntegerOption(readRequiredValue(args, ++index, "--exp-backoff-restart-delay"), "--exp-backoff-restart-delay", 1);
4159
+ break;
4160
+ case "--exp-backoff-restart-max-delay":
4161
+ parsed.expBackoffRestartMaxDelay = normalizeIntegerOption(readRequiredValue(args, ++index, "--exp-backoff-restart-max-delay"), "--exp-backoff-restart-max-delay", 1);
4162
+ break;
4163
+ case "--restart-window":
4164
+ parsed.restartWindow = normalizeIntegerOption(readRequiredValue(args, ++index, "--restart-window"), "--restart-window", 1);
4165
+ break;
4166
+ case "--wait-ready":
4167
+ parsed.waitReady = true;
4168
+ break;
4169
+ case "--listen-timeout":
4170
+ parsed.listenTimeout = normalizeIntegerOption(readRequiredValue(args, ++index, "--listen-timeout"), "--listen-timeout", 1);
4171
+ break;
3784
4172
  case "--min-uptime":
3785
4173
  parsed.minUptime = normalizeIntegerOption(readRequiredValue(args, ++index, "--min-uptime"), "--min-uptime");
3786
4174
  break;
@@ -3820,6 +4208,9 @@ error: ${text}`);
3820
4208
  case "--restart-delay":
3821
4209
  parsed.restartDelay = normalizeIntegerOption(readRequiredValue(args, ++index, "--restart-delay"), "--restart-delay");
3822
4210
  break;
4211
+ case "--kill-timeout":
4212
+ parsed.killTimeout = normalizeIntegerOption(readRequiredValue(args, ++index, "--kill-timeout"), "--kill-timeout", 1);
4213
+ break;
3823
4214
  case "--max-restarts":
3824
4215
  parsed.maxRestarts = normalizeIntegerOption(readRequiredValue(args, ++index, "--max-restarts"), "--max-restarts");
3825
4216
  break;
@@ -3861,8 +4252,15 @@ error: ${text}`);
3861
4252
  " elit pm start my-app",
3862
4253
  " elit pm start",
3863
4254
  " elit pm list",
4255
+ " elit pm list --json",
4256
+ " elit pm show <name>",
4257
+ " elit pm describe <name> --json",
3864
4258
  " elit pm stop <name|all>",
3865
4259
  " elit pm restart <name|all>",
4260
+ " elit pm reload <name|all>",
4261
+ " elit pm scale <name> <count>",
4262
+ " elit pm reset <name|all>",
4263
+ " elit pm send-signal <signal> <name|all>",
3866
4264
  " elit pm delete <name|all>",
3867
4265
  " elit pm save",
3868
4266
  " elit pm resurrect",
@@ -3878,6 +4276,12 @@ error: ${text}`);
3878
4276
  " --google-drive-shared-drive Forward supportsAllDrives=true for shared drives",
3879
4277
  " --runtime, -r <name> Runtime override: node, bun, deno",
3880
4278
  " --name, -n <name> Process name used by list/stop/restart",
4279
+ " --instances <count> Start multiple managed instances for the same app name",
4280
+ " --proxy-port <port> Own a public HTTP port through a PM proxy for single-instance reload handoff",
4281
+ " --proxy-strategy <mode> Public socket mode: proxy or inherit (default: proxy)",
4282
+ " --proxy-host <host> Public host bound by the PM proxy (default: 0.0.0.0)",
4283
+ " --proxy-target-host <host> Internal host used for upstream child traffic (default: 127.0.0.1)",
4284
+ " --proxy-env <name> Env var populated with the child private port (default: PORT)",
3881
4285
  " --cwd <dir> Working directory for the managed process",
3882
4286
  " --env KEY=VALUE Add or override an environment variable",
3883
4287
  " --password <value> Password for locked WAPK archives",
@@ -3889,6 +4293,14 @@ error: ${text}`);
3889
4293
  " --no-archive-watch Disable archive-source read sync for WAPK apps",
3890
4294
  " --archive-sync-interval <ms> Forward WAPK archive read-sync interval (>= 50ms)",
3891
4295
  " --restart-policy <mode> Restart policy: always, on-failure, never",
4296
+ " --max-memory <bytes|size> Trigger a memory action after a limit like 268435456 or 256M",
4297
+ " --memory-action <mode> Action on max-memory: restart, stop",
4298
+ " --cron-restart <expr> Restart on a cron schedule or @every <duration>",
4299
+ " --exp-backoff-restart-delay <ms> Exponential unstable-restart backoff base delay",
4300
+ " --exp-backoff-restart-max-delay <ms> Maximum unstable-restart backoff delay (default 15000)",
4301
+ " --restart-window <ms> Rolling time window used when counting restart attempts",
4302
+ " --wait-ready Keep the process in starting state until its health check succeeds",
4303
+ " --listen-timeout <ms> Startup wait limit when --wait-ready is enabled (default 3000)",
3892
4304
  " --min-uptime <ms> Reset crash counter after this healthy uptime",
3893
4305
  " --watch Restart when watched files change",
3894
4306
  " --watch-path <path> Add a file or directory to watch",
@@ -3901,6 +4313,7 @@ error: ${text}`);
3901
4313
  " --health-max-failures <n> Consecutive failures before restart (default 3)",
3902
4314
  " --no-autorestart Disable automatic restart",
3903
4315
  " --restart-delay <ms> Delay between restart attempts (default 1000)",
4316
+ " --kill-timeout <ms> Grace period before force-killing a stop or restart (default 5000)",
3904
4317
  " --max-restarts <count> Maximum restart attempts (default 10)",
3905
4318
  "",
3906
4319
  "Config:",
@@ -3924,6 +4337,17 @@ error: ${text}`);
3924
4337
  " - elit pm save persists running apps to pm.dumpFile or ./.elit/pm/dump.json.",
3925
4338
  " - elit pm resurrect restarts whatever was last saved by elit pm save.",
3926
4339
  " - elit pm start <name> starts a configured app by name.",
4340
+ " - elit pm reload <name|all> performs a rolling stop/start and waits for each instance to return online before continuing.",
4341
+ " - elit pm scale <name> <count> changes the number of managed instances for a running app group.",
4342
+ " - elit pm reset <name|all> clears restart count, last exit code, and saved error metadata.",
4343
+ " - elit pm send-signal <signal> <name|all> forwards a POSIX-style signal such as SIGUSR2 or TERM.",
4344
+ " - maxMemory works with memoryAction=restart|stop, cronRestart accepts cron or @every schedules, expBackoffRestartDelay doubles unstable restart delays, expBackoffRestartMaxDelay caps them, and restartWindow limits how long restart attempts keep counting toward maxRestarts.",
4345
+ " - killTimeout controls how long PM waits before force-killing an app that ignores stop or restart requests.",
4346
+ " - waitReady uses the configured health check as a startup gate and errors if it never becomes healthy within listenTimeout.",
4347
+ " - elit pm list shows live cpu, memory, and uptime columns when the child process is running.",
4348
+ " - elit pm list --json and elit pm jlist print machine-readable process state.",
4349
+ " - elit pm list --json, elit pm show --json, and elit pm describe --json include liveMetrics when available.",
4350
+ " - elit pm show <name> and elit pm describe <name> expose the full saved process record.",
3927
4351
  " - TypeScript files with runtime node require tsx, otherwise use --runtime bun.",
3928
4352
  " - WAPK processes are executed through elit wapk run inside the manager.",
3929
4353
  " - WAPK PM apps can use local archives, gdrive://<fileId>, or pm.apps[].wapkRun.googleDrive.",
@@ -3979,16 +4403,94 @@ error: ${text}`);
3979
4403
  const globalCommand = process.platform === "win32" ? "tsx.cmd" : "tsx";
3980
4404
  return commandExists(globalCommand) ? globalCommand : void 0;
3981
4405
  }
4406
+ function resolvePmNodeSharedListenerBootstrapPath() {
4407
+ const cliEntry = process.argv[1];
4408
+ const candidates = [
4409
+ (0, import_node_path5.join)(__dirname, "node-shared-listener-bootstrap.cjs"),
4410
+ (0, import_node_path5.join)(__dirname, "..", "..", "..", "dist", "pm-node-shared-listener-bootstrap.cjs")
4411
+ ];
4412
+ if (cliEntry) {
4413
+ const resolvedCliEntry = (0, import_node_path5.resolve)(cliEntry);
4414
+ const baseDir = (0, import_node_path5.dirname)(resolvedCliEntry);
4415
+ candidates.push(
4416
+ (0, import_node_path5.join)(baseDir, "cli", "pm", "node-shared-listener-bootstrap.cjs"),
4417
+ (0, import_node_path5.join)(baseDir, "pm-node-shared-listener-bootstrap.cjs")
4418
+ );
4419
+ }
4420
+ return candidates.find((candidate) => (0, import_node_fs3.existsSync)(candidate));
4421
+ }
4422
+ function appendNodeOption(existing, option) {
4423
+ return existing && existing.trim().length > 0 ? `${existing.trim()} ${option}` : option;
4424
+ }
4425
+ function supportsPmInheritedListener(record, runtime2) {
4426
+ return (record.proxy?.strategy ?? DEFAULT_PM_PROXY_STRATEGY) === "inherit" && record.type === "file" && runtime2 === "node" && !isTypescriptFile(record.file);
4427
+ }
3982
4428
  function inferRuntimeFromFile(filePath) {
3983
4429
  if (isTypescriptFile(filePath) && commandExists("bun")) {
3984
4430
  return "bun";
3985
4431
  }
3986
4432
  return "node";
3987
4433
  }
4434
+ function sampleWindowsPmProcessMetrics(pid) {
4435
+ const script = [
4436
+ '$ErrorActionPreference = "Stop"',
4437
+ `$sample = Get-CimInstance -ClassName Win32_PerfFormattedData_PerfProc_Process -Filter "IDProcess = ${pid}" | Select-Object -First 1`,
4438
+ "if (-not $sample) { exit 2 }",
4439
+ "$cpu = [double]$sample.PercentProcessorTime",
4440
+ `$memory = if ($sample.PSObject.Properties.Match('WorkingSetPrivate').Count -gt 0) { [int64]$sample.WorkingSetPrivate } else { [int64](Get-Process -Id ${pid} -ErrorAction Stop).WorkingSet64 }`,
4441
+ 'Write-Output ($cpu.ToString([System.Globalization.CultureInfo]::InvariantCulture) + "," + $memory)'
4442
+ ].join("; ");
4443
+ const result = (0, import_node_child_process.spawnSync)(
4444
+ "powershell.exe",
4445
+ ["-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-Command", script],
4446
+ {
4447
+ encoding: "utf8",
4448
+ stdio: ["ignore", "pipe", "ignore"],
4449
+ windowsHide: true
4450
+ }
4451
+ );
4452
+ if (result.error || result.status !== 0) {
4453
+ return {};
4454
+ }
4455
+ const [cpuText, memoryText] = result.stdout.trim().split(",");
4456
+ const cpuPercent = Number.parseFloat((cpuText ?? "").replace(",", "."));
4457
+ const memoryRssBytes = Number.parseInt(memoryText ?? "", 10);
4458
+ return {
4459
+ cpuPercent: Number.isFinite(cpuPercent) ? cpuPercent : void 0,
4460
+ memoryRssBytes: Number.isFinite(memoryRssBytes) ? memoryRssBytes : void 0
4461
+ };
4462
+ }
4463
+ function samplePosixPmProcessMetrics(pid) {
4464
+ const result = (0, import_node_child_process.spawnSync)(
4465
+ "ps",
4466
+ ["-p", String(pid), "-o", "%cpu=", "-o", "rss="],
4467
+ {
4468
+ encoding: "utf8",
4469
+ stdio: ["ignore", "pipe", "ignore"],
4470
+ windowsHide: true
4471
+ }
4472
+ );
4473
+ if (result.error || result.status !== 0) {
4474
+ return {};
4475
+ }
4476
+ const [cpuText, memoryText] = result.stdout.trim().split(/\s+/, 2);
4477
+ const cpuPercent = Number.parseFloat((cpuText ?? "").replace(",", "."));
4478
+ const rssKilobytes = Number.parseInt(memoryText ?? "", 10);
4479
+ return {
4480
+ cpuPercent: Number.isFinite(cpuPercent) ? cpuPercent : void 0,
4481
+ memoryRssBytes: Number.isFinite(rssKilobytes) ? rssKilobytes * 1024 : void 0
4482
+ };
4483
+ }
4484
+ function samplePmProcessMetrics(pid) {
4485
+ return process.platform === "win32" ? sampleWindowsPmProcessMetrics(pid) : samplePosixPmProcessMetrics(pid);
4486
+ }
3988
4487
  function isPmOnlineWapkRecord(record) {
3989
4488
  return record.type === "wapk" && isPmWapkOnlineRunConfig(record.wapkRun);
3990
4489
  }
3991
4490
  function buildPmCommand(record) {
4491
+ if ((record.proxy?.strategy ?? DEFAULT_PM_PROXY_STRATEGY) === "inherit" && record.type !== "file") {
4492
+ throw new Error('pm proxy strategy "inherit" currently supports only Node .js/.mjs/.cjs file targets.');
4493
+ }
3992
4494
  if (record.type === "script") {
3993
4495
  return {
3994
4496
  command: record.script,
@@ -4060,9 +4562,23 @@ error: ${text}`);
4060
4562
  }
4061
4563
  const executable = preferCurrentExecutable("node");
4062
4564
  ensureCommandAvailable(executable, "Node.js runtime");
4565
+ const wantsInheritedListener = (record.proxy?.strategy ?? DEFAULT_PM_PROXY_STRATEGY) === "inherit";
4566
+ if (wantsInheritedListener && !supportsPmInheritedListener(record, runtime2)) {
4567
+ throw new Error('pm proxy strategy "inherit" currently supports only Node .js/.mjs/.cjs file targets.');
4568
+ }
4569
+ const bootstrapPath = wantsInheritedListener ? resolvePmNodeSharedListenerBootstrapPath() : void 0;
4570
+ if (wantsInheritedListener && !bootstrapPath) {
4571
+ throw new Error("Unable to resolve the PM shared-listener bootstrap module.");
4572
+ }
4063
4573
  return {
4064
4574
  command: executable,
4065
4575
  args: [record.file],
4576
+ env: bootstrapPath ? {
4577
+ NODE_OPTIONS: appendNodeOption(process.env.NODE_OPTIONS, `--require=${bootstrapPath}`),
4578
+ ELIT_PM_LISTEN_MODE: "ipc",
4579
+ ELIT_PM_PUBLIC_PORT: String(record.proxy?.port ?? record.env.PORT ?? "")
4580
+ } : void 0,
4581
+ ipc: Boolean(bootstrapPath),
4066
4582
  runtime: runtime2,
4067
4583
  preview: `${(0, import_node_path5.basename)(executable)} ${quoteCommandSegment(record.file)}`
4068
4584
  };
@@ -4074,20 +4590,33 @@ error: ${text}`);
4074
4590
  return {
4075
4591
  id,
4076
4592
  name: definition.name,
4593
+ baseName: definition.baseName,
4594
+ instanceIndex: definition.instanceIndex,
4595
+ instances: definition.instances,
4077
4596
  type: definition.type,
4078
4597
  source: definition.source,
4079
4598
  cwd: definition.cwd,
4080
4599
  runtime: definition.runtime,
4081
4600
  env: definition.env,
4601
+ proxy: definition.proxy,
4082
4602
  script: definition.script,
4083
4603
  file: definition.file,
4084
4604
  wapk: definition.wapk,
4085
4605
  wapkRun: definition.wapkRun,
4086
4606
  autorestart: definition.autorestart,
4087
4607
  restartDelay: definition.restartDelay,
4608
+ killTimeout: definition.killTimeout,
4088
4609
  maxRestarts: definition.maxRestarts,
4089
4610
  password: definition.password,
4090
4611
  restartPolicy: definition.restartPolicy,
4612
+ maxMemoryBytes: definition.maxMemoryBytes,
4613
+ memoryAction: definition.memoryAction,
4614
+ cronRestart: definition.cronRestart,
4615
+ expBackoffRestartDelay: definition.expBackoffRestartDelay,
4616
+ expBackoffRestartMaxDelay: definition.expBackoffRestartMaxDelay,
4617
+ restartWindow: definition.restartWindow,
4618
+ waitReady: definition.waitReady,
4619
+ listenTimeout: definition.listenTimeout,
4091
4620
  minUptime: definition.minUptime,
4092
4621
  watch: definition.watch,
4093
4622
  watchPaths: definition.watchPaths,
@@ -4103,16 +4632,20 @@ error: ${text}`);
4103
4632
  stoppedAt: void 0,
4104
4633
  runnerPid: void 0,
4105
4634
  childPid: void 0,
4635
+ proxyTargetPort: void 0,
4106
4636
  restartCount: existing?.restartCount ?? 0,
4637
+ reloadRequestedAt: void 0,
4638
+ lastRestartAt: existing?.lastRestartAt,
4107
4639
  lastExitCode: existing?.lastExitCode,
4108
4640
  error: void 0,
4641
+ proxyReadyAt: void 0,
4109
4642
  logFiles: existing?.logFiles ?? {
4110
4643
  out: (0, import_node_path5.join)(paths.logsDir, `${id}.out.log`),
4111
4644
  err: (0, import_node_path5.join)(paths.logsDir, `${id}.err.log`)
4112
4645
  }
4113
4646
  };
4114
4647
  }
4115
- function terminateProcessTree(pid) {
4648
+ function terminateProcessTree(pid, options) {
4116
4649
  if (process.platform === "win32") {
4117
4650
  const result = (0, import_node_child_process.spawnSync)("taskkill", ["/PID", String(pid), "/T", "/F"], {
4118
4651
  stdio: "ignore",
@@ -4124,7 +4657,17 @@ error: ${text}`);
4124
4657
  return;
4125
4658
  }
4126
4659
  try {
4127
- process.kill(pid, "SIGTERM");
4660
+ process.kill(pid, options?.force ? "SIGKILL" : "SIGTERM");
4661
+ } catch (error) {
4662
+ const code = error.code;
4663
+ if (code !== "ESRCH") {
4664
+ throw error;
4665
+ }
4666
+ }
4667
+ }
4668
+ function sendPmSignal(pid, signal) {
4669
+ try {
4670
+ process.kill(pid, signal);
4128
4671
  } catch (error) {
4129
4672
  const code = error.code;
4130
4673
  if (code !== "ESRCH") {
@@ -4176,6 +4719,7 @@ error: ${text}`);
4176
4719
 
4177
4720
  // src/cli/pm/runner.ts
4178
4721
  var import_node_child_process2 = __require("child_process");
4722
+ var import_node_http2 = __require("http");
4179
4723
  var import_node_fs4 = __require("fs");
4180
4724
  var import_node_os = __require("os");
4181
4725
  var import_node_path6 = __require("path");
@@ -4409,13 +4953,13 @@ error: ${text}`);
4409
4953
  function join5(...paths) {
4410
4954
  return joinPaths(paths, isWindows);
4411
4955
  }
4412
- function resolve4(...paths) {
4956
+ function resolve5(...paths) {
4413
4957
  return resolvePaths(paths, isWindows);
4414
4958
  }
4415
4959
  function relative(from, to) {
4416
4960
  return relativePath(from, to, isWindows);
4417
4961
  }
4418
- function dirname2(path) {
4962
+ function dirname3(path) {
4419
4963
  return getDirname(path, isWindows);
4420
4964
  }
4421
4965
 
@@ -4453,14 +4997,14 @@ error: ${text}`);
4453
4997
  }
4454
4998
  if (normalizedPattern && existsSync4(normalizedPattern)) {
4455
4999
  try {
4456
- return statSync2(normalizedPattern).isDirectory() ? normalizedPattern : normalizePath2(dirname2(normalizedPattern)) || ".";
5000
+ return statSync2(normalizedPattern).isDirectory() ? normalizedPattern : normalizePath2(dirname3(normalizedPattern)) || ".";
4457
5001
  } catch {
4458
- return normalizePath2(dirname2(normalizedPattern)) || ".";
5002
+ return normalizePath2(dirname3(normalizedPattern)) || ".";
4459
5003
  }
4460
5004
  }
4461
5005
  const lastSegment = parts[parts.length - 1] || "";
4462
5006
  if (lastSegment.includes(".") && !lastSegment.startsWith(".")) {
4463
- return normalizePath2(dirname2(normalizedPattern)) || ".";
5007
+ return normalizePath2(dirname3(normalizedPattern)) || ".";
4464
5008
  }
4465
5009
  return normalizedPattern || ".";
4466
5010
  }
@@ -4655,9 +5199,210 @@ error: ${text}`);
4655
5199
  return watcher;
4656
5200
  }
4657
5201
 
5202
+ // src/cli/pm/proxy.ts
5203
+ var import_node_http = __require("http");
5204
+ var import_node_https = __require("https");
5205
+ var import_node_net = __require("net");
5206
+ function resolvePmProxyHost(proxy) {
5207
+ return proxy.host?.trim() || "0.0.0.0";
5208
+ }
5209
+ function resolvePmProxyTargetHost(proxy) {
5210
+ return proxy.targetHost?.trim() || "127.0.0.1";
5211
+ }
5212
+ function resolvePmProxyEnvVar(proxy) {
5213
+ return proxy.envVar?.trim() || "PORT";
5214
+ }
5215
+ function buildPmProxyTargetUrl(proxy, targetPort) {
5216
+ return `http://${resolvePmProxyTargetHost(proxy)}:${targetPort}`;
5217
+ }
5218
+ function rewritePmProxyHealthCheckUrl(url, targetHost, targetPort) {
5219
+ const targetUrl = new URL(url);
5220
+ targetUrl.hostname = targetHost;
5221
+ targetUrl.port = String(targetPort);
5222
+ return targetUrl.toString();
5223
+ }
5224
+ async function allocatePmProxyTargetPort(host = "127.0.0.1") {
5225
+ const server = (0, import_node_net.createServer)();
5226
+ return await new Promise((resolve7, reject) => {
5227
+ server.once("error", reject);
5228
+ server.listen(0, host, () => {
5229
+ const address = server.address();
5230
+ if (!address || typeof address === "string") {
5231
+ server.close(() => reject(new Error("Failed to allocate an internal PM proxy port.")));
5232
+ return;
5233
+ }
5234
+ const port = address.port;
5235
+ server.close((error) => {
5236
+ if (error) {
5237
+ reject(error);
5238
+ return;
5239
+ }
5240
+ resolve7(port);
5241
+ });
5242
+ });
5243
+ });
5244
+ }
5245
+ function buildPmProxyHeaders(headersInput, host) {
5246
+ const headers = {};
5247
+ for (const [key, value] of Object.entries(headersInput)) {
5248
+ if (value !== void 0) {
5249
+ headers[key] = value;
5250
+ }
5251
+ }
5252
+ headers.host = host;
5253
+ return headers;
5254
+ }
5255
+ function writeRawHttpResponse(socket, statusCode, statusMessage, headers) {
5256
+ const lines = [`HTTP/1.1 ${statusCode} ${statusMessage}`];
5257
+ for (const [key, value] of Object.entries(headers)) {
5258
+ if (value === void 0) {
5259
+ continue;
5260
+ }
5261
+ if (Array.isArray(value)) {
5262
+ for (const item of value) {
5263
+ lines.push(`${key}: ${item}`);
5264
+ }
5265
+ continue;
5266
+ }
5267
+ lines.push(`${key}: ${value}`);
5268
+ }
5269
+ socket.write(`${lines.join("\r\n")}\r
5270
+ \r
5271
+ `);
5272
+ }
5273
+ async function createPmProxyController(proxy) {
5274
+ let targets = [];
5275
+ let nextTargetIndex = 0;
5276
+ const setResolvedTargets = (nextTargets) => {
5277
+ const unchanged = nextTargets.length === targets.length && nextTargets.every((target, index) => targets[index]?.href === target.href);
5278
+ targets = nextTargets;
5279
+ if (targets.length === 0) {
5280
+ nextTargetIndex = 0;
5281
+ return;
5282
+ }
5283
+ if (!unchanged) {
5284
+ nextTargetIndex = nextTargetIndex % targets.length;
5285
+ }
5286
+ };
5287
+ const pickTarget = () => {
5288
+ if (targets.length === 0) {
5289
+ return null;
5290
+ }
5291
+ const target = targets[nextTargetIndex % targets.length];
5292
+ nextTargetIndex = (nextTargetIndex + 1) % targets.length;
5293
+ return target;
5294
+ };
5295
+ const server = (0, import_node_http.createServer)((req, res) => {
5296
+ const target = pickTarget();
5297
+ if (!target) {
5298
+ res.statusCode = 503;
5299
+ res.end("PM proxy target is not ready.");
5300
+ return;
5301
+ }
5302
+ const requestLib = target.protocol === "https:" ? import_node_https.request : import_node_http.request;
5303
+ const targetUrl = new URL(req.url || "/", target);
5304
+ const headers = buildPmProxyHeaders(req.headers, target.host);
5305
+ const proxyReq = requestLib(targetUrl, {
5306
+ method: req.method,
5307
+ headers
5308
+ }, (proxyRes) => {
5309
+ const outgoingHeaders = {};
5310
+ for (const [key, value] of Object.entries(proxyRes.headers)) {
5311
+ if (value !== void 0) {
5312
+ outgoingHeaders[key] = value;
5313
+ }
5314
+ }
5315
+ res.writeHead(proxyRes.statusCode || 200, outgoingHeaders);
5316
+ proxyRes.pipe(res);
5317
+ });
5318
+ proxyReq.on("error", (error) => {
5319
+ if (!res.headersSent) {
5320
+ res.statusCode = 502;
5321
+ }
5322
+ res.end(`PM proxy error: ${error.message}`);
5323
+ });
5324
+ req.pipe(proxyReq);
5325
+ });
5326
+ server.on("upgrade", (req, socket, head) => {
5327
+ const target = pickTarget();
5328
+ if (!target) {
5329
+ writeRawHttpResponse(socket, 503, "Service Unavailable", {
5330
+ connection: "close",
5331
+ "content-length": 0
5332
+ });
5333
+ socket.destroy();
5334
+ return;
5335
+ }
5336
+ const requestLib = target.protocol === "https:" ? import_node_https.request : import_node_http.request;
5337
+ const targetUrl = new URL(req.url || "/", target);
5338
+ const proxyReq = requestLib(targetUrl, {
5339
+ method: req.method,
5340
+ headers: buildPmProxyHeaders(req.headers, target.host)
5341
+ });
5342
+ proxyReq.on("upgrade", (proxyRes, proxySocket, proxyHead) => {
5343
+ writeRawHttpResponse(socket, proxyRes.statusCode || 101, proxyRes.statusMessage || "Switching Protocols", proxyRes.headers);
5344
+ if (head.length > 0) {
5345
+ proxySocket.write(head);
5346
+ }
5347
+ if (proxyHead.length > 0) {
5348
+ socket.write(proxyHead);
5349
+ }
5350
+ socket.on("error", () => proxySocket.destroy());
5351
+ proxySocket.on("error", () => socket.destroy());
5352
+ proxySocket.pipe(socket);
5353
+ socket.pipe(proxySocket);
5354
+ });
5355
+ proxyReq.on("response", (proxyRes) => {
5356
+ writeRawHttpResponse(socket, proxyRes.statusCode || 502, proxyRes.statusMessage || "Bad Gateway", proxyRes.headers);
5357
+ proxyRes.pipe(socket);
5358
+ });
5359
+ proxyReq.on("error", (error) => {
5360
+ writeRawHttpResponse(socket, 502, "Bad Gateway", {
5361
+ connection: "close",
5362
+ "content-type": "text/plain; charset=utf-8",
5363
+ "content-length": Buffer.byteLength(`PM proxy error: ${error.message}`)
5364
+ });
5365
+ socket.end(`PM proxy error: ${error.message}`);
5366
+ });
5367
+ proxyReq.end();
5368
+ });
5369
+ await new Promise((resolve7, reject) => {
5370
+ server.once("error", reject);
5371
+ server.listen(proxy.port, resolvePmProxyHost(proxy), () => resolve7());
5372
+ });
5373
+ return {
5374
+ setTarget(targetUrl) {
5375
+ setResolvedTargets(targetUrl ? [new URL(targetUrl)] : []);
5376
+ },
5377
+ setTargets(targetUrls) {
5378
+ setResolvedTargets(targetUrls.map((targetUrl) => new URL(targetUrl)));
5379
+ },
5380
+ close() {
5381
+ return new Promise((resolve7, reject) => {
5382
+ server.close((error) => {
5383
+ if (error) {
5384
+ reject(error);
5385
+ return;
5386
+ }
5387
+ resolve7();
5388
+ });
5389
+ });
5390
+ }
5391
+ };
5392
+ }
5393
+
4658
5394
  // src/cli/pm/runner.ts
4659
5395
  function writePmLog(stream, message) {
4660
- stream.write(`[elit pm] ${(/* @__PURE__ */ new Date()).toISOString()} ${message}${import_node_os.EOL}`);
5396
+ if (stream.writableEnded || stream.destroyed) {
5397
+ return;
5398
+ }
5399
+ try {
5400
+ stream.write(`[elit pm] ${(/* @__PURE__ */ new Date()).toISOString()} ${message}${import_node_os.EOL}`);
5401
+ } catch (error) {
5402
+ if (error.code !== "ERR_STREAM_WRITE_AFTER_END") {
5403
+ throw error;
5404
+ }
5405
+ }
4661
5406
  }
4662
5407
  function waitForExit(code, signal) {
4663
5408
  if (typeof code === "number") {
@@ -4671,38 +5416,367 @@ error: ${text}`);
4671
5416
  async function delay(milliseconds) {
4672
5417
  await new Promise((resolvePromise) => setTimeout(resolvePromise, milliseconds));
4673
5418
  }
4674
- async function waitForProcessTermination(pid, timeoutMs) {
4675
- if (!pid || !isProcessAlive(pid)) {
4676
- return true;
4677
- }
4678
- const deadline = Date.now() + timeoutMs;
4679
- while (Date.now() < deadline) {
4680
- if (!isProcessAlive(pid)) {
4681
- return true;
4682
- }
4683
- await delay(DEFAULT_PM_STOP_POLL_MS);
4684
- }
4685
- return !isProcessAlive(pid);
5419
+ function resolveRunnerPathsFromRecordFile(filePath) {
5420
+ const appsDir = (0, import_node_path6.dirname)(filePath);
5421
+ const dataDir = (0, import_node_path6.dirname)(appsDir);
5422
+ return {
5423
+ dataDir,
5424
+ appsDir,
5425
+ logsDir: (0, import_node_path6.join)(dataDir, "logs"),
5426
+ dumpFile: (0, import_node_path6.join)(dataDir, DEFAULT_PM_DUMP_FILE)
5427
+ };
4686
5428
  }
4687
- async function waitForManagedChildExit(child) {
4688
- return await new Promise((resolvePromise) => {
4689
- let resolved = false;
4690
- child.once("error", (error) => {
4691
- if (resolved) {
4692
- return;
4693
- }
4694
- resolved = true;
4695
- resolvePromise({ code: 1, signal: null, error: error instanceof Error ? error.message : String(error) });
4696
- });
4697
- child.once("close", (code, signal) => {
4698
- if (resolved) {
4699
- return;
4700
- }
4701
- resolved = true;
4702
- resolvePromise({ code, signal });
5429
+ function usesPmProxyController(record) {
5430
+ return Boolean(record.proxy) && (record.proxy?.strategy ?? DEFAULT_PM_PROXY_STRATEGY) === "proxy";
5431
+ }
5432
+ function usesPmInheritedListener(record) {
5433
+ return Boolean(record.proxy) && (record.proxy?.strategy ?? DEFAULT_PM_PROXY_STRATEGY) === "inherit";
5434
+ }
5435
+ function isPmProxyOwner(record) {
5436
+ return usesPmProxyController(record) && record.instanceIndex === 1;
5437
+ }
5438
+ function resolvePmProxyTargetUrls(paths, baseName) {
5439
+ return listPmRecordMatches(paths).filter((match) => match.record.baseName === baseName).filter((match) => usesPmProxyController(match.record)).filter((match) => match.record.desiredState === "running" && Boolean(match.record.proxyReadyAt)).filter((match) => typeof match.record.proxyTargetPort === "number" && match.record.proxyTargetPort > 0).sort((left, right) => left.record.instanceIndex - right.record.instanceIndex).map((match) => buildPmProxyTargetUrl(match.record.proxy, match.record.proxyTargetPort));
5440
+ }
5441
+ async function createPmInheritedListener(proxy) {
5442
+ const server = (0, import_node_http2.createServer)();
5443
+ await new Promise((resolvePromise, reject) => {
5444
+ server.once("error", reject);
5445
+ server.listen(proxy.port, resolvePmProxyHost(proxy), () => resolvePromise());
5446
+ });
5447
+ return server;
5448
+ }
5449
+ function createPmChildIpcState(child, sharedListener) {
5450
+ let bootstrapReady = false;
5451
+ let listenerReady = false;
5452
+ let sharedHandleSent = false;
5453
+ const sendSharedHandle = () => {
5454
+ if (!sharedListener || !bootstrapReady || sharedHandleSent || !child.connected) {
5455
+ return;
5456
+ }
5457
+ sharedHandleSent = true;
5458
+ child.send?.({ type: "elit:pm:listen-handle" }, sharedListener);
5459
+ };
5460
+ const onMessage = (message) => {
5461
+ if (!message || typeof message !== "object") {
5462
+ return;
5463
+ }
5464
+ if (message.type === "elit:pm:bootstrap-ready") {
5465
+ bootstrapReady = true;
5466
+ sendSharedHandle();
5467
+ return;
5468
+ }
5469
+ if (message.type === "elit:pm:listener-ready") {
5470
+ listenerReady = true;
5471
+ }
5472
+ };
5473
+ child.on("message", onMessage);
5474
+ return {
5475
+ get bootstrapReady() {
5476
+ return bootstrapReady;
5477
+ },
5478
+ get listenerReady() {
5479
+ return listenerReady;
5480
+ },
5481
+ stop() {
5482
+ child.off("message", onMessage);
5483
+ }
5484
+ };
5485
+ }
5486
+ function buildPmChildEnv(record, command, targetPort) {
5487
+ return {
5488
+ ...process.env,
5489
+ ...record.env,
5490
+ ...command.env,
5491
+ ...usesPmProxyController(record) && targetPort ? {
5492
+ [resolvePmProxyEnvVar(record.proxy)]: String(targetPort),
5493
+ ELIT_PM_PUBLIC_PORT: String(record.proxy.port)
5494
+ } : {},
5495
+ ELIT_PM_NAME: record.name,
5496
+ ELIT_PM_ID: record.id
5497
+ };
5498
+ }
5499
+ function buildPmChildStdio(command, onlineStdinShutdownEnabled) {
5500
+ return [
5501
+ onlineStdinShutdownEnabled ? "pipe" : "ignore",
5502
+ "pipe",
5503
+ "pipe",
5504
+ ...command.ipc ? ["ipc"] : []
5505
+ ];
5506
+ }
5507
+ function createPmReadinessMonitor(record, onReady, onFailure, options) {
5508
+ if (options?.ipcController) {
5509
+ let stopped2 = false;
5510
+ let timer2 = null;
5511
+ let timeoutTimer2 = null;
5512
+ const clearTimers2 = () => {
5513
+ if (timer2) {
5514
+ clearInterval(timer2);
5515
+ timer2 = null;
5516
+ }
5517
+ if (timeoutTimer2) {
5518
+ clearTimeout(timeoutTimer2);
5519
+ timeoutTimer2 = null;
5520
+ }
5521
+ };
5522
+ const host = record.proxy?.host ?? "0.0.0.0";
5523
+ const port = record.proxy?.port ?? 0;
5524
+ timer2 = setInterval(() => {
5525
+ if (stopped2 || !options.ipcController?.listenerReady) {
5526
+ return;
5527
+ }
5528
+ stopped2 = true;
5529
+ clearTimers2();
5530
+ onReady(`shared listener ready on ${host}:${port}`);
5531
+ }, 25);
5532
+ timer2.unref?.();
5533
+ timeoutTimer2 = setTimeout(() => {
5534
+ if (stopped2) {
5535
+ return;
5536
+ }
5537
+ stopped2 = true;
5538
+ clearTimers2();
5539
+ onFailure(`listen timeout reached after ${record.listenTimeout}ms while waiting for shared listener ${host}:${port}`);
5540
+ }, record.listenTimeout);
5541
+ timeoutTimer2.unref?.();
5542
+ return {
5543
+ stop() {
5544
+ stopped2 = true;
5545
+ clearTimers2();
5546
+ }
5547
+ };
5548
+ }
5549
+ if (!record.waitReady || !record.healthCheck) {
5550
+ return {
5551
+ stop() {
5552
+ }
5553
+ };
5554
+ }
5555
+ const healthCheck = record.healthCheck;
5556
+ const pollInterval = Math.max(50, Math.min(healthCheck.interval, 250));
5557
+ let stopped = false;
5558
+ let timer = null;
5559
+ let timeoutTimer = null;
5560
+ let inFlight = false;
5561
+ const clearTimers = () => {
5562
+ if (timer) {
5563
+ clearInterval(timer);
5564
+ timer = null;
5565
+ }
5566
+ if (timeoutTimer) {
5567
+ clearTimeout(timeoutTimer);
5568
+ timeoutTimer = null;
5569
+ }
5570
+ };
5571
+ const runHealthCheck = async () => {
5572
+ if (stopped || inFlight) {
5573
+ return;
5574
+ }
5575
+ inFlight = true;
5576
+ const controller = new AbortController();
5577
+ const timeoutId = setTimeout(() => controller.abort(), healthCheck.timeout);
5578
+ timeoutId.unref?.();
5579
+ try {
5580
+ const response = await fetch(healthCheck.url, {
5581
+ method: "GET",
5582
+ signal: controller.signal
5583
+ });
5584
+ if (stopped) {
5585
+ return;
5586
+ }
5587
+ if (!response.ok) {
5588
+ throw new Error(`health check returned ${response.status}`);
5589
+ }
5590
+ stopped = true;
5591
+ clearTimers();
5592
+ onReady(`readiness check passed: ${healthCheck.url}`);
5593
+ } catch {
5594
+ } finally {
5595
+ clearTimeout(timeoutId);
5596
+ inFlight = false;
5597
+ }
5598
+ };
5599
+ timeoutTimer = setTimeout(() => {
5600
+ if (stopped) {
5601
+ return;
5602
+ }
5603
+ stopped = true;
5604
+ clearTimers();
5605
+ onFailure(`listen timeout reached after ${record.listenTimeout}ms while waiting for ${healthCheck.url}`);
5606
+ }, record.listenTimeout);
5607
+ timeoutTimer.unref?.();
5608
+ void runHealthCheck();
5609
+ timer = setInterval(() => {
5610
+ void runHealthCheck();
5611
+ }, pollInterval);
5612
+ timer.unref?.();
5613
+ return {
5614
+ stop() {
5615
+ stopped = true;
5616
+ clearTimers();
5617
+ }
5618
+ };
5619
+ }
5620
+ function resolvePmStopTimeout(record) {
5621
+ if (isPmOnlineWapkRecord(record)) {
5622
+ return Math.max(record.killTimeout, PM_WAPK_ONLINE_SHUTDOWN_TIMEOUT_MS);
5623
+ }
5624
+ return record.killTimeout;
5625
+ }
5626
+ function supportsPmProxyReload(record) {
5627
+ return Boolean(record.proxy) && record.instances === 1;
5628
+ }
5629
+ function buildPmMonitorRecord(record, targetPort) {
5630
+ if (!record.proxy || !targetPort) {
5631
+ return record;
5632
+ }
5633
+ const targetHost = resolvePmProxyTargetHost(record.proxy);
5634
+ return {
5635
+ ...record,
5636
+ proxyTargetPort: targetPort,
5637
+ healthCheck: record.healthCheck ? {
5638
+ ...record.healthCheck,
5639
+ url: rewritePmProxyHealthCheckUrl(record.healthCheck.url, targetHost, targetPort)
5640
+ } : void 0
5641
+ };
5642
+ }
5643
+ async function waitForProcessTermination(pid, timeoutMs) {
5644
+ if (!pid || !isProcessAlive(pid)) {
5645
+ return true;
5646
+ }
5647
+ const deadline = Date.now() + timeoutMs;
5648
+ while (Date.now() < deadline) {
5649
+ if (!isProcessAlive(pid)) {
5650
+ return true;
5651
+ }
5652
+ await delay(DEFAULT_PM_STOP_POLL_MS);
5653
+ }
5654
+ return !isProcessAlive(pid);
5655
+ }
5656
+ async function waitForManagedChildExit(child) {
5657
+ return await new Promise((resolvePromise) => {
5658
+ let resolved = false;
5659
+ child.once("error", (error) => {
5660
+ if (resolved) {
5661
+ return;
5662
+ }
5663
+ resolved = true;
5664
+ resolvePromise({ code: 1, signal: null, error: error instanceof Error ? error.message : String(error) });
5665
+ });
5666
+ child.once("close", (code, signal) => {
5667
+ if (resolved) {
5668
+ return;
5669
+ }
5670
+ resolved = true;
5671
+ resolvePromise({ code, signal });
4703
5672
  });
4704
5673
  });
4705
5674
  }
5675
+ async function createPmChildControllers(record, child, stdoutLog, stderrLog, requestPlannedRestart, onReady, options) {
5676
+ let healthMonitor = {
5677
+ stop() {
5678
+ }
5679
+ };
5680
+ let memoryMonitor = {
5681
+ stop() {
5682
+ }
5683
+ };
5684
+ let scheduleMonitor = {
5685
+ stop() {
5686
+ }
5687
+ };
5688
+ const startHealthMonitor = () => {
5689
+ healthMonitor = createPmHealthMonitor(
5690
+ record,
5691
+ (message) => requestPlannedRestart("health", message),
5692
+ (message) => writePmLog(stdoutLog, message)
5693
+ );
5694
+ };
5695
+ const needsReadySignal = Boolean(options?.ipcController) || record.waitReady;
5696
+ const readinessMonitor = options?.ready || !needsReadySignal ? { stop() {
5697
+ } } : createPmReadinessMonitor(
5698
+ record,
5699
+ (message) => {
5700
+ onReady(message);
5701
+ startHealthMonitor();
5702
+ },
5703
+ (message) => requestPlannedRestart("startup", message),
5704
+ { ipcController: options?.ipcController }
5705
+ );
5706
+ const watchController = await createPmWatchController(
5707
+ record,
5708
+ (changedPath) => requestPlannedRestart("watch", changedPath),
5709
+ (message) => writePmLog(stderrLog, `watch error: ${message}`)
5710
+ );
5711
+ memoryMonitor = createPmMemoryMonitor(
5712
+ record,
5713
+ child.pid,
5714
+ (kind, message) => requestPlannedRestart(kind, message)
5715
+ );
5716
+ scheduleMonitor = createPmScheduleMonitor(
5717
+ record,
5718
+ (message) => requestPlannedRestart("cron", message),
5719
+ (message) => writePmLog(stdoutLog, message)
5720
+ );
5721
+ if (options?.ready || !needsReadySignal) {
5722
+ startHealthMonitor();
5723
+ }
5724
+ return {
5725
+ async stop() {
5726
+ await watchController.close();
5727
+ readinessMonitor.stop();
5728
+ healthMonitor.stop();
5729
+ memoryMonitor.stop();
5730
+ scheduleMonitor.stop();
5731
+ }
5732
+ };
5733
+ }
5734
+ async function waitForPmChildReady(record, child, ipcController) {
5735
+ if (!ipcController && (!record.waitReady || !record.healthCheck)) {
5736
+ return { ready: true };
5737
+ }
5738
+ let readyMessage;
5739
+ let failureMessage;
5740
+ let exitResult;
5741
+ const readinessMonitor = createPmReadinessMonitor(
5742
+ record,
5743
+ (message) => {
5744
+ readyMessage = message;
5745
+ },
5746
+ (message) => {
5747
+ failureMessage = message;
5748
+ },
5749
+ { ipcController }
5750
+ );
5751
+ void waitForManagedChildExit(child).then((result) => {
5752
+ exitResult = result;
5753
+ });
5754
+ while (!readyMessage && !failureMessage && !exitResult) {
5755
+ await delay(25);
5756
+ }
5757
+ readinessMonitor.stop();
5758
+ if (readyMessage) {
5759
+ return { ready: true, message: readyMessage };
5760
+ }
5761
+ return {
5762
+ ready: false,
5763
+ message: failureMessage,
5764
+ exitResult
5765
+ };
5766
+ }
5767
+ async function stopProxyManagedChild(child, record, stderrLog) {
5768
+ if (!child.pid || !isProcessAlive(child.pid)) {
5769
+ return;
5770
+ }
5771
+ terminateProcessTree(child.pid);
5772
+ const stopTimeout = resolvePmStopTimeout(record);
5773
+ const stopped = await waitForProcessTermination(child.pid, stopTimeout);
5774
+ if (!stopped && child.pid && isProcessAlive(child.pid)) {
5775
+ writePmLog(stderrLog, `proxy handoff shutdown timed out after ${stopTimeout}ms; forcing process termination`);
5776
+ terminateProcessTree(child.pid, { force: true });
5777
+ await waitForProcessTermination(child.pid, DEFAULT_PM_STOP_POLL_MS);
5778
+ }
5779
+ }
4706
5780
  async function createPmWatchController(record, onChange, onError) {
4707
5781
  if (!record.watch || record.watchPaths.length === 0) {
4708
5782
  return {
@@ -4766,11 +5840,17 @@ error: ${text}`);
4766
5840
  method: "GET",
4767
5841
  signal: controller.signal
4768
5842
  });
5843
+ if (stopped) {
5844
+ return;
5845
+ }
4769
5846
  if (!response.ok) {
4770
5847
  throw new Error(`health check returned ${response.status}`);
4771
5848
  }
4772
5849
  failureCount = 0;
4773
5850
  } catch (error) {
5851
+ if (stopped) {
5852
+ return;
5853
+ }
4774
5854
  failureCount += 1;
4775
5855
  const message = error instanceof Error ? error.message : String(error);
4776
5856
  onLog(`health check failed (${failureCount}/${healthCheck.maxFailures}): ${message}`);
@@ -4805,6 +5885,90 @@ error: ${text}`);
4805
5885
  }
4806
5886
  };
4807
5887
  }
5888
+ function createPmMemoryMonitor(record, pid, onFailure) {
5889
+ if (!record.maxMemoryBytes || !pid) {
5890
+ return {
5891
+ stop() {
5892
+ }
5893
+ };
5894
+ }
5895
+ const memoryLimit = record.maxMemoryBytes;
5896
+ let stopped = false;
5897
+ const timer = setInterval(() => {
5898
+ if (stopped) {
5899
+ return;
5900
+ }
5901
+ const memoryRssBytes = samplePmProcessMetrics(pid).memoryRssBytes;
5902
+ if (memoryRssBytes === void 0 || memoryRssBytes <= memoryLimit) {
5903
+ return;
5904
+ }
5905
+ stopped = true;
5906
+ onFailure(record.memoryAction === "stop" ? "memory-stop" : "memory", `memory usage ${memoryRssBytes} exceeded limit ${memoryLimit}`);
5907
+ }, DEFAULT_PM_MEMORY_CHECK_INTERVAL);
5908
+ timer.unref?.();
5909
+ return {
5910
+ stop() {
5911
+ stopped = true;
5912
+ clearInterval(timer);
5913
+ }
5914
+ };
5915
+ }
5916
+ function createPmScheduleMonitor(record, onTrigger, onLog) {
5917
+ if (!record.cronRestart) {
5918
+ return {
5919
+ stop() {
5920
+ }
5921
+ };
5922
+ }
5923
+ const schedule = parsePmRestartSchedule(record.cronRestart, "pm cronRestart");
5924
+ let stopped = false;
5925
+ let timer = null;
5926
+ const armTimer = (from = /* @__PURE__ */ new Date()) => {
5927
+ const nextOccurrence = resolveNextPmScheduleOccurrence(schedule, from);
5928
+ if (!nextOccurrence) {
5929
+ onLog(`schedule has no next occurrence: ${record.cronRestart}`);
5930
+ return;
5931
+ }
5932
+ const delayMs = Math.max(0, nextOccurrence.getTime() - Date.now());
5933
+ timer = setTimeout(() => {
5934
+ timer = null;
5935
+ if (stopped) {
5936
+ return;
5937
+ }
5938
+ onTrigger(`restart schedule matched: ${record.cronRestart}`);
5939
+ }, delayMs);
5940
+ timer.unref?.();
5941
+ };
5942
+ armTimer();
5943
+ return {
5944
+ stop() {
5945
+ stopped = true;
5946
+ if (timer) {
5947
+ clearTimeout(timer);
5948
+ timer = null;
5949
+ }
5950
+ }
5951
+ };
5952
+ }
5953
+ function resolvePmRestartDelay(record, restartCount, shouldApplyBackoff) {
5954
+ if (!shouldApplyBackoff || !record.expBackoffRestartDelay) {
5955
+ return record.restartDelay;
5956
+ }
5957
+ const exponent = Math.max(0, restartCount - 1);
5958
+ return Math.min(record.expBackoffRestartDelay * 2 ** exponent, record.expBackoffRestartMaxDelay ?? DEFAULT_PM_EXP_BACKOFF_MAX_DELAY);
5959
+ }
5960
+ function resolvePmRestartCountBase(record, wasStable, restartKind) {
5961
+ if (wasStable) {
5962
+ return 0;
5963
+ }
5964
+ if (record.restartWindow && record.lastRestartAt) {
5965
+ const lastRestartTime = Date.parse(record.lastRestartAt);
5966
+ if (!Number.isNaN(lastRestartTime) && Date.now() - lastRestartTime > record.restartWindow) {
5967
+ return 0;
5968
+ }
5969
+ }
5970
+ return restartKind === "watch" ? record.restartCount ?? 0 : record.restartCount ?? 0;
5971
+ }
4808
5972
  function readPlannedRestartRequest(state) {
4809
5973
  return state.request;
4810
5974
  }
@@ -4818,6 +5982,16 @@ error: ${text}`);
4818
5982
  (0, import_node_fs4.mkdirSync)((0, import_node_path6.dirname)(initialRecord.logFiles.err), { recursive: true });
4819
5983
  const stdoutLog = (0, import_node_fs4.createWriteStream)(initialRecord.logFiles.out, { flags: "a" });
4820
5984
  const stderrLog = (0, import_node_fs4.createWriteStream)(initialRecord.logFiles.err, { flags: "a" });
5985
+ let proxyController = null;
5986
+ let proxyTargetSyncTimer = null;
5987
+ let inheritedListener = null;
5988
+ const runnerPaths = resolveRunnerPathsFromRecordFile(filePath);
5989
+ const syncOwnedProxyTargets = (baseName) => {
5990
+ if (!proxyController) {
5991
+ return;
5992
+ }
5993
+ proxyController.setTargets(resolvePmProxyTargetUrls(runnerPaths, baseName));
5994
+ };
4821
5995
  const persist = (mutator) => {
4822
5996
  const current = readLatestPmRecord(filePath, record);
4823
5997
  record = mutator(current);
@@ -4830,26 +6004,30 @@ error: ${text}`);
4830
6004
  activeChildStopTimer = null;
4831
6005
  }
4832
6006
  };
6007
+ const scheduleForcedActiveChildStop = (timeoutMs, reason) => {
6008
+ if (!activeChild?.pid || process.platform === "win32") {
6009
+ return;
6010
+ }
6011
+ clearActiveChildStopTimer();
6012
+ activeChildStopTimer = setTimeout(() => {
6013
+ if (activeChild?.pid && isProcessAlive(activeChild.pid)) {
6014
+ writePmLog(stderrLog, `${reason} after ${timeoutMs}ms; forcing process termination`);
6015
+ terminateProcessTree(activeChild.pid, { force: true });
6016
+ }
6017
+ }, timeoutMs);
6018
+ activeChildStopTimer.unref?.();
6019
+ };
4833
6020
  const stopActiveChild = () => {
4834
6021
  if (!activeChild?.pid || !isProcessAlive(activeChild.pid)) {
4835
6022
  return;
4836
6023
  }
4837
6024
  const current = readLatestPmRecord(filePath, record);
6025
+ const stopTimeout = resolvePmStopTimeout(current);
4838
6026
  if (isPmOnlineWapkRecord(current) && activeChild.stdin && !activeChild.stdin.destroyed && activeChild.stdin.writable) {
4839
6027
  try {
4840
6028
  activeChild.stdin.end(`${PM_WAPK_ONLINE_SHUTDOWN_COMMAND}
4841
6029
  `);
4842
- clearActiveChildStopTimer();
4843
- activeChildStopTimer = setTimeout(() => {
4844
- if (activeChild?.pid && isProcessAlive(activeChild.pid)) {
4845
- writePmLog(
4846
- stderrLog,
4847
- `graceful WAPK online shutdown timed out after ${PM_WAPK_ONLINE_SHUTDOWN_TIMEOUT_MS}ms; forcing process termination`
4848
- );
4849
- terminateProcessTree(activeChild.pid);
4850
- }
4851
- }, PM_WAPK_ONLINE_SHUTDOWN_TIMEOUT_MS);
4852
- activeChildStopTimer.unref?.();
6030
+ scheduleForcedActiveChildStop(stopTimeout, "graceful WAPK online shutdown timed out");
4853
6031
  return;
4854
6032
  } catch (error) {
4855
6033
  const message = error instanceof Error ? error.message : String(error);
@@ -4857,6 +6035,7 @@ error: ${text}`);
4857
6035
  }
4858
6036
  }
4859
6037
  terminateProcessTree(activeChild.pid);
6038
+ scheduleForcedActiveChildStop(stopTimeout, "graceful shutdown timed out");
4860
6039
  };
4861
6040
  const requestManagedStop = (reason) => {
4862
6041
  if (stopRequested) {
@@ -4894,6 +6073,17 @@ error: ${text}`);
4894
6073
  let command;
4895
6074
  try {
4896
6075
  command = buildPmCommand(latest);
6076
+ if (latest.proxy && isPmProxyOwner(latest) && !proxyController) {
6077
+ proxyController = await createPmProxyController(latest.proxy);
6078
+ syncOwnedProxyTargets(latest.baseName);
6079
+ if (!proxyTargetSyncTimer) {
6080
+ proxyTargetSyncTimer = setInterval(() => syncOwnedProxyTargets(latest.baseName), 50);
6081
+ proxyTargetSyncTimer.unref?.();
6082
+ }
6083
+ }
6084
+ if (latest.proxy && usesPmInheritedListener(latest) && !inheritedListener) {
6085
+ inheritedListener = await createPmInheritedListener(latest.proxy);
6086
+ }
4897
6087
  } catch (error) {
4898
6088
  const message = error instanceof Error ? error.message : String(error);
4899
6089
  writePmLog(stderrLog, message);
@@ -4903,25 +6093,24 @@ error: ${text}`);
4903
6093
  error: message,
4904
6094
  runnerPid: void 0,
4905
6095
  childPid: void 0,
6096
+ proxyTargetPort: void 0,
6097
+ proxyReadyAt: void 0,
4906
6098
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
4907
6099
  }));
4908
6100
  return;
4909
6101
  }
4910
6102
  const onlineStdinShutdownEnabled = isPmOnlineWapkRecord(latest);
4911
- const child = (0, import_node_child_process2.spawn)(command.command, command.args, {
6103
+ const initialTargetPort = usesPmProxyController(latest) ? await allocatePmProxyTargetPort(resolvePmProxyTargetHost(latest.proxy)) : void 0;
6104
+ const monitorRecord = buildPmMonitorRecord(latest, initialTargetPort);
6105
+ let child = (0, import_node_child_process2.spawn)(command.command, command.args, {
4912
6106
  cwd: latest.cwd,
4913
- env: {
4914
- ...process.env,
4915
- ...latest.env,
4916
- ...command.env,
4917
- ELIT_PM_NAME: latest.name,
4918
- ELIT_PM_ID: latest.id
4919
- },
4920
- stdio: [onlineStdinShutdownEnabled ? "pipe" : "ignore", "pipe", "pipe"],
6107
+ env: buildPmChildEnv(latest, command, initialTargetPort),
6108
+ stdio: buildPmChildStdio(command, onlineStdinShutdownEnabled),
4921
6109
  windowsHide: true,
4922
6110
  shell: command.shell
4923
6111
  });
4924
- const childStartedAt = Date.now();
6112
+ let childStartedAt = Date.now();
6113
+ let childIpcState = command.ipc ? createPmChildIpcState(child, inheritedListener) : void 0;
4925
6114
  activeChild = child;
4926
6115
  if (child.stdout) {
4927
6116
  child.stdout.pipe(stdoutLog, { end: false });
@@ -4929,26 +6118,38 @@ error: ${text}`);
4929
6118
  if (child.stderr) {
4930
6119
  child.stderr.pipe(stderrLog, { end: false });
4931
6120
  }
6121
+ let childWaitState = { settled: false, result: void 0 };
6122
+ void waitForManagedChildExit(child).then((result) => {
6123
+ childWaitState.result = result;
6124
+ childWaitState.settled = true;
6125
+ });
4932
6126
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
6127
+ const waitingForReady = latest.waitReady || Boolean(childIpcState);
4933
6128
  persist((current2) => ({
4934
6129
  ...current2,
4935
- status: "online",
6130
+ status: waitingForReady ? "starting" : "online",
4936
6131
  commandPreview: command.preview,
4937
6132
  runtime: command.runtime ?? current2.runtime,
4938
6133
  runnerPid: process.pid,
4939
6134
  childPid: child.pid,
6135
+ proxyTargetPort: initialTargetPort,
6136
+ proxyReadyAt: !waitingForReady && usesPmProxyController(latest) ? startedAt : void 0,
4940
6137
  startedAt,
4941
6138
  stoppedAt: void 0,
6139
+ reloadRequestedAt: void 0,
4942
6140
  error: void 0,
4943
6141
  updatedAt: startedAt
4944
6142
  }));
4945
6143
  writePmLog(stdoutLog, `started ${command.preview}${child.pid ? ` (pid ${child.pid})` : ""}`);
6144
+ if (isPmProxyOwner(latest) && !waitingForReady) {
6145
+ syncOwnedProxyTargets(latest.baseName);
6146
+ }
4946
6147
  const requestPlannedRestart = (kind, detail) => {
4947
6148
  if (stopRequested || restartState.request) {
4948
6149
  return;
4949
6150
  }
4950
6151
  restartState.request = { kind, detail };
4951
- writePmLog(kind === "health" ? stderrLog : stdoutLog, `${kind} restart requested: ${detail}`);
6152
+ writePmLog(kind === "watch" || kind === "cron" ? stdoutLog : stderrLog, `${kind} restart requested: ${detail}`);
4952
6153
  persist((current2) => ({
4953
6154
  ...current2,
4954
6155
  status: "restarting",
@@ -4956,27 +6157,124 @@ error: ${text}`);
4956
6157
  }));
4957
6158
  stopActiveChild();
4958
6159
  };
4959
- const watchController = await createPmWatchController(
4960
- latest,
4961
- (changedPath) => requestPlannedRestart("watch", changedPath),
4962
- (message) => writePmLog(stderrLog, `watch error: ${message}`)
4963
- );
4964
- const healthMonitor = createPmHealthMonitor(
4965
- latest,
4966
- (message) => requestPlannedRestart("health", message),
4967
- (message) => writePmLog(stdoutLog, message)
6160
+ let controllers = await createPmChildControllers(
6161
+ monitorRecord,
6162
+ child,
6163
+ stdoutLog,
6164
+ stderrLog,
6165
+ requestPlannedRestart,
6166
+ (message) => {
6167
+ const readyAt = (/* @__PURE__ */ new Date()).toISOString();
6168
+ if (isPmProxyOwner(latest)) {
6169
+ syncOwnedProxyTargets(latest.baseName);
6170
+ }
6171
+ persist((current2) => ({
6172
+ ...current2,
6173
+ status: "online",
6174
+ proxyTargetPort: initialTargetPort,
6175
+ proxyReadyAt: readyAt,
6176
+ updatedAt: readyAt
6177
+ }));
6178
+ writePmLog(stdoutLog, message);
6179
+ },
6180
+ { ready: !waitingForReady, ipcController: childIpcState }
4968
6181
  );
4969
- const desiredStatePoller = setInterval(() => {
6182
+ let handledReloadAt = latest.reloadRequestedAt;
6183
+ while (!childWaitState.settled) {
4970
6184
  const latestRecord = readLatestPmRecord(filePath, record);
4971
- if (!stopRequested && latestRecord.desiredState === "stopped") {
6185
+ if (latestRecord.desiredState === "stopped" && !stopRequested) {
4972
6186
  requestManagedStop("stop requested by PM control state");
4973
6187
  }
4974
- }, DEFAULT_PM_STOP_POLL_MS);
4975
- desiredStatePoller.unref?.();
4976
- const exitResult = await waitForManagedChildExit(child);
4977
- clearInterval(desiredStatePoller);
4978
- await watchController.close();
4979
- healthMonitor.stop();
6188
+ const reloadRequestedAt = latestRecord.reloadRequestedAt;
6189
+ if (!stopRequested && supportsPmProxyReload(latestRecord) && reloadRequestedAt && reloadRequestedAt !== handledReloadAt && latestRecord.proxy) {
6190
+ handledReloadAt = reloadRequestedAt;
6191
+ const replacementTargetPort = usesPmProxyController(latestRecord) ? await allocatePmProxyTargetPort(resolvePmProxyTargetHost(latestRecord.proxy)) : void 0;
6192
+ const replacementMonitorRecord = buildPmMonitorRecord(latestRecord, replacementTargetPort);
6193
+ const replacementChild = (0, import_node_child_process2.spawn)(command.command, command.args, {
6194
+ cwd: latestRecord.cwd,
6195
+ env: buildPmChildEnv(latestRecord, command, replacementTargetPort),
6196
+ stdio: buildPmChildStdio(command, onlineStdinShutdownEnabled),
6197
+ windowsHide: true,
6198
+ shell: command.shell
6199
+ });
6200
+ const replacementIpcState = command.ipc ? createPmChildIpcState(replacementChild, inheritedListener) : void 0;
6201
+ if (replacementChild.stdout) {
6202
+ replacementChild.stdout.pipe(stdoutLog, { end: false });
6203
+ }
6204
+ if (replacementChild.stderr) {
6205
+ replacementChild.stderr.pipe(stderrLog, { end: false });
6206
+ }
6207
+ writePmLog(stdoutLog, `starting ${usesPmInheritedListener(latestRecord) ? "shared-listener" : "proxy handoff"} replacement${replacementChild.pid ? ` (pid ${replacementChild.pid})` : ""}`);
6208
+ const readyResult = await waitForPmChildReady(replacementMonitorRecord, replacementChild, replacementIpcState);
6209
+ if (!readyResult.ready) {
6210
+ writePmLog(stderrLog, readyResult.message ?? "replacement exited before becoming ready");
6211
+ replacementIpcState?.stop();
6212
+ await stopProxyManagedChild(replacementChild, latestRecord, stderrLog);
6213
+ persist((current2) => ({
6214
+ ...current2,
6215
+ status: "online",
6216
+ proxyReadyAt: current2.proxyReadyAt,
6217
+ reloadRequestedAt: void 0,
6218
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
6219
+ }));
6220
+ continue;
6221
+ }
6222
+ const previousChild = child;
6223
+ const previousControllers = controllers;
6224
+ const previousIpcState = childIpcState;
6225
+ const handoffAt = (/* @__PURE__ */ new Date()).toISOString();
6226
+ if (usesPmProxyController(latestRecord) && replacementTargetPort) {
6227
+ proxyController?.setTarget(buildPmProxyTargetUrl(latestRecord.proxy, replacementTargetPort));
6228
+ }
6229
+ persist((current2) => ({
6230
+ ...current2,
6231
+ status: "online",
6232
+ childPid: replacementChild.pid,
6233
+ proxyTargetPort: replacementTargetPort,
6234
+ proxyReadyAt: handoffAt,
6235
+ startedAt: handoffAt,
6236
+ reloadRequestedAt: void 0,
6237
+ error: void 0,
6238
+ updatedAt: handoffAt
6239
+ }));
6240
+ if (readyResult.message) {
6241
+ writePmLog(stdoutLog, readyResult.message);
6242
+ }
6243
+ writePmLog(stdoutLog, `${usesPmInheritedListener(latestRecord) ? "shared listener" : "proxy handoff"} activated on ${latestRecord.proxy.host ?? "0.0.0.0"}:${latestRecord.proxy.port}`);
6244
+ child = replacementChild;
6245
+ childIpcState = replacementIpcState;
6246
+ childStartedAt = Date.now();
6247
+ activeChild = replacementChild;
6248
+ childWaitState = { settled: false, result: void 0 };
6249
+ void waitForManagedChildExit(replacementChild).then((result) => {
6250
+ childWaitState.result = result;
6251
+ childWaitState.settled = true;
6252
+ });
6253
+ controllers = await createPmChildControllers(
6254
+ replacementMonitorRecord,
6255
+ replacementChild,
6256
+ stdoutLog,
6257
+ stderrLog,
6258
+ requestPlannedRestart,
6259
+ () => {
6260
+ },
6261
+ { ready: true, ipcController: replacementIpcState }
6262
+ );
6263
+ await delay(250);
6264
+ await previousControllers.stop();
6265
+ previousIpcState?.stop();
6266
+ await stopProxyManagedChild(previousChild, latestRecord, stderrLog);
6267
+ clearActiveChildStopTimer();
6268
+ if (isPmProxyOwner(latestRecord)) {
6269
+ syncOwnedProxyTargets(latestRecord.baseName);
6270
+ }
6271
+ continue;
6272
+ }
6273
+ await delay(25);
6274
+ }
6275
+ const exitResult = childWaitState.result;
6276
+ await controllers.stop();
6277
+ childIpcState?.stop();
4980
6278
  clearActiveChildStopTimer();
4981
6279
  activeChild = null;
4982
6280
  const exitCode = waitForExit(exitResult.code, exitResult.signal);
@@ -4992,56 +6290,77 @@ error: ${text}`);
4992
6290
  if (stopRequested || current.desiredState === "stopped") {
4993
6291
  break;
4994
6292
  }
4995
- const shouldRestartForExit = plannedRestart ? true : current.restartPolicy === "always" ? true : current.restartPolicy === "on-failure" ? exitCode !== 0 || Boolean(exitResult.error) : false;
6293
+ const shouldRestartForExit = plannedRestart?.kind === "memory-stop" ? false : plannedRestart ? true : current.restartPolicy === "always" ? true : current.restartPolicy === "on-failure" ? exitCode !== 0 || Boolean(exitResult.error) : false;
4996
6294
  if (!shouldRestartForExit) {
4997
6295
  persist((latestRecord) => ({
4998
6296
  ...latestRecord,
4999
- status: exitCode === 0 && !exitResult.error ? "exited" : "errored",
6297
+ status: plannedRestart?.kind === "memory-stop" ? "errored" : exitCode === 0 && !exitResult.error ? "exited" : "errored",
5000
6298
  childPid: void 0,
6299
+ proxyTargetPort: void 0,
6300
+ proxyReadyAt: void 0,
5001
6301
  runnerPid: void 0,
5002
6302
  lastExitCode: exitCode,
5003
- error: exitCode === 0 && !exitResult.error ? void 0 : exitResult.error ?? `Process exited with code ${exitCode}.`,
6303
+ reloadRequestedAt: void 0,
6304
+ error: plannedRestart?.kind === "memory-stop" ? plannedRestart.detail : exitCode === 0 && !exitResult.error ? void 0 : exitResult.error ?? `Process exited with code ${exitCode}.`,
5004
6305
  stoppedAt: (/* @__PURE__ */ new Date()).toISOString(),
5005
6306
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
5006
6307
  }));
6308
+ if (isPmProxyOwner(current)) {
6309
+ syncOwnedProxyTargets(current.baseName);
6310
+ }
5007
6311
  return;
5008
6312
  }
5009
6313
  const shouldCountRestart = plannedRestart?.kind !== "watch";
5010
- const baseRestartCount = wasStable ? 0 : current.restartCount ?? 0;
6314
+ const baseRestartCount = resolvePmRestartCountBase(current, wasStable, plannedRestart?.kind);
5011
6315
  const nextRestartCount = shouldCountRestart ? baseRestartCount + 1 : current.restartCount ?? 0;
5012
6316
  if (nextRestartCount > current.maxRestarts) {
5013
6317
  persist((latestRecord) => ({
5014
6318
  ...latestRecord,
5015
6319
  status: "errored",
5016
6320
  childPid: void 0,
6321
+ proxyTargetPort: void 0,
6322
+ proxyReadyAt: void 0,
5017
6323
  runnerPid: void 0,
5018
6324
  restartCount: nextRestartCount,
6325
+ lastRestartAt: (/* @__PURE__ */ new Date()).toISOString(),
5019
6326
  lastExitCode: exitCode,
6327
+ reloadRequestedAt: void 0,
5020
6328
  error: plannedRestart ? `Reached max restart attempts (${current.maxRestarts}) after ${plannedRestart.kind} restart requests.` : `Reached max restart attempts (${current.maxRestarts}).`,
5021
6329
  stoppedAt: (/* @__PURE__ */ new Date()).toISOString(),
5022
6330
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
5023
6331
  }));
5024
6332
  writePmLog(stderrLog, `max restart attempts reached (${current.maxRestarts})`);
6333
+ if (isPmProxyOwner(current)) {
6334
+ syncOwnedProxyTargets(current.baseName);
6335
+ }
5025
6336
  return;
5026
6337
  }
5027
6338
  persist((latestRecord) => ({
5028
6339
  ...latestRecord,
5029
6340
  status: "restarting",
5030
6341
  childPid: void 0,
6342
+ proxyTargetPort: void 0,
6343
+ proxyReadyAt: void 0,
5031
6344
  lastExitCode: exitCode,
5032
6345
  restartCount: nextRestartCount,
6346
+ lastRestartAt: (/* @__PURE__ */ new Date()).toISOString(),
6347
+ reloadRequestedAt: void 0,
5033
6348
  error: void 0,
5034
6349
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
5035
6350
  }));
6351
+ if (isPmProxyOwner(current)) {
6352
+ syncOwnedProxyTargets(current.baseName);
6353
+ }
6354
+ const resolvedRestartDelay = resolvePmRestartDelay(current, nextRestartCount, shouldCountRestart && !wasStable && plannedRestart?.kind !== "watch");
5036
6355
  if (plannedRestart) {
5037
6356
  writePmLog(
5038
- plannedRestart.kind === "health" ? stderrLog : stdoutLog,
5039
- `restarting in ${current.restartDelay}ms after ${plannedRestart.kind}: ${plannedRestart.detail}`
6357
+ plannedRestart.kind === "health" || plannedRestart.kind === "memory" || plannedRestart.kind === "memory-stop" || plannedRestart.kind === "startup" ? stderrLog : stdoutLog,
6358
+ `restarting in ${resolvedRestartDelay}ms after ${plannedRestart.kind}: ${plannedRestart.detail}`
5040
6359
  );
5041
6360
  } else {
5042
- writePmLog(stdoutLog, `restarting in ${current.restartDelay}ms`);
6361
+ writePmLog(stdoutLog, `restarting in ${resolvedRestartDelay}ms`);
5043
6362
  }
5044
- await delay(current.restartDelay);
6363
+ await delay(resolvedRestartDelay);
5045
6364
  }
5046
6365
  } finally {
5047
6366
  stopRequested = true;
@@ -5054,11 +6373,26 @@ error: ${text}`);
5054
6373
  status: finalRecord.status === "errored" ? "errored" : finalRecord.status === "exited" ? "exited" : "stopped",
5055
6374
  runnerPid: void 0,
5056
6375
  childPid: void 0,
6376
+ proxyTargetPort: void 0,
6377
+ proxyReadyAt: void 0,
6378
+ reloadRequestedAt: void 0,
5057
6379
  stoppedAt: (/* @__PURE__ */ new Date()).toISOString(),
5058
6380
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
5059
6381
  });
5060
6382
  process.off("SIGINT", handleStopSignal);
5061
6383
  process.off("SIGTERM", handleStopSignal);
6384
+ if (proxyTargetSyncTimer) {
6385
+ clearInterval(proxyTargetSyncTimer);
6386
+ proxyTargetSyncTimer = null;
6387
+ }
6388
+ if (proxyController) {
6389
+ await proxyController.close().catch(() => void 0);
6390
+ proxyController = null;
6391
+ }
6392
+ if (inheritedListener) {
6393
+ await new Promise((resolvePromise) => inheritedListener?.close(() => resolvePromise()));
6394
+ inheritedListener = null;
6395
+ }
5062
6396
  await new Promise((resolvePromise) => stdoutLog.end(resolvePromise));
5063
6397
  await new Promise((resolvePromise) => stderrLog.end(resolvePromise));
5064
6398
  }
@@ -5112,23 +6446,26 @@ error: ${text}`);
5112
6446
  stoppedAt: (/* @__PURE__ */ new Date()).toISOString()
5113
6447
  };
5114
6448
  writePmRecord(match.filePath, updated);
5115
- const runnerStopped = await waitForProcessTermination(match.record.runnerPid, DEFAULT_PM_STOP_GRACE_PERIOD_MS);
6449
+ const stopTimeout = resolvePmStopTimeout(match.record);
6450
+ const runnerStopped = await waitForProcessTermination(match.record.runnerPid, stopTimeout);
5116
6451
  const childStopped = await waitForProcessTermination(
5117
6452
  match.record.childPid,
5118
- runnerStopped ? DEFAULT_PM_STOP_POLL_MS : DEFAULT_PM_STOP_GRACE_PERIOD_MS
6453
+ runnerStopped ? DEFAULT_PM_STOP_POLL_MS : stopTimeout
5119
6454
  );
5120
6455
  if (!runnerStopped && match.record.runnerPid && isProcessAlive(match.record.runnerPid)) {
5121
- terminateProcessTree(match.record.runnerPid);
6456
+ terminateProcessTree(match.record.runnerPid, { force: true });
5122
6457
  await waitForProcessTermination(match.record.runnerPid, DEFAULT_PM_STOP_POLL_MS);
5123
6458
  }
5124
6459
  if (!childStopped && match.record.childPid && isProcessAlive(match.record.childPid)) {
5125
- terminateProcessTree(match.record.childPid);
6460
+ terminateProcessTree(match.record.childPid, { force: true });
5126
6461
  await waitForProcessTermination(match.record.childPid, DEFAULT_PM_STOP_POLL_MS);
5127
6462
  }
5128
6463
  writePmRecord(match.filePath, {
5129
6464
  ...updated,
5130
6465
  runnerPid: void 0,
5131
6466
  childPid: void 0,
6467
+ proxyTargetPort: void 0,
6468
+ reloadRequestedAt: void 0,
5132
6469
  status: "stopped",
5133
6470
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
5134
6471
  });
@@ -5197,13 +6534,13 @@ error: ${text}`);
5197
6534
 
5198
6535
  // src/shares/workspace-package/roots.ts
5199
6536
  function findPackageDirectory(startDir, resolveMatch) {
5200
- let currentDir = resolve4(startDir);
6537
+ let currentDir = resolve5(startDir);
5201
6538
  while (true) {
5202
6539
  const match = resolveMatch(currentDir);
5203
6540
  if (match) {
5204
6541
  return match;
5205
6542
  }
5206
- const parentDir = dirname2(currentDir);
6543
+ const parentDir = dirname3(currentDir);
5207
6544
  if (parentDir === currentDir) {
5208
6545
  return void 0;
5209
6546
  }
@@ -5231,17 +6568,17 @@ error: ${text}`);
5231
6568
  function getWorkspacePackageImportCandidates(packageRoot, specifier, options = {}) {
5232
6569
  const subpath = specifier === "elit" ? "index" : specifier.slice("elit/".length);
5233
6570
  const builtCandidates = options.preferredBuiltFormat === "cjs" ? [
5234
- resolve4(packageRoot, "dist", `${subpath}.cjs`),
5235
- resolve4(packageRoot, "dist", `${subpath}.js`),
5236
- resolve4(packageRoot, "dist", `${subpath}.mjs`)
6571
+ resolve5(packageRoot, "dist", `${subpath}.cjs`),
6572
+ resolve5(packageRoot, "dist", `${subpath}.js`),
6573
+ resolve5(packageRoot, "dist", `${subpath}.mjs`)
5237
6574
  ] : [
5238
- resolve4(packageRoot, "dist", `${subpath}.mjs`),
5239
- resolve4(packageRoot, "dist", `${subpath}.js`),
5240
- resolve4(packageRoot, "dist", `${subpath}.cjs`)
6575
+ resolve5(packageRoot, "dist", `${subpath}.mjs`),
6576
+ resolve5(packageRoot, "dist", `${subpath}.js`),
6577
+ resolve5(packageRoot, "dist", `${subpath}.cjs`)
5241
6578
  ];
5242
6579
  const sourceCandidates = [
5243
- resolve4(packageRoot, "src", `${subpath}.ts`),
5244
- resolve4(packageRoot, "src", `${subpath}.tsx`)
6580
+ resolve5(packageRoot, "src", `${subpath}.ts`),
6581
+ resolve5(packageRoot, "src", `${subpath}.tsx`)
5245
6582
  ];
5246
6583
  return options.preferBuilt ? [...builtCandidates, ...sourceCandidates] : [...sourceCandidates, ...builtCandidates];
5247
6584
  }
@@ -5276,7 +6613,7 @@ error: ${text}`);
5276
6613
  // src/shares/config/loader.ts
5277
6614
  function resolveConfigPath(cwd = process.cwd()) {
5278
6615
  for (const configFile of ELIT_CONFIG_FILES) {
5279
- const configPath = resolve4(cwd, configFile);
6616
+ const configPath = resolve5(cwd, configFile);
5280
6617
  if (existsSync4(configPath)) {
5281
6618
  return configPath;
5282
6619
  }
@@ -5305,7 +6642,7 @@ error: ${text}`);
5305
6642
  if (ext === "ts" || ext === "mts") {
5306
6643
  try {
5307
6644
  const { build } = await Promise.resolve().then(() => __toESM(require_main()));
5308
- const configDir = dirname2(configPath);
6645
+ const configDir = dirname3(configPath);
5309
6646
  const tempFile = join5(configDir, `.elit-config-${Date.now()}.mjs`);
5310
6647
  const externalAllPlugin = {
5311
6648
  name: "external-all",
@@ -5379,6 +6716,43 @@ error: ${text}`);
5379
6716
  }
5380
6717
 
5381
6718
  // src/cli/pm/commands.ts
6719
+ var PM_SIGNAL_NAMES = /* @__PURE__ */ new Set([
6720
+ "SIGABRT",
6721
+ "SIGALRM",
6722
+ "SIGBREAK",
6723
+ "SIGBUS",
6724
+ "SIGCHLD",
6725
+ "SIGCONT",
6726
+ "SIGFPE",
6727
+ "SIGHUP",
6728
+ "SIGILL",
6729
+ "SIGINT",
6730
+ "SIGIO",
6731
+ "SIGIOT",
6732
+ "SIGKILL",
6733
+ "SIGPIPE",
6734
+ "SIGPOLL",
6735
+ "SIGPROF",
6736
+ "SIGPWR",
6737
+ "SIGQUIT",
6738
+ "SIGSEGV",
6739
+ "SIGSTKFLT",
6740
+ "SIGSTOP",
6741
+ "SIGSYS",
6742
+ "SIGTERM",
6743
+ "SIGTRAP",
6744
+ "SIGTSTP",
6745
+ "SIGTTIN",
6746
+ "SIGTTOU",
6747
+ "SIGUNUSED",
6748
+ "SIGURG",
6749
+ "SIGUSR1",
6750
+ "SIGUSR2",
6751
+ "SIGVTALRM",
6752
+ "SIGWINCH",
6753
+ "SIGXCPU",
6754
+ "SIGXFSZ"
6755
+ ]);
5382
6756
  async function runPmStart(args) {
5383
6757
  const parsed = parsePmStartArgs(args);
5384
6758
  const workspaceRoot = process.cwd();
@@ -5411,9 +6785,130 @@ error: ${text}`);
5411
6785
  if (value === "all") {
5412
6786
  return listPmRecordMatches(paths).map(syncPmRecordLiveness);
5413
6787
  }
6788
+ const groupMatches = findPmGroupMatches(paths, value);
6789
+ if (groupMatches.length > 0) {
6790
+ return groupMatches.map(syncPmRecordLiveness);
6791
+ }
5414
6792
  const match = findPmRecordMatch(paths, value);
5415
6793
  return match ? [syncPmRecordLiveness(match)] : [];
5416
6794
  }
6795
+ function sortPmMatchesByInstance(matches) {
6796
+ return [...matches].sort((left, right) => left.record.instanceIndex - right.record.instanceIndex);
6797
+ }
6798
+ function resolveInspectableMatch(paths, value) {
6799
+ const exactMatch = findPmRecordMatch(paths, value);
6800
+ const groupMatches = findPmGroupMatches(paths, value).map(syncPmRecordLiveness);
6801
+ if (groupMatches.length > 1 && exactMatch?.record.baseName === value && exactMatch.record.name === value) {
6802
+ throw new Error(`Multiple managed processes found for: ${value}. Use a specific instance name such as ${groupMatches[0]?.record.name} or ${groupMatches[1]?.record.name}.`);
6803
+ }
6804
+ if (exactMatch) {
6805
+ return syncPmRecordLiveness(exactMatch);
6806
+ }
6807
+ return groupMatches[0];
6808
+ }
6809
+ function rebuildPmRecordDefinition(record, targetInstances = record.instances) {
6810
+ const definition = resolvePmAppDefinition(
6811
+ toPmAppConfig(record),
6812
+ { name: record.baseName, env: {}, watchPaths: [], watchIgnore: [], instances: targetInstances },
6813
+ process.cwd(),
6814
+ record.source
6815
+ );
6816
+ return {
6817
+ ...definition,
6818
+ name: record.name,
6819
+ baseName: record.baseName,
6820
+ instanceIndex: record.instanceIndex,
6821
+ instances: targetInstances
6822
+ };
6823
+ }
6824
+ function rebuildPmSavedDefinition(app) {
6825
+ const definition = resolvePmAppDefinition(
6826
+ toSavedPmAppConfig(app),
6827
+ { name: app.baseName, env: {}, watchPaths: [], watchIgnore: [], instances: app.instances },
6828
+ process.cwd(),
6829
+ "cli"
6830
+ );
6831
+ return {
6832
+ ...definition,
6833
+ name: app.name,
6834
+ baseName: app.baseName,
6835
+ instanceIndex: app.instanceIndex,
6836
+ instances: app.instances
6837
+ };
6838
+ }
6839
+ function deletePmMatches(matches) {
6840
+ for (const match of matches) {
6841
+ if ((0, import_node_fs5.existsSync)(match.record.logFiles.out)) {
6842
+ (0, import_node_fs5.rmSync)(match.record.logFiles.out, { force: true });
6843
+ }
6844
+ if ((0, import_node_fs5.existsSync)(match.record.logFiles.err)) {
6845
+ (0, import_node_fs5.rmSync)(match.record.logFiles.err, { force: true });
6846
+ }
6847
+ (0, import_node_fs5.rmSync)(match.filePath, { force: true });
6848
+ }
6849
+ }
6850
+ function updatePmInstanceCount(matches, instances) {
6851
+ const now = (/* @__PURE__ */ new Date()).toISOString();
6852
+ for (const match of matches) {
6853
+ writePmRecord(match.filePath, {
6854
+ ...match.record,
6855
+ instances,
6856
+ updatedAt: now
6857
+ });
6858
+ }
6859
+ }
6860
+ function normalizePmSignalName(value) {
6861
+ const trimmed = value.trim().toUpperCase();
6862
+ const signalName = trimmed.startsWith("SIG") ? trimmed : `SIG${trimmed}`;
6863
+ if (!PM_SIGNAL_NAMES.has(signalName)) {
6864
+ throw new Error(`Unsupported pm signal: ${value}`);
6865
+ }
6866
+ return signalName;
6867
+ }
6868
+ function resolveSignalablePid(record) {
6869
+ if (record.childPid && record.childPid > 0) {
6870
+ return record.childPid;
6871
+ }
6872
+ if (record.runnerPid && record.runnerPid > 0) {
6873
+ return record.runnerPid;
6874
+ }
6875
+ return void 0;
6876
+ }
6877
+ function groupPmMatchesByBaseName(matches) {
6878
+ const grouped = /* @__PURE__ */ new Map();
6879
+ for (const match of matches) {
6880
+ const group = grouped.get(match.record.baseName);
6881
+ if (group) {
6882
+ group.push(match);
6883
+ continue;
6884
+ }
6885
+ grouped.set(match.record.baseName, [match]);
6886
+ }
6887
+ return [...grouped.entries()].sort((left, right) => left[0].localeCompare(right[0])).map(([, group]) => sortPmMatchesByInstance(group));
6888
+ }
6889
+ async function waitForPmRecordOnline(filePath, timeoutMs) {
6890
+ const deadline = Date.now() + timeoutMs;
6891
+ while (Date.now() < deadline) {
6892
+ if (!(0, import_node_fs5.existsSync)(filePath)) {
6893
+ break;
6894
+ }
6895
+ const record = readPmRecord(filePath);
6896
+ if (record.status === "online") {
6897
+ return record;
6898
+ }
6899
+ if (record.status === "errored" || record.status === "exited" || record.status === "stopped") {
6900
+ throw new Error(record.error ?? `Process ${record.name} failed while reloading.`);
6901
+ }
6902
+ await new Promise((resolvePromise) => setTimeout(resolvePromise, 50));
6903
+ }
6904
+ throw new Error(`Timed out waiting for the reloaded process to become online after ${timeoutMs}ms.`);
6905
+ }
6906
+ function resolvePmReloadReadyTimeout(record) {
6907
+ return record.waitReady || record.proxy?.strategy === "inherit" ? Math.max(record.listenTimeout + 1e3, 2e3) : Math.max(record.restartDelay + 1e3, 2e3);
6908
+ }
6909
+ function supportsPmProxyReload2(record) {
6910
+ return Boolean(record.proxy) && record.instances === 1 && Boolean(record.runnerPid);
6911
+ }
5417
6912
  function padCell(value, width) {
5418
6913
  return value.length >= width ? value : `${value}${" ".repeat(width - value.length)}`;
5419
6914
  }
@@ -5424,8 +6919,255 @@ error: ${text}`);
5424
6919
  const lines = (0, import_node_fs5.readFileSync)(filePath, "utf8").split(/\r?\n/).filter((line) => line.length > 0);
5425
6920
  return lines.slice(-lineCount).join(import_node_os2.EOL);
5426
6921
  }
5427
- function printPmList(paths) {
5428
- const matches = listPmRecordMatches(paths).map(syncPmRecordLiveness);
6922
+ function listPmMatches(paths) {
6923
+ return listPmRecordMatches(paths).map(syncPmRecordLiveness);
6924
+ }
6925
+ function isPmRecordActive(record) {
6926
+ return record.desiredState === "running" && (record.status === "starting" || record.status === "online" || record.status === "restarting");
6927
+ }
6928
+ function resolvePmUptimeMs(record) {
6929
+ if (!isPmRecordActive(record) || !record.startedAt) {
6930
+ return void 0;
6931
+ }
6932
+ const startedTime = Date.parse(record.startedAt);
6933
+ if (Number.isNaN(startedTime)) {
6934
+ return void 0;
6935
+ }
6936
+ return Math.max(0, Date.now() - startedTime);
6937
+ }
6938
+ function resolvePmLiveMetrics(record) {
6939
+ const uptimeMs = resolvePmUptimeMs(record);
6940
+ if (!isPmRecordActive(record) || !record.childPid) {
6941
+ return { uptimeMs };
6942
+ }
6943
+ const sampledMetrics = samplePmProcessMetrics(record.childPid);
6944
+ return {
6945
+ ...sampledMetrics,
6946
+ uptimeMs,
6947
+ updatedAt: sampledMetrics.cpuPercent !== void 0 || sampledMetrics.memoryRssBytes !== void 0 ? (/* @__PURE__ */ new Date()).toISOString() : void 0
6948
+ };
6949
+ }
6950
+ function toPmDisplayRecord(record) {
6951
+ return {
6952
+ record,
6953
+ liveMetrics: resolvePmLiveMetrics(record)
6954
+ };
6955
+ }
6956
+ function serializePmRecord(record) {
6957
+ return {
6958
+ ...record,
6959
+ liveMetrics: resolvePmLiveMetrics(record)
6960
+ };
6961
+ }
6962
+ function parsePmFormatOption(args, index, option) {
6963
+ let value;
6964
+ if (option.startsWith("--format=")) {
6965
+ value = option.slice("--format=".length);
6966
+ } else {
6967
+ value = readRequiredValue(args, index + 1, "--format");
6968
+ index += 1;
6969
+ }
6970
+ if (value !== "table" && value !== "json") {
6971
+ throw new Error(`Unsupported pm output format: ${value}`);
6972
+ }
6973
+ return {
6974
+ format: value,
6975
+ nextIndex: index
6976
+ };
6977
+ }
6978
+ function parsePmListArgs(args) {
6979
+ let format = "table";
6980
+ for (let index = 0; index < args.length; index++) {
6981
+ const arg = args[index];
6982
+ switch (arg) {
6983
+ case "--json":
6984
+ format = "json";
6985
+ break;
6986
+ case "--format":
6987
+ default:
6988
+ if (arg === "--format" || arg.startsWith("--format=")) {
6989
+ const parsed = parsePmFormatOption(args, index, arg);
6990
+ format = parsed.format;
6991
+ index = parsed.nextIndex;
6992
+ break;
6993
+ }
6994
+ throw new Error(`Unknown pm list option: ${arg}`);
6995
+ }
6996
+ }
6997
+ return { format };
6998
+ }
6999
+ function parsePmShowArgs(args) {
7000
+ let format = "text";
7001
+ let name;
7002
+ for (let index = 0; index < args.length; index++) {
7003
+ const arg = args[index];
7004
+ switch (arg) {
7005
+ case "--json":
7006
+ format = "json";
7007
+ break;
7008
+ case "--format":
7009
+ default:
7010
+ if (arg === "--format" || arg.startsWith("--format=")) {
7011
+ const parsed = parsePmFormatOption(args, index, arg);
7012
+ format = parsed.format === "json" ? "json" : "text";
7013
+ index = parsed.nextIndex;
7014
+ break;
7015
+ }
7016
+ if (arg.startsWith("-")) {
7017
+ throw new Error(`Unknown pm show option: ${arg}`);
7018
+ }
7019
+ if (name) {
7020
+ throw new Error("pm show accepts exactly one process name.");
7021
+ }
7022
+ name = arg;
7023
+ break;
7024
+ }
7025
+ }
7026
+ if (!name) {
7027
+ throw new Error("Usage: elit pm show <name> [--json]");
7028
+ }
7029
+ return { name, format };
7030
+ }
7031
+ function formatPmDuration(durationMs) {
7032
+ if (durationMs < 1e3) {
7033
+ return `${durationMs}ms`;
7034
+ }
7035
+ const totalSeconds = Math.floor(durationMs / 1e3);
7036
+ const days = Math.floor(totalSeconds / 86400);
7037
+ const hours = Math.floor(totalSeconds % 86400 / 3600);
7038
+ const minutes = Math.floor(totalSeconds % 3600 / 60);
7039
+ const seconds = totalSeconds % 60;
7040
+ const parts = [];
7041
+ if (days > 0) {
7042
+ parts.push(`${days}d`);
7043
+ }
7044
+ if (hours > 0) {
7045
+ parts.push(`${hours}h`);
7046
+ }
7047
+ if (minutes > 0) {
7048
+ parts.push(`${minutes}m`);
7049
+ }
7050
+ if (seconds > 0 || parts.length === 0) {
7051
+ parts.push(`${seconds}s`);
7052
+ }
7053
+ return parts.slice(0, 2).join(" ");
7054
+ }
7055
+ function formatPmCpuPercent(cpuPercent) {
7056
+ if (cpuPercent === void 0 || !Number.isFinite(cpuPercent)) {
7057
+ return "-";
7058
+ }
7059
+ return `${cpuPercent >= 100 ? cpuPercent.toFixed(0) : cpuPercent.toFixed(1)}%`;
7060
+ }
7061
+ function formatPmMemory(memoryRssBytes) {
7062
+ if (memoryRssBytes === void 0 || !Number.isFinite(memoryRssBytes) || memoryRssBytes < 0) {
7063
+ return "-";
7064
+ }
7065
+ const units = ["B", "KB", "MB", "GB", "TB"];
7066
+ let value = memoryRssBytes;
7067
+ let unitIndex = 0;
7068
+ while (value >= 1024 && unitIndex < units.length - 1) {
7069
+ value /= 1024;
7070
+ unitIndex += 1;
7071
+ }
7072
+ const formatted = value >= 10 || unitIndex === 0 ? value.toFixed(0) : value.toFixed(1);
7073
+ return `${formatted}${units[unitIndex]}`;
7074
+ }
7075
+ function formatPmUptime(uptimeMs) {
7076
+ if (uptimeMs === void 0 || !Number.isFinite(uptimeMs)) {
7077
+ return "-";
7078
+ }
7079
+ return formatPmDuration(Math.max(0, uptimeMs));
7080
+ }
7081
+ function formatPmTarget(record) {
7082
+ if (record.script) {
7083
+ return record.script;
7084
+ }
7085
+ if (record.file) {
7086
+ return record.file;
7087
+ }
7088
+ if (record.wapk) {
7089
+ return record.wapk;
7090
+ }
7091
+ return "-";
7092
+ }
7093
+ function pushPmDetail(lines, label, value) {
7094
+ lines.push(`${padCell(`${label}:`, 18)} ${value}`);
7095
+ }
7096
+ function pushPmDetailList(lines, label, values) {
7097
+ if (values.length === 0) {
7098
+ pushPmDetail(lines, label, "-");
7099
+ return;
7100
+ }
7101
+ pushPmDetail(lines, label, values[0] ?? "-");
7102
+ for (const value of values.slice(1)) {
7103
+ lines.push(`${" ".repeat(20)} ${value}`);
7104
+ }
7105
+ }
7106
+ function formatPmRecordDetails(record, liveMetrics) {
7107
+ const lines = [`Process: ${record.name}`];
7108
+ pushPmDetail(lines, "id", record.id);
7109
+ pushPmDetail(lines, "status", record.status);
7110
+ pushPmDetail(lines, "desired state", record.desiredState);
7111
+ pushPmDetail(lines, "instance", `${record.instanceIndex}/${record.instances}`);
7112
+ pushPmDetail(lines, "cpu", formatPmCpuPercent(liveMetrics.cpuPercent));
7113
+ pushPmDetail(lines, "memory", formatPmMemory(liveMetrics.memoryRssBytes));
7114
+ pushPmDetail(lines, "uptime", formatPmUptime(liveMetrics.uptimeMs));
7115
+ pushPmDetail(lines, "type", record.type);
7116
+ pushPmDetail(lines, "source", record.source);
7117
+ pushPmDetail(lines, "runtime", record.runtime ?? "-");
7118
+ pushPmDetail(lines, "cwd", record.cwd);
7119
+ pushPmDetail(lines, "target", formatPmTarget(record));
7120
+ pushPmDetail(lines, "command", record.commandPreview || "-");
7121
+ pushPmDetail(lines, "runner pid", record.runnerPid ? String(record.runnerPid) : "-");
7122
+ pushPmDetail(lines, "child pid", record.childPid ? String(record.childPid) : "-");
7123
+ pushPmDetail(lines, "restart count", `${record.restartCount}/${record.maxRestarts}`);
7124
+ pushPmDetail(lines, "restart policy", record.restartPolicy);
7125
+ pushPmDetail(lines, "proxy", record.proxy ? `http://${record.proxy.host ?? "0.0.0.0"}:${record.proxy.port}` : "-");
7126
+ pushPmDetail(lines, "proxy strategy", record.proxy?.strategy ?? "-");
7127
+ pushPmDetail(lines, "proxy target", record.proxy && record.proxyTargetPort ? `${record.proxy.targetHost ?? "127.0.0.1"}:${record.proxyTargetPort}` : "-");
7128
+ pushPmDetail(lines, "max memory", record.maxMemoryBytes ? formatPmMemory(record.maxMemoryBytes) : "-");
7129
+ pushPmDetail(lines, "memory action", record.memoryAction ?? "-");
7130
+ pushPmDetail(lines, "cron restart", record.cronRestart ?? "-");
7131
+ pushPmDetail(lines, "exp backoff", record.expBackoffRestartDelay ? formatPmDuration(record.expBackoffRestartDelay) : "-");
7132
+ pushPmDetail(lines, "exp backoff max", record.expBackoffRestartMaxDelay ? formatPmDuration(record.expBackoffRestartMaxDelay) : "-");
7133
+ pushPmDetail(lines, "restart window", record.restartWindow ? formatPmDuration(record.restartWindow) : "-");
7134
+ pushPmDetail(lines, "wait ready", record.waitReady ? "enabled" : "disabled");
7135
+ pushPmDetail(lines, "listen timeout", record.waitReady ? formatPmDuration(record.listenTimeout) : "-");
7136
+ pushPmDetail(lines, "restart delay", formatPmDuration(record.restartDelay));
7137
+ pushPmDetail(lines, "kill timeout", formatPmDuration(record.killTimeout));
7138
+ pushPmDetail(lines, "min uptime", formatPmDuration(record.minUptime));
7139
+ pushPmDetail(lines, "autorestart", record.autorestart ? "enabled" : "disabled");
7140
+ pushPmDetail(lines, "watch", record.watch ? "enabled" : "disabled");
7141
+ pushPmDetail(lines, "watch debounce", record.watch ? formatPmDuration(record.watchDebounce) : "-");
7142
+ pushPmDetailList(lines, "watch paths", record.watchPaths);
7143
+ pushPmDetailList(lines, "watch ignore", record.watchIgnore);
7144
+ if (record.healthCheck) {
7145
+ pushPmDetail(lines, "health check", record.healthCheck.url);
7146
+ pushPmDetail(lines, "health grace", formatPmDuration(record.healthCheck.gracePeriod));
7147
+ pushPmDetail(lines, "health interval", formatPmDuration(record.healthCheck.interval));
7148
+ pushPmDetail(lines, "health timeout", formatPmDuration(record.healthCheck.timeout));
7149
+ pushPmDetail(lines, "health failures", String(record.healthCheck.maxFailures));
7150
+ } else {
7151
+ pushPmDetail(lines, "health check", "-");
7152
+ }
7153
+ pushPmDetailList(lines, "env", Object.entries(record.env).map(([key, value]) => `${key}=${value}`));
7154
+ pushPmDetail(lines, "stdout log", record.logFiles.out);
7155
+ pushPmDetail(lines, "stderr log", record.logFiles.err);
7156
+ pushPmDetail(lines, "created at", record.createdAt);
7157
+ pushPmDetail(lines, "updated at", record.updatedAt);
7158
+ pushPmDetail(lines, "metrics at", liveMetrics.updatedAt ?? "-");
7159
+ pushPmDetail(lines, "started at", record.startedAt ?? "-");
7160
+ pushPmDetail(lines, "stopped at", record.stoppedAt ?? "-");
7161
+ pushPmDetail(lines, "last exit", record.lastExitCode === void 0 ? "-" : String(record.lastExitCode));
7162
+ pushPmDetail(lines, "error", record.error ?? "-");
7163
+ return lines.join(import_node_os2.EOL);
7164
+ }
7165
+ function printPmList(paths, format = "table") {
7166
+ const matches = listPmMatches(paths).map((match) => toPmDisplayRecord(match.record));
7167
+ if (format === "json") {
7168
+ console.log(JSON.stringify(matches.map((match) => ({ ...match.record, liveMetrics: match.liveMetrics })), null, 2));
7169
+ return;
7170
+ }
5429
7171
  if (matches.length === 0) {
5430
7172
  console.log("No managed processes found.");
5431
7173
  return;
@@ -5434,22 +7176,47 @@ error: ${text}`);
5434
7176
  padCell("name", 20),
5435
7177
  padCell("status", 12),
5436
7178
  padCell("pid", 8),
7179
+ padCell("cpu", 8),
7180
+ padCell("memory", 10),
7181
+ padCell("uptime", 10),
5437
7182
  padCell("restarts", 10),
5438
7183
  padCell("type", 8),
5439
7184
  "runtime"
5440
7185
  ];
5441
7186
  console.log(headers.join(" "));
5442
- for (const { record } of matches) {
7187
+ for (const { record, liveMetrics } of matches) {
5443
7188
  console.log([
5444
7189
  padCell(record.name, 20),
5445
7190
  padCell(record.status, 12),
5446
7191
  padCell(record.childPid ? String(record.childPid) : "-", 8),
7192
+ padCell(formatPmCpuPercent(liveMetrics.cpuPercent), 8),
7193
+ padCell(formatPmMemory(liveMetrics.memoryRssBytes), 10),
7194
+ padCell(formatPmUptime(liveMetrics.uptimeMs), 10),
5447
7195
  padCell(String(record.restartCount ?? 0), 10),
5448
7196
  padCell(record.type, 8),
5449
7197
  record.runtime ?? "-"
5450
7198
  ].join(" "));
5451
7199
  }
5452
7200
  }
7201
+ async function runPmList(args) {
7202
+ const options = parsePmListArgs(args);
7203
+ const { paths } = await loadPmContext();
7204
+ printPmList(paths, options.format);
7205
+ }
7206
+ async function runPmShow(args) {
7207
+ const options = parsePmShowArgs(args);
7208
+ const { paths } = await loadPmContext();
7209
+ const match = resolveInspectableMatch(paths, options.name);
7210
+ if (!match) {
7211
+ throw new Error(`No managed process found for: ${options.name}`);
7212
+ }
7213
+ const synced = syncPmRecordLiveness(match);
7214
+ if (options.format === "json") {
7215
+ console.log(JSON.stringify(serializePmRecord(synced.record), null, 2));
7216
+ return;
7217
+ }
7218
+ console.log(formatPmRecordDetails(synced.record, resolvePmLiveMetrics(synced.record)));
7219
+ }
5453
7220
  async function runPmStop(args) {
5454
7221
  const target = args[0];
5455
7222
  if (!target) {
@@ -5476,17 +7243,62 @@ error: ${text}`);
5476
7243
  await stopPmMatches(matches);
5477
7244
  const restarted = [];
5478
7245
  for (const match of matches) {
5479
- const definition = resolvePmAppDefinition(
5480
- toPmAppConfig(match.record),
5481
- { name: match.record.name, env: {}, watchPaths: [], watchIgnore: [] },
5482
- process.cwd(),
5483
- match.record.source
5484
- );
7246
+ const definition = rebuildPmRecordDefinition(match.record);
5485
7247
  await startManagedProcess(definition, paths);
5486
7248
  restarted.push(match.record.name);
5487
7249
  }
5488
7250
  console.log(`[pm] restarted ${restarted.join(", ")}`);
5489
7251
  }
7252
+ async function runPmReload(args) {
7253
+ const target = args[0];
7254
+ if (!target) {
7255
+ throw new Error("Usage: elit pm reload <name|all>");
7256
+ }
7257
+ const { paths } = await loadPmContext();
7258
+ const matches = resolveNamedMatches(paths, target);
7259
+ if (matches.length === 0) {
7260
+ throw new Error(`No managed process found for: ${target}`);
7261
+ }
7262
+ const reloaded = [];
7263
+ const errors = [];
7264
+ for (const group of groupPmMatchesByBaseName(matches)) {
7265
+ for (const match of group) {
7266
+ try {
7267
+ if (supportsPmProxyReload2(match.record)) {
7268
+ const reloadRequestedAt = (/* @__PURE__ */ new Date()).toISOString();
7269
+ writePmRecord(match.filePath, {
7270
+ ...match.record,
7271
+ status: "restarting",
7272
+ reloadRequestedAt,
7273
+ updatedAt: reloadRequestedAt,
7274
+ error: void 0
7275
+ });
7276
+ await waitForPmRecordOnline(
7277
+ getPmRecordPath(paths, match.record.id),
7278
+ resolvePmReloadReadyTimeout(match.record)
7279
+ );
7280
+ reloaded.push(match.record.name);
7281
+ continue;
7282
+ }
7283
+ await stopPmMatches([match]);
7284
+ const definition = rebuildPmRecordDefinition(match.record);
7285
+ const startedRecord = await startManagedProcess(definition, paths);
7286
+ await waitForPmRecordOnline(
7287
+ getPmRecordPath(paths, startedRecord.id),
7288
+ resolvePmReloadReadyTimeout(startedRecord)
7289
+ );
7290
+ reloaded.push(match.record.name);
7291
+ } catch (error) {
7292
+ const message = error instanceof Error ? error.message : String(error);
7293
+ errors.push(`[pm] ${match.record.name}: ${message}`);
7294
+ }
7295
+ }
7296
+ }
7297
+ if (errors.length > 0) {
7298
+ throw new Error([`[pm] reloaded ${reloaded.length} process${reloaded.length === 1 ? "" : "es"}`, ...errors].join(import_node_os2.EOL));
7299
+ }
7300
+ console.log(`[pm] reloaded ${reloaded.join(", ")}`);
7301
+ }
5490
7302
  async function runPmSave() {
5491
7303
  const { paths } = await loadPmContext();
5492
7304
  ensurePmDirectories(paths);
@@ -5508,12 +7320,7 @@ error: ${text}`);
5508
7320
  let restored = 0;
5509
7321
  for (const app of dump.apps) {
5510
7322
  try {
5511
- const definition = resolvePmAppDefinition(
5512
- toSavedPmAppConfig(app),
5513
- { name: app.name, env: {}, watchPaths: [], watchIgnore: [] },
5514
- process.cwd(),
5515
- "cli"
5516
- );
7323
+ const definition = rebuildPmSavedDefinition(app);
5517
7324
  await startManagedProcess(definition, paths);
5518
7325
  restored += 1;
5519
7326
  } catch (error) {
@@ -5537,16 +7344,127 @@ error: ${text}`);
5537
7344
  throw new Error(`No managed process found for: ${target}`);
5538
7345
  }
5539
7346
  await stopPmMatches(matches);
7347
+ deletePmMatches(matches);
7348
+ console.log(`[pm] deleted ${matches.length} process${matches.length === 1 ? "" : "es"}`);
7349
+ }
7350
+ async function runPmScale(args) {
7351
+ const target = args[0];
7352
+ const countArg = args[1];
7353
+ if (!target || countArg === void 0 || args.length > 2) {
7354
+ throw new Error("Usage: elit pm scale <name> <count>");
7355
+ }
7356
+ const desiredCount = normalizeIntegerOption(countArg, "pm scale <count>", 0);
7357
+ const { config, paths } = await loadPmContext();
7358
+ const exactMatch = findPmRecordMatch(paths, target);
7359
+ const currentMatches = sortPmMatchesByInstance(resolveNamedMatches(paths, exactMatch?.record.baseName ?? target));
7360
+ if (currentMatches.length === 0) {
7361
+ if (desiredCount === 0) {
7362
+ console.log(`[pm] ${target} already scaled to 0 instances`);
7363
+ return;
7364
+ }
7365
+ const definitions = resolvePmStartDefinitions(
7366
+ { name: target, env: {}, watchPaths: [], watchIgnore: [], instances: desiredCount },
7367
+ config,
7368
+ process.cwd()
7369
+ );
7370
+ for (const definition of definitions) {
7371
+ await startManagedProcess(definition, paths);
7372
+ }
7373
+ console.log(`[pm] scaled ${target} to ${desiredCount} instance${desiredCount === 1 ? "" : "s"}`);
7374
+ return;
7375
+ }
7376
+ const baseName = currentMatches[0]?.record.baseName ?? target;
7377
+ if (desiredCount === currentMatches.length) {
7378
+ console.log(`[pm] ${baseName} already scaled to ${desiredCount} instance${desiredCount === 1 ? "" : "s"}`);
7379
+ return;
7380
+ }
7381
+ if (desiredCount === 0) {
7382
+ await stopPmMatches(currentMatches);
7383
+ deletePmMatches(currentMatches);
7384
+ console.log(`[pm] scaled ${baseName} to 0 instances`);
7385
+ return;
7386
+ }
7387
+ if (desiredCount < currentMatches.length) {
7388
+ const toRemove = [...currentMatches].sort((left, right) => right.record.instanceIndex - left.record.instanceIndex).slice(0, currentMatches.length - desiredCount);
7389
+ const remaining = currentMatches.filter((match) => !toRemove.some((removal) => removal.record.id === match.record.id));
7390
+ await stopPmMatches(toRemove);
7391
+ deletePmMatches(toRemove);
7392
+ updatePmInstanceCount(remaining, desiredCount);
7393
+ console.log(`[pm] scaled ${baseName} to ${desiredCount} instance${desiredCount === 1 ? "" : "s"}`);
7394
+ return;
7395
+ }
7396
+ updatePmInstanceCount(currentMatches, desiredCount);
7397
+ const baseRecord = currentMatches[0]?.record;
7398
+ if (!baseRecord) {
7399
+ throw new Error(`No managed process found for: ${target}`);
7400
+ }
7401
+ const baseDefinition = rebuildPmRecordDefinition(baseRecord, desiredCount);
7402
+ const expandedDefinitions = expandPmInstanceDefinitions(baseDefinition, desiredCount);
7403
+ const existingNames = new Set(currentMatches.map((match) => match.record.name));
7404
+ for (const definition of expandedDefinitions) {
7405
+ if (existingNames.has(definition.name)) {
7406
+ continue;
7407
+ }
7408
+ await startManagedProcess(definition, paths);
7409
+ }
7410
+ console.log(`[pm] scaled ${baseName} to ${desiredCount} instance${desiredCount === 1 ? "" : "s"}`);
7411
+ }
7412
+ async function runPmSendSignal(args) {
7413
+ const signalArg = args[0];
7414
+ const target = args[1];
7415
+ if (!signalArg || !target || args.length > 2) {
7416
+ throw new Error("Usage: elit pm send-signal <signal> <name|all>");
7417
+ }
7418
+ const signalName = normalizePmSignalName(signalArg);
7419
+ const { paths } = await loadPmContext();
7420
+ const matches = resolveNamedMatches(paths, target);
7421
+ if (matches.length === 0) {
7422
+ throw new Error(`No managed process found for: ${target}`);
7423
+ }
7424
+ let signaled = 0;
7425
+ const errors = [];
5540
7426
  for (const match of matches) {
5541
- if ((0, import_node_fs5.existsSync)(match.record.logFiles.out)) {
5542
- (0, import_node_fs5.rmSync)(match.record.logFiles.out, { force: true });
7427
+ const pid = resolveSignalablePid(match.record);
7428
+ if (!pid) {
7429
+ continue;
5543
7430
  }
5544
- if ((0, import_node_fs5.existsSync)(match.record.logFiles.err)) {
5545
- (0, import_node_fs5.rmSync)(match.record.logFiles.err, { force: true });
7431
+ try {
7432
+ sendPmSignal(pid, signalName);
7433
+ signaled += 1;
7434
+ } catch (error) {
7435
+ const message = error instanceof Error ? error.message : String(error);
7436
+ errors.push(`[pm] ${match.record.name}: ${message}`);
5546
7437
  }
5547
- (0, import_node_fs5.rmSync)(match.filePath, { force: true });
5548
7438
  }
5549
- console.log(`[pm] deleted ${matches.length} process${matches.length === 1 ? "" : "es"}`);
7439
+ if (errors.length > 0) {
7440
+ throw new Error([`[pm] sent ${signalName} to ${signaled} process${signaled === 1 ? "" : "es"}`, ...errors].join(import_node_os2.EOL));
7441
+ }
7442
+ if (signaled === 0) {
7443
+ throw new Error(`No running managed process found for: ${target}`);
7444
+ }
7445
+ console.log(`[pm] sent ${signalName} to ${signaled} process${signaled === 1 ? "" : "es"}`);
7446
+ }
7447
+ async function runPmReset(args) {
7448
+ const target = args[0];
7449
+ if (!target) {
7450
+ throw new Error("Usage: elit pm reset <name|all>");
7451
+ }
7452
+ const { paths } = await loadPmContext();
7453
+ const matches = resolveNamedMatches(paths, target);
7454
+ if (matches.length === 0) {
7455
+ throw new Error(`No managed process found for: ${target}`);
7456
+ }
7457
+ const now = (/* @__PURE__ */ new Date()).toISOString();
7458
+ for (const match of matches) {
7459
+ writePmRecord(match.filePath, {
7460
+ ...match.record,
7461
+ restartCount: 0,
7462
+ lastExitCode: void 0,
7463
+ error: void 0,
7464
+ updatedAt: now
7465
+ });
7466
+ }
7467
+ console.log(`[pm] reset ${matches.length} process${matches.length === 1 ? "" : "es"}`);
5550
7468
  }
5551
7469
  async function runPmLogs(args) {
5552
7470
  if (args.length === 0) {
@@ -5579,7 +7497,7 @@ error: ${text}`);
5579
7497
  throw new Error("Usage: elit pm logs <name> [--lines <n>] [--stderr]");
5580
7498
  }
5581
7499
  const { paths } = await loadPmContext();
5582
- const match = findPmRecordMatch(paths, name);
7500
+ const match = resolveInspectableMatch(paths, name);
5583
7501
  if (!match) {
5584
7502
  throw new Error(`No managed process found for: ${name}`);
5585
7503
  }
@@ -5603,17 +7521,36 @@ error: ${text}`);
5603
7521
  await runPmStart(args.slice(1));
5604
7522
  return;
5605
7523
  case "list":
5606
- case "ls": {
5607
- const { paths } = await loadPmContext();
5608
- printPmList(paths);
7524
+ case "ls":
7525
+ await runPmList(args.slice(1));
7526
+ return;
7527
+ case "jlist":
7528
+ await runPmList(["--json", ...args.slice(1)]);
7529
+ return;
7530
+ case "show":
7531
+ case "describe":
7532
+ await runPmShow(args.slice(1));
5609
7533
  return;
5610
- }
5611
7534
  case "stop":
5612
7535
  await runPmStop(args.slice(1));
5613
7536
  return;
5614
7537
  case "restart":
5615
7538
  await runPmRestart(args.slice(1));
5616
7539
  return;
7540
+ case "reload":
7541
+ await runPmReload(args.slice(1));
7542
+ return;
7543
+ case "scale":
7544
+ await runPmScale(args.slice(1));
7545
+ return;
7546
+ case "send-signal":
7547
+ case "signal":
7548
+ case "sendSignal":
7549
+ await runPmSendSignal(args.slice(1));
7550
+ return;
7551
+ case "reset":
7552
+ await runPmReset(args.slice(1));
7553
+ return;
5617
7554
  case "delete":
5618
7555
  case "remove":
5619
7556
  case "rm":