elit 3.6.7 → 3.6.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/Cargo.lock +1 -1
  2. package/Cargo.toml +1 -1
  3. package/README.md +20 -1
  4. package/dist/cli.cjs +2496 -303
  5. package/dist/cli.mjs +2501 -308
  6. package/dist/config.d.ts +6 -6
  7. package/dist/{contracts-BeW9k0yZ.d.ts → contracts-_0p1-15U.d.ts} +1 -1
  8. package/dist/coverage.d.ts +1 -1
  9. package/dist/dev-build.d.ts +3 -3
  10. package/dist/http.cjs +160 -41
  11. package/dist/http.d.ts +5 -11
  12. package/dist/http.js +160 -41
  13. package/dist/http.mjs +160 -41
  14. package/dist/https.cjs +194 -46
  15. package/dist/https.d.ts +3 -6
  16. package/dist/https.js +194 -46
  17. package/dist/https.mjs +194 -46
  18. package/dist/pm-node-shared-listener-bootstrap.cjs +75 -0
  19. package/dist/pm.cjs +2101 -134
  20. package/dist/pm.d.ts +83 -8
  21. package/dist/pm.js +2095 -158
  22. package/dist/pm.mjs +2091 -139
  23. package/dist/preview-build.d.ts +3 -3
  24. package/dist/server.cjs +417 -168
  25. package/dist/server.d.ts +4 -4
  26. package/dist/server.js +453 -179
  27. package/dist/server.mjs +417 -168
  28. package/dist/smtp-server.js +37 -12
  29. package/dist/test-reporter.d.ts +1 -1
  30. package/dist/test-runtime.d.ts +1 -1
  31. package/dist/{types-tJn88E1N.d.ts → types-BayMVo_k.d.ts} +39 -3
  32. package/dist/{types-CIhpN1-K.d.ts → types-C70T-42Z.d.ts} +1 -1
  33. package/dist/{types-DAisuVr5.d.ts → types-DPOgoGs-.d.ts} +7 -1
  34. package/dist/{state-DvEkDehk.d.ts → types-fiLday0L.d.ts} +96 -92
  35. package/dist/types.d.ts +4 -0
  36. package/dist/{websocket-XfyK23zD.d.ts → websocket-BLBEAnhp.d.ts} +1 -1
  37. package/dist/ws.d.ts +3 -3
  38. package/dist/wss.cjs +194 -46
  39. package/dist/wss.d.ts +3 -3
  40. package/dist/wss.js +194 -46
  41. package/dist/wss.mjs +194 -46
  42. package/package.json +11 -6
