elit 3.6.7 → 3.6.9

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 +2777 -321
  5. package/dist/cli.mjs +2764 -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 +2390 -160
  20. package/dist/pm.d.ts +83 -8
  21. package/dist/pm.js +2388 -188
  22. package/dist/pm.mjs +2380 -165
  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,84 +5199,877 @@ error: ${text}`);
4655
5199
  return watcher;
4656
5200
  }
4657
5201
 
4658
- // src/cli/pm/runner.ts
4659
- function writePmLog(stream, message) {
4660
- stream.write(`[elit pm] ${(/* @__PURE__ */ new Date()).toISOString()} ${message}${import_node_os.EOL}`);
4661
- }
4662
- function waitForExit(code, signal) {
4663
- if (typeof code === "number") {
4664
- return code;
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
+ var import_promises = __require("dns/promises");
5207
+ var BLOCKED_IPV4_PREFIXES = [
5208
+ "0.",
5209
+ "10.",
5210
+ "100.64.",
5211
+ "100.65.",
5212
+ "100.66.",
5213
+ "100.67.",
5214
+ "100.68.",
5215
+ "100.69.",
5216
+ "100.70.",
5217
+ "100.71.",
5218
+ "100.72.",
5219
+ "100.73.",
5220
+ "100.74.",
5221
+ "100.75.",
5222
+ "100.76.",
5223
+ "100.77.",
5224
+ "100.78.",
5225
+ "100.79.",
5226
+ "100.80.",
5227
+ "100.81.",
5228
+ "100.82.",
5229
+ "100.83.",
5230
+ "100.84.",
5231
+ "100.85.",
5232
+ "100.86.",
5233
+ "100.87.",
5234
+ "100.88.",
5235
+ "100.89.",
5236
+ "100.90.",
5237
+ "100.91.",
5238
+ "100.92.",
5239
+ "100.93.",
5240
+ "100.94.",
5241
+ "100.95.",
5242
+ "100.96.",
5243
+ "100.97.",
5244
+ "100.98.",
5245
+ "100.99.",
5246
+ "100.100.",
5247
+ "100.101.",
5248
+ "100.102.",
5249
+ "100.103.",
5250
+ "100.104.",
5251
+ "100.105.",
5252
+ "100.106.",
5253
+ "100.107.",
5254
+ "100.108.",
5255
+ "100.109.",
5256
+ "100.110.",
5257
+ "100.111.",
5258
+ "100.112.",
5259
+ "100.113.",
5260
+ "100.114.",
5261
+ "100.115.",
5262
+ "100.116.",
5263
+ "100.117.",
5264
+ "100.118.",
5265
+ "100.119.",
5266
+ "100.120.",
5267
+ "100.121.",
5268
+ "100.122.",
5269
+ "100.123.",
5270
+ "100.124.",
5271
+ "100.125.",
5272
+ "100.126.",
5273
+ "100.127.",
5274
+ "127.",
5275
+ "169.254.",
5276
+ "172.16.",
5277
+ "172.17.",
5278
+ "172.18.",
5279
+ "172.19.",
5280
+ "172.20.",
5281
+ "172.21.",
5282
+ "172.22.",
5283
+ "172.23.",
5284
+ "172.24.",
5285
+ "172.25.",
5286
+ "172.26.",
5287
+ "172.27.",
5288
+ "172.28.",
5289
+ "172.29.",
5290
+ "172.30.",
5291
+ "172.31.",
5292
+ "192.0.2.",
5293
+ "192.88.99.",
5294
+ "192.168.",
5295
+ "198.18.",
5296
+ "198.19.",
5297
+ "198.51.100.",
5298
+ "203.0.113.",
5299
+ "224.",
5300
+ "225.",
5301
+ "226.",
5302
+ "227.",
5303
+ "228.",
5304
+ "229.",
5305
+ "230.",
5306
+ "231.",
5307
+ "232.",
5308
+ "233.",
5309
+ "234.",
5310
+ "235.",
5311
+ "236.",
5312
+ "237.",
5313
+ "238.",
5314
+ "239.",
5315
+ "240.",
5316
+ "241.",
5317
+ "242.",
5318
+ "243.",
5319
+ "244.",
5320
+ "245.",
5321
+ "246.",
5322
+ "247.",
5323
+ "248.",
5324
+ "249.",
5325
+ "250.",
5326
+ "251.",
5327
+ "252.",
5328
+ "253.",
5329
+ "254.",
5330
+ "255."
5331
+ ];
5332
+ var ALLOWED_PROXY_PROTOCOLS = /* @__PURE__ */ new Set(["http:", "https:"]);
5333
+ function isBlockedIpv4(hostname) {
5334
+ const octets = hostname.split(".");
5335
+ if (octets.length !== 4) return false;
5336
+ const joined = hostname;
5337
+ return BLOCKED_IPV4_PREFIXES.some((prefix) => joined.startsWith(prefix));
5338
+ }
5339
+ function isBlockedIpv6(hostname) {
5340
+ const lower = hostname.toLowerCase();
5341
+ if (lower === "::1" || lower === "::" || lower === "0:0:0:0:0:0:0:1" || lower === "0:0:0:0:0:0:0:0") {
5342
+ return true;
4665
5343
  }
4666
- if (signal === "SIGINT" || signal === "SIGTERM") {
4667
- return 0;
5344
+ const ffffMatch = lower.match(/^::ffff:(\d+\.\d+\.\d+\.\d+)$/);
5345
+ if (ffffMatch) {
5346
+ return isBlockedIpv4(ffffMatch[1]);
4668
5347
  }
4669
- return 1;
5348
+ const compatMatch = lower.match(/^::(\d+\.\d+\.\d+\.\d+)$/);
5349
+ if (compatMatch) {
5350
+ return isBlockedIpv4(compatMatch[1]);
5351
+ }
5352
+ return false;
4670
5353
  }
4671
- async function delay(milliseconds) {
4672
- await new Promise((resolvePromise) => setTimeout(resolvePromise, milliseconds));
5354
+ function isSafeHostname(hostname) {
5355
+ if (!hostname) return false;
5356
+ if (/^\d{1,3}(\.\d{1,3}){3}$/.test(hostname)) return !isBlockedIpv4(hostname);
5357
+ if (/^\[.*\]$/.test(hostname)) return !isBlockedIpv6(hostname.slice(1, -1));
5358
+ if (hostname.includes(":")) return !isBlockedIpv6(hostname);
5359
+ return true;
4673
5360
  }
4674
- async function waitForProcessTermination(pid, timeoutMs) {
4675
- if (!pid || !isProcessAlive(pid)) {
4676
- return true;
5361
+ async function safeResolveHostname(hostname) {
5362
+ try {
5363
+ const result = await (0, import_promises.lookup)(hostname);
5364
+ const ip = result.address;
5365
+ if (isBlockedIpv4(ip) || isBlockedIpv6(ip)) {
5366
+ throw new Error(`PM proxy target resolved to a blocked address: ${ip}`);
5367
+ }
5368
+ return ip;
5369
+ } catch (error) {
5370
+ if (error instanceof Error && error.message.includes("blocked address")) {
5371
+ throw error;
5372
+ }
5373
+ throw new Error(`PM proxy failed to resolve target hostname "${hostname}": ${error instanceof Error ? error.message : String(error)}`);
4677
5374
  }
4678
- const deadline = Date.now() + timeoutMs;
4679
- while (Date.now() < deadline) {
4680
- if (!isProcessAlive(pid)) {
4681
- return true;
5375
+ }
5376
+ async function validateProxyTargetUrl(target) {
5377
+ if (!ALLOWED_PROXY_PROTOCOLS.has(target.protocol)) {
5378
+ throw new Error(`PM proxy target protocol "${target.protocol}" is not allowed. Only http: and https: are permitted.`);
5379
+ }
5380
+ const hostname = target.hostname;
5381
+ if (!isSafeHostname(hostname)) {
5382
+ throw new Error(`PM proxy target "${hostname}" resolves to a blocked address and is not allowed.`);
5383
+ }
5384
+ if (!/^\d{1,3}(\.\d{1,3}){3}$/.test(hostname) && !/^\[.*\]$/.test(hostname)) {
5385
+ await safeResolveHostname(hostname);
5386
+ }
5387
+ }
5388
+ function sanitizeProxyRequestPath(requestUrl) {
5389
+ if (!requestUrl || requestUrl === "/") return "/";
5390
+ try {
5391
+ const normalizedInput = requestUrl.replace(/\\/g, "/");
5392
+ const parsed = new URL(normalizedInput, "http://placeholder");
5393
+ if (parsed.username || parsed.password || parsed.hostname !== "placeholder" || parsed.port) {
5394
+ return "/";
4682
5395
  }
4683
- await delay(DEFAULT_PM_STOP_POLL_MS);
5396
+ const pathname = parsed.pathname || "/";
5397
+ let decodedPathname = pathname;
5398
+ try {
5399
+ decodedPathname = decodeURIComponent(pathname);
5400
+ } catch {
5401
+ return "/";
5402
+ }
5403
+ const lowerPath = pathname.toLowerCase();
5404
+ if (lowerPath.includes("%2f") || lowerPath.includes("%5c") || lowerPath.includes("%40") || lowerPath.includes("%00")) {
5405
+ return "/";
5406
+ }
5407
+ const segments = decodedPathname.split("/");
5408
+ if (segments.some((segment) => segment === "." || segment === "..")) {
5409
+ return "/";
5410
+ }
5411
+ const sanitized = pathname + parsed.search;
5412
+ return sanitized.startsWith("/") ? sanitized || "/" : `/${sanitized}`;
5413
+ } catch {
5414
+ return "/";
4684
5415
  }
4685
- return !isProcessAlive(pid);
4686
5416
  }
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) {
5417
+ function resolvePmProxyHost(proxy) {
5418
+ return proxy.host?.trim() || "0.0.0.0";
5419
+ }
5420
+ function resolvePmProxyTargetHost(proxy) {
5421
+ return proxy.targetHost?.trim() || "127.0.0.1";
5422
+ }
5423
+ function resolvePmProxyEnvVar(proxy) {
5424
+ return proxy.envVar?.trim() || "PORT";
5425
+ }
5426
+ function buildPmProxyTargetUrl(proxy, targetPort) {
5427
+ return `http://${resolvePmProxyTargetHost(proxy)}:${targetPort}`;
5428
+ }
5429
+ function rewritePmProxyHealthCheckUrl(url, targetHost, targetPort) {
5430
+ const targetUrl = new URL(url);
5431
+ targetUrl.hostname = targetHost;
5432
+ targetUrl.port = String(targetPort);
5433
+ return targetUrl.toString();
5434
+ }
5435
+ async function allocatePmProxyTargetPort(host = "127.0.0.1") {
5436
+ const server = (0, import_node_net.createServer)();
5437
+ return await new Promise((resolve7, reject) => {
5438
+ server.once("error", reject);
5439
+ server.listen(0, host, () => {
5440
+ const address = server.address();
5441
+ if (!address || typeof address === "string") {
5442
+ server.close(() => reject(new Error("Failed to allocate an internal PM proxy port.")));
4699
5443
  return;
4700
5444
  }
4701
- resolved = true;
4702
- resolvePromise({ code, signal });
5445
+ const port = address.port;
5446
+ server.close((error) => {
5447
+ if (error) {
5448
+ reject(error);
5449
+ return;
5450
+ }
5451
+ resolve7(port);
5452
+ });
4703
5453
  });
4704
5454
  });
4705
5455
  }
