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.mjs CHANGED
@@ -461,6 +461,7 @@ var DEFAULT_RESTART_DELAY = 1e3;
461
461
  var DEFAULT_MAX_RESTARTS = 10;
462
462
  var DEFAULT_WATCH_DEBOUNCE = 250;
463
463
  var DEFAULT_MIN_UPTIME = 0;
464
+ var DEFAULT_PM_LISTEN_TIMEOUT = 3e3;
464
465
  var DEFAULT_HEALTHCHECK_GRACE_PERIOD = 5e3;
465
466
  var DEFAULT_HEALTHCHECK_INTERVAL = 1e4;
466
467
  var DEFAULT_HEALTHCHECK_TIMEOUT = 3e3;
@@ -468,6 +469,11 @@ var DEFAULT_HEALTHCHECK_MAX_FAILURES = 3;
468
469
  var DEFAULT_LOG_LINES = 40;
469
470
  var DEFAULT_PM_STOP_POLL_MS = 100;
470
471
  var DEFAULT_PM_STOP_GRACE_PERIOD_MS = 5e3;
472
+ var DEFAULT_PM_KILL_TIMEOUT = DEFAULT_PM_STOP_GRACE_PERIOD_MS;
473
+ var DEFAULT_PM_EXP_BACKOFF_MAX_DELAY = 15e3;
474
+ var DEFAULT_PM_MEMORY_CHECK_INTERVAL = 500;
475
+ var DEFAULT_PM_RESTART_WINDOW = 0;
476
+ var DEFAULT_PM_PROXY_STRATEGY = "proxy";
471
477
  var PM_WAPK_ONLINE_STDIN_SHUTDOWN_ENV = "ELIT_PM_WAPK_ONLINE_STDIN_SHUTDOWN";
472
478
  var PM_WAPK_ONLINE_SHUTDOWN_COMMAND = "__ELIT_PM_WAPK_ONLINE_SHUTDOWN__";
473
479
  var PM_WAPK_ONLINE_SHUTDOWN_TIMEOUT_MS = 8e3;
@@ -505,6 +511,32 @@ function normalizePmRestartPolicy(value, optionName = "--restart-policy") {
505
511
  }
506
512
  throw new Error(`${optionName} must be one of: always, on-failure, never`);
507
513
  }