package/dist/pm.cjs CHANGED
@@ -480,6 +480,12 @@ __export(pm_exports, {
480
480
  DEFAULT_MIN_UPTIME: () => DEFAULT_MIN_UPTIME,
481
481
  DEFAULT_PM_DATA_DIR: () => DEFAULT_PM_DATA_DIR,
482
482
  DEFAULT_PM_DUMP_FILE: () => DEFAULT_PM_DUMP_FILE,
483
+ DEFAULT_PM_EXP_BACKOFF_MAX_DELAY: () => DEFAULT_PM_EXP_BACKOFF_MAX_DELAY,
484
+ DEFAULT_PM_KILL_TIMEOUT: () => DEFAULT_PM_KILL_TIMEOUT,
485
+ DEFAULT_PM_LISTEN_TIMEOUT: () => DEFAULT_PM_LISTEN_TIMEOUT,
486
+ DEFAULT_PM_MEMORY_CHECK_INTERVAL: () => DEFAULT_PM_MEMORY_CHECK_INTERVAL,
487
+ DEFAULT_PM_PROXY_STRATEGY: () => DEFAULT_PM_PROXY_STRATEGY,
488
+ DEFAULT_PM_RESTART_WINDOW: () => DEFAULT_PM_RESTART_WINDOW,
483
489
  DEFAULT_PM_STOP_GRACE_PERIOD_MS: () => DEFAULT_PM_STOP_GRACE_PERIOD_MS,
484
490
  DEFAULT_PM_STOP_POLL_MS: () => DEFAULT_PM_STOP_POLL_MS,
485
491
  DEFAULT_RESTART_DELAY: () => DEFAULT_RESTART_DELAY,
@@ -498,7 +504,10 @@ __export(pm_exports, {
498
504
  countDefinedPmWapkSources: () => countDefinedPmWapkSources,
499
505
  deriveDefaultWatchPaths: () => deriveDefaultWatchPaths,
500
506
  ensurePmDirectories: () => ensurePmDirectories,
507
+ expandPmInstanceDefinitions: () => expandPmInstanceDefinitions,
508
+ findPmGroupMatches: () => findPmGroupMatches,
501
509
  findPmRecordMatch: () => findPmRecordMatch,
510
+ formatPmInstanceName: () => formatPmInstanceName,
502
511
  getPmRecordPath: () => getPmRecordPath,
503
512
  hasPmGoogleDriveConfig: () => hasPmGoogleDriveConfig,
504
513
  hasPmWapkRunConfig: () => hasPmWapkRunConfig,
@@ -517,6 +526,10 @@ __export(pm_exports, {
517
526
  normalizeHealthCheckConfig: () => normalizeHealthCheckConfig,
518
527
  normalizeIntegerOption: () => normalizeIntegerOption,
519
528
  normalizeNonEmptyString: () => normalizeNonEmptyString,
529
+ normalizePmMemoryAction: () => normalizePmMemoryAction,
530
+ normalizePmMemoryLimit: () => normalizePmMemoryLimit,
531
+ normalizePmProxyConfig: () => normalizePmProxyConfig,
532
+ normalizePmProxyStrategy: () => normalizePmProxyStrategy,
520
533
  normalizePmRestartPolicy: () => normalizePmRestartPolicy,
521
534
  normalizePmRuntime: () => normalizePmRuntime,
522
535
  normalizeResolvedWatchIgnorePaths: () => normalizeResolvedWatchIgnorePaths,
@@ -540,7 +553,9 @@ __export(pm_exports, {
540
553
  runManagedProcessLoop: () => runManagedProcessLoop,
541
554
  runPmCommand: () => runPmCommand,
542
555
  runPmRunner: () => runPmRunner,
556
+ samplePmProcessMetrics: () => samplePmProcessMetrics,
543
557
  sanitizePmProcessName: () => sanitizePmProcessName,
558
+ sendPmSignal: () => sendPmSignal,
544
559
  startManagedProcess: () => startManagedProcess,
545
560
  stopPmMatches: () => stopPmMatches,
546
561
  stripPmWapkSourceFromRunConfig: () => stripPmWapkSourceFromRunConfig,
@@ -564,6 +579,7 @@ var DEFAULT_RESTART_DELAY = 1e3;
564
579
  var DEFAULT_MAX_RESTARTS = 10;
565
580
  var DEFAULT_WATCH_DEBOUNCE = 250;
566
581
  var DEFAULT_MIN_UPTIME = 0;
582
+ var DEFAULT_PM_LISTEN_TIMEOUT = 3e3;
567
583
  var DEFAULT_HEALTHCHECK_GRACE_PERIOD = 5e3;
568
584
  var DEFAULT_HEALTHCHECK_INTERVAL = 1e4;
569
585
  var DEFAULT_HEALTHCHECK_TIMEOUT = 3e3;
@@ -571,6 +587,11 @@ var DEFAULT_HEALTHCHECK_MAX_FAILURES = 3;
571
587
  var DEFAULT_LOG_LINES = 40;
572
588
  var DEFAULT_PM_STOP_POLL_MS = 100;
573
589
  var DEFAULT_PM_STOP_GRACE_PERIOD_MS = 5e3;
590
+ var DEFAULT_PM_KILL_TIMEOUT = DEFAULT_PM_STOP_GRACE_PERIOD_MS;
591
+ var DEFAULT_PM_EXP_BACKOFF_MAX_DELAY = 15e3;
592
+ var DEFAULT_PM_MEMORY_CHECK_INTERVAL = 500;
593
+ var DEFAULT_PM_RESTART_WINDOW = 0;
594
+ var DEFAULT_PM_PROXY_STRATEGY = "proxy";
574
595
  var PM_WAPK_ONLINE_STDIN_SHUTDOWN_ENV = "ELIT_PM_WAPK_ONLINE_STDIN_SHUTDOWN";
575
596
  var PM_WAPK_ONLINE_SHUTDOWN_COMMAND = "__ELIT_PM_WAPK_ONLINE_SHUTDOWN__";
576
597
  var PM_WAPK_ONLINE_SHUTDOWN_TIMEOUT_MS = 8e3;
@@ -608,6 +629,32 @@ function normalizePmRestartPolicy(value, optionName = "--restart-policy") {
608
629
  }
609
630
  throw new Error(`${optionName} must be one of: always, on-failure, never`);
610
631
  }
632
+ function normalizePmMemoryAction(value, optionName = "--memory-action") {
633
+ if (value === void 0 || value === null || value === "") {
634
+ return void 0;
635
+ }
636
+ if (typeof value !== "string") {
637
+ throw new Error(`${optionName} must be one of: restart, stop`);
638
+ }
639
+ const action = value.trim().toLowerCase();
640
+ if (action === "restart" || action === "stop") {
641
+ return action;
642
+ }
643
+ throw new Error(`${optionName} must be one of: restart, stop`);
644
+ }
645
+ function normalizePmProxyStrategy(value, optionName = "--proxy-strategy") {
646
+ if (value === void 0 || value === null || value === "") {
647
+ return void 0;
648
+ }
649
+ if (typeof value !== "string") {
650
+ throw new Error(`${optionName} must be one of: proxy, inherit`);
651
+ }
652
+ const strategy = value.trim().toLowerCase();
653
+ if (strategy === "proxy" || strategy === "inherit") {
654
+ return strategy;
655
+ }
656
+ throw new Error(`${optionName} must be one of: proxy, inherit`);
657
+ }
611
658
  function normalizeIntegerOption(value, optionName, min = 0) {
612
659
  const parsed = Number.parseInt(value, 10);
613
660
  if (!Number.isFinite(parsed) || parsed < min) {
@@ -615,6 +662,30 @@ function normalizeIntegerOption(value, optionName, min = 0) {
615
662
  }
616
663
  return parsed;
617
664
  }
665
+ function normalizePmMemoryLimit(value, optionName = "--max-memory") {
666
+ if (value === void 0 || value === null || value === "") {
667
+ return void 0;
668
+ }
669
+ if (typeof value === "number") {
670
+ const normalized2 = Math.trunc(value);
671
+ if (!Number.isFinite(normalized2) || normalized2 < 1) {
672
+ throw new Error(`${optionName} must be a number >= 1 or a size like 256M.`);
673
+ }
674
+ return normalized2;
675
+ }
676
+ if (typeof value !== "string") {
677
+ throw new Error(`${optionName} must be a number >= 1 or a size like 256M.`);
678
+ }
679
+ const normalized = value.trim();
680
+ const match = /^(\d+)(b|kb|mb|gb|tb|k|m|g|t)?$/i.exec(normalized);
681
+ if (!match) {
682
+ throw new Error(`${optionName} must be a number >= 1 or a size like 256M.`);
683
+ }
684
+ const amount = normalizeIntegerOption(match[1] ?? "", optionName, 1);
685
+ const unit = (match[2] ?? "b").toLowerCase();
686
+ const multiplier = unit === "b" ? 1 : unit === "k" || unit === "kb" ? 1024 : unit === "m" || unit === "mb" ? 1024 ** 2 : unit === "g" || unit === "gb" ? 1024 ** 3 : 1024 ** 4;
687
+ return amount * multiplier;
688
+ }
618
689
  function normalizeNonEmptyString(value) {
619
690
  if (typeof value !== "string") {
620
691
  return void 0;
@@ -891,6 +962,30 @@ function readRequiredValue(args, index, optionName) {
891
962
  }
892
963
  return value;
893
964
  }
965
+ function normalizePmProxyConfig(value, optionName = "pm proxy") {
966
+ if (value === void 0 || value === null) {
967
+ return void 0;
968
+ }
969
+ if (typeof value !== "object") {
970
+ throw new Error(`${optionName} must be an object with at least a port.`);
971
+ }
972
+ const candidate = value;
973
+ const hasAnyValue = candidate.port !== void 0 || candidate.host !== void 0 || candidate.targetHost !== void 0 || candidate.envVar !== void 0;
974
+ if (!hasAnyValue) {
975
+ return void 0;
976
+ }
977
+ if (candidate.port === void 0 || candidate.port === null || candidate.port === "") {
978
+ throw new Error(`${optionName}.port is required.`);
979
+ }
980
+ const port = typeof candidate.port === "number" ? normalizeIntegerOption(String(Math.trunc(candidate.port)), `${optionName}.port`, 1) : normalizeIntegerOption(String(candidate.port), `${optionName}.port`, 1);
981
+ return {
982
+ port,
983
+ strategy: normalizePmProxyStrategy(candidate.strategy, `${optionName}.strategy`) ?? DEFAULT_PM_PROXY_STRATEGY,
984
+ host: normalizeNonEmptyString(candidate.host),
985
+ targetHost: normalizeNonEmptyString(candidate.targetHost),
986
+ envVar: normalizeNonEmptyString(candidate.envVar)
987
+ };
988
+ }
894
989
 
895
990
  // src/cli/pm/records.ts
896
991
  var import_node_fs2 = require("fs");
@@ -923,18 +1018,31 @@ function writePmRecord(filePath, record) {
923
1018
  function toSavedAppDefinition(record) {
924
1019
  return {
925
1020
  name: record.name,
1021
+ baseName: record.baseName,
1022
+ instanceIndex: record.instanceIndex,
1023
+ instances: record.instances,
926
1024
  type: record.type,
927
1025
  cwd: record.cwd,
928
1026
  runtime: record.runtime,
929
1027
  env: record.env,
1028
+ proxy: record.proxy,
930
1029
  script: record.script,
931
1030
  file: record.file,
932
1031
  wapk: record.wapk,
933
1032
  password: record.password,
934
1033
  wapkRun: record.wapkRun,
935
1034
  restartPolicy: record.restartPolicy,
1035
+ maxMemoryBytes: record.maxMemoryBytes,
1036
+ memoryAction: record.memoryAction,
1037
+ cronRestart: record.cronRestart,
1038
+ expBackoffRestartDelay: record.expBackoffRestartDelay,
1039
+ expBackoffRestartMaxDelay: record.expBackoffRestartMaxDelay,
1040
+ restartWindow: record.restartWindow,
1041
+ waitReady: record.waitReady,
1042
+ listenTimeout: record.listenTimeout,
936
1043
  autorestart: record.autorestart,
937
1044
  restartDelay: record.restartDelay,
1045
+ killTimeout: record.killTimeout,
938
1046
  maxRestarts: record.maxRestarts,
939
1047
  minUptime: record.minUptime,
940
1048
  watch: record.watch,
@@ -1001,6 +1109,9 @@ function findPmRecordMatch(paths, nameOrId) {
1001
1109
  }
1002
1110
  return listPmRecordMatches(paths).find((match) => match.record.name === nameOrId);
1003
1111
  }
1112
+ function findPmGroupMatches(paths, baseName) {
1113
+ return listPmRecordMatches(paths).filter((match) => match.record.baseName === baseName).sort((left, right) => left.record.instanceIndex - right.record.instanceIndex);
1114
+ }
1004
1115
  function isProcessAlive(pid) {
1005
1116
  if (!pid || pid <= 0) {
1006
1117
  return false;
@@ -1050,11 +1161,22 @@ function toPmAppConfig(record) {
1050
1161
  runtime: record.runtime,
1051
1162
  cwd: record.cwd,
1052
1163
  env: record.env,
1164
+ proxy: record.proxy,
1165
+ instances: record.instances,
1053
1166
  autorestart: record.autorestart,
1054
1167
  restartDelay: record.restartDelay,
1168
+ killTimeout: record.killTimeout,
1055
1169
  maxRestarts: record.maxRestarts,
1056
1170
  password: record.password,
1057
1171
  restartPolicy: record.restartPolicy,
1172
+ maxMemory: record.maxMemoryBytes,
1173
+ memoryAction: record.memoryAction,
1174
+ cronRestart: record.cronRestart,
1175
+ expBackoffRestartDelay: record.expBackoffRestartDelay,
1176
+ expBackoffRestartMaxDelay: record.expBackoffRestartMaxDelay,
1177
+ restartWindow: record.restartWindow,
1178
+ waitReady: record.waitReady,
1179
+ listenTimeout: record.listenTimeout,
1058
1180
  minUptime: record.minUptime,
1059
1181
  watch: record.watch,
1060
1182
  watchPaths: record.watchPaths,
@@ -1073,11 +1195,22 @@ function toSavedPmAppConfig(app) {
1073
1195
  runtime: app.runtime,
1074
1196
  cwd: app.cwd,
1075
1197
  env: app.env,
1198
+ proxy: app.proxy,
1199
+ instances: app.instances,
1076
1200
  autorestart: app.autorestart,
1077
1201
  restartDelay: app.restartDelay,
1202
+ killTimeout: app.killTimeout,
1078
1203
  maxRestarts: app.maxRestarts,
1079
1204
  password: app.password,
1080
1205
  restartPolicy: app.restartPolicy,
1206
+ maxMemory: app.maxMemoryBytes,
1207
+ memoryAction: app.memoryAction,
1208
+ cronRestart: app.cronRestart,
1209
+ expBackoffRestartDelay: app.expBackoffRestartDelay,
1210
+ expBackoffRestartMaxDelay: app.expBackoffRestartMaxDelay,
1211
+ restartWindow: app.restartWindow,
1212
+ waitReady: app.waitReady,
1213
+ listenTimeout: app.listenTimeout,
1081
1214
  minUptime: app.minUptime,
1082
1215
  watch: app.watch,
1083
1216
  watchPaths: app.watchPaths,
@@ -1089,6 +1222,170 @@ function toSavedPmAppConfig(app) {
1089
1222
 
1090
1223
  // src/cli/pm/config.ts
1091
1224
  var import_node_path4 = require("path");
1225
+
1226
+ // src/cli/pm/schedule.ts
1227
+ var PM_EVERY_PATTERN = /^@every\s+(\d+)(ms|s|m|h|d)$/i;
1228
+ function parsePositiveInteger(value, optionName, min = 0) {
1229
+ const parsed = Number.parseInt(value, 10);
1230
+ if (!Number.isFinite(parsed) || parsed < min) {
1231
+ throw new Error(`${optionName} must be a number >= ${min}`);
1232
+ }
1233
+ return parsed;
1234
+ }
1235
+ function fillRange(values, start, end, step, min, max, optionName) {
1236
+ if (!Number.isInteger(start) || !Number.isInteger(end) || start < min || end > max || start > end) {
1237
+ throw new Error(`${optionName} contains an invalid range: ${start}-${end}`);
1238
+ }
1239
+ if (!Number.isInteger(step) || step < 1) {
1240
+ throw new Error(`${optionName} contains an invalid step: ${step}`);
1241
+ }
1242
+ for (let current = start; current <= end; current += step) {
1243
+ values.add(current);
1244
+ }
1245
+ }
1246
+ function parseCronSegment(segment, values, min, max, optionName) {
1247
+ const normalized = segment.trim();
1248
+ if (!normalized) {
1249
+ throw new Error(`${optionName} contains an empty field segment.`);
1250
+ }
1251
+ const [rangeToken, stepToken] = normalized.split("/");
1252
+ if (normalized.split("/").length > 2) {
1253
+ throw new Error(`${optionName} contains an invalid step segment: ${normalized}`);
1254
+ }
1255
+ const step = stepToken === void 0 ? 1 : parsePositiveInteger(stepToken, optionName, 1);
1256
+ if (rangeToken === "*") {
1257
+ fillRange(values, min, max, step, min, max, optionName);
1258
+ return;
1259
+ }
1260
+ if (rangeToken.includes("-")) {
1261
+ const [startToken, endToken] = rangeToken.split("-");
1262
+ if (!startToken || !endToken) {
1263
+ throw new Error(`${optionName} contains an invalid range: ${normalized}`);
1264
+ }
1265
+ const start2 = parsePositiveInteger(startToken, optionName, min);
1266
+ const end = parsePositiveInteger(endToken, optionName, min);
1267
+ fillRange(values, start2, end, step, min, max, optionName);
1268
+ return;
1269
+ }
1270
+ const start = parsePositiveInteger(rangeToken, optionName, min);
1271
+ fillRange(values, start, stepToken === void 0 ? start : max, step, min, max, optionName);
1272
+ }
1273
+ function parseCronField(token, min, max, optionName) {
1274
+ const normalized = token.trim();
1275
+ if (!normalized) {
1276
+ throw new Error(`${optionName} contains an empty field.`);
1277
+ }
1278
+ if (normalized === "*") {
1279
+ const values2 = /* @__PURE__ */ new Set();
1280
+ fillRange(values2, min, max, 1, min, max, optionName);
1281
+ return {
1282
+ values: values2,
1283
+ wildcard: true
1284
+ };
1285
+ }
1286
+ const values = /* @__PURE__ */ new Set();
1287
+ for (const segment of normalized.split(",")) {
1288
+ parseCronSegment(segment, values, min, max, optionName);
1289
+ }
1290
+ if (values.size === 0) {
1291
+ throw new Error(`${optionName} must match at least one value.`);
1292
+ }
1293
+ return {
1294
+ values,
1295
+ wildcard: false
1296
+ };
1297
+ }
1298
+ function matchesCronField(field, value) {
1299
+ if (!field) {
1300
+ return true;
1301
+ }
1302
+ return field.wildcard || field.values.has(value);
1303
+ }
1304
+ function matchesCronDate(schedule, candidate) {
1305
+ if (schedule.kind !== "cron") {
1306
+ return false;
1307
+ }
1308
+ const secondMatches = schedule.hasSeconds ? matchesCronField(schedule.seconds, candidate.getSeconds()) : true;
1309
+ const minuteMatches = matchesCronField(schedule.minutes, candidate.getMinutes());
1310
+ const hourMatches = matchesCronField(schedule.hours, candidate.getHours());
1311
+ const monthMatches = matchesCronField(schedule.months, candidate.getMonth() + 1);
1312
+ const dayOfMonthMatches = matchesCronField(schedule.daysOfMonth, candidate.getDate());
1313
+ const dayOfWeekMatches = matchesCronField(schedule.daysOfWeek, candidate.getDay());
1314
+ const domWildcard = schedule.daysOfMonth?.wildcard ?? true;
1315
+ const dowWildcard = schedule.daysOfWeek?.wildcard ?? true;
1316
+ const dayMatches = domWildcard && dowWildcard ? true : domWildcard ? dayOfWeekMatches : dowWildcard ? dayOfMonthMatches : dayOfMonthMatches || dayOfWeekMatches;
1317
+ return secondMatches && minuteMatches && hourMatches && monthMatches && dayMatches;
1318
+ }
1319
+ function parsePmRestartSchedule(expression, optionName = "--cron-restart") {
1320
+ const normalized = expression.trim();
1321
+ const everyMatch = PM_EVERY_PATTERN.exec(normalized);
1322
+ if (everyMatch) {
1323
+ const [, valueText, unit] = everyMatch;
1324
+ const value = parsePositiveInteger(valueText ?? "", optionName, 1);
1325
+ const multiplier = unit?.toLowerCase() === "ms" ? 1 : unit?.toLowerCase() === "s" ? 1e3 : unit?.toLowerCase() === "m" ? 6e4 : unit?.toLowerCase() === "h" ? 36e5 : 864e5;
1326
+ return {
1327
+ expression: normalized,
1328
+ kind: "every",
1329
+ intervalMs: value * multiplier,
1330
+ hasSeconds: true
1331
+ };
1332
+ }
1333
+ const fields = normalized.split(/\s+/).filter((field) => field.length > 0);
1334
+ if (fields.length !== 5 && fields.length !== 6) {
1335
+ throw new Error(`${optionName} must be a 5-field cron expression or @every <duration>.`);
1336
+ }
1337
+ const hasSeconds = fields.length === 6;
1338
+ const offset = hasSeconds ? 1 : 0;
1339
+ return {
1340
+ expression: normalized,
1341
+ kind: "cron",
1342
+ hasSeconds,
1343
+ seconds: hasSeconds ? parseCronField(fields[0] ?? "", 0, 59, optionName) : void 0,
1344
+ minutes: parseCronField(fields[0 + offset] ?? "", 0, 59, optionName),
1345
+ hours: parseCronField(fields[1 + offset] ?? "", 0, 23, optionName),
1346
+ daysOfMonth: parseCronField(fields[2 + offset] ?? "", 1, 31, optionName),
1347
+ months: parseCronField(fields[3 + offset] ?? "", 1, 12, optionName),
1348
+ daysOfWeek: parseCronField(fields[4 + offset] ?? "", 0, 6, optionName)
1349
+ };
1350
+ }
1351
+ function normalizePmRestartSchedule(value, optionName = "--cron-restart") {
1352
+ if (value === void 0 || value === null || value === "") {
1353
+ return void 0;
1354
+ }
1355
+ if (typeof value !== "string") {
1356
+ throw new Error(`${optionName} must be a cron string or @every <duration>.`);
1357
+ }
1358
+ const normalized = value.trim();
1359
+ if (!normalized) {
1360
+ throw new Error(`${optionName} must be a non-empty cron string or @every <duration>.`);
1361
+ }
1362
+ parsePmRestartSchedule(normalized, optionName);
1363
+ return normalized;
1364
+ }
1365
+ function resolveNextPmScheduleOccurrence(schedule, after = /* @__PURE__ */ new Date()) {
1366
+ if (schedule.kind === "every") {
1367
+ return new Date(after.getTime() + (schedule.intervalMs ?? 0));
1368
+ }
1369
+ const stepMs = schedule.hasSeconds ? 1e3 : 6e4;
1370
+ const candidate = new Date(after.getTime());
1371
+ if (schedule.hasSeconds) {
1372
+ candidate.setMilliseconds(0);
1373
+ candidate.setSeconds(candidate.getSeconds() + 1);
1374
+ } else {
1375
+ candidate.setSeconds(0, 0);
1376
+ candidate.setMinutes(candidate.getMinutes() + 1);
1377
+ }
1378
+ const maxIterations = schedule.hasSeconds ? 2678400 : 527040;
1379
+ for (let index = 0; index < maxIterations; index++) {
1380
+ if (matchesCronDate(schedule, candidate)) {
1381
+ return new Date(candidate.getTime());
1382
+ }
1383
+ candidate.setTime(candidate.getTime() + stepMs);
1384
+ }
1385
+ return void 0;
1386
+ }
1387
+
1388
+ // src/cli/pm/config.ts
1092
1389
  function parsePmTarget(parsed, workspaceRoot) {
1093
1390
  if (parsed.script) {
1094
1391
  return { script: parsed.script };
@@ -1131,6 +1428,20 @@ function defaultProcessName(base, explicitName) {
1131
1428
  }
1132
1429
  return "process";
1133
1430
  }
1431
+ function formatPmInstanceName(baseName, instanceIndex) {
1432
+ return instanceIndex <= 1 ? baseName : `${baseName}:${instanceIndex}`;
1433
+ }
1434
+ function expandPmInstanceDefinitions(definition, targetInstances = definition.instances) {
1435
+ return Array.from({ length: targetInstances }, (_, index) => {
1436
+ const instanceIndex = index + 1;
1437
+ return {
1438
+ ...definition,
1439
+ name: formatPmInstanceName(definition.baseName, instanceIndex),
1440
+ instanceIndex,
1441
+ instances: targetInstances
1442
+ };
1443
+ });
1444
+ }
1134
1445
  function countDefinedTargets(app) {
1135
1446
  return [app.script, app.file, app.wapk].filter(Boolean).length;
1136
1447
  }
@@ -1168,8 +1479,25 @@ function resolvePmAppDefinition(base, parsed, workspaceRoot, source) {
1168
1479
  throw new Error("A pm app must define exactly one of script, file, or wapk.");
1169
1480
  }
1170
1481
  const name = defaultProcessName({ script, file, wapk }, parsed.name ?? base?.name);
1482
+ const instances = parsed.instances ?? base?.instances ?? 1;
1483
+ if (!Number.isInteger(instances) || instances < 1) {
1484
+ throw new Error("pm instances must be a number >= 1.");
1485
+ }
1486
+ const proxy = normalizePmProxyConfig(
1487
+ parsed.proxy ? { ...base?.proxy ?? {}, ...parsed.proxy } : base?.proxy,
1488
+ parsed.proxy ? "pm proxy" : "pm.apps[].proxy"
1489
+ );
1490
+ if (proxy?.strategy === "inherit" && instances > 1) {
1491
+ throw new Error('pm proxy strategy "inherit" currently supports only one managed instance per app.');
1492
+ }
1171
1493
  const mergedWapkRun = mergePmWapkRunConfig(base?.wapkRun, parsed.wapkRun);
1172
1494
  const runtime2 = normalizePmRuntime(parsed.runtime ?? mergedWapkRun?.runtime ?? base?.runtime, "--runtime");
1495
+ const maxMemoryBytes = parsed.maxMemoryBytes ?? normalizePmMemoryLimit(base?.maxMemory, "pm.apps[].maxMemory");
1496
+ const memoryAction = parsed.memoryAction ?? normalizePmMemoryAction(base?.memoryAction, "pm.apps[].memoryAction") ?? "restart";
1497
+ const cronRestart = parsed.cronRestart ?? normalizePmRestartSchedule(base?.cronRestart, "pm.apps[].cronRestart");
1498
+ const expBackoffRestartDelay = parsed.expBackoffRestartDelay ?? (base?.expBackoffRestartDelay === void 0 ? void 0 : normalizeIntegerOption(String(base.expBackoffRestartDelay), "pm.apps[].expBackoffRestartDelay", 1));
1499
+ const expBackoffRestartMaxDelay = parsed.expBackoffRestartMaxDelay ?? (base?.expBackoffRestartMaxDelay === void 0 ? void 0 : normalizeIntegerOption(String(base.expBackoffRestartMaxDelay), "pm.apps[].expBackoffRestartMaxDelay", 1));
1500
+ const restartWindow = parsed.restartWindow ?? (base?.restartWindow === void 0 ? void 0 : normalizeIntegerOption(String(base.restartWindow), "pm.apps[].restartWindow", 1));
1173
1501
  let restartPolicy = normalizePmRestartPolicy(parsed.restartPolicy ?? base?.restartPolicy, "--restart-policy") ?? (base?.autorestart ?? true ? "always" : "never");
1174
1502
  if (parsed.autorestart === false) {
1175
1503
  restartPolicy = "never";
@@ -1189,6 +1517,7 @@ function resolvePmAppDefinition(base, parsed, workspaceRoot, source) {
1189
1517
  timeout: parsed.healthCheckTimeout,
1190
1518
  maxFailures: parsed.healthCheckMaxFailures
1191
1519
  } : base?.healthCheck);
1520
+ const waitReady = parsed.waitReady ?? base?.waitReady ?? false;
1192
1521
  const password = parsed.password ?? mergedWapkRun?.password ?? base?.password;
1193
1522
  const wapkRun = stripPmWapkSourceFromRunConfig(mergedWapkRun);
1194
1523
  if (password && !wapk) {
@@ -1197,8 +1526,14 @@ function resolvePmAppDefinition(base, parsed, workspaceRoot, source) {
1197
1526
  if (wapkRun && !wapk) {
1198
1527
  throw new Error("WAPK run options are only supported when starting a WAPK app.");
1199
1528
  }
1529
+ if (waitReady && !healthCheck) {
1530
+ throw new Error("--wait-ready requires --health-url or pm.apps[].healthCheck.");
1531
+ }
1200
1532
  return {
1201
- name,
1533
+ name: formatPmInstanceName(name, 1),
1534
+ baseName: name,
1535
+ instanceIndex: 1,
1536
+ instances,
1202
1537
  type: script ? "script" : wapk ? "wapk" : "file",
1203
1538
  source,
1204
1539
  cwd: resolvedCwd,
@@ -1213,9 +1548,19 @@ function resolvePmAppDefinition(base, parsed, workspaceRoot, source) {
1213
1548
  wapkRun,
1214
1549
  autorestart,
1215
1550
  restartDelay: parsed.restartDelay ?? base?.restartDelay ?? DEFAULT_RESTART_DELAY,
1551
+ proxy,
1552
+ killTimeout: parsed.killTimeout ?? base?.killTimeout ?? DEFAULT_PM_KILL_TIMEOUT,
1216
1553
  maxRestarts: parsed.maxRestarts ?? base?.maxRestarts ?? DEFAULT_MAX_RESTARTS,
1217
1554
  password,
1218
1555
  restartPolicy,
1556
+ maxMemoryBytes,
1557
+ memoryAction,
1558
+ cronRestart,
1559
+ expBackoffRestartDelay,
1560
+ expBackoffRestartMaxDelay,
1561
+ restartWindow,
1562
+ waitReady,
1563
+ listenTimeout: parsed.listenTimeout ?? base?.listenTimeout ?? DEFAULT_PM_LISTEN_TIMEOUT,
1219
1564
  minUptime: parsed.minUptime ?? base?.minUptime ?? DEFAULT_MIN_UPTIME,
1220
1565
  watch: watch2,
1221
1566
  watchPaths: watch2 ? normalizeResolvedWatchPaths(configuredWatchPaths, resolvedCwd, script ? "script" : wapk ? "wapk" : "file", file, wapk) : [],
@@ -1227,16 +1572,17 @@ function resolvePmAppDefinition(base, parsed, workspaceRoot, source) {
1227
1572
  function resolvePmStartDefinitions(parsed, config, workspaceRoot) {
1228
1573
  const configApps = getConfiguredPmApps(config);
1229
1574
  const selection = resolveStartSelection(configApps, parsed, workspaceRoot);
1575
+ const expandDefinitions = (definitions) => definitions.flatMap((definition) => expandPmInstanceDefinitions(definition));
1230
1576
  if (selection.startAll) {
1231
1577
  if (configApps.length === 0) {
1232
1578
  throw new Error("No pm apps configured in elit.config.* and no start target was provided.");
1233
1579
  }
1234
- return configApps.map((app) => resolvePmAppDefinition(app, { ...parsed, name: app.name }, workspaceRoot, "config"));
1580
+ return expandDefinitions(configApps.map((app) => resolvePmAppDefinition(app, { ...parsed, name: app.name }, workspaceRoot, "config")));
1235
1581
  }
1236
1582
  if (selection.selected) {
1237
- return [resolvePmAppDefinition(selection.selected, parsed, workspaceRoot, "config")];
1583
+ return expandDefinitions([resolvePmAppDefinition(selection.selected, parsed, workspaceRoot, "config")]);
1238
1584
  }
1239
- return [resolvePmAppDefinition(void 0, parsed, workspaceRoot, "cli")];
1585
+ return expandDefinitions([resolvePmAppDefinition(void 0, parsed, workspaceRoot, "cli")]);
1240
1586
  }
1241
1587
  function parsePmStartArgs(args) {
1242
1588
  const parsed = {
@@ -1309,6 +1655,39 @@ function parsePmStartArgs(args) {
1309
1655
  parsed.env[key] = value;
1310
1656
  break;
1311
1657
  }
1658
+ case "--instances":
1659
+ parsed.instances = normalizeIntegerOption(readRequiredValue(args, ++index, "--instances"), "--instances", 1);
1660
+ break;
1661
+ case "--proxy-port":
1662
+ parsed.proxy = {
1663
+ ...parsed.proxy,
1664
+ port: normalizeIntegerOption(readRequiredValue(args, ++index, "--proxy-port"), "--proxy-port", 1)
1665
+ };
1666
+ break;
1667
+ case "--proxy-strategy":
1668
+ parsed.proxy = {
1669
+ ...parsed.proxy,
1670
+ strategy: normalizePmProxyStrategy(readRequiredValue(args, ++index, "--proxy-strategy"), "--proxy-strategy")
1671
+ };
1672
+ break;
1673
+ case "--proxy-host":
1674
+ parsed.proxy = {
1675
+ ...parsed.proxy,
1676
+ host: readRequiredValue(args, ++index, "--proxy-host")
1677
+ };
1678
+ break;
1679
+ case "--proxy-target-host":
1680
+ parsed.proxy = {
1681
+ ...parsed.proxy,
1682
+ targetHost: readRequiredValue(args, ++index, "--proxy-target-host")
1683
+ };
1684
+ break;
1685
+ case "--proxy-env":
1686
+ parsed.proxy = {
1687
+ ...parsed.proxy,
1688
+ envVar: readRequiredValue(args, ++index, "--proxy-env")
1689
+ };
1690
+ break;
1312
1691
  case "--password":
1313
1692
  parsed.password = readRequiredValue(args, ++index, "--password");
1314
1693
  break;
@@ -1359,6 +1738,30 @@ function parsePmStartArgs(args) {
1359
1738
  case "--restart-policy":
1360
1739
  parsed.restartPolicy = normalizePmRestartPolicy(readRequiredValue(args, ++index, "--restart-policy"));
1361
1740
  break;
1741
+ case "--max-memory":
1742
+ parsed.maxMemoryBytes = normalizePmMemoryLimit(readRequiredValue(args, ++index, "--max-memory"), "--max-memory");
1743
+ break;
1744
+ case "--memory-action":
1745
+ parsed.memoryAction = normalizePmMemoryAction(readRequiredValue(args, ++index, "--memory-action"), "--memory-action");
1746
+ break;
1747
+ case "--cron-restart":
1748
+ parsed.cronRestart = normalizePmRestartSchedule(readRequiredValue(args, ++index, "--cron-restart"), "--cron-restart");
1749
+ break;
1750
+ case "--exp-backoff-restart-delay":
1751
+ parsed.expBackoffRestartDelay = normalizeIntegerOption(readRequiredValue(args, ++index, "--exp-backoff-restart-delay"), "--exp-backoff-restart-delay", 1);
1752
+ break;
1753
+ case "--exp-backoff-restart-max-delay":
1754
+ parsed.expBackoffRestartMaxDelay = normalizeIntegerOption(readRequiredValue(args, ++index, "--exp-backoff-restart-max-delay"), "--exp-backoff-restart-max-delay", 1);
1755
+ break;
1756
+ case "--restart-window":
1757
+ parsed.restartWindow = normalizeIntegerOption(readRequiredValue(args, ++index, "--restart-window"), "--restart-window", 1);
1758
+ break;
1759
+ case "--wait-ready":
1760
+ parsed.waitReady = true;
1761
+ break;
1762
+ case "--listen-timeout":
1763
+ parsed.listenTimeout = normalizeIntegerOption(readRequiredValue(args, ++index, "--listen-timeout"), "--listen-timeout", 1);
1764
+ break;
1362
1765
  case "--min-uptime":
1363
1766
  parsed.minUptime = normalizeIntegerOption(readRequiredValue(args, ++index, "--min-uptime"), "--min-uptime");
1364
1767
  break;
@@ -1398,6 +1801,9 @@ function parsePmStartArgs(args) {
1398
1801
  case "--restart-delay":
1399
1802
  parsed.restartDelay = normalizeIntegerOption(readRequiredValue(args, ++index, "--restart-delay"), "--restart-delay");
1400
1803
  break;
1804
+ case "--kill-timeout":
1805
+ parsed.killTimeout = normalizeIntegerOption(readRequiredValue(args, ++index, "--kill-timeout"), "--kill-timeout", 1);
1806
+ break;
1401
1807
  case "--max-restarts":
1402
1808
  parsed.maxRestarts = normalizeIntegerOption(readRequiredValue(args, ++index, "--max-restarts"), "--max-restarts");
1403
1809
  break;
@@ -1439,8 +1845,15 @@ function printPmHelp() {
1439
1845
  " elit pm start my-app",
1440
1846
  " elit pm start",
1441
1847
  " elit pm list",
1848
+ " elit pm list --json",
1849
+ " elit pm show <name>",
1850
+ " elit pm describe <name> --json",
1442
1851
  " elit pm stop <name|all>",
1443
1852
  " elit pm restart <name|all>",
1853
+ " elit pm reload <name|all>",
1854
+ " elit pm scale <name> <count>",
1855
+ " elit pm reset <name|all>",
1856
+ " elit pm send-signal <signal> <name|all>",
1444
1857
  " elit pm delete <name|all>",
1445
1858
  " elit pm save",
1446
1859
  " elit pm resurrect",
@@ -1456,6 +1869,12 @@ function printPmHelp() {
1456
1869
  " --google-drive-shared-drive Forward supportsAllDrives=true for shared drives",
1457
1870
  " --runtime, -r <name> Runtime override: node, bun, deno",
1458
1871
  " --name, -n <name> Process name used by list/stop/restart",
1872
+ " --instances <count> Start multiple managed instances for the same app name",
1873
+ " --proxy-port <port> Own a public HTTP port through a PM proxy for single-instance reload handoff",
1874
+ " --proxy-strategy <mode> Public socket mode: proxy or inherit (default: proxy)",
1875
+ " --proxy-host <host> Public host bound by the PM proxy (default: 0.0.0.0)",
1876
+ " --proxy-target-host <host> Internal host used for upstream child traffic (default: 127.0.0.1)",
1877
+ " --proxy-env <name> Env var populated with the child private port (default: PORT)",
1459
1878
  " --cwd <dir> Working directory for the managed process",
1460
1879
  " --env KEY=VALUE Add or override an environment variable",
1461
1880
  " --password <value> Password for locked WAPK archives",
@@ -1467,6 +1886,14 @@ function printPmHelp() {
1467
1886
  " --no-archive-watch Disable archive-source read sync for WAPK apps",
1468
1887
  " --archive-sync-interval <ms> Forward WAPK archive read-sync interval (>= 50ms)",
1469
1888
  " --restart-policy <mode> Restart policy: always, on-failure, never",
1889
+ " --max-memory <bytes|size> Trigger a memory action after a limit like 268435456 or 256M",
1890
+ " --memory-action <mode> Action on max-memory: restart, stop",
1891
+ " --cron-restart <expr> Restart on a cron schedule or @every <duration>",
1892
+ " --exp-backoff-restart-delay <ms> Exponential unstable-restart backoff base delay",
1893
+ " --exp-backoff-restart-max-delay <ms> Maximum unstable-restart backoff delay (default 15000)",
1894
+ " --restart-window <ms> Rolling time window used when counting restart attempts",
1895
+ " --wait-ready Keep the process in starting state until its health check succeeds",
1896
+ " --listen-timeout <ms> Startup wait limit when --wait-ready is enabled (default 3000)",
1470
1897
  " --min-uptime <ms> Reset crash counter after this healthy uptime",
1471
1898
  " --watch Restart when watched files change",
1472
1899
  " --watch-path <path> Add a file or directory to watch",
@@ -1479,6 +1906,7 @@ function printPmHelp() {
1479
1906
  " --health-max-failures <n> Consecutive failures before restart (default 3)",
1480
1907
  " --no-autorestart Disable automatic restart",
1481
1908
  " --restart-delay <ms> Delay between restart attempts (default 1000)",
1909
+ " --kill-timeout <ms> Grace period before force-killing a stop or restart (default 5000)",
1482
1910
  " --max-restarts <count> Maximum restart attempts (default 10)",
1483
1911
  "",
1484
1912
  "Config:",
@@ -1502,6 +1930,17 @@ function printPmHelp() {
1502
1930
  " - elit pm save persists running apps to pm.dumpFile or ./.elit/pm/dump.json.",
1503
1931
  " - elit pm resurrect restarts whatever was last saved by elit pm save.",
1504
1932
  " - elit pm start <name> starts a configured app by name.",
1933
+ " - elit pm reload <name|all> performs a rolling stop/start and waits for each instance to return online before continuing.",
1934
+ " - elit pm scale <name> <count> changes the number of managed instances for a running app group.",
1935
+ " - elit pm reset <name|all> clears restart count, last exit code, and saved error metadata.",
1936
+ " - elit pm send-signal <signal> <name|all> forwards a POSIX-style signal such as SIGUSR2 or TERM.",
1937
+ " - 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.",
1938
+ " - killTimeout controls how long PM waits before force-killing an app that ignores stop or restart requests.",
1939
+ " - waitReady uses the configured health check as a startup gate and errors if it never becomes healthy within listenTimeout.",
1940
+ " - elit pm list shows live cpu, memory, and uptime columns when the child process is running.",
1941
+ " - elit pm list --json and elit pm jlist print machine-readable process state.",
1942
+ " - elit pm list --json, elit pm show --json, and elit pm describe --json include liveMetrics when available.",
1943
+ " - elit pm show <name> and elit pm describe <name> expose the full saved process record.",
1505
1944
  " - TypeScript files with runtime node require tsx, otherwise use --runtime bun.",
1506
1945
  " - WAPK processes are executed through elit wapk run inside the manager.",
1507
1946
  " - WAPK PM apps can use local archives, gdrive://<fileId>, or pm.apps[].wapkRun.googleDrive.",
@@ -1557,16 +1996,94 @@ function resolveTsxExecutable(cwd) {
1557
1996
  const globalCommand = process.platform === "win32" ? "tsx.cmd" : "tsx";
1558
1997
  return commandExists(globalCommand) ? globalCommand : void 0;
1559
1998
  }
1999
+ function resolvePmNodeSharedListenerBootstrapPath() {
2000
+ const cliEntry = process.argv[1];
2001
+ const candidates = [
2002
+ (0, import_node_path5.join)(__dirname, "node-shared-listener-bootstrap.cjs"),
2003
+ (0, import_node_path5.join)(__dirname, "..", "..", "..", "dist", "pm-node-shared-listener-bootstrap.cjs")
2004
+ ];
2005
+ if (cliEntry) {
2006
+ const resolvedCliEntry = (0, import_node_path5.resolve)(cliEntry);
2007
+ const baseDir = (0, import_node_path5.dirname)(resolvedCliEntry);
2008
+ candidates.push(
2009
+ (0, import_node_path5.join)(baseDir, "cli", "pm", "node-shared-listener-bootstrap.cjs"),
2010
+ (0, import_node_path5.join)(baseDir, "pm-node-shared-listener-bootstrap.cjs")
2011
+ );
2012
+ }
2013
+ return candidates.find((candidate) => (0, import_node_fs3.existsSync)(candidate));
2014
+ }
2015
+ function appendNodeOption(existing, option) {
2016
+ return existing && existing.trim().length > 0 ? `${existing.trim()} ${option}` : option;
2017
+ }
2018
+ function supportsPmInheritedListener(record, runtime2) {
2019
+ return (record.proxy?.strategy ?? DEFAULT_PM_PROXY_STRATEGY) === "inherit" && record.type === "file" && runtime2 === "node" && !isTypescriptFile(record.file);
2020
+ }
1560
2021
  function inferRuntimeFromFile(filePath) {
1561
2022
  if (isTypescriptFile(filePath) && commandExists("bun")) {
1562
2023
  return "bun";
1563
2024
  }
1564
2025
  return "node";
1565
2026
  }
2027
+ function sampleWindowsPmProcessMetrics(pid) {
2028
+ const script = [
2029
+ '$ErrorActionPreference = "Stop"',
2030
+ `$sample = Get-CimInstance -ClassName Win32_PerfFormattedData_PerfProc_Process -Filter "IDProcess = ${pid}" | Select-Object -First 1`,
2031
+ "if (-not $sample) { exit 2 }",
2032
+ "$cpu = [double]$sample.PercentProcessorTime",
2033
+ `$memory = if ($sample.PSObject.Properties.Match('WorkingSetPrivate').Count -gt 0) { [int64]$sample.WorkingSetPrivate } else { [int64](Get-Process -Id ${pid} -ErrorAction Stop).WorkingSet64 }`,
2034
+ 'Write-Output ($cpu.ToString([System.Globalization.CultureInfo]::InvariantCulture) + "," + $memory)'
2035
+ ].join("; ");
2036
+ const result = (0, import_node_child_process.spawnSync)(
2037
+ "powershell.exe",
2038
+ ["-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-Command", script],
2039
+ {
2040
+ encoding: "utf8",
2041
+ stdio: ["ignore", "pipe", "ignore"],
2042
+ windowsHide: true
2043
+ }
2044
+ );
2045
+ if (result.error || result.status !== 0) {
2046
+ return {};
2047
+ }
2048
+ const [cpuText, memoryText] = result.stdout.trim().split(",");
2049
+ const cpuPercent = Number.parseFloat((cpuText ?? "").replace(",", "."));
2050
+ const memoryRssBytes = Number.parseInt(memoryText ?? "", 10);
2051
+ return {
2052
+ cpuPercent: Number.isFinite(cpuPercent) ? cpuPercent : void 0,
2053
+ memoryRssBytes: Number.isFinite(memoryRssBytes) ? memoryRssBytes : void 0
2054
+ };
2055
+ }
2056
+ function samplePosixPmProcessMetrics(pid) {
2057
+ const result = (0, import_node_child_process.spawnSync)(
2058
+ "ps",
2059
+ ["-p", String(pid), "-o", "%cpu=", "-o", "rss="],
2060
+ {
2061
+ encoding: "utf8",
2062
+ stdio: ["ignore", "pipe", "ignore"],
2063
+ windowsHide: true
2064
+ }
2065
+ );
2066
+ if (result.error || result.status !== 0) {
2067
+ return {};
2068
+ }
2069
+ const [cpuText, memoryText] = result.stdout.trim().split(/\s+/, 2);
2070
+ const cpuPercent = Number.parseFloat((cpuText ?? "").replace(",", "."));
2071
+ const rssKilobytes = Number.parseInt(memoryText ?? "", 10);
2072
+ return {
2073
+ cpuPercent: Number.isFinite(cpuPercent) ? cpuPercent : void 0,
2074
+ memoryRssBytes: Number.isFinite(rssKilobytes) ? rssKilobytes * 1024 : void 0
2075
+ };
2076
+ }
2077
+ function samplePmProcessMetrics(pid) {
2078
+ return process.platform === "win32" ? sampleWindowsPmProcessMetrics(pid) : samplePosixPmProcessMetrics(pid);
2079
+ }
1566
2080
  function isPmOnlineWapkRecord(record) {
1567
2081
  return record.type === "wapk" && isPmWapkOnlineRunConfig(record.wapkRun);
1568
2082
  }
1569
2083
  function buildPmCommand(record) {
2084
+ if ((record.proxy?.strategy ?? DEFAULT_PM_PROXY_STRATEGY) === "inherit" && record.type !== "file") {
2085
+ throw new Error('pm proxy strategy "inherit" currently supports only Node .js/.mjs/.cjs file targets.');
2086
+ }
1570
2087
  if (record.type === "script") {
1571
2088
  return {
1572
2089
  command: record.script,
@@ -1638,9 +2155,23 @@ function buildPmCommand(record) {
1638
2155
  }
1639
2156
  const executable = preferCurrentExecutable("node");
1640
2157
  ensureCommandAvailable(executable, "Node.js runtime");
2158
+ const wantsInheritedListener = (record.proxy?.strategy ?? DEFAULT_PM_PROXY_STRATEGY) === "inherit";
2159
+ if (wantsInheritedListener && !supportsPmInheritedListener(record, runtime2)) {
2160
+ throw new Error('pm proxy strategy "inherit" currently supports only Node .js/.mjs/.cjs file targets.');
2161
+ }
2162
+ const bootstrapPath = wantsInheritedListener ? resolvePmNodeSharedListenerBootstrapPath() : void 0;
2163
+ if (wantsInheritedListener && !bootstrapPath) {
2164
+ throw new Error("Unable to resolve the PM shared-listener bootstrap module.");
2165
+ }
1641
2166
  return {
1642
2167
  command: executable,
1643
2168
  args: [record.file],
2169
+ env: bootstrapPath ? {
2170
+ NODE_OPTIONS: appendNodeOption(process.env.NODE_OPTIONS, `--require=${bootstrapPath}`),
2171
+ ELIT_PM_LISTEN_MODE: "ipc",
2172
+ ELIT_PM_PUBLIC_PORT: String(record.proxy?.port ?? record.env.PORT ?? "")
2173
+ } : void 0,
2174
+ ipc: Boolean(bootstrapPath),
1644
2175
  runtime: runtime2,
1645
2176
  preview: `${(0, import_node_path5.basename)(executable)} ${quoteCommandSegment(record.file)}`
1646
2177
  };
@@ -1652,20 +2183,33 @@ function createRecordFromDefinition(definition, paths, existing) {
1652
2183
  return {
1653
2184
  id,
1654
2185
  name: definition.name,
2186
+ baseName: definition.baseName,
2187
+ instanceIndex: definition.instanceIndex,
2188
+ instances: definition.instances,
1655
2189
  type: definition.type,
1656
2190
  source: definition.source,
1657
2191
  cwd: definition.cwd,
1658
2192
  runtime: definition.runtime,
1659
2193
  env: definition.env,
2194
+ proxy: definition.proxy,
1660
2195
  script: definition.script,
1661
2196
  file: definition.file,
1662
2197
  wapk: definition.wapk,
1663
2198
  wapkRun: definition.wapkRun,
1664
2199
  autorestart: definition.autorestart,
1665
2200
  restartDelay: definition.restartDelay,
2201
+ killTimeout: definition.killTimeout,
1666
2202
  maxRestarts: definition.maxRestarts,
1667
2203
  password: definition.password,
1668
2204
  restartPolicy: definition.restartPolicy,
2205
+ maxMemoryBytes: definition.maxMemoryBytes,
2206
+ memoryAction: definition.memoryAction,
2207
+ cronRestart: definition.cronRestart,
2208
+ expBackoffRestartDelay: definition.expBackoffRestartDelay,
2209
+ expBackoffRestartMaxDelay: definition.expBackoffRestartMaxDelay,
2210
+ restartWindow: definition.restartWindow,
2211
+ waitReady: definition.waitReady,
2212
+ listenTimeout: definition.listenTimeout,
1669
2213
  minUptime: definition.minUptime,
1670
2214
  watch: definition.watch,
1671
2215
  watchPaths: definition.watchPaths,
@@ -1681,16 +2225,20 @@ function createRecordFromDefinition(definition, paths, existing) {
1681
2225
  stoppedAt: void 0,
1682
2226
  runnerPid: void 0,
1683
2227
  childPid: void 0,
2228
+ proxyTargetPort: void 0,
1684
2229
  restartCount: existing?.restartCount ?? 0,
2230
+ reloadRequestedAt: void 0,
2231
+ lastRestartAt: existing?.lastRestartAt,
1685
2232
  lastExitCode: existing?.lastExitCode,
1686
2233
  error: void 0,
2234
+ proxyReadyAt: void 0,
1687
2235
  logFiles: existing?.logFiles ?? {
1688
2236
  out: (0, import_node_path5.join)(paths.logsDir, `${id}.out.log`),
1689
2237
  err: (0, import_node_path5.join)(paths.logsDir, `${id}.err.log`)
1690
2238
  }
1691
2239
  };
1692
2240
  }
1693
- function terminateProcessTree(pid) {
2241
+ function terminateProcessTree(pid, options) {
1694
2242
  if (process.platform === "win32") {
1695
2243
  const result = (0, import_node_child_process.spawnSync)("taskkill", ["/PID", String(pid), "/T", "/F"], {
1696
2244
  stdio: "ignore",
@@ -1702,7 +2250,17 @@ function terminateProcessTree(pid) {
1702
2250
  return;
1703
2251
  }
1704
2252
  try {
1705
- process.kill(pid, "SIGTERM");
2253
+ process.kill(pid, options?.force ? "SIGKILL" : "SIGTERM");
2254
+ } catch (error) {
2255
+ const code = error.code;
2256
+ if (code !== "ESRCH") {
2257
+ throw error;
2258
+ }
2259
+ }
2260
+ }
2261
+ function sendPmSignal(pid, signal) {
2262
+ try {
2263
+ process.kill(pid, signal);
1706
2264
  } catch (error) {
1707
2265
  const code = error.code;
1708
2266
  if (code !== "ESRCH") {
@@ -1754,6 +2312,7 @@ async function startManagedProcess(definition, paths) {
1754
2312
 
1755
2313
  // src/cli/pm/runner.ts
1756
2314
  var import_node_child_process2 = require("child_process");
2315
+ var import_node_http2 = require("http");
1757
2316
  var import_node_fs4 = require("fs");
1758
2317
  var import_node_os = require("os");
1759
2318
  var import_node_path6 = require("path");
@@ -1987,13 +2546,13 @@ var win32 = createPathOps(true);
1987
2546
  function join5(...paths) {
1988
2547
  return joinPaths(paths, isWindows);
1989
2548
  }
1990
- function resolve4(...paths) {
2549
+ function resolve5(...paths) {
1991
2550
  return resolvePaths(paths, isWindows);
1992
2551
  }
1993
2552
  function relative(from, to) {
1994
2553
  return relativePath(from, to, isWindows);
1995
2554
  }
1996
- function dirname2(path) {
2555
+ function dirname3(path) {
1997
2556
  return getDirname(path, isWindows);
1998
2557
  }
1999
2558
 
@@ -2031,14 +2590,14 @@ function getBaseDirectory(pattern) {
2031
2590
  }
2032
2591
  if (normalizedPattern && existsSync4(normalizedPattern)) {
2033
2592
  try {
2034
- return statSync2(normalizedPattern).isDirectory() ? normalizedPattern : normalizePath2(dirname2(normalizedPattern)) || ".";
2593
+ return statSync2(normalizedPattern).isDirectory() ? normalizedPattern : normalizePath2(dirname3(normalizedPattern)) || ".";
2035
2594
  } catch {
2036
- return normalizePath2(dirname2(normalizedPattern)) || ".";
2595
+ return normalizePath2(dirname3(normalizedPattern)) || ".";
2037
2596
  }
2038
2597
  }
2039
2598
  const lastSegment = parts[parts.length - 1] || "";
2040
2599
  if (lastSegment.includes(".") && !lastSegment.startsWith(".")) {
2041
- return normalizePath2(dirname2(normalizedPattern)) || ".";
2600
+ return normalizePath2(dirname3(normalizedPattern)) || ".";
2042
2601
  }
2043
2602
  return normalizedPattern || ".";
2044
2603
  }
@@ -2233,9 +2792,210 @@ function watch(paths, options) {
2233
2792
  return watcher;
2234
2793
  }
2235
2794
 
2795
+ // src/cli/pm/proxy.ts
2796
+ var import_node_http = require("http");
2797
+ var import_node_https = require("https");
2798
+ var import_node_net = require("net");
2799
+ function resolvePmProxyHost(proxy) {
2800
+ return proxy.host?.trim() || "0.0.0.0";
2801
+ }
2802
+ function resolvePmProxyTargetHost(proxy) {
2803
+ return proxy.targetHost?.trim() || "127.0.0.1";
2804
+ }
2805
+ function resolvePmProxyEnvVar(proxy) {
2806
+ return proxy.envVar?.trim() || "PORT";
2807
+ }
2808
+ function buildPmProxyTargetUrl(proxy, targetPort) {
2809
+ return `http://${resolvePmProxyTargetHost(proxy)}:${targetPort}`;
2810
+ }
2811
+ function rewritePmProxyHealthCheckUrl(url, targetHost, targetPort) {
2812
+ const targetUrl = new URL(url);
2813
+ targetUrl.hostname = targetHost;
2814
+ targetUrl.port = String(targetPort);
2815
+ return targetUrl.toString();
2816
+ }
2817
+ async function allocatePmProxyTargetPort(host = "127.0.0.1") {
2818
+ const server = (0, import_node_net.createServer)();
2819
+ return await new Promise((resolve7, reject) => {
2820
+ server.once("error", reject);
2821
+ server.listen(0, host, () => {
2822
+ const address = server.address();
2823
+ if (!address || typeof address === "string") {
2824
+ server.close(() => reject(new Error("Failed to allocate an internal PM proxy port.")));
2825
+ return;
2826
+ }
2827
+ const port = address.port;
2828
+ server.close((error) => {
2829
+ if (error) {
2830
+ reject(error);
2831
+ return;
2832
+ }
2833
+ resolve7(port);
2834
+ });
2835
+ });
2836
+ });
2837
+ }
2838
+ function buildPmProxyHeaders(headersInput, host) {
2839
+ const headers = {};
2840
+ for (const [key, value] of Object.entries(headersInput)) {
2841
+ if (value !== void 0) {
2842
+ headers[key] = value;
2843
+ }
2844
+ }
2845
+ headers.host = host;
2846
+ return headers;
2847
+ }
2848
+ function writeRawHttpResponse(socket, statusCode, statusMessage, headers) {
2849
+ const lines = [`HTTP/1.1 ${statusCode} ${statusMessage}`];
2850
+ for (const [key, value] of Object.entries(headers)) {
2851
+ if (value === void 0) {
2852
+ continue;
2853
+ }
2854
+ if (Array.isArray(value)) {
2855
+ for (const item of value) {
2856
+ lines.push(`${key}: ${item}`);
2857
+ }
2858
+ continue;
2859
+ }
2860
+ lines.push(`${key}: ${value}`);
2861
+ }
2862
+ socket.write(`${lines.join("\r\n")}\r
2863
+ \r
2864
+ `);
2865
+ }
2866
+ async function createPmProxyController(proxy) {
2867
+ let targets = [];
2868
+ let nextTargetIndex = 0;
2869
+ const setResolvedTargets = (nextTargets) => {
2870
+ const unchanged = nextTargets.length === targets.length && nextTargets.every((target, index) => targets[index]?.href === target.href);
2871
+ targets = nextTargets;
2872
+ if (targets.length === 0) {
2873
+ nextTargetIndex = 0;
2874
+ return;
2875
+ }
2876
+ if (!unchanged) {
2877
+ nextTargetIndex = nextTargetIndex % targets.length;
2878
+ }
2879
+ };
2880
+ const pickTarget = () => {
2881
+ if (targets.length === 0) {
2882
+ return null;
2883
+ }
2884
+ const target = targets[nextTargetIndex % targets.length];
2885
+ nextTargetIndex = (nextTargetIndex + 1) % targets.length;
2886
+ return target;
2887
+ };
2888
+ const server = (0, import_node_http.createServer)((req, res) => {
2889
+ const target = pickTarget();
2890
+ if (!target) {
2891
+ res.statusCode = 503;
2892
+ res.end("PM proxy target is not ready.");
2893
+ return;
2894
+ }
2895
+ const requestLib = target.protocol === "https:" ? import_node_https.request : import_node_http.request;
2896
+ const targetUrl = new URL(req.url || "/", target);
2897
+ const headers = buildPmProxyHeaders(req.headers, target.host);
2898
+ const proxyReq = requestLib(targetUrl, {
2899
+ method: req.method,
2900
+ headers
2901
+ }, (proxyRes) => {
2902
+ const outgoingHeaders = {};
2903
+ for (const [key, value] of Object.entries(proxyRes.headers)) {
2904
+ if (value !== void 0) {
2905
+ outgoingHeaders[key] = value;
2906
+ }
2907
+ }
2908
+ res.writeHead(proxyRes.statusCode || 200, outgoingHeaders);
2909
+ proxyRes.pipe(res);
2910
+ });
2911
+ proxyReq.on("error", (error) => {
2912
+ if (!res.headersSent) {
2913
+ res.statusCode = 502;
2914
+ }
2915
+ res.end(`PM proxy error: ${error.message}`);
2916
+ });
2917
+ req.pipe(proxyReq);
2918
+ });
2919
+ server.on("upgrade", (req, socket, head) => {
2920
+ const target = pickTarget();
2921
+ if (!target) {
2922
+ writeRawHttpResponse(socket, 503, "Service Unavailable", {
2923
+ connection: "close",
2924
+ "content-length": 0
2925
+ });
2926
+ socket.destroy();
2927
+ return;
2928
+ }
2929
+ const requestLib = target.protocol === "https:" ? import_node_https.request : import_node_http.request;
2930
+ const targetUrl = new URL(req.url || "/", target);
2931
+ const proxyReq = requestLib(targetUrl, {
2932
+ method: req.method,
2933
+ headers: buildPmProxyHeaders(req.headers, target.host)
2934
+ });
2935
+ proxyReq.on("upgrade", (proxyRes, proxySocket, proxyHead) => {
2936
+ writeRawHttpResponse(socket, proxyRes.statusCode || 101, proxyRes.statusMessage || "Switching Protocols", proxyRes.headers);
2937
+ if (head.length > 0) {
2938
+ proxySocket.write(head);
2939
+ }
2940
+ if (proxyHead.length > 0) {
2941
+ socket.write(proxyHead);
2942
+ }
2943
+ socket.on("error", () => proxySocket.destroy());
2944
+ proxySocket.on("error", () => socket.destroy());
2945
+ proxySocket.pipe(socket);
2946
+ socket.pipe(proxySocket);
2947
+ });
2948
+ proxyReq.on("response", (proxyRes) => {
2949
+ writeRawHttpResponse(socket, proxyRes.statusCode || 502, proxyRes.statusMessage || "Bad Gateway", proxyRes.headers);
2950
+ proxyRes.pipe(socket);
2951
+ });
2952
+ proxyReq.on("error", (error) => {
2953
+ writeRawHttpResponse(socket, 502, "Bad Gateway", {
2954
+ connection: "close",
2955
+ "content-type": "text/plain; charset=utf-8",
2956
+ "content-length": Buffer.byteLength(`PM proxy error: ${error.message}`)
2957
+ });
2958
+ socket.end(`PM proxy error: ${error.message}`);
2959
+ });
2960
+ proxyReq.end();
2961
+ });
2962
+ await new Promise((resolve7, reject) => {
2963
+ server.once("error", reject);
2964
+ server.listen(proxy.port, resolvePmProxyHost(proxy), () => resolve7());
2965
+ });
2966
+ return {
2967
+ setTarget(targetUrl) {
2968
+ setResolvedTargets(targetUrl ? [new URL(targetUrl)] : []);
2969
+ },
2970
+ setTargets(targetUrls) {
2971
+ setResolvedTargets(targetUrls.map((targetUrl) => new URL(targetUrl)));
2972
+ },
2973
+ close() {
2974
+ return new Promise((resolve7, reject) => {
2975
+ server.close((error) => {
2976
+ if (error) {
2977
+ reject(error);
2978
+ return;
2979
+ }
2980
+ resolve7();
2981
+ });
2982
+ });
2983
+ }
2984
+ };
2985
+ }
2986
+
2236
2987
  // src/cli/pm/runner.ts
2237
2988
  function writePmLog(stream, message) {
2238
- stream.write(`[elit pm] ${(/* @__PURE__ */ new Date()).toISOString()} ${message}${import_node_os.EOL}`);
2989
+ if (stream.writableEnded || stream.destroyed) {
2990
+ return;
2991
+ }
2992
+ try {
2993
+ stream.write(`[elit pm] ${(/* @__PURE__ */ new Date()).toISOString()} ${message}${import_node_os.EOL}`);
2994
+ } catch (error) {
2995
+ if (error.code !== "ERR_STREAM_WRITE_AFTER_END") {
2996
+ throw error;
2997
+ }
2998
+ }
2239
2999
  }
2240
3000
  function waitForExit(code, signal) {
2241
3001
  if (typeof code === "number") {
@@ -2249,40 +3009,369 @@ function waitForExit(code, signal) {
2249
3009
  async function delay(milliseconds) {
2250
3010
  await new Promise((resolvePromise) => setTimeout(resolvePromise, milliseconds));
2251
3011
  }
2252
- async function waitForProcessTermination(pid, timeoutMs) {
2253
- if (!pid || !isProcessAlive(pid)) {
2254
- return true;
2255
- }
2256
- const deadline = Date.now() + timeoutMs;
2257
- while (Date.now() < deadline) {
2258
- if (!isProcessAlive(pid)) {
2259
- return true;
2260
- }
2261
- await delay(DEFAULT_PM_STOP_POLL_MS);
2262
- }
2263
- return !isProcessAlive(pid);
3012
+ function resolveRunnerPathsFromRecordFile(filePath) {
3013
+ const appsDir = (0, import_node_path6.dirname)(filePath);
3014
+ const dataDir = (0, import_node_path6.dirname)(appsDir);
3015
+ return {
3016
+ dataDir,
3017
+ appsDir,
3018
+ logsDir: (0, import_node_path6.join)(dataDir, "logs"),
3019
+ dumpFile: (0, import_node_path6.join)(dataDir, DEFAULT_PM_DUMP_FILE)
3020
+ };
2264
3021
  }
2265
- async function waitForManagedChildExit(child) {
2266
- return await new Promise((resolvePromise) => {
2267
- let resolved = false;
2268
- child.once("error", (error) => {
2269
- if (resolved) {
2270
- return;
2271
- }
2272
- resolved = true;
2273
- resolvePromise({ code: 1, signal: null, error: error instanceof Error ? error.message : String(error) });
2274
- });
2275
- child.once("close", (code, signal) => {
2276
- if (resolved) {
2277
- return;
2278
- }
2279
- resolved = true;
2280
- resolvePromise({ code, signal });
2281
- });
2282
- });
3022
+ function usesPmProxyController(record) {
3023
+ return Boolean(record.proxy) && (record.proxy?.strategy ?? DEFAULT_PM_PROXY_STRATEGY) === "proxy";
2283
3024
  }
2284
- async function createPmWatchController(record, onChange, onError) {
2285
- if (!record.watch || record.watchPaths.length === 0) {
3025
+ function usesPmInheritedListener(record) {
3026
+ return Boolean(record.proxy) && (record.proxy?.strategy ?? DEFAULT_PM_PROXY_STRATEGY) === "inherit";
3027
+ }
3028
+ function isPmProxyOwner(record) {
3029
+ return usesPmProxyController(record) && record.instanceIndex === 1;
3030
+ }
3031
+ function resolvePmProxyTargetUrls(paths, baseName) {
3032
+ 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));
3033
+ }
3034
+ async function createPmInheritedListener(proxy) {
3035
+ const server = (0, import_node_http2.createServer)();
3036
+ await new Promise((resolvePromise, reject) => {
3037
+ server.once("error", reject);
3038
+ server.listen(proxy.port, resolvePmProxyHost(proxy), () => resolvePromise());
3039
+ });
3040
+ return server;
3041
+ }
3042
+ function createPmChildIpcState(child, sharedListener) {
3043
+ let bootstrapReady = false;
3044
+ let listenerReady = false;
3045
+ let sharedHandleSent = false;
3046
+ const sendSharedHandle = () => {
3047
+ if (!sharedListener || !bootstrapReady || sharedHandleSent || !child.connected) {
3048
+ return;
3049
+ }
3050
+ sharedHandleSent = true;
3051
+ child.send?.({ type: "elit:pm:listen-handle" }, sharedListener);
3052
+ };
3053
+ const onMessage = (message) => {
3054
+ if (!message || typeof message !== "object") {
3055
+ return;
3056
+ }
3057
+ if (message.type === "elit:pm:bootstrap-ready") {
3058
+ bootstrapReady = true;
3059
+ sendSharedHandle();
3060
+ return;
3061
+ }
3062
+ if (message.type === "elit:pm:listener-ready") {
3063
+ listenerReady = true;
3064
+ }
3065
+ };
3066
+ child.on("message", onMessage);
3067
+ return {
3068
+ get bootstrapReady() {
3069
+ return bootstrapReady;
3070
+ },
3071
+ get listenerReady() {
3072
+ return listenerReady;
3073
+ },
3074
+ stop() {
3075
+ child.off("message", onMessage);
3076
+ }
3077
+ };
3078
+ }
3079
+ function buildPmChildEnv(record, command, targetPort) {
3080
+ return {
3081
+ ...process.env,
3082
+ ...record.env,
3083
+ ...command.env,
3084
+ ...usesPmProxyController(record) && targetPort ? {
3085
+ [resolvePmProxyEnvVar(record.proxy)]: String(targetPort),
3086
+ ELIT_PM_PUBLIC_PORT: String(record.proxy.port)
3087
+ } : {},
3088
+ ELIT_PM_NAME: record.name,
3089
+ ELIT_PM_ID: record.id
3090
+ };
3091
+ }
3092
+ function buildPmChildStdio(command, onlineStdinShutdownEnabled) {
3093
+ return [
3094
+ onlineStdinShutdownEnabled ? "pipe" : "ignore",
3095
+ "pipe",
3096
+ "pipe",
3097
+ ...command.ipc ? ["ipc"] : []
3098
+ ];
3099
+ }
3100
+ function createPmReadinessMonitor(record, onReady, onFailure, options) {
3101
+ if (options?.ipcController) {
3102
+ let stopped2 = false;
3103
+ let timer2 = null;
3104
+ let timeoutTimer2 = null;
3105
+ const clearTimers2 = () => {
3106
+ if (timer2) {
3107
+ clearInterval(timer2);
3108
+ timer2 = null;
3109
+ }
3110
+ if (timeoutTimer2) {
3111
+ clearTimeout(timeoutTimer2);
3112
+ timeoutTimer2 = null;
3113
+ }
3114
+ };
3115
+ const host = record.proxy?.host ?? "0.0.0.0";
3116
+ const port = record.proxy?.port ?? 0;
3117
+ timer2 = setInterval(() => {
3118
+ if (stopped2 || !options.ipcController?.listenerReady) {
3119
+ return;
3120
+ }
3121
+ stopped2 = true;
3122
+ clearTimers2();
3123
+ onReady(`shared listener ready on ${host}:${port}`);
3124
+ }, 25);
3125
+ timer2.unref?.();
3126
+ timeoutTimer2 = setTimeout(() => {
3127
+ if (stopped2) {
3128
+ return;
3129
+ }
3130
+ stopped2 = true;
3131
+ clearTimers2();
3132
+ onFailure(`listen timeout reached after ${record.listenTimeout}ms while waiting for shared listener ${host}:${port}`);
3133
+ }, record.listenTimeout);
3134
+ timeoutTimer2.unref?.();
3135
+ return {
3136
+ stop() {
3137
+ stopped2 = true;
3138
+ clearTimers2();
3139
+ }
3140
+ };
3141
+ }
3142
+ if (!record.waitReady || !record.healthCheck) {
3143
+ return {
3144
+ stop() {
3145
+ }
3146
+ };
3147
+ }
3148
+ const healthCheck = record.healthCheck;
3149
+ const pollInterval = Math.max(50, Math.min(healthCheck.interval, 250));
3150
+ let stopped = false;
3151
+ let timer = null;
3152
+ let timeoutTimer = null;
3153
+ let inFlight = false;
3154
+ const clearTimers = () => {
3155
+ if (timer) {
3156
+ clearInterval(timer);
3157
+ timer = null;
3158
+ }
3159
+ if (timeoutTimer) {
3160
+ clearTimeout(timeoutTimer);
3161
+ timeoutTimer = null;
3162
+ }
3163
+ };
3164
+ const runHealthCheck = async () => {
3165
+ if (stopped || inFlight) {
3166
+ return;
3167
+ }
3168
+ inFlight = true;
3169
+ const controller = new AbortController();
3170
+ const timeoutId = setTimeout(() => controller.abort(), healthCheck.timeout);
3171
+ timeoutId.unref?.();
3172
+ try {
3173
+ const response = await fetch(healthCheck.url, {
3174
+ method: "GET",
3175
+ signal: controller.signal
3176
+ });
3177
+ if (stopped) {
3178
+ return;
3179
+ }
3180
+ if (!response.ok) {
3181
+ throw new Error(`health check returned ${response.status}`);
3182
+ }
3183
+ stopped = true;
3184
+ clearTimers();
3185
+ onReady(`readiness check passed: ${healthCheck.url}`);
3186
+ } catch {
3187
+ } finally {
3188
+ clearTimeout(timeoutId);
3189
+ inFlight = false;
3190
+ }
3191
+ };
3192
+ timeoutTimer = setTimeout(() => {
3193
+ if (stopped) {
3194
+ return;
3195
+ }
3196
+ stopped = true;
3197
+ clearTimers();
3198
+ onFailure(`listen timeout reached after ${record.listenTimeout}ms while waiting for ${healthCheck.url}`);
3199
+ }, record.listenTimeout);
3200
+ timeoutTimer.unref?.();
3201
+ void runHealthCheck();
3202
+ timer = setInterval(() => {
3203
+ void runHealthCheck();
3204
+ }, pollInterval);
3205
+ timer.unref?.();
3206
+ return {
3207
+ stop() {
3208
+ stopped = true;
3209
+ clearTimers();
3210
+ }
3211
+ };
3212
+ }
3213
+ function resolvePmStopTimeout(record) {
3214
+ if (isPmOnlineWapkRecord(record)) {
3215
+ return Math.max(record.killTimeout, PM_WAPK_ONLINE_SHUTDOWN_TIMEOUT_MS);
3216
+ }
3217
+ return record.killTimeout;
3218
+ }
3219
+ function supportsPmProxyReload(record) {
3220
+ return Boolean(record.proxy) && record.instances === 1;
3221
+ }
3222
+ function buildPmMonitorRecord(record, targetPort) {
3223
+ if (!record.proxy || !targetPort) {
3224
+ return record;
3225
+ }
3226
+ const targetHost = resolvePmProxyTargetHost(record.proxy);
3227
+ return {
3228
+ ...record,
3229
+ proxyTargetPort: targetPort,
3230
+ healthCheck: record.healthCheck ? {
3231
+ ...record.healthCheck,
3232
+ url: rewritePmProxyHealthCheckUrl(record.healthCheck.url, targetHost, targetPort)
3233
+ } : void 0
3234
+ };
3235
+ }
3236
+ async function waitForProcessTermination(pid, timeoutMs) {
3237
+ if (!pid || !isProcessAlive(pid)) {
3238
+ return true;
3239
+ }
3240
+ const deadline = Date.now() + timeoutMs;
3241
+ while (Date.now() < deadline) {
3242
+ if (!isProcessAlive(pid)) {
3243
+ return true;
3244
+ }
3245
+ await delay(DEFAULT_PM_STOP_POLL_MS);
3246
+ }
3247
+ return !isProcessAlive(pid);
3248
+ }
3249
+ async function waitForManagedChildExit(child) {
3250
+ return await new Promise((resolvePromise) => {
3251
+ let resolved = false;
3252
+ child.once("error", (error) => {
3253
+ if (resolved) {
3254
+ return;
3255
+ }
3256
+ resolved = true;
3257
+ resolvePromise({ code: 1, signal: null, error: error instanceof Error ? error.message : String(error) });
3258
+ });
3259
+ child.once("close", (code, signal) => {
3260
+ if (resolved) {
3261
+ return;
3262
+ }
3263
+ resolved = true;
3264
+ resolvePromise({ code, signal });
3265
+ });
3266
+ });
3267
+ }
3268
+ async function createPmChildControllers(record, child, stdoutLog, stderrLog, requestPlannedRestart, onReady, options) {
3269
+ let healthMonitor = {
3270
+ stop() {
3271
+ }
3272
+ };
3273
+ let memoryMonitor = {
3274
+ stop() {
3275
+ }
3276
+ };
3277
+ let scheduleMonitor = {
3278
+ stop() {
3279
+ }
3280
+ };
3281
+ const startHealthMonitor = () => {
3282
+ healthMonitor = createPmHealthMonitor(
3283
+ record,
3284
+ (message) => requestPlannedRestart("health", message),
3285
+ (message) => writePmLog(stdoutLog, message)
3286
+ );
3287
+ };
3288
+ const needsReadySignal = Boolean(options?.ipcController) || record.waitReady;
3289
+ const readinessMonitor = options?.ready || !needsReadySignal ? { stop() {
3290
+ } } : createPmReadinessMonitor(
3291
+ record,
3292
+ (message) => {
3293
+ onReady(message);
3294
+ startHealthMonitor();
3295
+ },
3296
+ (message) => requestPlannedRestart("startup", message),
3297
+ { ipcController: options?.ipcController }
3298
+ );
3299
+ const watchController = await createPmWatchController(
3300
+ record,
3301
+ (changedPath) => requestPlannedRestart("watch", changedPath),
3302
+ (message) => writePmLog(stderrLog, `watch error: ${message}`)
3303
+ );
3304
+ memoryMonitor = createPmMemoryMonitor(
3305
+ record,
3306
+ child.pid,
3307
+ (kind, message) => requestPlannedRestart(kind, message)
3308
+ );
3309
+ scheduleMonitor = createPmScheduleMonitor(
3310
+ record,
3311
+ (message) => requestPlannedRestart("cron", message),
3312
+ (message) => writePmLog(stdoutLog, message)
3313
+ );
3314
+ if (options?.ready || !needsReadySignal) {
3315
+ startHealthMonitor();
3316
+ }
3317
+ return {
3318
+ async stop() {
3319
+ await watchController.close();
3320
+ readinessMonitor.stop();
3321
+ healthMonitor.stop();
3322
+ memoryMonitor.stop();
3323
+ scheduleMonitor.stop();
3324
+ }
3325
+ };
3326
+ }
3327
+ async function waitForPmChildReady(record, child, ipcController) {
3328
+ if (!ipcController && (!record.waitReady || !record.healthCheck)) {
3329
+ return { ready: true };
3330
+ }
3331
+ let readyMessage;
3332
+ let failureMessage;
3333
+ let exitResult;
3334
+ const readinessMonitor = createPmReadinessMonitor(
3335
+ record,
3336
+ (message) => {
3337
+ readyMessage = message;
3338
+ },
3339
+ (message) => {
3340
+ failureMessage = message;
3341
+ },
3342
+ { ipcController }
3343
+ );
3344
+ void waitForManagedChildExit(child).then((result) => {
3345
+ exitResult = result;
3346
+ });
3347
+ while (!readyMessage && !failureMessage && !exitResult) {
3348
+ await delay(25);
3349
+ }
3350
+ readinessMonitor.stop();
3351
+ if (readyMessage) {
3352
+ return { ready: true, message: readyMessage };
3353
+ }
3354
+ return {
3355
+ ready: false,
3356
+ message: failureMessage,
3357
+ exitResult
3358
+ };
3359
+ }
3360
+ async function stopProxyManagedChild(child, record, stderrLog) {
3361
+ if (!child.pid || !isProcessAlive(child.pid)) {
3362
+ return;
3363
+ }
3364
+ terminateProcessTree(child.pid);
3365
+ const stopTimeout = resolvePmStopTimeout(record);
3366
+ const stopped = await waitForProcessTermination(child.pid, stopTimeout);
3367
+ if (!stopped && child.pid && isProcessAlive(child.pid)) {
3368
+ writePmLog(stderrLog, `proxy handoff shutdown timed out after ${stopTimeout}ms; forcing process termination`);
3369
+ terminateProcessTree(child.pid, { force: true });
3370
+ await waitForProcessTermination(child.pid, DEFAULT_PM_STOP_POLL_MS);
3371
+ }
3372
+ }
3373
+ async function createPmWatchController(record, onChange, onError) {
3374
+ if (!record.watch || record.watchPaths.length === 0) {
2286
3375
  return {
2287
3376
  async close() {
2288
3377
  }
@@ -2344,11 +3433,17 @@ function createPmHealthMonitor(record, onFailure, onLog) {
2344
3433
  method: "GET",
2345
3434
  signal: controller.signal
2346
3435
  });
3436
+ if (stopped) {
3437
+ return;
3438
+ }
2347
3439
  if (!response.ok) {
2348
3440
  throw new Error(`health check returned ${response.status}`);
2349
3441
  }
2350
3442
  failureCount = 0;
2351
3443
  } catch (error) {
3444
+ if (stopped) {
3445
+ return;
3446
+ }
2352
3447
  failureCount += 1;
2353
3448
  const message = error instanceof Error ? error.message : String(error);
2354
3449
  onLog(`health check failed (${failureCount}/${healthCheck.maxFailures}): ${message}`);
@@ -2383,6 +3478,90 @@ function createPmHealthMonitor(record, onFailure, onLog) {
2383
3478
  }
2384
3479
  };
2385
3480
  }
3481
+ function createPmMemoryMonitor(record, pid, onFailure) {
3482
+ if (!record.maxMemoryBytes || !pid) {
3483
+ return {
3484
+ stop() {
3485
+ }
3486
+ };
3487
+ }
3488
+ const memoryLimit = record.maxMemoryBytes;
3489
+ let stopped = false;
3490
+ const timer = setInterval(() => {
3491
+ if (stopped) {
3492
+ return;
3493
+ }
3494
+ const memoryRssBytes = samplePmProcessMetrics(pid).memoryRssBytes;
3495
+ if (memoryRssBytes === void 0 || memoryRssBytes <= memoryLimit) {
3496
+ return;
3497
+ }
3498
+ stopped = true;
3499
+ onFailure(record.memoryAction === "stop" ? "memory-stop" : "memory", `memory usage ${memoryRssBytes} exceeded limit ${memoryLimit}`);
3500
+ }, DEFAULT_PM_MEMORY_CHECK_INTERVAL);
3501
+ timer.unref?.();
3502
+ return {
3503
+ stop() {
3504
+ stopped = true;
3505
+ clearInterval(timer);
3506
+ }
3507
+ };
3508
+ }
3509
+ function createPmScheduleMonitor(record, onTrigger, onLog) {
3510
+ if (!record.cronRestart) {
3511
+ return {
3512
+ stop() {
3513
+ }
3514
+ };
3515
+ }
3516
+ const schedule = parsePmRestartSchedule(record.cronRestart, "pm cronRestart");
3517
+ let stopped = false;
3518
+ let timer = null;
3519
+ const armTimer = (from = /* @__PURE__ */ new Date()) => {
3520
+ const nextOccurrence = resolveNextPmScheduleOccurrence(schedule, from);
3521
+ if (!nextOccurrence) {
3522
+ onLog(`schedule has no next occurrence: ${record.cronRestart}`);
3523
+ return;
3524
+ }
3525
+ const delayMs = Math.max(0, nextOccurrence.getTime() - Date.now());
3526
+ timer = setTimeout(() => {
3527
+ timer = null;
3528
+ if (stopped) {
3529
+ return;
3530
+ }
3531
+ onTrigger(`restart schedule matched: ${record.cronRestart}`);
3532
+ }, delayMs);
3533
+ timer.unref?.();
3534
+ };
3535
+ armTimer();
3536
+ return {
3537
+ stop() {
3538
+ stopped = true;
3539
+ if (timer) {
3540
+ clearTimeout(timer);
3541
+ timer = null;
3542
+ }
3543
+ }
3544
+ };
3545
+ }
3546
+ function resolvePmRestartDelay(record, restartCount, shouldApplyBackoff) {
3547
+ if (!shouldApplyBackoff || !record.expBackoffRestartDelay) {
3548
+ return record.restartDelay;
3549
+ }
3550
+ const exponent = Math.max(0, restartCount - 1);
3551
+ return Math.min(record.expBackoffRestartDelay * 2 ** exponent, record.expBackoffRestartMaxDelay ?? DEFAULT_PM_EXP_BACKOFF_MAX_DELAY);
3552
+ }
3553
+ function resolvePmRestartCountBase(record, wasStable, restartKind) {
3554
+ if (wasStable) {
3555
+ return 0;
3556
+ }
3557
+ if (record.restartWindow && record.lastRestartAt) {
3558
+ const lastRestartTime = Date.parse(record.lastRestartAt);
3559
+ if (!Number.isNaN(lastRestartTime) && Date.now() - lastRestartTime > record.restartWindow) {
3560
+ return 0;
3561
+ }
3562
+ }
3563
+ return restartKind === "watch" ? record.restartCount ?? 0 : record.restartCount ?? 0;
3564
+ }
2386
3565
  function readPlannedRestartRequest(state) {
2387
3566
  return state.request;
2388
3567
  }
@@ -2396,6 +3575,16 @@ async function runManagedProcessLoop(filePath, initialRecord) {
2396
3575
  (0, import_node_fs4.mkdirSync)((0, import_node_path6.dirname)(initialRecord.logFiles.err), { recursive: true });
2397
3576
  const stdoutLog = (0, import_node_fs4.createWriteStream)(initialRecord.logFiles.out, { flags: "a" });
2398
3577
  const stderrLog = (0, import_node_fs4.createWriteStream)(initialRecord.logFiles.err, { flags: "a" });
3578
+ let proxyController = null;
3579
+ let proxyTargetSyncTimer = null;
3580
+ let inheritedListener = null;
3581
+ const runnerPaths = resolveRunnerPathsFromRecordFile(filePath);
3582
+ const syncOwnedProxyTargets = (baseName) => {
3583
+ if (!proxyController) {
3584
+ return;
3585
+ }
3586
+ proxyController.setTargets(resolvePmProxyTargetUrls(runnerPaths, baseName));
3587
+ };
2399
3588
  const persist = (mutator) => {
2400
3589
  const current = readLatestPmRecord(filePath, record);
2401
3590
  record = mutator(current);
@@ -2408,26 +3597,30 @@ async function runManagedProcessLoop(filePath, initialRecord) {
2408
3597
  activeChildStopTimer = null;
2409
3598
  }
2410
3599
  };
3600
+ const scheduleForcedActiveChildStop = (timeoutMs, reason) => {
3601
+ if (!activeChild?.pid || process.platform === "win32") {
3602
+ return;
3603
+ }
3604
+ clearActiveChildStopTimer();
3605
+ activeChildStopTimer = setTimeout(() => {
3606
+ if (activeChild?.pid && isProcessAlive(activeChild.pid)) {
3607
+ writePmLog(stderrLog, `${reason} after ${timeoutMs}ms; forcing process termination`);
3608
+ terminateProcessTree(activeChild.pid, { force: true });
3609
+ }
3610
+ }, timeoutMs);
3611
+ activeChildStopTimer.unref?.();
3612
+ };
2411
3613
  const stopActiveChild = () => {
2412
3614
  if (!activeChild?.pid || !isProcessAlive(activeChild.pid)) {
2413
3615
  return;
2414
3616
  }
2415
3617
  const current = readLatestPmRecord(filePath, record);
3618
+ const stopTimeout = resolvePmStopTimeout(current);
2416
3619
  if (isPmOnlineWapkRecord(current) && activeChild.stdin && !activeChild.stdin.destroyed && activeChild.stdin.writable) {
2417
3620
  try {
2418
3621
  activeChild.stdin.end(`${PM_WAPK_ONLINE_SHUTDOWN_COMMAND}
2419
3622
  `);
2420
- clearActiveChildStopTimer();
2421
- activeChildStopTimer = setTimeout(() => {
2422
- if (activeChild?.pid && isProcessAlive(activeChild.pid)) {
2423
- writePmLog(
2424
- stderrLog,
2425
- `graceful WAPK online shutdown timed out after ${PM_WAPK_ONLINE_SHUTDOWN_TIMEOUT_MS}ms; forcing process termination`
2426
- );
2427
- terminateProcessTree(activeChild.pid);
2428
- }
2429
- }, PM_WAPK_ONLINE_SHUTDOWN_TIMEOUT_MS);
2430
- activeChildStopTimer.unref?.();
3623
+ scheduleForcedActiveChildStop(stopTimeout, "graceful WAPK online shutdown timed out");
2431
3624
  return;
2432
3625
  } catch (error) {
2433
3626
  const message = error instanceof Error ? error.message : String(error);
@@ -2435,6 +3628,7 @@ async function runManagedProcessLoop(filePath, initialRecord) {
2435
3628
  }
2436
3629
  }
2437
3630
  terminateProcessTree(activeChild.pid);
3631
+ scheduleForcedActiveChildStop(stopTimeout, "graceful shutdown timed out");
2438
3632
  };
2439
3633
  const requestManagedStop = (reason) => {
2440
3634
  if (stopRequested) {
@@ -2472,6 +3666,17 @@ async function runManagedProcessLoop(filePath, initialRecord) {
2472
3666
  let command;
2473
3667
  try {
2474
3668
  command = buildPmCommand(latest);
3669
+ if (latest.proxy && isPmProxyOwner(latest) && !proxyController) {
3670
+ proxyController = await createPmProxyController(latest.proxy);
3671
+ syncOwnedProxyTargets(latest.baseName);
3672
+ if (!proxyTargetSyncTimer) {
3673
+ proxyTargetSyncTimer = setInterval(() => syncOwnedProxyTargets(latest.baseName), 50);
3674
+ proxyTargetSyncTimer.unref?.();
3675
+ }
3676
+ }
3677
+ if (latest.proxy && usesPmInheritedListener(latest) && !inheritedListener) {
3678
+ inheritedListener = await createPmInheritedListener(latest.proxy);
3679
+ }
2475
3680
  } catch (error) {
2476
3681
  const message = error instanceof Error ? error.message : String(error);
2477
3682
  writePmLog(stderrLog, message);
@@ -2481,25 +3686,24 @@ async function runManagedProcessLoop(filePath, initialRecord) {
2481
3686
  error: message,
2482
3687
  runnerPid: void 0,
2483
3688
  childPid: void 0,
3689
+ proxyTargetPort: void 0,
3690
+ proxyReadyAt: void 0,
2484
3691
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
2485
3692
  }));
2486
3693
  return;
2487
3694
  }
2488
3695
  const onlineStdinShutdownEnabled = isPmOnlineWapkRecord(latest);
2489
- const child = (0, import_node_child_process2.spawn)(command.command, command.args, {
3696
+ const initialTargetPort = usesPmProxyController(latest) ? await allocatePmProxyTargetPort(resolvePmProxyTargetHost(latest.proxy)) : void 0;
3697
+ const monitorRecord = buildPmMonitorRecord(latest, initialTargetPort);
3698
+ let child = (0, import_node_child_process2.spawn)(command.command, command.args, {
2490
3699
  cwd: latest.cwd,
2491
- env: {
2492
- ...process.env,
2493
- ...latest.env,
2494
- ...command.env,
2495
- ELIT_PM_NAME: latest.name,
2496
- ELIT_PM_ID: latest.id
2497
- },
2498
- stdio: [onlineStdinShutdownEnabled ? "pipe" : "ignore", "pipe", "pipe"],
3700
+ env: buildPmChildEnv(latest, command, initialTargetPort),
3701
+ stdio: buildPmChildStdio(command, onlineStdinShutdownEnabled),
2499
3702
  windowsHide: true,
2500
3703
  shell: command.shell
2501
3704
  });
2502
- const childStartedAt = Date.now();
3705
+ let childStartedAt = Date.now();
3706
+ let childIpcState = command.ipc ? createPmChildIpcState(child, inheritedListener) : void 0;
2503
3707
  activeChild = child;
2504
3708
  if (child.stdout) {
2505
3709
  child.stdout.pipe(stdoutLog, { end: false });
@@ -2507,26 +3711,38 @@ async function runManagedProcessLoop(filePath, initialRecord) {
2507
3711
  if (child.stderr) {
2508
3712
  child.stderr.pipe(stderrLog, { end: false });
2509
3713
  }
3714
+ let childWaitState = { settled: false, result: void 0 };
3715
+ void waitForManagedChildExit(child).then((result) => {
3716
+ childWaitState.result = result;
3717
+ childWaitState.settled = true;
3718
+ });
2510
3719
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
3720
+ const waitingForReady = latest.waitReady || Boolean(childIpcState);
2511
3721
  persist((current2) => ({
2512
3722
  ...current2,
2513
- status: "online",
3723
+ status: waitingForReady ? "starting" : "online",
2514
3724
  commandPreview: command.preview,
2515
3725
  runtime: command.runtime ?? current2.runtime,
2516
3726
  runnerPid: process.pid,
2517
3727
  childPid: child.pid,
3728
+ proxyTargetPort: initialTargetPort,
3729
+ proxyReadyAt: !waitingForReady && usesPmProxyController(latest) ? startedAt : void 0,
2518
3730
  startedAt,
2519
3731
  stoppedAt: void 0,
3732
+ reloadRequestedAt: void 0,
2520
3733
  error: void 0,
2521
3734
  updatedAt: startedAt
2522
3735
  }));
2523
3736
  writePmLog(stdoutLog, `started ${command.preview}${child.pid ? ` (pid ${child.pid})` : ""}`);
3737
+ if (isPmProxyOwner(latest) && !waitingForReady) {
3738
+ syncOwnedProxyTargets(latest.baseName);
3739
+ }
2524
3740
  const requestPlannedRestart = (kind, detail) => {
2525
3741
  if (stopRequested || restartState.request) {
2526
3742
  return;
2527
3743
  }
2528
3744
  restartState.request = { kind, detail };
2529
- writePmLog(kind === "health" ? stderrLog : stdoutLog, `${kind} restart requested: ${detail}`);
3745
+ writePmLog(kind === "watch" || kind === "cron" ? stdoutLog : stderrLog, `${kind} restart requested: ${detail}`);
2530
3746
  persist((current2) => ({
2531
3747
  ...current2,
2532
3748
  status: "restarting",
@@ -2534,27 +3750,124 @@ async function runManagedProcessLoop(filePath, initialRecord) {
2534
3750
  }));
2535
3751
  stopActiveChild();
2536
3752
  };
2537
- const watchController = await createPmWatchController(
2538
- latest,
2539
- (changedPath) => requestPlannedRestart("watch", changedPath),
2540
- (message) => writePmLog(stderrLog, `watch error: ${message}`)
2541
- );
2542
- const healthMonitor = createPmHealthMonitor(
2543
- latest,
2544
- (message) => requestPlannedRestart("health", message),
2545
- (message) => writePmLog(stdoutLog, message)
3753
+ let controllers = await createPmChildControllers(
3754
+ monitorRecord,
3755
+ child,
3756
+ stdoutLog,
3757
+ stderrLog,
3758
+ requestPlannedRestart,
3759
+ (message) => {
3760
+ const readyAt = (/* @__PURE__ */ new Date()).toISOString();
3761
+ if (isPmProxyOwner(latest)) {
3762
+ syncOwnedProxyTargets(latest.baseName);
3763
+ }
3764
+ persist((current2) => ({
3765
+ ...current2,
3766
+ status: "online",
3767
+ proxyTargetPort: initialTargetPort,
3768
+ proxyReadyAt: readyAt,
3769
+ updatedAt: readyAt
3770
+ }));
3771
+ writePmLog(stdoutLog, message);
3772
+ },
3773
+ { ready: !waitingForReady, ipcController: childIpcState }
2546
3774
  );
2547
- const desiredStatePoller = setInterval(() => {
3775
+ let handledReloadAt = latest.reloadRequestedAt;
3776
+ while (!childWaitState.settled) {
2548
3777
  const latestRecord = readLatestPmRecord(filePath, record);
2549
- if (!stopRequested && latestRecord.desiredState === "stopped") {
3778
+ if (latestRecord.desiredState === "stopped" && !stopRequested) {
2550
3779
  requestManagedStop("stop requested by PM control state");
2551
3780
  }
2552
- }, DEFAULT_PM_STOP_POLL_MS);
2553
- desiredStatePoller.unref?.();
2554
- const exitResult = await waitForManagedChildExit(child);
2555
- clearInterval(desiredStatePoller);
2556
- await watchController.close();
2557
- healthMonitor.stop();
3781
+ const reloadRequestedAt = latestRecord.reloadRequestedAt;
3782
+ if (!stopRequested && supportsPmProxyReload(latestRecord) && reloadRequestedAt && reloadRequestedAt !== handledReloadAt && latestRecord.proxy) {
3783
+ handledReloadAt = reloadRequestedAt;
3784
+ const replacementTargetPort = usesPmProxyController(latestRecord) ? await allocatePmProxyTargetPort(resolvePmProxyTargetHost(latestRecord.proxy)) : void 0;
3785
+ const replacementMonitorRecord = buildPmMonitorRecord(latestRecord, replacementTargetPort);
3786
+ const replacementChild = (0, import_node_child_process2.spawn)(command.command, command.args, {
3787
+ cwd: latestRecord.cwd,
3788
+ env: buildPmChildEnv(latestRecord, command, replacementTargetPort),
3789
+ stdio: buildPmChildStdio(command, onlineStdinShutdownEnabled),
3790
+ windowsHide: true,
3791
+ shell: command.shell
3792
+ });
3793
+ const replacementIpcState = command.ipc ? createPmChildIpcState(replacementChild, inheritedListener) : void 0;
3794
+ if (replacementChild.stdout) {
3795
+ replacementChild.stdout.pipe(stdoutLog, { end: false });
3796
+ }
3797
+ if (replacementChild.stderr) {
3798
+ replacementChild.stderr.pipe(stderrLog, { end: false });
3799
+ }
3800
+ writePmLog(stdoutLog, `starting ${usesPmInheritedListener(latestRecord) ? "shared-listener" : "proxy handoff"} replacement${replacementChild.pid ? ` (pid ${replacementChild.pid})` : ""}`);
3801
+ const readyResult = await waitForPmChildReady(replacementMonitorRecord, replacementChild, replacementIpcState);
3802
+ if (!readyResult.ready) {
3803
+ writePmLog(stderrLog, readyResult.message ?? "replacement exited before becoming ready");
3804
+ replacementIpcState?.stop();
3805
+ await stopProxyManagedChild(replacementChild, latestRecord, stderrLog);
3806
+ persist((current2) => ({
3807
+ ...current2,
3808
+ status: "online",
3809
+ proxyReadyAt: current2.proxyReadyAt,
3810
+ reloadRequestedAt: void 0,
3811
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
3812
+ }));
3813
+ continue;
3814
+ }
3815
+ const previousChild = child;
3816
+ const previousControllers = controllers;
3817
+ const previousIpcState = childIpcState;
3818
+ const handoffAt = (/* @__PURE__ */ new Date()).toISOString();
3819
+ if (usesPmProxyController(latestRecord) && replacementTargetPort) {
3820
+ proxyController?.setTarget(buildPmProxyTargetUrl(latestRecord.proxy, replacementTargetPort));
3821
+ }
3822
+ persist((current2) => ({
3823
+ ...current2,
3824
+ status: "online",
3825
+ childPid: replacementChild.pid,
3826
+ proxyTargetPort: replacementTargetPort,
3827
+ proxyReadyAt: handoffAt,
3828
+ startedAt: handoffAt,
3829
+ reloadRequestedAt: void 0,
3830
+ error: void 0,
3831
+ updatedAt: handoffAt
3832
+ }));
3833
+ if (readyResult.message) {
3834
+ writePmLog(stdoutLog, readyResult.message);
3835
+ }
3836
+ writePmLog(stdoutLog, `${usesPmInheritedListener(latestRecord) ? "shared listener" : "proxy handoff"} activated on ${latestRecord.proxy.host ?? "0.0.0.0"}:${latestRecord.proxy.port}`);
3837
+ child = replacementChild;
3838
+ childIpcState = replacementIpcState;
3839
+ childStartedAt = Date.now();
3840
+ activeChild = replacementChild;
3841
+ childWaitState = { settled: false, result: void 0 };
3842
+ void waitForManagedChildExit(replacementChild).then((result) => {
3843
+ childWaitState.result = result;
3844
+ childWaitState.settled = true;
3845
+ });
3846
+ controllers = await createPmChildControllers(
3847
+ replacementMonitorRecord,
3848
+ replacementChild,
3849
+ stdoutLog,
3850
+ stderrLog,
3851
+ requestPlannedRestart,
3852
+ () => {
3853
+ },
3854
+ { ready: true, ipcController: replacementIpcState }
3855
+ );
3856
+ await delay(250);
3857
+ await previousControllers.stop();
3858
+ previousIpcState?.stop();
3859
+ await stopProxyManagedChild(previousChild, latestRecord, stderrLog);
3860
+ clearActiveChildStopTimer();
3861
+ if (isPmProxyOwner(latestRecord)) {
3862
+ syncOwnedProxyTargets(latestRecord.baseName);
3863
+ }
3864
+ continue;
3865
+ }
3866
+ await delay(25);
3867
+ }
3868
+ const exitResult = childWaitState.result;
3869
+ await controllers.stop();
3870
+ childIpcState?.stop();
2558
3871
  clearActiveChildStopTimer();
2559
3872
  activeChild = null;
2560
3873
  const exitCode = waitForExit(exitResult.code, exitResult.signal);
@@ -2570,56 +3883,77 @@ async function runManagedProcessLoop(filePath, initialRecord) {
2570
3883
  if (stopRequested || current.desiredState === "stopped") {
2571
3884
  break;
2572
3885
  }
2573
- const shouldRestartForExit = plannedRestart ? true : current.restartPolicy === "always" ? true : current.restartPolicy === "on-failure" ? exitCode !== 0 || Boolean(exitResult.error) : false;
3886
+ const shouldRestartForExit = plannedRestart?.kind === "memory-stop" ? false : plannedRestart ? true : current.restartPolicy === "always" ? true : current.restartPolicy === "on-failure" ? exitCode !== 0 || Boolean(exitResult.error) : false;
2574
3887
  if (!shouldRestartForExit) {
2575
3888
  persist((latestRecord) => ({
2576
3889
  ...latestRecord,
2577
- status: exitCode === 0 && !exitResult.error ? "exited" : "errored",
3890
+ status: plannedRestart?.kind === "memory-stop" ? "errored" : exitCode === 0 && !exitResult.error ? "exited" : "errored",
2578
3891
  childPid: void 0,
3892
+ proxyTargetPort: void 0,
3893
+ proxyReadyAt: void 0,
2579
3894
  runnerPid: void 0,
2580
3895
  lastExitCode: exitCode,
2581
- error: exitCode === 0 && !exitResult.error ? void 0 : exitResult.error ?? `Process exited with code ${exitCode}.`,
3896
+ reloadRequestedAt: void 0,
3897
+ error: plannedRestart?.kind === "memory-stop" ? plannedRestart.detail : exitCode === 0 && !exitResult.error ? void 0 : exitResult.error ?? `Process exited with code ${exitCode}.`,
2582
3898
  stoppedAt: (/* @__PURE__ */ new Date()).toISOString(),
2583
3899
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
2584
3900
  }));
3901
+ if (isPmProxyOwner(current)) {
3902
+ syncOwnedProxyTargets(current.baseName);
3903
+ }
2585
3904
  return;
2586
3905
  }
2587
3906
  const shouldCountRestart = plannedRestart?.kind !== "watch";
2588
- const baseRestartCount = wasStable ? 0 : current.restartCount ?? 0;
3907
+ const baseRestartCount = resolvePmRestartCountBase(current, wasStable, plannedRestart?.kind);
2589
3908
  const nextRestartCount = shouldCountRestart ? baseRestartCount + 1 : current.restartCount ?? 0;
2590
3909
  if (nextRestartCount > current.maxRestarts) {
2591
3910
  persist((latestRecord) => ({
2592
3911
  ...latestRecord,
2593
3912
  status: "errored",
2594
3913
  childPid: void 0,
3914
+ proxyTargetPort: void 0,
3915
+ proxyReadyAt: void 0,
2595
3916
  runnerPid: void 0,
2596
3917
  restartCount: nextRestartCount,
3918
+ lastRestartAt: (/* @__PURE__ */ new Date()).toISOString(),
2597
3919
  lastExitCode: exitCode,
3920
+ reloadRequestedAt: void 0,
2598
3921
  error: plannedRestart ? `Reached max restart attempts (${current.maxRestarts}) after ${plannedRestart.kind} restart requests.` : `Reached max restart attempts (${current.maxRestarts}).`,
2599
3922
  stoppedAt: (/* @__PURE__ */ new Date()).toISOString(),
2600
3923
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
2601
3924
  }));
2602
3925
  writePmLog(stderrLog, `max restart attempts reached (${current.maxRestarts})`);
3926
+ if (isPmProxyOwner(current)) {
3927
+ syncOwnedProxyTargets(current.baseName);
3928
+ }
2603
3929
  return;
2604
3930
  }
2605
3931
  persist((latestRecord) => ({
2606
3932
  ...latestRecord,
2607
3933
  status: "restarting",
2608
3934
  childPid: void 0,
3935
+ proxyTargetPort: void 0,
3936
+ proxyReadyAt: void 0,
2609
3937
  lastExitCode: exitCode,
2610
3938
  restartCount: nextRestartCount,
3939
+ lastRestartAt: (/* @__PURE__ */ new Date()).toISOString(),
3940
+ reloadRequestedAt: void 0,
2611
3941
  error: void 0,
2612
3942
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
2613
3943
  }));
3944
+ if (isPmProxyOwner(current)) {
3945
+ syncOwnedProxyTargets(current.baseName);
3946
+ }
3947
+ const resolvedRestartDelay = resolvePmRestartDelay(current, nextRestartCount, shouldCountRestart && !wasStable && plannedRestart?.kind !== "watch");
2614
3948
  if (plannedRestart) {
2615
3949
  writePmLog(
2616
- plannedRestart.kind === "health" ? stderrLog : stdoutLog,
2617
- `restarting in ${current.restartDelay}ms after ${plannedRestart.kind}: ${plannedRestart.detail}`
3950
+ plannedRestart.kind === "health" || plannedRestart.kind === "memory" || plannedRestart.kind === "memory-stop" || plannedRestart.kind === "startup" ? stderrLog : stdoutLog,
3951
+ `restarting in ${resolvedRestartDelay}ms after ${plannedRestart.kind}: ${plannedRestart.detail}`
2618
3952
  );
2619
3953
  } else {
2620
- writePmLog(stdoutLog, `restarting in ${current.restartDelay}ms`);
3954
+ writePmLog(stdoutLog, `restarting in ${resolvedRestartDelay}ms`);
2621
3955
  }
2622
- await delay(current.restartDelay);
3956
+ await delay(resolvedRestartDelay);
2623
3957
  }
2624
3958
  } finally {
2625
3959
  stopRequested = true;
@@ -2632,11 +3966,26 @@ async function runManagedProcessLoop(filePath, initialRecord) {
2632
3966
  status: finalRecord.status === "errored" ? "errored" : finalRecord.status === "exited" ? "exited" : "stopped",
2633
3967
  runnerPid: void 0,
2634
3968
  childPid: void 0,
3969
+ proxyTargetPort: void 0,
3970
+ proxyReadyAt: void 0,
3971
+ reloadRequestedAt: void 0,
2635
3972
  stoppedAt: (/* @__PURE__ */ new Date()).toISOString(),
2636
3973
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
2637
3974
  });
2638
3975
  process.off("SIGINT", handleStopSignal);
2639
3976
  process.off("SIGTERM", handleStopSignal);
3977
+ if (proxyTargetSyncTimer) {
3978
+ clearInterval(proxyTargetSyncTimer);
3979
+ proxyTargetSyncTimer = null;
3980
+ }
3981
+ if (proxyController) {
3982
+ await proxyController.close().catch(() => void 0);
3983
+ proxyController = null;
3984
+ }
3985
+ if (inheritedListener) {
3986
+ await new Promise((resolvePromise) => inheritedListener?.close(() => resolvePromise()));
3987
+ inheritedListener = null;
3988
+ }
2640
3989
  await new Promise((resolvePromise) => stdoutLog.end(resolvePromise));
2641
3990
  await new Promise((resolvePromise) => stderrLog.end(resolvePromise));
2642
3991
  }
@@ -2690,23 +4039,26 @@ async function stopPmMatches(matches) {
2690
4039
  stoppedAt: (/* @__PURE__ */ new Date()).toISOString()
2691
4040
  };
2692
4041
  writePmRecord(match.filePath, updated);
2693
- const runnerStopped = await waitForProcessTermination(match.record.runnerPid, DEFAULT_PM_STOP_GRACE_PERIOD_MS);
4042
+ const stopTimeout = resolvePmStopTimeout(match.record);
4043
+ const runnerStopped = await waitForProcessTermination(match.record.runnerPid, stopTimeout);
2694
4044
  const childStopped = await waitForProcessTermination(
2695
4045
  match.record.childPid,
2696
- runnerStopped ? DEFAULT_PM_STOP_POLL_MS : DEFAULT_PM_STOP_GRACE_PERIOD_MS
4046
+ runnerStopped ? DEFAULT_PM_STOP_POLL_MS : stopTimeout
2697
4047
  );
2698
4048
  if (!runnerStopped && match.record.runnerPid && isProcessAlive(match.record.runnerPid)) {
2699
- terminateProcessTree(match.record.runnerPid);
4049
+ terminateProcessTree(match.record.runnerPid, { force: true });
2700
4050
  await waitForProcessTermination(match.record.runnerPid, DEFAULT_PM_STOP_POLL_MS);
2701
4051
  }
2702
4052
  if (!childStopped && match.record.childPid && isProcessAlive(match.record.childPid)) {
2703
- terminateProcessTree(match.record.childPid);
4053
+ terminateProcessTree(match.record.childPid, { force: true });
2704
4054
  await waitForProcessTermination(match.record.childPid, DEFAULT_PM_STOP_POLL_MS);
2705
4055
  }
2706
4056
  writePmRecord(match.filePath, {
2707
4057
  ...updated,
2708
4058
  runnerPid: void 0,
2709
4059
  childPid: void 0,
4060
+ proxyTargetPort: void 0,
4061
+ reloadRequestedAt: void 0,
2710
4062
  status: "stopped",
2711
4063
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
2712
4064
  });
@@ -2775,13 +4127,13 @@ function readPackageJson(filePath) {
2775
4127
 
2776
4128
  // src/shares/workspace-package/roots.ts
2777
4129
  function findPackageDirectory(startDir, resolveMatch) {
2778
- let currentDir = resolve4(startDir);
4130
+ let currentDir = resolve5(startDir);
2779
4131
  while (true) {
2780
4132
  const match = resolveMatch(currentDir);
2781
4133
  if (match) {
2782
4134
  return match;
2783
4135
  }
2784
- const parentDir = dirname2(currentDir);
4136
+ const parentDir = dirname3(currentDir);
2785
4137
  if (parentDir === currentDir) {
2786
4138
  return void 0;
2787
4139
  }
@@ -2809,17 +4161,17 @@ init_fs();
2809
4161
  function getWorkspacePackageImportCandidates(packageRoot, specifier, options = {}) {
2810
4162
  const subpath = specifier === "elit" ? "index" : specifier.slice("elit/".length);
2811
4163
  const builtCandidates = options.preferredBuiltFormat === "cjs" ? [
2812
- resolve4(packageRoot, "dist", `${subpath}.cjs`),
2813
- resolve4(packageRoot, "dist", `${subpath}.js`),
2814
- resolve4(packageRoot, "dist", `${subpath}.mjs`)
4164
+ resolve5(packageRoot, "dist", `${subpath}.cjs`),
4165
+ resolve5(packageRoot, "dist", `${subpath}.js`),
4166
+ resolve5(packageRoot, "dist", `${subpath}.mjs`)
2815
4167
  ] : [
2816
- resolve4(packageRoot, "dist", `${subpath}.mjs`),
2817
- resolve4(packageRoot, "dist", `${subpath}.js`),
2818
- resolve4(packageRoot, "dist", `${subpath}.cjs`)
4168
+ resolve5(packageRoot, "dist", `${subpath}.mjs`),
4169
+ resolve5(packageRoot, "dist", `${subpath}.js`),
4170
+ resolve5(packageRoot, "dist", `${subpath}.cjs`)
2819
4171
  ];
2820
4172
  const sourceCandidates = [
2821
- resolve4(packageRoot, "src", `${subpath}.ts`),
2822
- resolve4(packageRoot, "src", `${subpath}.tsx`)
4173
+ resolve5(packageRoot, "src", `${subpath}.ts`),
4174
+ resolve5(packageRoot, "src", `${subpath}.tsx`)
2823
4175
  ];
2824
4176
  return options.preferBuilt ? [...builtCandidates, ...sourceCandidates] : [...sourceCandidates, ...builtCandidates];
2825
4177
  }
@@ -2854,7 +4206,7 @@ function resolveWorkspacePackageImport(specifier, startDir, options = {}) {
2854
4206
  // src/shares/config/loader.ts
2855
4207
  function resolveConfigPath(cwd = process.cwd()) {
2856
4208
  for (const configFile of ELIT_CONFIG_FILES) {
2857
- const configPath = resolve4(cwd, configFile);
4209
+ const configPath = resolve5(cwd, configFile);
2858
4210
  if (existsSync4(configPath)) {
2859
4211
  return configPath;
2860
4212
  }
@@ -2883,7 +4235,7 @@ async function loadConfigFile(configPath) {
2883
4235
  if (ext === "ts" || ext === "mts") {
2884
4236
  try {
2885
4237
  const { build } = await import("esbuild");
2886
- const configDir = dirname2(configPath);
4238
+ const configDir = dirname3(configPath);
2887
4239
  const tempFile = join5(configDir, `.elit-config-${Date.now()}.mjs`);
2888
4240
  const externalAllPlugin = {
2889
4241
  name: "external-all",
@@ -2957,6 +4309,43 @@ async function loadConfigFile(configPath) {
2957
4309
  }
2958
4310
 
2959
4311
  // src/cli/pm/commands.ts
4312
+ var PM_SIGNAL_NAMES = /* @__PURE__ */ new Set([
4313
+ "SIGABRT",
4314
+ "SIGALRM",
4315
+ "SIGBREAK",
4316
+ "SIGBUS",
4317
+ "SIGCHLD",
4318
+ "SIGCONT",
4319
+ "SIGFPE",
4320
+ "SIGHUP",
4321
+ "SIGILL",
4322
+ "SIGINT",
4323
+ "SIGIO",
4324
+ "SIGIOT",
4325
+ "SIGKILL",
4326
+ "SIGPIPE",
4327
+ "SIGPOLL",
4328
+ "SIGPROF",
4329
+ "SIGPWR",
4330
+ "SIGQUIT",
4331
+ "SIGSEGV",
4332
+ "SIGSTKFLT",
4333
+ "SIGSTOP",
4334
+ "SIGSYS",
4335
+ "SIGTERM",
4336
+ "SIGTRAP",
4337
+ "SIGTSTP",
4338
+ "SIGTTIN",
4339
+ "SIGTTOU",
4340
+ "SIGUNUSED",
4341
+ "SIGURG",
4342
+ "SIGUSR1",
4343
+ "SIGUSR2",
4344
+ "SIGVTALRM",
4345
+ "SIGWINCH",
4346
+ "SIGXCPU",
4347
+ "SIGXFSZ"
4348
+ ]);
2960
4349
  async function runPmStart(args) {
2961
4350
  const parsed = parsePmStartArgs(args);
2962
4351
  const workspaceRoot = process.cwd();
@@ -2989,9 +4378,130 @@ function resolveNamedMatches(paths, value) {
2989
4378
  if (value === "all") {
2990
4379
  return listPmRecordMatches(paths).map(syncPmRecordLiveness);
2991
4380
  }
4381
+ const groupMatches = findPmGroupMatches(paths, value);
4382
+ if (groupMatches.length > 0) {
4383
+ return groupMatches.map(syncPmRecordLiveness);
4384
+ }
2992
4385
  const match = findPmRecordMatch(paths, value);
2993
4386
  return match ? [syncPmRecordLiveness(match)] : [];
2994
4387
  }
4388
+ function sortPmMatchesByInstance(matches) {
4389
+ return [...matches].sort((left, right) => left.record.instanceIndex - right.record.instanceIndex);
4390
+ }
4391
+ function resolveInspectableMatch(paths, value) {
4392
+ const exactMatch = findPmRecordMatch(paths, value);
4393
+ const groupMatches = findPmGroupMatches(paths, value).map(syncPmRecordLiveness);
4394
+ if (groupMatches.length > 1 && exactMatch?.record.baseName === value && exactMatch.record.name === value) {
4395
+ 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}.`);
4396
+ }
4397
+ if (exactMatch) {
4398
+ return syncPmRecordLiveness(exactMatch);
4399
+ }
4400
+ return groupMatches[0];
4401
+ }
4402
+ function rebuildPmRecordDefinition(record, targetInstances = record.instances) {
4403
+ const definition = resolvePmAppDefinition(
4404
+ toPmAppConfig(record),
4405
+ { name: record.baseName, env: {}, watchPaths: [], watchIgnore: [], instances: targetInstances },
4406
+ process.cwd(),
4407
+ record.source
4408
+ );
4409
+ return {
4410
+ ...definition,
4411
+ name: record.name,
4412
+ baseName: record.baseName,
4413
+ instanceIndex: record.instanceIndex,
4414
+ instances: targetInstances
4415
+ };
4416
+ }
4417
+ function rebuildPmSavedDefinition(app) {
4418
+ const definition = resolvePmAppDefinition(
4419
+ toSavedPmAppConfig(app),
4420
+ { name: app.baseName, env: {}, watchPaths: [], watchIgnore: [], instances: app.instances },
4421
+ process.cwd(),
4422
+ "cli"
4423
+ );
4424
+ return {
4425
+ ...definition,
4426
+ name: app.name,
4427
+ baseName: app.baseName,
4428
+ instanceIndex: app.instanceIndex,
4429
+ instances: app.instances
4430
+ };
4431
+ }
4432
+ function deletePmMatches(matches) {
4433
+ for (const match of matches) {
4434
+ if ((0, import_node_fs5.existsSync)(match.record.logFiles.out)) {
4435
+ (0, import_node_fs5.rmSync)(match.record.logFiles.out, { force: true });
4436
+ }
4437
+ if ((0, import_node_fs5.existsSync)(match.record.logFiles.err)) {
4438
+ (0, import_node_fs5.rmSync)(match.record.logFiles.err, { force: true });
4439
+ }
4440
+ (0, import_node_fs5.rmSync)(match.filePath, { force: true });
4441
+ }
4442
+ }
4443
+ function updatePmInstanceCount(matches, instances) {
4444
+ const now = (/* @__PURE__ */ new Date()).toISOString();
4445
+ for (const match of matches) {
4446
+ writePmRecord(match.filePath, {
4447
+ ...match.record,
4448
+ instances,
4449
+ updatedAt: now
4450
+ });
4451
+ }
4452
+ }
4453
+ function normalizePmSignalName(value) {
4454
+ const trimmed = value.trim().toUpperCase();
4455
+ const signalName = trimmed.startsWith("SIG") ? trimmed : `SIG${trimmed}`;
4456
+ if (!PM_SIGNAL_NAMES.has(signalName)) {
4457
+ throw new Error(`Unsupported pm signal: ${value}`);
4458
+ }
4459
+ return signalName;
4460
+ }
4461
+ function resolveSignalablePid(record) {
4462
+ if (record.childPid && record.childPid > 0) {
4463
+ return record.childPid;
4464
+ }
4465
+ if (record.runnerPid && record.runnerPid > 0) {
4466
+ return record.runnerPid;
4467
+ }
4468
+ return void 0;
4469
+ }
4470
+ function groupPmMatchesByBaseName(matches) {
4471
+ const grouped = /* @__PURE__ */ new Map();
4472
+ for (const match of matches) {
4473
+ const group = grouped.get(match.record.baseName);
4474
+ if (group) {
4475
+ group.push(match);
4476
+ continue;
4477
+ }
4478
+ grouped.set(match.record.baseName, [match]);
4479
+ }
4480
+ return [...grouped.entries()].sort((left, right) => left[0].localeCompare(right[0])).map(([, group]) => sortPmMatchesByInstance(group));
4481
+ }
4482
+ async function waitForPmRecordOnline(filePath, timeoutMs) {
4483
+ const deadline = Date.now() + timeoutMs;
4484
+ while (Date.now() < deadline) {
4485
+ if (!(0, import_node_fs5.existsSync)(filePath)) {
4486
+ break;
4487
+ }
4488
+ const record = readPmRecord(filePath);
4489
+ if (record.status === "online") {
4490
+ return record;
4491
+ }
4492
+ if (record.status === "errored" || record.status === "exited" || record.status === "stopped") {
4493
+ throw new Error(record.error ?? `Process ${record.name} failed while reloading.`);
4494
+ }
4495
+ await new Promise((resolvePromise) => setTimeout(resolvePromise, 50));
4496
+ }
4497
+ throw new Error(`Timed out waiting for the reloaded process to become online after ${timeoutMs}ms.`);
4498
+ }
4499
+ function resolvePmReloadReadyTimeout(record) {
4500
+ return record.waitReady || record.proxy?.strategy === "inherit" ? Math.max(record.listenTimeout + 1e3, 2e3) : Math.max(record.restartDelay + 1e3, 2e3);
4501
+ }
4502
+ function supportsPmProxyReload2(record) {
4503
+ return Boolean(record.proxy) && record.instances === 1 && Boolean(record.runnerPid);
4504
+ }
2995
4505
  function padCell(value, width) {
2996
4506
  return value.length >= width ? value : `${value}${" ".repeat(width - value.length)}`;
2997
4507
  }
@@ -3002,8 +4512,255 @@ function tailLogFile(filePath, lineCount) {
3002
4512
  const lines = (0, import_node_fs5.readFileSync)(filePath, "utf8").split(/\r?\n/).filter((line) => line.length > 0);
3003
4513
  return lines.slice(-lineCount).join(import_node_os2.EOL);
3004
4514
  }
3005
- function printPmList(paths) {
3006
- const matches = listPmRecordMatches(paths).map(syncPmRecordLiveness);
4515
+ function listPmMatches(paths) {
4516
+ return listPmRecordMatches(paths).map(syncPmRecordLiveness);
4517
+ }
4518
+ function isPmRecordActive(record) {
4519
+ return record.desiredState === "running" && (record.status === "starting" || record.status === "online" || record.status === "restarting");
4520
+ }
4521
+ function resolvePmUptimeMs(record) {
4522
+ if (!isPmRecordActive(record) || !record.startedAt) {
4523
+ return void 0;
4524
+ }
4525
+ const startedTime = Date.parse(record.startedAt);
4526
+ if (Number.isNaN(startedTime)) {
4527
+ return void 0;
4528
+ }
4529
+ return Math.max(0, Date.now() - startedTime);
4530
+ }
4531
+ function resolvePmLiveMetrics(record) {
4532
+ const uptimeMs = resolvePmUptimeMs(record);
4533
+ if (!isPmRecordActive(record) || !record.childPid) {
4534
+ return { uptimeMs };
4535
+ }
4536
+ const sampledMetrics = samplePmProcessMetrics(record.childPid);
4537
+ return {
4538
+ ...sampledMetrics,
4539
+ uptimeMs,
4540
+ updatedAt: sampledMetrics.cpuPercent !== void 0 || sampledMetrics.memoryRssBytes !== void 0 ? (/* @__PURE__ */ new Date()).toISOString() : void 0
4541
+ };
4542
+ }
4543
+ function toPmDisplayRecord(record) {
4544
+ return {
4545
+ record,
4546
+ liveMetrics: resolvePmLiveMetrics(record)
4547
+ };
4548
+ }
4549
+ function serializePmRecord(record) {
4550
+ return {
4551
+ ...record,
4552
+ liveMetrics: resolvePmLiveMetrics(record)
4553
+ };
4554
+ }
4555
+ function parsePmFormatOption(args, index, option) {
4556
+ let value;
4557
+ if (option.startsWith("--format=")) {
4558
+ value = option.slice("--format=".length);
4559
+ } else {
4560
+ value = readRequiredValue(args, index + 1, "--format");
4561
+ index += 1;
4562
+ }
4563
+ if (value !== "table" && value !== "json") {
4564
+ throw new Error(`Unsupported pm output format: ${value}`);
4565
+ }
4566
+ return {
4567
+ format: value,
4568
+ nextIndex: index
4569
+ };
4570
+ }
4571
+ function parsePmListArgs(args) {
4572
+ let format = "table";
4573
+ for (let index = 0; index < args.length; index++) {
4574
+ const arg = args[index];
4575
+ switch (arg) {
4576
+ case "--json":
4577
+ format = "json";
4578
+ break;
4579
+ case "--format":
4580
+ default:
4581
+ if (arg === "--format" || arg.startsWith("--format=")) {
4582
+ const parsed = parsePmFormatOption(args, index, arg);
4583
+ format = parsed.format;
4584
+ index = parsed.nextIndex;
4585
+ break;
4586
+ }
4587
+ throw new Error(`Unknown pm list option: ${arg}`);
4588
+ }
4589
+ }
4590
+ return { format };
4591
+ }
4592
+ function parsePmShowArgs(args) {
4593
+ let format = "text";
4594
+ let name;
4595
+ for (let index = 0; index < args.length; index++) {
4596
+ const arg = args[index];
4597
+ switch (arg) {
4598
+ case "--json":
4599
+ format = "json";
4600
+ break;
4601
+ case "--format":
4602
+ default:
4603
+ if (arg === "--format" || arg.startsWith("--format=")) {
4604
+ const parsed = parsePmFormatOption(args, index, arg);
4605
+ format = parsed.format === "json" ? "json" : "text";
4606
+ index = parsed.nextIndex;
4607
+ break;
4608
+ }
4609
+ if (arg.startsWith("-")) {
4610
+ throw new Error(`Unknown pm show option: ${arg}`);
4611
+ }
4612
+ if (name) {
4613
+ throw new Error("pm show accepts exactly one process name.");
4614
+ }
4615
+ name = arg;
4616
+ break;
4617
+ }
4618
+ }
4619
+ if (!name) {
4620
+ throw new Error("Usage: elit pm show <name> [--json]");
4621
+ }
4622
+ return { name, format };
4623
+ }
4624
+ function formatPmDuration(durationMs) {
4625
+ if (durationMs < 1e3) {
4626
+ return `${durationMs}ms`;
4627
+ }
4628
+ const totalSeconds = Math.floor(durationMs / 1e3);
4629
+ const days = Math.floor(totalSeconds / 86400);
4630
+ const hours = Math.floor(totalSeconds % 86400 / 3600);
4631
+ const minutes = Math.floor(totalSeconds % 3600 / 60);
4632
+ const seconds = totalSeconds % 60;
4633
+ const parts = [];
4634
+ if (days > 0) {
4635
+ parts.push(`${days}d`);
4636
+ }
4637
+ if (hours > 0) {
4638
+ parts.push(`${hours}h`);
4639
+ }
4640
+ if (minutes > 0) {
4641
+ parts.push(`${minutes}m`);
4642
+ }
4643
+ if (seconds > 0 || parts.length === 0) {
4644
+ parts.push(`${seconds}s`);
4645
+ }
4646
+ return parts.slice(0, 2).join(" ");
4647
+ }
4648
+ function formatPmCpuPercent(cpuPercent) {
4649
+ if (cpuPercent === void 0 || !Number.isFinite(cpuPercent)) {
4650
+ return "-";
4651
+ }
4652
+ return `${cpuPercent >= 100 ? cpuPercent.toFixed(0) : cpuPercent.toFixed(1)}%`;
4653
+ }
4654
+ function formatPmMemory(memoryRssBytes) {
4655
+ if (memoryRssBytes === void 0 || !Number.isFinite(memoryRssBytes) || memoryRssBytes < 0) {
4656
+ return "-";
4657
+ }
4658
+ const units = ["B", "KB", "MB", "GB", "TB"];
4659
+ let value = memoryRssBytes;
4660
+ let unitIndex = 0;
4661
+ while (value >= 1024 && unitIndex < units.length - 1) {
4662
+ value /= 1024;
4663
+ unitIndex += 1;
4664
+ }
4665
+ const formatted = value >= 10 || unitIndex === 0 ? value.toFixed(0) : value.toFixed(1);
4666
+ return `${formatted}${units[unitIndex]}`;
4667
+ }
4668
+ function formatPmUptime(uptimeMs) {
4669
+ if (uptimeMs === void 0 || !Number.isFinite(uptimeMs)) {
4670
+ return "-";
4671
+ }
4672
+ return formatPmDuration(Math.max(0, uptimeMs));
4673
+ }
4674
+ function formatPmTarget(record) {
4675
+ if (record.script) {
4676
+ return record.script;
4677
+ }
4678
+ if (record.file) {
4679
+ return record.file;
4680
+ }
4681
+ if (record.wapk) {
4682
+ return record.wapk;
4683
+ }
4684
+ return "-";
4685
+ }
4686
+ function pushPmDetail(lines, label, value) {
4687
+ lines.push(`${padCell(`${label}:`, 18)} ${value}`);
4688
+ }
4689
+ function pushPmDetailList(lines, label, values) {
4690
+ if (values.length === 0) {
4691
+ pushPmDetail(lines, label, "-");
4692
+ return;
4693
+ }
4694
+ pushPmDetail(lines, label, values[0] ?? "-");
4695
+ for (const value of values.slice(1)) {
4696
+ lines.push(`${" ".repeat(20)} ${value}`);
4697
+ }
4698
+ }
4699
+ function formatPmRecordDetails(record, liveMetrics) {
4700
+ const lines = [`Process: ${record.name}`];
4701
+ pushPmDetail(lines, "id", record.id);
4702
+ pushPmDetail(lines, "status", record.status);
4703
+ pushPmDetail(lines, "desired state", record.desiredState);
4704
+ pushPmDetail(lines, "instance", `${record.instanceIndex}/${record.instances}`);
4705
+ pushPmDetail(lines, "cpu", formatPmCpuPercent(liveMetrics.cpuPercent));
4706
+ pushPmDetail(lines, "memory", formatPmMemory(liveMetrics.memoryRssBytes));
4707
+ pushPmDetail(lines, "uptime", formatPmUptime(liveMetrics.uptimeMs));
4708
+ pushPmDetail(lines, "type", record.type);
4709
+ pushPmDetail(lines, "source", record.source);
4710
+ pushPmDetail(lines, "runtime", record.runtime ?? "-");
4711
+ pushPmDetail(lines, "cwd", record.cwd);
4712
+ pushPmDetail(lines, "target", formatPmTarget(record));
4713
+ pushPmDetail(lines, "command", record.commandPreview || "-");
4714
+ pushPmDetail(lines, "runner pid", record.runnerPid ? String(record.runnerPid) : "-");
4715
+ pushPmDetail(lines, "child pid", record.childPid ? String(record.childPid) : "-");
4716
+ pushPmDetail(lines, "restart count", `${record.restartCount}/${record.maxRestarts}`);
4717
+ pushPmDetail(lines, "restart policy", record.restartPolicy);
4718
+ pushPmDetail(lines, "proxy", record.proxy ? `http://${record.proxy.host ?? "0.0.0.0"}:${record.proxy.port}` : "-");
4719
+ pushPmDetail(lines, "proxy strategy", record.proxy?.strategy ?? "-");
4720
+ pushPmDetail(lines, "proxy target", record.proxy && record.proxyTargetPort ? `${record.proxy.targetHost ?? "127.0.0.1"}:${record.proxyTargetPort}` : "-");
4721
+ pushPmDetail(lines, "max memory", record.maxMemoryBytes ? formatPmMemory(record.maxMemoryBytes) : "-");
4722
+ pushPmDetail(lines, "memory action", record.memoryAction ?? "-");
4723
+ pushPmDetail(lines, "cron restart", record.cronRestart ?? "-");
4724
+ pushPmDetail(lines, "exp backoff", record.expBackoffRestartDelay ? formatPmDuration(record.expBackoffRestartDelay) : "-");
4725
+ pushPmDetail(lines, "exp backoff max", record.expBackoffRestartMaxDelay ? formatPmDuration(record.expBackoffRestartMaxDelay) : "-");
4726
+ pushPmDetail(lines, "restart window", record.restartWindow ? formatPmDuration(record.restartWindow) : "-");
4727
+ pushPmDetail(lines, "wait ready", record.waitReady ? "enabled" : "disabled");
4728
+ pushPmDetail(lines, "listen timeout", record.waitReady ? formatPmDuration(record.listenTimeout) : "-");
4729
+ pushPmDetail(lines, "restart delay", formatPmDuration(record.restartDelay));
4730
+ pushPmDetail(lines, "kill timeout", formatPmDuration(record.killTimeout));
4731
+ pushPmDetail(lines, "min uptime", formatPmDuration(record.minUptime));
4732
+ pushPmDetail(lines, "autorestart", record.autorestart ? "enabled" : "disabled");
4733
+ pushPmDetail(lines, "watch", record.watch ? "enabled" : "disabled");
4734
+ pushPmDetail(lines, "watch debounce", record.watch ? formatPmDuration(record.watchDebounce) : "-");
4735
+ pushPmDetailList(lines, "watch paths", record.watchPaths);
4736
+ pushPmDetailList(lines, "watch ignore", record.watchIgnore);
4737
+ if (record.healthCheck) {
4738
+ pushPmDetail(lines, "health check", record.healthCheck.url);
4739
+ pushPmDetail(lines, "health grace", formatPmDuration(record.healthCheck.gracePeriod));
4740
+ pushPmDetail(lines, "health interval", formatPmDuration(record.healthCheck.interval));
4741
+ pushPmDetail(lines, "health timeout", formatPmDuration(record.healthCheck.timeout));
4742
+ pushPmDetail(lines, "health failures", String(record.healthCheck.maxFailures));
4743
+ } else {
4744
+ pushPmDetail(lines, "health check", "-");
4745
+ }
4746
+ pushPmDetailList(lines, "env", Object.entries(record.env).map(([key, value]) => `${key}=${value}`));
4747
+ pushPmDetail(lines, "stdout log", record.logFiles.out);
4748
+ pushPmDetail(lines, "stderr log", record.logFiles.err);
4749
+ pushPmDetail(lines, "created at", record.createdAt);
4750
+ pushPmDetail(lines, "updated at", record.updatedAt);
4751
+ pushPmDetail(lines, "metrics at", liveMetrics.updatedAt ?? "-");
4752
+ pushPmDetail(lines, "started at", record.startedAt ?? "-");
4753
+ pushPmDetail(lines, "stopped at", record.stoppedAt ?? "-");
4754
+ pushPmDetail(lines, "last exit", record.lastExitCode === void 0 ? "-" : String(record.lastExitCode));
4755
+ pushPmDetail(lines, "error", record.error ?? "-");
4756
+ return lines.join(import_node_os2.EOL);
4757
+ }
4758
+ function printPmList(paths, format = "table") {
4759
+ const matches = listPmMatches(paths).map((match) => toPmDisplayRecord(match.record));
4760
+ if (format === "json") {
4761
+ console.log(JSON.stringify(matches.map((match) => ({ ...match.record, liveMetrics: match.liveMetrics })), null, 2));
4762
+ return;
4763
+ }
3007
4764
  if (matches.length === 0) {
3008
4765
  console.log("No managed processes found.");
3009
4766
  return;
@@ -3012,22 +4769,47 @@ function printPmList(paths) {
3012
4769
  padCell("name", 20),
3013
4770
  padCell("status", 12),
3014
4771
  padCell("pid", 8),
4772
+ padCell("cpu", 8),
4773
+ padCell("memory", 10),
4774
+ padCell("uptime", 10),
3015
4775
  padCell("restarts", 10),
3016
4776
  padCell("type", 8),
3017
4777
  "runtime"
3018
4778
  ];
3019
4779
  console.log(headers.join(" "));
3020
- for (const { record } of matches) {
4780
+ for (const { record, liveMetrics } of matches) {
3021
4781
  console.log([
3022
4782
  padCell(record.name, 20),
3023
4783
  padCell(record.status, 12),
3024
4784
  padCell(record.childPid ? String(record.childPid) : "-", 8),
4785
+ padCell(formatPmCpuPercent(liveMetrics.cpuPercent), 8),
4786
+ padCell(formatPmMemory(liveMetrics.memoryRssBytes), 10),
4787
+ padCell(formatPmUptime(liveMetrics.uptimeMs), 10),
3025
4788
  padCell(String(record.restartCount ?? 0), 10),
3026
4789
  padCell(record.type, 8),
3027
4790
  record.runtime ?? "-"
3028
4791
  ].join(" "));
3029
4792
  }
3030
4793
  }
4794
+ async function runPmList(args) {
4795
+ const options = parsePmListArgs(args);
4796
+ const { paths } = await loadPmContext();
4797
+ printPmList(paths, options.format);
4798
+ }
4799
+ async function runPmShow(args) {
4800
+ const options = parsePmShowArgs(args);
4801
+ const { paths } = await loadPmContext();
4802
+ const match = resolveInspectableMatch(paths, options.name);
4803
+ if (!match) {
4804
+ throw new Error(`No managed process found for: ${options.name}`);
4805
+ }
4806
+ const synced = syncPmRecordLiveness(match);
4807
+ if (options.format === "json") {
4808
+ console.log(JSON.stringify(serializePmRecord(synced.record), null, 2));
4809
+ return;
4810
+ }
4811
+ console.log(formatPmRecordDetails(synced.record, resolvePmLiveMetrics(synced.record)));
4812
+ }
3031
4813
  async function runPmStop(args) {
3032
4814
  const target = args[0];
3033
4815
  if (!target) {
@@ -3054,17 +4836,62 @@ async function runPmRestart(args) {
3054
4836
  await stopPmMatches(matches);
3055
4837
  const restarted = [];
3056
4838
  for (const match of matches) {
3057
- const definition = resolvePmAppDefinition(
3058
- toPmAppConfig(match.record),
3059
- { name: match.record.name, env: {}, watchPaths: [], watchIgnore: [] },
3060
- process.cwd(),
3061
- match.record.source
3062
- );
4839
+ const definition = rebuildPmRecordDefinition(match.record);
3063
4840
  await startManagedProcess(definition, paths);
3064
4841
  restarted.push(match.record.name);
3065
4842
  }
3066
4843
  console.log(`[pm] restarted ${restarted.join(", ")}`);
3067
4844
  }
4845
+ async function runPmReload(args) {
4846
+ const target = args[0];
4847
+ if (!target) {
4848
+ throw new Error("Usage: elit pm reload <name|all>");
4849
+ }
4850
+ const { paths } = await loadPmContext();
4851
+ const matches = resolveNamedMatches(paths, target);
4852
+ if (matches.length === 0) {
4853
+ throw new Error(`No managed process found for: ${target}`);
4854
+ }
4855
+ const reloaded = [];
4856
+ const errors = [];
4857
+ for (const group of groupPmMatchesByBaseName(matches)) {
4858
+ for (const match of group) {
4859
+ try {
4860
+ if (supportsPmProxyReload2(match.record)) {
4861
+ const reloadRequestedAt = (/* @__PURE__ */ new Date()).toISOString();
4862
+ writePmRecord(match.filePath, {
4863
+ ...match.record,
4864
+ status: "restarting",
4865
+ reloadRequestedAt,
4866
+ updatedAt: reloadRequestedAt,
4867
+ error: void 0
4868
+ });
4869
+ await waitForPmRecordOnline(
4870
+ getPmRecordPath(paths, match.record.id),
4871
+ resolvePmReloadReadyTimeout(match.record)
4872
+ );
4873
+ reloaded.push(match.record.name);
4874
+ continue;
4875
+ }
4876
+ await stopPmMatches([match]);
4877
+ const definition = rebuildPmRecordDefinition(match.record);
4878
+ const startedRecord = await startManagedProcess(definition, paths);
4879
+ await waitForPmRecordOnline(
4880
+ getPmRecordPath(paths, startedRecord.id),
4881
+ resolvePmReloadReadyTimeout(startedRecord)
4882
+ );
4883
+ reloaded.push(match.record.name);
4884
+ } catch (error) {
4885
+ const message = error instanceof Error ? error.message : String(error);
4886
+ errors.push(`[pm] ${match.record.name}: ${message}`);
4887
+ }
4888
+ }
4889
+ }
4890
+ if (errors.length > 0) {
4891
+ throw new Error([`[pm] reloaded ${reloaded.length} process${reloaded.length === 1 ? "" : "es"}`, ...errors].join(import_node_os2.EOL));
4892
+ }
4893
+ console.log(`[pm] reloaded ${reloaded.join(", ")}`);
4894
+ }
3068
4895
  async function runPmSave() {
3069
4896
  const { paths } = await loadPmContext();
3070
4897
  ensurePmDirectories(paths);
@@ -3086,12 +4913,7 @@ async function runPmResurrect() {
3086
4913
  let restored = 0;
3087
4914
  for (const app of dump.apps) {
3088
4915
  try {
3089
- const definition = resolvePmAppDefinition(
3090
- toSavedPmAppConfig(app),
3091
- { name: app.name, env: {}, watchPaths: [], watchIgnore: [] },
3092
- process.cwd(),
3093
- "cli"
3094
- );
4916
+ const definition = rebuildPmSavedDefinition(app);
3095
4917
  await startManagedProcess(definition, paths);
3096
4918
  restored += 1;
3097
4919
  } catch (error) {
@@ -3115,16 +4937,127 @@ async function runPmDelete(args) {
3115
4937
  throw new Error(`No managed process found for: ${target}`);
3116
4938
  }
3117
4939
  await stopPmMatches(matches);
4940
+ deletePmMatches(matches);
4941
+ console.log(`[pm] deleted ${matches.length} process${matches.length === 1 ? "" : "es"}`);
4942
+ }
4943
+ async function runPmScale(args) {
4944
+ const target = args[0];
4945
+ const countArg = args[1];
4946
+ if (!target || countArg === void 0 || args.length > 2) {
4947
+ throw new Error("Usage: elit pm scale <name> <count>");
4948
+ }
4949
+ const desiredCount = normalizeIntegerOption(countArg, "pm scale <count>", 0);
4950
+ const { config, paths } = await loadPmContext();
4951
+ const exactMatch = findPmRecordMatch(paths, target);
4952
+ const currentMatches = sortPmMatchesByInstance(resolveNamedMatches(paths, exactMatch?.record.baseName ?? target));
4953
+ if (currentMatches.length === 0) {
4954
+ if (desiredCount === 0) {
4955
+ console.log(`[pm] ${target} already scaled to 0 instances`);
4956
+ return;
4957
+ }
4958
+ const definitions = resolvePmStartDefinitions(
4959
+ { name: target, env: {}, watchPaths: [], watchIgnore: [], instances: desiredCount },
4960
+ config,
4961
+ process.cwd()
4962
+ );
4963
+ for (const definition of definitions) {
4964
+ await startManagedProcess(definition, paths);
4965
+ }
4966
+ console.log(`[pm] scaled ${target} to ${desiredCount} instance${desiredCount === 1 ? "" : "s"}`);
4967
+ return;
4968
+ }
4969
+ const baseName = currentMatches[0]?.record.baseName ?? target;
4970
+ if (desiredCount === currentMatches.length) {
4971
+ console.log(`[pm] ${baseName} already scaled to ${desiredCount} instance${desiredCount === 1 ? "" : "s"}`);
4972
+ return;
4973
+ }
4974
+ if (desiredCount === 0) {
4975
+ await stopPmMatches(currentMatches);
4976
+ deletePmMatches(currentMatches);
4977
+ console.log(`[pm] scaled ${baseName} to 0 instances`);
4978
+ return;
4979
+ }
4980
+ if (desiredCount < currentMatches.length) {
4981
+ const toRemove = [...currentMatches].sort((left, right) => right.record.instanceIndex - left.record.instanceIndex).slice(0, currentMatches.length - desiredCount);
4982
+ const remaining = currentMatches.filter((match) => !toRemove.some((removal) => removal.record.id === match.record.id));
4983
+ await stopPmMatches(toRemove);
4984
+ deletePmMatches(toRemove);
4985
+ updatePmInstanceCount(remaining, desiredCount);
4986
+ console.log(`[pm] scaled ${baseName} to ${desiredCount} instance${desiredCount === 1 ? "" : "s"}`);
4987
+ return;
4988
+ }
4989
+ updatePmInstanceCount(currentMatches, desiredCount);
4990
+ const baseRecord = currentMatches[0]?.record;
4991
+ if (!baseRecord) {
4992
+ throw new Error(`No managed process found for: ${target}`);
4993
+ }
4994
+ const baseDefinition = rebuildPmRecordDefinition(baseRecord, desiredCount);
4995
+ const expandedDefinitions = expandPmInstanceDefinitions(baseDefinition, desiredCount);
4996
+ const existingNames = new Set(currentMatches.map((match) => match.record.name));
4997
+ for (const definition of expandedDefinitions) {
4998
+ if (existingNames.has(definition.name)) {
4999
+ continue;
5000
+ }
5001
+ await startManagedProcess(definition, paths);
5002
+ }
5003
+ console.log(`[pm] scaled ${baseName} to ${desiredCount} instance${desiredCount === 1 ? "" : "s"}`);
5004
+ }
5005
+ async function runPmSendSignal(args) {
5006
+ const signalArg = args[0];
5007
+ const target = args[1];
5008
+ if (!signalArg || !target || args.length > 2) {
5009
+ throw new Error("Usage: elit pm send-signal <signal> <name|all>");
5010
+ }
5011
+ const signalName = normalizePmSignalName(signalArg);
5012
+ const { paths } = await loadPmContext();
5013
+ const matches = resolveNamedMatches(paths, target);
5014
+ if (matches.length === 0) {
5015
+ throw new Error(`No managed process found for: ${target}`);
5016
+ }
5017
+ let signaled = 0;
5018
+ const errors = [];
3118
5019
  for (const match of matches) {
3119
- if ((0, import_node_fs5.existsSync)(match.record.logFiles.out)) {
3120
- (0, import_node_fs5.rmSync)(match.record.logFiles.out, { force: true });
5020
+ const pid = resolveSignalablePid(match.record);
5021
+ if (!pid) {
5022
+ continue;
3121
5023
  }
3122
- if ((0, import_node_fs5.existsSync)(match.record.logFiles.err)) {
3123
- (0, import_node_fs5.rmSync)(match.record.logFiles.err, { force: true });
5024
+ try {
5025
+ sendPmSignal(pid, signalName);
5026
+ signaled += 1;
5027
+ } catch (error) {
5028
+ const message = error instanceof Error ? error.message : String(error);
5029
+ errors.push(`[pm] ${match.record.name}: ${message}`);
3124
5030
  }
3125
- (0, import_node_fs5.rmSync)(match.filePath, { force: true });
3126
5031
  }
3127
- console.log(`[pm] deleted ${matches.length} process${matches.length === 1 ? "" : "es"}`);
5032
+ if (errors.length > 0) {
5033
+ throw new Error([`[pm] sent ${signalName} to ${signaled} process${signaled === 1 ? "" : "es"}`, ...errors].join(import_node_os2.EOL));
5034
+ }
5035
+ if (signaled === 0) {
5036
+ throw new Error(`No running managed process found for: ${target}`);
5037
+ }
5038
+ console.log(`[pm] sent ${signalName} to ${signaled} process${signaled === 1 ? "" : "es"}`);
5039
+ }
5040
+ async function runPmReset(args) {
5041
+ const target = args[0];
5042
+ if (!target) {
5043
+ throw new Error("Usage: elit pm reset <name|all>");
5044
+ }
5045
+ const { paths } = await loadPmContext();
5046
+ const matches = resolveNamedMatches(paths, target);
5047
+ if (matches.length === 0) {
5048
+ throw new Error(`No managed process found for: ${target}`);
5049
+ }
5050
+ const now = (/* @__PURE__ */ new Date()).toISOString();
5051
+ for (const match of matches) {
5052
+ writePmRecord(match.filePath, {
5053
+ ...match.record,
5054
+ restartCount: 0,
5055
+ lastExitCode: void 0,
5056
+ error: void 0,
5057
+ updatedAt: now
5058
+ });
5059
+ }
5060
+ console.log(`[pm] reset ${matches.length} process${matches.length === 1 ? "" : "es"}`);
3128
5061
  }
3129
5062
  async function runPmLogs(args) {
3130
5063
  if (args.length === 0) {
@@ -3157,7 +5090,7 @@ async function runPmLogs(args) {
3157
5090
  throw new Error("Usage: elit pm logs <name> [--lines <n>] [--stderr]");
3158
5091
  }
3159
5092
  const { paths } = await loadPmContext();
3160
- const match = findPmRecordMatch(paths, name);
5093
+ const match = resolveInspectableMatch(paths, name);
3161
5094
  if (!match) {
3162
5095
  throw new Error(`No managed process found for: ${name}`);
3163
5096
  }
@@ -3181,17 +5114,36 @@ async function runPmCommand(args) {
3181
5114
  await runPmStart(args.slice(1));
3182
5115
  return;
3183
5116
  case "list":
3184
- case "ls": {
3185
- const { paths } = await loadPmContext();
3186
- printPmList(paths);
5117
+ case "ls":
5118
+ await runPmList(args.slice(1));
5119
+ return;
5120
+ case "jlist":
5121
+ await runPmList(["--json", ...args.slice(1)]);
5122
+ return;
5123
+ case "show":
5124
+ case "describe":
5125
+ await runPmShow(args.slice(1));
3187
5126
  return;
3188
- }
3189
5127
  case "stop":
3190
5128
  await runPmStop(args.slice(1));
3191
5129
  return;
3192
5130
  case "restart":
3193
5131
  await runPmRestart(args.slice(1));
3194
5132
  return;
5133
+ case "reload":
5134
+ await runPmReload(args.slice(1));
5135
+ return;
5136
+ case "scale":
5137
+ await runPmScale(args.slice(1));
5138
+ return;
5139
+ case "send-signal":
5140
+ case "signal":
5141
+ case "sendSignal":
5142
+ await runPmSendSignal(args.slice(1));
5143
+ return;
5144
+ case "reset":
5145
+ await runPmReset(args.slice(1));
5146
+ return;
3195
5147
  case "delete":
3196
5148
  case "remove":
3197
5149
  case "rm":
@@ -3224,6 +5176,12 @@ async function runPmCommand(args) {
3224
5176
  DEFAULT_MIN_UPTIME,
3225
5177
  DEFAULT_PM_DATA_DIR,
3226
5178
  DEFAULT_PM_DUMP_FILE,
5179
+ DEFAULT_PM_EXP_BACKOFF_MAX_DELAY,
5180
+ DEFAULT_PM_KILL_TIMEOUT,
5181
+ DEFAULT_PM_LISTEN_TIMEOUT,
5182
+ DEFAULT_PM_MEMORY_CHECK_INTERVAL,
5183
+ DEFAULT_PM_PROXY_STRATEGY,
5184
+ DEFAULT_PM_RESTART_WINDOW,
3227
5185
  DEFAULT_PM_STOP_GRACE_PERIOD_MS,
3228
5186
  DEFAULT_PM_STOP_POLL_MS,
3229
5187
  DEFAULT_RESTART_DELAY,
@@ -3242,7 +5200,10 @@ async function runPmCommand(args) {
3242
5200
  countDefinedPmWapkSources,
3243
5201
  deriveDefaultWatchPaths,
3244
5202
  ensurePmDirectories,
5203
+ expandPmInstanceDefinitions,
5204
+ findPmGroupMatches,
3245
5205
  findPmRecordMatch,
5206
+ formatPmInstanceName,
3246
5207
  getPmRecordPath,
3247
5208
  hasPmGoogleDriveConfig,
3248
5209
  hasPmWapkRunConfig,
@@ -3261,6 +5222,10 @@ async function runPmCommand(args) {
3261
5222
  normalizeHealthCheckConfig,
3262
5223
  normalizeIntegerOption,
3263
5224
  normalizeNonEmptyString,
5225
+ normalizePmMemoryAction,
5226
+ normalizePmMemoryLimit,
5227
+ normalizePmProxyConfig,
5228
+ normalizePmProxyStrategy,
3264
5229
  normalizePmRestartPolicy,
3265
5230
  normalizePmRuntime,
3266
5231
  normalizeResolvedWatchIgnorePaths,
@@ -3284,7 +5249,9 @@ async function runPmCommand(args) {
3284
5249
  runManagedProcessLoop,
3285
5250
  runPmCommand,
3286
5251
  runPmRunner,
5252
+ samplePmProcessMetrics,
3287
5253
  sanitizePmProcessName,
5254
+ sendPmSignal,
3288
5255
  startManagedProcess,
3289
5256
  stopPmMatches,
3290
5257
  stripPmWapkSourceFromRunConfig,