4706
- async function createPmWatchController(record, onChange, onError) {
4707
- if (!record.watch || record.watchPaths.length === 0) {
4708
- return {
4709
- async close() {
5456
+ function buildPmProxyHeaders(headersInput, host) {
5457
+ const headers = {};
5458
+ for (const [key, value] of Object.entries(headersInput)) {
5459
+ if (value !== void 0) {
5460
+ headers[key] = value;
5461
+ }
5462
+ }
5463
+ headers.host = host;
5464
+ return headers;
5465
+ }
5466
+ function writeRawHttpResponse(socket, statusCode, statusMessage, headers) {
5467
+ const lines = [`HTTP/1.1 ${statusCode} ${statusMessage}`];
5468
+ for (const [key, value] of Object.entries(headers)) {
5469
+ if (value === void 0) {
5470
+ continue;
5471
+ }
5472
+ if (Array.isArray(value)) {
5473
+ for (const item of value) {
5474
+ lines.push(`${key}: ${item}`);
4710
5475
  }
4711
- };
5476
+ continue;
5477
+ }
5478
+ lines.push(`${key}: ${value}`);
4712
5479
  }
4713
- const watcher = watch(record.watchPaths);
4714
- let debounceTimer = null;
4715
- const scheduleRestart = (filePath) => {
4716
- const normalizedPath = filePath.replace(/\\/g, "/");
4717
- if (isIgnoredWatchPath(normalizedPath, record.watchIgnore)) {
5480
+ socket.write(`${lines.join("\r\n")}\r
5481
+ \r
5482
+ `);
5483
+ }
5484
+ async function createPmProxyController(proxy) {
5485
+ let targets = [];
5486
+ let nextTargetIndex = 0;
5487
+ const setResolvedTargets = (nextTargets) => {
5488
+ const unchanged = nextTargets.length === targets.length && nextTargets.every((target, index) => targets[index]?.href === target.href);
5489
+ targets = nextTargets;
5490
+ if (targets.length === 0) {
5491
+ nextTargetIndex = 0;
4718
5492
  return;
4719
5493
  }
4720
- if (debounceTimer) {
4721
- clearTimeout(debounceTimer);
5494
+ if (!unchanged) {
5495
+ nextTargetIndex = nextTargetIndex % targets.length;
4722
5496
  }
4723
- debounceTimer = setTimeout(() => {
4724
- debounceTimer = null;
4725
- onChange(normalizedPath);
4726
- }, record.watchDebounce);
4727
- debounceTimer.unref?.();
4728
5497
  };
4729
- watcher.on("add", scheduleRestart);
4730
- watcher.on("change", scheduleRestart);
4731
- watcher.on("unlink", scheduleRestart);
4732
- watcher.on("error", (error) => onError(error instanceof Error ? error.message : String(error)));
4733
- return {
4734
- async close() {
4735
- if (debounceTimer) {
5498
+ const pickTarget = () => {
5499
+ if (targets.length === 0) {
5500
+ return null;
5501
+ }
5502
+ const target = targets[nextTargetIndex % targets.length];
5503
+ nextTargetIndex = (nextTargetIndex + 1) % targets.length;
5504
+ return target;
5505
+ };
5506
+ const validateTarget = async (target) => {
5507
+ await validateProxyTargetUrl(target);
5508
+ };
5509
+ const server = (0, import_node_http.createServer)((req, res) => {
5510
+ const target = pickTarget();
5511
+ if (!target) {
5512
+ res.statusCode = 503;
5513
+ res.end("PM proxy target is not ready.");
5514
+ return;
5515
+ }
5516
+ const sanitizedPath = sanitizeProxyRequestPath(req.url || "/");
5517
+ const requestLib = target.protocol === "https:" ? import_node_https.request : import_node_http.request;
5518
+ const headers = buildPmProxyHeaders(req.headers, target.host);
5519
+ if (!ALLOWED_PROXY_PROTOCOLS.has(target.protocol)) {
5520
+ res.statusCode = 400;
5521
+ res.end("PM proxy rejected unsafe target protocol.");
5522
+ return;
5523
+ }
5524
+ validateTarget(target).then(() => {
5525
+ const proxyReq = requestLib({
5526
+ protocol: target.protocol,
5527
+ hostname: target.hostname,
5528
+ port: target.port || void 0,
5529
+ path: sanitizedPath,
5530
+ method: req.method,
5531
+ headers
5532
+ }, (proxyRes) => {
5533
+ const outgoingHeaders = {};
5534
+ for (const [key, value] of Object.entries(proxyRes.headers)) {
5535
+ if (value !== void 0) {
5536
+ outgoingHeaders[key] = value;
5537
+ }
5538
+ }
5539
+ res.writeHead(proxyRes.statusCode || 200, outgoingHeaders);
5540
+ proxyRes.pipe(res);
5541
+ });
5542
+ proxyReq.on("error", (error) => {
5543
+ if (!res.headersSent) {
5544
+ res.statusCode = 502;
5545
+ }
5546
+ res.end(`PM proxy error: ${error.message}`);
5547
+ });
5548
+ req.pipe(proxyReq);
5549
+ }).catch((error) => {
5550
+ if (!res.headersSent) {
5551
+ res.statusCode = 403;
5552
+ }
5553
+ res.end(`PM proxy blocked target: ${error instanceof Error ? error.message : String(error)}`);
5554
+ });
5555
+ });
5556
+ server.on("upgrade", (req, socket, head) => {
5557
+ const target = pickTarget();
5558
+ if (!target) {
5559
+ writeRawHttpResponse(socket, 503, "Service Unavailable", {
5560
+ connection: "close",
5561
+ "content-length": 0
5562
+ });
5563
+ socket.destroy();
5564
+ return;
5565
+ }
5566
+ const sanitizedPath = sanitizeProxyRequestPath(req.url || "/");
5567
+ const requestLib = target.protocol === "https:" ? import_node_https.request : import_node_http.request;
5568
+ const targetUrl = new URL(sanitizedPath, target);
5569
+ if (targetUrl.hostname !== target.hostname || targetUrl.port !== target.port || !ALLOWED_PROXY_PROTOCOLS.has(targetUrl.protocol)) {
5570
+ writeRawHttpResponse(socket, 400, "Bad Request", {
5571
+ connection: "close",
5572
+ "content-length": 0
5573
+ });
5574
+ socket.destroy();
5575
+ return;
5576
+ }
5577
+ validateTarget(target).then(() => {
5578
+ const proxyReq = requestLib(targetUrl, {
5579
+ method: req.method,
5580
+ headers: buildPmProxyHeaders(req.headers, target.host)
5581
+ });
5582
+ proxyReq.on("upgrade", (proxyRes, proxySocket, proxyHead) => {
5583
+ writeRawHttpResponse(socket, proxyRes.statusCode || 101, proxyRes.statusMessage || "Switching Protocols", proxyRes.headers);
5584
+ if (head.length > 0) {
5585
+ proxySocket.write(head);
5586
+ }
5587
+ if (proxyHead.length > 0) {
5588
+ socket.write(proxyHead);
5589
+ }
5590
+ socket.on("error", () => proxySocket.destroy());
5591
+ proxySocket.on("error", () => socket.destroy());
5592
+ proxySocket.pipe(socket);
5593
+ socket.pipe(proxySocket);
5594
+ });
5595
+ proxyReq.on("response", (proxyRes) => {
5596
+ writeRawHttpResponse(socket, proxyRes.statusCode || 502, proxyRes.statusMessage || "Bad Gateway", proxyRes.headers);
5597
+ proxyRes.pipe(socket);
5598
+ });
5599
+ proxyReq.on("error", (error) => {
5600
+ writeRawHttpResponse(socket, 502, "Bad Gateway", {
5601
+ connection: "close",
5602
+ "content-type": "text/plain; charset=utf-8",
5603
+ "content-length": Buffer.byteLength(`PM proxy error: ${error.message}`)
5604
+ });
5605
+ socket.end(`PM proxy error: ${error.message}`);
5606
+ });
5607
+ proxyReq.end();
5608
+ }).catch((error) => {
5609
+ writeRawHttpResponse(socket, 403, "Forbidden", {
5610
+ connection: "close",
5611
+ "content-type": "text/plain; charset=utf-8",
5612
+ "content-length": Buffer.byteLength(`PM proxy blocked target: ${error instanceof Error ? error.message : String(error)}`)
5613
+ });
5614
+ socket.end(`PM proxy blocked target: ${error instanceof Error ? error.message : String(error)}`);
5615
+ });
5616
+ });
5617
+ await new Promise((resolve7, reject) => {
5618
+ server.once("error", reject);
5619
+ server.listen(proxy.port, resolvePmProxyHost(proxy), () => resolve7());
5620
+ });
5621
+ return {
5622
+ setTarget(targetUrl) {
5623
+ if (targetUrl) {
5624
+ const parsed = new URL(targetUrl);
5625
+ validateProxyTargetUrl(parsed).then(() => {
5626
+ setResolvedTargets([parsed]);
5627
+ }).catch((error) => {
5628
+ console.error(`[PM proxy] Blocked setTarget: ${error instanceof Error ? error.message : String(error)}`);
5629
+ setResolvedTargets([]);
5630
+ });
5631
+ } else {
5632
+ setResolvedTargets([]);
5633
+ }
5634
+ },
5635
+ setTargets(targetUrls) {
5636
+ Promise.all(targetUrls.map((url) => validateProxyTargetUrl(new URL(url)))).then(() => {
5637
+ setResolvedTargets(targetUrls.map((targetUrl) => new URL(targetUrl)));
5638
+ }).catch((error) => {
5639
+ console.error(`[PM proxy] Blocked setTargets: ${error instanceof Error ? error.message : String(error)}`);
5640
+ setResolvedTargets([]);
5641
+ });
5642
+ },
5643
+ close() {
5644
+ return new Promise((resolve7, reject) => {
5645
+ server.close((error) => {
5646
+ if (error) {
5647
+ reject(error);
5648
+ return;
5649
+ }
5650
+ resolve7();
5651
+ });
5652
+ });
5653
+ }
5654
+ };
5655
+ }
5656
+
5657
+ // src/cli/pm/runner.ts
5658
+ function writePmLog(stream, message) {
5659
+ if (stream.writableEnded || stream.destroyed) {
5660
+ return;
5661
+ }
5662
+ try {
5663
+ stream.write(`[elit pm] ${(/* @__PURE__ */ new Date()).toISOString()} ${message}${import_node_os.EOL}`);
5664
+ } catch (error) {
5665
+ if (error.code !== "ERR_STREAM_WRITE_AFTER_END") {
5666
+ throw error;
5667
+ }
5668
+ }
5669
+ }
5670
+ function waitForExit(code, signal) {
5671
+ if (typeof code === "number") {
5672
+ return code;
5673
+ }
5674
+ if (signal === "SIGINT" || signal === "SIGTERM") {
5675
+ return 0;
5676
+ }
5677
+ return 1;
5678
+ }
5679
+ async function delay(milliseconds) {
5680
+ await new Promise((resolvePromise) => setTimeout(resolvePromise, milliseconds));
5681
+ }
5682
+ function resolveRunnerPathsFromRecordFile(filePath) {
5683
+ const appsDir = (0, import_node_path6.dirname)(filePath);
5684
+ const dataDir = (0, import_node_path6.dirname)(appsDir);
5685
+ return {
5686
+ dataDir,
5687
+ appsDir,
5688
+ logsDir: (0, import_node_path6.join)(dataDir, "logs"),
5689
+ dumpFile: (0, import_node_path6.join)(dataDir, DEFAULT_PM_DUMP_FILE)
5690
+ };
5691
+ }
5692
+ function usesPmProxyController(record) {
5693
+ return Boolean(record.proxy) && (record.proxy?.strategy ?? DEFAULT_PM_PROXY_STRATEGY) === "proxy";
5694
+ }
5695
+ function usesPmInheritedListener(record) {
5696
+ return Boolean(record.proxy) && (record.proxy?.strategy ?? DEFAULT_PM_PROXY_STRATEGY) === "inherit";
5697
+ }
5698
+ function isPmProxyOwner(record) {
5699
+ return usesPmProxyController(record) && record.instanceIndex === 1;
5700
+ }
5701
+ function resolvePmProxyTargetUrls(paths, baseName) {
5702
+ 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));
5703
+ }
5704
+ async function createPmInheritedListener(proxy) {
5705
+ const server = (0, import_node_http2.createServer)();
5706
+ await new Promise((resolvePromise, reject) => {
5707
+ server.once("error", reject);
5708
+ server.listen(proxy.port, resolvePmProxyHost(proxy), () => resolvePromise());
5709
+ });
5710
+ return server;
5711
+ }
5712
+ function createPmChildIpcState(child, sharedListener) {
5713
+ let bootstrapReady = false;
5714
+ let listenerReady = false;
5715
+ let sharedHandleSent = false;
5716
+ const sendSharedHandle = () => {
5717
+ if (!sharedListener || !bootstrapReady || sharedHandleSent || !child.connected) {
5718
+ return;
5719
+ }
5720
+ sharedHandleSent = true;
5721
+ child.send?.({ type: "elit:pm:listen-handle" }, sharedListener);
5722
+ };
5723
+ const onMessage = (message) => {
5724
+ if (!message || typeof message !== "object") {
5725
+ return;
5726
+ }
5727
+ if (message.type === "elit:pm:bootstrap-ready") {
5728
+ bootstrapReady = true;
5729
+ sendSharedHandle();
5730
+ return;
5731
+ }
5732
+ if (message.type === "elit:pm:listener-ready") {
5733
+ listenerReady = true;
5734
+ }
5735
+ };
5736
+ child.on("message", onMessage);
5737
+ return {
5738
+ get bootstrapReady() {
5739
+ return bootstrapReady;
5740
+ },
5741
+ get listenerReady() {
5742
+ return listenerReady;
5743
+ },
5744
+ stop() {
5745
+ child.off("message", onMessage);
5746
+ }
5747
+ };
5748
+ }
5749
+ function buildPmChildEnv(record, command, targetPort) {
5750
+ return {
5751
+ ...process.env,
5752
+ ...record.env,
5753
+ ...command.env,
5754
+ ...usesPmProxyController(record) && targetPort ? {
5755
+ [resolvePmProxyEnvVar(record.proxy)]: String(targetPort),
5756
+ ELIT_PM_PUBLIC_PORT: String(record.proxy.port)
5757
+ } : {},
5758
+ ELIT_PM_NAME: record.name,
5759
+ ELIT_PM_ID: record.id
5760
+ };
5761
+ }
5762
+ function buildPmChildStdio(command, onlineStdinShutdownEnabled) {
5763
+ return [
5764
+ onlineStdinShutdownEnabled ? "pipe" : "ignore",
5765
+ "pipe",
5766
+ "pipe",
5767
+ ...command.ipc ? ["ipc"] : []
5768
+ ];
5769
+ }
5770
+ function createPmReadinessMonitor(record, onReady, onFailure, options) {
5771
+ if (options?.ipcController) {
5772
+ let stopped2 = false;
5773
+ let timer2 = null;
5774
+ let timeoutTimer2 = null;
5775
+ const clearTimers2 = () => {
5776
+ if (timer2) {
5777
+ clearInterval(timer2);
5778
+ timer2 = null;
5779
+ }
5780
+ if (timeoutTimer2) {
5781
+ clearTimeout(timeoutTimer2);
5782
+ timeoutTimer2 = null;
5783
+ }
5784
+ };
5785
+ const host = record.proxy?.host ?? "0.0.0.0";
5786
+ const port = record.proxy?.port ?? 0;
5787
+ timer2 = setInterval(() => {
5788
+ if (stopped2 || !options.ipcController?.listenerReady) {
5789
+ return;
5790
+ }
5791
+ stopped2 = true;
5792
+ clearTimers2();
5793
+ onReady(`shared listener ready on ${host}:${port}`);
5794
+ }, 25);
5795
+ timer2.unref?.();
5796
+ timeoutTimer2 = setTimeout(() => {
5797
+ if (stopped2) {
5798
+ return;
5799
+ }
5800
+ stopped2 = true;
5801
+ clearTimers2();
5802
+ onFailure(`listen timeout reached after ${record.listenTimeout}ms while waiting for shared listener ${host}:${port}`);
5803
+ }, record.listenTimeout);
5804
+ timeoutTimer2.unref?.();
5805
+ return {
5806
+ stop() {
5807
+ stopped2 = true;
5808
+ clearTimers2();
5809
+ }
5810
+ };
5811
+ }
5812
+ if (!record.waitReady || !record.healthCheck) {
5813
+ return {
5814
+ stop() {
5815
+ }
5816
+ };
5817
+ }
5818
+ const healthCheck = record.healthCheck;
5819
+ const pollInterval = Math.max(50, Math.min(healthCheck.interval, 250));
5820
+ let stopped = false;
5821
+ let timer = null;
5822
+ let timeoutTimer = null;
5823
+ let inFlight = false;
5824
+ const clearTimers = () => {
5825
+ if (timer) {
5826
+ clearInterval(timer);
5827
+ timer = null;
5828
+ }
5829
+ if (timeoutTimer) {
5830
+ clearTimeout(timeoutTimer);
5831
+ timeoutTimer = null;
5832
+ }
5833
+ };
5834
+ const runHealthCheck = async () => {
5835
+ if (stopped || inFlight) {
5836
+ return;
5837
+ }
5838
+ inFlight = true;
5839
+ const controller = new AbortController();
5840
+ const timeoutId = setTimeout(() => controller.abort(), healthCheck.timeout);
5841
+ timeoutId.unref?.();
5842
+ try {
5843
+ const response = await fetch(healthCheck.url, {
5844
+ method: "GET",
5845
+ signal: controller.signal
5846
+ });
5847
+ if (stopped) {
5848
+ return;
5849
+ }
5850
+ if (!response.ok) {
5851
+ throw new Error(`health check returned ${response.status}`);
5852
+ }
5853
+ stopped = true;
5854
+ clearTimers();
5855
+ onReady(`readiness check passed: ${healthCheck.url}`);
5856
+ } catch {
5857
+ } finally {
5858
+ clearTimeout(timeoutId);
5859
+ inFlight = false;
5860
+ }
5861
+ };
5862
+ timeoutTimer = setTimeout(() => {
5863
+ if (stopped) {
5864
+ return;
5865
+ }
5866
+ stopped = true;
5867
+ clearTimers();
5868
+ onFailure(`listen timeout reached after ${record.listenTimeout}ms while waiting for ${healthCheck.url}`);
5869
+ }, record.listenTimeout);
5870
+ timeoutTimer.unref?.();
5871
+ void runHealthCheck();
5872
+ timer = setInterval(() => {
5873
+ void runHealthCheck();
5874
+ }, pollInterval);
5875
+ timer.unref?.();
5876
+ return {
5877
+ stop() {
5878
+ stopped = true;
5879
+ clearTimers();
5880
+ }
5881
+ };
5882
+ }
5883
+ function resolvePmStopTimeout(record) {
5884
+ if (isPmOnlineWapkRecord(record)) {
5885
+ return Math.max(record.killTimeout, PM_WAPK_ONLINE_SHUTDOWN_TIMEOUT_MS);
5886
+ }
5887
+ return record.killTimeout;
5888
+ }
5889
+ function supportsPmProxyReload(record) {
5890
+ return Boolean(record.proxy) && record.instances === 1;
5891
+ }
5892
+ function buildPmMonitorRecord(record, targetPort) {
5893
+ if (!record.proxy || !targetPort) {
5894
+ return record;
5895
+ }
5896
+ const targetHost = resolvePmProxyTargetHost(record.proxy);
5897
+ return {
5898
+ ...record,
5899
+ proxyTargetPort: targetPort,
5900
+ healthCheck: record.healthCheck ? {
5901
+ ...record.healthCheck,
5902
+ url: rewritePmProxyHealthCheckUrl(record.healthCheck.url, targetHost, targetPort)
5903
+ } : void 0
5904
+ };
5905
+ }
5906
+ async function waitForProcessTermination(pid, timeoutMs) {
5907
+ if (!pid || !isProcessAlive(pid)) {
5908
+ return true;
5909
+ }
5910
+ const deadline = Date.now() + timeoutMs;
5911
+ while (Date.now() < deadline) {
5912
+ if (!isProcessAlive(pid)) {
5913
+ return true;
5914
+ }
5915
+ await delay(DEFAULT_PM_STOP_POLL_MS);
5916
+ }
5917
+ return !isProcessAlive(pid);
5918
+ }
5919
+ async function waitForManagedChildExit(child) {
5920
+ return await new Promise((resolvePromise) => {
5921
+ let resolved = false;
5922
+ child.once("error", (error) => {
5923
+ if (resolved) {
5924
+ return;
5925
+ }
5926
+ resolved = true;
5927
+ resolvePromise({ code: 1, signal: null, error: error instanceof Error ? error.message : String(error) });
5928
+ });
5929
+ child.once("close", (code, signal) => {
5930
+ if (resolved) {
5931
+ return;
5932
+ }
5933
+ resolved = true;
5934
+ resolvePromise({ code, signal });
5935
+ });
5936
+ });
5937
+ }
5938
+ async function createPmChildControllers(record, child, stdoutLog, stderrLog, requestPlannedRestart, onReady, options) {
5939
+ let healthMonitor = {
5940
+ stop() {
5941
+ }
5942
+ };
5943
+ let memoryMonitor = {
5944
+ stop() {
5945
+ }
5946
+ };
5947
+ let scheduleMonitor = {
5948
+ stop() {
5949
+ }
5950
+ };
5951
+ const startHealthMonitor = () => {
5952
+ healthMonitor = createPmHealthMonitor(
5953
+ record,
5954
+ (message) => requestPlannedRestart("health", message),
5955
+ (message) => writePmLog(stdoutLog, message)
5956
+ );
5957
+ };
5958
+ const needsReadySignal = Boolean(options?.ipcController) || record.waitReady;
5959
+ const readinessMonitor = options?.ready || !needsReadySignal ? { stop() {
5960
+ } } : createPmReadinessMonitor(
5961
+ record,
5962
+ (message) => {
5963
+ onReady(message);
5964
+ startHealthMonitor();
5965
+ },
5966
+ (message) => requestPlannedRestart("startup", message),
5967
+ { ipcController: options?.ipcController }
5968
+ );
5969
+ const watchController = await createPmWatchController(
5970
+ record,
5971
+ (changedPath) => requestPlannedRestart("watch", changedPath),
5972
+ (message) => writePmLog(stderrLog, `watch error: ${message}`)
5973
+ );
5974
+ memoryMonitor = createPmMemoryMonitor(
5975
+ record,
5976
+ child.pid,
5977
+ (kind, message) => requestPlannedRestart(kind, message)
5978
+ );
5979
+ scheduleMonitor = createPmScheduleMonitor(
5980
+ record,
5981
+ (message) => requestPlannedRestart("cron", message),
5982
+ (message) => writePmLog(stdoutLog, message)
5983
+ );
5984
+ if (options?.ready || !needsReadySignal) {
5985
+ startHealthMonitor();
5986
+ }
5987
+ return {
5988
+ async stop() {
5989
+ await watchController.close();
5990
+ readinessMonitor.stop();
5991
+ healthMonitor.stop();
5992
+ memoryMonitor.stop();
5993
+ scheduleMonitor.stop();
5994
+ }
5995
+ };
5996
+ }
5997
+ async function waitForPmChildReady(record, child, ipcController) {
5998
+ if (!ipcController && (!record.waitReady || !record.healthCheck)) {
5999
+ return { ready: true };
6000
+ }
6001
+ let readyMessage;
6002
+ let failureMessage;
6003
+ let exitResult;
6004
+ const readinessMonitor = createPmReadinessMonitor(
6005
+ record,
6006
+ (message) => {
6007
+ readyMessage = message;
6008
+ },
6009
+ (message) => {
6010
+ failureMessage = message;
6011
+ },
6012
+ { ipcController }
6013
+ );
6014
+ void waitForManagedChildExit(child).then((result) => {
6015
+ exitResult = result;
6016
+ });
6017
+ while (!readyMessage && !failureMessage && !exitResult) {
6018
+ await delay(25);
6019
+ }
6020
+ readinessMonitor.stop();
6021
+ if (readyMessage) {
6022
+ return { ready: true, message: readyMessage };
6023
+ }
6024
+ return {
6025
+ ready: false,
6026
+ message: failureMessage,
6027
+ exitResult
6028
+ };
6029
+ }
6030
+ async function stopProxyManagedChild(child, record, stderrLog) {
6031
+ if (!child.pid || !isProcessAlive(child.pid)) {
6032
+ return;
6033
+ }
6034
+ terminateProcessTree(child.pid);
6035
+ const stopTimeout = resolvePmStopTimeout(record);
6036
+ const stopped = await waitForProcessTermination(child.pid, stopTimeout);
6037
+ if (!stopped && child.pid && isProcessAlive(child.pid)) {
6038
+ writePmLog(stderrLog, `proxy handoff shutdown timed out after ${stopTimeout}ms; forcing process termination`);
6039
+ terminateProcessTree(child.pid, { force: true });
6040
+ await waitForProcessTermination(child.pid, DEFAULT_PM_STOP_POLL_MS);
6041
+ }
6042
+ }
6043
+ async function createPmWatchController(record, onChange, onError) {
6044
+ if (!record.watch || record.watchPaths.length === 0) {
6045
+ return {
6046
+ async close() {
6047
+ }
6048
+ };
6049
+ }
6050
+ const watcher = watch(record.watchPaths);
6051
+ let debounceTimer = null;
6052
+ const scheduleRestart = (filePath) => {
6053
+ const normalizedPath = filePath.replace(/\\/g, "/");
6054
+ if (isIgnoredWatchPath(normalizedPath, record.watchIgnore)) {
6055
+ return;
6056
+ }
6057
+ if (debounceTimer) {
6058
+ clearTimeout(debounceTimer);
6059
+ }
6060
+ debounceTimer = setTimeout(() => {
6061
+ debounceTimer = null;
6062
+ onChange(normalizedPath);
6063
+ }, record.watchDebounce);
6064
+ debounceTimer.unref?.();
6065
+ };
6066
+ watcher.on("add", scheduleRestart);
6067
+ watcher.on("change", scheduleRestart);
6068
+ watcher.on("unlink", scheduleRestart);
6069
+ watcher.on("error", (error) => onError(error instanceof Error ? error.message : String(error)));
6070
+ return {
6071
+ async close() {
6072
+ if (debounceTimer) {
4736
6073
  clearTimeout(debounceTimer);
4737
6074
  debounceTimer = null;
4738
6075
  }
@@ -4766,11 +6103,17 @@ error: ${text}`);
4766
6103
  method: "GET",
4767
6104
  signal: controller.signal
4768
6105
  });
6106
+ if (stopped) {
6107
+ return;
6108
+ }
4769
6109
  if (!response.ok) {
4770
6110
  throw new Error(`health check returned ${response.status}`);
4771
6111
  }
4772
6112
  failureCount = 0;
4773
6113
  } catch (error) {
6114
+ if (stopped) {
6115
+ return;
6116
+ }
4774
6117
  failureCount += 1;
4775
6118
  const message = error instanceof Error ? error.message : String(error);
4776
6119
  onLog(`health check failed (${failureCount}/${healthCheck.maxFailures}): ${message}`);
@@ -4805,6 +6148,90 @@ error: ${text}`);
4805
6148
  }
4806
6149
  };
4807
6150
  }
6151
+ function createPmMemoryMonitor(record, pid, onFailure) {
6152
+ if (!record.maxMemoryBytes || !pid) {
6153
+ return {
6154
+ stop() {
6155
+ }
6156
+ };
6157
+ }
6158
+ const memoryLimit = record.maxMemoryBytes;
6159
+ let stopped = false;
6160
+ const timer = setInterval(() => {
6161
+ if (stopped) {
6162
+ return;
6163
+ }
6164
+ const memoryRssBytes = samplePmProcessMetrics(pid).memoryRssBytes;
6165
+ if (memoryRssBytes === void 0 || memoryRssBytes <= memoryLimit) {
6166
+ return;
6167
+ }
6168
+ stopped = true;
6169
+ onFailure(record.memoryAction === "stop" ? "memory-stop" : "memory", `memory usage ${memoryRssBytes} exceeded limit ${memoryLimit}`);
6170
+ }, DEFAULT_PM_MEMORY_CHECK_INTERVAL);
6171
+ timer.unref?.();
6172
+ return {
6173
+ stop() {
6174
+ stopped = true;
6175
+ clearInterval(timer);
6176
+ }
6177
+ };
6178
+ }
6179
+ function createPmScheduleMonitor(record, onTrigger, onLog) {
6180
+ if (!record.cronRestart) {
6181
+ return {
6182
+ stop() {
6183
+ }
6184
+ };
6185
+ }
6186
+ const schedule = parsePmRestartSchedule(record.cronRestart, "pm cronRestart");
6187
+ let stopped = false;
6188
+ let timer = null;
6189
+ const armTimer = (from = /* @__PURE__ */ new Date()) => {
6190
+ const nextOccurrence = resolveNextPmScheduleOccurrence(schedule, from);
6191
+ if (!nextOccurrence) {
6192
+ onLog(`schedule has no next occurrence: ${record.cronRestart}`);
6193
+ return;
6194
+ }
6195
+ const delayMs = Math.max(0, nextOccurrence.getTime() - Date.now());
6196
+ timer = setTimeout(() => {
6197
+ timer = null;
6198
+ if (stopped) {
6199
+ return;
6200
+ }
6201
+ onTrigger(`restart schedule matched: ${record.cronRestart}`);
6202
+ }, delayMs);
6203
+ timer.unref?.();
6204
+ };
6205
+ armTimer();
6206
+ return {
6207
+ stop() {
6208
+ stopped = true;
6209
+ if (timer) {
6210
+ clearTimeout(timer);
6211
+ timer = null;
6212
+ }
6213
+ }
6214
+ };
6215
+ }
6216
+ function resolvePmRestartDelay(record, restartCount, shouldApplyBackoff) {
6217
+ if (!shouldApplyBackoff || !record.expBackoffRestartDelay) {
6218
+ return record.restartDelay;
6219
+ }
6220
+ const exponent = Math.max(0, restartCount - 1);
6221
+ return Math.min(record.expBackoffRestartDelay * 2 ** exponent, record.expBackoffRestartMaxDelay ?? DEFAULT_PM_EXP_BACKOFF_MAX_DELAY);
6222
+ }
6223
+ function resolvePmRestartCountBase(record, wasStable, restartKind) {
6224
+ if (wasStable) {
6225
+ return 0;
6226
+ }
6227
+ if (record.restartWindow && record.lastRestartAt) {
6228
+ const lastRestartTime = Date.parse(record.lastRestartAt);
6229
+ if (!Number.isNaN(lastRestartTime) && Date.now() - lastRestartTime > record.restartWindow) {
6230
+ return 0;
6231
+ }
6232
+ }
6233
+ return restartKind === "watch" ? record.restartCount ?? 0 : record.restartCount ?? 0;
6234
+ }
4808
6235
  function readPlannedRestartRequest(state) {
4809
6236
  return state.request;
4810
6237
  }
@@ -4818,6 +6245,16 @@ error: ${text}`);
4818
6245
  (0, import_node_fs4.mkdirSync)((0, import_node_path6.dirname)(initialRecord.logFiles.err), { recursive: true });
4819
6246
  const stdoutLog = (0, import_node_fs4.createWriteStream)(initialRecord.logFiles.out, { flags: "a" });
4820
6247
  const stderrLog = (0, import_node_fs4.createWriteStream)(initialRecord.logFiles.err, { flags: "a" });
6248
+ let proxyController = null;
6249
+ let proxyTargetSyncTimer = null;
6250
+ let inheritedListener = null;
6251
+ const runnerPaths = resolveRunnerPathsFromRecordFile(filePath);
6252
+ const syncOwnedProxyTargets = (baseName) => {
6253
+ if (!proxyController) {
6254
+ return;
6255
+ }
6256
+ proxyController.setTargets(resolvePmProxyTargetUrls(runnerPaths, baseName));
6257
+ };
4821
6258
  const persist = (mutator) => {
4822
6259
  const current = readLatestPmRecord(filePath, record);
4823
6260
  record = mutator(current);
@@ -4830,26 +6267,30 @@ error: ${text}`);
4830
6267
  activeChildStopTimer = null;
4831
6268
  }
4832
6269
  };
6270
+ const scheduleForcedActiveChildStop = (timeoutMs, reason) => {
6271
+ if (!activeChild?.pid || process.platform === "win32") {
6272
+ return;
6273
+ }
6274
+ clearActiveChildStopTimer();
6275
+ activeChildStopTimer = setTimeout(() => {
6276
+ if (activeChild?.pid && isProcessAlive(activeChild.pid)) {
6277
+ writePmLog(stderrLog, `${reason} after ${timeoutMs}ms; forcing process termination`);
6278
+ terminateProcessTree(activeChild.pid, { force: true });
6279
+ }
6280
+ }, timeoutMs);
6281
+ activeChildStopTimer.unref?.();
6282
+ };
4833
6283
  const stopActiveChild = () => {
4834
6284
  if (!activeChild?.pid || !isProcessAlive(activeChild.pid)) {
4835
6285
  return;
4836
6286
  }
4837
6287
  const current = readLatestPmRecord(filePath, record);
6288
+ const stopTimeout = resolvePmStopTimeout(current);
4838
6289
  if (isPmOnlineWapkRecord(current) && activeChild.stdin && !activeChild.stdin.destroyed && activeChild.stdin.writable) {
4839
6290
  try {
4840
6291
  activeChild.stdin.end(`${PM_WAPK_ONLINE_SHUTDOWN_COMMAND}
4841
6292
  `);
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?.();
6293
+ scheduleForcedActiveChildStop(stopTimeout, "graceful WAPK online shutdown timed out");
4853
6294
  return;
4854
6295
  } catch (error) {
4855
6296
  const message = error instanceof Error ? error.message : String(error);
@@ -4857,6 +6298,7 @@ error: ${text}`);
4857
6298
  }
4858
6299
  }
4859
6300
  terminateProcessTree(activeChild.pid);
6301
+ scheduleForcedActiveChildStop(stopTimeout, "graceful shutdown timed out");
4860
6302
  };
4861
6303
  const requestManagedStop = (reason) => {
4862
6304
  if (stopRequested) {
@@ -4894,6 +6336,17 @@ error: ${text}`);
4894
6336
  let command;
4895
6337
  try {
4896
6338
  command = buildPmCommand(latest);
6339
+ if (latest.proxy && isPmProxyOwner(latest) && !proxyController) {
6340
+ proxyController = await createPmProxyController(latest.proxy);
6341
+ syncOwnedProxyTargets(latest.baseName);
6342
+ if (!proxyTargetSyncTimer) {
6343
+ proxyTargetSyncTimer = setInterval(() => syncOwnedProxyTargets(latest.baseName), 50);
6344
+ proxyTargetSyncTimer.unref?.();
6345
+ }
6346
+ }
6347
+ if (latest.proxy && usesPmInheritedListener(latest) && !inheritedListener) {
6348
+ inheritedListener = await createPmInheritedListener(latest.proxy);
6349
+ }
4897
6350
  } catch (error) {
4898
6351
  const message = error instanceof Error ? error.message : String(error);
4899
6352
  writePmLog(stderrLog, message);
@@ -4903,25 +6356,24 @@ error: ${text}`);
4903
6356
  error: message,
4904
6357
  runnerPid: void 0,
4905
6358
  childPid: void 0,
6359
+ proxyTargetPort: void 0,
6360
+ proxyReadyAt: void 0,
4906
6361
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
4907
6362
  }));
4908
6363
  return;
4909
6364
  }
4910
6365
  const onlineStdinShutdownEnabled = isPmOnlineWapkRecord(latest);
4911
- const child = (0, import_node_child_process2.spawn)(command.command, command.args, {
6366
+ const initialTargetPort = usesPmProxyController(latest) ? await allocatePmProxyTargetPort(resolvePmProxyTargetHost(latest.proxy)) : void 0;
6367
+ const monitorRecord = buildPmMonitorRecord(latest, initialTargetPort);
6368
+ let child = (0, import_node_child_process2.spawn)(command.command, command.args, {
4912
6369
  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"],
6370
+ env: buildPmChildEnv(latest, command, initialTargetPort),
6371
+ stdio: buildPmChildStdio(command, onlineStdinShutdownEnabled),
4921
6372
  windowsHide: true,
4922
6373
  shell: command.shell
4923
6374
  });
4924
- const childStartedAt = Date.now();
6375
+ let childStartedAt = Date.now();
6376
+ let childIpcState = command.ipc ? createPmChildIpcState(child, inheritedListener) : void 0;
4925
6377
  activeChild = child;
4926
6378
  if (child.stdout) {
4927
6379
  child.stdout.pipe(stdoutLog, { end: false });
@@ -4929,26 +6381,38 @@ error: ${text}`);
4929
6381
  if (child.stderr) {
4930
6382
  child.stderr.pipe(stderrLog, { end: false });
4931
6383
  }
6384
+ let childWaitState = { settled: false, result: void 0 };
6385
+ void waitForManagedChildExit(child).then((result) => {
6386
+ childWaitState.result = result;
6387
+ childWaitState.settled = true;
6388
+ });
4932
6389
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
6390
+ const waitingForReady = latest.waitReady || Boolean(childIpcState);
4933
6391
  persist((current2) => ({
4934
6392
  ...current2,
4935
- status: "online",
6393
+ status: waitingForReady ? "starting" : "online",
4936
6394
  commandPreview: command.preview,
4937
6395
  runtime: command.runtime ?? current2.runtime,
4938
6396
  runnerPid: process.pid,
4939
6397
  childPid: child.pid,
6398
+ proxyTargetPort: initialTargetPort,
6399
+ proxyReadyAt: !waitingForReady && usesPmProxyController(latest) ? startedAt : void 0,
4940
6400
  startedAt,
4941
6401
  stoppedAt: void 0,
6402
+ reloadRequestedAt: void 0,
4942
6403
  error: void 0,
4943
6404
  updatedAt: startedAt
4944
6405
  }));
4945
6406
  writePmLog(stdoutLog, `started ${command.preview}${child.pid ? ` (pid ${child.pid})` : ""}`);
6407
+ if (isPmProxyOwner(latest) && !waitingForReady) {
6408
+ syncOwnedProxyTargets(latest.baseName);
6409
+ }
4946
6410
  const requestPlannedRestart = (kind, detail) => {
4947
6411
  if (stopRequested || restartState.request) {
4948
6412
  return;
4949
6413
  }
4950
6414
  restartState.request = { kind, detail };
4951
- writePmLog(kind === "health" ? stderrLog : stdoutLog, `${kind} restart requested: ${detail}`);
6415
+ writePmLog(kind === "watch" || kind === "cron" ? stdoutLog : stderrLog, `${kind} restart requested: ${detail}`);
4952
6416
  persist((current2) => ({
4953
6417
  ...current2,
4954
6418
  status: "restarting",
@@ -4956,27 +6420,124 @@ error: ${text}`);
4956
6420
  }));
4957
6421
  stopActiveChild();
4958
6422
  };
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)
6423
+ let controllers = await createPmChildControllers(
6424
+ monitorRecord,
6425
+ child,
6426
+ stdoutLog,
6427
+ stderrLog,
6428
+ requestPlannedRestart,
6429
+ (message) => {
6430
+ const readyAt = (/* @__PURE__ */ new Date()).toISOString();
6431
+ if (isPmProxyOwner(latest)) {
6432
+ syncOwnedProxyTargets(latest.baseName);
6433
+ }
6434
+ persist((current2) => ({
6435
+ ...current2,
6436
+ status: "online",
6437
+ proxyTargetPort: initialTargetPort,
6438
+ proxyReadyAt: readyAt,
6439
+ updatedAt: readyAt
6440
+ }));
6441
+ writePmLog(stdoutLog, message);
6442
+ },
6443
+ { ready: !waitingForReady, ipcController: childIpcState }
4968
6444
  );
4969
- const desiredStatePoller = setInterval(() => {
6445
+ let handledReloadAt = latest.reloadRequestedAt;
6446
+ while (!childWaitState.settled) {
4970
6447
  const latestRecord = readLatestPmRecord(filePath, record);
4971
- if (!stopRequested && latestRecord.desiredState === "stopped") {
6448
+ if (latestRecord.desiredState === "stopped" && !stopRequested) {
4972
6449
  requestManagedStop("stop requested by PM control state");
4973
6450
  }
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();
6451
+ const reloadRequestedAt = latestRecord.reloadRequestedAt;
6452
+ if (!stopRequested && supportsPmProxyReload(latestRecord) && reloadRequestedAt && reloadRequestedAt !== handledReloadAt && latestRecord.proxy) {
6453
+ handledReloadAt = reloadRequestedAt;
6454
+ const replacementTargetPort = usesPmProxyController(latestRecord) ? await allocatePmProxyTargetPort(resolvePmProxyTargetHost(latestRecord.proxy)) : void 0;
6455
+ const replacementMonitorRecord = buildPmMonitorRecord(latestRecord, replacementTargetPort);
6456
+ const replacementChild = (0, import_node_child_process2.spawn)(command.command, command.args, {
6457
+ cwd: latestRecord.cwd,
6458
+ env: buildPmChildEnv(latestRecord, command, replacementTargetPort),
6459
+ stdio: buildPmChildStdio(command, onlineStdinShutdownEnabled),
6460
+ windowsHide: true,
6461
+ shell: command.shell
6462
+ });
6463
+ const replacementIpcState = command.ipc ? createPmChildIpcState(replacementChild, inheritedListener) : void 0;
6464
+ if (replacementChild.stdout) {
6465
+ replacementChild.stdout.pipe(stdoutLog, { end: false });
6466
+ }
6467
+ if (replacementChild.stderr) {
6468
+ replacementChild.stderr.pipe(stderrLog, { end: false });
6469
+ }
6470
+ writePmLog(stdoutLog, `starting ${usesPmInheritedListener(latestRecord) ? "shared-listener" : "proxy handoff"} replacement${replacementChild.pid ? ` (pid ${replacementChild.pid})` : ""}`);
6471
+ const readyResult = await waitForPmChildReady(replacementMonitorRecord, replacementChild, replacementIpcState);
6472
+ if (!readyResult.ready) {
6473
+ writePmLog(stderrLog, readyResult.message ?? "replacement exited before becoming ready");
6474
+ replacementIpcState?.stop();
6475
+ await stopProxyManagedChild(replacementChild, latestRecord, stderrLog);
6476
+ persist((current2) => ({
6477
+ ...current2,
6478
+ status: "online",
6479
+ proxyReadyAt: current2.proxyReadyAt,
6480
+ reloadRequestedAt: void 0,
6481
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
6482
+ }));
6483
+ continue;
6484
+ }
6485
+ const previousChild = child;
6486
+ const previousControllers = controllers;
6487
+ const previousIpcState = childIpcState;
6488
+ const handoffAt = (/* @__PURE__ */ new Date()).toISOString();
6489
+ if (usesPmProxyController(latestRecord) && replacementTargetPort) {
6490
+ proxyController?.setTarget(buildPmProxyTargetUrl(latestRecord.proxy, replacementTargetPort));
6491
+ }
6492
+ persist((current2) => ({
6493
+ ...current2,
6494
+ status: "online",
6495
+ childPid: replacementChild.pid,
6496
+ proxyTargetPort: replacementTargetPort,
6497
+ proxyReadyAt: handoffAt,
6498
+ startedAt: handoffAt,
6499
+ reloadRequestedAt: void 0,
6500
+ error: void 0,
6501
+ updatedAt: handoffAt
6502
+ }));
6503
+ if (readyResult.message) {
6504
+ writePmLog(stdoutLog, readyResult.message);
6505
+ }
6506
+ writePmLog(stdoutLog, `${usesPmInheritedListener(latestRecord) ? "shared listener" : "proxy handoff"} activated on ${latestRecord.proxy.host ?? "0.0.0.0"}:${latestRecord.proxy.port}`);
6507
+ child = replacementChild;
6508
+ childIpcState = replacementIpcState;
6509
+ childStartedAt = Date.now();
6510
+ activeChild = replacementChild;
6511
+ childWaitState = { settled: false, result: void 0 };
6512
+ void waitForManagedChildExit(replacementChild).then((result) => {
6513
+ childWaitState.result = result;
6514
+ childWaitState.settled = true;
6515
+ });
6516
+ controllers = await createPmChildControllers(
6517
+ replacementMonitorRecord,
6518
+ replacementChild,
6519
+ stdoutLog,
6520
+ stderrLog,
6521
+ requestPlannedRestart,
6522
+ () => {
6523
+ },
6524
+ { ready: true, ipcController: replacementIpcState }
6525
+ );
6526
+ await delay(250);
6527
+ await previousControllers.stop();
6528
+ previousIpcState?.stop();
6529
+ await stopProxyManagedChild(previousChild, latestRecord, stderrLog);
6530
+ clearActiveChildStopTimer();
6531
+ if (isPmProxyOwner(latestRecord)) {
6532
+ syncOwnedProxyTargets(latestRecord.baseName);
6533
+ }
6534
+ continue;
6535
+ }
6536
+ await delay(25);
6537
+ }
6538
+ const exitResult = childWaitState.result;
6539
+ await controllers.stop();
6540
+ childIpcState?.stop();
4980
6541
  clearActiveChildStopTimer();
4981
6542
  activeChild = null;
4982
6543
  const exitCode = waitForExit(exitResult.code, exitResult.signal);
@@ -4992,56 +6553,77 @@ error: ${text}`);
4992
6553
  if (stopRequested || current.desiredState === "stopped") {
4993
6554
  break;
4994
6555
  }
4995
- const shouldRestartForExit = plannedRestart ? true : current.restartPolicy === "always" ? true : current.restartPolicy === "on-failure" ? exitCode !== 0 || Boolean(exitResult.error) : false;
6556
+ const shouldRestartForExit = plannedRestart?.kind === "memory-stop" ? false : plannedRestart ? true : current.restartPolicy === "always" ? true : current.restartPolicy === "on-failure" ? exitCode !== 0 || Boolean(exitResult.error) : false;
4996
6557
  if (!shouldRestartForExit) {
4997
6558
  persist((latestRecord) => ({
4998
6559
  ...latestRecord,
4999
- status: exitCode === 0 && !exitResult.error ? "exited" : "errored",
6560
+ status: plannedRestart?.kind === "memory-stop" ? "errored" : exitCode === 0 && !exitResult.error ? "exited" : "errored",
5000
6561
  childPid: void 0,
6562
+ proxyTargetPort: void 0,
6563
+ proxyReadyAt: void 0,
5001
6564
  runnerPid: void 0,
5002
6565
  lastExitCode: exitCode,
5003
- error: exitCode === 0 && !exitResult.error ? void 0 : exitResult.error ?? `Process exited with code ${exitCode}.`,
6566
+ reloadRequestedAt: void 0,
6567
+ error: plannedRestart?.kind === "memory-stop" ? plannedRestart.detail : exitCode === 0 && !exitResult.error ? void 0 : exitResult.error ?? `Process exited with code ${exitCode}.`,
5004
6568
  stoppedAt: (/* @__PURE__ */ new Date()).toISOString(),
5005
6569
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
5006
6570
  }));
6571
+ if (isPmProxyOwner(current)) {
6572
+ syncOwnedProxyTargets(current.baseName);
6573
+ }
5007
6574
  return;
5008
6575
  }
5009
6576
  const shouldCountRestart = plannedRestart?.kind !== "watch";
5010
- const baseRestartCount = wasStable ? 0 : current.restartCount ?? 0;
6577
+ const baseRestartCount = resolvePmRestartCountBase(current, wasStable, plannedRestart?.kind);
5011
6578
  const nextRestartCount = shouldCountRestart ? baseRestartCount + 1 : current.restartCount ?? 0;
5012
6579
  if (nextRestartCount > current.maxRestarts) {
5013
6580
  persist((latestRecord) => ({
5014
6581
  ...latestRecord,
5015
6582
  status: "errored",
5016
6583
  childPid: void 0,
6584
+ proxyTargetPort: void 0,
6585
+ proxyReadyAt: void 0,
5017
6586
  runnerPid: void 0,
5018
6587
  restartCount: nextRestartCount,
6588
+ lastRestartAt: (/* @__PURE__ */ new Date()).toISOString(),
5019
6589
  lastExitCode: exitCode,
6590
+ reloadRequestedAt: void 0,
5020
6591
  error: plannedRestart ? `Reached max restart attempts (${current.maxRestarts}) after ${plannedRestart.kind} restart requests.` : `Reached max restart attempts (${current.maxRestarts}).`,
5021
6592
  stoppedAt: (/* @__PURE__ */ new Date()).toISOString(),
5022
6593
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
5023
6594
  }));
5024
6595
  writePmLog(stderrLog, `max restart attempts reached (${current.maxRestarts})`);
6596
+ if (isPmProxyOwner(current)) {
6597
+ syncOwnedProxyTargets(current.baseName);
6598
+ }
5025
6599
  return;
5026
6600
  }
5027
6601
  persist((latestRecord) => ({
5028
6602
  ...latestRecord,
5029
6603
  status: "restarting",
5030
6604
  childPid: void 0,
6605
+ proxyTargetPort: void 0,
6606
+ proxyReadyAt: void 0,
5031
6607
  lastExitCode: exitCode,
5032
6608
  restartCount: nextRestartCount,
6609
+ lastRestartAt: (/* @__PURE__ */ new Date()).toISOString(),
6610
+ reloadRequestedAt: void 0,
5033
6611
  error: void 0,
5034
6612
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
5035
6613
  }));
6614
+ if (isPmProxyOwner(current)) {
6615
+ syncOwnedProxyTargets(current.baseName);
6616
+ }
6617
+ const resolvedRestartDelay = resolvePmRestartDelay(current, nextRestartCount, shouldCountRestart && !wasStable && plannedRestart?.kind !== "watch");
5036
6618
  if (plannedRestart) {
5037
6619
  writePmLog(
5038
- plannedRestart.kind === "health" ? stderrLog : stdoutLog,
5039
- `restarting in ${current.restartDelay}ms after ${plannedRestart.kind}: ${plannedRestart.detail}`
6620
+ plannedRestart.kind === "health" || plannedRestart.kind === "memory" || plannedRestart.kind === "memory-stop" || plannedRestart.kind === "startup" ? stderrLog : stdoutLog,
6621
+ `restarting in ${resolvedRestartDelay}ms after ${plannedRestart.kind}: ${plannedRestart.detail}`
5040
6622
  );
5041
6623
  } else {
5042
- writePmLog(stdoutLog, `restarting in ${current.restartDelay}ms`);
6624
+ writePmLog(stdoutLog, `restarting in ${resolvedRestartDelay}ms`);
5043
6625
  }
5044
- await delay(current.restartDelay);
6626
+ await delay(resolvedRestartDelay);
5045
6627
  }
5046
6628
  } finally {
5047
6629
  stopRequested = true;
@@ -5054,11 +6636,26 @@ error: ${text}`);
5054
6636
  status: finalRecord.status === "errored" ? "errored" : finalRecord.status === "exited" ? "exited" : "stopped",
5055
6637
  runnerPid: void 0,
5056
6638
  childPid: void 0,
6639
+ proxyTargetPort: void 0,
6640
+ proxyReadyAt: void 0,
6641
+ reloadRequestedAt: void 0,
5057
6642
  stoppedAt: (/* @__PURE__ */ new Date()).toISOString(),
5058
6643
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
5059
6644
  });
5060
6645
  process.off("SIGINT", handleStopSignal);
5061
6646
  process.off("SIGTERM", handleStopSignal);
6647
+ if (proxyTargetSyncTimer) {
6648
+ clearInterval(proxyTargetSyncTimer);
6649
+ proxyTargetSyncTimer = null;
6650
+ }
6651
+ if (proxyController) {
6652
+ await proxyController.close().catch(() => void 0);
6653
+ proxyController = null;
6654
+ }
6655
+ if (inheritedListener) {
6656
+ await new Promise((resolvePromise) => inheritedListener?.close(() => resolvePromise()));
6657
+ inheritedListener = null;
6658
+ }
5062
6659
  await new Promise((resolvePromise) => stdoutLog.end(resolvePromise));
5063
6660
  await new Promise((resolvePromise) => stderrLog.end(resolvePromise));
5064
6661
  }
@@ -5112,23 +6709,26 @@ error: ${text}`);
5112
6709
  stoppedAt: (/* @__PURE__ */ new Date()).toISOString()
5113
6710
  };
5114
6711
  writePmRecord(match.filePath, updated);
5115
- const runnerStopped = await waitForProcessTermination(match.record.runnerPid, DEFAULT_PM_STOP_GRACE_PERIOD_MS);
6712
+ const stopTimeout = resolvePmStopTimeout(match.record);
6713
+ const runnerStopped = await waitForProcessTermination(match.record.runnerPid, stopTimeout);
5116
6714
  const childStopped = await waitForProcessTermination(
5117
6715
  match.record.childPid,
5118
- runnerStopped ? DEFAULT_PM_STOP_POLL_MS : DEFAULT_PM_STOP_GRACE_PERIOD_MS
6716
+ runnerStopped ? DEFAULT_PM_STOP_POLL_MS : stopTimeout
5119
6717
  );
5120
6718
  if (!runnerStopped && match.record.runnerPid && isProcessAlive(match.record.runnerPid)) {
5121
- terminateProcessTree(match.record.runnerPid);
6719
+ terminateProcessTree(match.record.runnerPid, { force: true });
5122
6720
  await waitForProcessTermination(match.record.runnerPid, DEFAULT_PM_STOP_POLL_MS);
5123
6721
  }
5124
6722
  if (!childStopped && match.record.childPid && isProcessAlive(match.record.childPid)) {
5125
- terminateProcessTree(match.record.childPid);
6723
+ terminateProcessTree(match.record.childPid, { force: true });
5126
6724
  await waitForProcessTermination(match.record.childPid, DEFAULT_PM_STOP_POLL_MS);
5127
6725
  }
5128
6726
  writePmRecord(match.filePath, {
5129
6727
  ...updated,
5130
6728
  runnerPid: void 0,
5131
6729
  childPid: void 0,
6730
+ proxyTargetPort: void 0,
6731
+ reloadRequestedAt: void 0,
5132
6732
  status: "stopped",
5133
6733
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
5134
6734
  });
@@ -5197,13 +6797,13 @@ error: ${text}`);
5197
6797
 
5198
6798
  // src/shares/workspace-package/roots.ts
5199
6799
  function findPackageDirectory(startDir, resolveMatch) {
5200
- let currentDir = resolve4(startDir);
6800
+ let currentDir = resolve5(startDir);
5201
6801
  while (true) {
5202
6802
  const match = resolveMatch(currentDir);
5203
6803
  if (match) {
5204
6804
  return match;
5205
6805
  }
5206
- const parentDir = dirname2(currentDir);
6806
+ const parentDir = dirname3(currentDir);
5207
6807
  if (parentDir === currentDir) {
5208
6808
  return void 0;
5209
6809
  }
@@ -5231,17 +6831,17 @@ error: ${text}`);
5231
6831
  function getWorkspacePackageImportCandidates(packageRoot, specifier, options = {}) {
5232
6832
  const subpath = specifier === "elit" ? "index" : specifier.slice("elit/".length);
5233
6833
  const builtCandidates = options.preferredBuiltFormat === "cjs" ? [
5234
- resolve4(packageRoot, "dist", `${subpath}.cjs`),
5235
- resolve4(packageRoot, "dist", `${subpath}.js`),
5236
- resolve4(packageRoot, "dist", `${subpath}.mjs`)
6834
+ resolve5(packageRoot, "dist", `${subpath}.cjs`),
6835
+ resolve5(packageRoot, "dist", `${subpath}.js`),
6836
+ resolve5(packageRoot, "dist", `${subpath}.mjs`)
5237
6837
  ] : [
5238
- resolve4(packageRoot, "dist", `${subpath}.mjs`),
5239
- resolve4(packageRoot, "dist", `${subpath}.js`),
5240
- resolve4(packageRoot, "dist", `${subpath}.cjs`)
6838
+ resolve5(packageRoot, "dist", `${subpath}.mjs`),
6839
+ resolve5(packageRoot, "dist", `${subpath}.js`),
6840
+ resolve5(packageRoot, "dist", `${subpath}.cjs`)
5241
6841
  ];
5242
6842
  const sourceCandidates = [
5243
- resolve4(packageRoot, "src", `${subpath}.ts`),
5244
- resolve4(packageRoot, "src", `${subpath}.tsx`)
6843
+ resolve5(packageRoot, "src", `${subpath}.ts`),
6844
+ resolve5(packageRoot, "src", `${subpath}.tsx`)
5245
6845
  ];
5246
6846
  return options.preferBuilt ? [...builtCandidates, ...sourceCandidates] : [...sourceCandidates, ...builtCandidates];
5247
6847
  }
@@ -5276,7 +6876,7 @@ error: ${text}`);
5276
6876
  // src/shares/config/loader.ts
5277
6877
  function resolveConfigPath(cwd = process.cwd()) {
5278
6878
  for (const configFile of ELIT_CONFIG_FILES) {
5279
- const configPath = resolve4(cwd, configFile);
6879
+ const configPath = resolve5(cwd, configFile);
5280
6880
  if (existsSync4(configPath)) {
5281
6881
  return configPath;
5282
6882
  }
@@ -5305,7 +6905,7 @@ error: ${text}`);
5305
6905
  if (ext === "ts" || ext === "mts") {
5306
6906
  try {
5307
6907
  const { build } = await Promise.resolve().then(() => __toESM(require_main()));
5308
- const configDir = dirname2(configPath);
6908
+ const configDir = dirname3(configPath);
5309
6909
  const tempFile = join5(configDir, `.elit-config-${Date.now()}.mjs`);
5310
6910
  const externalAllPlugin = {
5311
6911
  name: "external-all",
@@ -5379,6 +6979,43 @@ error: ${text}`);
5379
6979
  }
5380
6980
 
5381
6981
  // src/cli/pm/commands.ts
6982
+ var PM_SIGNAL_NAMES = /* @__PURE__ */ new Set([
6983
+ "SIGABRT",
6984
+ "SIGALRM",
6985
+ "SIGBREAK",
6986
+ "SIGBUS",
6987
+ "SIGCHLD",
6988
+ "SIGCONT",
6989
+ "SIGFPE",
6990
+ "SIGHUP",
6991
+ "SIGILL",
6992
+ "SIGINT",
6993
+ "SIGIO",
6994
+ "SIGIOT",
6995
+ "SIGKILL",
6996
+ "SIGPIPE",
6997
+ "SIGPOLL",
6998
+ "SIGPROF",
6999
+ "SIGPWR",
7000
+ "SIGQUIT",
7001
+ "SIGSEGV",
7002
+ "SIGSTKFLT",
7003
+ "SIGSTOP",
7004
+ "SIGSYS",
7005
+ "SIGTERM",
7006
+ "SIGTRAP",
7007
+ "SIGTSTP",
7008
+ "SIGTTIN",
7009
+ "SIGTTOU",
7010
+ "SIGUNUSED",
7011
+ "SIGURG",
7012
+ "SIGUSR1",
7013
+ "SIGUSR2",
7014
+ "SIGVTALRM",
7015
+ "SIGWINCH",
7016
+ "SIGXCPU",
7017
+ "SIGXFSZ"
7018
+ ]);
5382
7019
  async function runPmStart(args) {
5383
7020
  const parsed = parsePmStartArgs(args);
5384
7021
  const workspaceRoot = process.cwd();
@@ -5411,9 +7048,130 @@ error: ${text}`);
5411
7048
  if (value === "all") {
5412
7049
  return listPmRecordMatches(paths).map(syncPmRecordLiveness);
5413
7050
  }
7051
+ const groupMatches = findPmGroupMatches(paths, value);
7052
+ if (groupMatches.length > 0) {
7053
+ return groupMatches.map(syncPmRecordLiveness);
7054
+ }
5414
7055
  const match = findPmRecordMatch(paths, value);
5415
7056
  return match ? [syncPmRecordLiveness(match)] : [];
5416
7057
  }
7058
+ function sortPmMatchesByInstance(matches) {
7059
+ return [...matches].sort((left, right) => left.record.instanceIndex - right.record.instanceIndex);
7060
+ }
7061
+ function resolveInspectableMatch(paths, value) {
7062
+ const exactMatch = findPmRecordMatch(paths, value);
7063
+ const groupMatches = findPmGroupMatches(paths, value).map(syncPmRecordLiveness);
7064
+ if (groupMatches.length > 1 && exactMatch?.record.baseName === value && exactMatch.record.name === value) {
7065
+ 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}.`);
7066
+ }
7067
+ if (exactMatch) {
7068
+ return syncPmRecordLiveness(exactMatch);
7069
+ }
7070
+ return groupMatches[0];
7071
+ }
7072
+ function rebuildPmRecordDefinition(record, targetInstances = record.instances) {
7073
+ const definition = resolvePmAppDefinition(
7074
+ toPmAppConfig(record),
7075
+ { name: record.baseName, env: {}, watchPaths: [], watchIgnore: [], instances: targetInstances },
7076
+ process.cwd(),
7077
+ record.source
7078
+ );
7079
+ return {
7080
+ ...definition,
7081
+ name: record.name,
7082
+ baseName: record.baseName,
7083
+ instanceIndex: record.instanceIndex,
7084
+ instances: targetInstances
7085
+ };
7086
+ }
7087
+ function rebuildPmSavedDefinition(app) {
7088
+ const definition = resolvePmAppDefinition(
7089
+ toSavedPmAppConfig(app),
7090
+ { name: app.baseName, env: {}, watchPaths: [], watchIgnore: [], instances: app.instances },
7091
+ process.cwd(),
7092
+ "cli"
7093
+ );
7094
+ return {
7095
+ ...definition,
7096
+ name: app.name,
7097
+ baseName: app.baseName,
7098
+ instanceIndex: app.instanceIndex,
7099
+ instances: app.instances
7100
+ };
7101
+ }
7102
+ function deletePmMatches(matches) {
7103
+ for (const match of matches) {
7104
+ if ((0, import_node_fs5.existsSync)(match.record.logFiles.out)) {
7105
+ (0, import_node_fs5.rmSync)(match.record.logFiles.out, { force: true });
7106
+ }
7107
+ if ((0, import_node_fs5.existsSync)(match.record.logFiles.err)) {
7108
+ (0, import_node_fs5.rmSync)(match.record.logFiles.err, { force: true });
7109
+ }
7110
+ (0, import_node_fs5.rmSync)(match.filePath, { force: true });
7111
+ }
7112
+ }
7113
+ function updatePmInstanceCount(matches, instances) {
7114
+ const now = (/* @__PURE__ */ new Date()).toISOString();
7115
+ for (const match of matches) {
7116
+ writePmRecord(match.filePath, {
7117
+ ...match.record,
7118
+ instances,
7119
+ updatedAt: now
7120
+ });
7121
+ }
7122
+ }
7123
+ function normalizePmSignalName(value) {
7124
+ const trimmed = value.trim().toUpperCase();
7125
+ const signalName = trimmed.startsWith("SIG") ? trimmed : `SIG${trimmed}`;
7126
+ if (!PM_SIGNAL_NAMES.has(signalName)) {
7127
+ throw new Error(`Unsupported pm signal: ${value}`);
7128
+ }
7129
+ return signalName;
7130
+ }
7131
+ function resolveSignalablePid(record) {
7132
+ if (record.childPid && record.childPid > 0) {
7133
+ return record.childPid;
7134
+ }
7135
+ if (record.runnerPid && record.runnerPid > 0) {
7136
+ return record.runnerPid;
7137
+ }
7138
+ return void 0;
7139
+ }
7140
+ function groupPmMatchesByBaseName(matches) {
7141
+ const grouped = /* @__PURE__ */ new Map();
7142
+ for (const match of matches) {
7143
+ const group = grouped.get(match.record.baseName);
7144
+ if (group) {
7145
+ group.push(match);
7146
+ continue;
7147
+ }
7148
+ grouped.set(match.record.baseName, [match]);
7149
+ }
7150
+ return [...grouped.entries()].sort((left, right) => left[0].localeCompare(right[0])).map(([, group]) => sortPmMatchesByInstance(group));
7151
+ }
7152
+ async function waitForPmRecordOnline(filePath, timeoutMs) {
7153
+ const deadline = Date.now() + timeoutMs;
7154
+ while (Date.now() < deadline) {
7155
+ if (!(0, import_node_fs5.existsSync)(filePath)) {
7156
+ break;
7157
+ }
7158
+ const record = readPmRecord(filePath);
7159
+ if (record.status === "online") {
7160
+ return record;
7161
+ }
7162
+ if (record.status === "errored" || record.status === "exited" || record.status === "stopped") {
7163
+ throw new Error(record.error ?? `Process ${record.name} failed while reloading.`);
7164
+ }
7165
+ await new Promise((resolvePromise) => setTimeout(resolvePromise, 50));
7166
+ }
7167
+ throw new Error(`Timed out waiting for the reloaded process to become online after ${timeoutMs}ms.`);
7168
+ }
7169
+ function resolvePmReloadReadyTimeout(record) {
7170
+ return record.waitReady || record.proxy?.strategy === "inherit" ? Math.max(record.listenTimeout + 1e3, 2e3) : Math.max(record.restartDelay + 1e3, 2e3);
7171
+ }
7172
+ function supportsPmProxyReload2(record) {
7173
+ return Boolean(record.proxy) && record.instances === 1 && Boolean(record.runnerPid);
7174
+ }
5417
7175
  function padCell(value, width) {
5418
7176
  return value.length >= width ? value : `${value}${" ".repeat(width - value.length)}`;
5419
7177
  }
@@ -5424,8 +7182,255 @@ error: ${text}`);
5424
7182
  const lines = (0, import_node_fs5.readFileSync)(filePath, "utf8").split(/\r?\n/).filter((line) => line.length > 0);
5425
7183
  return lines.slice(-lineCount).join(import_node_os2.EOL);
5426
7184
  }
5427
- function printPmList(paths) {
5428
- const matches = listPmRecordMatches(paths).map(syncPmRecordLiveness);
7185
+ function listPmMatches(paths) {
7186
+ return listPmRecordMatches(paths).map(syncPmRecordLiveness);
7187
+ }
7188
+ function isPmRecordActive(record) {
7189
+ return record.desiredState === "running" && (record.status === "starting" || record.status === "online" || record.status === "restarting");
7190
+ }
7191
+ function resolvePmUptimeMs(record) {
7192
+ if (!isPmRecordActive(record) || !record.startedAt) {
7193
+ return void 0;
7194
+ }
7195
+ const startedTime = Date.parse(record.startedAt);
7196
+ if (Number.isNaN(startedTime)) {
7197
+ return void 0;
7198
+ }
7199
+ return Math.max(0, Date.now() - startedTime);
7200
+ }
7201
+ function resolvePmLiveMetrics(record) {
7202
+ const uptimeMs = resolvePmUptimeMs(record);
7203
+ if (!isPmRecordActive(record) || !record.childPid) {
7204
+ return { uptimeMs };
7205
+ }
7206
+ const sampledMetrics = samplePmProcessMetrics(record.childPid);
7207
+ return {
7208
+ ...sampledMetrics,
7209
+ uptimeMs,
7210
+ updatedAt: sampledMetrics.cpuPercent !== void 0 || sampledMetrics.memoryRssBytes !== void 0 ? (/* @__PURE__ */ new Date()).toISOString() : void 0
7211
+ };
7212
+ }
7213
+ function toPmDisplayRecord(record) {
7214
+ return {
7215
+ record,
7216
+ liveMetrics: resolvePmLiveMetrics(record)
7217
+ };
7218
+ }
7219
+ function serializePmRecord(record) {
7220
+ return {
7221
+ ...record,
7222
+ liveMetrics: resolvePmLiveMetrics(record)
7223
+ };
7224
+ }
7225
+ function parsePmFormatOption(args, index, option) {
7226
+ let value;
7227
+ if (option.startsWith("--format=")) {
7228
+ value = option.slice("--format=".length);
7229
+ } else {
7230
+ value = readRequiredValue(args, index + 1, "--format");
7231
+ index += 1;
7232
+ }
7233
+ if (value !== "table" && value !== "json") {
7234
+ throw new Error(`Unsupported pm output format: ${value}`);
7235
+ }
7236
+ return {
7237
+ format: value,
7238
+ nextIndex: index
7239
+ };
7240
+ }
7241
+ function parsePmListArgs(args) {
7242
+ let format = "table";
7243
+ for (let index = 0; index < args.length; index++) {
7244
+ const arg = args[index];
7245
+ switch (arg) {
7246
+ case "--json":
7247
+ format = "json";
7248
+ break;
7249
+ case "--format":
7250
+ default:
7251
+ if (arg === "--format" || arg.startsWith("--format=")) {
7252
+ const parsed = parsePmFormatOption(args, index, arg);
7253
+ format = parsed.format;
7254
+ index = parsed.nextIndex;
7255
+ break;
7256
+ }
7257
+ throw new Error(`Unknown pm list option: ${arg}`);
7258
+ }
7259
+ }
7260
+ return { format };
7261
+ }
7262
+ function parsePmShowArgs(args) {
7263
+ let format = "text";
7264
+ let name;
7265
+ for (let index = 0; index < args.length; index++) {
7266
+ const arg = args[index];
7267
+ switch (arg) {
7268
+ case "--json":
7269
+ format = "json";
7270
+ break;
7271
+ case "--format":
7272
+ default:
7273
+ if (arg === "--format" || arg.startsWith("--format=")) {
7274
+ const parsed = parsePmFormatOption(args, index, arg);
7275
+ format = parsed.format === "json" ? "json" : "text";
7276
+ index = parsed.nextIndex;
7277
+ break;
7278
+ }
7279
+ if (arg.startsWith("-")) {
7280
+ throw new Error(`Unknown pm show option: ${arg}`);
7281
+ }
7282
+ if (name) {
7283
+ throw new Error("pm show accepts exactly one process name.");
7284
+ }
7285
+ name = arg;
7286
+ break;
7287
+ }
7288
+ }
7289
+ if (!name) {
7290
+ throw new Error("Usage: elit pm show <name> [--json]");
7291
+ }
7292
+ return { name, format };
7293
+ }
7294
+ function formatPmDuration(durationMs) {
7295
+ if (durationMs < 1e3) {
7296
+ return `${durationMs}ms`;
7297
+ }
7298
+ const totalSeconds = Math.floor(durationMs / 1e3);
7299
+ const days = Math.floor(totalSeconds / 86400);
7300
+ const hours = Math.floor(totalSeconds % 86400 / 3600);
7301
+ const minutes = Math.floor(totalSeconds % 3600 / 60);
7302
+ const seconds = totalSeconds % 60;
7303
+ const parts = [];
7304
+ if (days > 0) {
7305
+ parts.push(`${days}d`);
7306
+ }
7307
+ if (hours > 0) {
7308
+ parts.push(`${hours}h`);
7309
+ }
7310
+ if (minutes > 0) {
7311
+ parts.push(`${minutes}m`);
7312
+ }
7313
+ if (seconds > 0 || parts.length === 0) {
7314
+ parts.push(`${seconds}s`);
7315
+ }
7316
+ return parts.slice(0, 2).join(" ");
7317
+ }
7318
+ function formatPmCpuPercent(cpuPercent) {
7319
+ if (cpuPercent === void 0 || !Number.isFinite(cpuPercent)) {
7320
+ return "-";
7321
+ }
7322
+ return `${cpuPercent >= 100 ? cpuPercent.toFixed(0) : cpuPercent.toFixed(1)}%`;
7323
+ }
7324
+ function formatPmMemory(memoryRssBytes) {
7325
+ if (memoryRssBytes === void 0 || !Number.isFinite(memoryRssBytes) || memoryRssBytes < 0) {
7326
+ return "-";
7327
+ }
7328
+ const units = ["B", "KB", "MB", "GB", "TB"];
7329
+ let value = memoryRssBytes;
7330
+ let unitIndex = 0;
7331
+ while (value >= 1024 && unitIndex < units.length - 1) {
7332
+ value /= 1024;
7333
+ unitIndex += 1;
7334
+ }
7335
+ const formatted = value >= 10 || unitIndex === 0 ? value.toFixed(0) : value.toFixed(1);
7336
+ return `${formatted}${units[unitIndex]}`;
7337
+ }
7338
+ function formatPmUptime(uptimeMs) {
7339
+ if (uptimeMs === void 0 || !Number.isFinite(uptimeMs)) {
7340
+ return "-";
7341
+ }
7342
+ return formatPmDuration(Math.max(0, uptimeMs));
7343
+ }
7344
+ function formatPmTarget(record) {
7345
+ if (record.script) {
7346
+ return record.script;
7347
+ }
7348
+ if (record.file) {
7349
+ return record.file;
7350
+ }
7351
+ if (record.wapk) {
7352
+ return record.wapk;
7353
+ }
7354
+ return "-";
7355
+ }
7356
+ function pushPmDetail(lines, label, value) {
7357
+ lines.push(`${padCell(`${label}:`, 18)} ${value}`);
7358
+ }
7359
+ function pushPmDetailList(lines, label, values) {
7360
+ if (values.length === 0) {
7361
+ pushPmDetail(lines, label, "-");
7362
+ return;
7363
+ }
7364
+ pushPmDetail(lines, label, values[0] ?? "-");
7365
+ for (const value of values.slice(1)) {
7366
+ lines.push(`${" ".repeat(20)} ${value}`);
7367
+ }
7368
+ }
7369
+ function formatPmRecordDetails(record, liveMetrics) {
7370
+ const lines = [`Process: ${record.name}`];
7371
+ pushPmDetail(lines, "id", record.id);
7372
+ pushPmDetail(lines, "status", record.status);
7373
+ pushPmDetail(lines, "desired state", record.desiredState);
7374
+ pushPmDetail(lines, "instance", `${record.instanceIndex}/${record.instances}`);
7375
+ pushPmDetail(lines, "cpu", formatPmCpuPercent(liveMetrics.cpuPercent));
7376
+ pushPmDetail(lines, "memory", formatPmMemory(liveMetrics.memoryRssBytes));
7377
+ pushPmDetail(lines, "uptime", formatPmUptime(liveMetrics.uptimeMs));
7378
+ pushPmDetail(lines, "type", record.type);
7379
+ pushPmDetail(lines, "source", record.source);
7380
+ pushPmDetail(lines, "runtime", record.runtime ?? "-");
7381
+ pushPmDetail(lines, "cwd", record.cwd);
7382
+ pushPmDetail(lines, "target", formatPmTarget(record));
7383
+ pushPmDetail(lines, "command", record.commandPreview || "-");
7384
+ pushPmDetail(lines, "runner pid", record.runnerPid ? String(record.runnerPid) : "-");
7385
+ pushPmDetail(lines, "child pid", record.childPid ? String(record.childPid) : "-");
7386
+ pushPmDetail(lines, "restart count", `${record.restartCount}/${record.maxRestarts}`);
7387
+ pushPmDetail(lines, "restart policy", record.restartPolicy);
7388
+ pushPmDetail(lines, "proxy", record.proxy ? `http://${record.proxy.host ?? "0.0.0.0"}:${record.proxy.port}` : "-");
7389
+ pushPmDetail(lines, "proxy strategy", record.proxy?.strategy ?? "-");
7390
+ pushPmDetail(lines, "proxy target", record.proxy && record.proxyTargetPort ? `${record.proxy.targetHost ?? "127.0.0.1"}:${record.proxyTargetPort}` : "-");
7391
+ pushPmDetail(lines, "max memory", record.maxMemoryBytes ? formatPmMemory(record.maxMemoryBytes) : "-");
7392
+ pushPmDetail(lines, "memory action", record.memoryAction ?? "-");
7393
+ pushPmDetail(lines, "cron restart", record.cronRestart ?? "-");
7394
+ pushPmDetail(lines, "exp backoff", record.expBackoffRestartDelay ? formatPmDuration(record.expBackoffRestartDelay) : "-");
7395
+ pushPmDetail(lines, "exp backoff max", record.expBackoffRestartMaxDelay ? formatPmDuration(record.expBackoffRestartMaxDelay) : "-");
7396
+ pushPmDetail(lines, "restart window", record.restartWindow ? formatPmDuration(record.restartWindow) : "-");
7397
+ pushPmDetail(lines, "wait ready", record.waitReady ? "enabled" : "disabled");
7398
+ pushPmDetail(lines, "listen timeout", record.waitReady ? formatPmDuration(record.listenTimeout) : "-");
7399
+ pushPmDetail(lines, "restart delay", formatPmDuration(record.restartDelay));
7400
+ pushPmDetail(lines, "kill timeout", formatPmDuration(record.killTimeout));
7401
+ pushPmDetail(lines, "min uptime", formatPmDuration(record.minUptime));
7402
+ pushPmDetail(lines, "autorestart", record.autorestart ? "enabled" : "disabled");
7403
+ pushPmDetail(lines, "watch", record.watch ? "enabled" : "disabled");
7404
+ pushPmDetail(lines, "watch debounce", record.watch ? formatPmDuration(record.watchDebounce) : "-");
7405
+ pushPmDetailList(lines, "watch paths", record.watchPaths);
7406
+ pushPmDetailList(lines, "watch ignore", record.watchIgnore);
7407
+ if (record.healthCheck) {
7408
+ pushPmDetail(lines, "health check", record.healthCheck.url);
7409
+ pushPmDetail(lines, "health grace", formatPmDuration(record.healthCheck.gracePeriod));
7410
+ pushPmDetail(lines, "health interval", formatPmDuration(record.healthCheck.interval));
7411
+ pushPmDetail(lines, "health timeout", formatPmDuration(record.healthCheck.timeout));
7412
+ pushPmDetail(lines, "health failures", String(record.healthCheck.maxFailures));
7413
+ } else {
7414
+ pushPmDetail(lines, "health check", "-");
7415
+ }
7416
+ pushPmDetailList(lines, "env", Object.entries(record.env).map(([key, value]) => `${key}=${value}`));
7417
+ pushPmDetail(lines, "stdout log", record.logFiles.out);
7418
+ pushPmDetail(lines, "stderr log", record.logFiles.err);
7419
+ pushPmDetail(lines, "created at", record.createdAt);
7420
+ pushPmDetail(lines, "updated at", record.updatedAt);
7421
+ pushPmDetail(lines, "metrics at", liveMetrics.updatedAt ?? "-");
7422
+ pushPmDetail(lines, "started at", record.startedAt ?? "-");
7423
+ pushPmDetail(lines, "stopped at", record.stoppedAt ?? "-");
7424
+ pushPmDetail(lines, "last exit", record.lastExitCode === void 0 ? "-" : String(record.lastExitCode));
7425
+ pushPmDetail(lines, "error", record.error ?? "-");
7426
+ return lines.join(import_node_os2.EOL);
7427
+ }
7428
+ function printPmList(paths, format = "table") {
7429
+ const matches = listPmMatches(paths).map((match) => toPmDisplayRecord(match.record));
7430
+ if (format === "json") {
7431
+ console.log(JSON.stringify(matches.map((match) => ({ ...match.record, liveMetrics: match.liveMetrics })), null, 2));
7432
+ return;
7433
+ }
5429
7434
  if (matches.length === 0) {
5430
7435
  console.log("No managed processes found.");
5431
7436
  return;
@@ -5434,22 +7439,47 @@ error: ${text}`);
5434
7439
  padCell("name", 20),
5435
7440
  padCell("status", 12),
5436
7441
  padCell("pid", 8),
7442
+ padCell("cpu", 8),
7443
+ padCell("memory", 10),
7444
+ padCell("uptime", 10),
5437
7445
  padCell("restarts", 10),
5438
7446
  padCell("type", 8),
5439
7447
  "runtime"
5440
7448
  ];
5441
7449
  console.log(headers.join(" "));
5442
- for (const { record } of matches) {
7450
+ for (const { record, liveMetrics } of matches) {
5443
7451
  console.log([
5444
7452
  padCell(record.name, 20),
5445
7453
  padCell(record.status, 12),
5446
7454
  padCell(record.childPid ? String(record.childPid) : "-", 8),
7455
+ padCell(formatPmCpuPercent(liveMetrics.cpuPercent), 8),
7456
+ padCell(formatPmMemory(liveMetrics.memoryRssBytes), 10),
7457
+ padCell(formatPmUptime(liveMetrics.uptimeMs), 10),
5447
7458
  padCell(String(record.restartCount ?? 0), 10),
5448
7459
  padCell(record.type, 8),
5449
7460
  record.runtime ?? "-"
5450
7461
  ].join(" "));
5451
7462
  }
5452
7463
  }
7464
+ async function runPmList(args) {
7465
+ const options = parsePmListArgs(args);
7466
+ const { paths } = await loadPmContext();
7467
+ printPmList(paths, options.format);
7468
+ }
7469
+ async function runPmShow(args) {
7470
+ const options = parsePmShowArgs(args);
7471
+ const { paths } = await loadPmContext();
7472
+ const match = resolveInspectableMatch(paths, options.name);
7473
+ if (!match) {
7474
+ throw new Error(`No managed process found for: ${options.name}`);
7475
+ }
7476
+ const synced = syncPmRecordLiveness(match);
7477
+ if (options.format === "json") {
7478
+ console.log(JSON.stringify(serializePmRecord(synced.record), null, 2));
7479
+ return;
7480
+ }
7481
+ console.log(formatPmRecordDetails(synced.record, resolvePmLiveMetrics(synced.record)));
7482
+ }
5453
7483
  async function runPmStop(args) {
5454
7484
  const target = args[0];
5455
7485
  if (!target) {
@@ -5476,17 +7506,62 @@ error: ${text}`);
5476
7506
  await stopPmMatches(matches);
5477
7507
  const restarted = [];
5478
7508
  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
- );
7509
+ const definition = rebuildPmRecordDefinition(match.record);
5485
7510
  await startManagedProcess(definition, paths);
5486
7511
  restarted.push(match.record.name);
5487
7512
  }
5488
7513
  console.log(`[pm] restarted ${restarted.join(", ")}`);
5489
7514
  }
7515
+ async function runPmReload(args) {
7516
+ const target = args[0];
7517
+ if (!target) {
7518
+ throw new Error("Usage: elit pm reload <name|all>");
7519
+ }
7520
+ const { paths } = await loadPmContext();
7521
+ const matches = resolveNamedMatches(paths, target);
7522
+ if (matches.length === 0) {
7523
+ throw new Error(`No managed process found for: ${target}`);
7524
+ }
7525
+ const reloaded = [];
7526
+ const errors = [];
7527
+ for (const group of groupPmMatchesByBaseName(matches)) {
7528
+ for (const match of group) {
7529
+ try {
7530
+ if (supportsPmProxyReload2(match.record)) {
7531
+ const reloadRequestedAt = (/* @__PURE__ */ new Date()).toISOString();
7532
+ writePmRecord(match.filePath, {
7533
+ ...match.record,
7534
+ status: "restarting",
7535
+ reloadRequestedAt,
7536
+ updatedAt: reloadRequestedAt,
7537
+ error: void 0
7538
+ });
7539
+ await waitForPmRecordOnline(
7540
+ getPmRecordPath(paths, match.record.id),
7541
+ resolvePmReloadReadyTimeout(match.record)
7542
+ );
7543
+ reloaded.push(match.record.name);
7544
+ continue;
7545
+ }
7546
+ await stopPmMatches([match]);
7547
+ const definition = rebuildPmRecordDefinition(match.record);
7548
+ const startedRecord = await startManagedProcess(definition, paths);
7549
+ await waitForPmRecordOnline(
7550
+ getPmRecordPath(paths, startedRecord.id),
7551
+ resolvePmReloadReadyTimeout(startedRecord)
7552
+ );
7553
+ reloaded.push(match.record.name);
7554
+ } catch (error) {
7555
+ const message = error instanceof Error ? error.message : String(error);
7556
+ errors.push(`[pm] ${match.record.name}: ${message}`);
7557
+ }
7558
+ }
7559
+ }
7560
+ if (errors.length > 0) {
7561
+ throw new Error([`[pm] reloaded ${reloaded.length} process${reloaded.length === 1 ? "" : "es"}`, ...errors].join(import_node_os2.EOL));
7562
+ }
7563
+ console.log(`[pm] reloaded ${reloaded.join(", ")}`);
7564
+ }
5490
7565
  async function runPmSave() {
5491
7566
  const { paths } = await loadPmContext();
5492
7567
  ensurePmDirectories(paths);
@@ -5508,12 +7583,7 @@ error: ${text}`);
5508
7583
  let restored = 0;
5509
7584
  for (const app of dump.apps) {
5510
7585
  try {
5511
- const definition = resolvePmAppDefinition(
5512
- toSavedPmAppConfig(app),
5513
- { name: app.name, env: {}, watchPaths: [], watchIgnore: [] },
5514
- process.cwd(),
5515
- "cli"
5516
- );
7586
+ const definition = rebuildPmSavedDefinition(app);
5517
7587
  await startManagedProcess(definition, paths);
5518
7588
  restored += 1;
5519
7589
  } catch (error) {
@@ -5537,16 +7607,127 @@ error: ${text}`);
5537
7607
  throw new Error(`No managed process found for: ${target}`);
5538
7608
  }
5539
7609
  await stopPmMatches(matches);
7610
+ deletePmMatches(matches);
7611
+ console.log(`[pm] deleted ${matches.length} process${matches.length === 1 ? "" : "es"}`);
7612
+ }
7613
+ async function runPmScale(args) {
7614
+ const target = args[0];
7615
+ const countArg = args[1];
7616
+ if (!target || countArg === void 0 || args.length > 2) {
7617
+ throw new Error("Usage: elit pm scale <name> <count>");
7618
+ }
7619
+ const desiredCount = normalizeIntegerOption(countArg, "pm scale <count>", 0);
7620
+ const { config, paths } = await loadPmContext();
7621
+ const exactMatch = findPmRecordMatch(paths, target);
7622
+ const currentMatches = sortPmMatchesByInstance(resolveNamedMatches(paths, exactMatch?.record.baseName ?? target));
7623
+ if (currentMatches.length === 0) {
7624
+ if (desiredCount === 0) {
7625
+ console.log(`[pm] ${target} already scaled to 0 instances`);
7626
+ return;
7627
+ }
7628
+ const definitions = resolvePmStartDefinitions(
7629
+ { name: target, env: {}, watchPaths: [], watchIgnore: [], instances: desiredCount },
7630
+ config,
7631
+ process.cwd()
7632
+ );
7633
+ for (const definition of definitions) {
7634
+ await startManagedProcess(definition, paths);
7635
+ }
7636
+ console.log(`[pm] scaled ${target} to ${desiredCount} instance${desiredCount === 1 ? "" : "s"}`);
7637
+ return;
7638
+ }
7639
+ const baseName = currentMatches[0]?.record.baseName ?? target;
7640
+ if (desiredCount === currentMatches.length) {
7641
+ console.log(`[pm] ${baseName} already scaled to ${desiredCount} instance${desiredCount === 1 ? "" : "s"}`);
7642
+ return;
7643
+ }
7644
+ if (desiredCount === 0) {
7645
+ await stopPmMatches(currentMatches);
7646
+ deletePmMatches(currentMatches);
7647
+ console.log(`[pm] scaled ${baseName} to 0 instances`);
7648
+ return;
7649
+ }
7650
+ if (desiredCount < currentMatches.length) {
7651
+ const toRemove = [...currentMatches].sort((left, right) => right.record.instanceIndex - left.record.instanceIndex).slice(0, currentMatches.length - desiredCount);
7652
+ const remaining = currentMatches.filter((match) => !toRemove.some((removal) => removal.record.id === match.record.id));
7653
+ await stopPmMatches(toRemove);
7654
+ deletePmMatches(toRemove);
7655
+ updatePmInstanceCount(remaining, desiredCount);
7656
+ console.log(`[pm] scaled ${baseName} to ${desiredCount} instance${desiredCount === 1 ? "" : "s"}`);
7657
+ return;
7658
+ }
7659
+ updatePmInstanceCount(currentMatches, desiredCount);
7660
+ const baseRecord = currentMatches[0]?.record;
7661
+ if (!baseRecord) {
7662
+ throw new Error(`No managed process found for: ${target}`);
7663
+ }
7664
+ const baseDefinition = rebuildPmRecordDefinition(baseRecord, desiredCount);
7665
+ const expandedDefinitions = expandPmInstanceDefinitions(baseDefinition, desiredCount);
7666
+ const existingNames = new Set(currentMatches.map((match) => match.record.name));
7667
+ for (const definition of expandedDefinitions) {
7668
+ if (existingNames.has(definition.name)) {
7669
+ continue;
7670
+ }
7671
+ await startManagedProcess(definition, paths);
7672
+ }
7673
+ console.log(`[pm] scaled ${baseName} to ${desiredCount} instance${desiredCount === 1 ? "" : "s"}`);
7674
+ }
7675
+ async function runPmSendSignal(args) {
7676
+ const signalArg = args[0];
7677
+ const target = args[1];
7678
+ if (!signalArg || !target || args.length > 2) {
7679
+ throw new Error("Usage: elit pm send-signal <signal> <name|all>");
7680
+ }
7681
+ const signalName = normalizePmSignalName(signalArg);
7682
+ const { paths } = await loadPmContext();
7683
+ const matches = resolveNamedMatches(paths, target);
7684
+ if (matches.length === 0) {
7685
+ throw new Error(`No managed process found for: ${target}`);
7686
+ }
7687
+ let signaled = 0;
7688
+ const errors = [];
5540
7689
  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 });