514
+ function normalizePmMemoryAction(value, optionName = "--memory-action") {
515
+ if (value === void 0 || value === null || value === "") {
516
+ return void 0;
517
+ }
518
+ if (typeof value !== "string") {
519
+ throw new Error(`${optionName} must be one of: restart, stop`);
520
+ }
521
+ const action = value.trim().toLowerCase();
522
+ if (action === "restart" || action === "stop") {
523
+ return action;
524
+ }
525
+ throw new Error(`${optionName} must be one of: restart, stop`);
526
+ }
527
+ function normalizePmProxyStrategy(value, optionName = "--proxy-strategy") {
528
+ if (value === void 0 || value === null || value === "") {
529
+ return void 0;
530
+ }
531
+ if (typeof value !== "string") {
532
+ throw new Error(`${optionName} must be one of: proxy, inherit`);
533
+ }
534
+ const strategy = value.trim().toLowerCase();
535
+ if (strategy === "proxy" || strategy === "inherit") {
536
+ return strategy;
537
+ }
538
+ throw new Error(`${optionName} must be one of: proxy, inherit`);
539
+ }
508
540
  function normalizeIntegerOption(value, optionName, min = 0) {
509
541
  const parsed = Number.parseInt(value, 10);
510
542
  if (!Number.isFinite(parsed) || parsed < min) {
@@ -512,6 +544,30 @@ function normalizeIntegerOption(value, optionName, min = 0) {
512
544
  }
513
545
  return parsed;
514
546
  }
547
+ function normalizePmMemoryLimit(value, optionName = "--max-memory") {
548
+ if (value === void 0 || value === null || value === "") {
549
+ return void 0;
550
+ }
551
+ if (typeof value === "number") {
552
+ const normalized2 = Math.trunc(value);
553
+ if (!Number.isFinite(normalized2) || normalized2 < 1) {
554
+ throw new Error(`${optionName} must be a number >= 1 or a size like 256M.`);
555
+ }
556
+ return normalized2;
557
+ }
558
+ if (typeof value !== "string") {
559
+ throw new Error(`${optionName} must be a number >= 1 or a size like 256M.`);
560
+ }
561
+ const normalized = value.trim();
562
+ const match = /^(\d+)(b|kb|mb|gb|tb|k|m|g|t)?$/i.exec(normalized);
563
+ if (!match) {
564
+ throw new Error(`${optionName} must be a number >= 1 or a size like 256M.`);
565
+ }
566
+ const amount = normalizeIntegerOption(match[1] ?? "", optionName, 1);
567
+ const unit = (match[2] ?? "b").toLowerCase();
568
+ const multiplier = unit === "b" ? 1 : unit === "k" || unit === "kb" ? 1024 : unit === "m" || unit === "mb" ? 1024 ** 2 : unit === "g" || unit === "gb" ? 1024 ** 3 : 1024 ** 4;
569
+ return amount * multiplier;
570
+ }
515
571
  function normalizeNonEmptyString(value) {
516
572
  if (typeof value !== "string") {
517
573
  return void 0;
@@ -788,6 +844,30 @@ function readRequiredValue(args, index, optionName) {
788
844
  }
789
845
  return value;
790
846
  }
847
+ function normalizePmProxyConfig(value, optionName = "pm proxy") {
848
+ if (value === void 0 || value === null) {
849
+ return void 0;
850
+ }
851
+ if (typeof value !== "object") {
852
+ throw new Error(`${optionName} must be an object with at least a port.`);
853
+ }
854
+ const candidate = value;
855
+ const hasAnyValue = candidate.port !== void 0 || candidate.host !== void 0 || candidate.targetHost !== void 0 || candidate.envVar !== void 0;
856
+ if (!hasAnyValue) {
857
+ return void 0;
858
+ }
859
+ if (candidate.port === void 0 || candidate.port === null || candidate.port === "") {
860
+ throw new Error(`${optionName}.port is required.`);
861
+ }
862
+ const port = typeof candidate.port === "number" ? normalizeIntegerOption(String(Math.trunc(candidate.port)), `${optionName}.port`, 1) : normalizeIntegerOption(String(candidate.port), `${optionName}.port`, 1);
863
+ return {
864
+ port,
865
+ strategy: normalizePmProxyStrategy(candidate.strategy, `${optionName}.strategy`) ?? DEFAULT_PM_PROXY_STRATEGY,
866
+ host: normalizeNonEmptyString(candidate.host),
867
+ targetHost: normalizeNonEmptyString(candidate.targetHost),
868
+ envVar: normalizeNonEmptyString(candidate.envVar)
869
+ };
870
+ }
791
871
 
792
872
  // src/cli/pm/records.ts
793
873
  import { existsSync as existsSync2, mkdirSync, readFileSync, readdirSync, writeFileSync } from "fs";
@@ -820,18 +900,31 @@ function writePmRecord(filePath, record) {
820
900
  function toSavedAppDefinition(record) {
821
901
  return {
822
902
  name: record.name,
903
+ baseName: record.baseName,
904
+ instanceIndex: record.instanceIndex,
905
+ instances: record.instances,
823
906
  type: record.type,
824
907
  cwd: record.cwd,
825
908
  runtime: record.runtime,
826
909
  env: record.env,
910
+ proxy: record.proxy,
827
911
  script: record.script,
828
912
  file: record.file,
829
913
  wapk: record.wapk,
830
914
  password: record.password,
831
915
  wapkRun: record.wapkRun,
832
916
  restartPolicy: record.restartPolicy,
917
+ maxMemoryBytes: record.maxMemoryBytes,
918
+ memoryAction: record.memoryAction,
919
+ cronRestart: record.cronRestart,
920
+ expBackoffRestartDelay: record.expBackoffRestartDelay,
921
+ expBackoffRestartMaxDelay: record.expBackoffRestartMaxDelay,
922
+ restartWindow: record.restartWindow,
923
+ waitReady: record.waitReady,
924
+ listenTimeout: record.listenTimeout,
833
925
  autorestart: record.autorestart,
834
926
  restartDelay: record.restartDelay,
927
+ killTimeout: record.killTimeout,
835
928
  maxRestarts: record.maxRestarts,
836
929
  minUptime: record.minUptime,
837
930
  watch: record.watch,
@@ -898,6 +991,9 @@ function findPmRecordMatch(paths, nameOrId) {
898
991
  }
899
992
  return listPmRecordMatches(paths).find((match) => match.record.name === nameOrId);
900
993
  }
994
+ function findPmGroupMatches(paths, baseName) {
995
+ return listPmRecordMatches(paths).filter((match) => match.record.baseName === baseName).sort((left, right) => left.record.instanceIndex - right.record.instanceIndex);
996
+ }
901
997
  function isProcessAlive(pid) {
902
998
  if (!pid || pid <= 0) {
903
999
  return false;
@@ -947,11 +1043,22 @@ function toPmAppConfig(record) {
947
1043
  runtime: record.runtime,
948
1044
  cwd: record.cwd,
949
1045
  env: record.env,
1046
+ proxy: record.proxy,
1047
+ instances: record.instances,
950
1048
  autorestart: record.autorestart,
951
1049
  restartDelay: record.restartDelay,
1050
+ killTimeout: record.killTimeout,
952
1051
  maxRestarts: record.maxRestarts,
953
1052
  password: record.password,
954
1053
  restartPolicy: record.restartPolicy,
1054
+ maxMemory: record.maxMemoryBytes,
1055
+ memoryAction: record.memoryAction,
1056
+ cronRestart: record.cronRestart,
1057
+ expBackoffRestartDelay: record.expBackoffRestartDelay,
1058
+ expBackoffRestartMaxDelay: record.expBackoffRestartMaxDelay,
1059
+ restartWindow: record.restartWindow,
1060
+ waitReady: record.waitReady,
1061
+ listenTimeout: record.listenTimeout,
955
1062
  minUptime: record.minUptime,
956
1063
  watch: record.watch,
957
1064
  watchPaths: record.watchPaths,
@@ -970,11 +1077,22 @@ function toSavedPmAppConfig(app) {
970
1077
  runtime: app.runtime,
971
1078
  cwd: app.cwd,
972
1079
  env: app.env,
1080
+ proxy: app.proxy,
1081
+ instances: app.instances,
973
1082
  autorestart: app.autorestart,
974
1083
  restartDelay: app.restartDelay,
1084
+ killTimeout: app.killTimeout,
975
1085
  maxRestarts: app.maxRestarts,
976
1086
  password: app.password,
977
1087
  restartPolicy: app.restartPolicy,
1088
+ maxMemory: app.maxMemoryBytes,
1089
+ memoryAction: app.memoryAction,
1090
+ cronRestart: app.cronRestart,
1091
+ expBackoffRestartDelay: app.expBackoffRestartDelay,
1092
+ expBackoffRestartMaxDelay: app.expBackoffRestartMaxDelay,
1093
+ restartWindow: app.restartWindow,
1094
+ waitReady: app.waitReady,
1095
+ listenTimeout: app.listenTimeout,
978
1096
  minUptime: app.minUptime,
979
1097
  watch: app.watch,
980
1098
  watchPaths: app.watchPaths,
@@ -986,6 +1104,170 @@ function toSavedPmAppConfig(app) {
986
1104
 
987
1105
  // src/cli/pm/config.ts
988
1106
  import { resolve as resolve3 } from "path";
1107
+
1108
+ // src/cli/pm/schedule.ts
1109
+ var PM_EVERY_PATTERN = /^@every\s+(\d+)(ms|s|m|h|d)$/i;
1110
+ function parsePositiveInteger(value, optionName, min = 0) {
1111
+ const parsed = Number.parseInt(value, 10);
1112
+ if (!Number.isFinite(parsed) || parsed < min) {
1113
+ throw new Error(`${optionName} must be a number >= ${min}`);
1114
+ }
1115
+ return parsed;
1116
+ }
1117
+ function fillRange(values, start, end, step, min, max, optionName) {
1118
+ if (!Number.isInteger(start) || !Number.isInteger(end) || start < min || end > max || start > end) {
1119
+ throw new Error(`${optionName} contains an invalid range: ${start}-${end}`);
1120
+ }
1121
+ if (!Number.isInteger(step) || step < 1) {
1122
+ throw new Error(`${optionName} contains an invalid step: ${step}`);
1123
+ }
1124
+ for (let current = start; current <= end; current += step) {
1125
+ values.add(current);
1126
+ }
1127
+ }
1128
+ function parseCronSegment(segment, values, min, max, optionName) {
1129
+ const normalized = segment.trim();
1130
+ if (!normalized) {
1131
+ throw new Error(`${optionName} contains an empty field segment.`);
1132
+ }
1133
+ const [rangeToken, stepToken] = normalized.split("/");
1134
+ if (normalized.split("/").length > 2) {
1135
+ throw new Error(`${optionName} contains an invalid step segment: ${normalized}`);
1136
+ }
1137
+ const step = stepToken === void 0 ? 1 : parsePositiveInteger(stepToken, optionName, 1);
1138
+ if (rangeToken === "*") {
1139
+ fillRange(values, min, max, step, min, max, optionName);
1140
+ return;
1141
+ }
1142
+ if (rangeToken.includes("-")) {
1143
+ const [startToken, endToken] = rangeToken.split("-");
1144
+ if (!startToken || !endToken) {
1145
+ throw new Error(`${optionName} contains an invalid range: ${normalized}`);
1146
+ }
1147
+ const start2 = parsePositiveInteger(startToken, optionName, min);
1148
+ const end = parsePositiveInteger(endToken, optionName, min);
1149
+ fillRange(values, start2, end, step, min, max, optionName);
1150
+ return;
1151
+ }
1152
+ const start = parsePositiveInteger(rangeToken, optionName, min);
1153
+ fillRange(values, start, stepToken === void 0 ? start : max, step, min, max, optionName);
1154
+ }
1155
+ function parseCronField(token, min, max, optionName) {
1156
+ const normalized = token.trim();
1157
+ if (!normalized) {
1158
+ throw new Error(`${optionName} contains an empty field.`);
1159
+ }
1160
+ if (normalized === "*") {
1161
+ const values2 = /* @__PURE__ */ new Set();
1162
+ fillRange(values2, min, max, 1, min, max, optionName);
1163
+ return {
1164
+ values: values2,
1165
+ wildcard: true
1166
+ };
1167
+ }
1168
+ const values = /* @__PURE__ */ new Set();
1169
+ for (const segment of normalized.split(",")) {
1170
+ parseCronSegment(segment, values, min, max, optionName);
1171
+ }
1172
+ if (values.size === 0) {
1173
+ throw new Error(`${optionName} must match at least one value.`);
1174
+ }
1175
+ return {
1176
+ values,
1177
+ wildcard: false
1178
+ };
1179
+ }
1180
+ function matchesCronField(field, value) {
1181
+ if (!field) {
1182
+ return true;
1183
+ }
1184
+ return field.wildcard || field.values.has(value);
1185
+ }
1186
+ function matchesCronDate(schedule, candidate) {
1187
+ if (schedule.kind !== "cron") {
1188
+ return false;
1189
+ }
1190
+ const secondMatches = schedule.hasSeconds ? matchesCronField(schedule.seconds, candidate.getSeconds()) : true;
1191
+ const minuteMatches = matchesCronField(schedule.minutes, candidate.getMinutes());
1192
+ const hourMatches = matchesCronField(schedule.hours, candidate.getHours());
1193
+ const monthMatches = matchesCronField(schedule.months, candidate.getMonth() + 1);
1194
+ const dayOfMonthMatches = matchesCronField(schedule.daysOfMonth, candidate.getDate());
1195
+ const dayOfWeekMatches = matchesCronField(schedule.daysOfWeek, candidate.getDay());
1196
+ const domWildcard = schedule.daysOfMonth?.wildcard ?? true;
1197
+ const dowWildcard = schedule.daysOfWeek?.wildcard ?? true;
1198
+ const dayMatches = domWildcard && dowWildcard ? true : domWildcard ? dayOfWeekMatches : dowWildcard ? dayOfMonthMatches : dayOfMonthMatches || dayOfWeekMatches;
1199
+ return secondMatches && minuteMatches && hourMatches && monthMatches && dayMatches;
1200
+ }
1201
+ function parsePmRestartSchedule(expression, optionName = "--cron-restart") {
1202
+ const normalized = expression.trim();
1203
+ const everyMatch = PM_EVERY_PATTERN.exec(normalized);
1204
+ if (everyMatch) {
1205
+ const [, valueText, unit] = everyMatch;
1206
+ const value = parsePositiveInteger(valueText ?? "", optionName, 1);
1207
+ const multiplier = unit?.toLowerCase() === "ms" ? 1 : unit?.toLowerCase() === "s" ? 1e3 : unit?.toLowerCase() === "m" ? 6e4 : unit?.toLowerCase() === "h" ? 36e5 : 864e5;
1208
+ return {
1209
+ expression: normalized,
1210
+ kind: "every",
1211
+ intervalMs: value * multiplier,
1212
+ hasSeconds: true
1213
+ };
1214
+ }
1215
+ const fields = normalized.split(/\s+/).filter((field) => field.length > 0);
1216
+ if (fields.length !== 5 && fields.length !== 6) {
1217
+ throw new Error(`${optionName} must be a 5-field cron expression or @every <duration>.`);
1218
+ }
1219
+ const hasSeconds = fields.length === 6;
1220
+ const offset = hasSeconds ? 1 : 0;
1221
+ return {
1222
+ expression: normalized,
1223
+ kind: "cron",
1224
+ hasSeconds,
1225
+ seconds: hasSeconds ? parseCronField(fields[0] ?? "", 0, 59, optionName) : void 0,
1226
+ minutes: parseCronField(fields[0 + offset] ?? "", 0, 59, optionName),
1227
+ hours: parseCronField(fields[1 + offset] ?? "", 0, 23, optionName),
1228
+ daysOfMonth: parseCronField(fields[2 + offset] ?? "", 1, 31, optionName),
1229
+ months: parseCronField(fields[3 + offset] ?? "", 1, 12, optionName),
1230
+ daysOfWeek: parseCronField(fields[4 + offset] ?? "", 0, 6, optionName)
1231
+ };
1232
+ }
1233
+ function normalizePmRestartSchedule(value, optionName = "--cron-restart") {
1234
+ if (value === void 0 || value === null || value === "") {
1235
+ return void 0;
1236
+ }
1237
+ if (typeof value !== "string") {
1238
+ throw new Error(`${optionName} must be a cron string or @every <duration>.`);
1239
+ }
1240
+ const normalized = value.trim();
1241
+ if (!normalized) {
1242
+ throw new Error(`${optionName} must be a non-empty cron string or @every <duration>.`);
1243
+ }
1244
+ parsePmRestartSchedule(normalized, optionName);
1245
+ return normalized;
1246
+ }
1247
+ function resolveNextPmScheduleOccurrence(schedule, after = /* @__PURE__ */ new Date()) {
1248
+ if (schedule.kind === "every") {
1249
+ return new Date(after.getTime() + (schedule.intervalMs ?? 0));
1250
+ }
1251
+ const stepMs = schedule.hasSeconds ? 1e3 : 6e4;
1252
+ const candidate = new Date(after.getTime());
1253
+ if (schedule.hasSeconds) {
1254
+ candidate.setMilliseconds(0);
1255
+ candidate.setSeconds(candidate.getSeconds() + 1);
1256
+ } else {
1257
+ candidate.setSeconds(0, 0);
1258
+ candidate.setMinutes(candidate.getMinutes() + 1);
1259
+ }
1260
+ const maxIterations = schedule.hasSeconds ? 2678400 : 527040;
1261
+ for (let index = 0; index < maxIterations; index++) {
1262
+ if (matchesCronDate(schedule, candidate)) {
1263
+ return new Date(candidate.getTime());
1264
+ }
1265
+ candidate.setTime(candidate.getTime() + stepMs);
1266
+ }
1267
+ return void 0;
1268
+ }
1269
+
1270
+ // src/cli/pm/config.ts
989
1271
  function parsePmTarget(parsed, workspaceRoot) {
990
1272
  if (parsed.script) {
991
1273
  return { script: parsed.script };
@@ -1028,6 +1310,20 @@ function defaultProcessName(base, explicitName) {
1028
1310
  }
1029
1311
  return "process";
1030
1312
  }
1313
+ function formatPmInstanceName(baseName, instanceIndex) {
1314
+ return instanceIndex <= 1 ? baseName : `${baseName}:${instanceIndex}`;
1315
+ }
1316
+ function expandPmInstanceDefinitions(definition, targetInstances = definition.instances) {
1317
+ return Array.from({ length: targetInstances }, (_, index) => {
1318
+ const instanceIndex = index + 1;
1319
+ return {
1320
+ ...definition,
1321
+ name: formatPmInstanceName(definition.baseName, instanceIndex),
1322
+ instanceIndex,
1323
+ instances: targetInstances
1324
+ };
1325
+ });
1326
+ }
1031
1327
  function countDefinedTargets(app) {
1032
1328
  return [app.script, app.file, app.wapk].filter(Boolean).length;
1033
1329
  }
@@ -1065,8 +1361,25 @@ function resolvePmAppDefinition(base, parsed, workspaceRoot, source) {
1065
1361
  throw new Error("A pm app must define exactly one of script, file, or wapk.");
1066
1362
  }
1067
1363
  const name = defaultProcessName({ script, file, wapk }, parsed.name ?? base?.name);
1364
+ const instances = parsed.instances ?? base?.instances ?? 1;
1365
+ if (!Number.isInteger(instances) || instances < 1) {
1366
+ throw new Error("pm instances must be a number >= 1.");
1367
+ }
1368
+ const proxy = normalizePmProxyConfig(
1369
+ parsed.proxy ? { ...base?.proxy ?? {}, ...parsed.proxy } : base?.proxy,
1370
+ parsed.proxy ? "pm proxy" : "pm.apps[].proxy"
1371
+ );
1372
+ if (proxy?.strategy === "inherit" && instances > 1) {
1373
+ throw new Error('pm proxy strategy "inherit" currently supports only one managed instance per app.');
1374
+ }
1068
1375
  const mergedWapkRun = mergePmWapkRunConfig(base?.wapkRun, parsed.wapkRun);
1069
1376
  const runtime2 = normalizePmRuntime(parsed.runtime ?? mergedWapkRun?.runtime ?? base?.runtime, "--runtime");
1377
+ const maxMemoryBytes = parsed.maxMemoryBytes ?? normalizePmMemoryLimit(base?.maxMemory, "pm.apps[].maxMemory");
1378
+ const memoryAction = parsed.memoryAction ?? normalizePmMemoryAction(base?.memoryAction, "pm.apps[].memoryAction") ?? "restart";
1379
+ const cronRestart = parsed.cronRestart ?? normalizePmRestartSchedule(base?.cronRestart, "pm.apps[].cronRestart");
1380
+ const expBackoffRestartDelay = parsed.expBackoffRestartDelay ?? (base?.expBackoffRestartDelay === void 0 ? void 0 : normalizeIntegerOption(String(base.expBackoffRestartDelay), "pm.apps[].expBackoffRestartDelay", 1));
1381
+ const expBackoffRestartMaxDelay = parsed.expBackoffRestartMaxDelay ?? (base?.expBackoffRestartMaxDelay === void 0 ? void 0 : normalizeIntegerOption(String(base.expBackoffRestartMaxDelay), "pm.apps[].expBackoffRestartMaxDelay", 1));
1382
+ const restartWindow = parsed.restartWindow ?? (base?.restartWindow === void 0 ? void 0 : normalizeIntegerOption(String(base.restartWindow), "pm.apps[].restartWindow", 1));
1070
1383
  let restartPolicy = normalizePmRestartPolicy(parsed.restartPolicy ?? base?.restartPolicy, "--restart-policy") ?? (base?.autorestart ?? true ? "always" : "never");
1071
1384
  if (parsed.autorestart === false) {
1072
1385
  restartPolicy = "never";
@@ -1086,6 +1399,7 @@ function resolvePmAppDefinition(base, parsed, workspaceRoot, source) {
1086
1399
  timeout: parsed.healthCheckTimeout,
1087
1400
  maxFailures: parsed.healthCheckMaxFailures
1088
1401
  } : base?.healthCheck);
1402
+ const waitReady = parsed.waitReady ?? base?.waitReady ?? false;
1089
1403
  const password = parsed.password ?? mergedWapkRun?.password ?? base?.password;
1090
1404
  const wapkRun = stripPmWapkSourceFromRunConfig(mergedWapkRun);
1091
1405
  if (password && !wapk) {
@@ -1094,8 +1408,14 @@ function resolvePmAppDefinition(base, parsed, workspaceRoot, source) {
1094
1408
  if (wapkRun && !wapk) {
1095
1409
  throw new Error("WAPK run options are only supported when starting a WAPK app.");
1096
1410
  }
1411
+ if (waitReady && !healthCheck) {
1412
+ throw new Error("--wait-ready requires --health-url or pm.apps[].healthCheck.");
1413
+ }
1097
1414
  return {
1098
- name,
1415
+ name: formatPmInstanceName(name, 1),
1416
+ baseName: name,
1417
+ instanceIndex: 1,
1418
+ instances,
1099
1419
  type: script ? "script" : wapk ? "wapk" : "file",
1100
1420
  source,
1101
1421
  cwd: resolvedCwd,
@@ -1110,9 +1430,19 @@ function resolvePmAppDefinition(base, parsed, workspaceRoot, source) {
1110
1430
  wapkRun,
1111
1431
  autorestart,
1112
1432
  restartDelay: parsed.restartDelay ?? base?.restartDelay ?? DEFAULT_RESTART_DELAY,
1433
+ proxy,
1434
+ killTimeout: parsed.killTimeout ?? base?.killTimeout ?? DEFAULT_PM_KILL_TIMEOUT,
1113
1435
  maxRestarts: parsed.maxRestarts ?? base?.maxRestarts ?? DEFAULT_MAX_RESTARTS,
1114
1436
  password,
1115
1437
  restartPolicy,
1438
+ maxMemoryBytes,
1439
+ memoryAction,
1440
+ cronRestart,
1441
+ expBackoffRestartDelay,
1442
+ expBackoffRestartMaxDelay,
1443
+ restartWindow,
1444
+ waitReady,
1445
+ listenTimeout: parsed.listenTimeout ?? base?.listenTimeout ?? DEFAULT_PM_LISTEN_TIMEOUT,
1116
1446
  minUptime: parsed.minUptime ?? base?.minUptime ?? DEFAULT_MIN_UPTIME,
1117
1447
  watch: watch2,
1118
1448
  watchPaths: watch2 ? normalizeResolvedWatchPaths(configuredWatchPaths, resolvedCwd, script ? "script" : wapk ? "wapk" : "file", file, wapk) : [],
@@ -1124,16 +1454,17 @@ function resolvePmAppDefinition(base, parsed, workspaceRoot, source) {
1124
1454
  function resolvePmStartDefinitions(parsed, config, workspaceRoot) {
1125
1455
  const configApps = getConfiguredPmApps(config);
1126
1456
  const selection = resolveStartSelection(configApps, parsed, workspaceRoot);
1457
+ const expandDefinitions = (definitions) => definitions.flatMap((definition) => expandPmInstanceDefinitions(definition));
1127
1458
  if (selection.startAll) {
1128
1459
  if (configApps.length === 0) {
1129
1460
  throw new Error("No pm apps configured in elit.config.* and no start target was provided.");
1130
1461
  }
1131
- return configApps.map((app) => resolvePmAppDefinition(app, { ...parsed, name: app.name }, workspaceRoot, "config"));
1462
+ return expandDefinitions(configApps.map((app) => resolvePmAppDefinition(app, { ...parsed, name: app.name }, workspaceRoot, "config")));
1132
1463
  }
1133
1464
  if (selection.selected) {
1134
- return [resolvePmAppDefinition(selection.selected, parsed, workspaceRoot, "config")];
1465
+ return expandDefinitions([resolvePmAppDefinition(selection.selected, parsed, workspaceRoot, "config")]);
1135
1466
  }
1136
- return [resolvePmAppDefinition(void 0, parsed, workspaceRoot, "cli")];
1467
+ return expandDefinitions([resolvePmAppDefinition(void 0, parsed, workspaceRoot, "cli")]);
1137
1468
  }
1138
1469
  function parsePmStartArgs(args) {
1139
1470
  const parsed = {
@@ -1206,6 +1537,39 @@ function parsePmStartArgs(args) {
1206
1537
  parsed.env[key] = value;
1207
1538
  break;
1208
1539
  }
1540
+ case "--instances":
1541
+ parsed.instances = normalizeIntegerOption(readRequiredValue(args, ++index, "--instances"), "--instances", 1);
1542
+ break;
1543
+ case "--proxy-port":
1544
+ parsed.proxy = {
1545
+ ...parsed.proxy,
1546
+ port: normalizeIntegerOption(readRequiredValue(args, ++index, "--proxy-port"), "--proxy-port", 1)
1547
+ };
1548
+ break;
1549
+ case "--proxy-strategy":
1550
+ parsed.proxy = {
1551
+ ...parsed.proxy,
1552
+ strategy: normalizePmProxyStrategy(readRequiredValue(args, ++index, "--proxy-strategy"), "--proxy-strategy")
1553
+ };
1554
+ break;
1555
+ case "--proxy-host":
1556
+ parsed.proxy = {
1557
+ ...parsed.proxy,
1558
+ host: readRequiredValue(args, ++index, "--proxy-host")
1559
+ };
1560
+ break;
1561
+ case "--proxy-target-host":
1562
+ parsed.proxy = {
1563
+ ...parsed.proxy,
1564
+ targetHost: readRequiredValue(args, ++index, "--proxy-target-host")
1565
+ };
1566
+ break;
1567
+ case "--proxy-env":
1568
+ parsed.proxy = {
1569
+ ...parsed.proxy,
1570
+ envVar: readRequiredValue(args, ++index, "--proxy-env")
1571
+ };
1572
+ break;
1209
1573
  case "--password":
1210
1574
  parsed.password = readRequiredValue(args, ++index, "--password");
1211
1575
  break;
@@ -1256,6 +1620,30 @@ function parsePmStartArgs(args) {
1256
1620
  case "--restart-policy":
1257
1621
  parsed.restartPolicy = normalizePmRestartPolicy(readRequiredValue(args, ++index, "--restart-policy"));
1258
1622
  break;
1623
+ case "--max-memory":
1624
+ parsed.maxMemoryBytes = normalizePmMemoryLimit(readRequiredValue(args, ++index, "--max-memory"), "--max-memory");
1625
+ break;
1626
+ case "--memory-action":
1627
+ parsed.memoryAction = normalizePmMemoryAction(readRequiredValue(args, ++index, "--memory-action"), "--memory-action");
1628
+ break;
1629
+ case "--cron-restart":
1630
+ parsed.cronRestart = normalizePmRestartSchedule(readRequiredValue(args, ++index, "--cron-restart"), "--cron-restart");
1631
+ break;
1632
+ case "--exp-backoff-restart-delay":
1633
+ parsed.expBackoffRestartDelay = normalizeIntegerOption(readRequiredValue(args, ++index, "--exp-backoff-restart-delay"), "--exp-backoff-restart-delay", 1);
1634
+ break;
1635
+ case "--exp-backoff-restart-max-delay":
1636
+ parsed.expBackoffRestartMaxDelay = normalizeIntegerOption(readRequiredValue(args, ++index, "--exp-backoff-restart-max-delay"), "--exp-backoff-restart-max-delay", 1);
1637
+ break;
1638
+ case "--restart-window":
1639
+ parsed.restartWindow = normalizeIntegerOption(readRequiredValue(args, ++index, "--restart-window"), "--restart-window", 1);
1640
+ break;
1641
+ case "--wait-ready":
1642
+ parsed.waitReady = true;
1643
+ break;
1644
+ case "--listen-timeout":
1645
+ parsed.listenTimeout = normalizeIntegerOption(readRequiredValue(args, ++index, "--listen-timeout"), "--listen-timeout", 1);
1646
+ break;
1259
1647
  case "--min-uptime":
1260
1648
  parsed.minUptime = normalizeIntegerOption(readRequiredValue(args, ++index, "--min-uptime"), "--min-uptime");
1261
1649
  break;
@@ -1295,6 +1683,9 @@ function parsePmStartArgs(args) {
1295
1683
  case "--restart-delay":
1296
1684
  parsed.restartDelay = normalizeIntegerOption(readRequiredValue(args, ++index, "--restart-delay"), "--restart-delay");
1297
1685
  break;
1686
+ case "--kill-timeout":
1687
+ parsed.killTimeout = normalizeIntegerOption(readRequiredValue(args, ++index, "--kill-timeout"), "--kill-timeout", 1);
1688
+ break;
1298
1689
  case "--max-restarts":
1299
1690
  parsed.maxRestarts = normalizeIntegerOption(readRequiredValue(args, ++index, "--max-restarts"), "--max-restarts");
1300
1691
  break;
@@ -1336,8 +1727,15 @@ function printPmHelp() {
1336
1727
  " elit pm start my-app",
1337
1728
  " elit pm start",
1338
1729
  " elit pm list",
1730
+ " elit pm list --json",
1731
+ " elit pm show <name>",
1732
+ " elit pm describe <name> --json",
1339
1733
  " elit pm stop <name|all>",
1340
1734
  " elit pm restart <name|all>",
1735
+ " elit pm reload <name|all>",
1736
+ " elit pm scale <name> <count>",
1737
+ " elit pm reset <name|all>",
1738
+ " elit pm send-signal <signal> <name|all>",
1341
1739
  " elit pm delete <name|all>",
1342
1740
  " elit pm save",
1343
1741
  " elit pm resurrect",
@@ -1353,6 +1751,12 @@ function printPmHelp() {
1353
1751
  " --google-drive-shared-drive Forward supportsAllDrives=true for shared drives",
1354
1752
  " --runtime, -r <name> Runtime override: node, bun, deno",
1355
1753
  " --name, -n <name> Process name used by list/stop/restart",
1754
+ " --instances <count> Start multiple managed instances for the same app name",
1755
+ " --proxy-port <port> Own a public HTTP port through a PM proxy for single-instance reload handoff",
1756
+ " --proxy-strategy <mode> Public socket mode: proxy or inherit (default: proxy)",
1757
+ " --proxy-host <host> Public host bound by the PM proxy (default: 0.0.0.0)",
1758
+ " --proxy-target-host <host> Internal host used for upstream child traffic (default: 127.0.0.1)",
1759
+ " --proxy-env <name> Env var populated with the child private port (default: PORT)",
1356
1760
  " --cwd <dir> Working directory for the managed process",
1357
1761
  " --env KEY=VALUE Add or override an environment variable",
1358
1762
  " --password <value> Password for locked WAPK archives",
@@ -1364,6 +1768,14 @@ function printPmHelp() {
1364
1768
  " --no-archive-watch Disable archive-source read sync for WAPK apps",
1365
1769
  " --archive-sync-interval <ms> Forward WAPK archive read-sync interval (>= 50ms)",
1366
1770
  " --restart-policy <mode> Restart policy: always, on-failure, never",
1771
+ " --max-memory <bytes|size> Trigger a memory action after a limit like 268435456 or 256M",
1772
+ " --memory-action <mode> Action on max-memory: restart, stop",
1773
+ " --cron-restart <expr> Restart on a cron schedule or @every <duration>",
1774
+ " --exp-backoff-restart-delay <ms> Exponential unstable-restart backoff base delay",
1775
+ " --exp-backoff-restart-max-delay <ms> Maximum unstable-restart backoff delay (default 15000)",
1776
+ " --restart-window <ms> Rolling time window used when counting restart attempts",
1777
+ " --wait-ready Keep the process in starting state until its health check succeeds",
1778
+ " --listen-timeout <ms> Startup wait limit when --wait-ready is enabled (default 3000)",
1367
1779
  " --min-uptime <ms> Reset crash counter after this healthy uptime",
1368
1780
  " --watch Restart when watched files change",
1369
1781
  " --watch-path <path> Add a file or directory to watch",
@@ -1376,6 +1788,7 @@ function printPmHelp() {
1376
1788
  " --health-max-failures <n> Consecutive failures before restart (default 3)",
1377
1789
  " --no-autorestart Disable automatic restart",
1378
1790
  " --restart-delay <ms> Delay between restart attempts (default 1000)",
1791
+ " --kill-timeout <ms> Grace period before force-killing a stop or restart (default 5000)",
1379
1792
  " --max-restarts <count> Maximum restart attempts (default 10)",
1380
1793
  "",
1381
1794
  "Config:",
@@ -1399,6 +1812,17 @@ function printPmHelp() {
1399
1812
  " - elit pm save persists running apps to pm.dumpFile or ./.elit/pm/dump.json.",
1400
1813
  " - elit pm resurrect restarts whatever was last saved by elit pm save.",
1401
1814
  " - elit pm start <name> starts a configured app by name.",
1815
+ " - elit pm reload <name|all> performs a rolling stop/start and waits for each instance to return online before continuing.",
1816
+ " - elit pm scale <name> <count> changes the number of managed instances for a running app group.",
1817
+ " - elit pm reset <name|all> clears restart count, last exit code, and saved error metadata.",
1818
+ " - elit pm send-signal <signal> <name|all> forwards a POSIX-style signal such as SIGUSR2 or TERM.",
1819
+ " - 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.",
1820
+ " - killTimeout controls how long PM waits before force-killing an app that ignores stop or restart requests.",
1821
+ " - waitReady uses the configured health check as a startup gate and errors if it never becomes healthy within listenTimeout.",
1822
+ " - elit pm list shows live cpu, memory, and uptime columns when the child process is running.",
1823
+ " - elit pm list --json and elit pm jlist print machine-readable process state.",
1824
+ " - elit pm list --json, elit pm show --json, and elit pm describe --json include liveMetrics when available.",
1825
+ " - elit pm show <name> and elit pm describe <name> expose the full saved process record.",
1402
1826
  " - TypeScript files with runtime node require tsx, otherwise use --runtime bun.",
1403
1827
  " - WAPK processes are executed through elit wapk run inside the manager.",
1404
1828
  " - WAPK PM apps can use local archives, gdrive://<fileId>, or pm.apps[].wapkRun.googleDrive.",
@@ -1409,7 +1833,7 @@ function printPmHelp() {
1409
1833
  // src/cli/pm/process.ts
1410
1834
  import { spawn, spawnSync } from "child_process";
1411
1835
  import { existsSync as existsSync3 } from "fs";
1412
- import { basename, join as join4 } from "path";
1836
+ import { basename, dirname as dirname2, join as join4, resolve as resolve4 } from "path";
1413
1837
  function readCurrentCliInvocation() {
1414
1838
  const cliEntry = process.argv[1];
1415
1839
  if (!cliEntry) {
@@ -1454,16 +1878,94 @@ function resolveTsxExecutable(cwd) {
1454
1878
  const globalCommand = process.platform === "win32" ? "tsx.cmd" : "tsx";
1455
1879
  return commandExists(globalCommand) ? globalCommand : void 0;
1456
1880
  }
1881
+ function resolvePmNodeSharedListenerBootstrapPath() {
1882
+ const cliEntry = process.argv[1];
1883
+ const candidates = [
1884
+ join4(__dirname, "node-shared-listener-bootstrap.cjs"),
1885
+ join4(__dirname, "..", "..", "..", "dist", "pm-node-shared-listener-bootstrap.cjs")
1886
+ ];
1887
+ if (cliEntry) {
1888
+ const resolvedCliEntry = resolve4(cliEntry);
1889
+ const baseDir = dirname2(resolvedCliEntry);
1890
+ candidates.push(
1891
+ join4(baseDir, "cli", "pm", "node-shared-listener-bootstrap.cjs"),
1892
+ join4(baseDir, "pm-node-shared-listener-bootstrap.cjs")
1893
+ );
1894
+ }
1895
+ return candidates.find((candidate) => existsSync3(candidate));
1896
+ }
1897
+ function appendNodeOption(existing, option) {
1898
+ return existing && existing.trim().length > 0 ? `${existing.trim()} ${option}` : option;
1899
+ }
1900
+ function supportsPmInheritedListener(record, runtime2) {
1901
+ return (record.proxy?.strategy ?? DEFAULT_PM_PROXY_STRATEGY) === "inherit" && record.type === "file" && runtime2 === "node" && !isTypescriptFile(record.file);
1902
+ }
1457
1903
  function inferRuntimeFromFile(filePath) {
1458
1904
  if (isTypescriptFile(filePath) && commandExists("bun")) {
1459
1905
  return "bun";
1460
1906
  }
1461
1907
  return "node";
1462
1908
  }
1909
+ function sampleWindowsPmProcessMetrics(pid) {
1910
+ const script = [
1911
+ '$ErrorActionPreference = "Stop"',
1912
+ `$sample = Get-CimInstance -ClassName Win32_PerfFormattedData_PerfProc_Process -Filter "IDProcess = ${pid}" | Select-Object -First 1`,
1913
+ "if (-not $sample) { exit 2 }",
1914
+ "$cpu = [double]$sample.PercentProcessorTime",
1915
+ `$memory = if ($sample.PSObject.Properties.Match('WorkingSetPrivate').Count -gt 0) { [int64]$sample.WorkingSetPrivate } else { [int64](Get-Process -Id ${pid} -ErrorAction Stop).WorkingSet64 }`,
1916
+ 'Write-Output ($cpu.ToString([System.Globalization.CultureInfo]::InvariantCulture) + "," + $memory)'
1917
+ ].join("; ");
1918
+ const result = spawnSync(
1919
+ "powershell.exe",
1920
+ ["-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-Command", script],
1921
+ {
1922
+ encoding: "utf8",
1923
+ stdio: ["ignore", "pipe", "ignore"],
1924
+ windowsHide: true
1925
+ }
1926
+ );
1927
+ if (result.error || result.status !== 0) {
1928
+ return {};
1929
+ }
1930
+ const [cpuText, memoryText] = result.stdout.trim().split(",");
1931
+ const cpuPercent = Number.parseFloat((cpuText ?? "").replace(",", "."));
1932
+ const memoryRssBytes = Number.parseInt(memoryText ?? "", 10);
1933
+ return {
1934
+ cpuPercent: Number.isFinite(cpuPercent) ? cpuPercent : void 0,
1935
+ memoryRssBytes: Number.isFinite(memoryRssBytes) ? memoryRssBytes : void 0
1936
+ };
1937
+ }
1938
+ function samplePosixPmProcessMetrics(pid) {
1939
+ const result = spawnSync(
1940
+ "ps",
1941
+ ["-p", String(pid), "-o", "%cpu=", "-o", "rss="],
1942
+ {
1943
+ encoding: "utf8",
1944
+ stdio: ["ignore", "pipe", "ignore"],
1945
+ windowsHide: true
1946
+ }
1947
+ );
1948
+ if (result.error || result.status !== 0) {
1949
+ return {};
1950
+ }
1951
+ const [cpuText, memoryText] = result.stdout.trim().split(/\s+/, 2);
1952
+ const cpuPercent = Number.parseFloat((cpuText ?? "").replace(",", "."));
1953
+ const rssKilobytes = Number.parseInt(memoryText ?? "", 10);
1954
+ return {
1955
+ cpuPercent: Number.isFinite(cpuPercent) ? cpuPercent : void 0,
1956
+ memoryRssBytes: Number.isFinite(rssKilobytes) ? rssKilobytes * 1024 : void 0
1957
+ };
1958
+ }
1959
+ function samplePmProcessMetrics(pid) {
1960
+ return process.platform === "win32" ? sampleWindowsPmProcessMetrics(pid) : samplePosixPmProcessMetrics(pid);
1961
+ }
1463
1962
  function isPmOnlineWapkRecord(record) {
1464
1963
  return record.type === "wapk" && isPmWapkOnlineRunConfig(record.wapkRun);
1465
1964
  }
1466
1965
  function buildPmCommand(record) {
1966
+ if ((record.proxy?.strategy ?? DEFAULT_PM_PROXY_STRATEGY) === "inherit" && record.type !== "file") {
1967
+ throw new Error('pm proxy strategy "inherit" currently supports only Node .js/.mjs/.cjs file targets.');
1968
+ }
1467
1969
  if (record.type === "script") {
1468
1970
  return {
1469
1971
  command: record.script,
@@ -1535,9 +2037,23 @@ function buildPmCommand(record) {
1535
2037
  }
1536
2038
  const executable = preferCurrentExecutable("node");
1537
2039
  ensureCommandAvailable(executable, "Node.js runtime");
2040
+ const wantsInheritedListener = (record.proxy?.strategy ?? DEFAULT_PM_PROXY_STRATEGY) === "inherit";
2041
+ if (wantsInheritedListener && !supportsPmInheritedListener(record, runtime2)) {
2042
+ throw new Error('pm proxy strategy "inherit" currently supports only Node .js/.mjs/.cjs file targets.');
2043
+ }
2044
+ const bootstrapPath = wantsInheritedListener ? resolvePmNodeSharedListenerBootstrapPath() : void 0;
2045
+ if (wantsInheritedListener && !bootstrapPath) {
2046
+ throw new Error("Unable to resolve the PM shared-listener bootstrap module.");
2047
+ }
1538
2048
  return {
1539
2049
  command: executable,
1540
2050
  args: [record.file],
2051
+ env: bootstrapPath ? {
2052
+ NODE_OPTIONS: appendNodeOption(process.env.NODE_OPTIONS, `--require=${bootstrapPath}`),
2053
+ ELIT_PM_LISTEN_MODE: "ipc",
2054
+ ELIT_PM_PUBLIC_PORT: String(record.proxy?.port ?? record.env.PORT ?? "")
2055
+ } : void 0,
2056
+ ipc: Boolean(bootstrapPath),
1541
2057
  runtime: runtime2,
1542
2058
  preview: `${basename(executable)} ${quoteCommandSegment(record.file)}`
1543
2059
  };
@@ -1549,20 +2065,33 @@ function createRecordFromDefinition(definition, paths, existing) {
1549
2065
  return {
1550
2066
  id,
1551
2067
  name: definition.name,
2068
+ baseName: definition.baseName,
2069
+ instanceIndex: definition.instanceIndex,
2070
+ instances: definition.instances,
1552
2071
  type: definition.type,
1553
2072
  source: definition.source,
1554
2073
  cwd: definition.cwd,
1555
2074
  runtime: definition.runtime,
1556
2075
  env: definition.env,
2076
+ proxy: definition.proxy,
1557
2077
  script: definition.script,
1558
2078
  file: definition.file,
1559
2079
  wapk: definition.wapk,
1560
2080
  wapkRun: definition.wapkRun,
1561
2081
  autorestart: definition.autorestart,
1562
2082
  restartDelay: definition.restartDelay,
2083
+ killTimeout: definition.killTimeout,
1563
2084
  maxRestarts: definition.maxRestarts,
1564
2085
  password: definition.password,
1565
2086
  restartPolicy: definition.restartPolicy,
2087
+ maxMemoryBytes: definition.maxMemoryBytes,
2088
+ memoryAction: definition.memoryAction,
2089
+ cronRestart: definition.cronRestart,
2090
+ expBackoffRestartDelay: definition.expBackoffRestartDelay,
2091
+ expBackoffRestartMaxDelay: definition.expBackoffRestartMaxDelay,
2092
+ restartWindow: definition.restartWindow,
2093
+ waitReady: definition.waitReady,
2094
+ listenTimeout: definition.listenTimeout,
1566
2095
  minUptime: definition.minUptime,
1567
2096
  watch: definition.watch,
1568
2097
  watchPaths: definition.watchPaths,
@@ -1578,16 +2107,20 @@ function createRecordFromDefinition(definition, paths, existing) {
1578
2107
  stoppedAt: void 0,
1579
2108
  runnerPid: void 0,
1580
2109
  childPid: void 0,
2110
+ proxyTargetPort: void 0,
1581
2111
  restartCount: existing?.restartCount ?? 0,
2112
+ reloadRequestedAt: void 0,
2113
+ lastRestartAt: existing?.lastRestartAt,
1582
2114
  lastExitCode: existing?.lastExitCode,
1583
2115
  error: void 0,
2116
+ proxyReadyAt: void 0,
1584
2117
  logFiles: existing?.logFiles ?? {
1585
2118
  out: join4(paths.logsDir, `${id}.out.log`),
1586
2119
  err: join4(paths.logsDir, `${id}.err.log`)
1587
2120
  }
1588
2121
  };
1589
2122
  }
1590
- function terminateProcessTree(pid) {
2123
+ function terminateProcessTree(pid, options) {
1591
2124
  if (process.platform === "win32") {
1592
2125
  const result = spawnSync("taskkill", ["/PID", String(pid), "/T", "/F"], {
1593
2126
  stdio: "ignore",
@@ -1599,7 +2132,17 @@ function terminateProcessTree(pid) {
1599
2132
  return;
1600
2133
  }
1601
2134
  try {
1602
- process.kill(pid, "SIGTERM");
2135
+ process.kill(pid, options?.force ? "SIGKILL" : "SIGTERM");
2136
+ } catch (error) {
2137
+ const code = error.code;
2138
+ if (code !== "ESRCH") {
2139
+ throw error;
2140
+ }
2141
+ }
2142
+ }
2143
+ function sendPmSignal(pid, signal) {
2144
+ try {
2145
+ process.kill(pid, signal);
1603
2146
  } catch (error) {
1604
2147
  const code = error.code;
1605
2148
  if (code !== "ESRCH") {
@@ -1651,9 +2194,10 @@ async function startManagedProcess(definition, paths) {
1651
2194
 
1652
2195
  // src/cli/pm/runner.ts
1653
2196
  import { spawn as spawn2 } from "child_process";
2197
+ import { createServer as createHttpServer } from "http";
1654
2198
  import { createWriteStream, mkdirSync as mkdirSync3 } from "fs";
1655
2199
  import { EOL } from "os";
1656
- import { dirname as dirname3, join as join6, resolve as resolve5 } from "path";
2200
+ import { dirname as dirname4, join as join6, resolve as resolve6 } from "path";
1657
2201
 
1658
2202
  // src/server/chokidar/watch.ts
1659
2203
  init_runtime();
@@ -1884,13 +2428,13 @@ var win32 = createPathOps(true);
1884
2428
  function join5(...paths) {
1885
2429
  return joinPaths(paths, isWindows);
1886
2430
  }
1887
- function resolve4(...paths) {
2431
+ function resolve5(...paths) {
1888
2432
  return resolvePaths(paths, isWindows);
1889
2433
  }
1890
2434
  function relative(from, to) {
1891
2435
  return relativePath(from, to, isWindows);
1892
2436
  }
1893
- function dirname2(path) {
2437
+ function dirname3(path) {
1894
2438
  return getDirname(path, isWindows);
1895
2439
  }
1896
2440
 
@@ -1928,14 +2472,14 @@ function getBaseDirectory(pattern) {
1928
2472
  }
1929
2473
  if (normalizedPattern && existsSync4(normalizedPattern)) {
1930
2474
  try {
1931
- return statSync2(normalizedPattern).isDirectory() ? normalizedPattern : normalizePath2(dirname2(normalizedPattern)) || ".";
2475
+ return statSync2(normalizedPattern).isDirectory() ? normalizedPattern : normalizePath2(dirname3(normalizedPattern)) || ".";
1932
2476
  } catch {
1933
- return normalizePath2(dirname2(normalizedPattern)) || ".";
2477
+ return normalizePath2(dirname3(normalizedPattern)) || ".";
1934
2478
  }
1935
2479
  }
1936
2480
  const lastSegment = parts[parts.length - 1] || "";
1937
2481
  if (lastSegment.includes(".") && !lastSegment.startsWith(".")) {
1938
- return normalizePath2(dirname2(normalizedPattern)) || ".";
2482
+ return normalizePath2(dirname3(normalizedPattern)) || ".";
1939
2483
  }
1940
2484
  return normalizedPattern || ".";
1941
2485
  }
@@ -2130,9 +2674,210 @@ function watch(paths, options) {
2130
2674
  return watcher;
2131
2675
  }
2132
2676
 
2677
+ // src/cli/pm/proxy.ts
2678
+ import { createServer, request as httpRequest } from "http";
2679
+ import { request as httpsRequest } from "https";
2680
+ import { createServer as createNetServer } from "net";
2681
+ function resolvePmProxyHost(proxy) {
2682
+ return proxy.host?.trim() || "0.0.0.0";
2683
+ }
2684
+ function resolvePmProxyTargetHost(proxy) {
2685
+ return proxy.targetHost?.trim() || "127.0.0.1";
2686
+ }
2687
+ function resolvePmProxyEnvVar(proxy) {
2688
+ return proxy.envVar?.trim() || "PORT";
2689
+ }
2690
+ function buildPmProxyTargetUrl(proxy, targetPort) {
2691
+ return `http://${resolvePmProxyTargetHost(proxy)}:${targetPort}`;
2692
+ }
2693
+ function rewritePmProxyHealthCheckUrl(url, targetHost, targetPort) {
2694
+ const targetUrl = new URL(url);
2695
+ targetUrl.hostname = targetHost;
2696
+ targetUrl.port = String(targetPort);
2697
+ return targetUrl.toString();
2698
+ }
2699
+ async function allocatePmProxyTargetPort(host = "127.0.0.1") {
2700
+ const server = createNetServer();
2701
+ return await new Promise((resolve7, reject) => {
2702
+ server.once("error", reject);
2703
+ server.listen(0, host, () => {
2704
+ const address = server.address();
2705
+ if (!address || typeof address === "string") {
2706
+ server.close(() => reject(new Error("Failed to allocate an internal PM proxy port.")));
2707
+ return;
2708
+ }
2709
+ const port = address.port;
2710
+ server.close((error) => {
2711
+ if (error) {
2712
+ reject(error);
2713
+ return;
2714
+ }
2715
+ resolve7(port);
2716
+ });
2717
+ });
2718
+ });
2719
+ }
2720
+ function buildPmProxyHeaders(headersInput, host) {
2721
+ const headers = {};
2722
+ for (const [key, value] of Object.entries(headersInput)) {
2723
+ if (value !== void 0) {
2724
+ headers[key] = value;
2725
+ }
2726
+ }
2727
+ headers.host = host;
2728
+ return headers;
2729
+ }
2730
+ function writeRawHttpResponse(socket, statusCode, statusMessage, headers) {
2731
+ const lines = [`HTTP/1.1 ${statusCode} ${statusMessage}`];
2732
+ for (const [key, value] of Object.entries(headers)) {
2733
+ if (value === void 0) {
2734
+ continue;
2735
+ }
2736
+ if (Array.isArray(value)) {
2737
+ for (const item of value) {
2738
+ lines.push(`${key}: ${item}`);
2739
+ }
2740
+ continue;
2741
+ }
2742
+ lines.push(`${key}: ${value}`);
2743
+ }
2744
+ socket.write(`${lines.join("\r\n")}\r
2745
+ \r
2746
+ `);
2747
+ }
2748
+ async function createPmProxyController(proxy) {
2749
+ let targets = [];
2750
+ let nextTargetIndex = 0;
2751
+ const setResolvedTargets = (nextTargets) => {
2752
+ const unchanged = nextTargets.length === targets.length && nextTargets.every((target, index) => targets[index]?.href === target.href);
2753
+ targets = nextTargets;
2754
+ if (targets.length === 0) {
2755
+ nextTargetIndex = 0;
2756
+ return;
2757
+ }
2758
+ if (!unchanged) {
2759
+ nextTargetIndex = nextTargetIndex % targets.length;
2760
+ }
2761
+ };
2762
+ const pickTarget = () => {
2763
+ if (targets.length === 0) {
2764
+ return null;
2765
+ }
2766
+ const target = targets[nextTargetIndex % targets.length];
2767
+ nextTargetIndex = (nextTargetIndex + 1) % targets.length;
2768
+ return target;
2769
+ };
2770
+ const server = createServer((req, res) => {
2771
+ const target = pickTarget();
2772
+ if (!target) {
2773
+ res.statusCode = 503;
2774
+ res.end("PM proxy target is not ready.");
2775
+ return;
2776
+ }
2777
+ const requestLib = target.protocol === "https:" ? httpsRequest : httpRequest;
2778
+ const targetUrl = new URL(req.url || "/", target);
2779
+ const headers = buildPmProxyHeaders(req.headers, target.host);
2780
+ const proxyReq = requestLib(targetUrl, {
2781
+ method: req.method,
2782
+ headers
2783
+ }, (proxyRes) => {
2784
+ const outgoingHeaders = {};
2785
+ for (const [key, value] of Object.entries(proxyRes.headers)) {
2786
+ if (value !== void 0) {
2787
+ outgoingHeaders[key] = value;
2788
+ }
2789
+ }
2790
+ res.writeHead(proxyRes.statusCode || 200, outgoingHeaders);
2791
+ proxyRes.pipe(res);
2792
+ });
2793
+ proxyReq.on("error", (error) => {
2794
+ if (!res.headersSent) {
2795
+ res.statusCode = 502;
2796
+ }
2797
+ res.end(`PM proxy error: ${error.message}`);
2798
+ });
2799
+ req.pipe(proxyReq);
2800
+ });
2801
+ server.on("upgrade", (req, socket, head) => {
2802
+ const target = pickTarget();
2803
+ if (!target) {
2804
+ writeRawHttpResponse(socket, 503, "Service Unavailable", {
2805
+ connection: "close",
2806
+ "content-length": 0
2807
+ });
2808
+ socket.destroy();
2809
+ return;
2810
+ }
2811
+ const requestLib = target.protocol === "https:" ? httpsRequest : httpRequest;
2812
+ const targetUrl = new URL(req.url || "/", target);
2813
+ const proxyReq = requestLib(targetUrl, {
2814
+ method: req.method,
2815
+ headers: buildPmProxyHeaders(req.headers, target.host)
2816
+ });
2817
+ proxyReq.on("upgrade", (proxyRes, proxySocket, proxyHead) => {
2818
+ writeRawHttpResponse(socket, proxyRes.statusCode || 101, proxyRes.statusMessage || "Switching Protocols", proxyRes.headers);
2819
+ if (head.length > 0) {
2820
+ proxySocket.write(head);
2821
+ }
2822
+ if (proxyHead.length > 0) {
2823
+ socket.write(proxyHead);
2824
+ }
2825
+ socket.on("error", () => proxySocket.destroy());
2826
+ proxySocket.on("error", () => socket.destroy());
2827
+ proxySocket.pipe(socket);
2828
+ socket.pipe(proxySocket);
2829
+ });
2830
+ proxyReq.on("response", (proxyRes) => {
2831
+ writeRawHttpResponse(socket, proxyRes.statusCode || 502, proxyRes.statusMessage || "Bad Gateway", proxyRes.headers);
2832
+ proxyRes.pipe(socket);
2833
+ });
2834
+ proxyReq.on("error", (error) => {
2835
+ writeRawHttpResponse(socket, 502, "Bad Gateway", {
2836
+ connection: "close",
2837
+ "content-type": "text/plain; charset=utf-8",
2838
+ "content-length": Buffer.byteLength(`PM proxy error: ${error.message}`)
2839
+ });
2840
+ socket.end(`PM proxy error: ${error.message}`);
2841
+ });
2842
+ proxyReq.end();
2843
+ });
2844
+ await new Promise((resolve7, reject) => {
2845
+ server.once("error", reject);
2846
+ server.listen(proxy.port, resolvePmProxyHost(proxy), () => resolve7());
2847
+ });
2848
+ return {
2849
+ setTarget(targetUrl) {
2850
+ setResolvedTargets(targetUrl ? [new URL(targetUrl)] : []);
2851
+ },
2852
+ setTargets(targetUrls) {
2853
+ setResolvedTargets(targetUrls.map((targetUrl) => new URL(targetUrl)));
2854
+ },
2855
+ close() {
2856
+ return new Promise((resolve7, reject) => {
2857
+ server.close((error) => {
2858
+ if (error) {
2859
+ reject(error);
2860
+ return;
2861
+ }
2862
+ resolve7();
2863
+ });
2864
+ });
2865
+ }
2866
+ };
2867
+ }
2868
+
2133
2869
  // src/cli/pm/runner.ts
2134
2870
  function writePmLog(stream, message) {
2135
- stream.write(`[elit pm] ${(/* @__PURE__ */ new Date()).toISOString()} ${message}${EOL}`);
2871
+ if (stream.writableEnded || stream.destroyed) {
2872
+ return;
2873
+ }
2874
+ try {
2875
+ stream.write(`[elit pm] ${(/* @__PURE__ */ new Date()).toISOString()} ${message}${EOL}`);
2876
+ } catch (error) {
2877
+ if (error.code !== "ERR_STREAM_WRITE_AFTER_END") {
2878
+ throw error;
2879
+ }
2880
+ }
2136
2881
  }
2137
2882
  function waitForExit(code, signal) {
2138
2883
  if (typeof code === "number") {
@@ -2146,40 +2891,369 @@ function waitForExit(code, signal) {
2146
2891
  async function delay(milliseconds) {
2147
2892
  await new Promise((resolvePromise) => setTimeout(resolvePromise, milliseconds));
2148
2893
  }
2149
- async function waitForProcessTermination(pid, timeoutMs) {
2150
- if (!pid || !isProcessAlive(pid)) {
2151
- return true;
2152
- }
2153
- const deadline = Date.now() + timeoutMs;
2154
- while (Date.now() < deadline) {
2155
- if (!isProcessAlive(pid)) {
2156
- return true;
2157
- }
2158
- await delay(DEFAULT_PM_STOP_POLL_MS);
2159
- }
2160
- return !isProcessAlive(pid);
2894
+ function resolveRunnerPathsFromRecordFile(filePath) {
2895
+ const appsDir = dirname4(filePath);
2896
+ const dataDir = dirname4(appsDir);
2897
+ return {
2898
+ dataDir,
2899
+ appsDir,
2900
+ logsDir: join6(dataDir, "logs"),
2901
+ dumpFile: join6(dataDir, DEFAULT_PM_DUMP_FILE)
2902
+ };
2161
2903
  }
2162
- async function waitForManagedChildExit(child) {
2163
- return await new Promise((resolvePromise) => {
2164
- let resolved = false;
2165
- child.once("error", (error) => {
2166
- if (resolved) {
2167
- return;
2168
- }
2169
- resolved = true;
2170
- resolvePromise({ code: 1, signal: null, error: error instanceof Error ? error.message : String(error) });
2171
- });
2172
- child.once("close", (code, signal) => {
2173
- if (resolved) {
2174
- return;
2175
- }
2176
- resolved = true;
2177
- resolvePromise({ code, signal });
2178
- });
2179
- });
2904
+ function usesPmProxyController(record) {
2905
+ return Boolean(record.proxy) && (record.proxy?.strategy ?? DEFAULT_PM_PROXY_STRATEGY) === "proxy";
2180
2906
  }
2181
- async function createPmWatchController(record, onChange, onError) {
2182
- if (!record.watch || record.watchPaths.length === 0) {
2907
+ function usesPmInheritedListener(record) {
2908
+ return Boolean(record.proxy) && (record.proxy?.strategy ?? DEFAULT_PM_PROXY_STRATEGY) === "inherit";
2909
+ }
2910
+ function isPmProxyOwner(record) {
2911
+ return usesPmProxyController(record) && record.instanceIndex === 1;
2912
+ }
2913
+ function resolvePmProxyTargetUrls(paths, baseName) {
2914
+ 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));
2915
+ }
2916
+ async function createPmInheritedListener(proxy) {
2917
+ const server = createHttpServer();
2918
+ await new Promise((resolvePromise, reject) => {
2919
+ server.once("error", reject);
2920
+ server.listen(proxy.port, resolvePmProxyHost(proxy), () => resolvePromise());
2921
+ });
2922
+ return server;
2923
+ }
2924
+ function createPmChildIpcState(child, sharedListener) {
2925
+ let bootstrapReady = false;
2926
+ let listenerReady = false;
2927
+ let sharedHandleSent = false;
2928
+ const sendSharedHandle = () => {
2929
+ if (!sharedListener || !bootstrapReady || sharedHandleSent || !child.connected) {
2930
+ return;
2931
+ }
2932
+ sharedHandleSent = true;
2933
+ child.send?.({ type: "elit:pm:listen-handle" }, sharedListener);
2934
+ };
2935
+ const onMessage = (message) => {
2936
+ if (!message || typeof message !== "object") {
2937
+ return;
2938
+ }
2939
+ if (message.type === "elit:pm:bootstrap-ready") {
2940
+ bootstrapReady = true;
2941
+ sendSharedHandle();
2942
+ return;
2943
+ }
2944
+ if (message.type === "elit:pm:listener-ready") {
2945
+ listenerReady = true;
2946
+ }
2947
+ };
2948
+ child.on("message", onMessage);
2949
+ return {
2950
+ get bootstrapReady() {
2951
+ return bootstrapReady;
2952
+ },
2953
+ get listenerReady() {
2954
+ return listenerReady;
2955
+ },
2956
+ stop() {
2957
+ child.off("message", onMessage);
2958
+ }
2959
+ };
2960
+ }
2961
+ function buildPmChildEnv(record, command, targetPort) {
2962
+ return {
2963
+ ...process.env,
2964
+ ...record.env,
2965
+ ...command.env,
2966
+ ...usesPmProxyController(record) && targetPort ? {
2967
+ [resolvePmProxyEnvVar(record.proxy)]: String(targetPort),
2968
+ ELIT_PM_PUBLIC_PORT: String(record.proxy.port)
2969
+ } : {},
2970
+ ELIT_PM_NAME: record.name,
2971
+ ELIT_PM_ID: record.id
2972
+ };
2973
+ }
2974
+ function buildPmChildStdio(command, onlineStdinShutdownEnabled) {
2975
+ return [
2976
+ onlineStdinShutdownEnabled ? "pipe" : "ignore",
2977
+ "pipe",
2978
+ "pipe",
2979
+ ...command.ipc ? ["ipc"] : []
2980
+ ];
2981
+ }
2982
+ function createPmReadinessMonitor(record, onReady, onFailure, options) {
2983
+ if (options?.ipcController) {
2984
+ let stopped2 = false;
2985
+ let timer2 = null;
2986
+ let timeoutTimer2 = null;
2987
+ const clearTimers2 = () => {
2988
+ if (timer2) {
2989
+ clearInterval(timer2);
2990
+ timer2 = null;
2991
+ }
2992
+ if (timeoutTimer2) {
2993
+ clearTimeout(timeoutTimer2);
2994
+ timeoutTimer2 = null;
2995
+ }
2996
+ };
2997
+ const host = record.proxy?.host ?? "0.0.0.0";
2998
+ const port = record.proxy?.port ?? 0;
2999
+ timer2 = setInterval(() => {
3000
+ if (stopped2 || !options.ipcController?.listenerReady) {
3001
+ return;
3002
+ }
3003
+ stopped2 = true;
3004
+ clearTimers2();
3005
+ onReady(`shared listener ready on ${host}:${port}`);
3006
+ }, 25);
3007
+ timer2.unref?.();
3008
+ timeoutTimer2 = setTimeout(() => {
3009
+ if (stopped2) {
3010
+ return;
3011
+ }
3012
+ stopped2 = true;
3013
+ clearTimers2();
3014
+ onFailure(`listen timeout reached after ${record.listenTimeout}ms while waiting for shared listener ${host}:${port}`);
3015
+ }, record.listenTimeout);
3016
+ timeoutTimer2.unref?.();
3017
+ return {
3018
+ stop() {
3019
+ stopped2 = true;
3020
+ clearTimers2();
3021
+ }
3022
+ };
3023
+ }
3024
+ if (!record.waitReady || !record.healthCheck) {
3025
+ return {
3026
+ stop() {
3027
+ }
3028
+ };
3029
+ }
3030
+ const healthCheck = record.healthCheck;
3031
+ const pollInterval = Math.max(50, Math.min(healthCheck.interval, 250));
3032
+ let stopped = false;
3033
+ let timer = null;
3034
+ let timeoutTimer = null;
3035
+ let inFlight = false;
3036
+ const clearTimers = () => {
3037
+ if (timer) {
3038
+ clearInterval(timer);
3039
+ timer = null;
3040
+ }
3041
+ if (timeoutTimer) {
3042
+ clearTimeout(timeoutTimer);
3043
+ timeoutTimer = null;
3044
+ }
3045
+ };
3046
+ const runHealthCheck = async () => {
3047
+ if (stopped || inFlight) {
3048
+ return;
3049
+ }
3050
+ inFlight = true;
3051
+ const controller = new AbortController();
3052
+ const timeoutId = setTimeout(() => controller.abort(), healthCheck.timeout);
3053
+ timeoutId.unref?.();
3054
+ try {
3055
+ const response = await fetch(healthCheck.url, {
3056
+ method: "GET",
3057
+ signal: controller.signal
3058
+ });
3059
+ if (stopped) {
3060
+ return;
3061
+ }
3062
+ if (!response.ok) {
3063
+ throw new Error(`health check returned ${response.status}`);
3064
+ }
3065
+ stopped = true;
3066
+ clearTimers();
3067
+ onReady(`readiness check passed: ${healthCheck.url}`);
3068
+ } catch {
3069
+ } finally {
3070
+ clearTimeout(timeoutId);
3071
+ inFlight = false;
3072
+ }
3073
+ };
3074
+ timeoutTimer = setTimeout(() => {
3075
+ if (stopped) {
3076
+ return;
3077
+ }
3078
+ stopped = true;
3079
+ clearTimers();
3080
+ onFailure(`listen timeout reached after ${record.listenTimeout}ms while waiting for ${healthCheck.url}`);
3081
+ }, record.listenTimeout);
3082
+ timeoutTimer.unref?.();
3083
+ void runHealthCheck();
3084
+ timer = setInterval(() => {
3085
+ void runHealthCheck();
3086
+ }, pollInterval);
3087
+ timer.unref?.();
3088
+ return {
3089
+ stop() {
3090
+ stopped = true;
3091
+ clearTimers();
3092
+ }
3093
+ };
3094
+ }
3095
+ function resolvePmStopTimeout(record) {
3096
+ if (isPmOnlineWapkRecord(record)) {
3097
+ return Math.max(record.killTimeout, PM_WAPK_ONLINE_SHUTDOWN_TIMEOUT_MS);
3098
+ }
3099
+ return record.killTimeout;
3100
+ }
3101
+ function supportsPmProxyReload(record) {
3102
+ return Boolean(record.proxy) && record.instances === 1;
3103
+ }
3104
+ function buildPmMonitorRecord(record, targetPort) {
3105
+ if (!record.proxy || !targetPort) {
3106
+ return record;
3107
+ }
3108
+ const targetHost = resolvePmProxyTargetHost(record.proxy);
3109
+ return {
3110
+ ...record,
3111
+ proxyTargetPort: targetPort,
3112
+ healthCheck: record.healthCheck ? {
3113
+ ...record.healthCheck,
3114
+ url: rewritePmProxyHealthCheckUrl(record.healthCheck.url, targetHost, targetPort)
3115
+ } : void 0
3116
+ };
3117
+ }
3118
+ async function waitForProcessTermination(pid, timeoutMs) {
3119
+ if (!pid || !isProcessAlive(pid)) {
3120
+ return true;
3121
+ }
3122
+ const deadline = Date.now() + timeoutMs;
3123
+ while (Date.now() < deadline) {
3124
+ if (!isProcessAlive(pid)) {
3125
+ return true;
3126
+ }
3127
+ await delay(DEFAULT_PM_STOP_POLL_MS);
3128
+ }
3129
+ return !isProcessAlive(pid);
3130
+ }
3131
+ async function waitForManagedChildExit(child) {
3132
+ return await new Promise((resolvePromise) => {
3133
+ let resolved = false;
3134
+ child.once("error", (error) => {
3135
+ if (resolved) {
3136
+ return;
3137
+ }
3138
+ resolved = true;
3139
+ resolvePromise({ code: 1, signal: null, error: error instanceof Error ? error.message : String(error) });
3140
+ });
3141
+ child.once("close", (code, signal) => {
3142
+ if (resolved) {
3143
+ return;
3144
+ }
3145
+ resolved = true;
3146
+ resolvePromise({ code, signal });
3147
+ });
3148
+ });
3149
+ }
3150
+ async function createPmChildControllers(record, child, stdoutLog, stderrLog, requestPlannedRestart, onReady, options) {
3151
+ let healthMonitor = {
3152
+ stop() {
3153
+ }
3154
+ };
3155
+ let memoryMonitor = {
3156
+ stop() {
3157
+ }
3158
+ };
3159
+ let scheduleMonitor = {
3160
+ stop() {
3161
+ }
3162
+ };
3163
+ const startHealthMonitor = () => {
3164
+ healthMonitor = createPmHealthMonitor(
3165
+ record,
3166
+ (message) => requestPlannedRestart("health", message),
3167
+ (message) => writePmLog(stdoutLog, message)
3168
+ );
3169
+ };
3170
+ const needsReadySignal = Boolean(options?.ipcController) || record.waitReady;
3171
+ const readinessMonitor = options?.ready || !needsReadySignal ? { stop() {
3172
+ } } : createPmReadinessMonitor(
3173
+ record,
3174
+ (message) => {
3175
+ onReady(message);
3176
+ startHealthMonitor();
3177
+ },
3178
+ (message) => requestPlannedRestart("startup", message),
3179
+ { ipcController: options?.ipcController }
3180
+ );
3181
+ const watchController = await createPmWatchController(
3182
+ record,
3183
+ (changedPath) => requestPlannedRestart("watch", changedPath),
3184
+ (message) => writePmLog(stderrLog, `watch error: ${message}`)
3185
+ );
3186
+ memoryMonitor = createPmMemoryMonitor(
3187
+ record,
3188
+ child.pid,
3189
+ (kind, message) => requestPlannedRestart(kind, message)
3190
+ );
3191
+ scheduleMonitor = createPmScheduleMonitor(
3192
+ record,
3193
+ (message) => requestPlannedRestart("cron", message),
3194
+ (message) => writePmLog(stdoutLog, message)
3195
+ );
3196
+ if (options?.ready || !needsReadySignal) {
3197
+ startHealthMonitor();
3198
+ }
3199
+ return {
3200
+ async stop() {
3201
+ await watchController.close();
3202
+ readinessMonitor.stop();
3203
+ healthMonitor.stop();
3204
+ memoryMonitor.stop();
3205
+ scheduleMonitor.stop();
3206
+ }
3207
+ };
3208
+ }
3209
+ async function waitForPmChildReady(record, child, ipcController) {
3210
+ if (!ipcController && (!record.waitReady || !record.healthCheck)) {
3211
+ return { ready: true };
3212
+ }
3213
+ let readyMessage;
3214
+ let failureMessage;
3215
+ let exitResult;
3216
+ const readinessMonitor = createPmReadinessMonitor(
3217
+ record,
3218
+ (message) => {
3219
+ readyMessage = message;
3220
+ },
3221
+ (message) => {
3222
+ failureMessage = message;
3223
+ },
3224
+ { ipcController }
3225
+ );
3226
+ void waitForManagedChildExit(child).then((result) => {
3227
+ exitResult = result;
3228
+ });
3229
+ while (!readyMessage && !failureMessage && !exitResult) {
3230
+ await delay(25);
3231
+ }
3232
+ readinessMonitor.stop();
3233
+ if (readyMessage) {
3234
+ return { ready: true, message: readyMessage };
3235
+ }
3236
+ return {
3237
+ ready: false,
3238
+ message: failureMessage,
3239
+ exitResult
3240
+ };
3241
+ }
3242
+ async function stopProxyManagedChild(child, record, stderrLog) {
3243
+ if (!child.pid || !isProcessAlive(child.pid)) {
3244
+ return;
3245
+ }
3246
+ terminateProcessTree(child.pid);
3247
+ const stopTimeout = resolvePmStopTimeout(record);
3248
+ const stopped = await waitForProcessTermination(child.pid, stopTimeout);
3249
+ if (!stopped && child.pid && isProcessAlive(child.pid)) {
3250
+ writePmLog(stderrLog, `proxy handoff shutdown timed out after ${stopTimeout}ms; forcing process termination`);
3251
+ terminateProcessTree(child.pid, { force: true });
3252
+ await waitForProcessTermination(child.pid, DEFAULT_PM_STOP_POLL_MS);
3253
+ }
3254
+ }
3255
+ async function createPmWatchController(record, onChange, onError) {
3256
+ if (!record.watch || record.watchPaths.length === 0) {
2183
3257
  return {
2184
3258
  async close() {
2185
3259
  }
@@ -2241,11 +3315,17 @@ function createPmHealthMonitor(record, onFailure, onLog) {
2241
3315
  method: "GET",
2242
3316
  signal: controller.signal
2243
3317
  });
3318
+ if (stopped) {
3319
+ return;
3320
+ }
2244
3321
  if (!response.ok) {
2245
3322
  throw new Error(`health check returned ${response.status}`);
2246
3323
  }
2247
3324
  failureCount = 0;
2248
3325
  } catch (error) {
3326
+ if (stopped) {
3327
+ return;
3328
+ }
2249
3329
  failureCount += 1;
2250
3330
  const message = error instanceof Error ? error.message : String(error);
2251
3331
  onLog(`health check failed (${failureCount}/${healthCheck.maxFailures}): ${message}`);
@@ -2280,6 +3360,90 @@ function createPmHealthMonitor(record, onFailure, onLog) {
2280
3360
  }
2281
3361
  };
2282
3362
  }
3363
+ function createPmMemoryMonitor(record, pid, onFailure) {
3364
+ if (!record.maxMemoryBytes || !pid) {
3365
+ return {
3366
+ stop() {
3367
+ }
3368
+ };
3369
+ }
3370
+ const memoryLimit = record.maxMemoryBytes;
3371
+ let stopped = false;
3372
+ const timer = setInterval(() => {
3373
+ if (stopped) {
3374
+ return;
3375
+ }
3376
+ const memoryRssBytes = samplePmProcessMetrics(pid).memoryRssBytes;
3377
+ if (memoryRssBytes === void 0 || memoryRssBytes <= memoryLimit) {
3378
+ return;
3379
+ }
3380
+ stopped = true;
3381
+ onFailure(record.memoryAction === "stop" ? "memory-stop" : "memory", `memory usage ${memoryRssBytes} exceeded limit ${memoryLimit}`);
3382
+ }, DEFAULT_PM_MEMORY_CHECK_INTERVAL);
3383
+ timer.unref?.();
3384
+ return {
3385
+ stop() {
3386
+ stopped = true;
3387
+ clearInterval(timer);
3388
+ }
3389
+ };
3390
+ }
3391
+ function createPmScheduleMonitor(record, onTrigger, onLog) {
3392
+ if (!record.cronRestart) {
3393
+ return {
3394
+ stop() {
3395
+ }
3396
+ };
3397
+ }
3398
+ const schedule = parsePmRestartSchedule(record.cronRestart, "pm cronRestart");
3399
+ let stopped = false;
3400
+ let timer = null;
3401
+ const armTimer = (from = /* @__PURE__ */ new Date()) => {
3402
+ const nextOccurrence = resolveNextPmScheduleOccurrence(schedule, from);
3403
+ if (!nextOccurrence) {
3404
+ onLog(`schedule has no next occurrence: ${record.cronRestart}`);
3405
+ return;
3406
+ }
3407
+ const delayMs = Math.max(0, nextOccurrence.getTime() - Date.now());
3408
+ timer = setTimeout(() => {
3409
+ timer = null;
3410
+ if (stopped) {
3411
+ return;
3412
+ }
3413
+ onTrigger(`restart schedule matched: ${record.cronRestart}`);
3414
+ }, delayMs);
3415
+ timer.unref?.();
3416
+ };
3417
+ armTimer();
3418
+ return {
3419
+ stop() {
3420
+ stopped = true;
3421
+ if (timer) {
3422
+ clearTimeout(timer);
3423
+ timer = null;
3424
+ }
3425
+ }
3426
+ };
3427
+ }
3428
+ function resolvePmRestartDelay(record, restartCount, shouldApplyBackoff) {
3429
+ if (!shouldApplyBackoff || !record.expBackoffRestartDelay) {
3430
+ return record.restartDelay;
3431
+ }
3432
+ const exponent = Math.max(0, restartCount - 1);
3433
+ return Math.min(record.expBackoffRestartDelay * 2 ** exponent, record.expBackoffRestartMaxDelay ?? DEFAULT_PM_EXP_BACKOFF_MAX_DELAY);
3434
+ }
3435
+ function resolvePmRestartCountBase(record, wasStable, restartKind) {
3436
+ if (wasStable) {
3437
+ return 0;
3438
+ }
3439
+ if (record.restartWindow && record.lastRestartAt) {
3440
+ const lastRestartTime = Date.parse(record.lastRestartAt);
3441
+ if (!Number.isNaN(lastRestartTime) && Date.now() - lastRestartTime > record.restartWindow) {
3442
+ return 0;
3443
+ }
3444
+ }
3445
+ return restartKind === "watch" ? record.restartCount ?? 0 : record.restartCount ?? 0;
3446
+ }
2283
3447
  function readPlannedRestartRequest(state) {
2284
3448
  return state.request;
2285
3449
  }
@@ -2289,10 +3453,20 @@ async function runManagedProcessLoop(filePath, initialRecord) {
2289
3453
  let activeChildStopTimer = null;
2290
3454
  let stopRequested = false;
2291
3455
  const restartState = { request: null };
2292
- mkdirSync3(dirname3(initialRecord.logFiles.out), { recursive: true });
2293
- mkdirSync3(dirname3(initialRecord.logFiles.err), { recursive: true });
3456
+ mkdirSync3(dirname4(initialRecord.logFiles.out), { recursive: true });
3457
+ mkdirSync3(dirname4(initialRecord.logFiles.err), { recursive: true });
2294
3458
  const stdoutLog = createWriteStream(initialRecord.logFiles.out, { flags: "a" });
2295
3459
  const stderrLog = createWriteStream(initialRecord.logFiles.err, { flags: "a" });
3460
+ let proxyController = null;
3461
+ let proxyTargetSyncTimer = null;
3462
+ let inheritedListener = null;
3463
+ const runnerPaths = resolveRunnerPathsFromRecordFile(filePath);
3464
+ const syncOwnedProxyTargets = (baseName) => {
3465
+ if (!proxyController) {
3466
+ return;
3467
+ }
3468
+ proxyController.setTargets(resolvePmProxyTargetUrls(runnerPaths, baseName));
3469
+ };
2296
3470
  const persist = (mutator) => {
2297
3471
  const current = readLatestPmRecord(filePath, record);
2298
3472
  record = mutator(current);
@@ -2305,26 +3479,30 @@ async function runManagedProcessLoop(filePath, initialRecord) {
2305
3479
  activeChildStopTimer = null;
2306
3480
  }
2307
3481
  };
3482
+ const scheduleForcedActiveChildStop = (timeoutMs, reason) => {
3483
+ if (!activeChild?.pid || process.platform === "win32") {
3484
+ return;
3485
+ }
3486
+ clearActiveChildStopTimer();
3487
+ activeChildStopTimer = setTimeout(() => {
3488
+ if (activeChild?.pid && isProcessAlive(activeChild.pid)) {
3489
+ writePmLog(stderrLog, `${reason} after ${timeoutMs}ms; forcing process termination`);
3490
+ terminateProcessTree(activeChild.pid, { force: true });
3491
+ }
3492
+ }, timeoutMs);
3493
+ activeChildStopTimer.unref?.();
3494
+ };
2308
3495
  const stopActiveChild = () => {
2309
3496
  if (!activeChild?.pid || !isProcessAlive(activeChild.pid)) {
2310
3497
  return;
2311
3498
  }
2312
3499
  const current = readLatestPmRecord(filePath, record);
3500
+ const stopTimeout = resolvePmStopTimeout(current);
2313
3501
  if (isPmOnlineWapkRecord(current) && activeChild.stdin && !activeChild.stdin.destroyed && activeChild.stdin.writable) {
2314
3502
  try {
2315
3503
  activeChild.stdin.end(`${PM_WAPK_ONLINE_SHUTDOWN_COMMAND}
2316
3504
  `);
2317
- clearActiveChildStopTimer();
2318
- activeChildStopTimer = setTimeout(() => {
2319
- if (activeChild?.pid && isProcessAlive(activeChild.pid)) {
2320
- writePmLog(
2321
- stderrLog,
2322
- `graceful WAPK online shutdown timed out after ${PM_WAPK_ONLINE_SHUTDOWN_TIMEOUT_MS}ms; forcing process termination`
2323
- );
2324
- terminateProcessTree(activeChild.pid);
2325
- }
2326
- }, PM_WAPK_ONLINE_SHUTDOWN_TIMEOUT_MS);
2327
- activeChildStopTimer.unref?.();
3505
+ scheduleForcedActiveChildStop(stopTimeout, "graceful WAPK online shutdown timed out");
2328
3506
  return;
2329
3507
  } catch (error) {
2330
3508
  const message = error instanceof Error ? error.message : String(error);
@@ -2332,6 +3510,7 @@ async function runManagedProcessLoop(filePath, initialRecord) {
2332
3510
  }
2333
3511
  }
2334
3512
  terminateProcessTree(activeChild.pid);
3513
+ scheduleForcedActiveChildStop(stopTimeout, "graceful shutdown timed out");
2335
3514
  };
2336
3515
  const requestManagedStop = (reason) => {
2337
3516
  if (stopRequested) {
@@ -2369,6 +3548,17 @@ async function runManagedProcessLoop(filePath, initialRecord) {
2369
3548
  let command;
2370
3549
  try {
2371
3550
  command = buildPmCommand(latest);
3551
+ if (latest.proxy && isPmProxyOwner(latest) && !proxyController) {
3552
+ proxyController = await createPmProxyController(latest.proxy);
3553
+ syncOwnedProxyTargets(latest.baseName);
3554
+ if (!proxyTargetSyncTimer) {
3555
+ proxyTargetSyncTimer = setInterval(() => syncOwnedProxyTargets(latest.baseName), 50);
3556
+ proxyTargetSyncTimer.unref?.();
3557
+ }
3558
+ }
3559
+ if (latest.proxy && usesPmInheritedListener(latest) && !inheritedListener) {
3560
+ inheritedListener = await createPmInheritedListener(latest.proxy);
3561
+ }
2372
3562
  } catch (error) {
2373
3563
  const message = error instanceof Error ? error.message : String(error);
2374
3564
  writePmLog(stderrLog, message);
@@ -2378,25 +3568,24 @@ async function runManagedProcessLoop(filePath, initialRecord) {
2378
3568
  error: message,
2379
3569
  runnerPid: void 0,
2380
3570
  childPid: void 0,
3571
+ proxyTargetPort: void 0,
3572
+ proxyReadyAt: void 0,
2381
3573
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
2382
3574
  }));
2383
3575
  return;
2384
3576
  }
2385
3577
  const onlineStdinShutdownEnabled = isPmOnlineWapkRecord(latest);
2386
- const child = spawn2(command.command, command.args, {
3578
+ const initialTargetPort = usesPmProxyController(latest) ? await allocatePmProxyTargetPort(resolvePmProxyTargetHost(latest.proxy)) : void 0;
3579
+ const monitorRecord = buildPmMonitorRecord(latest, initialTargetPort);
3580
+ let child = spawn2(command.command, command.args, {
2387
3581
  cwd: latest.cwd,
2388
- env: {
2389
- ...process.env,
2390
- ...latest.env,
2391
- ...command.env,
2392
- ELIT_PM_NAME: latest.name,
2393
- ELIT_PM_ID: latest.id
2394
- },
2395
- stdio: [onlineStdinShutdownEnabled ? "pipe" : "ignore", "pipe", "pipe"],
3582
+ env: buildPmChildEnv(latest, command, initialTargetPort),
3583
+ stdio: buildPmChildStdio(command, onlineStdinShutdownEnabled),
2396
3584
  windowsHide: true,
2397
3585
  shell: command.shell
2398
3586
  });
2399
- const childStartedAt = Date.now();
3587
+ let childStartedAt = Date.now();
3588
+ let childIpcState = command.ipc ? createPmChildIpcState(child, inheritedListener) : void 0;
2400
3589
  activeChild = child;
2401
3590
  if (child.stdout) {
2402
3591
  child.stdout.pipe(stdoutLog, { end: false });
@@ -2404,26 +3593,38 @@ async function runManagedProcessLoop(filePath, initialRecord) {
2404
3593
  if (child.stderr) {
2405
3594
  child.stderr.pipe(stderrLog, { end: false });
2406
3595
  }
3596
+ let childWaitState = { settled: false, result: void 0 };
3597
+ void waitForManagedChildExit(child).then((result) => {
3598
+ childWaitState.result = result;
3599
+ childWaitState.settled = true;
3600
+ });
2407
3601
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
3602
+ const waitingForReady = latest.waitReady || Boolean(childIpcState);
2408
3603
  persist((current2) => ({
2409
3604
  ...current2,
2410
- status: "online",
3605
+ status: waitingForReady ? "starting" : "online",
2411
3606
  commandPreview: command.preview,
2412
3607
  runtime: command.runtime ?? current2.runtime,
2413
3608
  runnerPid: process.pid,
2414
3609
  childPid: child.pid,
3610
+ proxyTargetPort: initialTargetPort,
3611
+ proxyReadyAt: !waitingForReady && usesPmProxyController(latest) ? startedAt : void 0,
2415
3612
  startedAt,
2416
3613
  stoppedAt: void 0,
3614
+ reloadRequestedAt: void 0,
2417
3615
  error: void 0,
2418
3616
  updatedAt: startedAt
2419
3617
  }));
2420
3618
  writePmLog(stdoutLog, `started ${command.preview}${child.pid ? ` (pid ${child.pid})` : ""}`);
3619
+ if (isPmProxyOwner(latest) && !waitingForReady) {
3620
+ syncOwnedProxyTargets(latest.baseName);
3621
+ }
2421
3622
  const requestPlannedRestart = (kind, detail) => {
2422
3623
  if (stopRequested || restartState.request) {
2423
3624
  return;
2424
3625
  }
2425
3626
  restartState.request = { kind, detail };
2426
- writePmLog(kind === "health" ? stderrLog : stdoutLog, `${kind} restart requested: ${detail}`);
3627
+ writePmLog(kind === "watch" || kind === "cron" ? stdoutLog : stderrLog, `${kind} restart requested: ${detail}`);
2427
3628
  persist((current2) => ({
2428
3629
  ...current2,
2429
3630
  status: "restarting",
@@ -2431,27 +3632,124 @@ async function runManagedProcessLoop(filePath, initialRecord) {
2431
3632
  }));
2432
3633
  stopActiveChild();
2433
3634
  };
2434
- const watchController = await createPmWatchController(
2435
- latest,
2436
- (changedPath) => requestPlannedRestart("watch", changedPath),
2437
- (message) => writePmLog(stderrLog, `watch error: ${message}`)
2438
- );
2439
- const healthMonitor = createPmHealthMonitor(
2440
- latest,
2441
- (message) => requestPlannedRestart("health", message),
2442
- (message) => writePmLog(stdoutLog, message)
3635
+ let controllers = await createPmChildControllers(
3636
+ monitorRecord,
3637
+ child,
3638
+ stdoutLog,
3639
+ stderrLog,
3640
+ requestPlannedRestart,
3641
+ (message) => {
3642
+ const readyAt = (/* @__PURE__ */ new Date()).toISOString();
3643
+ if (isPmProxyOwner(latest)) {
3644
+ syncOwnedProxyTargets(latest.baseName);
3645
+ }
3646
+ persist((current2) => ({
3647
+ ...current2,
3648
+ status: "online",
3649
+ proxyTargetPort: initialTargetPort,
3650
+ proxyReadyAt: readyAt,
3651
+ updatedAt: readyAt
3652
+ }));
3653
+ writePmLog(stdoutLog, message);
3654
+ },
3655
+ { ready: !waitingForReady, ipcController: childIpcState }
2443
3656
  );
2444
- const desiredStatePoller = setInterval(() => {
3657
+ let handledReloadAt = latest.reloadRequestedAt;
3658
+ while (!childWaitState.settled) {
2445
3659
  const latestRecord = readLatestPmRecord(filePath, record);
2446
- if (!stopRequested && latestRecord.desiredState === "stopped") {
3660
+ if (latestRecord.desiredState === "stopped" && !stopRequested) {
2447
3661
  requestManagedStop("stop requested by PM control state");
2448
3662
  }
2449
- }, DEFAULT_PM_STOP_POLL_MS);
2450
- desiredStatePoller.unref?.();
2451
- const exitResult = await waitForManagedChildExit(child);
2452
- clearInterval(desiredStatePoller);
2453
- await watchController.close();
2454
- healthMonitor.stop();
3663
+ const reloadRequestedAt = latestRecord.reloadRequestedAt;
3664
+ if (!stopRequested && supportsPmProxyReload(latestRecord) && reloadRequestedAt && reloadRequestedAt !== handledReloadAt && latestRecord.proxy) {
3665
+ handledReloadAt = reloadRequestedAt;
3666
+ const replacementTargetPort = usesPmProxyController(latestRecord) ? await allocatePmProxyTargetPort(resolvePmProxyTargetHost(latestRecord.proxy)) : void 0;
3667
+ const replacementMonitorRecord = buildPmMonitorRecord(latestRecord, replacementTargetPort);
3668
+ const replacementChild = spawn2(command.command, command.args, {
3669
+ cwd: latestRecord.cwd,
3670
+ env: buildPmChildEnv(latestRecord, command, replacementTargetPort),
3671
+ stdio: buildPmChildStdio(command, onlineStdinShutdownEnabled),
3672
+ windowsHide: true,
3673
+ shell: command.shell
3674
+ });
3675
+ const replacementIpcState = command.ipc ? createPmChildIpcState(replacementChild, inheritedListener) : void 0;
3676
+ if (replacementChild.stdout) {
3677
+ replacementChild.stdout.pipe(stdoutLog, { end: false });
3678
+ }
3679
+ if (replacementChild.stderr) {
3680
+ replacementChild.stderr.pipe(stderrLog, { end: false });
3681
+ }
3682
+ writePmLog(stdoutLog, `starting ${usesPmInheritedListener(latestRecord) ? "shared-listener" : "proxy handoff"} replacement${replacementChild.pid ? ` (pid ${replacementChild.pid})` : ""}`);
3683
+ const readyResult = await waitForPmChildReady(replacementMonitorRecord, replacementChild, replacementIpcState);
3684
+ if (!readyResult.ready) {
3685
+ writePmLog(stderrLog, readyResult.message ?? "replacement exited before becoming ready");
3686
+ replacementIpcState?.stop();
3687
+ await stopProxyManagedChild(replacementChild, latestRecord, stderrLog);
3688
+ persist((current2) => ({
3689
+ ...current2,
3690
+ status: "online",
3691
+ proxyReadyAt: current2.proxyReadyAt,
3692
+ reloadRequestedAt: void 0,
3693
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
3694
+ }));
3695
+ continue;
3696
+ }
3697
+ const previousChild = child;
3698
+ const previousControllers = controllers;
3699
+ const previousIpcState = childIpcState;
3700
+ const handoffAt = (/* @__PURE__ */ new Date()).toISOString();
3701
+ if (usesPmProxyController(latestRecord) && replacementTargetPort) {
3702
+ proxyController?.setTarget(buildPmProxyTargetUrl(latestRecord.proxy, replacementTargetPort));
3703
+ }
3704
+ persist((current2) => ({
3705
+ ...current2,
3706
+ status: "online",
3707
+ childPid: replacementChild.pid,
3708
+ proxyTargetPort: replacementTargetPort,
3709
+ proxyReadyAt: handoffAt,
3710
+ startedAt: handoffAt,
3711
+ reloadRequestedAt: void 0,
3712
+ error: void 0,
3713
+ updatedAt: handoffAt
3714
+ }));
3715
+ if (readyResult.message) {
3716
+ writePmLog(stdoutLog, readyResult.message);
3717
+ }
3718
+ writePmLog(stdoutLog, `${usesPmInheritedListener(latestRecord) ? "shared listener" : "proxy handoff"} activated on ${latestRecord.proxy.host ?? "0.0.0.0"}:${latestRecord.proxy.port}`);
3719
+ child = replacementChild;
3720
+ childIpcState = replacementIpcState;
3721
+ childStartedAt = Date.now();
3722
+ activeChild = replacementChild;
3723
+ childWaitState = { settled: false, result: void 0 };
3724
+ void waitForManagedChildExit(replacementChild).then((result) => {
3725
+ childWaitState.result = result;
3726
+ childWaitState.settled = true;
3727
+ });
3728
+ controllers = await createPmChildControllers(
3729
+ replacementMonitorRecord,
3730
+ replacementChild,
3731
+ stdoutLog,
3732
+ stderrLog,
3733
+ requestPlannedRestart,
3734
+ () => {
3735
+ },
3736
+ { ready: true, ipcController: replacementIpcState }
3737
+ );
3738
+ await delay(250);
3739
+ await previousControllers.stop();
3740
+ previousIpcState?.stop();
3741
+ await stopProxyManagedChild(previousChild, latestRecord, stderrLog);
3742
+ clearActiveChildStopTimer();
3743
+ if (isPmProxyOwner(latestRecord)) {
3744
+ syncOwnedProxyTargets(latestRecord.baseName);
3745
+ }
3746
+ continue;
3747
+ }
3748
+ await delay(25);
3749
+ }
3750
+ const exitResult = childWaitState.result;
3751
+ await controllers.stop();
3752
+ childIpcState?.stop();
2455
3753
  clearActiveChildStopTimer();
2456
3754
  activeChild = null;
2457
3755
  const exitCode = waitForExit(exitResult.code, exitResult.signal);
@@ -2467,56 +3765,77 @@ async function runManagedProcessLoop(filePath, initialRecord) {
2467
3765
  if (stopRequested || current.desiredState === "stopped") {
2468
3766
  break;
2469
3767
  }
2470
- const shouldRestartForExit = plannedRestart ? true : current.restartPolicy === "always" ? true : current.restartPolicy === "on-failure" ? exitCode !== 0 || Boolean(exitResult.error) : false;
3768
+ const shouldRestartForExit = plannedRestart?.kind === "memory-stop" ? false : plannedRestart ? true : current.restartPolicy === "always" ? true : current.restartPolicy === "on-failure" ? exitCode !== 0 || Boolean(exitResult.error) : false;
2471
3769
  if (!shouldRestartForExit) {
2472
3770
  persist((latestRecord) => ({
2473
3771
  ...latestRecord,
2474
- status: exitCode === 0 && !exitResult.error ? "exited" : "errored",
3772
+ status: plannedRestart?.kind === "memory-stop" ? "errored" : exitCode === 0 && !exitResult.error ? "exited" : "errored",
2475
3773
  childPid: void 0,
3774
+ proxyTargetPort: void 0,
3775
+ proxyReadyAt: void 0,
2476
3776
  runnerPid: void 0,
2477
3777
  lastExitCode: exitCode,
2478
- error: exitCode === 0 && !exitResult.error ? void 0 : exitResult.error ?? `Process exited with code ${exitCode}.`,
3778
+ reloadRequestedAt: void 0,
3779
+ error: plannedRestart?.kind === "memory-stop" ? plannedRestart.detail : exitCode === 0 && !exitResult.error ? void 0 : exitResult.error ?? `Process exited with code ${exitCode}.`,
2479
3780
  stoppedAt: (/* @__PURE__ */ new Date()).toISOString(),
2480
3781
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
2481
3782
  }));
3783
+ if (isPmProxyOwner(current)) {
3784
+ syncOwnedProxyTargets(current.baseName);
3785
+ }
2482
3786
  return;
2483
3787
  }
2484
3788
  const shouldCountRestart = plannedRestart?.kind !== "watch";
2485
- const baseRestartCount = wasStable ? 0 : current.restartCount ?? 0;
3789
+ const baseRestartCount = resolvePmRestartCountBase(current, wasStable, plannedRestart?.kind);
2486
3790
  const nextRestartCount = shouldCountRestart ? baseRestartCount + 1 : current.restartCount ?? 0;
2487
3791
  if (nextRestartCount > current.maxRestarts) {
2488
3792
  persist((latestRecord) => ({
2489
3793
  ...latestRecord,
2490
3794
  status: "errored",
2491
3795
  childPid: void 0,
3796
+ proxyTargetPort: void 0,
3797
+ proxyReadyAt: void 0,
2492
3798
  runnerPid: void 0,
2493
3799
  restartCount: nextRestartCount,
3800
+ lastRestartAt: (/* @__PURE__ */ new Date()).toISOString(),
2494
3801
  lastExitCode: exitCode,
3802
+ reloadRequestedAt: void 0,
2495
3803
  error: plannedRestart ? `Reached max restart attempts (${current.maxRestarts}) after ${plannedRestart.kind} restart requests.` : `Reached max restart attempts (${current.maxRestarts}).`,
2496
3804
  stoppedAt: (/* @__PURE__ */ new Date()).toISOString(),
2497
3805
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
2498
3806
  }));
2499
3807
  writePmLog(stderrLog, `max restart attempts reached (${current.maxRestarts})`);
3808
+ if (isPmProxyOwner(current)) {
3809
+ syncOwnedProxyTargets(current.baseName);
3810
+ }
2500
3811
  return;
2501
3812
  }
2502
3813
  persist((latestRecord) => ({
2503
3814
  ...latestRecord,
2504
3815
  status: "restarting",
2505
3816
  childPid: void 0,
3817
+ proxyTargetPort: void 0,
3818
+ proxyReadyAt: void 0,
2506
3819
  lastExitCode: exitCode,
2507
3820
  restartCount: nextRestartCount,
3821
+ lastRestartAt: (/* @__PURE__ */ new Date()).toISOString(),
3822
+ reloadRequestedAt: void 0,
2508
3823
  error: void 0,
2509
3824
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
2510
3825
  }));
3826
+ if (isPmProxyOwner(current)) {
3827
+ syncOwnedProxyTargets(current.baseName);
3828
+ }
3829
+ const resolvedRestartDelay = resolvePmRestartDelay(current, nextRestartCount, shouldCountRestart && !wasStable && plannedRestart?.kind !== "watch");
2511
3830
  if (plannedRestart) {
2512
3831
  writePmLog(
2513
- plannedRestart.kind === "health" ? stderrLog : stdoutLog,
2514
- `restarting in ${current.restartDelay}ms after ${plannedRestart.kind}: ${plannedRestart.detail}`
3832
+ plannedRestart.kind === "health" || plannedRestart.kind === "memory" || plannedRestart.kind === "memory-stop" || plannedRestart.kind === "startup" ? stderrLog : stdoutLog,
3833
+ `restarting in ${resolvedRestartDelay}ms after ${plannedRestart.kind}: ${plannedRestart.detail}`
2515
3834
  );
2516
3835
  } else {
2517
- writePmLog(stdoutLog, `restarting in ${current.restartDelay}ms`);
3836
+ writePmLog(stdoutLog, `restarting in ${resolvedRestartDelay}ms`);
2518
3837
  }
2519
- await delay(current.restartDelay);
3838
+ await delay(resolvedRestartDelay);
2520
3839
  }
2521
3840
  } finally {
2522
3841
  stopRequested = true;
@@ -2529,11 +3848,26 @@ async function runManagedProcessLoop(filePath, initialRecord) {
2529
3848
  status: finalRecord.status === "errored" ? "errored" : finalRecord.status === "exited" ? "exited" : "stopped",
2530
3849
  runnerPid: void 0,
2531
3850
  childPid: void 0,
3851
+ proxyTargetPort: void 0,
3852
+ proxyReadyAt: void 0,
3853
+ reloadRequestedAt: void 0,
2532
3854
  stoppedAt: (/* @__PURE__ */ new Date()).toISOString(),
2533
3855
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
2534
3856
  });
2535
3857
  process.off("SIGINT", handleStopSignal);
2536
3858
  process.off("SIGTERM", handleStopSignal);
3859
+ if (proxyTargetSyncTimer) {
3860
+ clearInterval(proxyTargetSyncTimer);
3861
+ proxyTargetSyncTimer = null;
3862
+ }
3863
+ if (proxyController) {
3864
+ await proxyController.close().catch(() => void 0);
3865
+ proxyController = null;
3866
+ }
3867
+ if (inheritedListener) {
3868
+ await new Promise((resolvePromise) => inheritedListener?.close(() => resolvePromise()));
3869
+ inheritedListener = null;
3870
+ }
2537
3871
  await new Promise((resolvePromise) => stdoutLog.end(resolvePromise));
2538
3872
  await new Promise((resolvePromise) => stderrLog.end(resolvePromise));
2539
3873
  }
@@ -2558,7 +3892,7 @@ function parseRunnerArgs(args) {
2558
3892
  throw new Error("Usage: elit pm __run --data-dir <dir> --id <name>");
2559
3893
  }
2560
3894
  return {
2561
- dataDir: resolve5(dataDir),
3895
+ dataDir: resolve6(dataDir),
2562
3896
  id
2563
3897
  };
2564
3898
  }
@@ -2587,23 +3921,26 @@ async function stopPmMatches(matches) {
2587
3921
  stoppedAt: (/* @__PURE__ */ new Date()).toISOString()
2588
3922
  };
2589
3923
  writePmRecord(match.filePath, updated);
2590
- const runnerStopped = await waitForProcessTermination(match.record.runnerPid, DEFAULT_PM_STOP_GRACE_PERIOD_MS);
3924
+ const stopTimeout = resolvePmStopTimeout(match.record);
3925
+ const runnerStopped = await waitForProcessTermination(match.record.runnerPid, stopTimeout);
2591
3926
  const childStopped = await waitForProcessTermination(
2592
3927
  match.record.childPid,
2593
- runnerStopped ? DEFAULT_PM_STOP_POLL_MS : DEFAULT_PM_STOP_GRACE_PERIOD_MS
3928
+ runnerStopped ? DEFAULT_PM_STOP_POLL_MS : stopTimeout
2594
3929
  );
2595
3930
  if (!runnerStopped && match.record.runnerPid && isProcessAlive(match.record.runnerPid)) {
2596
- terminateProcessTree(match.record.runnerPid);
3931
+ terminateProcessTree(match.record.runnerPid, { force: true });
2597
3932
  await waitForProcessTermination(match.record.runnerPid, DEFAULT_PM_STOP_POLL_MS);
2598
3933
  }
2599
3934
  if (!childStopped && match.record.childPid && isProcessAlive(match.record.childPid)) {
2600
- terminateProcessTree(match.record.childPid);
3935
+ terminateProcessTree(match.record.childPid, { force: true });
2601
3936
  await waitForProcessTermination(match.record.childPid, DEFAULT_PM_STOP_POLL_MS);
2602
3937
  }
2603
3938
  writePmRecord(match.filePath, {
2604
3939
  ...updated,
2605
3940
  runnerPid: void 0,
2606
3941
  childPid: void 0,
3942
+ proxyTargetPort: void 0,
3943
+ reloadRequestedAt: void 0,
2607
3944
  status: "stopped",
2608
3945
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
2609
3946
  });
@@ -2672,13 +4009,13 @@ function readPackageJson(filePath) {
2672
4009
 
2673
4010
  // src/shares/workspace-package/roots.ts
2674
4011
  function findPackageDirectory(startDir, resolveMatch) {
2675
- let currentDir = resolve4(startDir);
4012
+ let currentDir = resolve5(startDir);
2676
4013
  while (true) {
2677
4014
  const match = resolveMatch(currentDir);
2678
4015
  if (match) {
2679
4016
  return match;
2680
4017
  }
2681
- const parentDir = dirname2(currentDir);
4018
+ const parentDir = dirname3(currentDir);
2682
4019
  if (parentDir === currentDir) {
2683
4020
  return void 0;
2684
4021
  }
@@ -2706,17 +4043,17 @@ init_fs();
2706
4043
  function getWorkspacePackageImportCandidates(packageRoot, specifier, options = {}) {
2707
4044
  const subpath = specifier === "elit" ? "index" : specifier.slice("elit/".length);
2708
4045
  const builtCandidates = options.preferredBuiltFormat === "cjs" ? [
2709
- resolve4(packageRoot, "dist", `${subpath}.cjs`),
2710
- resolve4(packageRoot, "dist", `${subpath}.js`),
2711
- resolve4(packageRoot, "dist", `${subpath}.mjs`)
4046
+ resolve5(packageRoot, "dist", `${subpath}.cjs`),
4047
+ resolve5(packageRoot, "dist", `${subpath}.js`),
4048
+ resolve5(packageRoot, "dist", `${subpath}.mjs`)
2712
4049
  ] : [
2713
- resolve4(packageRoot, "dist", `${subpath}.mjs`),
2714
- resolve4(packageRoot, "dist", `${subpath}.js`),
2715
- resolve4(packageRoot, "dist", `${subpath}.cjs`)
4050
+ resolve5(packageRoot, "dist", `${subpath}.mjs`),
4051
+ resolve5(packageRoot, "dist", `${subpath}.js`),
4052
+ resolve5(packageRoot, "dist", `${subpath}.cjs`)
2716
4053
  ];
2717
4054
  const sourceCandidates = [
2718
- resolve4(packageRoot, "src", `${subpath}.ts`),
2719
- resolve4(packageRoot, "src", `${subpath}.tsx`)
4055
+ resolve5(packageRoot, "src", `${subpath}.ts`),
4056
+ resolve5(packageRoot, "src", `${subpath}.tsx`)
2720
4057
  ];
2721
4058
  return options.preferBuilt ? [...builtCandidates, ...sourceCandidates] : [...sourceCandidates, ...builtCandidates];
2722
4059
  }
@@ -2751,7 +4088,7 @@ function resolveWorkspacePackageImport(specifier, startDir, options = {}) {
2751
4088
  // src/shares/config/loader.ts
2752
4089
  function resolveConfigPath(cwd = process.cwd()) {
2753
4090
  for (const configFile of ELIT_CONFIG_FILES) {
2754
- const configPath = resolve4(cwd, configFile);
4091
+ const configPath = resolve5(cwd, configFile);
2755
4092
  if (existsSync4(configPath)) {
2756
4093
  return configPath;
2757
4094
  }
@@ -2780,7 +4117,7 @@ async function loadConfigFile(configPath) {
2780
4117
  if (ext === "ts" || ext === "mts") {
2781
4118
  try {
2782
4119
  const { build } = await import("esbuild");
2783
- const configDir = dirname2(configPath);
4120
+ const configDir = dirname3(configPath);
2784
4121
  const tempFile = join5(configDir, `.elit-config-${Date.now()}.mjs`);
2785
4122
  const externalAllPlugin = {
2786
4123
  name: "external-all",
@@ -2854,6 +4191,43 @@ async function loadConfigFile(configPath) {
2854
4191
  }
2855
4192
 
2856
4193
  // src/cli/pm/commands.ts
4194
+ var PM_SIGNAL_NAMES = /* @__PURE__ */ new Set([
4195
+ "SIGABRT",
4196
+ "SIGALRM",
4197
+ "SIGBREAK",
4198
+ "SIGBUS",
4199
+ "SIGCHLD",
4200
+ "SIGCONT",
4201
+ "SIGFPE",
4202
+ "SIGHUP",
4203
+ "SIGILL",
4204
+ "SIGINT",
4205
+ "SIGIO",
4206
+ "SIGIOT",
4207
+ "SIGKILL",
4208
+ "SIGPIPE",
4209
+ "SIGPOLL",
4210
+ "SIGPROF",
4211
+ "SIGPWR",
4212
+ "SIGQUIT",
4213
+ "SIGSEGV",
4214
+ "SIGSTKFLT",
4215
+ "SIGSTOP",
4216
+ "SIGSYS",
4217
+ "SIGTERM",
4218
+ "SIGTRAP",
4219
+ "SIGTSTP",
4220
+ "SIGTTIN",
4221
+ "SIGTTOU",
4222
+ "SIGUNUSED",
4223
+ "SIGURG",
4224
+ "SIGUSR1",
4225
+ "SIGUSR2",
4226
+ "SIGVTALRM",
4227
+ "SIGWINCH",
4228
+ "SIGXCPU",
4229
+ "SIGXFSZ"
4230
+ ]);
2857
4231
  async function runPmStart(args) {
2858
4232
  const parsed = parsePmStartArgs(args);
2859
4233
  const workspaceRoot = process.cwd();
@@ -2886,9 +4260,130 @@ function resolveNamedMatches(paths, value) {
2886
4260
  if (value === "all") {
2887
4261
  return listPmRecordMatches(paths).map(syncPmRecordLiveness);
2888
4262
  }
4263
+ const groupMatches = findPmGroupMatches(paths, value);
4264
+ if (groupMatches.length > 0) {
4265
+ return groupMatches.map(syncPmRecordLiveness);
4266
+ }
2889
4267
  const match = findPmRecordMatch(paths, value);
2890
4268
  return match ? [syncPmRecordLiveness(match)] : [];
2891
4269
  }
4270
+ function sortPmMatchesByInstance(matches) {
4271
+ return [...matches].sort((left, right) => left.record.instanceIndex - right.record.instanceIndex);
4272
+ }
4273
+ function resolveInspectableMatch(paths, value) {
4274
+ const exactMatch = findPmRecordMatch(paths, value);
4275
+ const groupMatches = findPmGroupMatches(paths, value).map(syncPmRecordLiveness);
4276
+ if (groupMatches.length > 1 && exactMatch?.record.baseName === value && exactMatch.record.name === value) {
4277
+ 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}.`);
4278
+ }
4279
+ if (exactMatch) {
4280
+ return syncPmRecordLiveness(exactMatch);
4281
+ }
4282
+ return groupMatches[0];
4283
+ }
4284
+ function rebuildPmRecordDefinition(record, targetInstances = record.instances) {
4285
+ const definition = resolvePmAppDefinition(
4286
+ toPmAppConfig(record),
4287
+ { name: record.baseName, env: {}, watchPaths: [], watchIgnore: [], instances: targetInstances },
4288
+ process.cwd(),
4289
+ record.source
4290
+ );
4291
+ return {
4292
+ ...definition,
4293
+ name: record.name,
4294
+ baseName: record.baseName,
4295
+ instanceIndex: record.instanceIndex,
4296
+ instances: targetInstances
4297
+ };
4298
+ }
4299
+ function rebuildPmSavedDefinition(app) {
4300
+ const definition = resolvePmAppDefinition(
4301
+ toSavedPmAppConfig(app),
4302
+ { name: app.baseName, env: {}, watchPaths: [], watchIgnore: [], instances: app.instances },
4303
+ process.cwd(),
4304
+ "cli"
4305
+ );
4306
+ return {
4307
+ ...definition,
4308
+ name: app.name,
4309
+ baseName: app.baseName,
4310
+ instanceIndex: app.instanceIndex,
4311
+ instances: app.instances
4312
+ };
4313
+ }
4314
+ function deletePmMatches(matches) {
4315
+ for (const match of matches) {
4316
+ if (existsSync5(match.record.logFiles.out)) {
4317
+ rmSync(match.record.logFiles.out, { force: true });
4318
+ }
4319
+ if (existsSync5(match.record.logFiles.err)) {
4320
+ rmSync(match.record.logFiles.err, { force: true });
4321
+ }
4322
+ rmSync(match.filePath, { force: true });
4323
+ }
4324
+ }
4325
+ function updatePmInstanceCount(matches, instances) {
4326
+ const now = (/* @__PURE__ */ new Date()).toISOString();
4327
+ for (const match of matches) {
4328
+ writePmRecord(match.filePath, {
4329
+ ...match.record,
4330
+ instances,
4331
+ updatedAt: now
4332
+ });
4333
+ }
4334
+ }
4335
+ function normalizePmSignalName(value) {
4336
+ const trimmed = value.trim().toUpperCase();
4337
+ const signalName = trimmed.startsWith("SIG") ? trimmed : `SIG${trimmed}`;
4338
+ if (!PM_SIGNAL_NAMES.has(signalName)) {
4339
+ throw new Error(`Unsupported pm signal: ${value}`);
4340
+ }
4341
+ return signalName;
4342
+ }
4343
+ function resolveSignalablePid(record) {
4344
+ if (record.childPid && record.childPid > 0) {
4345
+ return record.childPid;
4346
+ }
4347
+ if (record.runnerPid && record.runnerPid > 0) {
4348
+ return record.runnerPid;
4349
+ }
4350
+ return void 0;
4351
+ }
4352
+ function groupPmMatchesByBaseName(matches) {
4353
+ const grouped = /* @__PURE__ */ new Map();
4354
+ for (const match of matches) {
4355
+ const group = grouped.get(match.record.baseName);
4356
+ if (group) {
4357
+ group.push(match);
4358
+ continue;
4359
+ }
4360
+ grouped.set(match.record.baseName, [match]);
4361
+ }
4362
+ return [...grouped.entries()].sort((left, right) => left[0].localeCompare(right[0])).map(([, group]) => sortPmMatchesByInstance(group));
4363
+ }
4364
+ async function waitForPmRecordOnline(filePath, timeoutMs) {
4365
+ const deadline = Date.now() + timeoutMs;
4366
+ while (Date.now() < deadline) {
4367
+ if (!existsSync5(filePath)) {
4368
+ break;
4369
+ }
4370
+ const record = readPmRecord(filePath);
4371
+ if (record.status === "online") {
4372
+ return record;
4373
+ }
4374
+ if (record.status === "errored" || record.status === "exited" || record.status === "stopped") {
4375
+ throw new Error(record.error ?? `Process ${record.name} failed while reloading.`);
4376
+ }
4377
+ await new Promise((resolvePromise) => setTimeout(resolvePromise, 50));
4378
+ }
4379
+ throw new Error(`Timed out waiting for the reloaded process to become online after ${timeoutMs}ms.`);
4380
+ }
4381
+ function resolvePmReloadReadyTimeout(record) {
4382
+ return record.waitReady || record.proxy?.strategy === "inherit" ? Math.max(record.listenTimeout + 1e3, 2e3) : Math.max(record.restartDelay + 1e3, 2e3);
4383
+ }
4384
+ function supportsPmProxyReload2(record) {
4385
+ return Boolean(record.proxy) && record.instances === 1 && Boolean(record.runnerPid);
4386
+ }
2892
4387
  function padCell(value, width) {
2893
4388
  return value.length >= width ? value : `${value}${" ".repeat(width - value.length)}`;
2894
4389
  }
@@ -2899,8 +4394,255 @@ function tailLogFile(filePath, lineCount) {
2899
4394
  const lines = readFileSync3(filePath, "utf8").split(/\r?\n/).filter((line) => line.length > 0);
2900
4395
  return lines.slice(-lineCount).join(EOL2);
2901
4396
  }
2902
- function printPmList(paths) {
2903
- const matches = listPmRecordMatches(paths).map(syncPmRecordLiveness);
4397
+ function listPmMatches(paths) {
4398
+ return listPmRecordMatches(paths).map(syncPmRecordLiveness);
4399
+ }
4400
+ function isPmRecordActive(record) {
4401
+ return record.desiredState === "running" && (record.status === "starting" || record.status === "online" || record.status === "restarting");
4402
+ }
4403
+ function resolvePmUptimeMs(record) {
4404
+ if (!isPmRecordActive(record) || !record.startedAt) {
4405
+ return void 0;
4406
+ }
4407
+ const startedTime = Date.parse(record.startedAt);
4408
+ if (Number.isNaN(startedTime)) {
4409
+ return void 0;
4410
+ }
4411
+ return Math.max(0, Date.now() - startedTime);
4412
+ }
4413
+ function resolvePmLiveMetrics(record) {
4414
+ const uptimeMs = resolvePmUptimeMs(record);
4415
+ if (!isPmRecordActive(record) || !record.childPid) {
4416
+ return { uptimeMs };
4417
+ }
4418
+ const sampledMetrics = samplePmProcessMetrics(record.childPid);
4419
+ return {
4420
+ ...sampledMetrics,
4421
+ uptimeMs,
4422
+ updatedAt: sampledMetrics.cpuPercent !== void 0 || sampledMetrics.memoryRssBytes !== void 0 ? (/* @__PURE__ */ new Date()).toISOString() : void 0
4423
+ };
4424
+ }
4425
+ function toPmDisplayRecord(record) {
4426
+ return {
4427
+ record,
4428
+ liveMetrics: resolvePmLiveMetrics(record)
4429
+ };
4430
+ }
4431
+ function serializePmRecord(record) {
4432
+ return {
4433
+ ...record,
4434
+ liveMetrics: resolvePmLiveMetrics(record)
4435
+ };
4436
+ }
4437
+ function parsePmFormatOption(args, index, option) {
4438
+ let value;
4439
+ if (option.startsWith("--format=")) {
4440
+ value = option.slice("--format=".length);
4441
+ } else {
4442
+ value = readRequiredValue(args, index + 1, "--format");
4443
+ index += 1;
4444
+ }
4445
+ if (value !== "table" && value !== "json") {
4446
+ throw new Error(`Unsupported pm output format: ${value}`);
4447
+ }
4448
+ return {
4449
+ format: value,
4450
+ nextIndex: index
4451
+ };
4452
+ }
4453
+ function parsePmListArgs(args) {
4454
+ let format = "table";
4455
+ for (let index = 0; index < args.length; index++) {
4456
+ const arg = args[index];
4457
+ switch (arg) {
4458
+ case "--json":
4459
+ format = "json";
4460
+ break;
4461
+ case "--format":
4462
+ default:
4463
+ if (arg === "--format" || arg.startsWith("--format=")) {
4464
+ const parsed = parsePmFormatOption(args, index, arg);
4465
+ format = parsed.format;
4466
+ index = parsed.nextIndex;
4467
+ break;
4468
+ }
4469
+ throw new Error(`Unknown pm list option: ${arg}`);
4470
+ }
4471
+ }
4472
+ return { format };
4473
+ }
4474
+ function parsePmShowArgs(args) {
4475
+ let format = "text";
4476
+ let name;
4477
+ for (let index = 0; index < args.length; index++) {
4478
+ const arg = args[index];
4479
+ switch (arg) {
4480
+ case "--json":
4481
+ format = "json";
4482
+ break;
4483
+ case "--format":
4484
+ default:
4485
+ if (arg === "--format" || arg.startsWith("--format=")) {
4486
+ const parsed = parsePmFormatOption(args, index, arg);
4487
+ format = parsed.format === "json" ? "json" : "text";
4488
+ index = parsed.nextIndex;
4489
+ break;
4490
+ }
4491
+ if (arg.startsWith("-")) {
4492
+ throw new Error(`Unknown pm show option: ${arg}`);
4493
+ }
4494
+ if (name) {
4495
+ throw new Error("pm show accepts exactly one process name.");
4496
+ }
4497
+ name = arg;
4498
+ break;
4499
+ }
4500
+ }
4501
+ if (!name) {
4502
+ throw new Error("Usage: elit pm show <name> [--json]");
4503
+ }
4504
+ return { name, format };
4505
+ }
4506
+ function formatPmDuration(durationMs) {
4507
+ if (durationMs < 1e3) {
4508
+ return `${durationMs}ms`;
4509
+ }
4510
+ const totalSeconds = Math.floor(durationMs / 1e3);
4511
+ const days = Math.floor(totalSeconds / 86400);
4512
+ const hours = Math.floor(totalSeconds % 86400 / 3600);
4513
+ const minutes = Math.floor(totalSeconds % 3600 / 60);
4514
+ const seconds = totalSeconds % 60;
4515
+ const parts = [];
4516
+ if (days > 0) {
4517
+ parts.push(`${days}d`);
4518
+ }
4519
+ if (hours > 0) {
4520
+ parts.push(`${hours}h`);
4521
+ }
4522
+ if (minutes > 0) {
4523
+ parts.push(`${minutes}m`);
4524
+ }
4525
+ if (seconds > 0 || parts.length === 0) {
4526
+ parts.push(`${seconds}s`);
4527
+ }
4528
+ return parts.slice(0, 2).join(" ");
4529
+ }
4530
+ function formatPmCpuPercent(cpuPercent) {
4531
+ if (cpuPercent === void 0 || !Number.isFinite(cpuPercent)) {
4532
+ return "-";
4533
+ }
4534
+ return `${cpuPercent >= 100 ? cpuPercent.toFixed(0) : cpuPercent.toFixed(1)}%`;
4535
+ }
4536
+ function formatPmMemory(memoryRssBytes) {
4537
+ if (memoryRssBytes === void 0 || !Number.isFinite(memoryRssBytes) || memoryRssBytes < 0) {
4538
+ return "-";
4539
+ }
4540
+ const units = ["B", "KB", "MB", "GB", "TB"];
4541
+ let value = memoryRssBytes;
4542
+ let unitIndex = 0;
4543
+ while (value >= 1024 && unitIndex < units.length - 1) {
4544
+ value /= 1024;
4545
+ unitIndex += 1;
4546
+ }
4547
+ const formatted = value >= 10 || unitIndex === 0 ? value.toFixed(0) : value.toFixed(1);
4548
+ return `${formatted}${units[unitIndex]}`;
4549
+ }
4550
+ function formatPmUptime(uptimeMs) {
4551
+ if (uptimeMs === void 0 || !Number.isFinite(uptimeMs)) {
4552
+ return "-";
4553
+ }
4554
+ return formatPmDuration(Math.max(0, uptimeMs));
4555
+ }
4556
+ function formatPmTarget(record) {
4557
+ if (record.script) {
4558
+ return record.script;
4559
+ }
4560
+ if (record.file) {
4561
+ return record.file;
4562
+ }
4563
+ if (record.wapk) {
4564
+ return record.wapk;
4565
+ }
4566
+ return "-";
4567
+ }
4568
+ function pushPmDetail(lines, label, value) {
4569
+ lines.push(`${padCell(`${label}:`, 18)} ${value}`);
4570
+ }
4571
+ function pushPmDetailList(lines, label, values) {
4572
+ if (values.length === 0) {
4573
+ pushPmDetail(lines, label, "-");
4574
+ return;
4575
+ }
4576
+ pushPmDetail(lines, label, values[0] ?? "-");
4577
+ for (const value of values.slice(1)) {
4578
+ lines.push(`${" ".repeat(20)} ${value}`);
4579
+ }
4580
+ }
4581
+ function formatPmRecordDetails(record, liveMetrics) {
4582
+ const lines = [`Process: ${record.name}`];
4583
+ pushPmDetail(lines, "id", record.id);
4584
+ pushPmDetail(lines, "status", record.status);
4585
+ pushPmDetail(lines, "desired state", record.desiredState);
4586
+ pushPmDetail(lines, "instance", `${record.instanceIndex}/${record.instances}`);
4587
+ pushPmDetail(lines, "cpu", formatPmCpuPercent(liveMetrics.cpuPercent));
4588
+ pushPmDetail(lines, "memory", formatPmMemory(liveMetrics.memoryRssBytes));
4589
+ pushPmDetail(lines, "uptime", formatPmUptime(liveMetrics.uptimeMs));
4590
+ pushPmDetail(lines, "type", record.type);
4591
+ pushPmDetail(lines, "source", record.source);
4592
+ pushPmDetail(lines, "runtime", record.runtime ?? "-");
4593
+ pushPmDetail(lines, "cwd", record.cwd);
4594
+ pushPmDetail(lines, "target", formatPmTarget(record));
4595
+ pushPmDetail(lines, "command", record.commandPreview || "-");
4596
+ pushPmDetail(lines, "runner pid", record.runnerPid ? String(record.runnerPid) : "-");
4597
+ pushPmDetail(lines, "child pid", record.childPid ? String(record.childPid) : "-");
4598
+ pushPmDetail(lines, "restart count", `${record.restartCount}/${record.maxRestarts}`);
4599
+ pushPmDetail(lines, "restart policy", record.restartPolicy);
4600
+ pushPmDetail(lines, "proxy", record.proxy ? `http://${record.proxy.host ?? "0.0.0.0"}:${record.proxy.port}` : "-");
4601
+ pushPmDetail(lines, "proxy strategy", record.proxy?.strategy ?? "-");
4602
+ pushPmDetail(lines, "proxy target", record.proxy && record.proxyTargetPort ? `${record.proxy.targetHost ?? "127.0.0.1"}:${record.proxyTargetPort}` : "-");
4603
+ pushPmDetail(lines, "max memory", record.maxMemoryBytes ? formatPmMemory(record.maxMemoryBytes) : "-");
4604
+ pushPmDetail(lines, "memory action", record.memoryAction ?? "-");
4605
+ pushPmDetail(lines, "cron restart", record.cronRestart ?? "-");
4606
+ pushPmDetail(lines, "exp backoff", record.expBackoffRestartDelay ? formatPmDuration(record.expBackoffRestartDelay) : "-");
4607
+ pushPmDetail(lines, "exp backoff max", record.expBackoffRestartMaxDelay ? formatPmDuration(record.expBackoffRestartMaxDelay) : "-");
4608
+ pushPmDetail(lines, "restart window", record.restartWindow ? formatPmDuration(record.restartWindow) : "-");
4609
+ pushPmDetail(lines, "wait ready", record.waitReady ? "enabled" : "disabled");
4610
+ pushPmDetail(lines, "listen timeout", record.waitReady ? formatPmDuration(record.listenTimeout) : "-");
4611
+ pushPmDetail(lines, "restart delay", formatPmDuration(record.restartDelay));
4612
+ pushPmDetail(lines, "kill timeout", formatPmDuration(record.killTimeout));
4613
+ pushPmDetail(lines, "min uptime", formatPmDuration(record.minUptime));
4614
+ pushPmDetail(lines, "autorestart", record.autorestart ? "enabled" : "disabled");
4615
+ pushPmDetail(lines, "watch", record.watch ? "enabled" : "disabled");
4616
+ pushPmDetail(lines, "watch debounce", record.watch ? formatPmDuration(record.watchDebounce) : "-");
4617
+ pushPmDetailList(lines, "watch paths", record.watchPaths);
4618
+ pushPmDetailList(lines, "watch ignore", record.watchIgnore);
4619
+ if (record.healthCheck) {
4620
+ pushPmDetail(lines, "health check", record.healthCheck.url);
4621
+ pushPmDetail(lines, "health grace", formatPmDuration(record.healthCheck.gracePeriod));
4622
+ pushPmDetail(lines, "health interval", formatPmDuration(record.healthCheck.interval));
4623
+ pushPmDetail(lines, "health timeout", formatPmDuration(record.healthCheck.timeout));
4624
+ pushPmDetail(lines, "health failures", String(record.healthCheck.maxFailures));
4625
+ } else {
4626
+ pushPmDetail(lines, "health check", "-");
4627
+ }
4628
+ pushPmDetailList(lines, "env", Object.entries(record.env).map(([key, value]) => `${key}=${value}`));
4629
+ pushPmDetail(lines, "stdout log", record.logFiles.out);
4630
+ pushPmDetail(lines, "stderr log", record.logFiles.err);
4631
+ pushPmDetail(lines, "created at", record.createdAt);
4632
+ pushPmDetail(lines, "updated at", record.updatedAt);
4633
+ pushPmDetail(lines, "metrics at", liveMetrics.updatedAt ?? "-");
4634
+ pushPmDetail(lines, "started at", record.startedAt ?? "-");
4635
+ pushPmDetail(lines, "stopped at", record.stoppedAt ?? "-");
4636
+ pushPmDetail(lines, "last exit", record.lastExitCode === void 0 ? "-" : String(record.lastExitCode));
4637
+ pushPmDetail(lines, "error", record.error ?? "-");
4638
+ return lines.join(EOL2);
4639
+ }
4640
+ function printPmList(paths, format = "table") {
4641
+ const matches = listPmMatches(paths).map((match) => toPmDisplayRecord(match.record));
4642
+ if (format === "json") {
4643
+ console.log(JSON.stringify(matches.map((match) => ({ ...match.record, liveMetrics: match.liveMetrics })), null, 2));
4644
+ return;
4645
+ }
2904
4646
  if (matches.length === 0) {
2905
4647
  console.log("No managed processes found.");
2906
4648
  return;
@@ -2909,22 +4651,47 @@ function printPmList(paths) {
2909
4651
  padCell("name", 20),
2910
4652
  padCell("status", 12),
2911
4653
  padCell("pid", 8),
4654
+ padCell("cpu", 8),
4655
+ padCell("memory", 10),
4656
+ padCell("uptime", 10),
2912
4657
  padCell("restarts", 10),
2913
4658
  padCell("type", 8),
2914
4659
  "runtime"
2915
4660
  ];
2916
4661
  console.log(headers.join(" "));
2917
- for (const { record } of matches) {
4662
+ for (const { record, liveMetrics } of matches) {
2918
4663
  console.log([
2919
4664
  padCell(record.name, 20),
2920
4665
  padCell(record.status, 12),
2921
4666
  padCell(record.childPid ? String(record.childPid) : "-", 8),
4667
+ padCell(formatPmCpuPercent(liveMetrics.cpuPercent), 8),
4668
+ padCell(formatPmMemory(liveMetrics.memoryRssBytes), 10),
4669
+ padCell(formatPmUptime(liveMetrics.uptimeMs), 10),
2922
4670
  padCell(String(record.restartCount ?? 0), 10),
2923
4671
  padCell(record.type, 8),
2924
4672
  record.runtime ?? "-"
2925
4673
  ].join(" "));
2926
4674
  }
2927
4675
  }
4676
+ async function runPmList(args) {
4677
+ const options = parsePmListArgs(args);
4678
+ const { paths } = await loadPmContext();
4679
+ printPmList(paths, options.format);
4680
+ }
4681
+ async function runPmShow(args) {
4682
+ const options = parsePmShowArgs(args);
4683
+ const { paths } = await loadPmContext();
4684
+ const match = resolveInspectableMatch(paths, options.name);
4685
+ if (!match) {
4686
+ throw new Error(`No managed process found for: ${options.name}`);
4687
+ }
4688
+ const synced = syncPmRecordLiveness(match);
4689
+ if (options.format === "json") {
4690
+ console.log(JSON.stringify(serializePmRecord(synced.record), null, 2));
4691
+ return;
4692
+ }
4693
+ console.log(formatPmRecordDetails(synced.record, resolvePmLiveMetrics(synced.record)));
4694
+ }
2928
4695
  async function runPmStop(args) {
2929
4696
  const target = args[0];
2930
4697
  if (!target) {
@@ -2951,17 +4718,62 @@ async function runPmRestart(args) {
2951
4718
  await stopPmMatches(matches);
2952
4719
  const restarted = [];
2953
4720
  for (const match of matches) {
2954
- const definition = resolvePmAppDefinition(
2955
- toPmAppConfig(match.record),
2956
- { name: match.record.name, env: {}, watchPaths: [], watchIgnore: [] },
2957
- process.cwd(),
2958
- match.record.source
2959
- );
4721
+ const definition = rebuildPmRecordDefinition(match.record);
2960
4722
  await startManagedProcess(definition, paths);
2961
4723
  restarted.push(match.record.name);
2962
4724
  }
2963
4725
  console.log(`[pm] restarted ${restarted.join(", ")}`);
2964
4726
  }
4727
+ async function runPmReload(args) {
4728
+ const target = args[0];
4729
+ if (!target) {
4730
+ throw new Error("Usage: elit pm reload <name|all>");
4731
+ }
4732
+ const { paths } = await loadPmContext();
4733
+ const matches = resolveNamedMatches(paths, target);
4734
+ if (matches.length === 0) {
4735
+ throw new Error(`No managed process found for: ${target}`);
4736
+ }
4737
+ const reloaded = [];
4738
+ const errors = [];
4739
+ for (const group of groupPmMatchesByBaseName(matches)) {
4740
+ for (const match of group) {
4741
+ try {
4742
+ if (supportsPmProxyReload2(match.record)) {
4743
+ const reloadRequestedAt = (/* @__PURE__ */ new Date()).toISOString();
4744
+ writePmRecord(match.filePath, {
4745
+ ...match.record,
4746
+ status: "restarting",
4747
+ reloadRequestedAt,
4748
+ updatedAt: reloadRequestedAt,
4749
+ error: void 0
4750
+ });
4751
+ await waitForPmRecordOnline(
4752
+ getPmRecordPath(paths, match.record.id),
4753
+ resolvePmReloadReadyTimeout(match.record)
4754
+ );
4755
+ reloaded.push(match.record.name);
4756
+ continue;
4757
+ }
4758
+ await stopPmMatches([match]);
4759
+ const definition = rebuildPmRecordDefinition(match.record);
4760
+ const startedRecord = await startManagedProcess(definition, paths);
4761
+ await waitForPmRecordOnline(
4762
+ getPmRecordPath(paths, startedRecord.id),
4763
+ resolvePmReloadReadyTimeout(startedRecord)
4764
+ );
4765
+ reloaded.push(match.record.name);
4766
+ } catch (error) {
4767
+ const message = error instanceof Error ? error.message : String(error);
4768
+ errors.push(`[pm] ${match.record.name}: ${message}`);
4769
+ }
4770
+ }
4771
+ }
4772
+ if (errors.length > 0) {
4773
+ throw new Error([`[pm] reloaded ${reloaded.length} process${reloaded.length === 1 ? "" : "es"}`, ...errors].join(EOL2));
4774
+ }
4775
+ console.log(`[pm] reloaded ${reloaded.join(", ")}`);
4776
+ }
2965
4777
  async function runPmSave() {
2966
4778
  const { paths } = await loadPmContext();
2967
4779
  ensurePmDirectories(paths);
@@ -2983,12 +4795,7 @@ async function runPmResurrect() {
2983
4795
  let restored = 0;
2984
4796
  for (const app of dump.apps) {
2985
4797
  try {
2986
- const definition = resolvePmAppDefinition(
2987
- toSavedPmAppConfig(app),
2988
- { name: app.name, env: {}, watchPaths: [], watchIgnore: [] },
2989
- process.cwd(),
2990
- "cli"
2991
- );
4798
+ const definition = rebuildPmSavedDefinition(app);
2992
4799
  await startManagedProcess(definition, paths);
2993
4800
  restored += 1;
2994
4801
  } catch (error) {
@@ -3012,16 +4819,127 @@ async function runPmDelete(args) {
3012
4819
  throw new Error(`No managed process found for: ${target}`);
3013
4820
  }
3014
4821
  await stopPmMatches(matches);
4822
+ deletePmMatches(matches);
4823
+ console.log(`[pm] deleted ${matches.length} process${matches.length === 1 ? "" : "es"}`);
4824
+ }
4825
+ async function runPmScale(args) {
4826
+ const target = args[0];
4827
+ const countArg = args[1];
4828
+ if (!target || countArg === void 0 || args.length > 2) {
4829
+ throw new Error("Usage: elit pm scale <name> <count>");
4830
+ }
4831
+ const desiredCount = normalizeIntegerOption(countArg, "pm scale <count>", 0);
4832
+ const { config, paths } = await loadPmContext();
4833
+ const exactMatch = findPmRecordMatch(paths, target);
4834
+ const currentMatches = sortPmMatchesByInstance(resolveNamedMatches(paths, exactMatch?.record.baseName ?? target));
4835
+ if (currentMatches.length === 0) {
4836
+ if (desiredCount === 0) {
4837
+ console.log(`[pm] ${target} already scaled to 0 instances`);
4838
+ return;
4839
+ }
4840
+ const definitions = resolvePmStartDefinitions(
4841
+ { name: target, env: {}, watchPaths: [], watchIgnore: [], instances: desiredCount },
4842
+ config,
4843
+ process.cwd()
4844
+ );
4845
+ for (const definition of definitions) {
4846
+ await startManagedProcess(definition, paths);
4847
+ }
4848
+ console.log(`[pm] scaled ${target} to ${desiredCount} instance${desiredCount === 1 ? "" : "s"}`);
4849
+ return;
4850
+ }
4851
+ const baseName = currentMatches[0]?.record.baseName ?? target;
4852
+ if (desiredCount === currentMatches.length) {
4853
+ console.log(`[pm] ${baseName} already scaled to ${desiredCount} instance${desiredCount === 1 ? "" : "s"}`);
4854
+ return;
4855
+ }
4856
+ if (desiredCount === 0) {
4857
+ await stopPmMatches(currentMatches);
4858
+ deletePmMatches(currentMatches);
4859
+ console.log(`[pm] scaled ${baseName} to 0 instances`);
4860
+ return;
4861
+ }
4862
+ if (desiredCount < currentMatches.length) {
4863
+ const toRemove = [...currentMatches].sort((left, right) => right.record.instanceIndex - left.record.instanceIndex).slice(0, currentMatches.length - desiredCount);
4864
+ const remaining = currentMatches.filter((match) => !toRemove.some((removal) => removal.record.id === match.record.id));
4865
+ await stopPmMatches(toRemove);
4866
+ deletePmMatches(toRemove);
4867
+ updatePmInstanceCount(remaining, desiredCount);
4868
+ console.log(`[pm] scaled ${baseName} to ${desiredCount} instance${desiredCount === 1 ? "" : "s"}`);
4869
+ return;
4870
+ }
4871
+ updatePmInstanceCount(currentMatches, desiredCount);
4872
+ const baseRecord = currentMatches[0]?.record;
4873
+ if (!baseRecord) {
4874
+ throw new Error(`No managed process found for: ${target}`);
4875
+ }
4876
+ const baseDefinition = rebuildPmRecordDefinition(baseRecord, desiredCount);
4877
+ const expandedDefinitions = expandPmInstanceDefinitions(baseDefinition, desiredCount);
4878
+ const existingNames = new Set(currentMatches.map((match) => match.record.name));
4879
+ for (const definition of expandedDefinitions) {
4880
+ if (existingNames.has(definition.name)) {
4881
+ continue;
4882
+ }
4883
+ await startManagedProcess(definition, paths);
4884
+ }
4885
+ console.log(`[pm] scaled ${baseName} to ${desiredCount} instance${desiredCount === 1 ? "" : "s"}`);
4886
+ }
4887
+ async function runPmSendSignal(args) {
4888
+ const signalArg = args[0];
4889
+ const target = args[1];
4890
+ if (!signalArg || !target || args.length > 2) {
4891
+ throw new Error("Usage: elit pm send-signal <signal> <name|all>");
4892
+ }
4893
+ const signalName = normalizePmSignalName(signalArg);
4894
+ const { paths } = await loadPmContext();
4895
+ const matches = resolveNamedMatches(paths, target);
4896
+ if (matches.length === 0) {
4897
+ throw new Error(`No managed process found for: ${target}`);
4898
+ }
4899
+ let signaled = 0;
4900
+ const errors = [];
3015
4901
  for (const match of matches) {
3016
- if (existsSync5(match.record.logFiles.out)) {
3017
- rmSync(match.record.logFiles.out, { force: true });
4902
+ const pid = resolveSignalablePid(match.record);
4903
+ if (!pid) {
4904
+ continue;
3018
4905
  }
3019
- if (existsSync5(match.record.logFiles.err)) {
3020
- rmSync(match.record.logFiles.err, { force: true });
4906
+ try {
4907
+ sendPmSignal(pid, signalName);
4908
+ signaled += 1;
4909
+ } catch (error) {
4910
+ const message = error instanceof Error ? error.message : String(error);
4911
+ errors.push(`[pm] ${match.record.name}: ${message}`);
3021
4912
  }
3022
- rmSync(match.filePath, { force: true });
3023
4913
  }
3024
- console.log(`[pm] deleted ${matches.length} process${matches.length === 1 ? "" : "es"}`);
4914
+ if (errors.length > 0) {
4915
+ throw new Error([`[pm] sent ${signalName} to ${signaled} process${signaled === 1 ? "" : "es"}`, ...errors].join(EOL2));
4916
+ }
4917
+ if (signaled === 0) {
4918
+ throw new Error(`No running managed process found for: ${target}`);
4919
+ }
4920
+ console.log(`[pm] sent ${signalName} to ${signaled} process${signaled === 1 ? "" : "es"}`);
4921
+ }
4922
+ async function runPmReset(args) {
4923
+ const target = args[0];
4924
+ if (!target) {
4925
+ throw new Error("Usage: elit pm reset <name|all>");
4926
+ }
4927
+ const { paths } = await loadPmContext();
4928
+ const matches = resolveNamedMatches(paths, target);
4929
+ if (matches.length === 0) {
4930
+ throw new Error(`No managed process found for: ${target}`);
4931
+ }
4932
+ const now = (/* @__PURE__ */ new Date()).toISOString();
4933
+ for (const match of matches) {
4934
+ writePmRecord(match.filePath, {
4935
+ ...match.record,
4936
+ restartCount: 0,
4937
+ lastExitCode: void 0,
4938
+ error: void 0,
4939
+ updatedAt: now
4940
+ });
4941
+ }
4942
+ console.log(`[pm] reset ${matches.length} process${matches.length === 1 ? "" : "es"}`);
3025
4943
  }
3026
4944
  async function runPmLogs(args) {
3027
4945
  if (args.length === 0) {
@@ -3054,7 +4972,7 @@ async function runPmLogs(args) {
3054
4972
  throw new Error("Usage: elit pm logs <name> [--lines <n>] [--stderr]");
3055
4973
  }
3056
4974
  const { paths } = await loadPmContext();
3057
- const match = findPmRecordMatch(paths, name);
4975
+ const match = resolveInspectableMatch(paths, name);
3058
4976
  if (!match) {
3059
4977
  throw new Error(`No managed process found for: ${name}`);
3060
4978
  }
@@ -3078,17 +4996,36 @@ async function runPmCommand(args) {
3078
4996
  await runPmStart(args.slice(1));
3079
4997
  return;
3080
4998
  case "list":
3081
- case "ls": {
3082
- const { paths } = await loadPmContext();
3083
- printPmList(paths);
4999
+ case "ls":
5000
+ await runPmList(args.slice(1));
5001
+ return;
5002
+ case "jlist":
5003
+ await runPmList(["--json", ...args.slice(1)]);
5004
+ return;
5005
+ case "show":
5006
+ case "describe":
5007
+ await runPmShow(args.slice(1));
3084
5008
  return;
3085
- }
3086
5009
  case "stop":
3087
5010
  await runPmStop(args.slice(1));
3088
5011
  return;
3089
5012
  case "restart":
3090
5013
  await runPmRestart(args.slice(1));
3091
5014
  return;
5015
+ case "reload":
5016
+ await runPmReload(args.slice(1));
5017
+ return;
5018
+ case "scale":
5019
+ await runPmScale(args.slice(1));
5020
+ return;
5021
+ case "send-signal":
5022
+ case "signal":
5023
+ case "sendSignal":
5024
+ await runPmSendSignal(args.slice(1));
5025
+ return;
5026
+ case "reset":
5027
+ await runPmReset(args.slice(1));
5028
+ return;
3092
5029
  case "delete":
3093
5030
  case "remove":
3094
5031
  case "rm":
@@ -3120,6 +5057,12 @@ export {
3120
5057
  DEFAULT_MIN_UPTIME,
3121
5058
  DEFAULT_PM_DATA_DIR,
3122
5059
  DEFAULT_PM_DUMP_FILE,
5060
+ DEFAULT_PM_EXP_BACKOFF_MAX_DELAY,
5061
+ DEFAULT_PM_KILL_TIMEOUT,
5062
+ DEFAULT_PM_LISTEN_TIMEOUT,
5063
+ DEFAULT_PM_MEMORY_CHECK_INTERVAL,
5064
+ DEFAULT_PM_PROXY_STRATEGY,
5065
+ DEFAULT_PM_RESTART_WINDOW,
3123
5066
  DEFAULT_PM_STOP_GRACE_PERIOD_MS,
3124
5067
  DEFAULT_PM_STOP_POLL_MS,
3125
5068
  DEFAULT_RESTART_DELAY,
@@ -3138,7 +5081,10 @@ export {
3138
5081
  countDefinedPmWapkSources,
3139
5082
  deriveDefaultWatchPaths,
3140
5083
  ensurePmDirectories,
5084
+ expandPmInstanceDefinitions,
5085
+ findPmGroupMatches,
3141
5086
  findPmRecordMatch,
5087
+ formatPmInstanceName,
3142
5088
  getPmRecordPath,
3143
5089
  hasPmGoogleDriveConfig,
3144
5090
  hasPmWapkRunConfig,
@@ -3157,6 +5103,10 @@ export {
3157
5103
  normalizeHealthCheckConfig,
3158
5104
  normalizeIntegerOption,
3159
5105
  normalizeNonEmptyString,
5106
+ normalizePmMemoryAction,
5107
+ normalizePmMemoryLimit,
5108
+ normalizePmProxyConfig,
5109
+ normalizePmProxyStrategy,
3160
5110
  normalizePmRestartPolicy,
3161
5111
  normalizePmRuntime,
3162
5112
  normalizeResolvedWatchIgnorePaths,
@@ -3180,7 +5130,9 @@ export {
3180
5130
  runManagedProcessLoop,
3181
5131
  runPmCommand,
3182
5132
  runPmRunner,
5133
+ samplePmProcessMetrics,
3183
5134
  sanitizePmProcessName,
5135
+ sendPmSignal,
3184
5136
  startManagedProcess,
3185
5137
  stopPmMatches,
3186
5138
  stripPmWapkSourceFromRunConfig,