elit 3.6.7 → 3.6.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Cargo.lock +1 -1
- package/Cargo.toml +1 -1
- package/README.md +20 -1
- package/dist/cli.cjs +2496 -303
- package/dist/cli.mjs +2501 -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 +2101 -134
- package/dist/pm.d.ts +83 -8
- package/dist/pm.js +2095 -158
- package/dist/pm.mjs +2091 -139
- 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,9 +2674,210 @@ function watch(paths, options) {
|
|
|
2130
2674
|
return watcher;
|
|
2131
2675
|
}
|
|
2132
2676
|
|
|
2677
|
+
// src/cli/pm/proxy.ts
|
|
2678
|
+
import { createServer, request as httpRequest } from "http";
|
|
2679
|
+
import { request as httpsRequest } from "https";
|
|
2680
|
+
import { createServer as createNetServer } from "net";
|
|
2681
|
+
function resolvePmProxyHost(proxy) {
|
|
2682
|
+
return proxy.host?.trim() || "0.0.0.0";
|
|
2683
|
+
}
|
|
2684
|
+
function resolvePmProxyTargetHost(proxy) {
|
|
2685
|
+
return proxy.targetHost?.trim() || "127.0.0.1";
|
|
2686
|
+
}
|
|
2687
|
+
function resolvePmProxyEnvVar(proxy) {
|
|
2688
|
+
return proxy.envVar?.trim() || "PORT";
|
|
2689
|
+
}
|
|
2690
|
+
function buildPmProxyTargetUrl(proxy, targetPort) {
|
|
2691
|
+
return `http://${resolvePmProxyTargetHost(proxy)}:${targetPort}`;
|
|
2692
|
+
}
|
|
2693
|
+
function rewritePmProxyHealthCheckUrl(url, targetHost, targetPort) {
|
|
2694
|
+
const targetUrl = new URL(url);
|
|
2695
|
+
targetUrl.hostname = targetHost;
|
|
2696
|
+
targetUrl.port = String(targetPort);
|
|
2697
|
+
return targetUrl.toString();
|
|
2698
|
+
}
|
|
2699
|
+
async function allocatePmProxyTargetPort(host = "127.0.0.1") {
|
|
2700
|
+
const server = createNetServer();
|
|
2701
|
+
return await new Promise((resolve7, reject) => {
|
|
2702
|
+
server.once("error", reject);
|
|
2703
|
+
server.listen(0, host, () => {
|
|
2704
|
+
const address = server.address();
|
|
2705
|
+
if (!address || typeof address === "string") {
|
|
2706
|
+
server.close(() => reject(new Error("Failed to allocate an internal PM proxy port.")));
|
|
2707
|
+
return;
|
|
2708
|
+
}
|
|
2709
|
+
const port = address.port;
|
|
2710
|
+
server.close((error) => {
|
|
2711
|
+
if (error) {
|
|
2712
|
+
reject(error);
|
|
2713
|
+
return;
|
|
2714
|
+
}
|
|
2715
|
+
resolve7(port);
|
|
2716
|
+
});
|
|
2717
|
+
});
|
|
2718
|
+
});
|
|
2719
|
+
}
|
|
2720
|
+
function buildPmProxyHeaders(headersInput, host) {
|
|
2721
|
+
const headers = {};
|
|
2722
|
+
for (const [key, value] of Object.entries(headersInput)) {
|
|
2723
|
+
if (value !== void 0) {
|
|
2724
|
+
headers[key] = value;
|
|
2725
|
+
}
|
|
2726
|
+
}
|
|
2727
|
+
headers.host = host;
|
|
2728
|
+
return headers;
|
|
2729
|
+
}
|
|
2730
|
+
function writeRawHttpResponse(socket, statusCode, statusMessage, headers) {
|
|
2731
|
+
const lines = [`HTTP/1.1 ${statusCode} ${statusMessage}`];
|
|
2732
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
2733
|
+
if (value === void 0) {
|
|
2734
|
+
continue;
|
|
2735
|
+
}
|
|
2736
|
+
if (Array.isArray(value)) {
|
|
2737
|
+
for (const item of value) {
|
|
2738
|
+
lines.push(`${key}: ${item}`);
|
|
2739
|
+
}
|
|
2740
|
+
continue;
|
|
2741
|
+
}
|
|
2742
|
+
lines.push(`${key}: ${value}`);
|
|
2743
|
+
}
|
|
2744
|
+
socket.write(`${lines.join("\r\n")}\r
|
|
2745
|
+
\r
|
|
2746
|
+
`);
|
|
2747
|
+
}
|
|
2748
|
+
async function createPmProxyController(proxy) {
|
|
2749
|
+
let targets = [];
|
|
2750
|
+
let nextTargetIndex = 0;
|
|
2751
|
+
const setResolvedTargets = (nextTargets) => {
|
|
2752
|
+
const unchanged = nextTargets.length === targets.length && nextTargets.every((target, index) => targets[index]?.href === target.href);
|
|
2753
|
+
targets = nextTargets;
|
|
2754
|
+
if (targets.length === 0) {
|
|
2755
|
+
nextTargetIndex = 0;
|
|
2756
|
+
return;
|
|
2757
|
+
}
|
|
2758
|
+
if (!unchanged) {
|
|
2759
|
+
nextTargetIndex = nextTargetIndex % targets.length;
|
|
2760
|
+
}
|
|
2761
|
+
};
|
|
2762
|
+
const pickTarget = () => {
|
|
2763
|
+
if (targets.length === 0) {
|
|
2764
|
+
return null;
|
|
2765
|
+
}
|
|
2766
|
+
const target = targets[nextTargetIndex % targets.length];
|
|
2767
|
+
nextTargetIndex = (nextTargetIndex + 1) % targets.length;
|
|
2768
|
+
return target;
|
|
2769
|
+
};
|
|
2770
|
+
const server = createServer((req, res) => {
|
|
2771
|
+
const target = pickTarget();
|
|
2772
|
+
if (!target) {
|
|
2773
|
+
res.statusCode = 503;
|
|
2774
|
+
res.end("PM proxy target is not ready.");
|
|
2775
|
+
return;
|
|
2776
|
+
}
|
|
2777
|
+
const requestLib = target.protocol === "https:" ? httpsRequest : httpRequest;
|
|
2778
|
+
const targetUrl = new URL(req.url || "/", target);
|
|
2779
|
+
const headers = buildPmProxyHeaders(req.headers, target.host);
|
|
2780
|
+
const proxyReq = requestLib(targetUrl, {
|
|
2781
|
+
method: req.method,
|
|
2782
|
+
headers
|
|
2783
|
+
}, (proxyRes) => {
|
|
2784
|
+
const outgoingHeaders = {};
|
|
2785
|
+
for (const [key, value] of Object.entries(proxyRes.headers)) {
|
|
2786
|
+
if (value !== void 0) {
|
|
2787
|
+
outgoingHeaders[key] = value;
|
|
2788
|
+
}
|
|
2789
|
+
}
|
|
2790
|
+
res.writeHead(proxyRes.statusCode || 200, outgoingHeaders);
|
|
2791
|
+
proxyRes.pipe(res);
|
|
2792
|
+
});
|
|
2793
|
+
proxyReq.on("error", (error) => {
|
|
2794
|
+
if (!res.headersSent) {
|
|
2795
|
+
res.statusCode = 502;
|
|
2796
|
+
}
|
|
2797
|
+
res.end(`PM proxy error: ${error.message}`);
|
|
2798
|
+
});
|
|
2799
|
+
req.pipe(proxyReq);
|
|
2800
|
+
});
|
|
2801
|
+
server.on("upgrade", (req, socket, head) => {
|
|
2802
|
+
const target = pickTarget();
|
|
2803
|
+
if (!target) {
|
|
2804
|
+
writeRawHttpResponse(socket, 503, "Service Unavailable", {
|
|
2805
|
+
connection: "close",
|
|
2806
|
+
"content-length": 0
|
|
2807
|
+
});
|
|
2808
|
+
socket.destroy();
|
|
2809
|
+
return;
|
|
2810
|
+
}
|
|
2811
|
+
const requestLib = target.protocol === "https:" ? httpsRequest : httpRequest;
|
|
2812
|
+
const targetUrl = new URL(req.url || "/", target);
|
|
2813
|
+
const proxyReq = requestLib(targetUrl, {
|
|
2814
|
+
method: req.method,
|
|
2815
|
+
headers: buildPmProxyHeaders(req.headers, target.host)
|
|
2816
|
+
});
|
|
2817
|
+
proxyReq.on("upgrade", (proxyRes, proxySocket, proxyHead) => {
|
|
2818
|
+
writeRawHttpResponse(socket, proxyRes.statusCode || 101, proxyRes.statusMessage || "Switching Protocols", proxyRes.headers);
|
|
2819
|
+
if (head.length > 0) {
|
|
2820
|
+
proxySocket.write(head);
|
|
2821
|
+
}
|
|
2822
|
+
if (proxyHead.length > 0) {
|
|
2823
|
+
socket.write(proxyHead);
|
|
2824
|
+
}
|
|
2825
|
+
socket.on("error", () => proxySocket.destroy());
|
|
2826
|
+
proxySocket.on("error", () => socket.destroy());
|
|
2827
|
+
proxySocket.pipe(socket);
|
|
2828
|
+
socket.pipe(proxySocket);
|
|
2829
|
+
});
|
|
2830
|
+
proxyReq.on("response", (proxyRes) => {
|
|
2831
|
+
writeRawHttpResponse(socket, proxyRes.statusCode || 502, proxyRes.statusMessage || "Bad Gateway", proxyRes.headers);
|
|
2832
|
+
proxyRes.pipe(socket);
|
|
2833
|
+
});
|
|
2834
|
+
proxyReq.on("error", (error) => {
|
|
2835
|
+
writeRawHttpResponse(socket, 502, "Bad Gateway", {
|
|
2836
|
+
connection: "close",
|
|
2837
|
+
"content-type": "text/plain; charset=utf-8",
|
|
2838
|
+
"content-length": Buffer.byteLength(`PM proxy error: ${error.message}`)
|
|
2839
|
+
});
|
|
2840
|
+
socket.end(`PM proxy error: ${error.message}`);
|
|
2841
|
+
});
|
|
2842
|
+
proxyReq.end();
|
|
2843
|
+
});
|
|
2844
|
+
await new Promise((resolve7, reject) => {
|
|
2845
|
+
server.once("error", reject);
|
|
2846
|
+
server.listen(proxy.port, resolvePmProxyHost(proxy), () => resolve7());
|
|
2847
|
+
});
|
|
2848
|
+
return {
|
|
2849
|
+
setTarget(targetUrl) {
|
|
2850
|
+
setResolvedTargets(targetUrl ? [new URL(targetUrl)] : []);
|
|
2851
|
+
},
|
|
2852
|
+
setTargets(targetUrls) {
|
|
2853
|
+
setResolvedTargets(targetUrls.map((targetUrl) => new URL(targetUrl)));
|
|
2854
|
+
},
|
|
2855
|
+
close() {
|
|
2856
|
+
return new Promise((resolve7, reject) => {
|
|
2857
|
+
server.close((error) => {
|
|
2858
|
+
if (error) {
|
|
2859
|
+
reject(error);
|
|
2860
|
+
return;
|
|
2861
|
+
}
|
|
2862
|
+
resolve7();
|
|
2863
|
+
});
|
|
2864
|
+
});
|
|
2865
|
+
}
|
|
2866
|
+
};
|
|
2867
|
+
}
|
|
2868
|
+
|
|
2133
2869
|
// src/cli/pm/runner.ts
|
|
2134
2870
|
function writePmLog(stream, message) {
|
|
2135
|
-
stream.
|
|
2871
|
+
if (stream.writableEnded || stream.destroyed) {
|
|
2872
|
+
return;
|
|
2873
|
+
}
|
|
2874
|
+
try {
|
|
2875
|
+
stream.write(`[elit pm] ${(/* @__PURE__ */ new Date()).toISOString()} ${message}${EOL}`);
|
|
2876
|
+
} catch (error) {
|
|
2877
|
+
if (error.code !== "ERR_STREAM_WRITE_AFTER_END") {
|
|
2878
|
+
throw error;
|
|
2879
|
+
}
|
|
2880
|
+
}
|
|
2136
2881
|
}
|
|
2137
2882
|
function waitForExit(code, signal) {
|
|
2138
2883
|
if (typeof code === "number") {
|
|
@@ -2146,40 +2891,369 @@ function waitForExit(code, signal) {
|
|
|
2146
2891
|
async function delay(milliseconds) {
|
|
2147
2892
|
await new Promise((resolvePromise) => setTimeout(resolvePromise, milliseconds));
|
|
2148
2893
|
}
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
await delay(DEFAULT_PM_STOP_POLL_MS);
|
|
2159
|
-
}
|
|
2160
|
-
return !isProcessAlive(pid);
|
|
2894
|
+
function resolveRunnerPathsFromRecordFile(filePath) {
|
|
2895
|
+
const appsDir = dirname4(filePath);
|
|
2896
|
+
const dataDir = dirname4(appsDir);
|
|
2897
|
+
return {
|
|
2898
|
+
dataDir,
|
|
2899
|
+
appsDir,
|
|
2900
|
+
logsDir: join6(dataDir, "logs"),
|
|
2901
|
+
dumpFile: join6(dataDir, DEFAULT_PM_DUMP_FILE)
|
|
2902
|
+
};
|
|
2161
2903
|
}
|
|
2162
|
-
|
|
2163
|
-
return
|
|
2164
|
-
let resolved = false;
|
|
2165
|
-
child.once("error", (error) => {
|
|
2166
|
-
if (resolved) {
|
|
2167
|
-
return;
|
|
2168
|
-
}
|
|
2169
|
-
resolved = true;
|
|
2170
|
-
resolvePromise({ code: 1, signal: null, error: error instanceof Error ? error.message : String(error) });
|
|
2171
|
-
});
|
|
2172
|
-
child.once("close", (code, signal) => {
|
|
2173
|
-
if (resolved) {
|
|
2174
|
-
return;
|
|
2175
|
-
}
|
|
2176
|
-
resolved = true;
|
|
2177
|
-
resolvePromise({ code, signal });
|
|
2178
|
-
});
|
|
2179
|
-
});
|
|
2904
|
+
function usesPmProxyController(record) {
|
|
2905
|
+
return Boolean(record.proxy) && (record.proxy?.strategy ?? DEFAULT_PM_PROXY_STRATEGY) === "proxy";
|
|
2180
2906
|
}
|
|
2181
|
-
|
|
2182
|
-
|
|
2907
|
+
function usesPmInheritedListener(record) {
|
|
2908
|
+
return Boolean(record.proxy) && (record.proxy?.strategy ?? DEFAULT_PM_PROXY_STRATEGY) === "inherit";
|
|
2909
|
+
}
|
|
2910
|
+
function isPmProxyOwner(record) {
|
|
2911
|
+
return usesPmProxyController(record) && record.instanceIndex === 1;
|
|
2912
|
+
}
|
|
2913
|
+
function resolvePmProxyTargetUrls(paths, baseName) {
|
|
2914
|
+
return listPmRecordMatches(paths).filter((match) => match.record.baseName === baseName).filter((match) => usesPmProxyController(match.record)).filter((match) => match.record.desiredState === "running" && Boolean(match.record.proxyReadyAt)).filter((match) => typeof match.record.proxyTargetPort === "number" && match.record.proxyTargetPort > 0).sort((left, right) => left.record.instanceIndex - right.record.instanceIndex).map((match) => buildPmProxyTargetUrl(match.record.proxy, match.record.proxyTargetPort));
|
|
2915
|
+
}
|
|
2916
|
+
async function createPmInheritedListener(proxy) {
|
|
2917
|
+
const server = createHttpServer();
|
|
2918
|
+
await new Promise((resolvePromise, reject) => {
|
|
2919
|
+
server.once("error", reject);
|
|
2920
|
+
server.listen(proxy.port, resolvePmProxyHost(proxy), () => resolvePromise());
|
|
2921
|
+
});
|
|
2922
|
+
return server;
|
|
2923
|
+
}
|
|
2924
|
+
function createPmChildIpcState(child, sharedListener) {
|
|
2925
|
+
let bootstrapReady = false;
|
|
2926
|
+
let listenerReady = false;
|
|
2927
|
+
let sharedHandleSent = false;
|
|
2928
|
+
const sendSharedHandle = () => {
|
|
2929
|
+
if (!sharedListener || !bootstrapReady || sharedHandleSent || !child.connected) {
|
|
2930
|
+
return;
|
|
2931
|
+
}
|
|
2932
|
+
sharedHandleSent = true;
|
|
2933
|
+
child.send?.({ type: "elit:pm:listen-handle" }, sharedListener);
|
|
2934
|
+
};
|
|
2935
|
+
const onMessage = (message) => {
|
|
2936
|
+
if (!message || typeof message !== "object") {
|
|
2937
|
+
return;
|
|
2938
|
+
}
|
|
2939
|
+
if (message.type === "elit:pm:bootstrap-ready") {
|
|
2940
|
+
bootstrapReady = true;
|
|
2941
|
+
sendSharedHandle();
|
|
2942
|
+
return;
|
|
2943
|
+
}
|
|
2944
|
+
if (message.type === "elit:pm:listener-ready") {
|
|
2945
|
+
listenerReady = true;
|
|
2946
|
+
}
|
|
2947
|
+
};
|
|
2948
|
+
child.on("message", onMessage);
|
|
2949
|
+
return {
|
|
2950
|
+
get bootstrapReady() {
|
|
2951
|
+
return bootstrapReady;
|
|
2952
|
+
},
|
|
2953
|
+
get listenerReady() {
|
|
2954
|
+
return listenerReady;
|
|
2955
|
+
},
|
|
2956
|
+
stop() {
|
|
2957
|
+
child.off("message", onMessage);
|
|
2958
|
+
}
|
|
2959
|
+
};
|
|
2960
|
+
}
|
|
2961
|
+
function buildPmChildEnv(record, command, targetPort) {
|
|
2962
|
+
return {
|
|
2963
|
+
...process.env,
|
|
2964
|
+
...record.env,
|
|
2965
|
+
...command.env,
|
|
2966
|
+
...usesPmProxyController(record) && targetPort ? {
|
|
2967
|
+
[resolvePmProxyEnvVar(record.proxy)]: String(targetPort),
|
|
2968
|
+
ELIT_PM_PUBLIC_PORT: String(record.proxy.port)
|
|
2969
|
+
} : {},
|
|
2970
|
+
ELIT_PM_NAME: record.name,
|
|
2971
|
+
ELIT_PM_ID: record.id
|
|
2972
|
+
};
|
|
2973
|
+
}
|
|
2974
|
+
function buildPmChildStdio(command, onlineStdinShutdownEnabled) {
|
|
2975
|
+
return [
|
|
2976
|
+
onlineStdinShutdownEnabled ? "pipe" : "ignore",
|
|
2977
|
+
"pipe",
|
|
2978
|
+
"pipe",
|
|
2979
|
+
...command.ipc ? ["ipc"] : []
|
|
2980
|
+
];
|
|
2981
|
+
}
|
|
2982
|
+
function createPmReadinessMonitor(record, onReady, onFailure, options) {
|
|
2983
|
+
if (options?.ipcController) {
|
|
2984
|
+
let stopped2 = false;
|
|
2985
|
+
let timer2 = null;
|
|
2986
|
+
let timeoutTimer2 = null;
|
|
2987
|
+
const clearTimers2 = () => {
|
|
2988
|
+
if (timer2) {
|
|
2989
|
+
clearInterval(timer2);
|
|
2990
|
+
timer2 = null;
|
|
2991
|
+
}
|
|
2992
|
+
if (timeoutTimer2) {
|
|
2993
|
+
clearTimeout(timeoutTimer2);
|
|
2994
|
+
timeoutTimer2 = null;
|
|
2995
|
+
}
|
|
2996
|
+
};
|
|
2997
|
+
const host = record.proxy?.host ?? "0.0.0.0";
|
|
2998
|
+
const port = record.proxy?.port ?? 0;
|
|
2999
|
+
timer2 = setInterval(() => {
|
|
3000
|
+
if (stopped2 || !options.ipcController?.listenerReady) {
|
|
3001
|
+
return;
|
|
3002
|
+
}
|
|
3003
|
+
stopped2 = true;
|
|
3004
|
+
clearTimers2();
|
|
3005
|
+
onReady(`shared listener ready on ${host}:${port}`);
|
|
3006
|
+
}, 25);
|
|
3007
|
+
timer2.unref?.();
|
|
3008
|
+
timeoutTimer2 = setTimeout(() => {
|
|
3009
|
+
if (stopped2) {
|
|
3010
|
+
return;
|
|
3011
|
+
}
|
|
3012
|
+
stopped2 = true;
|
|
3013
|
+
clearTimers2();
|
|
3014
|
+
onFailure(`listen timeout reached after ${record.listenTimeout}ms while waiting for shared listener ${host}:${port}`);
|
|
3015
|
+
}, record.listenTimeout);
|
|
3016
|
+
timeoutTimer2.unref?.();
|
|
3017
|
+
return {
|
|
3018
|
+
stop() {
|
|
3019
|
+
stopped2 = true;
|
|
3020
|
+
clearTimers2();
|
|
3021
|
+
}
|
|
3022
|
+
};
|
|
3023
|
+
}
|
|
3024
|
+
if (!record.waitReady || !record.healthCheck) {
|
|
3025
|
+
return {
|
|
3026
|
+
stop() {
|
|
3027
|
+
}
|
|
3028
|
+
};
|
|
3029
|
+
}
|
|
3030
|
+
const healthCheck = record.healthCheck;
|
|
3031
|
+
const pollInterval = Math.max(50, Math.min(healthCheck.interval, 250));
|
|
3032
|
+
let stopped = false;
|
|
3033
|
+
let timer = null;
|
|
3034
|
+
let timeoutTimer = null;
|
|
3035
|
+
let inFlight = false;
|
|
3036
|
+
const clearTimers = () => {
|
|
3037
|
+
if (timer) {
|
|
3038
|
+
clearInterval(timer);
|
|
3039
|
+
timer = null;
|
|
3040
|
+
}
|
|
3041
|
+
if (timeoutTimer) {
|
|
3042
|
+
clearTimeout(timeoutTimer);
|
|
3043
|
+
timeoutTimer = null;
|
|
3044
|
+
}
|
|
3045
|
+
};
|
|
3046
|
+
const runHealthCheck = async () => {
|
|
3047
|
+
if (stopped || inFlight) {
|
|
3048
|
+
return;
|
|
3049
|
+
}
|
|
3050
|
+
inFlight = true;
|
|
3051
|
+
const controller = new AbortController();
|
|
3052
|
+
const timeoutId = setTimeout(() => controller.abort(), healthCheck.timeout);
|
|
3053
|
+
timeoutId.unref?.();
|
|
3054
|
+
try {
|
|
3055
|
+
const response = await fetch(healthCheck.url, {
|
|
3056
|
+
method: "GET",
|
|
3057
|
+
signal: controller.signal
|
|
3058
|
+
});
|
|
3059
|
+
if (stopped) {
|
|
3060
|
+
return;
|
|
3061
|
+
}
|
|
3062
|
+
if (!response.ok) {
|
|
3063
|
+
throw new Error(`health check returned ${response.status}`);
|
|
3064
|
+
}
|
|
3065
|
+
stopped = true;
|
|
3066
|
+
clearTimers();
|
|
3067
|
+
onReady(`readiness check passed: ${healthCheck.url}`);
|
|
3068
|
+
} catch {
|
|
3069
|
+
} finally {
|
|
3070
|
+
clearTimeout(timeoutId);
|
|
3071
|
+
inFlight = false;
|
|
3072
|
+
}
|
|
3073
|
+
};
|
|
3074
|
+
timeoutTimer = setTimeout(() => {
|
|
3075
|
+
if (stopped) {
|
|
3076
|
+
return;
|
|
3077
|
+
}
|
|
3078
|
+
stopped = true;
|
|
3079
|
+
clearTimers();
|
|
3080
|
+
onFailure(`listen timeout reached after ${record.listenTimeout}ms while waiting for ${healthCheck.url}`);
|
|
3081
|
+
}, record.listenTimeout);
|
|
3082
|
+
timeoutTimer.unref?.();
|
|
3083
|
+
void runHealthCheck();
|
|
3084
|
+
timer = setInterval(() => {
|
|
3085
|
+
void runHealthCheck();
|
|
3086
|
+
}, pollInterval);
|
|
3087
|
+
timer.unref?.();
|
|
3088
|
+
return {
|
|
3089
|
+
stop() {
|
|
3090
|
+
stopped = true;
|
|
3091
|
+
clearTimers();
|
|
3092
|
+
}
|
|
3093
|
+
};
|
|
3094
|
+
}
|
|
3095
|
+
function resolvePmStopTimeout(record) {
|
|
3096
|
+
if (isPmOnlineWapkRecord(record)) {
|
|
3097
|
+
return Math.max(record.killTimeout, PM_WAPK_ONLINE_SHUTDOWN_TIMEOUT_MS);
|
|
3098
|
+
}
|
|
3099
|
+
return record.killTimeout;
|
|
3100
|
+
}
|
|
3101
|
+
function supportsPmProxyReload(record) {
|
|
3102
|
+
return Boolean(record.proxy) && record.instances === 1;
|
|
3103
|
+
}
|
|
3104
|
+
function buildPmMonitorRecord(record, targetPort) {
|
|
3105
|
+
if (!record.proxy || !targetPort) {
|
|
3106
|
+
return record;
|
|
3107
|
+
}
|
|
3108
|
+
const targetHost = resolvePmProxyTargetHost(record.proxy);
|
|
3109
|
+
return {
|
|
3110
|
+
...record,
|
|
3111
|
+
proxyTargetPort: targetPort,
|
|
3112
|
+
healthCheck: record.healthCheck ? {
|
|
3113
|
+
...record.healthCheck,
|
|
3114
|
+
url: rewritePmProxyHealthCheckUrl(record.healthCheck.url, targetHost, targetPort)
|
|
3115
|
+
} : void 0
|
|
3116
|
+
};
|
|
3117
|
+
}
|
|
3118
|
+
async function waitForProcessTermination(pid, timeoutMs) {
|
|
3119
|
+
if (!pid || !isProcessAlive(pid)) {
|
|
3120
|
+
return true;
|
|
3121
|
+
}
|
|
3122
|
+
const deadline = Date.now() + timeoutMs;
|
|
3123
|
+
while (Date.now() < deadline) {
|
|
3124
|
+
if (!isProcessAlive(pid)) {
|
|
3125
|
+
return true;
|
|
3126
|
+
}
|
|
3127
|
+
await delay(DEFAULT_PM_STOP_POLL_MS);
|
|
3128
|
+
}
|
|
3129
|
+
return !isProcessAlive(pid);
|
|
3130
|
+
}
|
|
3131
|
+
async function waitForManagedChildExit(child) {
|
|
3132
|
+
return await new Promise((resolvePromise) => {
|
|
3133
|
+
let resolved = false;
|
|
3134
|
+
child.once("error", (error) => {
|
|
3135
|
+
if (resolved) {
|
|
3136
|
+
return;
|
|
3137
|
+
}
|
|
3138
|
+
resolved = true;
|
|
3139
|
+
resolvePromise({ code: 1, signal: null, error: error instanceof Error ? error.message : String(error) });
|
|
3140
|
+
});
|
|
3141
|
+
child.once("close", (code, signal) => {
|
|
3142
|
+
if (resolved) {
|
|
3143
|
+
return;
|
|
3144
|
+
}
|
|
3145
|
+
resolved = true;
|
|
3146
|
+
resolvePromise({ code, signal });
|
|
3147
|
+
});
|
|
3148
|
+
});
|
|
3149
|
+
}
|
|
3150
|
+
async function createPmChildControllers(record, child, stdoutLog, stderrLog, requestPlannedRestart, onReady, options) {
|
|
3151
|
+
let healthMonitor = {
|
|
3152
|
+
stop() {
|
|
3153
|
+
}
|
|
3154
|
+
};
|
|
3155
|
+
let memoryMonitor = {
|
|
3156
|
+
stop() {
|
|
3157
|
+
}
|
|
3158
|
+
};
|
|
3159
|
+
let scheduleMonitor = {
|
|
3160
|
+
stop() {
|
|
3161
|
+
}
|
|
3162
|
+
};
|
|
3163
|
+
const startHealthMonitor = () => {
|
|
3164
|
+
healthMonitor = createPmHealthMonitor(
|
|
3165
|
+
record,
|
|
3166
|
+
(message) => requestPlannedRestart("health", message),
|
|
3167
|
+
(message) => writePmLog(stdoutLog, message)
|
|
3168
|
+
);
|
|
3169
|
+
};
|
|
3170
|
+
const needsReadySignal = Boolean(options?.ipcController) || record.waitReady;
|
|
3171
|
+
const readinessMonitor = options?.ready || !needsReadySignal ? { stop() {
|
|
3172
|
+
} } : createPmReadinessMonitor(
|
|
3173
|
+
record,
|
|
3174
|
+
(message) => {
|
|
3175
|
+
onReady(message);
|
|
3176
|
+
startHealthMonitor();
|
|
3177
|
+
},
|
|
3178
|
+
(message) => requestPlannedRestart("startup", message),
|
|
3179
|
+
{ ipcController: options?.ipcController }
|
|
3180
|
+
);
|
|
3181
|
+
const watchController = await createPmWatchController(
|
|
3182
|
+
record,
|
|
3183
|
+
(changedPath) => requestPlannedRestart("watch", changedPath),
|
|
3184
|
+
(message) => writePmLog(stderrLog, `watch error: ${message}`)
|
|
3185
|
+
);
|
|
3186
|
+
memoryMonitor = createPmMemoryMonitor(
|
|
3187
|
+
record,
|
|
3188
|
+
child.pid,
|
|
3189
|
+
(kind, message) => requestPlannedRestart(kind, message)
|
|
3190
|
+
);
|
|
3191
|
+
scheduleMonitor = createPmScheduleMonitor(
|
|
3192
|
+
record,
|
|
3193
|
+
(message) => requestPlannedRestart("cron", message),
|
|
3194
|
+
(message) => writePmLog(stdoutLog, message)
|
|
3195
|
+
);
|
|
3196
|
+
if (options?.ready || !needsReadySignal) {
|
|
3197
|
+
startHealthMonitor();
|
|
3198
|
+
}
|
|
3199
|
+
return {
|
|
3200
|
+
async stop() {
|
|
3201
|
+
await watchController.close();
|
|
3202
|
+
readinessMonitor.stop();
|
|
3203
|
+
healthMonitor.stop();
|
|
3204
|
+
memoryMonitor.stop();
|
|
3205
|
+
scheduleMonitor.stop();
|
|
3206
|
+
}
|
|
3207
|
+
};
|
|
3208
|
+
}
|
|
3209
|
+
async function waitForPmChildReady(record, child, ipcController) {
|
|
3210
|
+
if (!ipcController && (!record.waitReady || !record.healthCheck)) {
|
|
3211
|
+
return { ready: true };
|
|
3212
|
+
}
|
|
3213
|
+
let readyMessage;
|
|
3214
|
+
let failureMessage;
|
|
3215
|
+
let exitResult;
|
|
3216
|
+
const readinessMonitor = createPmReadinessMonitor(
|
|
3217
|
+
record,
|
|
3218
|
+
(message) => {
|
|
3219
|
+
readyMessage = message;
|
|
3220
|
+
},
|
|
3221
|
+
(message) => {
|
|
3222
|
+
failureMessage = message;
|
|
3223
|
+
},
|
|
3224
|
+
{ ipcController }
|
|
3225
|
+
);
|
|
3226
|
+
void waitForManagedChildExit(child).then((result) => {
|
|
3227
|
+
exitResult = result;
|
|
3228
|
+
});
|
|
3229
|
+
while (!readyMessage && !failureMessage && !exitResult) {
|
|
3230
|
+
await delay(25);
|
|
3231
|
+
}
|
|
3232
|
+
readinessMonitor.stop();
|
|
3233
|
+
if (readyMessage) {
|
|
3234
|
+
return { ready: true, message: readyMessage };
|
|
3235
|
+
}
|
|
3236
|
+
return {
|
|
3237
|
+
ready: false,
|
|
3238
|
+
message: failureMessage,
|
|
3239
|
+
exitResult
|
|
3240
|
+
};
|
|
3241
|
+
}
|
|
3242
|
+
async function stopProxyManagedChild(child, record, stderrLog) {
|
|
3243
|
+
if (!child.pid || !isProcessAlive(child.pid)) {
|
|
3244
|
+
return;
|
|
3245
|
+
}
|
|
3246
|
+
terminateProcessTree(child.pid);
|
|
3247
|
+
const stopTimeout = resolvePmStopTimeout(record);
|
|
3248
|
+
const stopped = await waitForProcessTermination(child.pid, stopTimeout);
|
|
3249
|
+
if (!stopped && child.pid && isProcessAlive(child.pid)) {
|
|
3250
|
+
writePmLog(stderrLog, `proxy handoff shutdown timed out after ${stopTimeout}ms; forcing process termination`);
|
|
3251
|
+
terminateProcessTree(child.pid, { force: true });
|
|
3252
|
+
await waitForProcessTermination(child.pid, DEFAULT_PM_STOP_POLL_MS);
|
|
3253
|
+
}
|
|
3254
|
+
}
|
|
3255
|
+
async function createPmWatchController(record, onChange, onError) {
|
|
3256
|
+
if (!record.watch || record.watchPaths.length === 0) {
|
|
2183
3257
|
return {
|
|
2184
3258
|
async close() {
|
|
2185
3259
|
}
|
|
@@ -2241,11 +3315,17 @@ function createPmHealthMonitor(record, onFailure, onLog) {
|
|
|
2241
3315
|
method: "GET",
|
|
2242
3316
|
signal: controller.signal
|
|
2243
3317
|
});
|
|
3318
|
+
if (stopped) {
|
|
3319
|
+
return;
|
|
3320
|
+
}
|
|
2244
3321
|
if (!response.ok) {
|
|
2245
3322
|
throw new Error(`health check returned ${response.status}`);
|
|
2246
3323
|
}
|
|
2247
3324
|
failureCount = 0;
|
|
2248
3325
|
} catch (error) {
|
|
3326
|
+
if (stopped) {
|
|
3327
|
+
return;
|
|
3328
|
+
}
|
|
2249
3329
|
failureCount += 1;
|
|
2250
3330
|
const message = error instanceof Error ? error.message : String(error);
|
|
2251
3331
|
onLog(`health check failed (${failureCount}/${healthCheck.maxFailures}): ${message}`);
|
|
@@ -2280,6 +3360,90 @@ function createPmHealthMonitor(record, onFailure, onLog) {
|
|
|
2280
3360
|
}
|
|
2281
3361
|
};
|
|
2282
3362
|
}
|
|
3363
|
+
function createPmMemoryMonitor(record, pid, onFailure) {
|
|
3364
|
+
if (!record.maxMemoryBytes || !pid) {
|
|
3365
|
+
return {
|
|
3366
|
+
stop() {
|
|
3367
|
+
}
|
|
3368
|
+
};
|
|
3369
|
+
}
|
|
3370
|
+
const memoryLimit = record.maxMemoryBytes;
|
|
3371
|
+
let stopped = false;
|
|
3372
|
+
const timer = setInterval(() => {
|
|
3373
|
+
if (stopped) {
|
|
3374
|
+
return;
|
|
3375
|
+
}
|
|
3376
|
+
const memoryRssBytes = samplePmProcessMetrics(pid).memoryRssBytes;
|
|
3377
|
+
if (memoryRssBytes === void 0 || memoryRssBytes <= memoryLimit) {
|
|
3378
|
+
return;
|
|
3379
|
+
}
|
|
3380
|
+
stopped = true;
|
|
3381
|
+
onFailure(record.memoryAction === "stop" ? "memory-stop" : "memory", `memory usage ${memoryRssBytes} exceeded limit ${memoryLimit}`);
|
|
3382
|
+
}, DEFAULT_PM_MEMORY_CHECK_INTERVAL);
|
|
3383
|
+
timer.unref?.();
|
|
3384
|
+
return {
|
|
3385
|
+
stop() {
|
|
3386
|
+
stopped = true;
|
|
3387
|
+
clearInterval(timer);
|
|
3388
|
+
}
|
|
3389
|
+
};
|
|
3390
|
+
}
|
|
3391
|
+
function createPmScheduleMonitor(record, onTrigger, onLog) {
|
|
3392
|
+
if (!record.cronRestart) {
|
|
3393
|
+
return {
|
|
3394
|
+
stop() {
|
|
3395
|
+
}
|
|
3396
|
+
};
|
|
3397
|
+
}
|
|
3398
|
+
const schedule = parsePmRestartSchedule(record.cronRestart, "pm cronRestart");
|
|
3399
|
+
let stopped = false;
|
|
3400
|
+
let timer = null;
|
|
3401
|
+
const armTimer = (from = /* @__PURE__ */ new Date()) => {
|
|
3402
|
+
const nextOccurrence = resolveNextPmScheduleOccurrence(schedule, from);
|
|
3403
|
+
if (!nextOccurrence) {
|
|
3404
|
+
onLog(`schedule has no next occurrence: ${record.cronRestart}`);
|
|
3405
|
+
return;
|
|
3406
|
+
}
|
|
3407
|
+
const delayMs = Math.max(0, nextOccurrence.getTime() - Date.now());
|
|
3408
|
+
timer = setTimeout(() => {
|
|
3409
|
+
timer = null;
|
|
3410
|
+
if (stopped) {
|
|
3411
|
+
return;
|
|
3412
|
+
}
|
|
3413
|
+
onTrigger(`restart schedule matched: ${record.cronRestart}`);
|
|
3414
|
+
}, delayMs);
|
|
3415
|
+
timer.unref?.();
|
|
3416
|
+
};
|
|
3417
|
+
armTimer();
|
|
3418
|
+
return {
|
|
3419
|
+
stop() {
|
|
3420
|
+
stopped = true;
|
|
3421
|
+
if (timer) {
|
|
3422
|
+
clearTimeout(timer);
|
|
3423
|
+
timer = null;
|
|
3424
|
+
}
|
|
3425
|
+
}
|
|
3426
|
+
};
|
|
3427
|
+
}
|
|
3428
|
+
function resolvePmRestartDelay(record, restartCount, shouldApplyBackoff) {
|
|
3429
|
+
if (!shouldApplyBackoff || !record.expBackoffRestartDelay) {
|
|
3430
|
+
return record.restartDelay;
|
|
3431
|
+
}
|
|
3432
|
+
const exponent = Math.max(0, restartCount - 1);
|
|
3433
|
+
return Math.min(record.expBackoffRestartDelay * 2 ** exponent, record.expBackoffRestartMaxDelay ?? DEFAULT_PM_EXP_BACKOFF_MAX_DELAY);
|
|
3434
|
+
}
|
|
3435
|
+
function resolvePmRestartCountBase(record, wasStable, restartKind) {
|
|
3436
|
+
if (wasStable) {
|
|
3437
|
+
return 0;
|
|
3438
|
+
}
|
|
3439
|
+
if (record.restartWindow && record.lastRestartAt) {
|
|
3440
|
+
const lastRestartTime = Date.parse(record.lastRestartAt);
|
|
3441
|
+
if (!Number.isNaN(lastRestartTime) && Date.now() - lastRestartTime > record.restartWindow) {
|
|
3442
|
+
return 0;
|
|
3443
|
+
}
|
|
3444
|
+
}
|
|
3445
|
+
return restartKind === "watch" ? record.restartCount ?? 0 : record.restartCount ?? 0;
|
|
3446
|
+
}
|
|
2283
3447
|
function readPlannedRestartRequest(state) {
|
|
2284
3448
|
return state.request;
|
|
2285
3449
|
}
|
|
@@ -2289,10 +3453,20 @@ async function runManagedProcessLoop(filePath, initialRecord) {
|
|
|
2289
3453
|
let activeChildStopTimer = null;
|
|
2290
3454
|
let stopRequested = false;
|
|
2291
3455
|
const restartState = { request: null };
|
|
2292
|
-
mkdirSync3(
|
|
2293
|
-
mkdirSync3(
|
|
3456
|
+
mkdirSync3(dirname4(initialRecord.logFiles.out), { recursive: true });
|
|
3457
|
+
mkdirSync3(dirname4(initialRecord.logFiles.err), { recursive: true });
|
|
2294
3458
|
const stdoutLog = createWriteStream(initialRecord.logFiles.out, { flags: "a" });
|
|
2295
3459
|
const stderrLog = createWriteStream(initialRecord.logFiles.err, { flags: "a" });
|
|
3460
|
+
let proxyController = null;
|
|
3461
|
+
let proxyTargetSyncTimer = null;
|
|
3462
|
+
let inheritedListener = null;
|
|
3463
|
+
const runnerPaths = resolveRunnerPathsFromRecordFile(filePath);
|
|
3464
|
+
const syncOwnedProxyTargets = (baseName) => {
|
|
3465
|
+
if (!proxyController) {
|
|
3466
|
+
return;
|
|
3467
|
+
}
|
|
3468
|
+
proxyController.setTargets(resolvePmProxyTargetUrls(runnerPaths, baseName));
|
|
3469
|
+
};
|
|
2296
3470
|
const persist = (mutator) => {
|
|
2297
3471
|
const current = readLatestPmRecord(filePath, record);
|
|
2298
3472
|
record = mutator(current);
|
|
@@ -2305,26 +3479,30 @@ async function runManagedProcessLoop(filePath, initialRecord) {
|
|
|
2305
3479
|
activeChildStopTimer = null;
|
|
2306
3480
|
}
|
|
2307
3481
|
};
|
|
3482
|
+
const scheduleForcedActiveChildStop = (timeoutMs, reason) => {
|
|
3483
|
+
if (!activeChild?.pid || process.platform === "win32") {
|
|
3484
|
+
return;
|
|
3485
|
+
}
|
|
3486
|
+
clearActiveChildStopTimer();
|
|
3487
|
+
activeChildStopTimer = setTimeout(() => {
|
|
3488
|
+
if (activeChild?.pid && isProcessAlive(activeChild.pid)) {
|
|
3489
|
+
writePmLog(stderrLog, `${reason} after ${timeoutMs}ms; forcing process termination`);
|
|
3490
|
+
terminateProcessTree(activeChild.pid, { force: true });
|
|
3491
|
+
}
|
|
3492
|
+
}, timeoutMs);
|
|
3493
|
+
activeChildStopTimer.unref?.();
|
|
3494
|
+
};
|
|
2308
3495
|
const stopActiveChild = () => {
|
|
2309
3496
|
if (!activeChild?.pid || !isProcessAlive(activeChild.pid)) {
|
|
2310
3497
|
return;
|
|
2311
3498
|
}
|
|
2312
3499
|
const current = readLatestPmRecord(filePath, record);
|
|
3500
|
+
const stopTimeout = resolvePmStopTimeout(current);
|
|
2313
3501
|
if (isPmOnlineWapkRecord(current) && activeChild.stdin && !activeChild.stdin.destroyed && activeChild.stdin.writable) {
|
|
2314
3502
|
try {
|
|
2315
3503
|
activeChild.stdin.end(`${PM_WAPK_ONLINE_SHUTDOWN_COMMAND}
|
|
2316
3504
|
`);
|
|
2317
|
-
|
|
2318
|
-
activeChildStopTimer = setTimeout(() => {
|
|
2319
|
-
if (activeChild?.pid && isProcessAlive(activeChild.pid)) {
|
|
2320
|
-
writePmLog(
|
|
2321
|
-
stderrLog,
|
|
2322
|
-
`graceful WAPK online shutdown timed out after ${PM_WAPK_ONLINE_SHUTDOWN_TIMEOUT_MS}ms; forcing process termination`
|
|
2323
|
-
);
|
|
2324
|
-
terminateProcessTree(activeChild.pid);
|
|
2325
|
-
}
|
|
2326
|
-
}, PM_WAPK_ONLINE_SHUTDOWN_TIMEOUT_MS);
|
|
2327
|
-
activeChildStopTimer.unref?.();
|
|
3505
|
+
scheduleForcedActiveChildStop(stopTimeout, "graceful WAPK online shutdown timed out");
|
|
2328
3506
|
return;
|
|
2329
3507
|
} catch (error) {
|
|
2330
3508
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -2332,6 +3510,7 @@ async function runManagedProcessLoop(filePath, initialRecord) {
|
|
|
2332
3510
|
}
|
|
2333
3511
|
}
|
|
2334
3512
|
terminateProcessTree(activeChild.pid);
|
|
3513
|
+
scheduleForcedActiveChildStop(stopTimeout, "graceful shutdown timed out");
|
|
2335
3514
|
};
|
|
2336
3515
|
const requestManagedStop = (reason) => {
|
|
2337
3516
|
if (stopRequested) {
|
|
@@ -2369,6 +3548,17 @@ async function runManagedProcessLoop(filePath, initialRecord) {
|
|
|
2369
3548
|
let command;
|
|
2370
3549
|
try {
|
|
2371
3550
|
command = buildPmCommand(latest);
|
|
3551
|
+
if (latest.proxy && isPmProxyOwner(latest) && !proxyController) {
|
|
3552
|
+
proxyController = await createPmProxyController(latest.proxy);
|
|
3553
|
+
syncOwnedProxyTargets(latest.baseName);
|
|
3554
|
+
if (!proxyTargetSyncTimer) {
|
|
3555
|
+
proxyTargetSyncTimer = setInterval(() => syncOwnedProxyTargets(latest.baseName), 50);
|
|
3556
|
+
proxyTargetSyncTimer.unref?.();
|
|
3557
|
+
}
|
|
3558
|
+
}
|
|
3559
|
+
if (latest.proxy && usesPmInheritedListener(latest) && !inheritedListener) {
|
|
3560
|
+
inheritedListener = await createPmInheritedListener(latest.proxy);
|
|
3561
|
+
}
|
|
2372
3562
|
} catch (error) {
|
|
2373
3563
|
const message = error instanceof Error ? error.message : String(error);
|
|
2374
3564
|
writePmLog(stderrLog, message);
|
|
@@ -2378,25 +3568,24 @@ async function runManagedProcessLoop(filePath, initialRecord) {
|
|
|
2378
3568
|
error: message,
|
|
2379
3569
|
runnerPid: void 0,
|
|
2380
3570
|
childPid: void 0,
|
|
3571
|
+
proxyTargetPort: void 0,
|
|
3572
|
+
proxyReadyAt: void 0,
|
|
2381
3573
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2382
3574
|
}));
|
|
2383
3575
|
return;
|
|
2384
3576
|
}
|
|
2385
3577
|
const onlineStdinShutdownEnabled = isPmOnlineWapkRecord(latest);
|
|
2386
|
-
const
|
|
3578
|
+
const initialTargetPort = usesPmProxyController(latest) ? await allocatePmProxyTargetPort(resolvePmProxyTargetHost(latest.proxy)) : void 0;
|
|
3579
|
+
const monitorRecord = buildPmMonitorRecord(latest, initialTargetPort);
|
|
3580
|
+
let child = spawn2(command.command, command.args, {
|
|
2387
3581
|
cwd: latest.cwd,
|
|
2388
|
-
env:
|
|
2389
|
-
|
|
2390
|
-
...latest.env,
|
|
2391
|
-
...command.env,
|
|
2392
|
-
ELIT_PM_NAME: latest.name,
|
|
2393
|
-
ELIT_PM_ID: latest.id
|
|
2394
|
-
},
|
|
2395
|
-
stdio: [onlineStdinShutdownEnabled ? "pipe" : "ignore", "pipe", "pipe"],
|
|
3582
|
+
env: buildPmChildEnv(latest, command, initialTargetPort),
|
|
3583
|
+
stdio: buildPmChildStdio(command, onlineStdinShutdownEnabled),
|
|
2396
3584
|
windowsHide: true,
|
|
2397
3585
|
shell: command.shell
|
|
2398
3586
|
});
|
|
2399
|
-
|
|
3587
|
+
let childStartedAt = Date.now();
|
|
3588
|
+
let childIpcState = command.ipc ? createPmChildIpcState(child, inheritedListener) : void 0;
|
|
2400
3589
|
activeChild = child;
|
|
2401
3590
|
if (child.stdout) {
|
|
2402
3591
|
child.stdout.pipe(stdoutLog, { end: false });
|
|
@@ -2404,26 +3593,38 @@ async function runManagedProcessLoop(filePath, initialRecord) {
|
|
|
2404
3593
|
if (child.stderr) {
|
|
2405
3594
|
child.stderr.pipe(stderrLog, { end: false });
|
|
2406
3595
|
}
|
|
3596
|
+
let childWaitState = { settled: false, result: void 0 };
|
|
3597
|
+
void waitForManagedChildExit(child).then((result) => {
|
|
3598
|
+
childWaitState.result = result;
|
|
3599
|
+
childWaitState.settled = true;
|
|
3600
|
+
});
|
|
2407
3601
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3602
|
+
const waitingForReady = latest.waitReady || Boolean(childIpcState);
|
|
2408
3603
|
persist((current2) => ({
|
|
2409
3604
|
...current2,
|
|
2410
|
-
status: "online",
|
|
3605
|
+
status: waitingForReady ? "starting" : "online",
|
|
2411
3606
|
commandPreview: command.preview,
|
|
2412
3607
|
runtime: command.runtime ?? current2.runtime,
|
|
2413
3608
|
runnerPid: process.pid,
|
|
2414
3609
|
childPid: child.pid,
|
|
3610
|
+
proxyTargetPort: initialTargetPort,
|
|
3611
|
+
proxyReadyAt: !waitingForReady && usesPmProxyController(latest) ? startedAt : void 0,
|
|
2415
3612
|
startedAt,
|
|
2416
3613
|
stoppedAt: void 0,
|
|
3614
|
+
reloadRequestedAt: void 0,
|
|
2417
3615
|
error: void 0,
|
|
2418
3616
|
updatedAt: startedAt
|
|
2419
3617
|
}));
|
|
2420
3618
|
writePmLog(stdoutLog, `started ${command.preview}${child.pid ? ` (pid ${child.pid})` : ""}`);
|
|
3619
|
+
if (isPmProxyOwner(latest) && !waitingForReady) {
|
|
3620
|
+
syncOwnedProxyTargets(latest.baseName);
|
|
3621
|
+
}
|
|
2421
3622
|
const requestPlannedRestart = (kind, detail) => {
|
|
2422
3623
|
if (stopRequested || restartState.request) {
|
|
2423
3624
|
return;
|
|
2424
3625
|
}
|
|
2425
3626
|
restartState.request = { kind, detail };
|
|
2426
|
-
writePmLog(kind === "
|
|
3627
|
+
writePmLog(kind === "watch" || kind === "cron" ? stdoutLog : stderrLog, `${kind} restart requested: ${detail}`);
|
|
2427
3628
|
persist((current2) => ({
|
|
2428
3629
|
...current2,
|
|
2429
3630
|
status: "restarting",
|
|
@@ -2431,27 +3632,124 @@ async function runManagedProcessLoop(filePath, initialRecord) {
|
|
|
2431
3632
|
}));
|
|
2432
3633
|
stopActiveChild();
|
|
2433
3634
|
};
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
3635
|
+
let controllers = await createPmChildControllers(
|
|
3636
|
+
monitorRecord,
|
|
3637
|
+
child,
|
|
3638
|
+
stdoutLog,
|
|
3639
|
+
stderrLog,
|
|
3640
|
+
requestPlannedRestart,
|
|
3641
|
+
(message) => {
|
|
3642
|
+
const readyAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3643
|
+
if (isPmProxyOwner(latest)) {
|
|
3644
|
+
syncOwnedProxyTargets(latest.baseName);
|
|
3645
|
+
}
|
|
3646
|
+
persist((current2) => ({
|
|
3647
|
+
...current2,
|
|
3648
|
+
status: "online",
|
|
3649
|
+
proxyTargetPort: initialTargetPort,
|
|
3650
|
+
proxyReadyAt: readyAt,
|
|
3651
|
+
updatedAt: readyAt
|
|
3652
|
+
}));
|
|
3653
|
+
writePmLog(stdoutLog, message);
|
|
3654
|
+
},
|
|
3655
|
+
{ ready: !waitingForReady, ipcController: childIpcState }
|
|
2443
3656
|
);
|
|
2444
|
-
|
|
3657
|
+
let handledReloadAt = latest.reloadRequestedAt;
|
|
3658
|
+
while (!childWaitState.settled) {
|
|
2445
3659
|
const latestRecord = readLatestPmRecord(filePath, record);
|
|
2446
|
-
if (
|
|
3660
|
+
if (latestRecord.desiredState === "stopped" && !stopRequested) {
|
|
2447
3661
|
requestManagedStop("stop requested by PM control state");
|
|
2448
3662
|
}
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
3663
|
+
const reloadRequestedAt = latestRecord.reloadRequestedAt;
|
|
3664
|
+
if (!stopRequested && supportsPmProxyReload(latestRecord) && reloadRequestedAt && reloadRequestedAt !== handledReloadAt && latestRecord.proxy) {
|
|
3665
|
+
handledReloadAt = reloadRequestedAt;
|
|
3666
|
+
const replacementTargetPort = usesPmProxyController(latestRecord) ? await allocatePmProxyTargetPort(resolvePmProxyTargetHost(latestRecord.proxy)) : void 0;
|
|
3667
|
+
const replacementMonitorRecord = buildPmMonitorRecord(latestRecord, replacementTargetPort);
|
|
3668
|
+
const replacementChild = spawn2(command.command, command.args, {
|
|
3669
|
+
cwd: latestRecord.cwd,
|
|
3670
|
+
env: buildPmChildEnv(latestRecord, command, replacementTargetPort),
|
|
3671
|
+
stdio: buildPmChildStdio(command, onlineStdinShutdownEnabled),
|
|
3672
|
+
windowsHide: true,
|
|
3673
|
+
shell: command.shell
|
|
3674
|
+
});
|
|
3675
|
+
const replacementIpcState = command.ipc ? createPmChildIpcState(replacementChild, inheritedListener) : void 0;
|
|
3676
|
+
if (replacementChild.stdout) {
|
|
3677
|
+
replacementChild.stdout.pipe(stdoutLog, { end: false });
|
|
3678
|
+
}
|
|
3679
|
+
if (replacementChild.stderr) {
|
|
3680
|
+
replacementChild.stderr.pipe(stderrLog, { end: false });
|
|
3681
|
+
}
|
|
3682
|
+
writePmLog(stdoutLog, `starting ${usesPmInheritedListener(latestRecord) ? "shared-listener" : "proxy handoff"} replacement${replacementChild.pid ? ` (pid ${replacementChild.pid})` : ""}`);
|
|
3683
|
+
const readyResult = await waitForPmChildReady(replacementMonitorRecord, replacementChild, replacementIpcState);
|
|
3684
|
+
if (!readyResult.ready) {
|
|
3685
|
+
writePmLog(stderrLog, readyResult.message ?? "replacement exited before becoming ready");
|
|
3686
|
+
replacementIpcState?.stop();
|
|
3687
|
+
await stopProxyManagedChild(replacementChild, latestRecord, stderrLog);
|
|
3688
|
+
persist((current2) => ({
|
|
3689
|
+
...current2,
|
|
3690
|
+
status: "online",
|
|
3691
|
+
proxyReadyAt: current2.proxyReadyAt,
|
|
3692
|
+
reloadRequestedAt: void 0,
|
|
3693
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3694
|
+
}));
|
|
3695
|
+
continue;
|
|
3696
|
+
}
|
|
3697
|
+
const previousChild = child;
|
|
3698
|
+
const previousControllers = controllers;
|
|
3699
|
+
const previousIpcState = childIpcState;
|
|
3700
|
+
const handoffAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3701
|
+
if (usesPmProxyController(latestRecord) && replacementTargetPort) {
|
|
3702
|
+
proxyController?.setTarget(buildPmProxyTargetUrl(latestRecord.proxy, replacementTargetPort));
|
|
3703
|
+
}
|
|
3704
|
+
persist((current2) => ({
|
|
3705
|
+
...current2,
|
|
3706
|
+
status: "online",
|
|
3707
|
+
childPid: replacementChild.pid,
|
|
3708
|
+
proxyTargetPort: replacementTargetPort,
|
|
3709
|
+
proxyReadyAt: handoffAt,
|
|
3710
|
+
startedAt: handoffAt,
|
|
3711
|
+
reloadRequestedAt: void 0,
|
|
3712
|
+
error: void 0,
|
|
3713
|
+
updatedAt: handoffAt
|
|
3714
|
+
}));
|
|
3715
|
+
if (readyResult.message) {
|
|
3716
|
+
writePmLog(stdoutLog, readyResult.message);
|
|
3717
|
+
}
|
|
3718
|
+
writePmLog(stdoutLog, `${usesPmInheritedListener(latestRecord) ? "shared listener" : "proxy handoff"} activated on ${latestRecord.proxy.host ?? "0.0.0.0"}:${latestRecord.proxy.port}`);
|
|
3719
|
+
child = replacementChild;
|
|
3720
|
+
childIpcState = replacementIpcState;
|
|
3721
|
+
childStartedAt = Date.now();
|
|
3722
|
+
activeChild = replacementChild;
|
|
3723
|
+
childWaitState = { settled: false, result: void 0 };
|
|
3724
|
+
void waitForManagedChildExit(replacementChild).then((result) => {
|
|
3725
|
+
childWaitState.result = result;
|
|
3726
|
+
childWaitState.settled = true;
|
|
3727
|
+
});
|
|
3728
|
+
controllers = await createPmChildControllers(
|
|
3729
|
+
replacementMonitorRecord,
|
|
3730
|
+
replacementChild,
|
|
3731
|
+
stdoutLog,
|
|
3732
|
+
stderrLog,
|
|
3733
|
+
requestPlannedRestart,
|
|
3734
|
+
() => {
|
|
3735
|
+
},
|
|
3736
|
+
{ ready: true, ipcController: replacementIpcState }
|
|
3737
|
+
);
|
|
3738
|
+
await delay(250);
|
|
3739
|
+
await previousControllers.stop();
|
|
3740
|
+
previousIpcState?.stop();
|
|
3741
|
+
await stopProxyManagedChild(previousChild, latestRecord, stderrLog);
|
|
3742
|
+
clearActiveChildStopTimer();
|
|
3743
|
+
if (isPmProxyOwner(latestRecord)) {
|
|
3744
|
+
syncOwnedProxyTargets(latestRecord.baseName);
|
|
3745
|
+
}
|
|
3746
|
+
continue;
|
|
3747
|
+
}
|
|
3748
|
+
await delay(25);
|
|
3749
|
+
}
|
|
3750
|
+
const exitResult = childWaitState.result;
|
|
3751
|
+
await controllers.stop();
|
|
3752
|
+
childIpcState?.stop();
|
|
2455
3753
|
clearActiveChildStopTimer();
|
|
2456
3754
|
activeChild = null;
|
|
2457
3755
|
const exitCode = waitForExit(exitResult.code, exitResult.signal);
|
|
@@ -2467,56 +3765,77 @@ async function runManagedProcessLoop(filePath, initialRecord) {
|
|
|
2467
3765
|
if (stopRequested || current.desiredState === "stopped") {
|
|
2468
3766
|
break;
|
|
2469
3767
|
}
|
|
2470
|
-
const shouldRestartForExit = plannedRestart ? true : current.restartPolicy === "always" ? true : current.restartPolicy === "on-failure" ? exitCode !== 0 || Boolean(exitResult.error) : false;
|
|
3768
|
+
const shouldRestartForExit = plannedRestart?.kind === "memory-stop" ? false : plannedRestart ? true : current.restartPolicy === "always" ? true : current.restartPolicy === "on-failure" ? exitCode !== 0 || Boolean(exitResult.error) : false;
|
|
2471
3769
|
if (!shouldRestartForExit) {
|
|
2472
3770
|
persist((latestRecord) => ({
|
|
2473
3771
|
...latestRecord,
|
|
2474
|
-
status: exitCode === 0 && !exitResult.error ? "exited" : "errored",
|
|
3772
|
+
status: plannedRestart?.kind === "memory-stop" ? "errored" : exitCode === 0 && !exitResult.error ? "exited" : "errored",
|
|
2475
3773
|
childPid: void 0,
|
|
3774
|
+
proxyTargetPort: void 0,
|
|
3775
|
+
proxyReadyAt: void 0,
|
|
2476
3776
|
runnerPid: void 0,
|
|
2477
3777
|
lastExitCode: exitCode,
|
|
2478
|
-
|
|
3778
|
+
reloadRequestedAt: void 0,
|
|
3779
|
+
error: plannedRestart?.kind === "memory-stop" ? plannedRestart.detail : exitCode === 0 && !exitResult.error ? void 0 : exitResult.error ?? `Process exited with code ${exitCode}.`,
|
|
2479
3780
|
stoppedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2480
3781
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2481
3782
|
}));
|
|
3783
|
+
if (isPmProxyOwner(current)) {
|
|
3784
|
+
syncOwnedProxyTargets(current.baseName);
|
|
3785
|
+
}
|
|
2482
3786
|
return;
|
|
2483
3787
|
}
|
|
2484
3788
|
const shouldCountRestart = plannedRestart?.kind !== "watch";
|
|
2485
|
-
const baseRestartCount =
|
|
3789
|
+
const baseRestartCount = resolvePmRestartCountBase(current, wasStable, plannedRestart?.kind);
|
|
2486
3790
|
const nextRestartCount = shouldCountRestart ? baseRestartCount + 1 : current.restartCount ?? 0;
|
|
2487
3791
|
if (nextRestartCount > current.maxRestarts) {
|
|
2488
3792
|
persist((latestRecord) => ({
|
|
2489
3793
|
...latestRecord,
|
|
2490
3794
|
status: "errored",
|
|
2491
3795
|
childPid: void 0,
|
|
3796
|
+
proxyTargetPort: void 0,
|
|
3797
|
+
proxyReadyAt: void 0,
|
|
2492
3798
|
runnerPid: void 0,
|
|
2493
3799
|
restartCount: nextRestartCount,
|
|
3800
|
+
lastRestartAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2494
3801
|
lastExitCode: exitCode,
|
|
3802
|
+
reloadRequestedAt: void 0,
|
|
2495
3803
|
error: plannedRestart ? `Reached max restart attempts (${current.maxRestarts}) after ${plannedRestart.kind} restart requests.` : `Reached max restart attempts (${current.maxRestarts}).`,
|
|
2496
3804
|
stoppedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2497
3805
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2498
3806
|
}));
|
|
2499
3807
|
writePmLog(stderrLog, `max restart attempts reached (${current.maxRestarts})`);
|
|
3808
|
+
if (isPmProxyOwner(current)) {
|
|
3809
|
+
syncOwnedProxyTargets(current.baseName);
|
|
3810
|
+
}
|
|
2500
3811
|
return;
|
|
2501
3812
|
}
|
|
2502
3813
|
persist((latestRecord) => ({
|
|
2503
3814
|
...latestRecord,
|
|
2504
3815
|
status: "restarting",
|
|
2505
3816
|
childPid: void 0,
|
|
3817
|
+
proxyTargetPort: void 0,
|
|
3818
|
+
proxyReadyAt: void 0,
|
|
2506
3819
|
lastExitCode: exitCode,
|
|
2507
3820
|
restartCount: nextRestartCount,
|
|
3821
|
+
lastRestartAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3822
|
+
reloadRequestedAt: void 0,
|
|
2508
3823
|
error: void 0,
|
|
2509
3824
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2510
3825
|
}));
|
|
3826
|
+
if (isPmProxyOwner(current)) {
|
|
3827
|
+
syncOwnedProxyTargets(current.baseName);
|
|
3828
|
+
}
|
|
3829
|
+
const resolvedRestartDelay = resolvePmRestartDelay(current, nextRestartCount, shouldCountRestart && !wasStable && plannedRestart?.kind !== "watch");
|
|
2511
3830
|
if (plannedRestart) {
|
|
2512
3831
|
writePmLog(
|
|
2513
|
-
plannedRestart.kind === "health" ? stderrLog : stdoutLog,
|
|
2514
|
-
`restarting in ${
|
|
3832
|
+
plannedRestart.kind === "health" || plannedRestart.kind === "memory" || plannedRestart.kind === "memory-stop" || plannedRestart.kind === "startup" ? stderrLog : stdoutLog,
|
|
3833
|
+
`restarting in ${resolvedRestartDelay}ms after ${plannedRestart.kind}: ${plannedRestart.detail}`
|
|
2515
3834
|
);
|
|
2516
3835
|
} else {
|
|
2517
|
-
writePmLog(stdoutLog, `restarting in ${
|
|
3836
|
+
writePmLog(stdoutLog, `restarting in ${resolvedRestartDelay}ms`);
|
|
2518
3837
|
}
|
|
2519
|
-
await delay(
|
|
3838
|
+
await delay(resolvedRestartDelay);
|
|
2520
3839
|
}
|
|
2521
3840
|
} finally {
|
|
2522
3841
|
stopRequested = true;
|
|
@@ -2529,11 +3848,26 @@ async function runManagedProcessLoop(filePath, initialRecord) {
|
|
|
2529
3848
|
status: finalRecord.status === "errored" ? "errored" : finalRecord.status === "exited" ? "exited" : "stopped",
|
|
2530
3849
|
runnerPid: void 0,
|
|
2531
3850
|
childPid: void 0,
|
|
3851
|
+
proxyTargetPort: void 0,
|
|
3852
|
+
proxyReadyAt: void 0,
|
|
3853
|
+
reloadRequestedAt: void 0,
|
|
2532
3854
|
stoppedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2533
3855
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2534
3856
|
});
|
|
2535
3857
|
process.off("SIGINT", handleStopSignal);
|
|
2536
3858
|
process.off("SIGTERM", handleStopSignal);
|
|
3859
|
+
if (proxyTargetSyncTimer) {
|
|
3860
|
+
clearInterval(proxyTargetSyncTimer);
|
|
3861
|
+
proxyTargetSyncTimer = null;
|
|
3862
|
+
}
|
|
3863
|
+
if (proxyController) {
|
|
3864
|
+
await proxyController.close().catch(() => void 0);
|
|
3865
|
+
proxyController = null;
|
|
3866
|
+
}
|
|
3867
|
+
if (inheritedListener) {
|
|
3868
|
+
await new Promise((resolvePromise) => inheritedListener?.close(() => resolvePromise()));
|
|
3869
|
+
inheritedListener = null;
|
|
3870
|
+
}
|
|
2537
3871
|
await new Promise((resolvePromise) => stdoutLog.end(resolvePromise));
|
|
2538
3872
|
await new Promise((resolvePromise) => stderrLog.end(resolvePromise));
|
|
2539
3873
|
}
|
|
@@ -2558,7 +3892,7 @@ function parseRunnerArgs(args) {
|
|
|
2558
3892
|
throw new Error("Usage: elit pm __run --data-dir <dir> --id <name>");
|
|
2559
3893
|
}
|
|
2560
3894
|
return {
|
|
2561
|
-
dataDir:
|
|
3895
|
+
dataDir: resolve6(dataDir),
|
|
2562
3896
|
id
|
|
2563
3897
|
};
|
|
2564
3898
|
}
|
|
@@ -2587,23 +3921,26 @@ async function stopPmMatches(matches) {
|
|
|
2587
3921
|
stoppedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2588
3922
|
};
|
|
2589
3923
|
writePmRecord(match.filePath, updated);
|
|
2590
|
-
const
|
|
3924
|
+
const stopTimeout = resolvePmStopTimeout(match.record);
|
|
3925
|
+
const runnerStopped = await waitForProcessTermination(match.record.runnerPid, stopTimeout);
|
|
2591
3926
|
const childStopped = await waitForProcessTermination(
|
|
2592
3927
|
match.record.childPid,
|
|
2593
|
-
runnerStopped ? DEFAULT_PM_STOP_POLL_MS :
|
|
3928
|
+
runnerStopped ? DEFAULT_PM_STOP_POLL_MS : stopTimeout
|
|
2594
3929
|
);
|
|
2595
3930
|
if (!runnerStopped && match.record.runnerPid && isProcessAlive(match.record.runnerPid)) {
|
|
2596
|
-
terminateProcessTree(match.record.runnerPid);
|
|
3931
|
+
terminateProcessTree(match.record.runnerPid, { force: true });
|
|
2597
3932
|
await waitForProcessTermination(match.record.runnerPid, DEFAULT_PM_STOP_POLL_MS);
|
|
2598
3933
|
}
|
|
2599
3934
|
if (!childStopped && match.record.childPid && isProcessAlive(match.record.childPid)) {
|
|
2600
|
-
terminateProcessTree(match.record.childPid);
|
|
3935
|
+
terminateProcessTree(match.record.childPid, { force: true });
|
|
2601
3936
|
await waitForProcessTermination(match.record.childPid, DEFAULT_PM_STOP_POLL_MS);
|
|
2602
3937
|
}
|
|
2603
3938
|
writePmRecord(match.filePath, {
|
|
2604
3939
|
...updated,
|
|
2605
3940
|
runnerPid: void 0,
|
|
2606
3941
|
childPid: void 0,
|
|
3942
|
+
proxyTargetPort: void 0,
|
|
3943
|
+
reloadRequestedAt: void 0,
|
|
2607
3944
|
status: "stopped",
|
|
2608
3945
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2609
3946
|
});
|
|
@@ -2672,13 +4009,13 @@ function readPackageJson(filePath) {
|
|
|
2672
4009
|
|
|
2673
4010
|
// src/shares/workspace-package/roots.ts
|
|
2674
4011
|
function findPackageDirectory(startDir, resolveMatch) {
|
|
2675
|
-
let currentDir =
|
|
4012
|
+
let currentDir = resolve5(startDir);
|
|
2676
4013
|
while (true) {
|
|
2677
4014
|
const match = resolveMatch(currentDir);
|
|
2678
4015
|
if (match) {
|
|
2679
4016
|
return match;
|
|
2680
4017
|
}
|
|
2681
|
-
const parentDir =
|
|
4018
|
+
const parentDir = dirname3(currentDir);
|
|
2682
4019
|
if (parentDir === currentDir) {
|
|
2683
4020
|
return void 0;
|
|
2684
4021
|
}
|
|
@@ -2706,17 +4043,17 @@ init_fs();
|
|
|
2706
4043
|
function getWorkspacePackageImportCandidates(packageRoot, specifier, options = {}) {
|
|
2707
4044
|
const subpath = specifier === "elit" ? "index" : specifier.slice("elit/".length);
|
|
2708
4045
|
const builtCandidates = options.preferredBuiltFormat === "cjs" ? [
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
4046
|
+
resolve5(packageRoot, "dist", `${subpath}.cjs`),
|
|
4047
|
+
resolve5(packageRoot, "dist", `${subpath}.js`),
|
|
4048
|
+
resolve5(packageRoot, "dist", `${subpath}.mjs`)
|
|
2712
4049
|
] : [
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
4050
|
+
resolve5(packageRoot, "dist", `${subpath}.mjs`),
|
|
4051
|
+
resolve5(packageRoot, "dist", `${subpath}.js`),
|
|
4052
|
+
resolve5(packageRoot, "dist", `${subpath}.cjs`)
|
|
2716
4053
|
];
|
|
2717
4054
|
const sourceCandidates = [
|
|
2718
|
-
|
|
2719
|
-
|
|
4055
|
+
resolve5(packageRoot, "src", `${subpath}.ts`),
|
|
4056
|
+
resolve5(packageRoot, "src", `${subpath}.tsx`)
|
|
2720
4057
|
];
|
|
2721
4058
|
return options.preferBuilt ? [...builtCandidates, ...sourceCandidates] : [...sourceCandidates, ...builtCandidates];
|
|
2722
4059
|
}
|
|
@@ -2751,7 +4088,7 @@ function resolveWorkspacePackageImport(specifier, startDir, options = {}) {
|
|
|
2751
4088
|
// src/shares/config/loader.ts
|
|
2752
4089
|
function resolveConfigPath(cwd = process.cwd()) {
|
|
2753
4090
|
for (const configFile of ELIT_CONFIG_FILES) {
|
|
2754
|
-
const configPath =
|
|
4091
|
+
const configPath = resolve5(cwd, configFile);
|
|
2755
4092
|
if (existsSync4(configPath)) {
|
|
2756
4093
|
return configPath;
|
|
2757
4094
|
}
|
|
@@ -2780,7 +4117,7 @@ async function loadConfigFile(configPath) {
|
|
|
2780
4117
|
if (ext === "ts" || ext === "mts") {
|
|
2781
4118
|
try {
|
|
2782
4119
|
const { build } = await import("esbuild");
|
|
2783
|
-
const configDir =
|
|
4120
|
+
const configDir = dirname3(configPath);
|
|
2784
4121
|
const tempFile = join5(configDir, `.elit-config-${Date.now()}.mjs`);
|
|
2785
4122
|
const externalAllPlugin = {
|
|
2786
4123
|
name: "external-all",
|
|
@@ -2854,6 +4191,43 @@ async function loadConfigFile(configPath) {
|
|
|
2854
4191
|
}
|
|
2855
4192
|
|
|
2856
4193
|
// src/cli/pm/commands.ts
|
|
4194
|
+
var PM_SIGNAL_NAMES = /* @__PURE__ */ new Set([
|
|
4195
|
+
"SIGABRT",
|
|
4196
|
+
"SIGALRM",
|
|
4197
|
+
"SIGBREAK",
|
|
4198
|
+
"SIGBUS",
|
|
4199
|
+
"SIGCHLD",
|
|
4200
|
+
"SIGCONT",
|
|
4201
|
+
"SIGFPE",
|
|
4202
|
+
"SIGHUP",
|
|
4203
|
+
"SIGILL",
|
|
4204
|
+
"SIGINT",
|
|
4205
|
+
"SIGIO",
|
|
4206
|
+
"SIGIOT",
|
|
4207
|
+
"SIGKILL",
|
|
4208
|
+
"SIGPIPE",
|
|
4209
|
+
"SIGPOLL",
|
|
4210
|
+
"SIGPROF",
|
|
4211
|
+
"SIGPWR",
|
|
4212
|
+
"SIGQUIT",
|
|
4213
|
+
"SIGSEGV",
|
|
4214
|
+
"SIGSTKFLT",
|
|
4215
|
+
"SIGSTOP",
|
|
4216
|
+
"SIGSYS",
|
|
4217
|
+
"SIGTERM",
|
|
4218
|
+
"SIGTRAP",
|
|
4219
|
+
"SIGTSTP",
|
|
4220
|
+
"SIGTTIN",
|
|
4221
|
+
"SIGTTOU",
|
|
4222
|
+
"SIGUNUSED",
|
|
4223
|
+
"SIGURG",
|
|
4224
|
+
"SIGUSR1",
|
|
4225
|
+
"SIGUSR2",
|
|
4226
|
+
"SIGVTALRM",
|
|
4227
|
+
"SIGWINCH",
|
|
4228
|
+
"SIGXCPU",
|
|
4229
|
+
"SIGXFSZ"
|
|
4230
|
+
]);
|
|
2857
4231
|
async function runPmStart(args) {
|
|
2858
4232
|
const parsed = parsePmStartArgs(args);
|
|
2859
4233
|
const workspaceRoot = process.cwd();
|
|
@@ -2886,9 +4260,130 @@ function resolveNamedMatches(paths, value) {
|
|
|
2886
4260
|
if (value === "all") {
|
|
2887
4261
|
return listPmRecordMatches(paths).map(syncPmRecordLiveness);
|
|
2888
4262
|
}
|
|
4263
|
+
const groupMatches = findPmGroupMatches(paths, value);
|
|
4264
|
+
if (groupMatches.length > 0) {
|
|
4265
|
+
return groupMatches.map(syncPmRecordLiveness);
|
|
4266
|
+
}
|
|
2889
4267
|
const match = findPmRecordMatch(paths, value);
|
|
2890
4268
|
return match ? [syncPmRecordLiveness(match)] : [];
|
|
2891
4269
|
}
|
|
4270
|
+
function sortPmMatchesByInstance(matches) {
|
|
4271
|
+
return [...matches].sort((left, right) => left.record.instanceIndex - right.record.instanceIndex);
|
|
4272
|
+
}
|
|
4273
|
+
function resolveInspectableMatch(paths, value) {
|
|
4274
|
+
const exactMatch = findPmRecordMatch(paths, value);
|
|
4275
|
+
const groupMatches = findPmGroupMatches(paths, value).map(syncPmRecordLiveness);
|
|
4276
|
+
if (groupMatches.length > 1 && exactMatch?.record.baseName === value && exactMatch.record.name === value) {
|
|
4277
|
+
throw new Error(`Multiple managed processes found for: ${value}. Use a specific instance name such as ${groupMatches[0]?.record.name} or ${groupMatches[1]?.record.name}.`);
|
|
4278
|
+
}
|
|
4279
|
+
if (exactMatch) {
|
|
4280
|
+
return syncPmRecordLiveness(exactMatch);
|
|
4281
|
+
}
|
|
4282
|
+
return groupMatches[0];
|
|
4283
|
+
}
|
|
4284
|
+
function rebuildPmRecordDefinition(record, targetInstances = record.instances) {
|
|
4285
|
+
const definition = resolvePmAppDefinition(
|
|
4286
|
+
toPmAppConfig(record),
|
|
4287
|
+
{ name: record.baseName, env: {}, watchPaths: [], watchIgnore: [], instances: targetInstances },
|
|
4288
|
+
process.cwd(),
|
|
4289
|
+
record.source
|
|
4290
|
+
);
|
|
4291
|
+
return {
|
|
4292
|
+
...definition,
|
|
4293
|
+
name: record.name,
|
|
4294
|
+
baseName: record.baseName,
|
|
4295
|
+
instanceIndex: record.instanceIndex,
|
|
4296
|
+
instances: targetInstances
|
|
4297
|
+
};
|
|
4298
|
+
}
|
|
4299
|
+
function rebuildPmSavedDefinition(app) {
|
|
4300
|
+
const definition = resolvePmAppDefinition(
|
|
4301
|
+
toSavedPmAppConfig(app),
|
|
4302
|
+
{ name: app.baseName, env: {}, watchPaths: [], watchIgnore: [], instances: app.instances },
|
|
4303
|
+
process.cwd(),
|
|
4304
|
+
"cli"
|
|
4305
|
+
);
|
|
4306
|
+
return {
|
|
4307
|
+
...definition,
|
|
4308
|
+
name: app.name,
|
|
4309
|
+
baseName: app.baseName,
|
|
4310
|
+
instanceIndex: app.instanceIndex,
|
|
4311
|
+
instances: app.instances
|
|
4312
|
+
};
|
|
4313
|
+
}
|
|
4314
|
+
function deletePmMatches(matches) {
|
|
4315
|
+
for (const match of matches) {
|
|
4316
|
+
if (existsSync5(match.record.logFiles.out)) {
|
|
4317
|
+
rmSync(match.record.logFiles.out, { force: true });
|
|
4318
|
+
}
|
|
4319
|
+
if (existsSync5(match.record.logFiles.err)) {
|
|
4320
|
+
rmSync(match.record.logFiles.err, { force: true });
|
|
4321
|
+
}
|
|
4322
|
+
rmSync(match.filePath, { force: true });
|
|
4323
|
+
}
|
|
4324
|
+
}
|
|
4325
|
+
function updatePmInstanceCount(matches, instances) {
|
|
4326
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4327
|
+
for (const match of matches) {
|
|
4328
|
+
writePmRecord(match.filePath, {
|
|
4329
|
+
...match.record,
|
|
4330
|
+
instances,
|
|
4331
|
+
updatedAt: now
|
|
4332
|
+
});
|
|
4333
|
+
}
|
|
4334
|
+
}
|
|
4335
|
+
function normalizePmSignalName(value) {
|
|
4336
|
+
const trimmed = value.trim().toUpperCase();
|
|
4337
|
+
const signalName = trimmed.startsWith("SIG") ? trimmed : `SIG${trimmed}`;
|
|
4338
|
+
if (!PM_SIGNAL_NAMES.has(signalName)) {
|
|
4339
|
+
throw new Error(`Unsupported pm signal: ${value}`);
|
|
4340
|
+
}
|
|
4341
|
+
return signalName;
|
|
4342
|
+
}
|
|
4343
|
+
function resolveSignalablePid(record) {
|
|
4344
|
+
if (record.childPid && record.childPid > 0) {
|
|
4345
|
+
return record.childPid;
|
|
4346
|
+
}
|
|
4347
|
+
if (record.runnerPid && record.runnerPid > 0) {
|
|
4348
|
+
return record.runnerPid;
|
|
4349
|
+
}
|
|
4350
|
+
return void 0;
|
|
4351
|
+
}
|
|
4352
|
+
function groupPmMatchesByBaseName(matches) {
|
|
4353
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
4354
|
+
for (const match of matches) {
|
|
4355
|
+
const group = grouped.get(match.record.baseName);
|
|
4356
|
+
if (group) {
|
|
4357
|
+
group.push(match);
|
|
4358
|
+
continue;
|
|
4359
|
+
}
|
|
4360
|
+
grouped.set(match.record.baseName, [match]);
|
|
4361
|
+
}
|
|
4362
|
+
return [...grouped.entries()].sort((left, right) => left[0].localeCompare(right[0])).map(([, group]) => sortPmMatchesByInstance(group));
|
|
4363
|
+
}
|
|
4364
|
+
async function waitForPmRecordOnline(filePath, timeoutMs) {
|
|
4365
|
+
const deadline = Date.now() + timeoutMs;
|
|
4366
|
+
while (Date.now() < deadline) {
|
|
4367
|
+
if (!existsSync5(filePath)) {
|
|
4368
|
+
break;
|
|
4369
|
+
}
|
|
4370
|
+
const record = readPmRecord(filePath);
|
|
4371
|
+
if (record.status === "online") {
|
|
4372
|
+
return record;
|
|
4373
|
+
}
|
|
4374
|
+
if (record.status === "errored" || record.status === "exited" || record.status === "stopped") {
|
|
4375
|
+
throw new Error(record.error ?? `Process ${record.name} failed while reloading.`);
|
|
4376
|
+
}
|
|
4377
|
+
await new Promise((resolvePromise) => setTimeout(resolvePromise, 50));
|
|
4378
|
+
}
|
|
4379
|
+
throw new Error(`Timed out waiting for the reloaded process to become online after ${timeoutMs}ms.`);
|
|
4380
|
+
}
|
|
4381
|
+
function resolvePmReloadReadyTimeout(record) {
|
|
4382
|
+
return record.waitReady || record.proxy?.strategy === "inherit" ? Math.max(record.listenTimeout + 1e3, 2e3) : Math.max(record.restartDelay + 1e3, 2e3);
|
|
4383
|
+
}
|
|
4384
|
+
function supportsPmProxyReload2(record) {
|
|
4385
|
+
return Boolean(record.proxy) && record.instances === 1 && Boolean(record.runnerPid);
|
|
4386
|
+
}
|
|
2892
4387
|
function padCell(value, width) {
|
|
2893
4388
|
return value.length >= width ? value : `${value}${" ".repeat(width - value.length)}`;
|
|
2894
4389
|
}
|
|
@@ -2899,8 +4394,255 @@ function tailLogFile(filePath, lineCount) {
|
|
|
2899
4394
|
const lines = readFileSync3(filePath, "utf8").split(/\r?\n/).filter((line) => line.length > 0);
|
|
2900
4395
|
return lines.slice(-lineCount).join(EOL2);
|
|
2901
4396
|
}
|
|
2902
|
-
function
|
|
2903
|
-
|
|
4397
|
+
function listPmMatches(paths) {
|
|
4398
|
+
return listPmRecordMatches(paths).map(syncPmRecordLiveness);
|
|
4399
|
+
}
|
|
4400
|
+
function isPmRecordActive(record) {
|
|
4401
|
+
return record.desiredState === "running" && (record.status === "starting" || record.status === "online" || record.status === "restarting");
|
|
4402
|
+
}
|
|
4403
|
+
function resolvePmUptimeMs(record) {
|
|
4404
|
+
if (!isPmRecordActive(record) || !record.startedAt) {
|
|
4405
|
+
return void 0;
|
|
4406
|
+
}
|
|
4407
|
+
const startedTime = Date.parse(record.startedAt);
|
|
4408
|
+
if (Number.isNaN(startedTime)) {
|
|
4409
|
+
return void 0;
|
|
4410
|
+
}
|
|
4411
|
+
return Math.max(0, Date.now() - startedTime);
|
|
4412
|
+
}
|
|
4413
|
+
function resolvePmLiveMetrics(record) {
|
|
4414
|
+
const uptimeMs = resolvePmUptimeMs(record);
|
|
4415
|
+
if (!isPmRecordActive(record) || !record.childPid) {
|
|
4416
|
+
return { uptimeMs };
|
|
4417
|
+
}
|
|
4418
|
+
const sampledMetrics = samplePmProcessMetrics(record.childPid);
|
|
4419
|
+
return {
|
|
4420
|
+
...sampledMetrics,
|
|
4421
|
+
uptimeMs,
|
|
4422
|
+
updatedAt: sampledMetrics.cpuPercent !== void 0 || sampledMetrics.memoryRssBytes !== void 0 ? (/* @__PURE__ */ new Date()).toISOString() : void 0
|
|
4423
|
+
};
|
|
4424
|
+
}
|
|
4425
|
+
function toPmDisplayRecord(record) {
|
|
4426
|
+
return {
|
|
4427
|
+
record,
|
|
4428
|
+
liveMetrics: resolvePmLiveMetrics(record)
|
|
4429
|
+
};
|
|
4430
|
+
}
|
|
4431
|
+
function serializePmRecord(record) {
|
|
4432
|
+
return {
|
|
4433
|
+
...record,
|
|
4434
|
+
liveMetrics: resolvePmLiveMetrics(record)
|
|
4435
|
+
};
|
|
4436
|
+
}
|
|
4437
|
+
function parsePmFormatOption(args, index, option) {
|
|
4438
|
+
let value;
|
|
4439
|
+
if (option.startsWith("--format=")) {
|
|
4440
|
+
value = option.slice("--format=".length);
|
|
4441
|
+
} else {
|
|
4442
|
+
value = readRequiredValue(args, index + 1, "--format");
|
|
4443
|
+
index += 1;
|
|
4444
|
+
}
|
|
4445
|
+
if (value !== "table" && value !== "json") {
|
|
4446
|
+
throw new Error(`Unsupported pm output format: ${value}`);
|
|
4447
|
+
}
|
|
4448
|
+
return {
|
|
4449
|
+
format: value,
|
|
4450
|
+
nextIndex: index
|
|
4451
|
+
};
|
|
4452
|
+
}
|
|
4453
|
+
function parsePmListArgs(args) {
|
|
4454
|
+
let format = "table";
|
|
4455
|
+
for (let index = 0; index < args.length; index++) {
|
|
4456
|
+
const arg = args[index];
|
|
4457
|
+
switch (arg) {
|
|
4458
|
+
case "--json":
|
|
4459
|
+
format = "json";
|
|
4460
|
+
break;
|
|
4461
|
+
case "--format":
|
|
4462
|
+
default:
|
|
4463
|
+
if (arg === "--format" || arg.startsWith("--format=")) {
|
|
4464
|
+
const parsed = parsePmFormatOption(args, index, arg);
|
|
4465
|
+
format = parsed.format;
|
|
4466
|
+
index = parsed.nextIndex;
|
|
4467
|
+
break;
|
|
4468
|
+
}
|
|
4469
|
+
throw new Error(`Unknown pm list option: ${arg}`);
|
|
4470
|
+
}
|
|
4471
|
+
}
|
|
4472
|
+
return { format };
|
|
4473
|
+
}
|
|
4474
|
+
function parsePmShowArgs(args) {
|
|
4475
|
+
let format = "text";
|
|
4476
|
+
let name;
|
|
4477
|
+
for (let index = 0; index < args.length; index++) {
|
|
4478
|
+
const arg = args[index];
|
|
4479
|
+
switch (arg) {
|
|
4480
|
+
case "--json":
|
|
4481
|
+
format = "json";
|
|
4482
|
+
break;
|
|
4483
|
+
case "--format":
|
|
4484
|
+
default:
|
|
4485
|
+
if (arg === "--format" || arg.startsWith("--format=")) {
|
|
4486
|
+
const parsed = parsePmFormatOption(args, index, arg);
|
|
4487
|
+
format = parsed.format === "json" ? "json" : "text";
|
|
4488
|
+
index = parsed.nextIndex;
|
|
4489
|
+
break;
|
|
4490
|
+
}
|
|
4491
|
+
if (arg.startsWith("-")) {
|
|
4492
|
+
throw new Error(`Unknown pm show option: ${arg}`);
|
|
4493
|
+
}
|
|
4494
|
+
if (name) {
|
|
4495
|
+
throw new Error("pm show accepts exactly one process name.");
|
|
4496
|
+
}
|
|
4497
|
+
name = arg;
|
|
4498
|
+
break;
|
|
4499
|
+
}
|
|
4500
|
+
}
|
|
4501
|
+
if (!name) {
|
|
4502
|
+
throw new Error("Usage: elit pm show <name> [--json]");
|
|
4503
|
+
}
|
|
4504
|
+
return { name, format };
|
|
4505
|
+
}
|
|
4506
|
+
function formatPmDuration(durationMs) {
|
|
4507
|
+
if (durationMs < 1e3) {
|
|
4508
|
+
return `${durationMs}ms`;
|
|
4509
|
+
}
|
|
4510
|
+
const totalSeconds = Math.floor(durationMs / 1e3);
|
|
4511
|
+
const days = Math.floor(totalSeconds / 86400);
|
|
4512
|
+
const hours = Math.floor(totalSeconds % 86400 / 3600);
|
|
4513
|
+
const minutes = Math.floor(totalSeconds % 3600 / 60);
|
|
4514
|
+
const seconds = totalSeconds % 60;
|
|
4515
|
+
const parts = [];
|
|
4516
|
+
if (days > 0) {
|
|
4517
|
+
parts.push(`${days}d`);
|
|
4518
|
+
}
|
|
4519
|
+
if (hours > 0) {
|
|
4520
|
+
parts.push(`${hours}h`);
|
|
4521
|
+
}
|
|
4522
|
+
if (minutes > 0) {
|
|
4523
|
+
parts.push(`${minutes}m`);
|
|
4524
|
+
}
|
|
4525
|
+
if (seconds > 0 || parts.length === 0) {
|
|
4526
|
+
parts.push(`${seconds}s`);
|
|
4527
|
+
}
|
|
4528
|
+
return parts.slice(0, 2).join(" ");
|
|
4529
|
+
}
|
|
4530
|
+
function formatPmCpuPercent(cpuPercent) {
|
|
4531
|
+
if (cpuPercent === void 0 || !Number.isFinite(cpuPercent)) {
|
|
4532
|
+
return "-";
|
|
4533
|
+
}
|
|
4534
|
+
return `${cpuPercent >= 100 ? cpuPercent.toFixed(0) : cpuPercent.toFixed(1)}%`;
|
|
4535
|
+
}
|
|
4536
|
+
function formatPmMemory(memoryRssBytes) {
|
|
4537
|
+
if (memoryRssBytes === void 0 || !Number.isFinite(memoryRssBytes) || memoryRssBytes < 0) {
|
|
4538
|
+
return "-";
|
|
4539
|
+
}
|
|
4540
|
+
const units = ["B", "KB", "MB", "GB", "TB"];
|
|
4541
|
+
let value = memoryRssBytes;
|
|
4542
|
+
let unitIndex = 0;
|
|
4543
|
+
while (value >= 1024 && unitIndex < units.length - 1) {
|
|
4544
|
+
value /= 1024;
|
|
4545
|
+
unitIndex += 1;
|
|
4546
|
+
}
|
|
4547
|
+
const formatted = value >= 10 || unitIndex === 0 ? value.toFixed(0) : value.toFixed(1);
|
|
4548
|
+
return `${formatted}${units[unitIndex]}`;
|
|
4549
|
+
}
|
|
4550
|
+
function formatPmUptime(uptimeMs) {
|
|
4551
|
+
if (uptimeMs === void 0 || !Number.isFinite(uptimeMs)) {
|
|
4552
|
+
return "-";
|
|
4553
|
+
}
|
|
4554
|
+
return formatPmDuration(Math.max(0, uptimeMs));
|
|
4555
|
+
}
|
|
4556
|
+
function formatPmTarget(record) {
|
|
4557
|
+
if (record.script) {
|
|
4558
|
+
return record.script;
|
|
4559
|
+
}
|
|
4560
|
+
if (record.file) {
|
|
4561
|
+
return record.file;
|
|
4562
|
+
}
|
|
4563
|
+
if (record.wapk) {
|
|
4564
|
+
return record.wapk;
|
|
4565
|
+
}
|
|
4566
|
+
return "-";
|
|
4567
|
+
}
|
|
4568
|
+
function pushPmDetail(lines, label, value) {
|
|
4569
|
+
lines.push(`${padCell(`${label}:`, 18)} ${value}`);
|
|
4570
|
+
}
|
|
4571
|
+
function pushPmDetailList(lines, label, values) {
|
|
4572
|
+
if (values.length === 0) {
|
|
4573
|
+
pushPmDetail(lines, label, "-");
|
|
4574
|
+
return;
|
|
4575
|
+
}
|
|
4576
|
+
pushPmDetail(lines, label, values[0] ?? "-");
|
|
4577
|
+
for (const value of values.slice(1)) {
|
|
4578
|
+
lines.push(`${" ".repeat(20)} ${value}`);
|
|
4579
|
+
}
|
|
4580
|
+
}
|
|
4581
|
+
function formatPmRecordDetails(record, liveMetrics) {
|
|
4582
|
+
const lines = [`Process: ${record.name}`];
|
|
4583
|
+
pushPmDetail(lines, "id", record.id);
|
|
4584
|
+
pushPmDetail(lines, "status", record.status);
|
|
4585
|
+
pushPmDetail(lines, "desired state", record.desiredState);
|
|
4586
|
+
pushPmDetail(lines, "instance", `${record.instanceIndex}/${record.instances}`);
|
|
4587
|
+
pushPmDetail(lines, "cpu", formatPmCpuPercent(liveMetrics.cpuPercent));
|
|
4588
|
+
pushPmDetail(lines, "memory", formatPmMemory(liveMetrics.memoryRssBytes));
|
|
4589
|
+
pushPmDetail(lines, "uptime", formatPmUptime(liveMetrics.uptimeMs));
|
|
4590
|
+
pushPmDetail(lines, "type", record.type);
|
|
4591
|
+
pushPmDetail(lines, "source", record.source);
|
|
4592
|
+
pushPmDetail(lines, "runtime", record.runtime ?? "-");
|
|
4593
|
+
pushPmDetail(lines, "cwd", record.cwd);
|
|
4594
|
+
pushPmDetail(lines, "target", formatPmTarget(record));
|
|
4595
|
+
pushPmDetail(lines, "command", record.commandPreview || "-");
|
|
4596
|
+
pushPmDetail(lines, "runner pid", record.runnerPid ? String(record.runnerPid) : "-");
|
|
4597
|
+
pushPmDetail(lines, "child pid", record.childPid ? String(record.childPid) : "-");
|
|
4598
|
+
pushPmDetail(lines, "restart count", `${record.restartCount}/${record.maxRestarts}`);
|
|
4599
|
+
pushPmDetail(lines, "restart policy", record.restartPolicy);
|
|
4600
|
+
pushPmDetail(lines, "proxy", record.proxy ? `http://${record.proxy.host ?? "0.0.0.0"}:${record.proxy.port}` : "-");
|
|
4601
|
+
pushPmDetail(lines, "proxy strategy", record.proxy?.strategy ?? "-");
|
|
4602
|
+
pushPmDetail(lines, "proxy target", record.proxy && record.proxyTargetPort ? `${record.proxy.targetHost ?? "127.0.0.1"}:${record.proxyTargetPort}` : "-");
|
|
4603
|
+
pushPmDetail(lines, "max memory", record.maxMemoryBytes ? formatPmMemory(record.maxMemoryBytes) : "-");
|
|
4604
|
+
pushPmDetail(lines, "memory action", record.memoryAction ?? "-");
|
|
4605
|
+
pushPmDetail(lines, "cron restart", record.cronRestart ?? "-");
|
|
4606
|
+
pushPmDetail(lines, "exp backoff", record.expBackoffRestartDelay ? formatPmDuration(record.expBackoffRestartDelay) : "-");
|
|
4607
|
+
pushPmDetail(lines, "exp backoff max", record.expBackoffRestartMaxDelay ? formatPmDuration(record.expBackoffRestartMaxDelay) : "-");
|
|
4608
|
+
pushPmDetail(lines, "restart window", record.restartWindow ? formatPmDuration(record.restartWindow) : "-");
|
|
4609
|
+
pushPmDetail(lines, "wait ready", record.waitReady ? "enabled" : "disabled");
|
|
4610
|
+
pushPmDetail(lines, "listen timeout", record.waitReady ? formatPmDuration(record.listenTimeout) : "-");
|
|
4611
|
+
pushPmDetail(lines, "restart delay", formatPmDuration(record.restartDelay));
|
|
4612
|
+
pushPmDetail(lines, "kill timeout", formatPmDuration(record.killTimeout));
|
|
4613
|
+
pushPmDetail(lines, "min uptime", formatPmDuration(record.minUptime));
|
|
4614
|
+
pushPmDetail(lines, "autorestart", record.autorestart ? "enabled" : "disabled");
|
|
4615
|
+
pushPmDetail(lines, "watch", record.watch ? "enabled" : "disabled");
|
|
4616
|
+
pushPmDetail(lines, "watch debounce", record.watch ? formatPmDuration(record.watchDebounce) : "-");
|
|
4617
|
+
pushPmDetailList(lines, "watch paths", record.watchPaths);
|
|
4618
|
+
pushPmDetailList(lines, "watch ignore", record.watchIgnore);
|
|
4619
|
+
if (record.healthCheck) {
|
|
4620
|
+
pushPmDetail(lines, "health check", record.healthCheck.url);
|
|
4621
|
+
pushPmDetail(lines, "health grace", formatPmDuration(record.healthCheck.gracePeriod));
|
|
4622
|
+
pushPmDetail(lines, "health interval", formatPmDuration(record.healthCheck.interval));
|
|
4623
|
+
pushPmDetail(lines, "health timeout", formatPmDuration(record.healthCheck.timeout));
|
|
4624
|
+
pushPmDetail(lines, "health failures", String(record.healthCheck.maxFailures));
|
|
4625
|
+
} else {
|
|
4626
|
+
pushPmDetail(lines, "health check", "-");
|
|
4627
|
+
}
|
|
4628
|
+
pushPmDetailList(lines, "env", Object.entries(record.env).map(([key, value]) => `${key}=${value}`));
|
|
4629
|
+
pushPmDetail(lines, "stdout log", record.logFiles.out);
|
|
4630
|
+
pushPmDetail(lines, "stderr log", record.logFiles.err);
|
|
4631
|
+
pushPmDetail(lines, "created at", record.createdAt);
|
|
4632
|
+
pushPmDetail(lines, "updated at", record.updatedAt);
|
|
4633
|
+
pushPmDetail(lines, "metrics at", liveMetrics.updatedAt ?? "-");
|
|
4634
|
+
pushPmDetail(lines, "started at", record.startedAt ?? "-");
|
|
4635
|
+
pushPmDetail(lines, "stopped at", record.stoppedAt ?? "-");
|
|
4636
|
+
pushPmDetail(lines, "last exit", record.lastExitCode === void 0 ? "-" : String(record.lastExitCode));
|
|
4637
|
+
pushPmDetail(lines, "error", record.error ?? "-");
|
|
4638
|
+
return lines.join(EOL2);
|
|
4639
|
+
}
|
|
4640
|
+
function printPmList(paths, format = "table") {
|
|
4641
|
+
const matches = listPmMatches(paths).map((match) => toPmDisplayRecord(match.record));
|
|
4642
|
+
if (format === "json") {
|
|
4643
|
+
console.log(JSON.stringify(matches.map((match) => ({ ...match.record, liveMetrics: match.liveMetrics })), null, 2));
|
|
4644
|
+
return;
|
|
4645
|
+
}
|
|
2904
4646
|
if (matches.length === 0) {
|
|
2905
4647
|
console.log("No managed processes found.");
|
|
2906
4648
|
return;
|
|
@@ -2909,22 +4651,47 @@ function printPmList(paths) {
|
|
|
2909
4651
|
padCell("name", 20),
|
|
2910
4652
|
padCell("status", 12),
|
|
2911
4653
|
padCell("pid", 8),
|
|
4654
|
+
padCell("cpu", 8),
|
|
4655
|
+
padCell("memory", 10),
|
|
4656
|
+
padCell("uptime", 10),
|
|
2912
4657
|
padCell("restarts", 10),
|
|
2913
4658
|
padCell("type", 8),
|
|
2914
4659
|
"runtime"
|
|
2915
4660
|
];
|
|
2916
4661
|
console.log(headers.join(" "));
|
|
2917
|
-
for (const { record } of matches) {
|
|
4662
|
+
for (const { record, liveMetrics } of matches) {
|
|
2918
4663
|
console.log([
|
|
2919
4664
|
padCell(record.name, 20),
|
|
2920
4665
|
padCell(record.status, 12),
|
|
2921
4666
|
padCell(record.childPid ? String(record.childPid) : "-", 8),
|
|
4667
|
+
padCell(formatPmCpuPercent(liveMetrics.cpuPercent), 8),
|
|
4668
|
+
padCell(formatPmMemory(liveMetrics.memoryRssBytes), 10),
|
|
4669
|
+
padCell(formatPmUptime(liveMetrics.uptimeMs), 10),
|
|
2922
4670
|
padCell(String(record.restartCount ?? 0), 10),
|
|
2923
4671
|
padCell(record.type, 8),
|
|
2924
4672
|
record.runtime ?? "-"
|
|
2925
4673
|
].join(" "));
|
|
2926
4674
|
}
|
|
2927
4675
|
}
|
|
4676
|
+
async function runPmList(args) {
|
|
4677
|
+
const options = parsePmListArgs(args);
|
|
4678
|
+
const { paths } = await loadPmContext();
|
|
4679
|
+
printPmList(paths, options.format);
|
|
4680
|
+
}
|
|
4681
|
+
async function runPmShow(args) {
|
|
4682
|
+
const options = parsePmShowArgs(args);
|
|
4683
|
+
const { paths } = await loadPmContext();
|
|
4684
|
+
const match = resolveInspectableMatch(paths, options.name);
|
|
4685
|
+
if (!match) {
|
|
4686
|
+
throw new Error(`No managed process found for: ${options.name}`);
|
|
4687
|
+
}
|
|
4688
|
+
const synced = syncPmRecordLiveness(match);
|
|
4689
|
+
if (options.format === "json") {
|
|
4690
|
+
console.log(JSON.stringify(serializePmRecord(synced.record), null, 2));
|
|
4691
|
+
return;
|
|
4692
|
+
}
|
|
4693
|
+
console.log(formatPmRecordDetails(synced.record, resolvePmLiveMetrics(synced.record)));
|
|
4694
|
+
}
|
|
2928
4695
|
async function runPmStop(args) {
|
|
2929
4696
|
const target = args[0];
|
|
2930
4697
|
if (!target) {
|
|
@@ -2951,17 +4718,62 @@ async function runPmRestart(args) {
|
|
|
2951
4718
|
await stopPmMatches(matches);
|
|
2952
4719
|
const restarted = [];
|
|
2953
4720
|
for (const match of matches) {
|
|
2954
|
-
const definition =
|
|
2955
|
-
toPmAppConfig(match.record),
|
|
2956
|
-
{ name: match.record.name, env: {}, watchPaths: [], watchIgnore: [] },
|
|
2957
|
-
process.cwd(),
|
|
2958
|
-
match.record.source
|
|
2959
|
-
);
|
|
4721
|
+
const definition = rebuildPmRecordDefinition(match.record);
|
|
2960
4722
|
await startManagedProcess(definition, paths);
|
|
2961
4723
|
restarted.push(match.record.name);
|
|
2962
4724
|
}
|
|
2963
4725
|
console.log(`[pm] restarted ${restarted.join(", ")}`);
|
|
2964
4726
|
}
|
|
4727
|
+
async function runPmReload(args) {
|
|
4728
|
+
const target = args[0];
|
|
4729
|
+
if (!target) {
|
|
4730
|
+
throw new Error("Usage: elit pm reload <name|all>");
|
|
4731
|
+
}
|
|
4732
|
+
const { paths } = await loadPmContext();
|
|
4733
|
+
const matches = resolveNamedMatches(paths, target);
|
|
4734
|
+
if (matches.length === 0) {
|
|
4735
|
+
throw new Error(`No managed process found for: ${target}`);
|
|
4736
|
+
}
|
|
4737
|
+
const reloaded = [];
|
|
4738
|
+
const errors = [];
|
|
4739
|
+
for (const group of groupPmMatchesByBaseName(matches)) {
|
|
4740
|
+
for (const match of group) {
|
|
4741
|
+
try {
|
|
4742
|
+
if (supportsPmProxyReload2(match.record)) {
|
|
4743
|
+
const reloadRequestedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
4744
|
+
writePmRecord(match.filePath, {
|
|
4745
|
+
...match.record,
|
|
4746
|
+
status: "restarting",
|
|
4747
|
+
reloadRequestedAt,
|
|
4748
|
+
updatedAt: reloadRequestedAt,
|
|
4749
|
+
error: void 0
|
|
4750
|
+
});
|
|
4751
|
+
await waitForPmRecordOnline(
|
|
4752
|
+
getPmRecordPath(paths, match.record.id),
|
|
4753
|
+
resolvePmReloadReadyTimeout(match.record)
|
|
4754
|
+
);
|
|
4755
|
+
reloaded.push(match.record.name);
|
|
4756
|
+
continue;
|
|
4757
|
+
}
|
|
4758
|
+
await stopPmMatches([match]);
|
|
4759
|
+
const definition = rebuildPmRecordDefinition(match.record);
|
|
4760
|
+
const startedRecord = await startManagedProcess(definition, paths);
|
|
4761
|
+
await waitForPmRecordOnline(
|
|
4762
|
+
getPmRecordPath(paths, startedRecord.id),
|
|
4763
|
+
resolvePmReloadReadyTimeout(startedRecord)
|
|
4764
|
+
);
|
|
4765
|
+
reloaded.push(match.record.name);
|
|
4766
|
+
} catch (error) {
|
|
4767
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
4768
|
+
errors.push(`[pm] ${match.record.name}: ${message}`);
|
|
4769
|
+
}
|
|
4770
|
+
}
|
|
4771
|
+
}
|
|
4772
|
+
if (errors.length > 0) {
|
|
4773
|
+
throw new Error([`[pm] reloaded ${reloaded.length} process${reloaded.length === 1 ? "" : "es"}`, ...errors].join(EOL2));
|
|
4774
|
+
}
|
|
4775
|
+
console.log(`[pm] reloaded ${reloaded.join(", ")}`);
|
|
4776
|
+
}
|
|
2965
4777
|
async function runPmSave() {
|
|
2966
4778
|
const { paths } = await loadPmContext();
|
|
2967
4779
|
ensurePmDirectories(paths);
|
|
@@ -2983,12 +4795,7 @@ async function runPmResurrect() {
|
|
|
2983
4795
|
let restored = 0;
|
|
2984
4796
|
for (const app of dump.apps) {
|
|
2985
4797
|
try {
|
|
2986
|
-
const definition =
|
|
2987
|
-
toSavedPmAppConfig(app),
|
|
2988
|
-
{ name: app.name, env: {}, watchPaths: [], watchIgnore: [] },
|
|
2989
|
-
process.cwd(),
|
|
2990
|
-
"cli"
|
|
2991
|
-
);
|
|
4798
|
+
const definition = rebuildPmSavedDefinition(app);
|
|
2992
4799
|
await startManagedProcess(definition, paths);
|
|
2993
4800
|
restored += 1;
|
|
2994
4801
|
} catch (error) {
|
|
@@ -3012,16 +4819,127 @@ async function runPmDelete(args) {
|
|
|
3012
4819
|
throw new Error(`No managed process found for: ${target}`);
|
|
3013
4820
|
}
|
|
3014
4821
|
await stopPmMatches(matches);
|
|
4822
|
+
deletePmMatches(matches);
|
|
4823
|
+
console.log(`[pm] deleted ${matches.length} process${matches.length === 1 ? "" : "es"}`);
|
|
4824
|
+
}
|
|
4825
|
+
async function runPmScale(args) {
|
|
4826
|
+
const target = args[0];
|
|
4827
|
+
const countArg = args[1];
|
|
4828
|
+
if (!target || countArg === void 0 || args.length > 2) {
|
|
4829
|
+
throw new Error("Usage: elit pm scale <name> <count>");
|
|
4830
|
+
}
|
|
4831
|
+
const desiredCount = normalizeIntegerOption(countArg, "pm scale <count>", 0);
|
|
4832
|
+
const { config, paths } = await loadPmContext();
|
|
4833
|
+
const exactMatch = findPmRecordMatch(paths, target);
|
|
4834
|
+
const currentMatches = sortPmMatchesByInstance(resolveNamedMatches(paths, exactMatch?.record.baseName ?? target));
|
|
4835
|
+
if (currentMatches.length === 0) {
|
|
4836
|
+
if (desiredCount === 0) {
|
|
4837
|
+
console.log(`[pm] ${target} already scaled to 0 instances`);
|
|
4838
|
+
return;
|
|
4839
|
+
}
|
|
4840
|
+
const definitions = resolvePmStartDefinitions(
|
|
4841
|
+
{ name: target, env: {}, watchPaths: [], watchIgnore: [], instances: desiredCount },
|
|
4842
|
+
config,
|
|
4843
|
+
process.cwd()
|
|
4844
|
+
);
|
|
4845
|
+
for (const definition of definitions) {
|
|
4846
|
+
await startManagedProcess(definition, paths);
|
|
4847
|
+
}
|
|
4848
|
+
console.log(`[pm] scaled ${target} to ${desiredCount} instance${desiredCount === 1 ? "" : "s"}`);
|
|
4849
|
+
return;
|
|
4850
|
+
}
|
|
4851
|
+
const baseName = currentMatches[0]?.record.baseName ?? target;
|
|
4852
|
+
if (desiredCount === currentMatches.length) {
|
|
4853
|
+
console.log(`[pm] ${baseName} already scaled to ${desiredCount} instance${desiredCount === 1 ? "" : "s"}`);
|
|
4854
|
+
return;
|
|
4855
|
+
}
|
|
4856
|
+
if (desiredCount === 0) {
|
|
4857
|
+
await stopPmMatches(currentMatches);
|
|
4858
|
+
deletePmMatches(currentMatches);
|
|
4859
|
+
console.log(`[pm] scaled ${baseName} to 0 instances`);
|
|
4860
|
+
return;
|
|
4861
|
+
}
|
|
4862
|
+
if (desiredCount < currentMatches.length) {
|
|
4863
|
+
const toRemove = [...currentMatches].sort((left, right) => right.record.instanceIndex - left.record.instanceIndex).slice(0, currentMatches.length - desiredCount);
|
|
4864
|
+
const remaining = currentMatches.filter((match) => !toRemove.some((removal) => removal.record.id === match.record.id));
|
|
4865
|
+
await stopPmMatches(toRemove);
|
|
4866
|
+
deletePmMatches(toRemove);
|
|
4867
|
+
updatePmInstanceCount(remaining, desiredCount);
|
|
4868
|
+
console.log(`[pm] scaled ${baseName} to ${desiredCount} instance${desiredCount === 1 ? "" : "s"}`);
|
|
4869
|
+
return;
|
|
4870
|
+
}
|
|
4871
|
+
updatePmInstanceCount(currentMatches, desiredCount);
|
|
4872
|
+
const baseRecord = currentMatches[0]?.record;
|
|
4873
|
+
if (!baseRecord) {
|
|
4874
|
+
throw new Error(`No managed process found for: ${target}`);
|
|
4875
|
+
}
|
|
4876
|
+
const baseDefinition = rebuildPmRecordDefinition(baseRecord, desiredCount);
|
|
4877
|
+
const expandedDefinitions = expandPmInstanceDefinitions(baseDefinition, desiredCount);
|
|
4878
|
+
const existingNames = new Set(currentMatches.map((match) => match.record.name));
|
|
4879
|
+
for (const definition of expandedDefinitions) {
|
|
4880
|
+
if (existingNames.has(definition.name)) {
|
|
4881
|
+
continue;
|
|
4882
|
+
}
|
|
4883
|
+
await startManagedProcess(definition, paths);
|
|
4884
|
+
}
|
|
4885
|
+
console.log(`[pm] scaled ${baseName} to ${desiredCount} instance${desiredCount === 1 ? "" : "s"}`);
|
|
4886
|
+
}
|
|
4887
|
+
async function runPmSendSignal(args) {
|
|
4888
|
+
const signalArg = args[0];
|
|
4889
|
+
const target = args[1];
|
|
4890
|
+
if (!signalArg || !target || args.length > 2) {
|
|
4891
|
+
throw new Error("Usage: elit pm send-signal <signal> <name|all>");
|
|
4892
|
+
}
|
|
4893
|
+
const signalName = normalizePmSignalName(signalArg);
|
|
4894
|
+
const { paths } = await loadPmContext();
|
|
4895
|
+
const matches = resolveNamedMatches(paths, target);
|
|
4896
|
+
if (matches.length === 0) {
|
|
4897
|
+
throw new Error(`No managed process found for: ${target}`);
|
|
4898
|
+
}
|
|
4899
|
+
let signaled = 0;
|
|
4900
|
+
const errors = [];
|
|
3015
4901
|
for (const match of matches) {
|
|
3016
|
-
|
|
3017
|
-
|
|
4902
|
+
const pid = resolveSignalablePid(match.record);
|
|
4903
|
+
if (!pid) {
|
|
4904
|
+
continue;
|
|
3018
4905
|
}
|
|
3019
|
-
|
|
3020
|
-
|
|
4906
|
+
try {
|
|
4907
|
+
sendPmSignal(pid, signalName);
|
|
4908
|
+
signaled += 1;
|
|
4909
|
+
} catch (error) {
|
|
4910
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
4911
|
+
errors.push(`[pm] ${match.record.name}: ${message}`);
|
|
3021
4912
|
}
|
|
3022
|
-
rmSync(match.filePath, { force: true });
|
|
3023
4913
|
}
|
|
3024
|
-
|
|
4914
|
+
if (errors.length > 0) {
|
|
4915
|
+
throw new Error([`[pm] sent ${signalName} to ${signaled} process${signaled === 1 ? "" : "es"}`, ...errors].join(EOL2));
|
|
4916
|
+
}
|
|
4917
|
+
if (signaled === 0) {
|
|
4918
|
+
throw new Error(`No running managed process found for: ${target}`);
|
|
4919
|
+
}
|
|
4920
|
+
console.log(`[pm] sent ${signalName} to ${signaled} process${signaled === 1 ? "" : "es"}`);
|
|
4921
|
+
}
|
|
4922
|
+
async function runPmReset(args) {
|
|
4923
|
+
const target = args[0];
|
|
4924
|
+
if (!target) {
|
|
4925
|
+
throw new Error("Usage: elit pm reset <name|all>");
|
|
4926
|
+
}
|
|
4927
|
+
const { paths } = await loadPmContext();
|
|
4928
|
+
const matches = resolveNamedMatches(paths, target);
|
|
4929
|
+
if (matches.length === 0) {
|
|
4930
|
+
throw new Error(`No managed process found for: ${target}`);
|
|
4931
|
+
}
|
|
4932
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4933
|
+
for (const match of matches) {
|
|
4934
|
+
writePmRecord(match.filePath, {
|
|
4935
|
+
...match.record,
|
|
4936
|
+
restartCount: 0,
|
|
4937
|
+
lastExitCode: void 0,
|
|
4938
|
+
error: void 0,
|
|
4939
|
+
updatedAt: now
|
|
4940
|
+
});
|
|
4941
|
+
}
|
|
4942
|
+
console.log(`[pm] reset ${matches.length} process${matches.length === 1 ? "" : "es"}`);
|
|
3025
4943
|
}
|
|
3026
4944
|
async function runPmLogs(args) {
|
|
3027
4945
|
if (args.length === 0) {
|
|
@@ -3054,7 +4972,7 @@ async function runPmLogs(args) {
|
|
|
3054
4972
|
throw new Error("Usage: elit pm logs <name> [--lines <n>] [--stderr]");
|
|
3055
4973
|
}
|
|
3056
4974
|
const { paths } = await loadPmContext();
|
|
3057
|
-
const match =
|
|
4975
|
+
const match = resolveInspectableMatch(paths, name);
|
|
3058
4976
|
if (!match) {
|
|
3059
4977
|
throw new Error(`No managed process found for: ${name}`);
|
|
3060
4978
|
}
|
|
@@ -3078,17 +4996,36 @@ async function runPmCommand(args) {
|
|
|
3078
4996
|
await runPmStart(args.slice(1));
|
|
3079
4997
|
return;
|
|
3080
4998
|
case "list":
|
|
3081
|
-
case "ls":
|
|
3082
|
-
|
|
3083
|
-
|
|
4999
|
+
case "ls":
|
|
5000
|
+
await runPmList(args.slice(1));
|
|
5001
|
+
return;
|
|
5002
|
+
case "jlist":
|
|
5003
|
+
await runPmList(["--json", ...args.slice(1)]);
|
|
5004
|
+
return;
|
|
5005
|
+
case "show":
|
|
5006
|
+
case "describe":
|
|
5007
|
+
await runPmShow(args.slice(1));
|
|
3084
5008
|
return;
|
|
3085
|
-
}
|
|
3086
5009
|
case "stop":
|
|
3087
5010
|
await runPmStop(args.slice(1));
|
|
3088
5011
|
return;
|
|
3089
5012
|
case "restart":
|
|
3090
5013
|
await runPmRestart(args.slice(1));
|
|
3091
5014
|
return;
|
|
5015
|
+
case "reload":
|
|
5016
|
+
await runPmReload(args.slice(1));
|
|
5017
|
+
return;
|
|
5018
|
+
case "scale":
|
|
5019
|
+
await runPmScale(args.slice(1));
|
|
5020
|
+
return;
|
|
5021
|
+
case "send-signal":
|
|
5022
|
+
case "signal":
|
|
5023
|
+
case "sendSignal":
|
|
5024
|
+
await runPmSendSignal(args.slice(1));
|
|
5025
|
+
return;
|
|
5026
|
+
case "reset":
|
|
5027
|
+
await runPmReset(args.slice(1));
|
|
5028
|
+
return;
|
|
3092
5029
|
case "delete":
|
|
3093
5030
|
case "remove":
|
|
3094
5031
|
case "rm":
|
|
@@ -3120,6 +5057,12 @@ export {
|
|
|
3120
5057
|
DEFAULT_MIN_UPTIME,
|
|
3121
5058
|
DEFAULT_PM_DATA_DIR,
|
|
3122
5059
|
DEFAULT_PM_DUMP_FILE,
|
|
5060
|
+
DEFAULT_PM_EXP_BACKOFF_MAX_DELAY,
|
|
5061
|
+
DEFAULT_PM_KILL_TIMEOUT,
|
|
5062
|
+
DEFAULT_PM_LISTEN_TIMEOUT,
|
|
5063
|
+
DEFAULT_PM_MEMORY_CHECK_INTERVAL,
|
|
5064
|
+
DEFAULT_PM_PROXY_STRATEGY,
|
|
5065
|
+
DEFAULT_PM_RESTART_WINDOW,
|
|
3123
5066
|
DEFAULT_PM_STOP_GRACE_PERIOD_MS,
|
|
3124
5067
|
DEFAULT_PM_STOP_POLL_MS,
|
|
3125
5068
|
DEFAULT_RESTART_DELAY,
|
|
@@ -3138,7 +5081,10 @@ export {
|
|
|
3138
5081
|
countDefinedPmWapkSources,
|
|
3139
5082
|
deriveDefaultWatchPaths,
|
|
3140
5083
|
ensurePmDirectories,
|
|
5084
|
+
expandPmInstanceDefinitions,
|
|
5085
|
+
findPmGroupMatches,
|
|
3141
5086
|
findPmRecordMatch,
|
|
5087
|
+
formatPmInstanceName,
|
|
3142
5088
|
getPmRecordPath,
|
|
3143
5089
|
hasPmGoogleDriveConfig,
|
|
3144
5090
|
hasPmWapkRunConfig,
|
|
@@ -3157,6 +5103,10 @@ export {
|
|
|
3157
5103
|
normalizeHealthCheckConfig,
|
|
3158
5104
|
normalizeIntegerOption,
|
|
3159
5105
|
normalizeNonEmptyString,
|
|
5106
|
+
normalizePmMemoryAction,
|
|
5107
|
+
normalizePmMemoryLimit,
|
|
5108
|
+
normalizePmProxyConfig,
|
|
5109
|
+
normalizePmProxyStrategy,
|
|
3160
5110
|
normalizePmRestartPolicy,
|
|
3161
5111
|
normalizePmRuntime,
|
|
3162
5112
|
normalizeResolvedWatchIgnorePaths,
|
|
@@ -3180,7 +5130,9 @@ export {
|
|
|
3180
5130
|
runManagedProcessLoop,
|
|
3181
5131
|
runPmCommand,
|
|
3182
5132
|
runPmRunner,
|
|
5133
|
+
samplePmProcessMetrics,
|
|
3183
5134
|
sanitizePmProcessName,
|
|
5135
|
+
sendPmSignal,
|
|
3184
5136
|
startManagedProcess,
|
|
3185
5137
|
stopPmMatches,
|
|
3186
5138
|
stripPmWapkSourceFromRunConfig,
|