7690
+ const pid = resolveSignalablePid(match.record);
7691
+ if (!pid) {
7692
+ continue;
5543
7693
  }
5544
- if ((0, import_node_fs5.existsSync)(match.record.logFiles.err)) {
5545
- (0, import_node_fs5.rmSync)(match.record.logFiles.err, { force: true });
7694
+ try {
7695
+ sendPmSignal(pid, signalName);
7696
+ signaled += 1;
7697
+ } catch (error) {
7698
+ const message = error instanceof Error ? error.message : String(error);
7699
+ errors.push(`[pm] ${match.record.name}: ${message}`);
5546
7700
  }
5547
- (0, import_node_fs5.rmSync)(match.filePath, { force: true });
5548
7701
  }
5549
- console.log(`[pm] deleted ${matches.length} process${matches.length === 1 ? "" : "es"}`);
7702
+ if (errors.length > 0) {
7703
+ throw new Error([`[pm] sent ${signalName} to ${signaled} process${signaled === 1 ? "" : "es"}`, ...errors].join(import_node_os2.EOL));
7704
+ }
7705
+ if (signaled === 0) {
7706
+ throw new Error(`No running managed process found for: ${target}`);
7707
+ }
7708
+ console.log(`[pm] sent ${signalName} to ${signaled} process${signaled === 1 ? "" : "es"}`);
7709
+ }
7710
+ async function runPmReset(args) {
7711
+ const target = args[0];
7712
+ if (!target) {
7713
+ throw new Error("Usage: elit pm reset <name|all>");
7714
+ }
7715
+ const { paths } = await loadPmContext();
7716
+ const matches = resolveNamedMatches(paths, target);
7717
+ if (matches.length === 0) {
7718
+ throw new Error(`No managed process found for: ${target}`);
7719
+ }
7720
+ const now = (/* @__PURE__ */ new Date()).toISOString();
7721
+ for (const match of matches) {
7722
+ writePmRecord(match.filePath, {
7723
+ ...match.record,
7724
+ restartCount: 0,
7725
+ lastExitCode: void 0,
7726
+ error: void 0,
7727
+ updatedAt: now
7728
+ });
7729
+ }
7730
+ console.log(`[pm] reset ${matches.length} process${matches.length === 1 ? "" : "es"}`);
5550
7731
  }
