elit 3.6.7 → 3.6.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Cargo.lock +1 -1
- package/Cargo.toml +1 -1
- package/README.md +20 -1
- package/dist/cli.cjs +2777 -321
- package/dist/cli.mjs +2764 -308
- package/dist/config.d.ts +6 -6
- package/dist/{contracts-BeW9k0yZ.d.ts → contracts-_0p1-15U.d.ts} +1 -1
- package/dist/coverage.d.ts +1 -1
- package/dist/dev-build.d.ts +3 -3
- package/dist/http.cjs +160 -41
- package/dist/http.d.ts +5 -11
- package/dist/http.js +160 -41
- package/dist/http.mjs +160 -41
- package/dist/https.cjs +194 -46
- package/dist/https.d.ts +3 -6
- package/dist/https.js +194 -46
- package/dist/https.mjs +194 -46
- package/dist/pm-node-shared-listener-bootstrap.cjs +75 -0
- package/dist/pm.cjs +2390 -160
- package/dist/pm.d.ts +83 -8
- package/dist/pm.js +2388 -188
- package/dist/pm.mjs +2380 -165
- package/dist/preview-build.d.ts +3 -3
- package/dist/server.cjs +417 -168
- package/dist/server.d.ts +4 -4
- package/dist/server.js +453 -179
- package/dist/server.mjs +417 -168
- package/dist/smtp-server.js +37 -12
- package/dist/test-reporter.d.ts +1 -1
- package/dist/test-runtime.d.ts +1 -1
- package/dist/{types-tJn88E1N.d.ts → types-BayMVo_k.d.ts} +39 -3
- package/dist/{types-CIhpN1-K.d.ts → types-C70T-42Z.d.ts} +1 -1
- package/dist/{types-DAisuVr5.d.ts → types-DPOgoGs-.d.ts} +7 -1
- package/dist/{state-DvEkDehk.d.ts → types-fiLday0L.d.ts} +96 -92
- package/dist/types.d.ts +4 -0
- package/dist/{websocket-XfyK23zD.d.ts → websocket-BLBEAnhp.d.ts} +1 -1
- package/dist/ws.d.ts +3 -3
- package/dist/wss.cjs +194 -46
- package/dist/wss.d.ts +3 -3
- package/dist/wss.js +194 -46
- package/dist/wss.mjs +194 -46
- 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
|
|
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
|
|
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
|
|
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(
|
|
2475
|
+
return statSync2(normalizedPattern).isDirectory() ? normalizedPattern : normalizePath2(dirname3(normalizedPattern)) || ".";
|
|
1932
2476
|
} catch {
|
|
1933
|
-
return normalizePath2(
|
|
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(
|
|
2482
|
+
return normalizePath2(dirname3(normalizedPattern)) || ".";
|
|
1939
2483
|
}
|
|
1940
2484
|
return normalizedPattern || ".";
|
|
1941
2485
|
}
|
|
@@ -2130,84 +2674,877 @@ function watch(paths, options) {
|
|
|
2130
2674
|
return watcher;
|
|
2131
2675
|
}
|
|
2132
2676
|
|
|
2133
|
-
// src/cli/pm/
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
}
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
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
|
+
import { lookup as dnsLookup } from "dns/promises";
|
|
2682
|
+
var BLOCKED_IPV4_PREFIXES = [
|
|
2683
|
+
"0.",
|
|
2684
|
+
"10.",
|
|
2685
|
+
"100.64.",
|
|
2686
|
+
"100.65.",
|
|
2687
|
+
"100.66.",
|
|
2688
|
+
"100.67.",
|
|
2689
|
+
"100.68.",
|
|
2690
|
+
"100.69.",
|
|
2691
|
+
"100.70.",
|
|
2692
|
+
"100.71.",
|
|
2693
|
+
"100.72.",
|
|
2694
|
+
"100.73.",
|
|
2695
|
+
"100.74.",
|
|
2696
|
+
"100.75.",
|
|
2697
|
+
"100.76.",
|
|
2698
|
+
"100.77.",
|
|
2699
|
+
"100.78.",
|
|
2700
|
+
"100.79.",
|
|
2701
|
+
"100.80.",
|
|
2702
|
+
"100.81.",
|
|
2703
|
+
"100.82.",
|
|
2704
|
+
"100.83.",
|
|
2705
|
+
"100.84.",
|
|
2706
|
+
"100.85.",
|
|
2707
|
+
"100.86.",
|
|
2708
|
+
"100.87.",
|
|
2709
|
+
"100.88.",
|
|
2710
|
+
"100.89.",
|
|
2711
|
+
"100.90.",
|
|
2712
|
+
"100.91.",
|
|
2713
|
+
"100.92.",
|
|
2714
|
+
"100.93.",
|
|
2715
|
+
"100.94.",
|
|
2716
|
+
"100.95.",
|
|
2717
|
+
"100.96.",
|
|
2718
|
+
"100.97.",
|
|
2719
|
+
"100.98.",
|
|
2720
|
+
"100.99.",
|
|
2721
|
+
"100.100.",
|
|
2722
|
+
"100.101.",
|
|
2723
|
+
"100.102.",
|
|
2724
|
+
"100.103.",
|
|
2725
|
+
"100.104.",
|
|
2726
|
+
"100.105.",
|
|
2727
|
+
"100.106.",
|
|
2728
|
+
"100.107.",
|
|
2729
|
+
"100.108.",
|
|
2730
|
+
"100.109.",
|
|
2731
|
+
"100.110.",
|
|
2732
|
+
"100.111.",
|
|
2733
|
+
"100.112.",
|
|
2734
|
+
"100.113.",
|
|
2735
|
+
"100.114.",
|
|
2736
|
+
"100.115.",
|
|
2737
|
+
"100.116.",
|
|
2738
|
+
"100.117.",
|
|
2739
|
+
"100.118.",
|
|
2740
|
+
"100.119.",
|
|
2741
|
+
"100.120.",
|
|
2742
|
+
"100.121.",
|
|
2743
|
+
"100.122.",
|
|
2744
|
+
"100.123.",
|
|
2745
|
+
"100.124.",
|
|
2746
|
+
"100.125.",
|
|
2747
|
+
"100.126.",
|
|
2748
|
+
"100.127.",
|
|
2749
|
+
"127.",
|
|
2750
|
+
"169.254.",
|
|
2751
|
+
"172.16.",
|
|
2752
|
+
"172.17.",
|
|
2753
|
+
"172.18.",
|
|
2754
|
+
"172.19.",
|
|
2755
|
+
"172.20.",
|
|
2756
|
+
"172.21.",
|
|
2757
|
+
"172.22.",
|
|
2758
|
+
"172.23.",
|
|
2759
|
+
"172.24.",
|
|
2760
|
+
"172.25.",
|
|
2761
|
+
"172.26.",
|
|
2762
|
+
"172.27.",
|
|
2763
|
+
"172.28.",
|
|
2764
|
+
"172.29.",
|
|
2765
|
+
"172.30.",
|
|
2766
|
+
"172.31.",
|
|
2767
|
+
"192.0.2.",
|
|
2768
|
+
"192.88.99.",
|
|
2769
|
+
"192.168.",
|
|
2770
|
+
"198.18.",
|
|
2771
|
+
"198.19.",
|
|
2772
|
+
"198.51.100.",
|
|
2773
|
+
"203.0.113.",
|
|
2774
|
+
"224.",
|
|
2775
|
+
"225.",
|
|
2776
|
+
"226.",
|
|
2777
|
+
"227.",
|
|
2778
|
+
"228.",
|
|
2779
|
+
"229.",
|
|
2780
|
+
"230.",
|
|
2781
|
+
"231.",
|
|
2782
|
+
"232.",
|
|
2783
|
+
"233.",
|
|
2784
|
+
"234.",
|
|
2785
|
+
"235.",
|
|
2786
|
+
"236.",
|
|
2787
|
+
"237.",
|
|
2788
|
+
"238.",
|
|
2789
|
+
"239.",
|
|
2790
|
+
"240.",
|
|
2791
|
+
"241.",
|
|
2792
|
+
"242.",
|
|
2793
|
+
"243.",
|
|
2794
|
+
"244.",
|
|
2795
|
+
"245.",
|
|
2796
|
+
"246.",
|
|
2797
|
+
"247.",
|
|
2798
|
+
"248.",
|
|
2799
|
+
"249.",
|
|
2800
|
+
"250.",
|
|
2801
|
+
"251.",
|
|
2802
|
+
"252.",
|
|
2803
|
+
"253.",
|
|
2804
|
+
"254.",
|
|
2805
|
+
"255."
|
|
2806
|
+
];
|
|
2807
|
+
var ALLOWED_PROXY_PROTOCOLS = /* @__PURE__ */ new Set(["http:", "https:"]);
|
|
2808
|
+
function isBlockedIpv4(hostname) {
|
|
2809
|
+
const octets = hostname.split(".");
|
|
2810
|
+
if (octets.length !== 4) return false;
|
|
2811
|
+
const joined = hostname;
|
|
2812
|
+
return BLOCKED_IPV4_PREFIXES.some((prefix) => joined.startsWith(prefix));
|
|
2813
|
+
}
|
|
2814
|
+
function isBlockedIpv6(hostname) {
|
|
2815
|
+
const lower = hostname.toLowerCase();
|
|
2816
|
+
if (lower === "::1" || lower === "::" || lower === "0:0:0:0:0:0:0:1" || lower === "0:0:0:0:0:0:0:0") {
|
|
2817
|
+
return true;
|
|
2140
2818
|
}
|
|
2141
|
-
|
|
2142
|
-
|
|
2819
|
+
const ffffMatch = lower.match(/^::ffff:(\d+\.\d+\.\d+\.\d+)$/);
|
|
2820
|
+
if (ffffMatch) {
|
|
2821
|
+
return isBlockedIpv4(ffffMatch[1]);
|
|
2143
2822
|
}
|
|
2144
|
-
|
|
2823
|
+
const compatMatch = lower.match(/^::(\d+\.\d+\.\d+\.\d+)$/);
|
|
2824
|
+
if (compatMatch) {
|
|
2825
|
+
return isBlockedIpv4(compatMatch[1]);
|
|
2826
|
+
}
|
|
2827
|
+
return false;
|
|
2145
2828
|
}
|
|
2146
|
-
|
|
2147
|
-
|
|
2829
|
+
function isSafeHostname(hostname) {
|
|
2830
|
+
if (!hostname) return false;
|
|
2831
|
+
if (/^\d{1,3}(\.\d{1,3}){3}$/.test(hostname)) return !isBlockedIpv4(hostname);
|
|
2832
|
+
if (/^\[.*\]$/.test(hostname)) return !isBlockedIpv6(hostname.slice(1, -1));
|
|
2833
|
+
if (hostname.includes(":")) return !isBlockedIpv6(hostname);
|
|
2834
|
+
return true;
|
|
2148
2835
|
}
|
|
2149
|
-
async function
|
|
2150
|
-
|
|
2151
|
-
|
|
2836
|
+
async function safeResolveHostname(hostname) {
|
|
2837
|
+
try {
|
|
2838
|
+
const result = await dnsLookup(hostname);
|
|
2839
|
+
const ip = result.address;
|
|
2840
|
+
if (isBlockedIpv4(ip) || isBlockedIpv6(ip)) {
|
|
2841
|
+
throw new Error(`PM proxy target resolved to a blocked address: ${ip}`);
|
|
2842
|
+
}
|
|
2843
|
+
return ip;
|
|
2844
|
+
} catch (error) {
|
|
2845
|
+
if (error instanceof Error && error.message.includes("blocked address")) {
|
|
2846
|
+
throw error;
|
|
2847
|
+
}
|
|
2848
|
+
throw new Error(`PM proxy failed to resolve target hostname "${hostname}": ${error instanceof Error ? error.message : String(error)}`);
|
|
2152
2849
|
}
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2850
|
+
}
|
|
2851
|
+
async function validateProxyTargetUrl(target) {
|
|
2852
|
+
if (!ALLOWED_PROXY_PROTOCOLS.has(target.protocol)) {
|
|
2853
|
+
throw new Error(`PM proxy target protocol "${target.protocol}" is not allowed. Only http: and https: are permitted.`);
|
|
2854
|
+
}
|
|
2855
|
+
const hostname = target.hostname;
|
|
2856
|
+
if (!isSafeHostname(hostname)) {
|
|
2857
|
+
throw new Error(`PM proxy target "${hostname}" resolves to a blocked address and is not allowed.`);
|
|
2858
|
+
}
|
|
2859
|
+
if (!/^\d{1,3}(\.\d{1,3}){3}$/.test(hostname) && !/^\[.*\]$/.test(hostname)) {
|
|
2860
|
+
await safeResolveHostname(hostname);
|
|
2861
|
+
}
|
|
2862
|
+
}
|
|
2863
|
+
function sanitizeProxyRequestPath(requestUrl) {
|
|
2864
|
+
if (!requestUrl || requestUrl === "/") return "/";
|
|
2865
|
+
try {
|
|
2866
|
+
const normalizedInput = requestUrl.replace(/\\/g, "/");
|
|
2867
|
+
const parsed = new URL(normalizedInput, "http://placeholder");
|
|
2868
|
+
if (parsed.username || parsed.password || parsed.hostname !== "placeholder" || parsed.port) {
|
|
2869
|
+
return "/";
|
|
2157
2870
|
}
|
|
2158
|
-
|
|
2871
|
+
const pathname = parsed.pathname || "/";
|
|
2872
|
+
let decodedPathname = pathname;
|
|
2873
|
+
try {
|
|
2874
|
+
decodedPathname = decodeURIComponent(pathname);
|
|
2875
|
+
} catch {
|
|
2876
|
+
return "/";
|
|
2877
|
+
}
|
|
2878
|
+
const lowerPath = pathname.toLowerCase();
|
|
2879
|
+
if (lowerPath.includes("%2f") || lowerPath.includes("%5c") || lowerPath.includes("%40") || lowerPath.includes("%00")) {
|
|
2880
|
+
return "/";
|
|
2881
|
+
}
|
|
2882
|
+
const segments = decodedPathname.split("/");
|
|
2883
|
+
if (segments.some((segment) => segment === "." || segment === "..")) {
|
|
2884
|
+
return "/";
|
|
2885
|
+
}
|
|
2886
|
+
const sanitized = pathname + parsed.search;
|
|
2887
|
+
return sanitized.startsWith("/") ? sanitized || "/" : `/${sanitized}`;
|
|
2888
|
+
} catch {
|
|
2889
|
+
return "/";
|
|
2159
2890
|
}
|
|
2160
|
-
return !isProcessAlive(pid);
|
|
2161
2891
|
}
|
|
2162
|
-
|
|
2163
|
-
return
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2892
|
+
function resolvePmProxyHost(proxy) {
|
|
2893
|
+
return proxy.host?.trim() || "0.0.0.0";
|
|
2894
|
+
}
|
|
2895
|
+
function resolvePmProxyTargetHost(proxy) {
|
|
2896
|
+
return proxy.targetHost?.trim() || "127.0.0.1";
|
|
2897
|
+
}
|
|
2898
|
+
function resolvePmProxyEnvVar(proxy) {
|
|
2899
|
+
return proxy.envVar?.trim() || "PORT";
|
|
2900
|
+
}
|
|
2901
|
+
function buildPmProxyTargetUrl(proxy, targetPort) {
|
|
2902
|
+
return `http://${resolvePmProxyTargetHost(proxy)}:${targetPort}`;
|
|
2903
|
+
}
|
|
2904
|
+
function rewritePmProxyHealthCheckUrl(url, targetHost, targetPort) {
|
|
2905
|
+
const targetUrl = new URL(url);
|
|
2906
|
+
targetUrl.hostname = targetHost;
|
|
2907
|
+
targetUrl.port = String(targetPort);
|
|
2908
|
+
return targetUrl.toString();
|
|
2909
|
+
}
|
|
2910
|
+
async function allocatePmProxyTargetPort(host = "127.0.0.1") {
|
|
2911
|
+
const server = createNetServer();
|
|
2912
|
+
return await new Promise((resolve7, reject) => {
|
|
2913
|
+
server.once("error", reject);
|
|
2914
|
+
server.listen(0, host, () => {
|
|
2915
|
+
const address = server.address();
|
|
2916
|
+
if (!address || typeof address === "string") {
|
|
2917
|
+
server.close(() => reject(new Error("Failed to allocate an internal PM proxy port.")));
|
|
2174
2918
|
return;
|
|
2175
2919
|
}
|
|
2176
|
-
|
|
2177
|
-
|
|
2920
|
+
const port = address.port;
|
|
2921
|
+
server.close((error) => {
|
|
2922
|
+
if (error) {
|
|
2923
|
+
reject(error);
|
|
2924
|
+
return;
|
|
2925
|
+
}
|
|
2926
|
+
resolve7(port);
|
|
2927
|
+
});
|
|
2178
2928
|
});
|
|
2179
2929
|
});
|
|
2180
2930
|
}
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2931
|
+
function buildPmProxyHeaders(headersInput, host) {
|
|
2932
|
+
const headers = {};
|
|
2933
|
+
for (const [key, value] of Object.entries(headersInput)) {
|
|
2934
|
+
if (value !== void 0) {
|
|
2935
|
+
headers[key] = value;
|
|
2936
|
+
}
|
|
2937
|
+
}
|
|
2938
|
+
headers.host = host;
|
|
2939
|
+
return headers;
|
|
2940
|
+
}
|
|
2941
|
+
function writeRawHttpResponse(socket, statusCode, statusMessage, headers) {
|
|
2942
|
+
const lines = [`HTTP/1.1 ${statusCode} ${statusMessage}`];
|
|
2943
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
2944
|
+
if (value === void 0) {
|
|
2945
|
+
continue;
|
|
2946
|
+
}
|
|
2947
|
+
if (Array.isArray(value)) {
|
|
2948
|
+
for (const item of value) {
|
|
2949
|
+
lines.push(`${key}: ${item}`);
|
|
2185
2950
|
}
|
|
2186
|
-
|
|
2951
|
+
continue;
|
|
2952
|
+
}
|
|
2953
|
+
lines.push(`${key}: ${value}`);
|
|
2187
2954
|
}
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2955
|
+
socket.write(`${lines.join("\r\n")}\r
|
|
2956
|
+
\r
|
|
2957
|
+
`);
|
|
2958
|
+
}
|
|
2959
|
+
async function createPmProxyController(proxy) {
|
|
2960
|
+
let targets = [];
|
|
2961
|
+
let nextTargetIndex = 0;
|
|
2962
|
+
const setResolvedTargets = (nextTargets) => {
|
|
2963
|
+
const unchanged = nextTargets.length === targets.length && nextTargets.every((target, index) => targets[index]?.href === target.href);
|
|
2964
|
+
targets = nextTargets;
|
|
2965
|
+
if (targets.length === 0) {
|
|
2966
|
+
nextTargetIndex = 0;
|
|
2193
2967
|
return;
|
|
2194
2968
|
}
|
|
2195
|
-
if (
|
|
2196
|
-
|
|
2969
|
+
if (!unchanged) {
|
|
2970
|
+
nextTargetIndex = nextTargetIndex % targets.length;
|
|
2197
2971
|
}
|
|
2198
|
-
debounceTimer = setTimeout(() => {
|
|
2199
|
-
debounceTimer = null;
|
|
2200
|
-
onChange(normalizedPath);
|
|
2201
|
-
}, record.watchDebounce);
|
|
2202
|
-
debounceTimer.unref?.();
|
|
2203
2972
|
};
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2973
|
+
const pickTarget = () => {
|
|
2974
|
+
if (targets.length === 0) {
|
|
2975
|
+
return null;
|
|
2976
|
+
}
|
|
2977
|
+
const target = targets[nextTargetIndex % targets.length];
|
|
2978
|
+
nextTargetIndex = (nextTargetIndex + 1) % targets.length;
|
|
2979
|
+
return target;
|
|
2980
|
+
};
|
|
2981
|
+
const validateTarget = async (target) => {
|
|
2982
|
+
await validateProxyTargetUrl(target);
|
|
2983
|
+
};
|
|
2984
|
+
const server = createServer((req, res) => {
|
|
2985
|
+
const target = pickTarget();
|
|
2986
|
+
if (!target) {
|
|
2987
|
+
res.statusCode = 503;
|
|
2988
|
+
res.end("PM proxy target is not ready.");
|
|
2989
|
+
return;
|
|
2990
|
+
}
|
|
2991
|
+
const sanitizedPath = sanitizeProxyRequestPath(req.url || "/");
|
|
2992
|
+
const requestLib = target.protocol === "https:" ? httpsRequest : httpRequest;
|
|
2993
|
+
const headers = buildPmProxyHeaders(req.headers, target.host);
|
|
2994
|
+
if (!ALLOWED_PROXY_PROTOCOLS.has(target.protocol)) {
|
|
2995
|
+
res.statusCode = 400;
|
|
2996
|
+
res.end("PM proxy rejected unsafe target protocol.");
|
|
2997
|
+
return;
|
|
2998
|
+
}
|
|
2999
|
+
validateTarget(target).then(() => {
|
|
3000
|
+
const proxyReq = requestLib({
|
|
3001
|
+
protocol: target.protocol,
|
|
3002
|
+
hostname: target.hostname,
|
|
3003
|
+
port: target.port || void 0,
|
|
3004
|
+
path: sanitizedPath,
|
|
3005
|
+
method: req.method,
|
|
3006
|
+
headers
|
|
3007
|
+
}, (proxyRes) => {
|
|
3008
|
+
const outgoingHeaders = {};
|
|
3009
|
+
for (const [key, value] of Object.entries(proxyRes.headers)) {
|
|
3010
|
+
if (value !== void 0) {
|
|
3011
|
+
outgoingHeaders[key] = value;
|
|
3012
|
+
}
|
|
3013
|
+
}
|
|
3014
|
+
res.writeHead(proxyRes.statusCode || 200, outgoingHeaders);
|
|
3015
|
+
proxyRes.pipe(res);
|
|
3016
|
+
});
|
|
3017
|
+
proxyReq.on("error", (error) => {
|
|
3018
|
+
if (!res.headersSent) {
|
|
3019
|
+
res.statusCode = 502;
|
|
3020
|
+
}
|
|
3021
|
+
res.end(`PM proxy error: ${error.message}`);
|
|
3022
|
+
});
|
|
3023
|
+
req.pipe(proxyReq);
|
|
3024
|
+
}).catch((error) => {
|
|
3025
|
+
if (!res.headersSent) {
|
|
3026
|
+
res.statusCode = 403;
|
|
3027
|
+
}
|
|
3028
|
+
res.end(`PM proxy blocked target: ${error instanceof Error ? error.message : String(error)}`);
|
|
3029
|
+
});
|
|
3030
|
+
});
|
|
3031
|
+
server.on("upgrade", (req, socket, head) => {
|
|
3032
|
+
const target = pickTarget();
|
|
3033
|
+
if (!target) {
|
|
3034
|
+
writeRawHttpResponse(socket, 503, "Service Unavailable", {
|
|
3035
|
+
connection: "close",
|
|
3036
|
+
"content-length": 0
|
|
3037
|
+
});
|
|
3038
|
+
socket.destroy();
|
|
3039
|
+
return;
|
|
3040
|
+
}
|
|
3041
|
+
const sanitizedPath = sanitizeProxyRequestPath(req.url || "/");
|
|
3042
|
+
const requestLib = target.protocol === "https:" ? httpsRequest : httpRequest;
|
|
3043
|
+
const targetUrl = new URL(sanitizedPath, target);
|
|
3044
|
+
if (targetUrl.hostname !== target.hostname || targetUrl.port !== target.port || !ALLOWED_PROXY_PROTOCOLS.has(targetUrl.protocol)) {
|
|
3045
|
+
writeRawHttpResponse(socket, 400, "Bad Request", {
|
|
3046
|
+
connection: "close",
|
|
3047
|
+
"content-length": 0
|
|
3048
|
+
});
|
|
3049
|
+
socket.destroy();
|
|
3050
|
+
return;
|
|
3051
|
+
}
|
|
3052
|
+
validateTarget(target).then(() => {
|
|
3053
|
+
const proxyReq = requestLib(targetUrl, {
|
|
3054
|
+
method: req.method,
|
|
3055
|
+
headers: buildPmProxyHeaders(req.headers, target.host)
|
|
3056
|
+
});
|
|
3057
|
+
proxyReq.on("upgrade", (proxyRes, proxySocket, proxyHead) => {
|
|
3058
|
+
writeRawHttpResponse(socket, proxyRes.statusCode || 101, proxyRes.statusMessage || "Switching Protocols", proxyRes.headers);
|
|
3059
|
+
if (head.length > 0) {
|
|
3060
|
+
proxySocket.write(head);
|
|
3061
|
+
}
|
|
3062
|
+
if (proxyHead.length > 0) {
|
|
3063
|
+
socket.write(proxyHead);
|
|
3064
|
+
}
|
|
3065
|
+
socket.on("error", () => proxySocket.destroy());
|
|
3066
|
+
proxySocket.on("error", () => socket.destroy());
|
|
3067
|
+
proxySocket.pipe(socket);
|
|
3068
|
+
socket.pipe(proxySocket);
|
|
3069
|
+
});
|
|
3070
|
+
proxyReq.on("response", (proxyRes) => {
|
|
3071
|
+
writeRawHttpResponse(socket, proxyRes.statusCode || 502, proxyRes.statusMessage || "Bad Gateway", proxyRes.headers);
|
|
3072
|
+
proxyRes.pipe(socket);
|
|
3073
|
+
});
|
|
3074
|
+
proxyReq.on("error", (error) => {
|
|
3075
|
+
writeRawHttpResponse(socket, 502, "Bad Gateway", {
|
|
3076
|
+
connection: "close",
|
|
3077
|
+
"content-type": "text/plain; charset=utf-8",
|
|
3078
|
+
"content-length": Buffer.byteLength(`PM proxy error: ${error.message}`)
|
|
3079
|
+
});
|
|
3080
|
+
socket.end(`PM proxy error: ${error.message}`);
|
|
3081
|
+
});
|
|
3082
|
+
proxyReq.end();
|
|
3083
|
+
}).catch((error) => {
|
|
3084
|
+
writeRawHttpResponse(socket, 403, "Forbidden", {
|
|
3085
|
+
connection: "close",
|
|
3086
|
+
"content-type": "text/plain; charset=utf-8",
|
|
3087
|
+
"content-length": Buffer.byteLength(`PM proxy blocked target: ${error instanceof Error ? error.message : String(error)}`)
|
|
3088
|
+
});
|
|
3089
|
+
socket.end(`PM proxy blocked target: ${error instanceof Error ? error.message : String(error)}`);
|
|
3090
|
+
});
|
|
3091
|
+
});
|
|
3092
|
+
await new Promise((resolve7, reject) => {
|
|
3093
|
+
server.once("error", reject);
|
|
3094
|
+
server.listen(proxy.port, resolvePmProxyHost(proxy), () => resolve7());
|
|
3095
|
+
});
|
|
3096
|
+
return {
|
|
3097
|
+
setTarget(targetUrl) {
|
|
3098
|
+
if (targetUrl) {
|
|
3099
|
+
const parsed = new URL(targetUrl);
|
|
3100
|
+
validateProxyTargetUrl(parsed).then(() => {
|
|
3101
|
+
setResolvedTargets([parsed]);
|
|
3102
|
+
}).catch((error) => {
|
|
3103
|
+
console.error(`[PM proxy] Blocked setTarget: ${error instanceof Error ? error.message : String(error)}`);
|
|
3104
|
+
setResolvedTargets([]);
|
|
3105
|
+
});
|
|
3106
|
+
} else {
|
|
3107
|
+
setResolvedTargets([]);
|
|
3108
|
+
}
|
|
3109
|
+
},
|
|
3110
|
+
setTargets(targetUrls) {
|
|
3111
|
+
Promise.all(targetUrls.map((url) => validateProxyTargetUrl(new URL(url)))).then(() => {
|
|
3112
|
+
setResolvedTargets(targetUrls.map((targetUrl) => new URL(targetUrl)));
|
|
3113
|
+
}).catch((error) => {
|
|
3114
|
+
console.error(`[PM proxy] Blocked setTargets: ${error instanceof Error ? error.message : String(error)}`);
|
|
3115
|
+
setResolvedTargets([]);
|
|
3116
|
+
});
|
|
3117
|
+
},
|
|
3118
|
+
close() {
|
|
3119
|
+
return new Promise((resolve7, reject) => {
|
|
3120
|
+
server.close((error) => {
|
|
3121
|
+
if (error) {
|
|
3122
|
+
reject(error);
|
|
3123
|
+
return;
|
|
3124
|
+
}
|
|
3125
|
+
resolve7();
|
|
3126
|
+
});
|
|
3127
|
+
});
|
|
3128
|
+
}
|
|
3129
|
+
};
|
|
3130
|
+
}
|
|
3131
|
+
|
|
3132
|
+
// src/cli/pm/runner.ts
|
|
3133
|
+
function writePmLog(stream, message) {
|
|
3134
|
+
if (stream.writableEnded || stream.destroyed) {
|
|
3135
|
+
return;
|
|
3136
|
+
}
|
|
3137
|
+
try {
|
|
3138
|
+
stream.write(`[elit pm] ${(/* @__PURE__ */ new Date()).toISOString()} ${message}${EOL}`);
|
|
3139
|
+
} catch (error) {
|
|
3140
|
+
if (error.code !== "ERR_STREAM_WRITE_AFTER_END") {
|
|
3141
|
+
throw error;
|
|
3142
|
+
}
|
|
3143
|
+
}
|
|
3144
|
+
}
|
|
3145
|
+
function waitForExit(code, signal) {
|
|
3146
|
+
if (typeof code === "number") {
|
|
3147
|
+
return code;
|
|
3148
|
+
}
|
|
3149
|
+
if (signal === "SIGINT" || signal === "SIGTERM") {
|
|
3150
|
+
return 0;
|
|
3151
|
+
}
|
|
3152
|
+
return 1;
|
|
3153
|
+
}
|
|
3154
|
+
async function delay(milliseconds) {
|
|
3155
|
+
await new Promise((resolvePromise) => setTimeout(resolvePromise, milliseconds));
|
|
3156
|
+
}
|
|
3157
|
+
function resolveRunnerPathsFromRecordFile(filePath) {
|
|
3158
|
+
const appsDir = dirname4(filePath);
|
|
3159
|
+
const dataDir = dirname4(appsDir);
|
|
3160
|
+
return {
|
|
3161
|
+
dataDir,
|
|
3162
|
+
appsDir,
|
|
3163
|
+
logsDir: join6(dataDir, "logs"),
|
|
3164
|
+
dumpFile: join6(dataDir, DEFAULT_PM_DUMP_FILE)
|
|
3165
|
+
};
|
|
3166
|
+
}
|
|
3167
|
+
function usesPmProxyController(record) {
|
|
3168
|
+
return Boolean(record.proxy) && (record.proxy?.strategy ?? DEFAULT_PM_PROXY_STRATEGY) === "proxy";
|
|
3169
|
+
}
|
|
3170
|
+
function usesPmInheritedListener(record) {
|
|
3171
|
+
return Boolean(record.proxy) && (record.proxy?.strategy ?? DEFAULT_PM_PROXY_STRATEGY) === "inherit";
|
|
3172
|
+
}
|
|
3173
|
+
function isPmProxyOwner(record) {
|
|
3174
|
+
return usesPmProxyController(record) && record.instanceIndex === 1;
|
|
3175
|
+
}
|
|
3176
|
+
function resolvePmProxyTargetUrls(paths, baseName) {
|
|
3177
|
+
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));
|
|
3178
|
+
}
|
|
3179
|
+
async function createPmInheritedListener(proxy) {
|
|
3180
|
+
const server = createHttpServer();
|
|
3181
|
+
await new Promise((resolvePromise, reject) => {
|
|
3182
|
+
server.once("error", reject);
|
|
3183
|
+
server.listen(proxy.port, resolvePmProxyHost(proxy), () => resolvePromise());
|
|
3184
|
+
});
|
|
3185
|
+
return server;
|
|
3186
|
+
}
|
|
3187
|
+
function createPmChildIpcState(child, sharedListener) {
|
|
3188
|
+
let bootstrapReady = false;
|
|
3189
|
+
let listenerReady = false;
|
|
3190
|
+
let sharedHandleSent = false;
|
|
3191
|
+
const sendSharedHandle = () => {
|
|
3192
|
+
if (!sharedListener || !bootstrapReady || sharedHandleSent || !child.connected) {
|
|
3193
|
+
return;
|
|
3194
|
+
}
|
|
3195
|
+
sharedHandleSent = true;
|
|
3196
|
+
child.send?.({ type: "elit:pm:listen-handle" }, sharedListener);
|
|
3197
|
+
};
|
|
3198
|
+
const onMessage = (message) => {
|
|
3199
|
+
if (!message || typeof message !== "object") {
|
|
3200
|
+
return;
|
|
3201
|
+
}
|
|
3202
|
+
if (message.type === "elit:pm:bootstrap-ready") {
|
|
3203
|
+
bootstrapReady = true;
|
|
3204
|
+
sendSharedHandle();
|
|
3205
|
+
return;
|
|
3206
|
+
}
|
|
3207
|
+
if (message.type === "elit:pm:listener-ready") {
|
|
3208
|
+
listenerReady = true;
|
|
3209
|
+
}
|
|
3210
|
+
};
|
|
3211
|
+
child.on("message", onMessage);
|
|
3212
|
+
return {
|
|
3213
|
+
get bootstrapReady() {
|
|
3214
|
+
return bootstrapReady;
|
|
3215
|
+
},
|
|
3216
|
+
get listenerReady() {
|
|
3217
|
+
return listenerReady;
|
|
3218
|
+
},
|
|
3219
|
+
stop() {
|
|
3220
|
+
child.off("message", onMessage);
|
|
3221
|
+
}
|
|
3222
|
+
};
|
|
3223
|
+
}
|
|
3224
|
+
function buildPmChildEnv(record, command, targetPort) {
|
|
3225
|
+
return {
|
|
3226
|
+
...process.env,
|
|
3227
|
+
...record.env,
|
|
3228
|
+
...command.env,
|
|
3229
|
+
...usesPmProxyController(record) && targetPort ? {
|
|
3230
|
+
[resolvePmProxyEnvVar(record.proxy)]: String(targetPort),
|
|
3231
|
+
ELIT_PM_PUBLIC_PORT: String(record.proxy.port)
|
|
3232
|
+
} : {},
|
|
3233
|
+
ELIT_PM_NAME: record.name,
|
|
3234
|
+
ELIT_PM_ID: record.id
|
|
3235
|
+
};
|
|
3236
|
+
}
|
|
3237
|
+
function buildPmChildStdio(command, onlineStdinShutdownEnabled) {
|
|
3238
|
+
return [
|
|
3239
|
+
onlineStdinShutdownEnabled ? "pipe" : "ignore",
|
|
3240
|
+
"pipe",
|
|
3241
|
+
"pipe",
|
|
3242
|
+
...command.ipc ? ["ipc"] : []
|
|
3243
|
+
];
|
|
3244
|
+
}
|
|
3245
|
+
function createPmReadinessMonitor(record, onReady, onFailure, options) {
|
|
3246
|
+
if (options?.ipcController) {
|
|
3247
|
+
let stopped2 = false;
|
|
3248
|
+
let timer2 = null;
|
|
3249
|
+
let timeoutTimer2 = null;
|
|
3250
|
+
const clearTimers2 = () => {
|
|
3251
|
+
if (timer2) {
|
|
3252
|
+
clearInterval(timer2);
|
|
3253
|
+
timer2 = null;
|
|
3254
|
+
}
|
|
3255
|
+
if (timeoutTimer2) {
|
|
3256
|
+
clearTimeout(timeoutTimer2);
|
|
3257
|
+
timeoutTimer2 = null;
|
|
3258
|
+
}
|
|
3259
|
+
};
|
|
3260
|
+
const host = record.proxy?.host ?? "0.0.0.0";
|
|
3261
|
+
const port = record.proxy?.port ?? 0;
|
|
3262
|
+
timer2 = setInterval(() => {
|
|
3263
|
+
if (stopped2 || !options.ipcController?.listenerReady) {
|
|
3264
|
+
return;
|
|
3265
|
+
}
|
|
3266
|
+
stopped2 = true;
|
|
3267
|
+
clearTimers2();
|
|
3268
|
+
onReady(`shared listener ready on ${host}:${port}`);
|
|
3269
|
+
}, 25);
|
|
3270
|
+
timer2.unref?.();
|
|
3271
|
+
timeoutTimer2 = setTimeout(() => {
|
|
3272
|
+
if (stopped2) {
|
|
3273
|
+
return;
|
|
3274
|
+
}
|
|
3275
|
+
stopped2 = true;
|
|
3276
|
+
clearTimers2();
|
|
3277
|
+
onFailure(`listen timeout reached after ${record.listenTimeout}ms while waiting for shared listener ${host}:${port}`);
|
|
3278
|
+
}, record.listenTimeout);
|
|
3279
|
+
timeoutTimer2.unref?.();
|
|
3280
|
+
return {
|
|
3281
|
+
stop() {
|
|
3282
|
+
stopped2 = true;
|
|
3283
|
+
clearTimers2();
|
|
3284
|
+
}
|
|
3285
|
+
};
|
|
3286
|
+
}
|
|
3287
|
+
if (!record.waitReady || !record.healthCheck) {
|
|
3288
|
+
return {
|
|
3289
|
+
stop() {
|
|
3290
|
+
}
|
|
3291
|
+
};
|
|
3292
|
+
}
|
|
3293
|
+
const healthCheck = record.healthCheck;
|
|
3294
|
+
const pollInterval = Math.max(50, Math.min(healthCheck.interval, 250));
|
|
3295
|
+
let stopped = false;
|
|
3296
|
+
let timer = null;
|
|
3297
|
+
let timeoutTimer = null;
|
|
3298
|
+
let inFlight = false;
|
|
3299
|
+
const clearTimers = () => {
|
|
3300
|
+
if (timer) {
|
|
3301
|
+
clearInterval(timer);
|
|
3302
|
+
timer = null;
|
|
3303
|
+
}
|
|
3304
|
+
if (timeoutTimer) {
|
|
3305
|
+
clearTimeout(timeoutTimer);
|
|
3306
|
+
timeoutTimer = null;
|
|
3307
|
+
}
|
|
3308
|
+
};
|
|
3309
|
+
const runHealthCheck = async () => {
|
|
3310
|
+
if (stopped || inFlight) {
|
|
3311
|
+
return;
|
|
3312
|
+
}
|
|
3313
|
+
inFlight = true;
|
|
3314
|
+
const controller = new AbortController();
|
|
3315
|
+
const timeoutId = setTimeout(() => controller.abort(), healthCheck.timeout);
|
|
3316
|
+
timeoutId.unref?.();
|
|
3317
|
+
try {
|
|
3318
|
+
const response = await fetch(healthCheck.url, {
|
|
3319
|
+
method: "GET",
|
|
3320
|
+
signal: controller.signal
|
|
3321
|
+
});
|
|
3322
|
+
if (stopped) {
|
|
3323
|
+
return;
|
|
3324
|
+
}
|
|
3325
|
+
if (!response.ok) {
|
|
3326
|
+
throw new Error(`health check returned ${response.status}`);
|
|
3327
|
+
}
|
|
3328
|
+
stopped = true;
|
|
3329
|
+
clearTimers();
|
|
3330
|
+
onReady(`readiness check passed: ${healthCheck.url}`);
|
|
3331
|
+
} catch {
|
|
3332
|
+
} finally {
|
|
3333
|
+
clearTimeout(timeoutId);
|
|
3334
|
+
inFlight = false;
|
|
3335
|
+
}
|
|
3336
|
+
};
|
|
3337
|
+
timeoutTimer = setTimeout(() => {
|
|
3338
|
+
if (stopped) {
|
|
3339
|
+
return;
|
|
3340
|
+
}
|
|
3341
|
+
stopped = true;
|
|
3342
|
+
clearTimers();
|
|
3343
|
+
onFailure(`listen timeout reached after ${record.listenTimeout}ms while waiting for ${healthCheck.url}`);
|
|
3344
|
+
}, record.listenTimeout);
|
|
3345
|
+
timeoutTimer.unref?.();
|
|
3346
|
+
void runHealthCheck();
|
|
3347
|
+
timer = setInterval(() => {
|
|
3348
|
+
void runHealthCheck();
|
|
3349
|
+
}, pollInterval);
|
|
3350
|
+
timer.unref?.();
|
|
3351
|
+
return {
|
|
3352
|
+
stop() {
|
|
3353
|
+
stopped = true;
|
|
3354
|
+
clearTimers();
|
|
3355
|
+
}
|
|
3356
|
+
};
|
|
3357
|
+
}
|
|
3358
|
+
function resolvePmStopTimeout(record) {
|
|
3359
|
+
if (isPmOnlineWapkRecord(record)) {
|
|
3360
|
+
return Math.max(record.killTimeout, PM_WAPK_ONLINE_SHUTDOWN_TIMEOUT_MS);
|
|
3361
|
+
}
|
|
3362
|
+
return record.killTimeout;
|
|
3363
|
+
}
|
|
3364
|
+
function supportsPmProxyReload(record) {
|
|
3365
|
+
return Boolean(record.proxy) && record.instances === 1;
|
|
3366
|
+
}
|
|
3367
|
+
function buildPmMonitorRecord(record, targetPort) {
|
|
3368
|
+
if (!record.proxy || !targetPort) {
|
|
3369
|
+
return record;
|
|
3370
|
+
}
|
|
3371
|
+
const targetHost = resolvePmProxyTargetHost(record.proxy);
|
|
3372
|
+
return {
|
|
3373
|
+
...record,
|
|
3374
|
+
proxyTargetPort: targetPort,
|
|
3375
|
+
healthCheck: record.healthCheck ? {
|
|
3376
|
+
...record.healthCheck,
|
|
3377
|
+
url: rewritePmProxyHealthCheckUrl(record.healthCheck.url, targetHost, targetPort)
|
|
3378
|
+
} : void 0
|
|
3379
|
+
};
|
|
3380
|
+
}
|
|
3381
|
+
async function waitForProcessTermination(pid, timeoutMs) {
|
|
3382
|
+
if (!pid || !isProcessAlive(pid)) {
|
|
3383
|
+
return true;
|
|
3384
|
+
}
|
|
3385
|
+
const deadline = Date.now() + timeoutMs;
|
|
3386
|
+
while (Date.now() < deadline) {
|
|
3387
|
+
if (!isProcessAlive(pid)) {
|
|
3388
|
+
return true;
|
|
3389
|
+
}
|
|
3390
|
+
await delay(DEFAULT_PM_STOP_POLL_MS);
|
|
3391
|
+
}
|
|
3392
|
+
return !isProcessAlive(pid);
|
|
3393
|
+
}
|
|
3394
|
+
async function waitForManagedChildExit(child) {
|
|
3395
|
+
return await new Promise((resolvePromise) => {
|
|
3396
|
+
let resolved = false;
|
|
3397
|
+
child.once("error", (error) => {
|
|
3398
|
+
if (resolved) {
|
|
3399
|
+
return;
|
|
3400
|
+
}
|
|
3401
|
+
resolved = true;
|
|
3402
|
+
resolvePromise({ code: 1, signal: null, error: error instanceof Error ? error.message : String(error) });
|
|
3403
|
+
});
|
|
3404
|
+
child.once("close", (code, signal) => {
|
|
3405
|
+
if (resolved) {
|
|
3406
|
+
return;
|
|
3407
|
+
}
|
|
3408
|
+
resolved = true;
|
|
3409
|
+
resolvePromise({ code, signal });
|
|
3410
|
+
});
|
|
3411
|
+
});
|
|
3412
|
+
}
|
|
3413
|
+
async function createPmChildControllers(record, child, stdoutLog, stderrLog, requestPlannedRestart, onReady, options) {
|
|
3414
|
+
let healthMonitor = {
|
|
3415
|
+
stop() {
|
|
3416
|
+
}
|
|
3417
|
+
};
|
|
3418
|
+
let memoryMonitor = {
|
|
3419
|
+
stop() {
|
|
3420
|
+
}
|
|
3421
|
+
};
|
|
3422
|
+
let scheduleMonitor = {
|
|
3423
|
+
stop() {
|
|
3424
|
+
}
|
|
3425
|
+
};
|
|
3426
|
+
const startHealthMonitor = () => {
|
|
3427
|
+
healthMonitor = createPmHealthMonitor(
|
|
3428
|
+
record,
|
|
3429
|
+
(message) => requestPlannedRestart("health", message),
|
|
3430
|
+
(message) => writePmLog(stdoutLog, message)
|
|
3431
|
+
);
|
|
3432
|
+
};
|
|
3433
|
+
const needsReadySignal = Boolean(options?.ipcController) || record.waitReady;
|
|
3434
|
+
const readinessMonitor = options?.ready || !needsReadySignal ? { stop() {
|
|
3435
|
+
} } : createPmReadinessMonitor(
|
|
3436
|
+
record,
|
|
3437
|
+
(message) => {
|
|
3438
|
+
onReady(message);
|
|
3439
|
+
startHealthMonitor();
|
|
3440
|
+
},
|
|
3441
|
+
(message) => requestPlannedRestart("startup", message),
|
|
3442
|
+
{ ipcController: options?.ipcController }
|
|
3443
|
+
);
|
|
3444
|
+
const watchController = await createPmWatchController(
|
|
3445
|
+
record,
|
|
3446
|
+
(changedPath) => requestPlannedRestart("watch", changedPath),
|
|
3447
|
+
(message) => writePmLog(stderrLog, `watch error: ${message}`)
|
|
3448
|
+
);
|
|
3449
|
+
memoryMonitor = createPmMemoryMonitor(
|
|
3450
|
+
record,
|
|
3451
|
+
child.pid,
|
|
3452
|
+
(kind, message) => requestPlannedRestart(kind, message)
|
|
3453
|
+
);
|
|
3454
|
+
scheduleMonitor = createPmScheduleMonitor(
|
|
3455
|
+
record,
|
|
3456
|
+
(message) => requestPlannedRestart("cron", message),
|
|
3457
|
+
(message) => writePmLog(stdoutLog, message)
|
|
3458
|
+
);
|
|
3459
|
+
if (options?.ready || !needsReadySignal) {
|
|
3460
|
+
startHealthMonitor();
|
|
3461
|
+
}
|
|
3462
|
+
return {
|
|
3463
|
+
async stop() {
|
|
3464
|
+
await watchController.close();
|
|
3465
|
+
readinessMonitor.stop();
|
|
3466
|
+
healthMonitor.stop();
|
|
3467
|
+
memoryMonitor.stop();
|
|
3468
|
+
scheduleMonitor.stop();
|
|
3469
|
+
}
|
|
3470
|
+
};
|
|
3471
|
+
}
|
|
3472
|
+
async function waitForPmChildReady(record, child, ipcController) {
|
|
3473
|
+
if (!ipcController && (!record.waitReady || !record.healthCheck)) {
|
|
3474
|
+
return { ready: true };
|
|
3475
|
+
}
|
|
3476
|
+
let readyMessage;
|
|
3477
|
+
let failureMessage;
|
|
3478
|
+
let exitResult;
|
|
3479
|
+
const readinessMonitor = createPmReadinessMonitor(
|
|
3480
|
+
record,
|
|
3481
|
+
(message) => {
|
|
3482
|
+
readyMessage = message;
|
|
3483
|
+
},
|
|
3484
|
+
(message) => {
|
|
3485
|
+
failureMessage = message;
|
|
3486
|
+
},
|
|
3487
|
+
{ ipcController }
|
|
3488
|
+
);
|
|
3489
|
+
void waitForManagedChildExit(child).then((result) => {
|
|
3490
|
+
exitResult = result;
|
|
3491
|
+
});
|
|
3492
|
+
while (!readyMessage && !failureMessage && !exitResult) {
|
|
3493
|
+
await delay(25);
|
|
3494
|
+
}
|
|
3495
|
+
readinessMonitor.stop();
|
|
3496
|
+
if (readyMessage) {
|
|
3497
|
+
return { ready: true, message: readyMessage };
|
|
3498
|
+
}
|
|
3499
|
+
return {
|
|
3500
|
+
ready: false,
|
|
3501
|
+
message: failureMessage,
|
|
3502
|
+
exitResult
|
|
3503
|
+
};
|
|
3504
|
+
}
|
|
3505
|
+
async function stopProxyManagedChild(child, record, stderrLog) {
|
|
3506
|
+
if (!child.pid || !isProcessAlive(child.pid)) {
|
|
3507
|
+
return;
|
|
3508
|
+
}
|
|
3509
|
+
terminateProcessTree(child.pid);
|
|
3510
|
+
const stopTimeout = resolvePmStopTimeout(record);
|
|
3511
|
+
const stopped = await waitForProcessTermination(child.pid, stopTimeout);
|
|
3512
|
+
if (!stopped && child.pid && isProcessAlive(child.pid)) {
|
|
3513
|
+
writePmLog(stderrLog, `proxy handoff shutdown timed out after ${stopTimeout}ms; forcing process termination`);
|
|
3514
|
+
terminateProcessTree(child.pid, { force: true });
|
|
3515
|
+
await waitForProcessTermination(child.pid, DEFAULT_PM_STOP_POLL_MS);
|
|
3516
|
+
}
|
|
3517
|
+
}
|
|
3518
|
+
async function createPmWatchController(record, onChange, onError) {
|
|
3519
|
+
if (!record.watch || record.watchPaths.length === 0) {
|
|
3520
|
+
return {
|
|
3521
|
+
async close() {
|
|
3522
|
+
}
|
|
3523
|
+
};
|
|
3524
|
+
}
|
|
3525
|
+
const watcher = watch(record.watchPaths);
|
|
3526
|
+
let debounceTimer = null;
|
|
3527
|
+
const scheduleRestart = (filePath) => {
|
|
3528
|
+
const normalizedPath = filePath.replace(/\\/g, "/");
|
|
3529
|
+
if (isIgnoredWatchPath(normalizedPath, record.watchIgnore)) {
|
|
3530
|
+
return;
|
|
3531
|
+
}
|
|
3532
|
+
if (debounceTimer) {
|
|
3533
|
+
clearTimeout(debounceTimer);
|
|
3534
|
+
}
|
|
3535
|
+
debounceTimer = setTimeout(() => {
|
|
3536
|
+
debounceTimer = null;
|
|
3537
|
+
onChange(normalizedPath);
|
|
3538
|
+
}, record.watchDebounce);
|
|
3539
|
+
debounceTimer.unref?.();
|
|
3540
|
+
};
|
|
3541
|
+
watcher.on("add", scheduleRestart);
|
|
3542
|
+
watcher.on("change", scheduleRestart);
|
|
3543
|
+
watcher.on("unlink", scheduleRestart);
|
|
3544
|
+
watcher.on("error", (error) => onError(error instanceof Error ? error.message : String(error)));
|
|
3545
|
+
return {
|
|
3546
|
+
async close() {
|
|
3547
|
+
if (debounceTimer) {
|
|
2211
3548
|
clearTimeout(debounceTimer);
|
|
2212
3549
|
debounceTimer = null;
|
|
2213
3550
|
}
|
|
@@ -2241,11 +3578,17 @@ function createPmHealthMonitor(record, onFailure, onLog) {
|
|
|
2241
3578
|
method: "GET",
|
|
2242
3579
|
signal: controller.signal
|
|
2243
3580
|
});
|
|
3581
|
+
if (stopped) {
|
|
3582
|
+
return;
|
|
3583
|
+
}
|
|
2244
3584
|
if (!response.ok) {
|
|
2245
3585
|
throw new Error(`health check returned ${response.status}`);
|
|
2246
3586
|
}
|
|
2247
3587
|
failureCount = 0;
|
|
2248
3588
|
} catch (error) {
|
|
3589
|
+
if (stopped) {
|
|
3590
|
+
return;
|
|
3591
|
+
}
|
|
2249
3592
|
failureCount += 1;
|
|
2250
3593
|
const message = error instanceof Error ? error.message : String(error);
|
|
2251
3594
|
onLog(`health check failed (${failureCount}/${healthCheck.maxFailures}): ${message}`);
|
|
@@ -2280,6 +3623,90 @@ function createPmHealthMonitor(record, onFailure, onLog) {
|
|
|
2280
3623
|
}
|
|
2281
3624
|
};
|
|
2282
3625
|
}
|
|
3626
|
+
function createPmMemoryMonitor(record, pid, onFailure) {
|
|
3627
|
+
if (!record.maxMemoryBytes || !pid) {
|
|
3628
|
+
return {
|
|
3629
|
+
stop() {
|
|
3630
|
+
}
|
|
3631
|
+
};
|
|
3632
|
+
}
|
|
3633
|
+
const memoryLimit = record.maxMemoryBytes;
|
|
3634
|
+
let stopped = false;
|
|
3635
|
+
const timer = setInterval(() => {
|
|
3636
|
+
if (stopped) {
|
|
3637
|
+
return;
|
|
3638
|
+
}
|
|
3639
|
+
const memoryRssBytes = samplePmProcessMetrics(pid).memoryRssBytes;
|
|
3640
|
+
if (memoryRssBytes === void 0 || memoryRssBytes <= memoryLimit) {
|
|
3641
|
+
return;
|
|
3642
|
+
}
|
|
3643
|
+
stopped = true;
|
|
3644
|
+
onFailure(record.memoryAction === "stop" ? "memory-stop" : "memory", `memory usage ${memoryRssBytes} exceeded limit ${memoryLimit}`);
|
|
3645
|
+
}, DEFAULT_PM_MEMORY_CHECK_INTERVAL);
|
|
3646
|
+
timer.unref?.();
|
|
3647
|
+
return {
|
|
3648
|
+
stop() {
|
|
3649
|
+
stopped = true;
|
|
3650
|
+
clearInterval(timer);
|
|
3651
|
+
}
|
|
3652
|
+
};
|
|
3653
|
+
}
|
|
3654
|
+
function createPmScheduleMonitor(record, onTrigger, onLog) {
|
|
3655
|
+
if (!record.cronRestart) {
|
|
3656
|
+
return {
|
|
3657
|
+
stop() {
|
|
3658
|
+
}
|
|
3659
|
+
};
|
|
3660
|
+
}
|
|
3661
|
+
const schedule = parsePmRestartSchedule(record.cronRestart, "pm cronRestart");
|
|
3662
|
+
let stopped = false;
|
|
3663
|
+
let timer = null;
|
|
3664
|
+
const armTimer = (from = /* @__PURE__ */ new Date()) => {
|
|
3665
|
+
const nextOccurrence = resolveNextPmScheduleOccurrence(schedule, from);
|
|
3666
|
+
if (!nextOccurrence) {
|
|
3667
|
+
onLog(`schedule has no next occurrence: ${record.cronRestart}`);
|
|
3668
|
+
return;
|
|
3669
|
+
}
|
|
3670
|
+
const delayMs = Math.max(0, nextOccurrence.getTime() - Date.now());
|
|
3671
|
+
timer = setTimeout(() => {
|
|
3672
|
+
timer = null;
|
|
3673
|
+
if (stopped) {
|
|
3674
|
+
return;
|
|
3675
|
+
}
|
|
3676
|
+
onTrigger(`restart schedule matched: ${record.cronRestart}`);
|
|
3677
|
+
}, delayMs);
|
|
3678
|
+
timer.unref?.();
|
|
3679
|
+
};
|
|
3680
|
+
armTimer();
|
|
3681
|
+
return {
|
|
3682
|
+
stop() {
|
|
3683
|
+
stopped = true;
|
|
3684
|
+
if (timer) {
|
|
3685
|
+
clearTimeout(timer);
|
|
3686
|
+
timer = null;
|
|
3687
|
+
}
|
|
3688
|
+
}
|
|
3689
|
+
};
|
|
3690
|
+
}
|
|
3691
|
+
function resolvePmRestartDelay(record, restartCount, shouldApplyBackoff) {
|
|
3692
|
+
if (!shouldApplyBackoff || !record.expBackoffRestartDelay) {
|
|
3693
|
+
return record.restartDelay;
|
|
3694
|
+
}
|
|
3695
|
+
const exponent = Math.max(0, restartCount - 1);
|
|
3696
|
+
return Math.min(record.expBackoffRestartDelay * 2 ** exponent, record.expBackoffRestartMaxDelay ?? DEFAULT_PM_EXP_BACKOFF_MAX_DELAY);
|
|
3697
|
+
}
|
|
3698
|
+
function resolvePmRestartCountBase(record, wasStable, restartKind) {
|
|
3699
|
+
if (wasStable) {
|
|
3700
|
+
return 0;
|
|
3701
|
+
}
|
|
3702
|
+
if (record.restartWindow && record.lastRestartAt) {
|
|
3703
|
+
const lastRestartTime = Date.parse(record.lastRestartAt);
|
|
3704
|
+
if (!Number.isNaN(lastRestartTime) && Date.now() - lastRestartTime > record.restartWindow) {
|
|
3705
|
+
return 0;
|
|
3706
|
+
}
|
|
3707
|
+
}
|
|
3708
|
+
return restartKind === "watch" ? record.restartCount ?? 0 : record.restartCount ?? 0;
|
|
3709
|
+
}
|
|
2283
3710
|
function readPlannedRestartRequest(state) {
|
|
2284
3711
|
return state.request;
|
|
2285
3712
|
}
|
|
@@ -2289,10 +3716,20 @@ async function runManagedProcessLoop(filePath, initialRecord) {
|
|
|
2289
3716
|
let activeChildStopTimer = null;
|
|
2290
3717
|
let stopRequested = false;
|
|
2291
3718
|
const restartState = { request: null };
|
|
2292
|
-
mkdirSync3(
|
|
2293
|
-
mkdirSync3(
|
|
3719
|
+
mkdirSync3(dirname4(initialRecord.logFiles.out), { recursive: true });
|
|
3720
|
+
mkdirSync3(dirname4(initialRecord.logFiles.err), { recursive: true });
|
|
2294
3721
|
const stdoutLog = createWriteStream(initialRecord.logFiles.out, { flags: "a" });
|
|
2295
3722
|
const stderrLog = createWriteStream(initialRecord.logFiles.err, { flags: "a" });
|
|
3723
|
+
let proxyController = null;
|
|
3724
|
+
let proxyTargetSyncTimer = null;
|
|
3725
|
+
let inheritedListener = null;
|
|
3726
|
+
const runnerPaths = resolveRunnerPathsFromRecordFile(filePath);
|
|
3727
|
+
const syncOwnedProxyTargets = (baseName) => {
|
|
3728
|
+
if (!proxyController) {
|
|
3729
|
+
return;
|
|
3730
|
+
}
|
|
3731
|
+
proxyController.setTargets(resolvePmProxyTargetUrls(runnerPaths, baseName));
|
|
3732
|
+
};
|
|
2296
3733
|
const persist = (mutator) => {
|
|
2297
3734
|
const current = readLatestPmRecord(filePath, record);
|
|
2298
3735
|
record = mutator(current);
|
|
@@ -2305,26 +3742,30 @@ async function runManagedProcessLoop(filePath, initialRecord) {
|
|
|
2305
3742
|
activeChildStopTimer = null;
|
|
2306
3743
|
}
|
|
2307
3744
|
};
|
|
3745
|
+
const scheduleForcedActiveChildStop = (timeoutMs, reason) => {
|
|
3746
|
+
if (!activeChild?.pid || process.platform === "win32") {
|
|
3747
|
+
return;
|
|
3748
|
+
}
|
|
3749
|
+
clearActiveChildStopTimer();
|
|
3750
|
+
activeChildStopTimer = setTimeout(() => {
|
|
3751
|
+
if (activeChild?.pid && isProcessAlive(activeChild.pid)) {
|
|
3752
|
+
writePmLog(stderrLog, `${reason} after ${timeoutMs}ms; forcing process termination`);
|
|
3753
|
+
terminateProcessTree(activeChild.pid, { force: true });
|
|
3754
|
+
}
|
|
3755
|
+
}, timeoutMs);
|
|
3756
|
+
activeChildStopTimer.unref?.();
|
|
3757
|
+
};
|
|
2308
3758
|
const stopActiveChild = () => {
|
|
2309
3759
|
if (!activeChild?.pid || !isProcessAlive(activeChild.pid)) {
|
|
2310
3760
|
return;
|
|
2311
3761
|
}
|
|
2312
3762
|
const current = readLatestPmRecord(filePath, record);
|
|
3763
|
+
const stopTimeout = resolvePmStopTimeout(current);
|
|
2313
3764
|
if (isPmOnlineWapkRecord(current) && activeChild.stdin && !activeChild.stdin.destroyed && activeChild.stdin.writable) {
|
|
2314
3765
|
try {
|
|
2315
3766
|
activeChild.stdin.end(`${PM_WAPK_ONLINE_SHUTDOWN_COMMAND}
|
|
2316
3767
|
`);
|
|
2317
|
-
|
|
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?.();
|
|
3768
|
+
scheduleForcedActiveChildStop(stopTimeout, "graceful WAPK online shutdown timed out");
|
|
2328
3769
|
return;
|
|
2329
3770
|
} catch (error) {
|
|
2330
3771
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -2332,6 +3773,7 @@ async function runManagedProcessLoop(filePath, initialRecord) {
|
|
|
2332
3773
|
}
|
|
2333
3774
|
}
|
|
2334
3775
|
terminateProcessTree(activeChild.pid);
|
|
3776
|
+
scheduleForcedActiveChildStop(stopTimeout, "graceful shutdown timed out");
|
|
2335
3777
|
};
|
|
2336
3778
|
const requestManagedStop = (reason) => {
|
|
2337
3779
|
if (stopRequested) {
|
|
@@ -2369,6 +3811,17 @@ async function runManagedProcessLoop(filePath, initialRecord) {
|
|
|
2369
3811
|
let command;
|
|
2370
3812
|
try {
|
|
2371
3813
|
command = buildPmCommand(latest);
|
|
3814
|
+
if (latest.proxy && isPmProxyOwner(latest) && !proxyController) {
|
|
3815
|
+
proxyController = await createPmProxyController(latest.proxy);
|
|
3816
|
+
syncOwnedProxyTargets(latest.baseName);
|
|
3817
|
+
if (!proxyTargetSyncTimer) {
|
|
3818
|
+
proxyTargetSyncTimer = setInterval(() => syncOwnedProxyTargets(latest.baseName), 50);
|
|
3819
|
+
proxyTargetSyncTimer.unref?.();
|
|
3820
|
+
}
|
|
3821
|
+
}
|
|
3822
|
+
if (latest.proxy && usesPmInheritedListener(latest) && !inheritedListener) {
|
|
3823
|
+
inheritedListener = await createPmInheritedListener(latest.proxy);
|
|
3824
|
+
}
|
|
2372
3825
|
} catch (error) {
|
|
2373
3826
|
const message = error instanceof Error ? error.message : String(error);
|
|
2374
3827
|
writePmLog(stderrLog, message);
|
|
@@ -2378,25 +3831,24 @@ async function runManagedProcessLoop(filePath, initialRecord) {
|
|
|
2378
3831
|
error: message,
|
|
2379
3832
|
runnerPid: void 0,
|
|
2380
3833
|
childPid: void 0,
|
|
3834
|
+
proxyTargetPort: void 0,
|
|
3835
|
+
proxyReadyAt: void 0,
|
|
2381
3836
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2382
3837
|
}));
|
|
2383
3838
|
return;
|
|
2384
3839
|
}
|
|
2385
3840
|
const onlineStdinShutdownEnabled = isPmOnlineWapkRecord(latest);
|
|
2386
|
-
const
|
|
3841
|
+
const initialTargetPort = usesPmProxyController(latest) ? await allocatePmProxyTargetPort(resolvePmProxyTargetHost(latest.proxy)) : void 0;
|
|
3842
|
+
const monitorRecord = buildPmMonitorRecord(latest, initialTargetPort);
|
|
3843
|
+
let child = spawn2(command.command, command.args, {
|
|
2387
3844
|
cwd: latest.cwd,
|
|
2388
|
-
env:
|
|
2389
|
-
|
|
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"],
|
|
3845
|
+
env: buildPmChildEnv(latest, command, initialTargetPort),
|
|
3846
|
+
stdio: buildPmChildStdio(command, onlineStdinShutdownEnabled),
|
|
2396
3847
|
windowsHide: true,
|
|
2397
3848
|
shell: command.shell
|
|
2398
3849
|
});
|
|
2399
|
-
|
|
3850
|
+
let childStartedAt = Date.now();
|
|
3851
|
+
let childIpcState = command.ipc ? createPmChildIpcState(child, inheritedListener) : void 0;
|
|
2400
3852
|
activeChild = child;
|
|
2401
3853
|
if (child.stdout) {
|
|
2402
3854
|
child.stdout.pipe(stdoutLog, { end: false });
|
|
@@ -2404,26 +3856,38 @@ async function runManagedProcessLoop(filePath, initialRecord) {
|
|
|
2404
3856
|
if (child.stderr) {
|
|
2405
3857
|
child.stderr.pipe(stderrLog, { end: false });
|
|
2406
3858
|
}
|
|
3859
|
+
let childWaitState = { settled: false, result: void 0 };
|
|
3860
|
+
void waitForManagedChildExit(child).then((result) => {
|
|
3861
|
+
childWaitState.result = result;
|
|
3862
|
+
childWaitState.settled = true;
|
|
3863
|
+
});
|
|
2407
3864
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3865
|
+
const waitingForReady = latest.waitReady || Boolean(childIpcState);
|
|
2408
3866
|
persist((current2) => ({
|
|
2409
3867
|
...current2,
|
|
2410
|
-
status: "online",
|
|
3868
|
+
status: waitingForReady ? "starting" : "online",
|
|
2411
3869
|
commandPreview: command.preview,
|
|
2412
3870
|
runtime: command.runtime ?? current2.runtime,
|
|
2413
3871
|
runnerPid: process.pid,
|
|
2414
3872
|
childPid: child.pid,
|
|
3873
|
+
proxyTargetPort: initialTargetPort,
|
|
3874
|
+
proxyReadyAt: !waitingForReady && usesPmProxyController(latest) ? startedAt : void 0,
|
|
2415
3875
|
startedAt,
|
|
2416
3876
|
stoppedAt: void 0,
|
|
3877
|
+
reloadRequestedAt: void 0,
|
|
2417
3878
|
error: void 0,
|
|
2418
3879
|
updatedAt: startedAt
|
|
2419
3880
|
}));
|
|
2420
3881
|
writePmLog(stdoutLog, `started ${command.preview}${child.pid ? ` (pid ${child.pid})` : ""}`);
|
|
3882
|
+
if (isPmProxyOwner(latest) && !waitingForReady) {
|
|
3883
|
+
syncOwnedProxyTargets(latest.baseName);
|
|
3884
|
+
}
|
|
2421
3885
|
const requestPlannedRestart = (kind, detail) => {
|
|
2422
3886
|
if (stopRequested || restartState.request) {
|
|
2423
3887
|
return;
|
|
2424
3888
|
}
|
|
2425
3889
|
restartState.request = { kind, detail };
|
|
2426
|
-
writePmLog(kind === "
|
|
3890
|
+
writePmLog(kind === "watch" || kind === "cron" ? stdoutLog : stderrLog, `${kind} restart requested: ${detail}`);
|
|
2427
3891
|
persist((current2) => ({
|
|
2428
3892
|
...current2,
|
|
2429
3893
|
status: "restarting",
|
|
@@ -2431,27 +3895,124 @@ async function runManagedProcessLoop(filePath, initialRecord) {
|
|
|
2431
3895
|
}));
|
|
2432
3896
|
stopActiveChild();
|
|
2433
3897
|
};
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
3898
|
+
let controllers = await createPmChildControllers(
|
|
3899
|
+
monitorRecord,
|
|
3900
|
+
child,
|
|
3901
|
+
stdoutLog,
|
|
3902
|
+
stderrLog,
|
|
3903
|
+
requestPlannedRestart,
|
|
3904
|
+
(message) => {
|
|
3905
|
+
const readyAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3906
|
+
if (isPmProxyOwner(latest)) {
|
|
3907
|
+
syncOwnedProxyTargets(latest.baseName);
|
|
3908
|
+
}
|
|
3909
|
+
persist((current2) => ({
|
|
3910
|
+
...current2,
|
|
3911
|
+
status: "online",
|
|
3912
|
+
proxyTargetPort: initialTargetPort,
|
|
3913
|
+
proxyReadyAt: readyAt,
|
|
3914
|
+
updatedAt: readyAt
|
|
3915
|
+
}));
|
|
3916
|
+
writePmLog(stdoutLog, message);
|
|
3917
|
+
},
|
|
3918
|
+
{ ready: !waitingForReady, ipcController: childIpcState }
|
|
2443
3919
|
);
|
|
2444
|
-
|
|
3920
|
+
let handledReloadAt = latest.reloadRequestedAt;
|
|
3921
|
+
while (!childWaitState.settled) {
|
|
2445
3922
|
const latestRecord = readLatestPmRecord(filePath, record);
|
|
2446
|
-
if (
|
|
3923
|
+
if (latestRecord.desiredState === "stopped" && !stopRequested) {
|
|
2447
3924
|
requestManagedStop("stop requested by PM control state");
|
|
2448
3925
|
}
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
3926
|
+
const reloadRequestedAt = latestRecord.reloadRequestedAt;
|
|
3927
|
+
if (!stopRequested && supportsPmProxyReload(latestRecord) && reloadRequestedAt && reloadRequestedAt !== handledReloadAt && latestRecord.proxy) {
|
|
3928
|
+
handledReloadAt = reloadRequestedAt;
|
|
3929
|
+
const replacementTargetPort = usesPmProxyController(latestRecord) ? await allocatePmProxyTargetPort(resolvePmProxyTargetHost(latestRecord.proxy)) : void 0;
|
|
3930
|
+
const replacementMonitorRecord = buildPmMonitorRecord(latestRecord, replacementTargetPort);
|
|
3931
|
+
const replacementChild = spawn2(command.command, command.args, {
|
|
3932
|
+
cwd: latestRecord.cwd,
|
|
3933
|
+
env: buildPmChildEnv(latestRecord, command, replacementTargetPort),
|
|
3934
|
+
stdio: buildPmChildStdio(command, onlineStdinShutdownEnabled),
|
|
3935
|
+
windowsHide: true,
|
|
3936
|
+
shell: command.shell
|
|
3937
|
+
});
|
|
3938
|
+
const replacementIpcState = command.ipc ? createPmChildIpcState(replacementChild, inheritedListener) : void 0;
|
|
3939
|
+
if (replacementChild.stdout) {
|
|
3940
|
+
replacementChild.stdout.pipe(stdoutLog, { end: false });
|
|
3941
|
+
}
|
|
3942
|
+
if (replacementChild.stderr) {
|
|
3943
|
+
replacementChild.stderr.pipe(stderrLog, { end: false });
|
|
3944
|
+
}
|
|
3945
|
+
writePmLog(stdoutLog, `starting ${usesPmInheritedListener(latestRecord) ? "shared-listener" : "proxy handoff"} replacement${replacementChild.pid ? ` (pid ${replacementChild.pid})` : ""}`);
|
|
3946
|
+
const readyResult = await waitForPmChildReady(replacementMonitorRecord, replacementChild, replacementIpcState);
|
|
3947
|
+
if (!readyResult.ready) {
|
|
3948
|
+
writePmLog(stderrLog, readyResult.message ?? "replacement exited before becoming ready");
|
|
3949
|
+
replacementIpcState?.stop();
|
|
3950
|
+
await stopProxyManagedChild(replacementChild, latestRecord, stderrLog);
|
|
3951
|
+
persist((current2) => ({
|
|
3952
|
+
...current2,
|
|
3953
|
+
status: "online",
|
|
3954
|
+
proxyReadyAt: current2.proxyReadyAt,
|
|
3955
|
+
reloadRequestedAt: void 0,
|
|
3956
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3957
|
+
}));
|
|
3958
|
+
continue;
|
|
3959
|
+
}
|
|
3960
|
+
const previousChild = child;
|
|
3961
|
+
const previousControllers = controllers;
|
|
3962
|
+
const previousIpcState = childIpcState;
|
|
3963
|
+
const handoffAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3964
|
+
if (usesPmProxyController(latestRecord) && replacementTargetPort) {
|
|
3965
|
+
proxyController?.setTarget(buildPmProxyTargetUrl(latestRecord.proxy, replacementTargetPort));
|
|
3966
|
+
}
|
|
3967
|
+
persist((current2) => ({
|
|
3968
|
+
...current2,
|
|
3969
|
+
status: "online",
|
|
3970
|
+
childPid: replacementChild.pid,
|
|
3971
|
+
proxyTargetPort: replacementTargetPort,
|
|
3972
|
+
proxyReadyAt: handoffAt,
|
|
3973
|
+
startedAt: handoffAt,
|
|
3974
|
+
reloadRequestedAt: void 0,
|
|
3975
|
+
error: void 0,
|
|
3976
|
+
updatedAt: handoffAt
|
|
3977
|
+
}));
|
|
3978
|
+
if (readyResult.message) {
|
|
3979
|
+
writePmLog(stdoutLog, readyResult.message);
|
|
3980
|
+
}
|
|
3981
|
+
writePmLog(stdoutLog, `${usesPmInheritedListener(latestRecord) ? "shared listener" : "proxy handoff"} activated on ${latestRecord.proxy.host ?? "0.0.0.0"}:${latestRecord.proxy.port}`);
|
|
3982
|
+
child = replacementChild;
|
|
3983
|
+
childIpcState = replacementIpcState;
|
|
3984
|
+
childStartedAt = Date.now();
|
|
3985
|
+
activeChild = replacementChild;
|
|
3986
|
+
childWaitState = { settled: false, result: void 0 };
|
|
3987
|
+
void waitForManagedChildExit(replacementChild).then((result) => {
|
|
3988
|
+
childWaitState.result = result;
|
|
3989
|
+
childWaitState.settled = true;
|
|
3990
|
+
});
|
|
3991
|
+
controllers = await createPmChildControllers(
|
|
3992
|
+
replacementMonitorRecord,
|
|
3993
|
+
replacementChild,
|
|
3994
|
+
stdoutLog,
|
|
3995
|
+
stderrLog,
|
|
3996
|
+
requestPlannedRestart,
|
|
3997
|
+
() => {
|
|
3998
|
+
},
|
|
3999
|
+
{ ready: true, ipcController: replacementIpcState }
|
|
4000
|
+
);
|
|
4001
|
+
await delay(250);
|
|
4002
|
+
await previousControllers.stop();
|
|
4003
|
+
previousIpcState?.stop();
|
|
4004
|
+
await stopProxyManagedChild(previousChild, latestRecord, stderrLog);
|
|
4005
|
+
clearActiveChildStopTimer();
|
|
4006
|
+
if (isPmProxyOwner(latestRecord)) {
|
|
4007
|
+
syncOwnedProxyTargets(latestRecord.baseName);
|
|
4008
|
+
}
|
|
4009
|
+
continue;
|
|
4010
|
+
}
|
|
4011
|
+
await delay(25);
|
|
4012
|
+
}
|
|
4013
|
+
const exitResult = childWaitState.result;
|
|
4014
|
+
await controllers.stop();
|
|
4015
|
+
childIpcState?.stop();
|
|
2455
4016
|
clearActiveChildStopTimer();
|
|
2456
4017
|
activeChild = null;
|
|
2457
4018
|
const exitCode = waitForExit(exitResult.code, exitResult.signal);
|
|
@@ -2467,56 +4028,77 @@ async function runManagedProcessLoop(filePath, initialRecord) {
|
|
|
2467
4028
|
if (stopRequested || current.desiredState === "stopped") {
|
|
2468
4029
|
break;
|
|
2469
4030
|
}
|
|
2470
|
-
const shouldRestartForExit = plannedRestart ? true : current.restartPolicy === "always" ? true : current.restartPolicy === "on-failure" ? exitCode !== 0 || Boolean(exitResult.error) : false;
|
|
4031
|
+
const shouldRestartForExit = plannedRestart?.kind === "memory-stop" ? false : plannedRestart ? true : current.restartPolicy === "always" ? true : current.restartPolicy === "on-failure" ? exitCode !== 0 || Boolean(exitResult.error) : false;
|
|
2471
4032
|
if (!shouldRestartForExit) {
|
|
2472
4033
|
persist((latestRecord) => ({
|
|
2473
4034
|
...latestRecord,
|
|
2474
|
-
status: exitCode === 0 && !exitResult.error ? "exited" : "errored",
|
|
4035
|
+
status: plannedRestart?.kind === "memory-stop" ? "errored" : exitCode === 0 && !exitResult.error ? "exited" : "errored",
|
|
2475
4036
|
childPid: void 0,
|
|
4037
|
+
proxyTargetPort: void 0,
|
|
4038
|
+
proxyReadyAt: void 0,
|
|
2476
4039
|
runnerPid: void 0,
|
|
2477
4040
|
lastExitCode: exitCode,
|
|
2478
|
-
|
|
4041
|
+
reloadRequestedAt: void 0,
|
|
4042
|
+
error: plannedRestart?.kind === "memory-stop" ? plannedRestart.detail : exitCode === 0 && !exitResult.error ? void 0 : exitResult.error ?? `Process exited with code ${exitCode}.`,
|
|
2479
4043
|
stoppedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2480
4044
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2481
4045
|
}));
|
|
4046
|
+
if (isPmProxyOwner(current)) {
|
|
4047
|
+
syncOwnedProxyTargets(current.baseName);
|
|
4048
|
+
}
|
|
2482
4049
|
return;
|
|
2483
4050
|
}
|
|
2484
4051
|
const shouldCountRestart = plannedRestart?.kind !== "watch";
|
|
2485
|
-
const baseRestartCount =
|
|
4052
|
+
const baseRestartCount = resolvePmRestartCountBase(current, wasStable, plannedRestart?.kind);
|
|
2486
4053
|
const nextRestartCount = shouldCountRestart ? baseRestartCount + 1 : current.restartCount ?? 0;
|
|
2487
4054
|
if (nextRestartCount > current.maxRestarts) {
|
|
2488
4055
|
persist((latestRecord) => ({
|
|
2489
4056
|
...latestRecord,
|
|
2490
4057
|
status: "errored",
|
|
2491
4058
|
childPid: void 0,
|
|
4059
|
+
proxyTargetPort: void 0,
|
|
4060
|
+
proxyReadyAt: void 0,
|
|
2492
4061
|
runnerPid: void 0,
|
|
2493
4062
|
restartCount: nextRestartCount,
|
|
4063
|
+
lastRestartAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2494
4064
|
lastExitCode: exitCode,
|
|
4065
|
+
reloadRequestedAt: void 0,
|
|
2495
4066
|
error: plannedRestart ? `Reached max restart attempts (${current.maxRestarts}) after ${plannedRestart.kind} restart requests.` : `Reached max restart attempts (${current.maxRestarts}).`,
|
|
2496
4067
|
stoppedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2497
4068
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2498
4069
|
}));
|
|
2499
4070
|
writePmLog(stderrLog, `max restart attempts reached (${current.maxRestarts})`);
|
|
4071
|
+
if (isPmProxyOwner(current)) {
|
|
4072
|
+
syncOwnedProxyTargets(current.baseName);
|
|
4073
|
+
}
|
|
2500
4074
|
return;
|
|
2501
4075
|
}
|
|
2502
4076
|
persist((latestRecord) => ({
|
|
2503
4077
|
...latestRecord,
|
|
2504
4078
|
status: "restarting",
|
|
2505
4079
|
childPid: void 0,
|
|
4080
|
+
proxyTargetPort: void 0,
|
|
4081
|
+
proxyReadyAt: void 0,
|
|
2506
4082
|
lastExitCode: exitCode,
|
|
2507
4083
|
restartCount: nextRestartCount,
|
|
4084
|
+
lastRestartAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4085
|
+
reloadRequestedAt: void 0,
|
|
2508
4086
|
error: void 0,
|
|
2509
4087
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2510
4088
|
}));
|
|
4089
|
+
if (isPmProxyOwner(current)) {
|
|
4090
|
+
syncOwnedProxyTargets(current.baseName);
|
|
4091
|
+
}
|
|
4092
|
+
const resolvedRestartDelay = resolvePmRestartDelay(current, nextRestartCount, shouldCountRestart && !wasStable && plannedRestart?.kind !== "watch");
|
|
2511
4093
|
if (plannedRestart) {
|
|
2512
4094
|
writePmLog(
|
|
2513
|
-
plannedRestart.kind === "health" ? stderrLog : stdoutLog,
|
|
2514
|
-
`restarting in ${
|
|
4095
|
+
plannedRestart.kind === "health" || plannedRestart.kind === "memory" || plannedRestart.kind === "memory-stop" || plannedRestart.kind === "startup" ? stderrLog : stdoutLog,
|
|
4096
|
+
`restarting in ${resolvedRestartDelay}ms after ${plannedRestart.kind}: ${plannedRestart.detail}`
|
|
2515
4097
|
);
|
|
2516
4098
|
} else {
|
|
2517
|
-
writePmLog(stdoutLog, `restarting in ${
|
|
4099
|
+
writePmLog(stdoutLog, `restarting in ${resolvedRestartDelay}ms`);
|
|
2518
4100
|
}
|
|
2519
|
-
await delay(
|
|
4101
|
+
await delay(resolvedRestartDelay);
|
|
2520
4102
|
}
|
|
2521
4103
|
} finally {
|
|
2522
4104
|
stopRequested = true;
|
|
@@ -2529,11 +4111,26 @@ async function runManagedProcessLoop(filePath, initialRecord) {
|
|
|
2529
4111
|
status: finalRecord.status === "errored" ? "errored" : finalRecord.status === "exited" ? "exited" : "stopped",
|
|
2530
4112
|
runnerPid: void 0,
|
|
2531
4113
|
childPid: void 0,
|
|
4114
|
+
proxyTargetPort: void 0,
|
|
4115
|
+
proxyReadyAt: void 0,
|
|
4116
|
+
reloadRequestedAt: void 0,
|
|
2532
4117
|
stoppedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2533
4118
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2534
4119
|
});
|
|
2535
4120
|
process.off("SIGINT", handleStopSignal);
|
|
2536
4121
|
process.off("SIGTERM", handleStopSignal);
|
|
4122
|
+
if (proxyTargetSyncTimer) {
|
|
4123
|
+
clearInterval(proxyTargetSyncTimer);
|
|
4124
|
+
proxyTargetSyncTimer = null;
|
|
4125
|
+
}
|
|
4126
|
+
if (proxyController) {
|
|
4127
|
+
await proxyController.close().catch(() => void 0);
|
|
4128
|
+
proxyController = null;
|
|
4129
|
+
}
|
|
4130
|
+
if (inheritedListener) {
|
|
4131
|
+
await new Promise((resolvePromise) => inheritedListener?.close(() => resolvePromise()));
|
|
4132
|
+
inheritedListener = null;
|
|
4133
|
+
}
|
|
2537
4134
|
await new Promise((resolvePromise) => stdoutLog.end(resolvePromise));
|
|
2538
4135
|
await new Promise((resolvePromise) => stderrLog.end(resolvePromise));
|
|
2539
4136
|
}
|
|
@@ -2558,7 +4155,7 @@ function parseRunnerArgs(args) {
|
|
|
2558
4155
|
throw new Error("Usage: elit pm __run --data-dir <dir> --id <name>");
|
|
2559
4156
|
}
|
|
2560
4157
|
return {
|
|
2561
|
-
dataDir:
|
|
4158
|
+
dataDir: resolve6(dataDir),
|
|
2562
4159
|
id
|
|
2563
4160
|
};
|
|
2564
4161
|
}
|
|
@@ -2587,23 +4184,26 @@ async function stopPmMatches(matches) {
|
|
|
2587
4184
|
stoppedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2588
4185
|
};
|
|
2589
4186
|
writePmRecord(match.filePath, updated);
|
|
2590
|
-
const
|
|
4187
|
+
const stopTimeout = resolvePmStopTimeout(match.record);
|
|
4188
|
+
const runnerStopped = await waitForProcessTermination(match.record.runnerPid, stopTimeout);
|
|
2591
4189
|
const childStopped = await waitForProcessTermination(
|
|
2592
4190
|
match.record.childPid,
|
|
2593
|
-
runnerStopped ? DEFAULT_PM_STOP_POLL_MS :
|
|
4191
|
+
runnerStopped ? DEFAULT_PM_STOP_POLL_MS : stopTimeout
|
|
2594
4192
|
);
|
|
2595
4193
|
if (!runnerStopped && match.record.runnerPid && isProcessAlive(match.record.runnerPid)) {
|
|
2596
|
-
terminateProcessTree(match.record.runnerPid);
|
|
4194
|
+
terminateProcessTree(match.record.runnerPid, { force: true });
|
|
2597
4195
|
await waitForProcessTermination(match.record.runnerPid, DEFAULT_PM_STOP_POLL_MS);
|
|
2598
4196
|
}
|
|
2599
4197
|
if (!childStopped && match.record.childPid && isProcessAlive(match.record.childPid)) {
|
|
2600
|
-
terminateProcessTree(match.record.childPid);
|
|
4198
|
+
terminateProcessTree(match.record.childPid, { force: true });
|
|
2601
4199
|
await waitForProcessTermination(match.record.childPid, DEFAULT_PM_STOP_POLL_MS);
|
|
2602
4200
|
}
|
|
2603
4201
|
writePmRecord(match.filePath, {
|
|
2604
4202
|
...updated,
|
|
2605
4203
|
runnerPid: void 0,
|
|
2606
4204
|
childPid: void 0,
|
|
4205
|
+
proxyTargetPort: void 0,
|
|
4206
|
+
reloadRequestedAt: void 0,
|
|
2607
4207
|
status: "stopped",
|
|
2608
4208
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2609
4209
|
});
|
|
@@ -2672,13 +4272,13 @@ function readPackageJson(filePath) {
|
|
|
2672
4272
|
|
|
2673
4273
|
// src/shares/workspace-package/roots.ts
|
|
2674
4274
|
function findPackageDirectory(startDir, resolveMatch) {
|
|
2675
|
-
let currentDir =
|
|
4275
|
+
let currentDir = resolve5(startDir);
|
|
2676
4276
|
while (true) {
|
|
2677
4277
|
const match = resolveMatch(currentDir);
|
|
2678
4278
|
if (match) {
|
|
2679
4279
|
return match;
|
|
2680
4280
|
}
|
|
2681
|
-
const parentDir =
|
|
4281
|
+
const parentDir = dirname3(currentDir);
|
|
2682
4282
|
if (parentDir === currentDir) {
|
|
2683
4283
|
return void 0;
|
|
2684
4284
|
}
|
|
@@ -2706,17 +4306,17 @@ init_fs();
|
|
|
2706
4306
|
function getWorkspacePackageImportCandidates(packageRoot, specifier, options = {}) {
|
|
2707
4307
|
const subpath = specifier === "elit" ? "index" : specifier.slice("elit/".length);
|
|
2708
4308
|
const builtCandidates = options.preferredBuiltFormat === "cjs" ? [
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
4309
|
+
resolve5(packageRoot, "dist", `${subpath}.cjs`),
|
|
4310
|
+
resolve5(packageRoot, "dist", `${subpath}.js`),
|
|
4311
|
+
resolve5(packageRoot, "dist", `${subpath}.mjs`)
|
|
2712
4312
|
] : [
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
4313
|
+
resolve5(packageRoot, "dist", `${subpath}.mjs`),
|
|
4314
|
+
resolve5(packageRoot, "dist", `${subpath}.js`),
|
|
4315
|
+
resolve5(packageRoot, "dist", `${subpath}.cjs`)
|
|
2716
4316
|
];
|
|
2717
4317
|
const sourceCandidates = [
|
|
2718
|
-
|
|
2719
|
-
|
|
4318
|
+
resolve5(packageRoot, "src", `${subpath}.ts`),
|
|
4319
|
+
resolve5(packageRoot, "src", `${subpath}.tsx`)
|
|
2720
4320
|
];
|
|
2721
4321
|
return options.preferBuilt ? [...builtCandidates, ...sourceCandidates] : [...sourceCandidates, ...builtCandidates];
|
|
2722
4322
|
}
|
|
@@ -2751,7 +4351,7 @@ function resolveWorkspacePackageImport(specifier, startDir, options = {}) {
|
|
|
2751
4351
|
// src/shares/config/loader.ts
|
|
2752
4352
|
function resolveConfigPath(cwd = process.cwd()) {
|
|
2753
4353
|
for (const configFile of ELIT_CONFIG_FILES) {
|
|
2754
|
-
const configPath =
|
|
4354
|
+
const configPath = resolve5(cwd, configFile);
|
|
2755
4355
|
if (existsSync4(configPath)) {
|
|
2756
4356
|
return configPath;
|
|
2757
4357
|
}
|
|
@@ -2780,7 +4380,7 @@ async function loadConfigFile(configPath) {
|
|
|
2780
4380
|
if (ext === "ts" || ext === "mts") {
|
|
2781
4381
|
try {
|
|
2782
4382
|
const { build } = await import("esbuild");
|
|
2783
|
-
const configDir =
|
|
4383
|
+
const configDir = dirname3(configPath);
|
|
2784
4384
|
const tempFile = join5(configDir, `.elit-config-${Date.now()}.mjs`);
|
|
2785
4385
|
const externalAllPlugin = {
|
|
2786
4386
|
name: "external-all",
|
|
@@ -2854,6 +4454,43 @@ async function loadConfigFile(configPath) {
|
|
|
2854
4454
|
}
|
|
2855
4455
|
|
|
2856
4456
|
// src/cli/pm/commands.ts
|
|
4457
|
+
var PM_SIGNAL_NAMES = /* @__PURE__ */ new Set([
|
|
4458
|
+
"SIGABRT",
|
|
4459
|
+
"SIGALRM",
|
|
4460
|
+
"SIGBREAK",
|
|
4461
|
+
"SIGBUS",
|
|
4462
|
+
"SIGCHLD",
|
|
4463
|
+
"SIGCONT",
|
|
4464
|
+
"SIGFPE",
|
|
4465
|
+
"SIGHUP",
|
|
4466
|
+
"SIGILL",
|
|
4467
|
+
"SIGINT",
|
|
4468
|
+
"SIGIO",
|
|
4469
|
+
"SIGIOT",
|
|
4470
|
+
"SIGKILL",
|
|
4471
|
+
"SIGPIPE",
|
|
4472
|
+
"SIGPOLL",
|
|
4473
|
+
"SIGPROF",
|
|
4474
|
+
"SIGPWR",
|
|
4475
|
+
"SIGQUIT",
|
|
4476
|
+
"SIGSEGV",
|
|
4477
|
+
"SIGSTKFLT",
|
|
4478
|
+
"SIGSTOP",
|
|
4479
|
+
"SIGSYS",
|
|
4480
|
+
"SIGTERM",
|
|
4481
|
+
"SIGTRAP",
|
|
4482
|
+
"SIGTSTP",
|
|
4483
|
+
"SIGTTIN",
|
|
4484
|
+
"SIGTTOU",
|
|
4485
|
+
"SIGUNUSED",
|
|
4486
|
+
"SIGURG",
|
|
4487
|
+
"SIGUSR1",
|
|
4488
|
+
"SIGUSR2",
|
|
4489
|
+
"SIGVTALRM",
|
|
4490
|
+
"SIGWINCH",
|
|
4491
|
+
"SIGXCPU",
|
|
4492
|
+
"SIGXFSZ"
|
|
4493
|
+
]);
|
|
2857
4494
|
async function runPmStart(args) {
|
|
2858
4495
|
const parsed = parsePmStartArgs(args);
|
|
2859
4496
|
const workspaceRoot = process.cwd();
|
|
@@ -2886,9 +4523,130 @@ function resolveNamedMatches(paths, value) {
|
|
|
2886
4523
|
if (value === "all") {
|
|
2887
4524
|
return listPmRecordMatches(paths).map(syncPmRecordLiveness);
|
|
2888
4525
|
}
|
|
4526
|
+
const groupMatches = findPmGroupMatches(paths, value);
|
|
4527
|
+
if (groupMatches.length > 0) {
|
|
4528
|
+
return groupMatches.map(syncPmRecordLiveness);
|
|
4529
|
+
}
|
|
2889
4530
|
const match = findPmRecordMatch(paths, value);
|
|
2890
4531
|
return match ? [syncPmRecordLiveness(match)] : [];
|
|
2891
4532
|
}
|
|
4533
|
+
function sortPmMatchesByInstance(matches) {
|
|
4534
|
+
return [...matches].sort((left, right) => left.record.instanceIndex - right.record.instanceIndex);
|
|
4535
|
+
}
|
|
4536
|
+
function resolveInspectableMatch(paths, value) {
|
|
4537
|
+
const exactMatch = findPmRecordMatch(paths, value);
|
|
4538
|
+
const groupMatches = findPmGroupMatches(paths, value).map(syncPmRecordLiveness);
|
|
4539
|
+
if (groupMatches.length > 1 && exactMatch?.record.baseName === value && exactMatch.record.name === value) {
|
|
4540
|
+
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}.`);
|
|
4541
|
+
}
|
|
4542
|
+
if (exactMatch) {
|
|
4543
|
+
return syncPmRecordLiveness(exactMatch);
|
|
4544
|
+
}
|
|
4545
|
+
return groupMatches[0];
|
|
4546
|
+
}
|
|
4547
|
+
function rebuildPmRecordDefinition(record, targetInstances = record.instances) {
|
|
4548
|
+
const definition = resolvePmAppDefinition(
|
|
4549
|
+
toPmAppConfig(record),
|
|
4550
|
+
{ name: record.baseName, env: {}, watchPaths: [], watchIgnore: [], instances: targetInstances },
|
|
4551
|
+
process.cwd(),
|
|
4552
|
+
record.source
|
|
4553
|
+
);
|
|
4554
|
+
return {
|
|
4555
|
+
...definition,
|
|
4556
|
+
name: record.name,
|
|
4557
|
+
baseName: record.baseName,
|
|
4558
|
+
instanceIndex: record.instanceIndex,
|
|
4559
|
+
instances: targetInstances
|
|
4560
|
+
};
|
|
4561
|
+
}
|
|
4562
|
+
function rebuildPmSavedDefinition(app) {
|
|
4563
|
+
const definition = resolvePmAppDefinition(
|
|
4564
|
+
toSavedPmAppConfig(app),
|
|
4565
|
+
{ name: app.baseName, env: {}, watchPaths: [], watchIgnore: [], instances: app.instances },
|
|
4566
|
+
process.cwd(),
|
|
4567
|
+
"cli"
|
|
4568
|
+
);
|
|
4569
|
+
return {
|
|
4570
|
+
...definition,
|
|
4571
|
+
name: app.name,
|
|
4572
|
+
baseName: app.baseName,
|
|
4573
|
+
instanceIndex: app.instanceIndex,
|
|
4574
|
+
instances: app.instances
|
|
4575
|
+
};
|
|
4576
|
+
}
|
|
4577
|
+
function deletePmMatches(matches) {
|
|
4578
|
+
for (const match of matches) {
|
|
4579
|
+
if (existsSync5(match.record.logFiles.out)) {
|
|
4580
|
+
rmSync(match.record.logFiles.out, { force: true });
|
|
4581
|
+
}
|
|
4582
|
+
if (existsSync5(match.record.logFiles.err)) {
|
|
4583
|
+
rmSync(match.record.logFiles.err, { force: true });
|
|
4584
|
+
}
|
|
4585
|
+
rmSync(match.filePath, { force: true });
|
|
4586
|
+
}
|
|
4587
|
+
}
|
|
4588
|
+
function updatePmInstanceCount(matches, instances) {
|
|
4589
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4590
|
+
for (const match of matches) {
|
|
4591
|
+
writePmRecord(match.filePath, {
|
|
4592
|
+
...match.record,
|
|
4593
|
+
instances,
|
|
4594
|
+
updatedAt: now
|
|
4595
|
+
});
|
|
4596
|
+
}
|
|
4597
|
+
}
|
|
4598
|
+
function normalizePmSignalName(value) {
|
|
4599
|
+
const trimmed = value.trim().toUpperCase();
|
|
4600
|
+
const signalName = trimmed.startsWith("SIG") ? trimmed : `SIG${trimmed}`;
|
|
4601
|
+
if (!PM_SIGNAL_NAMES.has(signalName)) {
|
|
4602
|
+
throw new Error(`Unsupported pm signal: ${value}`);
|
|
4603
|
+
}
|
|
4604
|
+
return signalName;
|
|
4605
|
+
}
|
|
4606
|
+
function resolveSignalablePid(record) {
|
|
4607
|
+
if (record.childPid && record.childPid > 0) {
|
|
4608
|
+
return record.childPid;
|
|
4609
|
+
}
|
|
4610
|
+
if (record.runnerPid && record.runnerPid > 0) {
|
|
4611
|
+
return record.runnerPid;
|
|
4612
|
+
}
|
|
4613
|
+
return void 0;
|
|
4614
|
+
}
|
|
4615
|
+
function groupPmMatchesByBaseName(matches) {
|
|
4616
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
4617
|
+
for (const match of matches) {
|
|
4618
|
+
const group = grouped.get(match.record.baseName);
|
|
4619
|
+
if (group) {
|
|
4620
|
+
group.push(match);
|
|
4621
|
+
continue;
|
|
4622
|
+
}
|
|
4623
|
+
grouped.set(match.record.baseName, [match]);
|
|
4624
|
+
}
|
|
4625
|
+
return [...grouped.entries()].sort((left, right) => left[0].localeCompare(right[0])).map(([, group]) => sortPmMatchesByInstance(group));
|
|
4626
|
+
}
|
|
4627
|
+
async function waitForPmRecordOnline(filePath, timeoutMs) {
|
|
4628
|
+
const deadline = Date.now() + timeoutMs;
|
|
4629
|
+
while (Date.now() < deadline) {
|
|
4630
|
+
if (!existsSync5(filePath)) {
|
|
4631
|
+
break;
|
|
4632
|
+
}
|
|
4633
|
+
const record = readPmRecord(filePath);
|
|
4634
|
+
if (record.status === "online") {
|
|
4635
|
+
return record;
|
|
4636
|
+
}
|
|
4637
|
+
if (record.status === "errored" || record.status === "exited" || record.status === "stopped") {
|
|
4638
|
+
throw new Error(record.error ?? `Process ${record.name} failed while reloading.`);
|
|
4639
|
+
}
|
|
4640
|
+
await new Promise((resolvePromise) => setTimeout(resolvePromise, 50));
|
|
4641
|
+
}
|
|
4642
|
+
throw new Error(`Timed out waiting for the reloaded process to become online after ${timeoutMs}ms.`);
|
|
4643
|
+
}
|
|
4644
|
+
function resolvePmReloadReadyTimeout(record) {
|
|
4645
|
+
return record.waitReady || record.proxy?.strategy === "inherit" ? Math.max(record.listenTimeout + 1e3, 2e3) : Math.max(record.restartDelay + 1e3, 2e3);
|
|
4646
|
+
}
|
|
4647
|
+
function supportsPmProxyReload2(record) {
|
|
4648
|
+
return Boolean(record.proxy) && record.instances === 1 && Boolean(record.runnerPid);
|
|
4649
|
+
}
|
|
2892
4650
|
function padCell(value, width) {
|
|
2893
4651
|
return value.length >= width ? value : `${value}${" ".repeat(width - value.length)}`;
|
|
2894
4652
|
}
|
|
@@ -2899,8 +4657,255 @@ function tailLogFile(filePath, lineCount) {
|
|
|
2899
4657
|
const lines = readFileSync3(filePath, "utf8").split(/\r?\n/).filter((line) => line.length > 0);
|
|
2900
4658
|
return lines.slice(-lineCount).join(EOL2);
|
|
2901
4659
|
}
|
|
2902
|
-
function
|
|
2903
|
-
|
|
4660
|
+
function listPmMatches(paths) {
|
|
4661
|
+
return listPmRecordMatches(paths).map(syncPmRecordLiveness);
|
|
4662
|
+
}
|
|
4663
|
+
function isPmRecordActive(record) {
|
|
4664
|
+
return record.desiredState === "running" && (record.status === "starting" || record.status === "online" || record.status === "restarting");
|
|
4665
|
+
}
|
|
4666
|
+
function resolvePmUptimeMs(record) {
|
|
4667
|
+
if (!isPmRecordActive(record) || !record.startedAt) {
|
|
4668
|
+
return void 0;
|
|
4669
|
+
}
|
|
4670
|
+
const startedTime = Date.parse(record.startedAt);
|
|
4671
|
+
if (Number.isNaN(startedTime)) {
|
|
4672
|
+
return void 0;
|
|
4673
|
+
}
|
|
4674
|
+
return Math.max(0, Date.now() - startedTime);
|
|
4675
|
+
}
|
|
4676
|
+
function resolvePmLiveMetrics(record) {
|
|
4677
|
+
const uptimeMs = resolvePmUptimeMs(record);
|
|
4678
|
+
if (!isPmRecordActive(record) || !record.childPid) {
|
|
4679
|
+
return { uptimeMs };
|
|
4680
|
+
}
|
|
4681
|
+
const sampledMetrics = samplePmProcessMetrics(record.childPid);
|
|
4682
|
+
return {
|
|
4683
|
+
...sampledMetrics,
|
|
4684
|
+
uptimeMs,
|
|
4685
|
+
updatedAt: sampledMetrics.cpuPercent !== void 0 || sampledMetrics.memoryRssBytes !== void 0 ? (/* @__PURE__ */ new Date()).toISOString() : void 0
|
|
4686
|
+
};
|
|
4687
|
+
}
|
|
4688
|
+
function toPmDisplayRecord(record) {
|
|
4689
|
+
return {
|
|
4690
|
+
record,
|
|
4691
|
+
liveMetrics: resolvePmLiveMetrics(record)
|
|
4692
|
+
};
|
|
4693
|
+
}
|
|
4694
|
+
function serializePmRecord(record) {
|
|
4695
|
+
return {
|
|
4696
|
+
...record,
|
|
4697
|
+
liveMetrics: resolvePmLiveMetrics(record)
|
|
4698
|
+
};
|
|
4699
|
+
}
|
|
4700
|
+
function parsePmFormatOption(args, index, option) {
|
|
4701
|
+
let value;
|
|
4702
|
+
if (option.startsWith("--format=")) {
|
|
4703
|
+
value = option.slice("--format=".length);
|
|
4704
|
+
} else {
|
|
4705
|
+
value = readRequiredValue(args, index + 1, "--format");
|
|
4706
|
+
index += 1;
|
|
4707
|
+
}
|
|
4708
|
+
if (value !== "table" && value !== "json") {
|
|
4709
|
+
throw new Error(`Unsupported pm output format: ${value}`);
|
|
4710
|
+
}
|
|
4711
|
+
return {
|
|
4712
|
+
format: value,
|
|
4713
|
+
nextIndex: index
|
|
4714
|
+
};
|
|
4715
|
+
}
|
|
4716
|
+
function parsePmListArgs(args) {
|
|
4717
|
+
let format = "table";
|
|
4718
|
+
for (let index = 0; index < args.length; index++) {
|
|
4719
|
+
const arg = args[index];
|
|
4720
|
+
switch (arg) {
|
|
4721
|
+
case "--json":
|
|
4722
|
+
format = "json";
|
|
4723
|
+
break;
|
|
4724
|
+
case "--format":
|
|
4725
|
+
default:
|
|
4726
|
+
if (arg === "--format" || arg.startsWith("--format=")) {
|
|
4727
|
+
const parsed = parsePmFormatOption(args, index, arg);
|
|
4728
|
+
format = parsed.format;
|
|
4729
|
+
index = parsed.nextIndex;
|
|
4730
|
+
break;
|
|
4731
|
+
}
|
|
4732
|
+
throw new Error(`Unknown pm list option: ${arg}`);
|
|
4733
|
+
}
|
|
4734
|
+
}
|
|
4735
|
+
return { format };
|
|
4736
|
+
}
|
|
4737
|
+
function parsePmShowArgs(args) {
|
|
4738
|
+
let format = "text";
|
|
4739
|
+
let name;
|
|
4740
|
+
for (let index = 0; index < args.length; index++) {
|
|
4741
|
+
const arg = args[index];
|
|
4742
|
+
switch (arg) {
|
|
4743
|
+
case "--json":
|
|
4744
|
+
format = "json";
|
|
4745
|
+
break;
|
|
4746
|
+
case "--format":
|
|
4747
|
+
default:
|
|
4748
|
+
if (arg === "--format" || arg.startsWith("--format=")) {
|
|
4749
|
+
const parsed = parsePmFormatOption(args, index, arg);
|
|
4750
|
+
format = parsed.format === "json" ? "json" : "text";
|
|
4751
|
+
index = parsed.nextIndex;
|
|
4752
|
+
break;
|
|
4753
|
+
}
|
|
4754
|
+
if (arg.startsWith("-")) {
|
|
4755
|
+
throw new Error(`Unknown pm show option: ${arg}`);
|
|
4756
|
+
}
|
|
4757
|
+
if (name) {
|
|
4758
|
+
throw new Error("pm show accepts exactly one process name.");
|
|
4759
|
+
}
|
|
4760
|
+
name = arg;
|
|
4761
|
+
break;
|
|
4762
|
+
}
|
|
4763
|
+
}
|
|
4764
|
+
if (!name) {
|
|
4765
|
+
throw new Error("Usage: elit pm show <name> [--json]");
|
|
4766
|
+
}
|
|
4767
|
+
return { name, format };
|
|
4768
|
+
}
|
|
4769
|
+
function formatPmDuration(durationMs) {
|
|
4770
|
+
if (durationMs < 1e3) {
|
|
4771
|
+
return `${durationMs}ms`;
|
|
4772
|
+
}
|
|
4773
|
+
const totalSeconds = Math.floor(durationMs / 1e3);
|
|
4774
|
+
const days = Math.floor(totalSeconds / 86400);
|
|
4775
|
+
const hours = Math.floor(totalSeconds % 86400 / 3600);
|
|
4776
|
+
const minutes = Math.floor(totalSeconds % 3600 / 60);
|
|
4777
|
+
const seconds = totalSeconds % 60;
|
|
4778
|
+
const parts = [];
|
|
4779
|
+
if (days > 0) {
|
|
4780
|
+
parts.push(`${days}d`);
|
|
4781
|
+
}
|
|
4782
|
+
if (hours > 0) {
|
|
4783
|
+
parts.push(`${hours}h`);
|
|
4784
|
+
}
|
|
4785
|
+
if (minutes > 0) {
|
|
4786
|
+
parts.push(`${minutes}m`);
|
|
4787
|
+
}
|
|
4788
|
+
if (seconds > 0 || parts.length === 0) {
|
|
4789
|
+
parts.push(`${seconds}s`);
|
|
4790
|
+
}
|
|
4791
|
+
return parts.slice(0, 2).join(" ");
|
|
4792
|
+
}
|
|
4793
|
+
function formatPmCpuPercent(cpuPercent) {
|
|
4794
|
+
if (cpuPercent === void 0 || !Number.isFinite(cpuPercent)) {
|
|
4795
|
+
return "-";
|
|
4796
|
+
}
|
|
4797
|
+
return `${cpuPercent >= 100 ? cpuPercent.toFixed(0) : cpuPercent.toFixed(1)}%`;
|
|
4798
|
+
}
|
|
4799
|
+
function formatPmMemory(memoryRssBytes) {
|
|
4800
|
+
if (memoryRssBytes === void 0 || !Number.isFinite(memoryRssBytes) || memoryRssBytes < 0) {
|
|
4801
|
+
return "-";
|
|
4802
|
+
}
|
|
4803
|
+
const units = ["B", "KB", "MB", "GB", "TB"];
|
|
4804
|
+
let value = memoryRssBytes;
|
|
4805
|
+
let unitIndex = 0;
|
|
4806
|
+
while (value >= 1024 && unitIndex < units.length - 1) {
|
|
4807
|
+
value /= 1024;
|
|
4808
|
+
unitIndex += 1;
|
|
4809
|
+
}
|
|
4810
|
+
const formatted = value >= 10 || unitIndex === 0 ? value.toFixed(0) : value.toFixed(1);
|
|
4811
|
+
return `${formatted}${units[unitIndex]}`;
|
|
4812
|
+
}
|
|
4813
|
+
function formatPmUptime(uptimeMs) {
|
|
4814
|
+
if (uptimeMs === void 0 || !Number.isFinite(uptimeMs)) {
|
|
4815
|
+
return "-";
|
|
4816
|
+
}
|
|
4817
|
+
return formatPmDuration(Math.max(0, uptimeMs));
|
|
4818
|
+
}
|
|
4819
|
+
function formatPmTarget(record) {
|
|
4820
|
+
if (record.script) {
|
|
4821
|
+
return record.script;
|
|
4822
|
+
}
|
|
4823
|
+
if (record.file) {
|
|
4824
|
+
return record.file;
|
|
4825
|
+
}
|
|
4826
|
+
if (record.wapk) {
|
|
4827
|
+
return record.wapk;
|
|
4828
|
+
}
|
|
4829
|
+
return "-";
|
|
4830
|
+
}
|
|
4831
|
+
function pushPmDetail(lines, label, value) {
|
|
4832
|
+
lines.push(`${padCell(`${label}:`, 18)} ${value}`);
|
|
4833
|
+
}
|
|
4834
|
+
function pushPmDetailList(lines, label, values) {
|
|
4835
|
+
if (values.length === 0) {
|
|
4836
|
+
pushPmDetail(lines, label, "-");
|
|
4837
|
+
return;
|
|
4838
|
+
}
|
|
4839
|
+
pushPmDetail(lines, label, values[0] ?? "-");
|
|
4840
|
+
for (const value of values.slice(1)) {
|
|
4841
|
+
lines.push(`${" ".repeat(20)} ${value}`);
|
|
4842
|
+
}
|
|
4843
|
+
}
|
|
4844
|
+
function formatPmRecordDetails(record, liveMetrics) {
|
|
4845
|
+
const lines = [`Process: ${record.name}`];
|
|
4846
|
+
pushPmDetail(lines, "id", record.id);
|
|
4847
|
+
pushPmDetail(lines, "status", record.status);
|
|
4848
|
+
pushPmDetail(lines, "desired state", record.desiredState);
|
|
4849
|
+
pushPmDetail(lines, "instance", `${record.instanceIndex}/${record.instances}`);
|
|
4850
|
+
pushPmDetail(lines, "cpu", formatPmCpuPercent(liveMetrics.cpuPercent));
|
|
4851
|
+
pushPmDetail(lines, "memory", formatPmMemory(liveMetrics.memoryRssBytes));
|
|
4852
|
+
pushPmDetail(lines, "uptime", formatPmUptime(liveMetrics.uptimeMs));
|
|
4853
|
+
pushPmDetail(lines, "type", record.type);
|
|
4854
|
+
pushPmDetail(lines, "source", record.source);
|
|
4855
|
+
pushPmDetail(lines, "runtime", record.runtime ?? "-");
|
|
4856
|
+
pushPmDetail(lines, "cwd", record.cwd);
|
|
4857
|
+
pushPmDetail(lines, "target", formatPmTarget(record));
|
|
4858
|
+
pushPmDetail(lines, "command", record.commandPreview || "-");
|
|
4859
|
+
pushPmDetail(lines, "runner pid", record.runnerPid ? String(record.runnerPid) : "-");
|
|
4860
|
+
pushPmDetail(lines, "child pid", record.childPid ? String(record.childPid) : "-");
|
|
4861
|
+
pushPmDetail(lines, "restart count", `${record.restartCount}/${record.maxRestarts}`);
|
|
4862
|
+
pushPmDetail(lines, "restart policy", record.restartPolicy);
|
|
4863
|
+
pushPmDetail(lines, "proxy", record.proxy ? `http://${record.proxy.host ?? "0.0.0.0"}:${record.proxy.port}` : "-");
|
|
4864
|
+
pushPmDetail(lines, "proxy strategy", record.proxy?.strategy ?? "-");
|
|
4865
|
+
pushPmDetail(lines, "proxy target", record.proxy && record.proxyTargetPort ? `${record.proxy.targetHost ?? "127.0.0.1"}:${record.proxyTargetPort}` : "-");
|
|
4866
|
+
pushPmDetail(lines, "max memory", record.maxMemoryBytes ? formatPmMemory(record.maxMemoryBytes) : "-");
|
|
4867
|
+
pushPmDetail(lines, "memory action", record.memoryAction ?? "-");
|
|
4868
|
+
pushPmDetail(lines, "cron restart", record.cronRestart ?? "-");
|
|
4869
|
+
pushPmDetail(lines, "exp backoff", record.expBackoffRestartDelay ? formatPmDuration(record.expBackoffRestartDelay) : "-");
|
|
4870
|
+
pushPmDetail(lines, "exp backoff max", record.expBackoffRestartMaxDelay ? formatPmDuration(record.expBackoffRestartMaxDelay) : "-");
|
|
4871
|
+
pushPmDetail(lines, "restart window", record.restartWindow ? formatPmDuration(record.restartWindow) : "-");
|
|
4872
|
+
pushPmDetail(lines, "wait ready", record.waitReady ? "enabled" : "disabled");
|
|
4873
|
+
pushPmDetail(lines, "listen timeout", record.waitReady ? formatPmDuration(record.listenTimeout) : "-");
|
|
4874
|
+
pushPmDetail(lines, "restart delay", formatPmDuration(record.restartDelay));
|
|
4875
|
+
pushPmDetail(lines, "kill timeout", formatPmDuration(record.killTimeout));
|
|
4876
|
+
pushPmDetail(lines, "min uptime", formatPmDuration(record.minUptime));
|
|
4877
|
+
pushPmDetail(lines, "autorestart", record.autorestart ? "enabled" : "disabled");
|
|
4878
|
+
pushPmDetail(lines, "watch", record.watch ? "enabled" : "disabled");
|
|
4879
|
+
pushPmDetail(lines, "watch debounce", record.watch ? formatPmDuration(record.watchDebounce) : "-");
|
|
4880
|
+
pushPmDetailList(lines, "watch paths", record.watchPaths);
|
|
4881
|
+
pushPmDetailList(lines, "watch ignore", record.watchIgnore);
|
|
4882
|
+
if (record.healthCheck) {
|
|
4883
|
+
pushPmDetail(lines, "health check", record.healthCheck.url);
|
|
4884
|
+
pushPmDetail(lines, "health grace", formatPmDuration(record.healthCheck.gracePeriod));
|
|
4885
|
+
pushPmDetail(lines, "health interval", formatPmDuration(record.healthCheck.interval));
|
|
4886
|
+
pushPmDetail(lines, "health timeout", formatPmDuration(record.healthCheck.timeout));
|
|
4887
|
+
pushPmDetail(lines, "health failures", String(record.healthCheck.maxFailures));
|
|
4888
|
+
} else {
|
|
4889
|
+
pushPmDetail(lines, "health check", "-");
|
|
4890
|
+
}
|
|
4891
|
+
pushPmDetailList(lines, "env", Object.entries(record.env).map(([key, value]) => `${key}=${value}`));
|
|
4892
|
+
pushPmDetail(lines, "stdout log", record.logFiles.out);
|
|
4893
|
+
pushPmDetail(lines, "stderr log", record.logFiles.err);
|
|
4894
|
+
pushPmDetail(lines, "created at", record.createdAt);
|
|
4895
|
+
pushPmDetail(lines, "updated at", record.updatedAt);
|
|
4896
|
+
pushPmDetail(lines, "metrics at", liveMetrics.updatedAt ?? "-");
|
|
4897
|
+
pushPmDetail(lines, "started at", record.startedAt ?? "-");
|
|
4898
|
+
pushPmDetail(lines, "stopped at", record.stoppedAt ?? "-");
|
|
4899
|
+
pushPmDetail(lines, "last exit", record.lastExitCode === void 0 ? "-" : String(record.lastExitCode));
|
|
4900
|
+
pushPmDetail(lines, "error", record.error ?? "-");
|
|
4901
|
+
return lines.join(EOL2);
|
|
4902
|
+
}
|
|
4903
|
+
function printPmList(paths, format = "table") {
|
|
4904
|
+
const matches = listPmMatches(paths).map((match) => toPmDisplayRecord(match.record));
|
|
4905
|
+
if (format === "json") {
|
|
4906
|
+
console.log(JSON.stringify(matches.map((match) => ({ ...match.record, liveMetrics: match.liveMetrics })), null, 2));
|
|
4907
|
+
return;
|
|
4908
|
+
}
|
|
2904
4909
|
if (matches.length === 0) {
|
|
2905
4910
|
console.log("No managed processes found.");
|
|
2906
4911
|
return;
|
|
@@ -2909,22 +4914,47 @@ function printPmList(paths) {
|
|
|
2909
4914
|
padCell("name", 20),
|
|
2910
4915
|
padCell("status", 12),
|
|
2911
4916
|
padCell("pid", 8),
|
|
4917
|
+
padCell("cpu", 8),
|
|
4918
|
+
padCell("memory", 10),
|
|
4919
|
+
padCell("uptime", 10),
|
|
2912
4920
|
padCell("restarts", 10),
|
|
2913
4921
|
padCell("type", 8),
|
|
2914
4922
|
"runtime"
|
|
2915
4923
|
];
|
|
2916
4924
|
console.log(headers.join(" "));
|
|
2917
|
-
for (const { record } of matches) {
|
|
4925
|
+
for (const { record, liveMetrics } of matches) {
|
|
2918
4926
|
console.log([
|
|
2919
4927
|
padCell(record.name, 20),
|
|
2920
4928
|
padCell(record.status, 12),
|
|
2921
4929
|
padCell(record.childPid ? String(record.childPid) : "-", 8),
|
|
4930
|
+
padCell(formatPmCpuPercent(liveMetrics.cpuPercent), 8),
|
|
4931
|
+
padCell(formatPmMemory(liveMetrics.memoryRssBytes), 10),
|
|
4932
|
+
padCell(formatPmUptime(liveMetrics.uptimeMs), 10),
|
|
2922
4933
|
padCell(String(record.restartCount ?? 0), 10),
|
|
2923
4934
|
padCell(record.type, 8),
|
|
2924
4935
|
record.runtime ?? "-"
|
|
2925
4936
|
].join(" "));
|
|
2926
4937
|
}
|
|
2927
4938
|
}
|
|
4939
|
+
async function runPmList(args) {
|
|
4940
|
+
const options = parsePmListArgs(args);
|
|
4941
|
+
const { paths } = await loadPmContext();
|
|
4942
|
+
printPmList(paths, options.format);
|
|
4943
|
+
}
|
|
4944
|
+
async function runPmShow(args) {
|
|
4945
|
+
const options = parsePmShowArgs(args);
|
|
4946
|
+
const { paths } = await loadPmContext();
|
|
4947
|
+
const match = resolveInspectableMatch(paths, options.name);
|
|
4948
|
+
if (!match) {
|
|
4949
|
+
throw new Error(`No managed process found for: ${options.name}`);
|
|
4950
|
+
}
|
|
4951
|
+
const synced = syncPmRecordLiveness(match);
|
|
4952
|
+
if (options.format === "json") {
|
|
4953
|
+
console.log(JSON.stringify(serializePmRecord(synced.record), null, 2));
|
|
4954
|
+
return;
|
|
4955
|
+
}
|
|
4956
|
+
console.log(formatPmRecordDetails(synced.record, resolvePmLiveMetrics(synced.record)));
|
|
4957
|
+
}
|
|
2928
4958
|
async function runPmStop(args) {
|
|
2929
4959
|
const target = args[0];
|
|
2930
4960
|
if (!target) {
|
|
@@ -2951,17 +4981,62 @@ async function runPmRestart(args) {
|
|
|
2951
4981
|
await stopPmMatches(matches);
|
|
2952
4982
|
const restarted = [];
|
|
2953
4983
|
for (const match of matches) {
|
|
2954
|
-
const definition =
|
|
2955
|
-
toPmAppConfig(match.record),
|
|
2956
|
-
{ name: match.record.name, env: {}, watchPaths: [], watchIgnore: [] },
|
|
2957
|
-
process.cwd(),
|
|
2958
|
-
match.record.source
|
|
2959
|
-
);
|
|
4984
|
+
const definition = rebuildPmRecordDefinition(match.record);
|
|
2960
4985
|
await startManagedProcess(definition, paths);
|
|
2961
4986
|
restarted.push(match.record.name);
|
|
2962
4987
|
}
|
|
2963
4988
|
console.log(`[pm] restarted ${restarted.join(", ")}`);
|
|
2964
4989
|
}
|
|
4990
|
+
async function runPmReload(args) {
|
|
4991
|
+
const target = args[0];
|
|
4992
|
+
if (!target) {
|
|
4993
|
+
throw new Error("Usage: elit pm reload <name|all>");
|
|
4994
|
+
}
|
|
4995
|
+
const { paths } = await loadPmContext();
|
|
4996
|
+
const matches = resolveNamedMatches(paths, target);
|
|
4997
|
+
if (matches.length === 0) {
|
|
4998
|
+
throw new Error(`No managed process found for: ${target}`);
|
|
4999
|
+
}
|
|
5000
|
+
const reloaded = [];
|
|
5001
|
+
const errors = [];
|
|
5002
|
+
for (const group of groupPmMatchesByBaseName(matches)) {
|
|
5003
|
+
for (const match of group) {
|
|
5004
|
+
try {
|
|
5005
|
+
if (supportsPmProxyReload2(match.record)) {
|
|
5006
|
+
const reloadRequestedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
5007
|
+
writePmRecord(match.filePath, {
|
|
5008
|
+
...match.record,
|
|
5009
|
+
status: "restarting",
|
|
5010
|
+
reloadRequestedAt,
|
|
5011
|
+
updatedAt: reloadRequestedAt,
|
|
5012
|
+
error: void 0
|
|
5013
|
+
});
|
|
5014
|
+
await waitForPmRecordOnline(
|
|
5015
|
+
getPmRecordPath(paths, match.record.id),
|
|
5016
|
+
resolvePmReloadReadyTimeout(match.record)
|
|
5017
|
+
);
|
|
5018
|
+
reloaded.push(match.record.name);
|
|
5019
|
+
continue;
|
|
5020
|
+
}
|
|
5021
|
+
await stopPmMatches([match]);
|
|
5022
|
+
const definition = rebuildPmRecordDefinition(match.record);
|
|
5023
|
+
const startedRecord = await startManagedProcess(definition, paths);
|
|
5024
|
+
await waitForPmRecordOnline(
|
|
5025
|
+
getPmRecordPath(paths, startedRecord.id),
|
|
5026
|
+
resolvePmReloadReadyTimeout(startedRecord)
|
|
5027
|
+
);
|
|
5028
|
+
reloaded.push(match.record.name);
|
|
5029
|
+
} catch (error) {
|
|
5030
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
5031
|
+
errors.push(`[pm] ${match.record.name}: ${message}`);
|
|
5032
|
+
}
|
|
5033
|
+
}
|
|
5034
|
+
}
|
|
5035
|
+
if (errors.length > 0) {
|
|
5036
|
+
throw new Error([`[pm] reloaded ${reloaded.length} process${reloaded.length === 1 ? "" : "es"}`, ...errors].join(EOL2));
|
|
5037
|
+
}
|
|
5038
|
+
console.log(`[pm] reloaded ${reloaded.join(", ")}`);
|
|
5039
|
+
}
|
|
2965
5040
|
async function runPmSave() {
|
|
2966
5041
|
const { paths } = await loadPmContext();
|
|
2967
5042
|
ensurePmDirectories(paths);
|
|
@@ -2983,12 +5058,7 @@ async function runPmResurrect() {
|
|
|
2983
5058
|
let restored = 0;
|
|
2984
5059
|
for (const app of dump.apps) {
|
|
2985
5060
|
try {
|
|
2986
|
-
const definition =
|
|
2987
|
-
toSavedPmAppConfig(app),
|
|
2988
|
-
{ name: app.name, env: {}, watchPaths: [], watchIgnore: [] },
|
|
2989
|
-
process.cwd(),
|
|
2990
|
-
"cli"
|
|
2991
|
-
);
|
|
5061
|
+
const definition = rebuildPmSavedDefinition(app);
|
|
2992
5062
|
await startManagedProcess(definition, paths);
|
|
2993
5063
|
restored += 1;
|
|
2994
5064
|
} catch (error) {
|
|
@@ -3012,16 +5082,127 @@ async function runPmDelete(args) {
|
|
|
3012
5082
|
throw new Error(`No managed process found for: ${target}`);
|
|
3013
5083
|
}
|
|
3014
5084
|
await stopPmMatches(matches);
|
|
5085
|
+
deletePmMatches(matches);
|
|
5086
|
+
console.log(`[pm] deleted ${matches.length} process${matches.length === 1 ? "" : "es"}`);
|
|
5087
|
+
}
|
|
5088
|
+
async function runPmScale(args) {
|
|
5089
|
+
const target = args[0];
|
|
5090
|
+
const countArg = args[1];
|
|
5091
|
+
if (!target || countArg === void 0 || args.length > 2) {
|
|
5092
|
+
throw new Error("Usage: elit pm scale <name> <count>");
|
|
5093
|
+
}
|
|
5094
|
+
const desiredCount = normalizeIntegerOption(countArg, "pm scale <count>", 0);
|
|
5095
|
+
const { config, paths } = await loadPmContext();
|
|
5096
|
+
const exactMatch = findPmRecordMatch(paths, target);
|
|
5097
|
+
const currentMatches = sortPmMatchesByInstance(resolveNamedMatches(paths, exactMatch?.record.baseName ?? target));
|
|
5098
|
+
if (currentMatches.length === 0) {
|
|
5099
|
+
if (desiredCount === 0) {
|
|
5100
|
+
console.log(`[pm] ${target} already scaled to 0 instances`);
|
|
5101
|
+
return;
|
|
5102
|
+
}
|
|
5103
|
+
const definitions = resolvePmStartDefinitions(
|
|
5104
|
+
{ name: target, env: {}, watchPaths: [], watchIgnore: [], instances: desiredCount },
|
|
5105
|
+
config,
|
|
5106
|
+
process.cwd()
|
|
5107
|
+
);
|
|
5108
|
+
for (const definition of definitions) {
|
|
5109
|
+
await startManagedProcess(definition, paths);
|
|
5110
|
+
}
|
|
5111
|
+
console.log(`[pm] scaled ${target} to ${desiredCount} instance${desiredCount === 1 ? "" : "s"}`);
|
|
5112
|
+
return;
|
|
5113
|
+
}
|
|
5114
|
+
const baseName = currentMatches[0]?.record.baseName ?? target;
|
|
5115
|
+
if (desiredCount === currentMatches.length) {
|
|
5116
|
+
console.log(`[pm] ${baseName} already scaled to ${desiredCount} instance${desiredCount === 1 ? "" : "s"}`);
|
|
5117
|
+
return;
|
|
5118
|
+
}
|
|
5119
|
+
if (desiredCount === 0) {
|
|
5120
|
+
await stopPmMatches(currentMatches);
|
|
5121
|
+
deletePmMatches(currentMatches);
|
|
5122
|
+
console.log(`[pm] scaled ${baseName} to 0 instances`);
|
|
5123
|
+
return;
|
|
5124
|
+
}
|
|
5125
|
+
if (desiredCount < currentMatches.length) {
|
|
5126
|
+
const toRemove = [...currentMatches].sort((left, right) => right.record.instanceIndex - left.record.instanceIndex).slice(0, currentMatches.length - desiredCount);
|
|
5127
|
+
const remaining = currentMatches.filter((match) => !toRemove.some((removal) => removal.record.id === match.record.id));
|
|
5128
|
+
await stopPmMatches(toRemove);
|
|
5129
|
+
deletePmMatches(toRemove);
|
|
5130
|
+
updatePmInstanceCount(remaining, desiredCount);
|
|
5131
|
+
console.log(`[pm] scaled ${baseName} to ${desiredCount} instance${desiredCount === 1 ? "" : "s"}`);
|
|
5132
|
+
return;
|
|
5133
|
+
}
|
|
5134
|
+
updatePmInstanceCount(currentMatches, desiredCount);
|
|
5135
|
+
const baseRecord = currentMatches[0]?.record;
|
|
5136
|
+
if (!baseRecord) {
|
|
5137
|
+
throw new Error(`No managed process found for: ${target}`);
|
|
5138
|
+
}
|
|
5139
|
+
const baseDefinition = rebuildPmRecordDefinition(baseRecord, desiredCount);
|
|
5140
|
+
const expandedDefinitions = expandPmInstanceDefinitions(baseDefinition, desiredCount);
|
|
5141
|
+
const existingNames = new Set(currentMatches.map((match) => match.record.name));
|
|
5142
|
+
for (const definition of expandedDefinitions) {
|
|
5143
|
+
if (existingNames.has(definition.name)) {
|
|
5144
|
+
continue;
|
|
5145
|
+
}
|
|
5146
|
+
await startManagedProcess(definition, paths);
|
|
5147
|
+
}
|
|
5148
|
+
console.log(`[pm] scaled ${baseName} to ${desiredCount} instance${desiredCount === 1 ? "" : "s"}`);
|
|
5149
|
+
}
|
|
5150
|
+
async function runPmSendSignal(args) {
|
|
5151
|
+
const signalArg = args[0];
|
|
5152
|
+
const target = args[1];
|
|
5153
|
+
if (!signalArg || !target || args.length > 2) {
|
|
5154
|
+
throw new Error("Usage: elit pm send-signal <signal> <name|all>");
|
|
5155
|
+
}
|
|
5156
|
+
const signalName = normalizePmSignalName(signalArg);
|
|
5157
|
+
const { paths } = await loadPmContext();
|
|
5158
|
+
const matches = resolveNamedMatches(paths, target);
|
|
5159
|
+
if (matches.length === 0) {
|
|
5160
|
+
throw new Error(`No managed process found for: ${target}`);
|
|
5161
|
+
}
|
|
5162
|
+
let signaled = 0;
|
|
5163
|
+
const errors = [];
|
|
3015
5164
|
for (const match of matches) {
|
|
3016
|
-
|
|
3017
|
-
|
|
5165
|
+
const pid = resolveSignalablePid(match.record);
|
|
5166
|
+
if (!pid) {
|
|
5167
|
+
continue;
|
|
3018
5168
|
}
|
|
3019
|
-
|
|
3020
|
-
|
|
5169
|
+
try {
|
|
5170
|
+
sendPmSignal(pid, signalName);
|
|
5171
|
+
signaled += 1;
|
|
5172
|
+
} catch (error) {
|
|
5173
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
5174
|
+
errors.push(`[pm] ${match.record.name}: ${message}`);
|
|
3021
5175
|
}
|
|
3022
|
-
rmSync(match.filePath, { force: true });
|
|
3023
5176
|
}
|
|
3024
|
-
|
|
5177
|
+
if (errors.length > 0) {
|
|
5178
|
+
throw new Error([`[pm] sent ${signalName} to ${signaled} process${signaled === 1 ? "" : "es"}`, ...errors].join(EOL2));
|
|
5179
|
+
}
|
|
5180
|
+
if (signaled === 0) {
|
|
5181
|
+
throw new Error(`No running managed process found for: ${target}`);
|
|
5182
|
+
}
|
|
5183
|
+
console.log(`[pm] sent ${signalName} to ${signaled} process${signaled === 1 ? "" : "es"}`);
|
|
5184
|
+
}
|
|
5185
|
+
async function runPmReset(args) {
|
|
5186
|
+
const target = args[0];
|
|
5187
|
+
if (!target) {
|
|
5188
|
+
throw new Error("Usage: elit pm reset <name|all>");
|
|
5189
|
+
}
|
|
5190
|
+
const { paths } = await loadPmContext();
|
|
5191
|
+
const matches = resolveNamedMatches(paths, target);
|
|
5192
|
+
if (matches.length === 0) {
|
|
5193
|
+
throw new Error(`No managed process found for: ${target}`);
|
|
5194
|
+
}
|
|
5195
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
5196
|
+
for (const match of matches) {
|
|
5197
|
+
writePmRecord(match.filePath, {
|
|
5198
|
+
...match.record,
|
|
5199
|
+
restartCount: 0,
|
|
5200
|
+
lastExitCode: void 0,
|
|
5201
|
+
error: void 0,
|
|
5202
|
+
updatedAt: now
|
|
5203
|
+
});
|
|
5204
|
+
}
|
|
5205
|
+
console.log(`[pm] reset ${matches.length} process${matches.length === 1 ? "" : "es"}`);
|
|
3025
5206
|
}
|
|
3026
5207
|
async function runPmLogs(args) {
|
|
3027
5208
|
if (args.length === 0) {
|
|
@@ -3054,7 +5235,7 @@ async function runPmLogs(args) {
|
|
|
3054
5235
|
throw new Error("Usage: elit pm logs <name> [--lines <n>] [--stderr]");
|
|
3055
5236
|
}
|
|
3056
5237
|
const { paths } = await loadPmContext();
|
|
3057
|
-
const match =
|
|
5238
|
+
const match = resolveInspectableMatch(paths, name);
|
|
3058
5239
|
if (!match) {
|
|
3059
5240
|
throw new Error(`No managed process found for: ${name}`);
|
|
3060
5241
|
}
|
|
@@ -3078,17 +5259,36 @@ async function runPmCommand(args) {
|
|
|
3078
5259
|
await runPmStart(args.slice(1));
|
|
3079
5260
|
return;
|
|
3080
5261
|
case "list":
|
|
3081
|
-
case "ls":
|
|
3082
|
-
|
|
3083
|
-
|
|
5262
|
+
case "ls":
|
|
5263
|
+
await runPmList(args.slice(1));
|
|
5264
|
+
return;
|
|
5265
|
+
case "jlist":
|
|
5266
|
+
await runPmList(["--json", ...args.slice(1)]);
|
|
5267
|
+
return;
|
|
5268
|
+
case "show":
|
|
5269
|
+
case "describe":
|
|
5270
|
+
await runPmShow(args.slice(1));
|
|
3084
5271
|
return;
|
|
3085
|
-
}
|
|
3086
5272
|
case "stop":
|
|
3087
5273
|
await runPmStop(args.slice(1));
|
|
3088
5274
|
return;
|
|
3089
5275
|
case "restart":
|
|
3090
5276
|
await runPmRestart(args.slice(1));
|
|
3091
5277
|
return;
|
|
5278
|
+
case "reload":
|
|
5279
|
+
await runPmReload(args.slice(1));
|
|
5280
|
+
return;
|
|
5281
|
+
case "scale":
|
|
5282
|
+
await runPmScale(args.slice(1));
|
|
5283
|
+
return;
|
|
5284
|
+
case "send-signal":
|
|
5285
|
+
case "signal":
|
|
5286
|
+
case "sendSignal":
|
|
5287
|
+
await runPmSendSignal(args.slice(1));
|
|
5288
|
+
return;
|
|
5289
|
+
case "reset":
|
|
5290
|
+
await runPmReset(args.slice(1));
|
|
5291
|
+
return;
|
|
3092
5292
|
case "delete":
|
|
3093
5293
|
case "remove":
|
|
3094
5294
|
case "rm":
|
|
@@ -3120,6 +5320,12 @@ export {
|
|
|
3120
5320
|
DEFAULT_MIN_UPTIME,
|
|
3121
5321
|
DEFAULT_PM_DATA_DIR,
|
|
3122
5322
|
DEFAULT_PM_DUMP_FILE,
|
|
5323
|
+
DEFAULT_PM_EXP_BACKOFF_MAX_DELAY,
|
|
5324
|
+
DEFAULT_PM_KILL_TIMEOUT,
|
|
5325
|
+
DEFAULT_PM_LISTEN_TIMEOUT,
|
|
5326
|
+
DEFAULT_PM_MEMORY_CHECK_INTERVAL,
|
|
5327
|
+
DEFAULT_PM_PROXY_STRATEGY,
|
|
5328
|
+
DEFAULT_PM_RESTART_WINDOW,
|
|
3123
5329
|
DEFAULT_PM_STOP_GRACE_PERIOD_MS,
|
|
3124
5330
|
DEFAULT_PM_STOP_POLL_MS,
|
|
3125
5331
|
DEFAULT_RESTART_DELAY,
|
|
@@ -3138,7 +5344,10 @@ export {
|
|
|
3138
5344
|
countDefinedPmWapkSources,
|
|
3139
5345
|
deriveDefaultWatchPaths,
|
|
3140
5346
|
ensurePmDirectories,
|
|
5347
|
+
expandPmInstanceDefinitions,
|
|
5348
|
+
findPmGroupMatches,
|
|
3141
5349
|
findPmRecordMatch,
|
|
5350
|
+
formatPmInstanceName,
|
|
3142
5351
|
getPmRecordPath,
|
|
3143
5352
|
hasPmGoogleDriveConfig,
|
|
3144
5353
|
hasPmWapkRunConfig,
|
|
@@ -3157,6 +5366,10 @@ export {
|
|
|
3157
5366
|
normalizeHealthCheckConfig,
|
|
3158
5367
|
normalizeIntegerOption,
|
|
3159
5368
|
normalizeNonEmptyString,
|
|
5369
|
+
normalizePmMemoryAction,
|
|
5370
|
+
normalizePmMemoryLimit,
|
|
5371
|
+
normalizePmProxyConfig,
|
|
5372
|
+
normalizePmProxyStrategy,
|
|
3160
5373
|
normalizePmRestartPolicy,
|
|
3161
5374
|
normalizePmRuntime,
|
|
3162
5375
|
normalizeResolvedWatchIgnorePaths,
|
|
@@ -3180,7 +5393,9 @@ export {
|
|
|
3180
5393
|
runManagedProcessLoop,
|
|
3181
5394
|
runPmCommand,
|
|
3182
5395
|
runPmRunner,
|
|
5396
|
+
samplePmProcessMetrics,
|
|
3183
5397
|
sanitizePmProcessName,
|
|
5398
|
+
sendPmSignal,
|
|
3184
5399
|
startManagedProcess,
|
|
3185
5400
|
stopPmMatches,
|
|
3186
5401
|
stripPmWapkSourceFromRunConfig,
|