5551
7732
  async function runPmLogs(args) {
5552
7733
  if (args.length === 0) {
@@ -5579,7 +7760,7 @@ error: ${text}`);
5579
7760
  throw new Error("Usage: elit pm logs <name> [--lines <n>] [--stderr]");
5580
7761
  }
5581
7762
  const { paths } = await loadPmContext();
5582
- const match = findPmRecordMatch(paths, name);
7763
+ const match = resolveInspectableMatch(paths, name);
5583
7764
  if (!match) {
5584
7765
  throw new Error(`No managed process found for: ${name}`);
5585
7766
  }
@@ -5603,17 +7784,36 @@ error: ${text}`);
5603
7784
  await runPmStart(args.slice(1));
5604
7785
  return;
5605
7786
  case "list":
5606
- case "ls": {
5607
- const { paths } = await loadPmContext();
5608
- printPmList(paths);
7787
+ case "ls":
7788
+ await runPmList(args.slice(1));
7789
+ return;
7790
+ case "jlist":
7791
+ await runPmList(["--json", ...args.slice(1)]);
7792
+ return;
7793
+ case "show":
7794
+ case "describe":
7795
+ await runPmShow(args.slice(1));
5609
7796
  return;
5610
- }
5611
7797
  case "stop":
5612
7798
  await runPmStop(args.slice(1));
5613
7799
  return;
5614
7800
  case "restart":
5615
7801
  await runPmRestart(args.slice(1));
5616
7802
  return;
7803
+ case "reload":
7804
+ await runPmReload(args.slice(1));
7805
+ return;
7806
+ case "scale":
7807
+ await runPmScale(args.slice(1));
7808
+ return;
7809
+ case "send-signal":
7810
+ case "signal":
7811
+ case "sendSignal":
7812
+ await runPmSendSignal(args.slice(1));
7813
+ return;
7814
+ case "reset":
7815
+ await runPmReset(args.slice(1));
7816
+ return;
5617
7817
  case "delete":
5618
7818
  case "remove":
5619
7819
  case "rm":