elit 3.6.7 → 3.6.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Cargo.lock +1 -1
- package/Cargo.toml +1 -1
- package/README.md +20 -1
- package/dist/cli.cjs +2777 -321
- package/dist/cli.mjs +2764 -308
- package/dist/config.d.ts +6 -6
- package/dist/{contracts-BeW9k0yZ.d.ts → contracts-_0p1-15U.d.ts} +1 -1
- package/dist/coverage.d.ts +1 -1
- package/dist/dev-build.d.ts +3 -3
- package/dist/http.cjs +160 -41
- package/dist/http.d.ts +5 -11
- package/dist/http.js +160 -41
- package/dist/http.mjs +160 -41
- package/dist/https.cjs +194 -46
- package/dist/https.d.ts +3 -6
- package/dist/https.js +194 -46
- package/dist/https.mjs +194 -46
- package/dist/pm-node-shared-listener-bootstrap.cjs +75 -0
- package/dist/pm.cjs +2390 -160
- package/dist/pm.d.ts +83 -8
- package/dist/pm.js +2388 -188
- package/dist/pm.mjs +2380 -165
- package/dist/preview-build.d.ts +3 -3
- package/dist/server.cjs +417 -168
- package/dist/server.d.ts +4 -4
- package/dist/server.js +453 -179
- package/dist/server.mjs +417 -168
- package/dist/smtp-server.js +37 -12
- package/dist/test-reporter.d.ts +1 -1
- package/dist/test-runtime.d.ts +1 -1
- package/dist/{types-tJn88E1N.d.ts → types-BayMVo_k.d.ts} +39 -3
- package/dist/{types-CIhpN1-K.d.ts → types-C70T-42Z.d.ts} +1 -1
- package/dist/{types-DAisuVr5.d.ts → types-DPOgoGs-.d.ts} +7 -1
- package/dist/{state-DvEkDehk.d.ts → types-fiLday0L.d.ts} +96 -92
- package/dist/types.d.ts +4 -0
- package/dist/{websocket-XfyK23zD.d.ts → websocket-BLBEAnhp.d.ts} +1 -1
- package/dist/ws.d.ts +3 -3
- package/dist/wss.cjs +194 -46
- package/dist/wss.d.ts +3 -3
- package/dist/wss.js +194 -46
- package/dist/wss.mjs +194 -46
- package/package.json +11 -6
package/dist/pm.cjs
CHANGED
|
@@ -480,6 +480,12 @@ __export(pm_exports, {
|
|
|
480
480
|
DEFAULT_MIN_UPTIME: () => DEFAULT_MIN_UPTIME,
|
|
481
481
|
DEFAULT_PM_DATA_DIR: () => DEFAULT_PM_DATA_DIR,
|
|
482
482
|
DEFAULT_PM_DUMP_FILE: () => DEFAULT_PM_DUMP_FILE,
|
|
483
|
+
DEFAULT_PM_EXP_BACKOFF_MAX_DELAY: () => DEFAULT_PM_EXP_BACKOFF_MAX_DELAY,
|
|
484
|
+
DEFAULT_PM_KILL_TIMEOUT: () => DEFAULT_PM_KILL_TIMEOUT,
|
|
485
|
+
DEFAULT_PM_LISTEN_TIMEOUT: () => DEFAULT_PM_LISTEN_TIMEOUT,
|
|
486
|
+
DEFAULT_PM_MEMORY_CHECK_INTERVAL: () => DEFAULT_PM_MEMORY_CHECK_INTERVAL,
|
|
487
|
+
DEFAULT_PM_PROXY_STRATEGY: () => DEFAULT_PM_PROXY_STRATEGY,
|
|
488
|
+
DEFAULT_PM_RESTART_WINDOW: () => DEFAULT_PM_RESTART_WINDOW,
|
|
483
489
|
DEFAULT_PM_STOP_GRACE_PERIOD_MS: () => DEFAULT_PM_STOP_GRACE_PERIOD_MS,
|
|
484
490
|
DEFAULT_PM_STOP_POLL_MS: () => DEFAULT_PM_STOP_POLL_MS,
|
|
485
491
|
DEFAULT_RESTART_DELAY: () => DEFAULT_RESTART_DELAY,
|
|
@@ -498,7 +504,10 @@ __export(pm_exports, {
|
|
|
498
504
|
countDefinedPmWapkSources: () => countDefinedPmWapkSources,
|
|
499
505
|
deriveDefaultWatchPaths: () => deriveDefaultWatchPaths,
|
|
500
506
|
ensurePmDirectories: () => ensurePmDirectories,
|
|
507
|
+
expandPmInstanceDefinitions: () => expandPmInstanceDefinitions,
|
|
508
|
+
findPmGroupMatches: () => findPmGroupMatches,
|
|
501
509
|
findPmRecordMatch: () => findPmRecordMatch,
|
|
510
|
+
formatPmInstanceName: () => formatPmInstanceName,
|
|
502
511
|
getPmRecordPath: () => getPmRecordPath,
|
|
503
512
|
hasPmGoogleDriveConfig: () => hasPmGoogleDriveConfig,
|
|
504
513
|
hasPmWapkRunConfig: () => hasPmWapkRunConfig,
|
|
@@ -517,6 +526,10 @@ __export(pm_exports, {
|
|
|
517
526
|
normalizeHealthCheckConfig: () => normalizeHealthCheckConfig,
|
|
518
527
|
normalizeIntegerOption: () => normalizeIntegerOption,
|
|
519
528
|
normalizeNonEmptyString: () => normalizeNonEmptyString,
|
|
529
|
+
normalizePmMemoryAction: () => normalizePmMemoryAction,
|
|
530
|
+
normalizePmMemoryLimit: () => normalizePmMemoryLimit,
|
|
531
|
+
normalizePmProxyConfig: () => normalizePmProxyConfig,
|
|
532
|
+
normalizePmProxyStrategy: () => normalizePmProxyStrategy,
|
|
520
533
|
normalizePmRestartPolicy: () => normalizePmRestartPolicy,
|
|
521
534
|
normalizePmRuntime: () => normalizePmRuntime,
|
|
522
535
|
normalizeResolvedWatchIgnorePaths: () => normalizeResolvedWatchIgnorePaths,
|
|
@@ -540,7 +553,9 @@ __export(pm_exports, {
|
|
|
540
553
|
runManagedProcessLoop: () => runManagedProcessLoop,
|
|
541
554
|
runPmCommand: () => runPmCommand,
|
|
542
555
|
runPmRunner: () => runPmRunner,
|
|
556
|
+
samplePmProcessMetrics: () => samplePmProcessMetrics,
|
|
543
557
|
sanitizePmProcessName: () => sanitizePmProcessName,
|
|
558
|
+
sendPmSignal: () => sendPmSignal,
|
|
544
559
|
startManagedProcess: () => startManagedProcess,
|
|
545
560
|
stopPmMatches: () => stopPmMatches,
|
|
546
561
|
stripPmWapkSourceFromRunConfig: () => stripPmWapkSourceFromRunConfig,
|
|
@@ -564,6 +579,7 @@ var DEFAULT_RESTART_DELAY = 1e3;
|
|
|
564
579
|
var DEFAULT_MAX_RESTARTS = 10;
|
|
565
580
|
var DEFAULT_WATCH_DEBOUNCE = 250;
|
|
566
581
|
var DEFAULT_MIN_UPTIME = 0;
|
|
582
|
+
var DEFAULT_PM_LISTEN_TIMEOUT = 3e3;
|
|
567
583
|
var DEFAULT_HEALTHCHECK_GRACE_PERIOD = 5e3;
|
|
568
584
|
var DEFAULT_HEALTHCHECK_INTERVAL = 1e4;
|
|
569
585
|
var DEFAULT_HEALTHCHECK_TIMEOUT = 3e3;
|
|
@@ -571,6 +587,11 @@ var DEFAULT_HEALTHCHECK_MAX_FAILURES = 3;
|
|
|
571
587
|
var DEFAULT_LOG_LINES = 40;
|
|
572
588
|
var DEFAULT_PM_STOP_POLL_MS = 100;
|
|
573
589
|
var DEFAULT_PM_STOP_GRACE_PERIOD_MS = 5e3;
|
|
590
|
+
var DEFAULT_PM_KILL_TIMEOUT = DEFAULT_PM_STOP_GRACE_PERIOD_MS;
|
|
591
|
+
var DEFAULT_PM_EXP_BACKOFF_MAX_DELAY = 15e3;
|
|
592
|
+
var DEFAULT_PM_MEMORY_CHECK_INTERVAL = 500;
|
|
593
|
+
var DEFAULT_PM_RESTART_WINDOW = 0;
|
|
594
|
+
var DEFAULT_PM_PROXY_STRATEGY = "proxy";
|
|
574
595
|
var PM_WAPK_ONLINE_STDIN_SHUTDOWN_ENV = "ELIT_PM_WAPK_ONLINE_STDIN_SHUTDOWN";
|
|
575
596
|
var PM_WAPK_ONLINE_SHUTDOWN_COMMAND = "__ELIT_PM_WAPK_ONLINE_SHUTDOWN__";
|
|
576
597
|
var PM_WAPK_ONLINE_SHUTDOWN_TIMEOUT_MS = 8e3;
|
|
@@ -608,6 +629,32 @@ function normalizePmRestartPolicy(value, optionName = "--restart-policy") {
|
|
|
608
629
|
}
|
|
609
630
|
throw new Error(`${optionName} must be one of: always, on-failure, never`);
|
|
610
631
|
}
|
|
632
|
+
function normalizePmMemoryAction(value, optionName = "--memory-action") {
|
|
633
|
+
if (value === void 0 || value === null || value === "") {
|
|
634
|
+
return void 0;
|
|
635
|
+
}
|
|
636
|
+
if (typeof value !== "string") {
|
|
637
|
+
throw new Error(`${optionName} must be one of: restart, stop`);
|
|
638
|
+
}
|
|
639
|
+
const action = value.trim().toLowerCase();
|
|
640
|
+
if (action === "restart" || action === "stop") {
|
|
641
|
+
return action;
|
|
642
|
+
}
|
|
643
|
+
throw new Error(`${optionName} must be one of: restart, stop`);
|
|
644
|
+
}
|
|
645
|
+
function normalizePmProxyStrategy(value, optionName = "--proxy-strategy") {
|
|
646
|
+
if (value === void 0 || value === null || value === "") {
|
|
647
|
+
return void 0;
|
|
648
|
+
}
|
|
649
|
+
if (typeof value !== "string") {
|
|
650
|
+
throw new Error(`${optionName} must be one of: proxy, inherit`);
|
|
651
|
+
}
|
|
652
|
+
const strategy = value.trim().toLowerCase();
|
|
653
|
+
if (strategy === "proxy" || strategy === "inherit") {
|
|
654
|
+
return strategy;
|
|
655
|
+
}
|
|
656
|
+
throw new Error(`${optionName} must be one of: proxy, inherit`);
|
|
657
|
+
}
|
|
611
658
|
function normalizeIntegerOption(value, optionName, min = 0) {
|
|
612
659
|
const parsed = Number.parseInt(value, 10);
|
|
613
660
|
if (!Number.isFinite(parsed) || parsed < min) {
|
|
@@ -615,6 +662,30 @@ function normalizeIntegerOption(value, optionName, min = 0) {
|
|
|
615
662
|
}
|
|
616
663
|
return parsed;
|
|
617
664
|
}
|
|
665
|
+
function normalizePmMemoryLimit(value, optionName = "--max-memory") {
|
|
666
|
+
if (value === void 0 || value === null || value === "") {
|
|
667
|
+
return void 0;
|
|
668
|
+
}
|
|
669
|
+
if (typeof value === "number") {
|
|
670
|
+
const normalized2 = Math.trunc(value);
|
|
671
|
+
if (!Number.isFinite(normalized2) || normalized2 < 1) {
|
|
672
|
+
throw new Error(`${optionName} must be a number >= 1 or a size like 256M.`);
|
|
673
|
+
}
|
|
674
|
+
return normalized2;
|
|
675
|
+
}
|
|
676
|
+
if (typeof value !== "string") {
|
|
677
|
+
throw new Error(`${optionName} must be a number >= 1 or a size like 256M.`);
|
|
678
|
+
}
|
|
679
|
+
const normalized = value.trim();
|
|
680
|
+
const match = /^(\d+)(b|kb|mb|gb|tb|k|m|g|t)?$/i.exec(normalized);
|
|
681
|
+
if (!match) {
|
|
682
|
+
throw new Error(`${optionName} must be a number >= 1 or a size like 256M.`);
|
|
683
|
+
}
|
|
684
|
+
const amount = normalizeIntegerOption(match[1] ?? "", optionName, 1);
|
|
685
|
+
const unit = (match[2] ?? "b").toLowerCase();
|
|
686
|
+
const multiplier = unit === "b" ? 1 : unit === "k" || unit === "kb" ? 1024 : unit === "m" || unit === "mb" ? 1024 ** 2 : unit === "g" || unit === "gb" ? 1024 ** 3 : 1024 ** 4;
|
|
687
|
+
return amount * multiplier;
|
|
688
|
+
}
|
|
618
689
|
function normalizeNonEmptyString(value) {
|
|
619
690
|
if (typeof value !== "string") {
|
|
620
691
|
return void 0;
|
|
@@ -891,6 +962,30 @@ function readRequiredValue(args, index, optionName) {
|
|
|
891
962
|
}
|
|
892
963
|
return value;
|
|
893
964
|
}
|
|
965
|
+
function normalizePmProxyConfig(value, optionName = "pm proxy") {
|
|
966
|
+
if (value === void 0 || value === null) {
|
|
967
|
+
return void 0;
|
|
968
|
+
}
|
|
969
|
+
if (typeof value !== "object") {
|
|
970
|
+
throw new Error(`${optionName} must be an object with at least a port.`);
|
|
971
|
+
}
|
|
972
|
+
const candidate = value;
|
|
973
|
+
const hasAnyValue = candidate.port !== void 0 || candidate.host !== void 0 || candidate.targetHost !== void 0 || candidate.envVar !== void 0;
|
|
974
|
+
if (!hasAnyValue) {
|
|
975
|
+
return void 0;
|
|
976
|
+
}
|
|
977
|
+
if (candidate.port === void 0 || candidate.port === null || candidate.port === "") {
|
|
978
|
+
throw new Error(`${optionName}.port is required.`);
|
|
979
|
+
}
|
|
980
|
+
const port = typeof candidate.port === "number" ? normalizeIntegerOption(String(Math.trunc(candidate.port)), `${optionName}.port`, 1) : normalizeIntegerOption(String(candidate.port), `${optionName}.port`, 1);
|
|
981
|
+
return {
|
|
982
|
+
port,
|
|
983
|
+
strategy: normalizePmProxyStrategy(candidate.strategy, `${optionName}.strategy`) ?? DEFAULT_PM_PROXY_STRATEGY,
|
|
984
|
+
host: normalizeNonEmptyString(candidate.host),
|
|
985
|
+
targetHost: normalizeNonEmptyString(candidate.targetHost),
|
|
986
|
+
envVar: normalizeNonEmptyString(candidate.envVar)
|
|
987
|
+
};
|
|
988
|
+
}
|
|
894
989
|
|
|
895
990
|
// src/cli/pm/records.ts
|
|
896
991
|
var import_node_fs2 = require("fs");
|
|
@@ -923,18 +1018,31 @@ function writePmRecord(filePath, record) {
|
|
|
923
1018
|
function toSavedAppDefinition(record) {
|
|
924
1019
|
return {
|
|
925
1020
|
name: record.name,
|
|
1021
|
+
baseName: record.baseName,
|
|
1022
|
+
instanceIndex: record.instanceIndex,
|
|
1023
|
+
instances: record.instances,
|
|
926
1024
|
type: record.type,
|
|
927
1025
|
cwd: record.cwd,
|
|
928
1026
|
runtime: record.runtime,
|
|
929
1027
|
env: record.env,
|
|
1028
|
+
proxy: record.proxy,
|
|
930
1029
|
script: record.script,
|
|
931
1030
|
file: record.file,
|
|
932
1031
|
wapk: record.wapk,
|
|
933
1032
|
password: record.password,
|
|
934
1033
|
wapkRun: record.wapkRun,
|
|
935
1034
|
restartPolicy: record.restartPolicy,
|
|
1035
|
+
maxMemoryBytes: record.maxMemoryBytes,
|
|
1036
|
+
memoryAction: record.memoryAction,
|
|
1037
|
+
cronRestart: record.cronRestart,
|
|
1038
|
+
expBackoffRestartDelay: record.expBackoffRestartDelay,
|
|
1039
|
+
expBackoffRestartMaxDelay: record.expBackoffRestartMaxDelay,
|
|
1040
|
+
restartWindow: record.restartWindow,
|
|
1041
|
+
waitReady: record.waitReady,
|
|
1042
|
+
listenTimeout: record.listenTimeout,
|
|
936
1043
|
autorestart: record.autorestart,
|
|
937
1044
|
restartDelay: record.restartDelay,
|
|
1045
|
+
killTimeout: record.killTimeout,
|
|
938
1046
|
maxRestarts: record.maxRestarts,
|
|
939
1047
|
minUptime: record.minUptime,
|
|
940
1048
|
watch: record.watch,
|
|
@@ -1001,6 +1109,9 @@ function findPmRecordMatch(paths, nameOrId) {
|
|
|
1001
1109
|
}
|
|
1002
1110
|
return listPmRecordMatches(paths).find((match) => match.record.name === nameOrId);
|
|
1003
1111
|
}
|
|
1112
|
+
function findPmGroupMatches(paths, baseName) {
|
|
1113
|
+
return listPmRecordMatches(paths).filter((match) => match.record.baseName === baseName).sort((left, right) => left.record.instanceIndex - right.record.instanceIndex);
|
|
1114
|
+
}
|
|
1004
1115
|
function isProcessAlive(pid) {
|
|
1005
1116
|
if (!pid || pid <= 0) {
|
|
1006
1117
|
return false;
|
|
@@ -1050,11 +1161,22 @@ function toPmAppConfig(record) {
|
|
|
1050
1161
|
runtime: record.runtime,
|
|
1051
1162
|
cwd: record.cwd,
|
|
1052
1163
|
env: record.env,
|
|
1164
|
+
proxy: record.proxy,
|
|
1165
|
+
instances: record.instances,
|
|
1053
1166
|
autorestart: record.autorestart,
|
|
1054
1167
|
restartDelay: record.restartDelay,
|
|
1168
|
+
killTimeout: record.killTimeout,
|
|
1055
1169
|
maxRestarts: record.maxRestarts,
|
|
1056
1170
|
password: record.password,
|
|
1057
1171
|
restartPolicy: record.restartPolicy,
|
|
1172
|
+
maxMemory: record.maxMemoryBytes,
|
|
1173
|
+
memoryAction: record.memoryAction,
|
|
1174
|
+
cronRestart: record.cronRestart,
|
|
1175
|
+
expBackoffRestartDelay: record.expBackoffRestartDelay,
|
|
1176
|
+
expBackoffRestartMaxDelay: record.expBackoffRestartMaxDelay,
|
|
1177
|
+
restartWindow: record.restartWindow,
|
|
1178
|
+
waitReady: record.waitReady,
|
|
1179
|
+
listenTimeout: record.listenTimeout,
|
|
1058
1180
|
minUptime: record.minUptime,
|
|
1059
1181
|
watch: record.watch,
|
|
1060
1182
|
watchPaths: record.watchPaths,
|
|
@@ -1073,11 +1195,22 @@ function toSavedPmAppConfig(app) {
|
|
|
1073
1195
|
runtime: app.runtime,
|
|
1074
1196
|
cwd: app.cwd,
|
|
1075
1197
|
env: app.env,
|
|
1198
|
+
proxy: app.proxy,
|
|
1199
|
+
instances: app.instances,
|
|
1076
1200
|
autorestart: app.autorestart,
|
|
1077
1201
|
restartDelay: app.restartDelay,
|
|
1202
|
+
killTimeout: app.killTimeout,
|
|
1078
1203
|
maxRestarts: app.maxRestarts,
|
|
1079
1204
|
password: app.password,
|
|
1080
1205
|
restartPolicy: app.restartPolicy,
|
|
1206
|
+
maxMemory: app.maxMemoryBytes,
|
|
1207
|
+
memoryAction: app.memoryAction,
|
|
1208
|
+
cronRestart: app.cronRestart,
|
|
1209
|
+
expBackoffRestartDelay: app.expBackoffRestartDelay,
|
|
1210
|
+
expBackoffRestartMaxDelay: app.expBackoffRestartMaxDelay,
|
|
1211
|
+
restartWindow: app.restartWindow,
|
|
1212
|
+
waitReady: app.waitReady,
|
|
1213
|
+
listenTimeout: app.listenTimeout,
|
|
1081
1214
|
minUptime: app.minUptime,
|
|
1082
1215
|
watch: app.watch,
|
|
1083
1216
|
watchPaths: app.watchPaths,
|
|
@@ -1089,6 +1222,170 @@ function toSavedPmAppConfig(app) {
|
|
|
1089
1222
|
|
|
1090
1223
|
// src/cli/pm/config.ts
|
|
1091
1224
|
var import_node_path4 = require("path");
|
|
1225
|
+
|
|
1226
|
+
// src/cli/pm/schedule.ts
|
|
1227
|
+
var PM_EVERY_PATTERN = /^@every\s+(\d+)(ms|s|m|h|d)$/i;
|
|
1228
|
+
function parsePositiveInteger(value, optionName, min = 0) {
|
|
1229
|
+
const parsed = Number.parseInt(value, 10);
|
|
1230
|
+
if (!Number.isFinite(parsed) || parsed < min) {
|
|
1231
|
+
throw new Error(`${optionName} must be a number >= ${min}`);
|
|
1232
|
+
}
|
|
1233
|
+
return parsed;
|
|
1234
|
+
}
|
|
1235
|
+
function fillRange(values, start, end, step, min, max, optionName) {
|
|
1236
|
+
if (!Number.isInteger(start) || !Number.isInteger(end) || start < min || end > max || start > end) {
|
|
1237
|
+
throw new Error(`${optionName} contains an invalid range: ${start}-${end}`);
|
|
1238
|
+
}
|
|
1239
|
+
if (!Number.isInteger(step) || step < 1) {
|
|
1240
|
+
throw new Error(`${optionName} contains an invalid step: ${step}`);
|
|
1241
|
+
}
|
|
1242
|
+
for (let current = start; current <= end; current += step) {
|
|
1243
|
+
values.add(current);
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1246
|
+
function parseCronSegment(segment, values, min, max, optionName) {
|
|
1247
|
+
const normalized = segment.trim();
|
|
1248
|
+
if (!normalized) {
|
|
1249
|
+
throw new Error(`${optionName} contains an empty field segment.`);
|
|
1250
|
+
}
|
|
1251
|
+
const [rangeToken, stepToken] = normalized.split("/");
|
|
1252
|
+
if (normalized.split("/").length > 2) {
|
|
1253
|
+
throw new Error(`${optionName} contains an invalid step segment: ${normalized}`);
|
|
1254
|
+
}
|
|
1255
|
+
const step = stepToken === void 0 ? 1 : parsePositiveInteger(stepToken, optionName, 1);
|
|
1256
|
+
if (rangeToken === "*") {
|
|
1257
|
+
fillRange(values, min, max, step, min, max, optionName);
|
|
1258
|
+
return;
|
|
1259
|
+
}
|
|
1260
|
+
if (rangeToken.includes("-")) {
|
|
1261
|
+
const [startToken, endToken] = rangeToken.split("-");
|
|
1262
|
+
if (!startToken || !endToken) {
|
|
1263
|
+
throw new Error(`${optionName} contains an invalid range: ${normalized}`);
|
|
1264
|
+
}
|
|
1265
|
+
const start2 = parsePositiveInteger(startToken, optionName, min);
|
|
1266
|
+
const end = parsePositiveInteger(endToken, optionName, min);
|
|
1267
|
+
fillRange(values, start2, end, step, min, max, optionName);
|
|
1268
|
+
return;
|
|
1269
|
+
}
|
|
1270
|
+
const start = parsePositiveInteger(rangeToken, optionName, min);
|
|
1271
|
+
fillRange(values, start, stepToken === void 0 ? start : max, step, min, max, optionName);
|
|
1272
|
+
}
|
|
1273
|
+
function parseCronField(token, min, max, optionName) {
|
|
1274
|
+
const normalized = token.trim();
|
|
1275
|
+
if (!normalized) {
|
|
1276
|
+
throw new Error(`${optionName} contains an empty field.`);
|
|
1277
|
+
}
|
|
1278
|
+
if (normalized === "*") {
|
|
1279
|
+
const values2 = /* @__PURE__ */ new Set();
|
|
1280
|
+
fillRange(values2, min, max, 1, min, max, optionName);
|
|
1281
|
+
return {
|
|
1282
|
+
values: values2,
|
|
1283
|
+
wildcard: true
|
|
1284
|
+
};
|
|
1285
|
+
}
|
|
1286
|
+
const values = /* @__PURE__ */ new Set();
|
|
1287
|
+
for (const segment of normalized.split(",")) {
|
|
1288
|
+
parseCronSegment(segment, values, min, max, optionName);
|
|
1289
|
+
}
|
|
1290
|
+
if (values.size === 0) {
|
|
1291
|
+
throw new Error(`${optionName} must match at least one value.`);
|
|
1292
|
+
}
|
|
1293
|
+
return {
|
|
1294
|
+
values,
|
|
1295
|
+
wildcard: false
|
|
1296
|
+
};
|
|
1297
|
+
}
|
|
1298
|
+
function matchesCronField(field, value) {
|
|
1299
|
+
if (!field) {
|
|
1300
|
+
return true;
|
|
1301
|
+
}
|
|
1302
|
+
return field.wildcard || field.values.has(value);
|
|
1303
|
+
}
|
|
1304
|
+
function matchesCronDate(schedule, candidate) {
|
|
1305
|
+
if (schedule.kind !== "cron") {
|
|
1306
|
+
return false;
|
|
1307
|
+
}
|
|
1308
|
+
const secondMatches = schedule.hasSeconds ? matchesCronField(schedule.seconds, candidate.getSeconds()) : true;
|
|
1309
|
+
const minuteMatches = matchesCronField(schedule.minutes, candidate.getMinutes());
|
|
1310
|
+
const hourMatches = matchesCronField(schedule.hours, candidate.getHours());
|
|
1311
|
+
const monthMatches = matchesCronField(schedule.months, candidate.getMonth() + 1);
|
|
1312
|
+
const dayOfMonthMatches = matchesCronField(schedule.daysOfMonth, candidate.getDate());
|
|
1313
|
+
const dayOfWeekMatches = matchesCronField(schedule.daysOfWeek, candidate.getDay());
|
|
1314
|
+
const domWildcard = schedule.daysOfMonth?.wildcard ?? true;
|
|
1315
|
+
const dowWildcard = schedule.daysOfWeek?.wildcard ?? true;
|
|
1316
|
+
const dayMatches = domWildcard && dowWildcard ? true : domWildcard ? dayOfWeekMatches : dowWildcard ? dayOfMonthMatches : dayOfMonthMatches || dayOfWeekMatches;
|
|
1317
|
+
return secondMatches && minuteMatches && hourMatches && monthMatches && dayMatches;
|
|
1318
|
+
}
|
|
1319
|
+
function parsePmRestartSchedule(expression, optionName = "--cron-restart") {
|
|
1320
|
+
const normalized = expression.trim();
|
|
1321
|
+
const everyMatch = PM_EVERY_PATTERN.exec(normalized);
|
|
1322
|
+
if (everyMatch) {
|
|
1323
|
+
const [, valueText, unit] = everyMatch;
|
|
1324
|
+
const value = parsePositiveInteger(valueText ?? "", optionName, 1);
|
|
1325
|
+
const multiplier = unit?.toLowerCase() === "ms" ? 1 : unit?.toLowerCase() === "s" ? 1e3 : unit?.toLowerCase() === "m" ? 6e4 : unit?.toLowerCase() === "h" ? 36e5 : 864e5;
|
|
1326
|
+
return {
|
|
1327
|
+
expression: normalized,
|
|
1328
|
+
kind: "every",
|
|
1329
|
+
intervalMs: value * multiplier,
|
|
1330
|
+
hasSeconds: true
|
|
1331
|
+
};
|
|
1332
|
+
}
|
|
1333
|
+
const fields = normalized.split(/\s+/).filter((field) => field.length > 0);
|
|
1334
|
+
if (fields.length !== 5 && fields.length !== 6) {
|
|
1335
|
+
throw new Error(`${optionName} must be a 5-field cron expression or @every <duration>.`);
|
|
1336
|
+
}
|
|
1337
|
+
const hasSeconds = fields.length === 6;
|
|
1338
|
+
const offset = hasSeconds ? 1 : 0;
|
|
1339
|
+
return {
|
|
1340
|
+
expression: normalized,
|
|
1341
|
+
kind: "cron",
|
|
1342
|
+
hasSeconds,
|
|
1343
|
+
seconds: hasSeconds ? parseCronField(fields[0] ?? "", 0, 59, optionName) : void 0,
|
|
1344
|
+
minutes: parseCronField(fields[0 + offset] ?? "", 0, 59, optionName),
|
|
1345
|
+
hours: parseCronField(fields[1 + offset] ?? "", 0, 23, optionName),
|
|
1346
|
+
daysOfMonth: parseCronField(fields[2 + offset] ?? "", 1, 31, optionName),
|
|
1347
|
+
months: parseCronField(fields[3 + offset] ?? "", 1, 12, optionName),
|
|
1348
|
+
daysOfWeek: parseCronField(fields[4 + offset] ?? "", 0, 6, optionName)
|
|
1349
|
+
};
|
|
1350
|
+
}
|
|
1351
|
+
function normalizePmRestartSchedule(value, optionName = "--cron-restart") {
|
|
1352
|
+
if (value === void 0 || value === null || value === "") {
|
|
1353
|
+
return void 0;
|
|
1354
|
+
}
|
|
1355
|
+
if (typeof value !== "string") {
|
|
1356
|
+
throw new Error(`${optionName} must be a cron string or @every <duration>.`);
|
|
1357
|
+
}
|
|
1358
|
+
const normalized = value.trim();
|
|
1359
|
+
if (!normalized) {
|
|
1360
|
+
throw new Error(`${optionName} must be a non-empty cron string or @every <duration>.`);
|
|
1361
|
+
}
|
|
1362
|
+
parsePmRestartSchedule(normalized, optionName);
|
|
1363
|
+
return normalized;
|
|
1364
|
+
}
|
|
1365
|
+
function resolveNextPmScheduleOccurrence(schedule, after = /* @__PURE__ */ new Date()) {
|
|
1366
|
+
if (schedule.kind === "every") {
|
|
1367
|
+
return new Date(after.getTime() + (schedule.intervalMs ?? 0));
|
|
1368
|
+
}
|
|
1369
|
+
const stepMs = schedule.hasSeconds ? 1e3 : 6e4;
|
|
1370
|
+
const candidate = new Date(after.getTime());
|
|
1371
|
+
if (schedule.hasSeconds) {
|
|
1372
|
+
candidate.setMilliseconds(0);
|
|
1373
|
+
candidate.setSeconds(candidate.getSeconds() + 1);
|
|
1374
|
+
} else {
|
|
1375
|
+
candidate.setSeconds(0, 0);
|
|
1376
|
+
candidate.setMinutes(candidate.getMinutes() + 1);
|
|
1377
|
+
}
|
|
1378
|
+
const maxIterations = schedule.hasSeconds ? 2678400 : 527040;
|
|
1379
|
+
for (let index = 0; index < maxIterations; index++) {
|
|
1380
|
+
if (matchesCronDate(schedule, candidate)) {
|
|
1381
|
+
return new Date(candidate.getTime());
|
|
1382
|
+
}
|
|
1383
|
+
candidate.setTime(candidate.getTime() + stepMs);
|
|
1384
|
+
}
|
|
1385
|
+
return void 0;
|
|
1386
|
+
}
|
|
1387
|
+
|
|
1388
|
+
// src/cli/pm/config.ts
|
|
1092
1389
|
function parsePmTarget(parsed, workspaceRoot) {
|
|
1093
1390
|
if (parsed.script) {
|
|
1094
1391
|
return { script: parsed.script };
|
|
@@ -1131,6 +1428,20 @@ function defaultProcessName(base, explicitName) {
|
|
|
1131
1428
|
}
|
|
1132
1429
|
return "process";
|
|
1133
1430
|
}
|
|
1431
|
+
function formatPmInstanceName(baseName, instanceIndex) {
|
|
1432
|
+
return instanceIndex <= 1 ? baseName : `${baseName}:${instanceIndex}`;
|
|
1433
|
+
}
|
|
1434
|
+
function expandPmInstanceDefinitions(definition, targetInstances = definition.instances) {
|
|
1435
|
+
return Array.from({ length: targetInstances }, (_, index) => {
|
|
1436
|
+
const instanceIndex = index + 1;
|
|
1437
|
+
return {
|
|
1438
|
+
...definition,
|
|
1439
|
+
name: formatPmInstanceName(definition.baseName, instanceIndex),
|
|
1440
|
+
instanceIndex,
|
|
1441
|
+
instances: targetInstances
|
|
1442
|
+
};
|
|
1443
|
+
});
|
|
1444
|
+
}
|
|
1134
1445
|
function countDefinedTargets(app) {
|
|
1135
1446
|
return [app.script, app.file, app.wapk].filter(Boolean).length;
|
|
1136
1447
|
}
|
|
@@ -1168,8 +1479,25 @@ function resolvePmAppDefinition(base, parsed, workspaceRoot, source) {
|
|
|
1168
1479
|
throw new Error("A pm app must define exactly one of script, file, or wapk.");
|
|
1169
1480
|
}
|
|
1170
1481
|
const name = defaultProcessName({ script, file, wapk }, parsed.name ?? base?.name);
|
|
1482
|
+
const instances = parsed.instances ?? base?.instances ?? 1;
|
|
1483
|
+
if (!Number.isInteger(instances) || instances < 1) {
|
|
1484
|
+
throw new Error("pm instances must be a number >= 1.");
|
|
1485
|
+
}
|
|
1486
|
+
const proxy = normalizePmProxyConfig(
|
|
1487
|
+
parsed.proxy ? { ...base?.proxy ?? {}, ...parsed.proxy } : base?.proxy,
|
|
1488
|
+
parsed.proxy ? "pm proxy" : "pm.apps[].proxy"
|
|
1489
|
+
);
|
|
1490
|
+
if (proxy?.strategy === "inherit" && instances > 1) {
|
|
1491
|
+
throw new Error('pm proxy strategy "inherit" currently supports only one managed instance per app.');
|
|
1492
|
+
}
|
|
1171
1493
|
const mergedWapkRun = mergePmWapkRunConfig(base?.wapkRun, parsed.wapkRun);
|
|
1172
1494
|
const runtime2 = normalizePmRuntime(parsed.runtime ?? mergedWapkRun?.runtime ?? base?.runtime, "--runtime");
|
|
1495
|
+
const maxMemoryBytes = parsed.maxMemoryBytes ?? normalizePmMemoryLimit(base?.maxMemory, "pm.apps[].maxMemory");
|
|
1496
|
+
const memoryAction = parsed.memoryAction ?? normalizePmMemoryAction(base?.memoryAction, "pm.apps[].memoryAction") ?? "restart";
|
|
1497
|
+
const cronRestart = parsed.cronRestart ?? normalizePmRestartSchedule(base?.cronRestart, "pm.apps[].cronRestart");
|
|
1498
|
+
const expBackoffRestartDelay = parsed.expBackoffRestartDelay ?? (base?.expBackoffRestartDelay === void 0 ? void 0 : normalizeIntegerOption(String(base.expBackoffRestartDelay), "pm.apps[].expBackoffRestartDelay", 1));
|
|
1499
|
+
const expBackoffRestartMaxDelay = parsed.expBackoffRestartMaxDelay ?? (base?.expBackoffRestartMaxDelay === void 0 ? void 0 : normalizeIntegerOption(String(base.expBackoffRestartMaxDelay), "pm.apps[].expBackoffRestartMaxDelay", 1));
|
|
1500
|
+
const restartWindow = parsed.restartWindow ?? (base?.restartWindow === void 0 ? void 0 : normalizeIntegerOption(String(base.restartWindow), "pm.apps[].restartWindow", 1));
|
|
1173
1501
|
let restartPolicy = normalizePmRestartPolicy(parsed.restartPolicy ?? base?.restartPolicy, "--restart-policy") ?? (base?.autorestart ?? true ? "always" : "never");
|
|
1174
1502
|
if (parsed.autorestart === false) {
|
|
1175
1503
|
restartPolicy = "never";
|
|
@@ -1189,6 +1517,7 @@ function resolvePmAppDefinition(base, parsed, workspaceRoot, source) {
|
|
|
1189
1517
|
timeout: parsed.healthCheckTimeout,
|
|
1190
1518
|
maxFailures: parsed.healthCheckMaxFailures
|
|
1191
1519
|
} : base?.healthCheck);
|
|
1520
|
+
const waitReady = parsed.waitReady ?? base?.waitReady ?? false;
|
|
1192
1521
|
const password = parsed.password ?? mergedWapkRun?.password ?? base?.password;
|
|
1193
1522
|
const wapkRun = stripPmWapkSourceFromRunConfig(mergedWapkRun);
|
|
1194
1523
|
if (password && !wapk) {
|
|
@@ -1197,8 +1526,14 @@ function resolvePmAppDefinition(base, parsed, workspaceRoot, source) {
|
|
|
1197
1526
|
if (wapkRun && !wapk) {
|
|
1198
1527
|
throw new Error("WAPK run options are only supported when starting a WAPK app.");
|
|
1199
1528
|
}
|
|
1529
|
+
if (waitReady && !healthCheck) {
|
|
1530
|
+
throw new Error("--wait-ready requires --health-url or pm.apps[].healthCheck.");
|
|
1531
|
+
}
|
|
1200
1532
|
return {
|
|
1201
|
-
name,
|
|
1533
|
+
name: formatPmInstanceName(name, 1),
|
|
1534
|
+
baseName: name,
|
|
1535
|
+
instanceIndex: 1,
|
|
1536
|
+
instances,
|
|
1202
1537
|
type: script ? "script" : wapk ? "wapk" : "file",
|
|
1203
1538
|
source,
|
|
1204
1539
|
cwd: resolvedCwd,
|
|
@@ -1213,9 +1548,19 @@ function resolvePmAppDefinition(base, parsed, workspaceRoot, source) {
|
|
|
1213
1548
|
wapkRun,
|
|
1214
1549
|
autorestart,
|
|
1215
1550
|
restartDelay: parsed.restartDelay ?? base?.restartDelay ?? DEFAULT_RESTART_DELAY,
|
|
1551
|
+
proxy,
|
|
1552
|
+
killTimeout: parsed.killTimeout ?? base?.killTimeout ?? DEFAULT_PM_KILL_TIMEOUT,
|
|
1216
1553
|
maxRestarts: parsed.maxRestarts ?? base?.maxRestarts ?? DEFAULT_MAX_RESTARTS,
|
|
1217
1554
|
password,
|
|
1218
1555
|
restartPolicy,
|
|
1556
|
+
maxMemoryBytes,
|
|
1557
|
+
memoryAction,
|
|
1558
|
+
cronRestart,
|
|
1559
|
+
expBackoffRestartDelay,
|
|
1560
|
+
expBackoffRestartMaxDelay,
|
|
1561
|
+
restartWindow,
|
|
1562
|
+
waitReady,
|
|
1563
|
+
listenTimeout: parsed.listenTimeout ?? base?.listenTimeout ?? DEFAULT_PM_LISTEN_TIMEOUT,
|
|
1219
1564
|
minUptime: parsed.minUptime ?? base?.minUptime ?? DEFAULT_MIN_UPTIME,
|
|
1220
1565
|
watch: watch2,
|
|
1221
1566
|
watchPaths: watch2 ? normalizeResolvedWatchPaths(configuredWatchPaths, resolvedCwd, script ? "script" : wapk ? "wapk" : "file", file, wapk) : [],
|
|
@@ -1227,16 +1572,17 @@ function resolvePmAppDefinition(base, parsed, workspaceRoot, source) {
|
|
|
1227
1572
|
function resolvePmStartDefinitions(parsed, config, workspaceRoot) {
|
|
1228
1573
|
const configApps = getConfiguredPmApps(config);
|
|
1229
1574
|
const selection = resolveStartSelection(configApps, parsed, workspaceRoot);
|
|
1575
|
+
const expandDefinitions = (definitions) => definitions.flatMap((definition) => expandPmInstanceDefinitions(definition));
|
|
1230
1576
|
if (selection.startAll) {
|
|
1231
1577
|
if (configApps.length === 0) {
|
|
1232
1578
|
throw new Error("No pm apps configured in elit.config.* and no start target was provided.");
|
|
1233
1579
|
}
|
|
1234
|
-
return configApps.map((app) => resolvePmAppDefinition(app, { ...parsed, name: app.name }, workspaceRoot, "config"));
|
|
1580
|
+
return expandDefinitions(configApps.map((app) => resolvePmAppDefinition(app, { ...parsed, name: app.name }, workspaceRoot, "config")));
|
|
1235
1581
|
}
|
|
1236
1582
|
if (selection.selected) {
|
|
1237
|
-
return [resolvePmAppDefinition(selection.selected, parsed, workspaceRoot, "config")];
|
|
1583
|
+
return expandDefinitions([resolvePmAppDefinition(selection.selected, parsed, workspaceRoot, "config")]);
|
|
1238
1584
|
}
|
|
1239
|
-
return [resolvePmAppDefinition(void 0, parsed, workspaceRoot, "cli")];
|
|
1585
|
+
return expandDefinitions([resolvePmAppDefinition(void 0, parsed, workspaceRoot, "cli")]);
|
|
1240
1586
|
}
|
|
1241
1587
|
function parsePmStartArgs(args) {
|
|
1242
1588
|
const parsed = {
|
|
@@ -1309,6 +1655,39 @@ function parsePmStartArgs(args) {
|
|
|
1309
1655
|
parsed.env[key] = value;
|
|
1310
1656
|
break;
|
|
1311
1657
|
}
|
|
1658
|
+
case "--instances":
|
|
1659
|
+
parsed.instances = normalizeIntegerOption(readRequiredValue(args, ++index, "--instances"), "--instances", 1);
|
|
1660
|
+
break;
|
|
1661
|
+
case "--proxy-port":
|
|
1662
|
+
parsed.proxy = {
|
|
1663
|
+
...parsed.proxy,
|
|
1664
|
+
port: normalizeIntegerOption(readRequiredValue(args, ++index, "--proxy-port"), "--proxy-port", 1)
|
|
1665
|
+
};
|
|
1666
|
+
break;
|
|
1667
|
+
case "--proxy-strategy":
|
|
1668
|
+
parsed.proxy = {
|
|
1669
|
+
...parsed.proxy,
|
|
1670
|
+
strategy: normalizePmProxyStrategy(readRequiredValue(args, ++index, "--proxy-strategy"), "--proxy-strategy")
|
|
1671
|
+
};
|
|
1672
|
+
break;
|
|
1673
|
+
case "--proxy-host":
|
|
1674
|
+
parsed.proxy = {
|
|
1675
|
+
...parsed.proxy,
|
|
1676
|
+
host: readRequiredValue(args, ++index, "--proxy-host")
|
|
1677
|
+
};
|
|
1678
|
+
break;
|
|
1679
|
+
case "--proxy-target-host":
|
|
1680
|
+
parsed.proxy = {
|
|
1681
|
+
...parsed.proxy,
|
|
1682
|
+
targetHost: readRequiredValue(args, ++index, "--proxy-target-host")
|
|
1683
|
+
};
|
|
1684
|
+
break;
|
|
1685
|
+
case "--proxy-env":
|
|
1686
|
+
parsed.proxy = {
|
|
1687
|
+
...parsed.proxy,
|
|
1688
|
+
envVar: readRequiredValue(args, ++index, "--proxy-env")
|
|
1689
|
+
};
|
|
1690
|
+
break;
|
|
1312
1691
|
case "--password":
|
|
1313
1692
|
parsed.password = readRequiredValue(args, ++index, "--password");
|
|
1314
1693
|
break;
|
|
@@ -1359,6 +1738,30 @@ function parsePmStartArgs(args) {
|
|
|
1359
1738
|
case "--restart-policy":
|
|
1360
1739
|
parsed.restartPolicy = normalizePmRestartPolicy(readRequiredValue(args, ++index, "--restart-policy"));
|
|
1361
1740
|
break;
|
|
1741
|
+
case "--max-memory":
|
|
1742
|
+
parsed.maxMemoryBytes = normalizePmMemoryLimit(readRequiredValue(args, ++index, "--max-memory"), "--max-memory");
|
|
1743
|
+
break;
|
|
1744
|
+
case "--memory-action":
|
|
1745
|
+
parsed.memoryAction = normalizePmMemoryAction(readRequiredValue(args, ++index, "--memory-action"), "--memory-action");
|
|
1746
|
+
break;
|
|
1747
|
+
case "--cron-restart":
|
|
1748
|
+
parsed.cronRestart = normalizePmRestartSchedule(readRequiredValue(args, ++index, "--cron-restart"), "--cron-restart");
|
|
1749
|
+
break;
|
|
1750
|
+
case "--exp-backoff-restart-delay":
|
|
1751
|
+
parsed.expBackoffRestartDelay = normalizeIntegerOption(readRequiredValue(args, ++index, "--exp-backoff-restart-delay"), "--exp-backoff-restart-delay", 1);
|
|
1752
|
+
break;
|
|
1753
|
+
case "--exp-backoff-restart-max-delay":
|
|
1754
|
+
parsed.expBackoffRestartMaxDelay = normalizeIntegerOption(readRequiredValue(args, ++index, "--exp-backoff-restart-max-delay"), "--exp-backoff-restart-max-delay", 1);
|
|
1755
|
+
break;
|
|
1756
|
+
case "--restart-window":
|
|
1757
|
+
parsed.restartWindow = normalizeIntegerOption(readRequiredValue(args, ++index, "--restart-window"), "--restart-window", 1);
|
|
1758
|
+
break;
|
|
1759
|
+
case "--wait-ready":
|
|
1760
|
+
parsed.waitReady = true;
|
|
1761
|
+
break;
|
|
1762
|
+
case "--listen-timeout":
|
|
1763
|
+
parsed.listenTimeout = normalizeIntegerOption(readRequiredValue(args, ++index, "--listen-timeout"), "--listen-timeout", 1);
|
|
1764
|
+
break;
|
|
1362
1765
|
case "--min-uptime":
|
|
1363
1766
|
parsed.minUptime = normalizeIntegerOption(readRequiredValue(args, ++index, "--min-uptime"), "--min-uptime");
|
|
1364
1767
|
break;
|
|
@@ -1398,6 +1801,9 @@ function parsePmStartArgs(args) {
|
|
|
1398
1801
|
case "--restart-delay":
|
|
1399
1802
|
parsed.restartDelay = normalizeIntegerOption(readRequiredValue(args, ++index, "--restart-delay"), "--restart-delay");
|
|
1400
1803
|
break;
|
|
1804
|
+
case "--kill-timeout":
|
|
1805
|
+
parsed.killTimeout = normalizeIntegerOption(readRequiredValue(args, ++index, "--kill-timeout"), "--kill-timeout", 1);
|
|
1806
|
+
break;
|
|
1401
1807
|
case "--max-restarts":
|
|
1402
1808
|
parsed.maxRestarts = normalizeIntegerOption(readRequiredValue(args, ++index, "--max-restarts"), "--max-restarts");
|
|
1403
1809
|
break;
|
|
@@ -1439,8 +1845,15 @@ function printPmHelp() {
|
|
|
1439
1845
|
" elit pm start my-app",
|
|
1440
1846
|
" elit pm start",
|
|
1441
1847
|
" elit pm list",
|
|
1848
|
+
" elit pm list --json",
|
|
1849
|
+
" elit pm show <name>",
|
|
1850
|
+
" elit pm describe <name> --json",
|
|
1442
1851
|
" elit pm stop <name|all>",
|
|
1443
1852
|
" elit pm restart <name|all>",
|
|
1853
|
+
" elit pm reload <name|all>",
|
|
1854
|
+
" elit pm scale <name> <count>",
|
|
1855
|
+
" elit pm reset <name|all>",
|
|
1856
|
+
" elit pm send-signal <signal> <name|all>",
|
|
1444
1857
|
" elit pm delete <name|all>",
|
|
1445
1858
|
" elit pm save",
|
|
1446
1859
|
" elit pm resurrect",
|
|
@@ -1456,6 +1869,12 @@ function printPmHelp() {
|
|
|
1456
1869
|
" --google-drive-shared-drive Forward supportsAllDrives=true for shared drives",
|
|
1457
1870
|
" --runtime, -r <name> Runtime override: node, bun, deno",
|
|
1458
1871
|
" --name, -n <name> Process name used by list/stop/restart",
|
|
1872
|
+
" --instances <count> Start multiple managed instances for the same app name",
|
|
1873
|
+
" --proxy-port <port> Own a public HTTP port through a PM proxy for single-instance reload handoff",
|
|
1874
|
+
" --proxy-strategy <mode> Public socket mode: proxy or inherit (default: proxy)",
|
|
1875
|
+
" --proxy-host <host> Public host bound by the PM proxy (default: 0.0.0.0)",
|
|
1876
|
+
" --proxy-target-host <host> Internal host used for upstream child traffic (default: 127.0.0.1)",
|
|
1877
|
+
" --proxy-env <name> Env var populated with the child private port (default: PORT)",
|
|
1459
1878
|
" --cwd <dir> Working directory for the managed process",
|
|
1460
1879
|
" --env KEY=VALUE Add or override an environment variable",
|
|
1461
1880
|
" --password <value> Password for locked WAPK archives",
|
|
@@ -1467,6 +1886,14 @@ function printPmHelp() {
|
|
|
1467
1886
|
" --no-archive-watch Disable archive-source read sync for WAPK apps",
|
|
1468
1887
|
" --archive-sync-interval <ms> Forward WAPK archive read-sync interval (>= 50ms)",
|
|
1469
1888
|
" --restart-policy <mode> Restart policy: always, on-failure, never",
|
|
1889
|
+
" --max-memory <bytes|size> Trigger a memory action after a limit like 268435456 or 256M",
|
|
1890
|
+
" --memory-action <mode> Action on max-memory: restart, stop",
|
|
1891
|
+
" --cron-restart <expr> Restart on a cron schedule or @every <duration>",
|
|
1892
|
+
" --exp-backoff-restart-delay <ms> Exponential unstable-restart backoff base delay",
|
|
1893
|
+
" --exp-backoff-restart-max-delay <ms> Maximum unstable-restart backoff delay (default 15000)",
|
|
1894
|
+
" --restart-window <ms> Rolling time window used when counting restart attempts",
|
|
1895
|
+
" --wait-ready Keep the process in starting state until its health check succeeds",
|
|
1896
|
+
" --listen-timeout <ms> Startup wait limit when --wait-ready is enabled (default 3000)",
|
|
1470
1897
|
" --min-uptime <ms> Reset crash counter after this healthy uptime",
|
|
1471
1898
|
" --watch Restart when watched files change",
|
|
1472
1899
|
" --watch-path <path> Add a file or directory to watch",
|
|
@@ -1479,6 +1906,7 @@ function printPmHelp() {
|
|
|
1479
1906
|
" --health-max-failures <n> Consecutive failures before restart (default 3)",
|
|
1480
1907
|
" --no-autorestart Disable automatic restart",
|
|
1481
1908
|
" --restart-delay <ms> Delay between restart attempts (default 1000)",
|
|
1909
|
+
" --kill-timeout <ms> Grace period before force-killing a stop or restart (default 5000)",
|
|
1482
1910
|
" --max-restarts <count> Maximum restart attempts (default 10)",
|
|
1483
1911
|
"",
|
|
1484
1912
|
"Config:",
|
|
@@ -1502,6 +1930,17 @@ function printPmHelp() {
|
|
|
1502
1930
|
" - elit pm save persists running apps to pm.dumpFile or ./.elit/pm/dump.json.",
|
|
1503
1931
|
" - elit pm resurrect restarts whatever was last saved by elit pm save.",
|
|
1504
1932
|
" - elit pm start <name> starts a configured app by name.",
|
|
1933
|
+
" - elit pm reload <name|all> performs a rolling stop/start and waits for each instance to return online before continuing.",
|
|
1934
|
+
" - elit pm scale <name> <count> changes the number of managed instances for a running app group.",
|
|
1935
|
+
" - elit pm reset <name|all> clears restart count, last exit code, and saved error metadata.",
|
|
1936
|
+
" - elit pm send-signal <signal> <name|all> forwards a POSIX-style signal such as SIGUSR2 or TERM.",
|
|
1937
|
+
" - maxMemory works with memoryAction=restart|stop, cronRestart accepts cron or @every schedules, expBackoffRestartDelay doubles unstable restart delays, expBackoffRestartMaxDelay caps them, and restartWindow limits how long restart attempts keep counting toward maxRestarts.",
|
|
1938
|
+
" - killTimeout controls how long PM waits before force-killing an app that ignores stop or restart requests.",
|
|
1939
|
+
" - waitReady uses the configured health check as a startup gate and errors if it never becomes healthy within listenTimeout.",
|
|
1940
|
+
" - elit pm list shows live cpu, memory, and uptime columns when the child process is running.",
|
|
1941
|
+
" - elit pm list --json and elit pm jlist print machine-readable process state.",
|
|
1942
|
+
" - elit pm list --json, elit pm show --json, and elit pm describe --json include liveMetrics when available.",
|
|
1943
|
+
" - elit pm show <name> and elit pm describe <name> expose the full saved process record.",
|
|
1505
1944
|
" - TypeScript files with runtime node require tsx, otherwise use --runtime bun.",
|
|
1506
1945
|
" - WAPK processes are executed through elit wapk run inside the manager.",
|
|
1507
1946
|
" - WAPK PM apps can use local archives, gdrive://<fileId>, or pm.apps[].wapkRun.googleDrive.",
|
|
@@ -1557,16 +1996,94 @@ function resolveTsxExecutable(cwd) {
|
|
|
1557
1996
|
const globalCommand = process.platform === "win32" ? "tsx.cmd" : "tsx";
|
|
1558
1997
|
return commandExists(globalCommand) ? globalCommand : void 0;
|
|
1559
1998
|
}
|
|
1999
|
+
function resolvePmNodeSharedListenerBootstrapPath() {
|
|
2000
|
+
const cliEntry = process.argv[1];
|
|
2001
|
+
const candidates = [
|
|
2002
|
+
(0, import_node_path5.join)(__dirname, "node-shared-listener-bootstrap.cjs"),
|
|
2003
|
+
(0, import_node_path5.join)(__dirname, "..", "..", "..", "dist", "pm-node-shared-listener-bootstrap.cjs")
|
|
2004
|
+
];
|
|
2005
|
+
if (cliEntry) {
|
|
2006
|
+
const resolvedCliEntry = (0, import_node_path5.resolve)(cliEntry);
|
|
2007
|
+
const baseDir = (0, import_node_path5.dirname)(resolvedCliEntry);
|
|
2008
|
+
candidates.push(
|
|
2009
|
+
(0, import_node_path5.join)(baseDir, "cli", "pm", "node-shared-listener-bootstrap.cjs"),
|
|
2010
|
+
(0, import_node_path5.join)(baseDir, "pm-node-shared-listener-bootstrap.cjs")
|
|
2011
|
+
);
|
|
2012
|
+
}
|
|
2013
|
+
return candidates.find((candidate) => (0, import_node_fs3.existsSync)(candidate));
|
|
2014
|
+
}
|
|
2015
|
+
function appendNodeOption(existing, option) {
|
|
2016
|
+
return existing && existing.trim().length > 0 ? `${existing.trim()} ${option}` : option;
|
|
2017
|
+
}
|
|
2018
|
+
function supportsPmInheritedListener(record, runtime2) {
|
|
2019
|
+
return (record.proxy?.strategy ?? DEFAULT_PM_PROXY_STRATEGY) === "inherit" && record.type === "file" && runtime2 === "node" && !isTypescriptFile(record.file);
|
|
2020
|
+
}
|
|
1560
2021
|
function inferRuntimeFromFile(filePath) {
|
|
1561
2022
|
if (isTypescriptFile(filePath) && commandExists("bun")) {
|
|
1562
2023
|
return "bun";
|
|
1563
2024
|
}
|
|
1564
2025
|
return "node";
|
|
1565
2026
|
}
|
|
2027
|
+
function sampleWindowsPmProcessMetrics(pid) {
|
|
2028
|
+
const script = [
|
|
2029
|
+
'$ErrorActionPreference = "Stop"',
|
|
2030
|
+
`$sample = Get-CimInstance -ClassName Win32_PerfFormattedData_PerfProc_Process -Filter "IDProcess = ${pid}" | Select-Object -First 1`,
|
|
2031
|
+
"if (-not $sample) { exit 2 }",
|
|
2032
|
+
"$cpu = [double]$sample.PercentProcessorTime",
|
|
2033
|
+
`$memory = if ($sample.PSObject.Properties.Match('WorkingSetPrivate').Count -gt 0) { [int64]$sample.WorkingSetPrivate } else { [int64](Get-Process -Id ${pid} -ErrorAction Stop).WorkingSet64 }`,
|
|
2034
|
+
'Write-Output ($cpu.ToString([System.Globalization.CultureInfo]::InvariantCulture) + "," + $memory)'
|
|
2035
|
+
].join("; ");
|
|
2036
|
+
const result = (0, import_node_child_process.spawnSync)(
|
|
2037
|
+
"powershell.exe",
|
|
2038
|
+
["-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-Command", script],
|
|
2039
|
+
{
|
|
2040
|
+
encoding: "utf8",
|
|
2041
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
2042
|
+
windowsHide: true
|
|
2043
|
+
}
|
|
2044
|
+
);
|
|
2045
|
+
if (result.error || result.status !== 0) {
|
|
2046
|
+
return {};
|
|
2047
|
+
}
|
|
2048
|
+
const [cpuText, memoryText] = result.stdout.trim().split(",");
|
|
2049
|
+
const cpuPercent = Number.parseFloat((cpuText ?? "").replace(",", "."));
|
|
2050
|
+
const memoryRssBytes = Number.parseInt(memoryText ?? "", 10);
|
|
2051
|
+
return {
|
|
2052
|
+
cpuPercent: Number.isFinite(cpuPercent) ? cpuPercent : void 0,
|
|
2053
|
+
memoryRssBytes: Number.isFinite(memoryRssBytes) ? memoryRssBytes : void 0
|
|
2054
|
+
};
|
|
2055
|
+
}
|
|
2056
|
+
function samplePosixPmProcessMetrics(pid) {
|
|
2057
|
+
const result = (0, import_node_child_process.spawnSync)(
|
|
2058
|
+
"ps",
|
|
2059
|
+
["-p", String(pid), "-o", "%cpu=", "-o", "rss="],
|
|
2060
|
+
{
|
|
2061
|
+
encoding: "utf8",
|
|
2062
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
2063
|
+
windowsHide: true
|
|
2064
|
+
}
|
|
2065
|
+
);
|
|
2066
|
+
if (result.error || result.status !== 0) {
|
|
2067
|
+
return {};
|
|
2068
|
+
}
|
|
2069
|
+
const [cpuText, memoryText] = result.stdout.trim().split(/\s+/, 2);
|
|
2070
|
+
const cpuPercent = Number.parseFloat((cpuText ?? "").replace(",", "."));
|
|
2071
|
+
const rssKilobytes = Number.parseInt(memoryText ?? "", 10);
|
|
2072
|
+
return {
|
|
2073
|
+
cpuPercent: Number.isFinite(cpuPercent) ? cpuPercent : void 0,
|
|
2074
|
+
memoryRssBytes: Number.isFinite(rssKilobytes) ? rssKilobytes * 1024 : void 0
|
|
2075
|
+
};
|
|
2076
|
+
}
|
|
2077
|
+
function samplePmProcessMetrics(pid) {
|
|
2078
|
+
return process.platform === "win32" ? sampleWindowsPmProcessMetrics(pid) : samplePosixPmProcessMetrics(pid);
|
|
2079
|
+
}
|
|
1566
2080
|
function isPmOnlineWapkRecord(record) {
|
|
1567
2081
|
return record.type === "wapk" && isPmWapkOnlineRunConfig(record.wapkRun);
|
|
1568
2082
|
}
|
|
1569
2083
|
function buildPmCommand(record) {
|
|
2084
|
+
if ((record.proxy?.strategy ?? DEFAULT_PM_PROXY_STRATEGY) === "inherit" && record.type !== "file") {
|
|
2085
|
+
throw new Error('pm proxy strategy "inherit" currently supports only Node .js/.mjs/.cjs file targets.');
|
|
2086
|
+
}
|
|
1570
2087
|
if (record.type === "script") {
|
|
1571
2088
|
return {
|
|
1572
2089
|
command: record.script,
|
|
@@ -1638,9 +2155,23 @@ function buildPmCommand(record) {
|
|
|
1638
2155
|
}
|
|
1639
2156
|
const executable = preferCurrentExecutable("node");
|
|
1640
2157
|
ensureCommandAvailable(executable, "Node.js runtime");
|
|
2158
|
+
const wantsInheritedListener = (record.proxy?.strategy ?? DEFAULT_PM_PROXY_STRATEGY) === "inherit";
|
|
2159
|
+
if (wantsInheritedListener && !supportsPmInheritedListener(record, runtime2)) {
|
|
2160
|
+
throw new Error('pm proxy strategy "inherit" currently supports only Node .js/.mjs/.cjs file targets.');
|
|
2161
|
+
}
|
|
2162
|
+
const bootstrapPath = wantsInheritedListener ? resolvePmNodeSharedListenerBootstrapPath() : void 0;
|
|
2163
|
+
if (wantsInheritedListener && !bootstrapPath) {
|
|
2164
|
+
throw new Error("Unable to resolve the PM shared-listener bootstrap module.");
|
|
2165
|
+
}
|
|
1641
2166
|
return {
|
|
1642
2167
|
command: executable,
|
|
1643
2168
|
args: [record.file],
|
|
2169
|
+
env: bootstrapPath ? {
|
|
2170
|
+
NODE_OPTIONS: appendNodeOption(process.env.NODE_OPTIONS, `--require=${bootstrapPath}`),
|
|
2171
|
+
ELIT_PM_LISTEN_MODE: "ipc",
|
|
2172
|
+
ELIT_PM_PUBLIC_PORT: String(record.proxy?.port ?? record.env.PORT ?? "")
|
|
2173
|
+
} : void 0,
|
|
2174
|
+
ipc: Boolean(bootstrapPath),
|
|
1644
2175
|
runtime: runtime2,
|
|
1645
2176
|
preview: `${(0, import_node_path5.basename)(executable)} ${quoteCommandSegment(record.file)}`
|
|
1646
2177
|
};
|
|
@@ -1652,20 +2183,33 @@ function createRecordFromDefinition(definition, paths, existing) {
|
|
|
1652
2183
|
return {
|
|
1653
2184
|
id,
|
|
1654
2185
|
name: definition.name,
|
|
2186
|
+
baseName: definition.baseName,
|
|
2187
|
+
instanceIndex: definition.instanceIndex,
|
|
2188
|
+
instances: definition.instances,
|
|
1655
2189
|
type: definition.type,
|
|
1656
2190
|
source: definition.source,
|
|
1657
2191
|
cwd: definition.cwd,
|
|
1658
2192
|
runtime: definition.runtime,
|
|
1659
2193
|
env: definition.env,
|
|
2194
|
+
proxy: definition.proxy,
|
|
1660
2195
|
script: definition.script,
|
|
1661
2196
|
file: definition.file,
|
|
1662
2197
|
wapk: definition.wapk,
|
|
1663
2198
|
wapkRun: definition.wapkRun,
|
|
1664
2199
|
autorestart: definition.autorestart,
|
|
1665
2200
|
restartDelay: definition.restartDelay,
|
|
2201
|
+
killTimeout: definition.killTimeout,
|
|
1666
2202
|
maxRestarts: definition.maxRestarts,
|
|
1667
2203
|
password: definition.password,
|
|
1668
2204
|
restartPolicy: definition.restartPolicy,
|
|
2205
|
+
maxMemoryBytes: definition.maxMemoryBytes,
|
|
2206
|
+
memoryAction: definition.memoryAction,
|
|
2207
|
+
cronRestart: definition.cronRestart,
|
|
2208
|
+
expBackoffRestartDelay: definition.expBackoffRestartDelay,
|
|
2209
|
+
expBackoffRestartMaxDelay: definition.expBackoffRestartMaxDelay,
|
|
2210
|
+
restartWindow: definition.restartWindow,
|
|
2211
|
+
waitReady: definition.waitReady,
|
|
2212
|
+
listenTimeout: definition.listenTimeout,
|
|
1669
2213
|
minUptime: definition.minUptime,
|
|
1670
2214
|
watch: definition.watch,
|
|
1671
2215
|
watchPaths: definition.watchPaths,
|
|
@@ -1681,16 +2225,20 @@ function createRecordFromDefinition(definition, paths, existing) {
|
|
|
1681
2225
|
stoppedAt: void 0,
|
|
1682
2226
|
runnerPid: void 0,
|
|
1683
2227
|
childPid: void 0,
|
|
2228
|
+
proxyTargetPort: void 0,
|
|
1684
2229
|
restartCount: existing?.restartCount ?? 0,
|
|
2230
|
+
reloadRequestedAt: void 0,
|
|
2231
|
+
lastRestartAt: existing?.lastRestartAt,
|
|
1685
2232
|
lastExitCode: existing?.lastExitCode,
|
|
1686
2233
|
error: void 0,
|
|
2234
|
+
proxyReadyAt: void 0,
|
|
1687
2235
|
logFiles: existing?.logFiles ?? {
|
|
1688
2236
|
out: (0, import_node_path5.join)(paths.logsDir, `${id}.out.log`),
|
|
1689
2237
|
err: (0, import_node_path5.join)(paths.logsDir, `${id}.err.log`)
|
|
1690
2238
|
}
|
|
1691
2239
|
};
|
|
1692
2240
|
}
|
|
1693
|
-
function terminateProcessTree(pid) {
|
|
2241
|
+
function terminateProcessTree(pid, options) {
|
|
1694
2242
|
if (process.platform === "win32") {
|
|
1695
2243
|
const result = (0, import_node_child_process.spawnSync)("taskkill", ["/PID", String(pid), "/T", "/F"], {
|
|
1696
2244
|
stdio: "ignore",
|
|
@@ -1702,7 +2250,17 @@ function terminateProcessTree(pid) {
|
|
|
1702
2250
|
return;
|
|
1703
2251
|
}
|
|
1704
2252
|
try {
|
|
1705
|
-
process.kill(pid, "SIGTERM");
|
|
2253
|
+
process.kill(pid, options?.force ? "SIGKILL" : "SIGTERM");
|
|
2254
|
+
} catch (error) {
|
|
2255
|
+
const code = error.code;
|
|
2256
|
+
if (code !== "ESRCH") {
|
|
2257
|
+
throw error;
|
|
2258
|
+
}
|
|
2259
|
+
}
|
|
2260
|
+
}
|
|
2261
|
+
function sendPmSignal(pid, signal) {
|
|
2262
|
+
try {
|
|
2263
|
+
process.kill(pid, signal);
|
|
1706
2264
|
} catch (error) {
|
|
1707
2265
|
const code = error.code;
|
|
1708
2266
|
if (code !== "ESRCH") {
|
|
@@ -1754,6 +2312,7 @@ async function startManagedProcess(definition, paths) {
|
|
|
1754
2312
|
|
|
1755
2313
|
// src/cli/pm/runner.ts
|
|
1756
2314
|
var import_node_child_process2 = require("child_process");
|
|
2315
|
+
var import_node_http2 = require("http");
|
|
1757
2316
|
var import_node_fs4 = require("fs");
|
|
1758
2317
|
var import_node_os = require("os");
|
|
1759
2318
|
var import_node_path6 = require("path");
|
|
@@ -1987,13 +2546,13 @@ var win32 = createPathOps(true);
|
|
|
1987
2546
|
function join5(...paths) {
|
|
1988
2547
|
return joinPaths(paths, isWindows);
|
|
1989
2548
|
}
|
|
1990
|
-
function
|
|
2549
|
+
function resolve5(...paths) {
|
|
1991
2550
|
return resolvePaths(paths, isWindows);
|
|
1992
2551
|
}
|
|
1993
2552
|
function relative(from, to) {
|
|
1994
2553
|
return relativePath(from, to, isWindows);
|
|
1995
2554
|
}
|
|
1996
|
-
function
|
|
2555
|
+
function dirname3(path) {
|
|
1997
2556
|
return getDirname(path, isWindows);
|
|
1998
2557
|
}
|
|
1999
2558
|
|
|
@@ -2031,14 +2590,14 @@ function getBaseDirectory(pattern) {
|
|
|
2031
2590
|
}
|
|
2032
2591
|
if (normalizedPattern && existsSync4(normalizedPattern)) {
|
|
2033
2592
|
try {
|
|
2034
|
-
return statSync2(normalizedPattern).isDirectory() ? normalizedPattern : normalizePath2(
|
|
2593
|
+
return statSync2(normalizedPattern).isDirectory() ? normalizedPattern : normalizePath2(dirname3(normalizedPattern)) || ".";
|
|
2035
2594
|
} catch {
|
|
2036
|
-
return normalizePath2(
|
|
2595
|
+
return normalizePath2(dirname3(normalizedPattern)) || ".";
|
|
2037
2596
|
}
|
|
2038
2597
|
}
|
|
2039
2598
|
const lastSegment = parts[parts.length - 1] || "";
|
|
2040
2599
|
if (lastSegment.includes(".") && !lastSegment.startsWith(".")) {
|
|
2041
|
-
return normalizePath2(
|
|
2600
|
+
return normalizePath2(dirname3(normalizedPattern)) || ".";
|
|
2042
2601
|
}
|
|
2043
2602
|
return normalizedPattern || ".";
|
|
2044
2603
|
}
|
|
@@ -2233,84 +2792,877 @@ function watch(paths, options) {
|
|
|
2233
2792
|
return watcher;
|
|
2234
2793
|
}
|
|
2235
2794
|
|
|
2236
|
-
// src/cli/pm/
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2795
|
+
// src/cli/pm/proxy.ts
|
|
2796
|
+
var import_node_http = require("http");
|
|
2797
|
+
var import_node_https = require("https");
|
|
2798
|
+
var import_node_net = require("net");
|
|
2799
|
+
var import_promises = require("dns/promises");
|
|
2800
|
+
var BLOCKED_IPV4_PREFIXES = [
|
|
2801
|
+
"0.",
|
|
2802
|
+
"10.",
|
|
2803
|
+
"100.64.",
|
|
2804
|
+
"100.65.",
|
|
2805
|
+
"100.66.",
|
|
2806
|
+
"100.67.",
|
|
2807
|
+
"100.68.",
|
|
2808
|
+
"100.69.",
|
|
2809
|
+
"100.70.",
|
|
2810
|
+
"100.71.",
|
|
2811
|
+
"100.72.",
|
|
2812
|
+
"100.73.",
|
|
2813
|
+
"100.74.",
|
|
2814
|
+
"100.75.",
|
|
2815
|
+
"100.76.",
|
|
2816
|
+
"100.77.",
|
|
2817
|
+
"100.78.",
|
|
2818
|
+
"100.79.",
|
|
2819
|
+
"100.80.",
|
|
2820
|
+
"100.81.",
|
|
2821
|
+
"100.82.",
|
|
2822
|
+
"100.83.",
|
|
2823
|
+
"100.84.",
|
|
2824
|
+
"100.85.",
|
|
2825
|
+
"100.86.",
|
|
2826
|
+
"100.87.",
|
|
2827
|
+
"100.88.",
|
|
2828
|
+
"100.89.",
|
|
2829
|
+
"100.90.",
|
|
2830
|
+
"100.91.",
|
|
2831
|
+
"100.92.",
|
|
2832
|
+
"100.93.",
|
|
2833
|
+
"100.94.",
|
|
2834
|
+
"100.95.",
|
|
2835
|
+
"100.96.",
|
|
2836
|
+
"100.97.",
|
|
2837
|
+
"100.98.",
|
|
2838
|
+
"100.99.",
|
|
2839
|
+
"100.100.",
|
|
2840
|
+
"100.101.",
|
|
2841
|
+
"100.102.",
|
|
2842
|
+
"100.103.",
|
|
2843
|
+
"100.104.",
|
|
2844
|
+
"100.105.",
|
|
2845
|
+
"100.106.",
|
|
2846
|
+
"100.107.",
|
|
2847
|
+
"100.108.",
|
|
2848
|
+
"100.109.",
|
|
2849
|
+
"100.110.",
|
|
2850
|
+
"100.111.",
|
|
2851
|
+
"100.112.",
|
|
2852
|
+
"100.113.",
|
|
2853
|
+
"100.114.",
|
|
2854
|
+
"100.115.",
|
|
2855
|
+
"100.116.",
|
|
2856
|
+
"100.117.",
|
|
2857
|
+
"100.118.",
|
|
2858
|
+
"100.119.",
|
|
2859
|
+
"100.120.",
|
|
2860
|
+
"100.121.",
|
|
2861
|
+
"100.122.",
|
|
2862
|
+
"100.123.",
|
|
2863
|
+
"100.124.",
|
|
2864
|
+
"100.125.",
|
|
2865
|
+
"100.126.",
|
|
2866
|
+
"100.127.",
|
|
2867
|
+
"127.",
|
|
2868
|
+
"169.254.",
|
|
2869
|
+
"172.16.",
|
|
2870
|
+
"172.17.",
|
|
2871
|
+
"172.18.",
|
|
2872
|
+
"172.19.",
|
|
2873
|
+
"172.20.",
|
|
2874
|
+
"172.21.",
|
|
2875
|
+
"172.22.",
|
|
2876
|
+
"172.23.",
|
|
2877
|
+
"172.24.",
|
|
2878
|
+
"172.25.",
|
|
2879
|
+
"172.26.",
|
|
2880
|
+
"172.27.",
|
|
2881
|
+
"172.28.",
|
|
2882
|
+
"172.29.",
|
|
2883
|
+
"172.30.",
|
|
2884
|
+
"172.31.",
|
|
2885
|
+
"192.0.2.",
|
|
2886
|
+
"192.88.99.",
|
|
2887
|
+
"192.168.",
|
|
2888
|
+
"198.18.",
|
|
2889
|
+
"198.19.",
|
|
2890
|
+
"198.51.100.",
|
|
2891
|
+
"203.0.113.",
|
|
2892
|
+
"224.",
|
|
2893
|
+
"225.",
|
|
2894
|
+
"226.",
|
|
2895
|
+
"227.",
|
|
2896
|
+
"228.",
|
|
2897
|
+
"229.",
|
|
2898
|
+
"230.",
|
|
2899
|
+
"231.",
|
|
2900
|
+
"232.",
|
|
2901
|
+
"233.",
|
|
2902
|
+
"234.",
|
|
2903
|
+
"235.",
|
|
2904
|
+
"236.",
|
|
2905
|
+
"237.",
|
|
2906
|
+
"238.",
|
|
2907
|
+
"239.",
|
|
2908
|
+
"240.",
|
|
2909
|
+
"241.",
|
|
2910
|
+
"242.",
|
|
2911
|
+
"243.",
|
|
2912
|
+
"244.",
|
|
2913
|
+
"245.",
|
|
2914
|
+
"246.",
|
|
2915
|
+
"247.",
|
|
2916
|
+
"248.",
|
|
2917
|
+
"249.",
|
|
2918
|
+
"250.",
|
|
2919
|
+
"251.",
|
|
2920
|
+
"252.",
|
|
2921
|
+
"253.",
|
|
2922
|
+
"254.",
|
|
2923
|
+
"255."
|
|
2924
|
+
];
|
|
2925
|
+
var ALLOWED_PROXY_PROTOCOLS = /* @__PURE__ */ new Set(["http:", "https:"]);
|
|
2926
|
+
function isBlockedIpv4(hostname) {
|
|
2927
|
+
const octets = hostname.split(".");
|
|
2928
|
+
if (octets.length !== 4) return false;
|
|
2929
|
+
const joined = hostname;
|
|
2930
|
+
return BLOCKED_IPV4_PREFIXES.some((prefix) => joined.startsWith(prefix));
|
|
2931
|
+
}
|
|
2932
|
+
function isBlockedIpv6(hostname) {
|
|
2933
|
+
const lower = hostname.toLowerCase();
|
|
2934
|
+
if (lower === "::1" || lower === "::" || lower === "0:0:0:0:0:0:0:1" || lower === "0:0:0:0:0:0:0:0") {
|
|
2935
|
+
return true;
|
|
2243
2936
|
}
|
|
2244
|
-
|
|
2245
|
-
|
|
2937
|
+
const ffffMatch = lower.match(/^::ffff:(\d+\.\d+\.\d+\.\d+)$/);
|
|
2938
|
+
if (ffffMatch) {
|
|
2939
|
+
return isBlockedIpv4(ffffMatch[1]);
|
|
2246
2940
|
}
|
|
2247
|
-
|
|
2941
|
+
const compatMatch = lower.match(/^::(\d+\.\d+\.\d+\.\d+)$/);
|
|
2942
|
+
if (compatMatch) {
|
|
2943
|
+
return isBlockedIpv4(compatMatch[1]);
|
|
2944
|
+
}
|
|
2945
|
+
return false;
|
|
2248
2946
|
}
|
|
2249
|
-
|
|
2250
|
-
|
|
2947
|
+
function isSafeHostname(hostname) {
|
|
2948
|
+
if (!hostname) return false;
|
|
2949
|
+
if (/^\d{1,3}(\.\d{1,3}){3}$/.test(hostname)) return !isBlockedIpv4(hostname);
|
|
2950
|
+
if (/^\[.*\]$/.test(hostname)) return !isBlockedIpv6(hostname.slice(1, -1));
|
|
2951
|
+
if (hostname.includes(":")) return !isBlockedIpv6(hostname);
|
|
2952
|
+
return true;
|
|
2251
2953
|
}
|
|
2252
|
-
async function
|
|
2253
|
-
|
|
2254
|
-
|
|
2954
|
+
async function safeResolveHostname(hostname) {
|
|
2955
|
+
try {
|
|
2956
|
+
const result = await (0, import_promises.lookup)(hostname);
|
|
2957
|
+
const ip = result.address;
|
|
2958
|
+
if (isBlockedIpv4(ip) || isBlockedIpv6(ip)) {
|
|
2959
|
+
throw new Error(`PM proxy target resolved to a blocked address: ${ip}`);
|
|
2960
|
+
}
|
|
2961
|
+
return ip;
|
|
2962
|
+
} catch (error) {
|
|
2963
|
+
if (error instanceof Error && error.message.includes("blocked address")) {
|
|
2964
|
+
throw error;
|
|
2965
|
+
}
|
|
2966
|
+
throw new Error(`PM proxy failed to resolve target hostname "${hostname}": ${error instanceof Error ? error.message : String(error)}`);
|
|
2255
2967
|
}
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2968
|
+
}
|
|
2969
|
+
async function validateProxyTargetUrl(target) {
|
|
2970
|
+
if (!ALLOWED_PROXY_PROTOCOLS.has(target.protocol)) {
|
|
2971
|
+
throw new Error(`PM proxy target protocol "${target.protocol}" is not allowed. Only http: and https: are permitted.`);
|
|
2972
|
+
}
|
|
2973
|
+
const hostname = target.hostname;
|
|
2974
|
+
if (!isSafeHostname(hostname)) {
|
|
2975
|
+
throw new Error(`PM proxy target "${hostname}" resolves to a blocked address and is not allowed.`);
|
|
2976
|
+
}
|
|
2977
|
+
if (!/^\d{1,3}(\.\d{1,3}){3}$/.test(hostname) && !/^\[.*\]$/.test(hostname)) {
|
|
2978
|
+
await safeResolveHostname(hostname);
|
|
2979
|
+
}
|
|
2980
|
+
}
|
|
2981
|
+
function sanitizeProxyRequestPath(requestUrl) {
|
|
2982
|
+
if (!requestUrl || requestUrl === "/") return "/";
|
|
2983
|
+
try {
|
|
2984
|
+
const normalizedInput = requestUrl.replace(/\\/g, "/");
|
|
2985
|
+
const parsed = new URL(normalizedInput, "http://placeholder");
|
|
2986
|
+
if (parsed.username || parsed.password || parsed.hostname !== "placeholder" || parsed.port) {
|
|
2987
|
+
return "/";
|
|
2260
2988
|
}
|
|
2261
|
-
|
|
2989
|
+
const pathname = parsed.pathname || "/";
|
|
2990
|
+
let decodedPathname = pathname;
|
|
2991
|
+
try {
|
|
2992
|
+
decodedPathname = decodeURIComponent(pathname);
|
|
2993
|
+
} catch {
|
|
2994
|
+
return "/";
|
|
2995
|
+
}
|
|
2996
|
+
const lowerPath = pathname.toLowerCase();
|
|
2997
|
+
if (lowerPath.includes("%2f") || lowerPath.includes("%5c") || lowerPath.includes("%40") || lowerPath.includes("%00")) {
|
|
2998
|
+
return "/";
|
|
2999
|
+
}
|
|
3000
|
+
const segments = decodedPathname.split("/");
|
|
3001
|
+
if (segments.some((segment) => segment === "." || segment === "..")) {
|
|
3002
|
+
return "/";
|
|
3003
|
+
}
|
|
3004
|
+
const sanitized = pathname + parsed.search;
|
|
3005
|
+
return sanitized.startsWith("/") ? sanitized || "/" : `/${sanitized}`;
|
|
3006
|
+
} catch {
|
|
3007
|
+
return "/";
|
|
2262
3008
|
}
|
|
2263
|
-
return !isProcessAlive(pid);
|
|
2264
3009
|
}
|
|
2265
|
-
|
|
2266
|
-
return
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
3010
|
+
function resolvePmProxyHost(proxy) {
|
|
3011
|
+
return proxy.host?.trim() || "0.0.0.0";
|
|
3012
|
+
}
|
|
3013
|
+
function resolvePmProxyTargetHost(proxy) {
|
|
3014
|
+
return proxy.targetHost?.trim() || "127.0.0.1";
|
|
3015
|
+
}
|
|
3016
|
+
function resolvePmProxyEnvVar(proxy) {
|
|
3017
|
+
return proxy.envVar?.trim() || "PORT";
|
|
3018
|
+
}
|
|
3019
|
+
function buildPmProxyTargetUrl(proxy, targetPort) {
|
|
3020
|
+
return `http://${resolvePmProxyTargetHost(proxy)}:${targetPort}`;
|
|
3021
|
+
}
|
|
3022
|
+
function rewritePmProxyHealthCheckUrl(url, targetHost, targetPort) {
|
|
3023
|
+
const targetUrl = new URL(url);
|
|
3024
|
+
targetUrl.hostname = targetHost;
|
|
3025
|
+
targetUrl.port = String(targetPort);
|
|
3026
|
+
return targetUrl.toString();
|
|
3027
|
+
}
|
|
3028
|
+
async function allocatePmProxyTargetPort(host = "127.0.0.1") {
|
|
3029
|
+
const server = (0, import_node_net.createServer)();
|
|
3030
|
+
return await new Promise((resolve7, reject) => {
|
|
3031
|
+
server.once("error", reject);
|
|
3032
|
+
server.listen(0, host, () => {
|
|
3033
|
+
const address = server.address();
|
|
3034
|
+
if (!address || typeof address === "string") {
|
|
3035
|
+
server.close(() => reject(new Error("Failed to allocate an internal PM proxy port.")));
|
|
2277
3036
|
return;
|
|
2278
3037
|
}
|
|
2279
|
-
|
|
2280
|
-
|
|
3038
|
+
const port = address.port;
|
|
3039
|
+
server.close((error) => {
|
|
3040
|
+
if (error) {
|
|
3041
|
+
reject(error);
|
|
3042
|
+
return;
|
|
3043
|
+
}
|
|
3044
|
+
resolve7(port);
|
|
3045
|
+
});
|
|
2281
3046
|
});
|
|
2282
3047
|
});
|
|
2283
3048
|
}
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
3049
|
+
function buildPmProxyHeaders(headersInput, host) {
|
|
3050
|
+
const headers = {};
|
|
3051
|
+
for (const [key, value] of Object.entries(headersInput)) {
|
|
3052
|
+
if (value !== void 0) {
|
|
3053
|
+
headers[key] = value;
|
|
3054
|
+
}
|
|
3055
|
+
}
|
|
3056
|
+
headers.host = host;
|
|
3057
|
+
return headers;
|
|
3058
|
+
}
|
|
3059
|
+
function writeRawHttpResponse(socket, statusCode, statusMessage, headers) {
|
|
3060
|
+
const lines = [`HTTP/1.1 ${statusCode} ${statusMessage}`];
|
|
3061
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
3062
|
+
if (value === void 0) {
|
|
3063
|
+
continue;
|
|
3064
|
+
}
|
|
3065
|
+
if (Array.isArray(value)) {
|
|
3066
|
+
for (const item of value) {
|
|
3067
|
+
lines.push(`${key}: ${item}`);
|
|
2288
3068
|
}
|
|
2289
|
-
|
|
3069
|
+
continue;
|
|
3070
|
+
}
|
|
3071
|
+
lines.push(`${key}: ${value}`);
|
|
2290
3072
|
}
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
3073
|
+
socket.write(`${lines.join("\r\n")}\r
|
|
3074
|
+
\r
|
|
3075
|
+
`);
|
|
3076
|
+
}
|
|
3077
|
+
async function createPmProxyController(proxy) {
|
|
3078
|
+
let targets = [];
|
|
3079
|
+
let nextTargetIndex = 0;
|
|
3080
|
+
const setResolvedTargets = (nextTargets) => {
|
|
3081
|
+
const unchanged = nextTargets.length === targets.length && nextTargets.every((target, index) => targets[index]?.href === target.href);
|
|
3082
|
+
targets = nextTargets;
|
|
3083
|
+
if (targets.length === 0) {
|
|
3084
|
+
nextTargetIndex = 0;
|
|
2296
3085
|
return;
|
|
2297
3086
|
}
|
|
2298
|
-
if (
|
|
2299
|
-
|
|
3087
|
+
if (!unchanged) {
|
|
3088
|
+
nextTargetIndex = nextTargetIndex % targets.length;
|
|
2300
3089
|
}
|
|
2301
|
-
debounceTimer = setTimeout(() => {
|
|
2302
|
-
debounceTimer = null;
|
|
2303
|
-
onChange(normalizedPath);
|
|
2304
|
-
}, record.watchDebounce);
|
|
2305
|
-
debounceTimer.unref?.();
|
|
2306
3090
|
};
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
3091
|
+
const pickTarget = () => {
|
|
3092
|
+
if (targets.length === 0) {
|
|
3093
|
+
return null;
|
|
3094
|
+
}
|
|
3095
|
+
const target = targets[nextTargetIndex % targets.length];
|
|
3096
|
+
nextTargetIndex = (nextTargetIndex + 1) % targets.length;
|
|
3097
|
+
return target;
|
|
3098
|
+
};
|
|
3099
|
+
const validateTarget = async (target) => {
|
|
3100
|
+
await validateProxyTargetUrl(target);
|
|
3101
|
+
};
|
|
3102
|
+
const server = (0, import_node_http.createServer)((req, res) => {
|
|
3103
|
+
const target = pickTarget();
|
|
3104
|
+
if (!target) {
|
|
3105
|
+
res.statusCode = 503;
|
|
3106
|
+
res.end("PM proxy target is not ready.");
|
|
3107
|
+
return;
|
|
3108
|
+
}
|
|
3109
|
+
const sanitizedPath = sanitizeProxyRequestPath(req.url || "/");
|
|
3110
|
+
const requestLib = target.protocol === "https:" ? import_node_https.request : import_node_http.request;
|
|
3111
|
+
const headers = buildPmProxyHeaders(req.headers, target.host);
|
|
3112
|
+
if (!ALLOWED_PROXY_PROTOCOLS.has(target.protocol)) {
|
|
3113
|
+
res.statusCode = 400;
|
|
3114
|
+
res.end("PM proxy rejected unsafe target protocol.");
|
|
3115
|
+
return;
|
|
3116
|
+
}
|
|
3117
|
+
validateTarget(target).then(() => {
|
|
3118
|
+
const proxyReq = requestLib({
|
|
3119
|
+
protocol: target.protocol,
|
|
3120
|
+
hostname: target.hostname,
|
|
3121
|
+
port: target.port || void 0,
|
|
3122
|
+
path: sanitizedPath,
|
|
3123
|
+
method: req.method,
|
|
3124
|
+
headers
|
|
3125
|
+
}, (proxyRes) => {
|
|
3126
|
+
const outgoingHeaders = {};
|
|
3127
|
+
for (const [key, value] of Object.entries(proxyRes.headers)) {
|
|
3128
|
+
if (value !== void 0) {
|
|
3129
|
+
outgoingHeaders[key] = value;
|
|
3130
|
+
}
|
|
3131
|
+
}
|
|
3132
|
+
res.writeHead(proxyRes.statusCode || 200, outgoingHeaders);
|
|
3133
|
+
proxyRes.pipe(res);
|
|
3134
|
+
});
|
|
3135
|
+
proxyReq.on("error", (error) => {
|
|
3136
|
+
if (!res.headersSent) {
|
|
3137
|
+
res.statusCode = 502;
|
|
3138
|
+
}
|
|
3139
|
+
res.end(`PM proxy error: ${error.message}`);
|
|
3140
|
+
});
|
|
3141
|
+
req.pipe(proxyReq);
|
|
3142
|
+
}).catch((error) => {
|
|
3143
|
+
if (!res.headersSent) {
|
|
3144
|
+
res.statusCode = 403;
|
|
3145
|
+
}
|
|
3146
|
+
res.end(`PM proxy blocked target: ${error instanceof Error ? error.message : String(error)}`);
|
|
3147
|
+
});
|
|
3148
|
+
});
|
|
3149
|
+
server.on("upgrade", (req, socket, head) => {
|
|
3150
|
+
const target = pickTarget();
|
|
3151
|
+
if (!target) {
|
|
3152
|
+
writeRawHttpResponse(socket, 503, "Service Unavailable", {
|
|
3153
|
+
connection: "close",
|
|
3154
|
+
"content-length": 0
|
|
3155
|
+
});
|
|
3156
|
+
socket.destroy();
|
|
3157
|
+
return;
|
|
3158
|
+
}
|
|
3159
|
+
const sanitizedPath = sanitizeProxyRequestPath(req.url || "/");
|
|
3160
|
+
const requestLib = target.protocol === "https:" ? import_node_https.request : import_node_http.request;
|
|
3161
|
+
const targetUrl = new URL(sanitizedPath, target);
|
|
3162
|
+
if (targetUrl.hostname !== target.hostname || targetUrl.port !== target.port || !ALLOWED_PROXY_PROTOCOLS.has(targetUrl.protocol)) {
|
|
3163
|
+
writeRawHttpResponse(socket, 400, "Bad Request", {
|
|
3164
|
+
connection: "close",
|
|
3165
|
+
"content-length": 0
|
|
3166
|
+
});
|
|
3167
|
+
socket.destroy();
|
|
3168
|
+
return;
|
|
3169
|
+
}
|
|
3170
|
+
validateTarget(target).then(() => {
|
|
3171
|
+
const proxyReq = requestLib(targetUrl, {
|
|
3172
|
+
method: req.method,
|
|
3173
|
+
headers: buildPmProxyHeaders(req.headers, target.host)
|
|
3174
|
+
});
|
|
3175
|
+
proxyReq.on("upgrade", (proxyRes, proxySocket, proxyHead) => {
|
|
3176
|
+
writeRawHttpResponse(socket, proxyRes.statusCode || 101, proxyRes.statusMessage || "Switching Protocols", proxyRes.headers);
|
|
3177
|
+
if (head.length > 0) {
|
|
3178
|
+
proxySocket.write(head);
|
|
3179
|
+
}
|
|
3180
|
+
if (proxyHead.length > 0) {
|
|
3181
|
+
socket.write(proxyHead);
|
|
3182
|
+
}
|
|
3183
|
+
socket.on("error", () => proxySocket.destroy());
|
|
3184
|
+
proxySocket.on("error", () => socket.destroy());
|
|
3185
|
+
proxySocket.pipe(socket);
|
|
3186
|
+
socket.pipe(proxySocket);
|
|
3187
|
+
});
|
|
3188
|
+
proxyReq.on("response", (proxyRes) => {
|
|
3189
|
+
writeRawHttpResponse(socket, proxyRes.statusCode || 502, proxyRes.statusMessage || "Bad Gateway", proxyRes.headers);
|
|
3190
|
+
proxyRes.pipe(socket);
|
|
3191
|
+
});
|
|
3192
|
+
proxyReq.on("error", (error) => {
|
|
3193
|
+
writeRawHttpResponse(socket, 502, "Bad Gateway", {
|
|
3194
|
+
connection: "close",
|
|
3195
|
+
"content-type": "text/plain; charset=utf-8",
|
|
3196
|
+
"content-length": Buffer.byteLength(`PM proxy error: ${error.message}`)
|
|
3197
|
+
});
|
|
3198
|
+
socket.end(`PM proxy error: ${error.message}`);
|
|
3199
|
+
});
|
|
3200
|
+
proxyReq.end();
|
|
3201
|
+
}).catch((error) => {
|
|
3202
|
+
writeRawHttpResponse(socket, 403, "Forbidden", {
|
|
3203
|
+
connection: "close",
|
|
3204
|
+
"content-type": "text/plain; charset=utf-8",
|
|
3205
|
+
"content-length": Buffer.byteLength(`PM proxy blocked target: ${error instanceof Error ? error.message : String(error)}`)
|
|
3206
|
+
});
|
|
3207
|
+
socket.end(`PM proxy blocked target: ${error instanceof Error ? error.message : String(error)}`);
|
|
3208
|
+
});
|
|
3209
|
+
});
|
|
3210
|
+
await new Promise((resolve7, reject) => {
|
|
3211
|
+
server.once("error", reject);
|
|
3212
|
+
server.listen(proxy.port, resolvePmProxyHost(proxy), () => resolve7());
|
|
3213
|
+
});
|
|
3214
|
+
return {
|
|
3215
|
+
setTarget(targetUrl) {
|
|
3216
|
+
if (targetUrl) {
|
|
3217
|
+
const parsed = new URL(targetUrl);
|
|
3218
|
+
validateProxyTargetUrl(parsed).then(() => {
|
|
3219
|
+
setResolvedTargets([parsed]);
|
|
3220
|
+
}).catch((error) => {
|
|
3221
|
+
console.error(`[PM proxy] Blocked setTarget: ${error instanceof Error ? error.message : String(error)}`);
|
|
3222
|
+
setResolvedTargets([]);
|
|
3223
|
+
});
|
|
3224
|
+
} else {
|
|
3225
|
+
setResolvedTargets([]);
|
|
3226
|
+
}
|
|
3227
|
+
},
|
|
3228
|
+
setTargets(targetUrls) {
|
|
3229
|
+
Promise.all(targetUrls.map((url) => validateProxyTargetUrl(new URL(url)))).then(() => {
|
|
3230
|
+
setResolvedTargets(targetUrls.map((targetUrl) => new URL(targetUrl)));
|
|
3231
|
+
}).catch((error) => {
|
|
3232
|
+
console.error(`[PM proxy] Blocked setTargets: ${error instanceof Error ? error.message : String(error)}`);
|
|
3233
|
+
setResolvedTargets([]);
|
|
3234
|
+
});
|
|
3235
|
+
},
|
|
3236
|
+
close() {
|
|
3237
|
+
return new Promise((resolve7, reject) => {
|
|
3238
|
+
server.close((error) => {
|
|
3239
|
+
if (error) {
|
|
3240
|
+
reject(error);
|
|
3241
|
+
return;
|
|
3242
|
+
}
|
|
3243
|
+
resolve7();
|
|
3244
|
+
});
|
|
3245
|
+
});
|
|
3246
|
+
}
|
|
3247
|
+
};
|
|
3248
|
+
}
|
|
3249
|
+
|
|
3250
|
+
// src/cli/pm/runner.ts
|
|
3251
|
+
function writePmLog(stream, message) {
|
|
3252
|
+
if (stream.writableEnded || stream.destroyed) {
|
|
3253
|
+
return;
|
|
3254
|
+
}
|
|
3255
|
+
try {
|
|
3256
|
+
stream.write(`[elit pm] ${(/* @__PURE__ */ new Date()).toISOString()} ${message}${import_node_os.EOL}`);
|
|
3257
|
+
} catch (error) {
|
|
3258
|
+
if (error.code !== "ERR_STREAM_WRITE_AFTER_END") {
|
|
3259
|
+
throw error;
|
|
3260
|
+
}
|
|
3261
|
+
}
|
|
3262
|
+
}
|
|
3263
|
+
function waitForExit(code, signal) {
|
|
3264
|
+
if (typeof code === "number") {
|
|
3265
|
+
return code;
|
|
3266
|
+
}
|
|
3267
|
+
if (signal === "SIGINT" || signal === "SIGTERM") {
|
|
3268
|
+
return 0;
|
|
3269
|
+
}
|
|
3270
|
+
return 1;
|
|
3271
|
+
}
|
|
3272
|
+
async function delay(milliseconds) {
|
|
3273
|
+
await new Promise((resolvePromise) => setTimeout(resolvePromise, milliseconds));
|
|
3274
|
+
}
|
|
3275
|
+
function resolveRunnerPathsFromRecordFile(filePath) {
|
|
3276
|
+
const appsDir = (0, import_node_path6.dirname)(filePath);
|
|
3277
|
+
const dataDir = (0, import_node_path6.dirname)(appsDir);
|
|
3278
|
+
return {
|
|
3279
|
+
dataDir,
|
|
3280
|
+
appsDir,
|
|
3281
|
+
logsDir: (0, import_node_path6.join)(dataDir, "logs"),
|
|
3282
|
+
dumpFile: (0, import_node_path6.join)(dataDir, DEFAULT_PM_DUMP_FILE)
|
|
3283
|
+
};
|
|
3284
|
+
}
|
|
3285
|
+
function usesPmProxyController(record) {
|
|
3286
|
+
return Boolean(record.proxy) && (record.proxy?.strategy ?? DEFAULT_PM_PROXY_STRATEGY) === "proxy";
|
|
3287
|
+
}
|
|
3288
|
+
function usesPmInheritedListener(record) {
|
|
3289
|
+
return Boolean(record.proxy) && (record.proxy?.strategy ?? DEFAULT_PM_PROXY_STRATEGY) === "inherit";
|
|
3290
|
+
}
|
|
3291
|
+
function isPmProxyOwner(record) {
|
|
3292
|
+
return usesPmProxyController(record) && record.instanceIndex === 1;
|
|
3293
|
+
}
|
|
3294
|
+
function resolvePmProxyTargetUrls(paths, baseName) {
|
|
3295
|
+
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));
|
|
3296
|
+
}
|
|
3297
|
+
async function createPmInheritedListener(proxy) {
|
|
3298
|
+
const server = (0, import_node_http2.createServer)();
|
|
3299
|
+
await new Promise((resolvePromise, reject) => {
|
|
3300
|
+
server.once("error", reject);
|
|
3301
|
+
server.listen(proxy.port, resolvePmProxyHost(proxy), () => resolvePromise());
|
|
3302
|
+
});
|
|
3303
|
+
return server;
|
|
3304
|
+
}
|
|
3305
|
+
function createPmChildIpcState(child, sharedListener) {
|
|
3306
|
+
let bootstrapReady = false;
|
|
3307
|
+
let listenerReady = false;
|
|
3308
|
+
let sharedHandleSent = false;
|
|
3309
|
+
const sendSharedHandle = () => {
|
|
3310
|
+
if (!sharedListener || !bootstrapReady || sharedHandleSent || !child.connected) {
|
|
3311
|
+
return;
|
|
3312
|
+
}
|
|
3313
|
+
sharedHandleSent = true;
|
|
3314
|
+
child.send?.({ type: "elit:pm:listen-handle" }, sharedListener);
|
|
3315
|
+
};
|
|
3316
|
+
const onMessage = (message) => {
|
|
3317
|
+
if (!message || typeof message !== "object") {
|
|
3318
|
+
return;
|
|
3319
|
+
}
|
|
3320
|
+
if (message.type === "elit:pm:bootstrap-ready") {
|
|
3321
|
+
bootstrapReady = true;
|
|
3322
|
+
sendSharedHandle();
|
|
3323
|
+
return;
|
|
3324
|
+
}
|
|
3325
|
+
if (message.type === "elit:pm:listener-ready") {
|
|
3326
|
+
listenerReady = true;
|
|
3327
|
+
}
|
|
3328
|
+
};
|
|
3329
|
+
child.on("message", onMessage);
|
|
3330
|
+
return {
|
|
3331
|
+
get bootstrapReady() {
|
|
3332
|
+
return bootstrapReady;
|
|
3333
|
+
},
|
|
3334
|
+
get listenerReady() {
|
|
3335
|
+
return listenerReady;
|
|
3336
|
+
},
|
|
3337
|
+
stop() {
|
|
3338
|
+
child.off("message", onMessage);
|
|
3339
|
+
}
|
|
3340
|
+
};
|
|
3341
|
+
}
|
|
3342
|
+
function buildPmChildEnv(record, command, targetPort) {
|
|
3343
|
+
return {
|
|
3344
|
+
...process.env,
|
|
3345
|
+
...record.env,
|
|
3346
|
+
...command.env,
|
|
3347
|
+
...usesPmProxyController(record) && targetPort ? {
|
|
3348
|
+
[resolvePmProxyEnvVar(record.proxy)]: String(targetPort),
|
|
3349
|
+
ELIT_PM_PUBLIC_PORT: String(record.proxy.port)
|
|
3350
|
+
} : {},
|
|
3351
|
+
ELIT_PM_NAME: record.name,
|
|
3352
|
+
ELIT_PM_ID: record.id
|
|
3353
|
+
};
|
|
3354
|
+
}
|
|
3355
|
+
function buildPmChildStdio(command, onlineStdinShutdownEnabled) {
|
|
3356
|
+
return [
|
|
3357
|
+
onlineStdinShutdownEnabled ? "pipe" : "ignore",
|
|
3358
|
+
"pipe",
|
|
3359
|
+
"pipe",
|
|
3360
|
+
...command.ipc ? ["ipc"] : []
|
|
3361
|
+
];
|
|
3362
|
+
}
|
|
3363
|
+
function createPmReadinessMonitor(record, onReady, onFailure, options) {
|
|
3364
|
+
if (options?.ipcController) {
|
|
3365
|
+
let stopped2 = false;
|
|
3366
|
+
let timer2 = null;
|
|
3367
|
+
let timeoutTimer2 = null;
|
|
3368
|
+
const clearTimers2 = () => {
|
|
3369
|
+
if (timer2) {
|
|
3370
|
+
clearInterval(timer2);
|
|
3371
|
+
timer2 = null;
|
|
3372
|
+
}
|
|
3373
|
+
if (timeoutTimer2) {
|
|
3374
|
+
clearTimeout(timeoutTimer2);
|
|
3375
|
+
timeoutTimer2 = null;
|
|
3376
|
+
}
|
|
3377
|
+
};
|
|
3378
|
+
const host = record.proxy?.host ?? "0.0.0.0";
|
|
3379
|
+
const port = record.proxy?.port ?? 0;
|
|
3380
|
+
timer2 = setInterval(() => {
|
|
3381
|
+
if (stopped2 || !options.ipcController?.listenerReady) {
|
|
3382
|
+
return;
|
|
3383
|
+
}
|
|
3384
|
+
stopped2 = true;
|
|
3385
|
+
clearTimers2();
|
|
3386
|
+
onReady(`shared listener ready on ${host}:${port}`);
|
|
3387
|
+
}, 25);
|
|
3388
|
+
timer2.unref?.();
|
|
3389
|
+
timeoutTimer2 = setTimeout(() => {
|
|
3390
|
+
if (stopped2) {
|
|
3391
|
+
return;
|
|
3392
|
+
}
|
|
3393
|
+
stopped2 = true;
|
|
3394
|
+
clearTimers2();
|
|
3395
|
+
onFailure(`listen timeout reached after ${record.listenTimeout}ms while waiting for shared listener ${host}:${port}`);
|
|
3396
|
+
}, record.listenTimeout);
|
|
3397
|
+
timeoutTimer2.unref?.();
|
|
3398
|
+
return {
|
|
3399
|
+
stop() {
|
|
3400
|
+
stopped2 = true;
|
|
3401
|
+
clearTimers2();
|
|
3402
|
+
}
|
|
3403
|
+
};
|
|
3404
|
+
}
|
|
3405
|
+
if (!record.waitReady || !record.healthCheck) {
|
|
3406
|
+
return {
|
|
3407
|
+
stop() {
|
|
3408
|
+
}
|
|
3409
|
+
};
|
|
3410
|
+
}
|
|
3411
|
+
const healthCheck = record.healthCheck;
|
|
3412
|
+
const pollInterval = Math.max(50, Math.min(healthCheck.interval, 250));
|
|
3413
|
+
let stopped = false;
|
|
3414
|
+
let timer = null;
|
|
3415
|
+
let timeoutTimer = null;
|
|
3416
|
+
let inFlight = false;
|
|
3417
|
+
const clearTimers = () => {
|
|
3418
|
+
if (timer) {
|
|
3419
|
+
clearInterval(timer);
|
|
3420
|
+
timer = null;
|
|
3421
|
+
}
|
|
3422
|
+
if (timeoutTimer) {
|
|
3423
|
+
clearTimeout(timeoutTimer);
|
|
3424
|
+
timeoutTimer = null;
|
|
3425
|
+
}
|
|
3426
|
+
};
|
|
3427
|
+
const runHealthCheck = async () => {
|
|
3428
|
+
if (stopped || inFlight) {
|
|
3429
|
+
return;
|
|
3430
|
+
}
|
|
3431
|
+
inFlight = true;
|
|
3432
|
+
const controller = new AbortController();
|
|
3433
|
+
const timeoutId = setTimeout(() => controller.abort(), healthCheck.timeout);
|
|
3434
|
+
timeoutId.unref?.();
|
|
3435
|
+
try {
|
|
3436
|
+
const response = await fetch(healthCheck.url, {
|
|
3437
|
+
method: "GET",
|
|
3438
|
+
signal: controller.signal
|
|
3439
|
+
});
|
|
3440
|
+
if (stopped) {
|
|
3441
|
+
return;
|
|
3442
|
+
}
|
|
3443
|
+
if (!response.ok) {
|
|
3444
|
+
throw new Error(`health check returned ${response.status}`);
|
|
3445
|
+
}
|
|
3446
|
+
stopped = true;
|
|
3447
|
+
clearTimers();
|
|
3448
|
+
onReady(`readiness check passed: ${healthCheck.url}`);
|
|
3449
|
+
} catch {
|
|
3450
|
+
} finally {
|
|
3451
|
+
clearTimeout(timeoutId);
|
|
3452
|
+
inFlight = false;
|
|
3453
|
+
}
|
|
3454
|
+
};
|
|
3455
|
+
timeoutTimer = setTimeout(() => {
|
|
3456
|
+
if (stopped) {
|
|
3457
|
+
return;
|
|
3458
|
+
}
|
|
3459
|
+
stopped = true;
|
|
3460
|
+
clearTimers();
|
|
3461
|
+
onFailure(`listen timeout reached after ${record.listenTimeout}ms while waiting for ${healthCheck.url}`);
|
|
3462
|
+
}, record.listenTimeout);
|
|
3463
|
+
timeoutTimer.unref?.();
|
|
3464
|
+
void runHealthCheck();
|
|
3465
|
+
timer = setInterval(() => {
|
|
3466
|
+
void runHealthCheck();
|
|
3467
|
+
}, pollInterval);
|
|
3468
|
+
timer.unref?.();
|
|
3469
|
+
return {
|
|
3470
|
+
stop() {
|
|
3471
|
+
stopped = true;
|
|
3472
|
+
clearTimers();
|
|
3473
|
+
}
|
|
3474
|
+
};
|
|
3475
|
+
}
|
|
3476
|
+
function resolvePmStopTimeout(record) {
|
|
3477
|
+
if (isPmOnlineWapkRecord(record)) {
|
|
3478
|
+
return Math.max(record.killTimeout, PM_WAPK_ONLINE_SHUTDOWN_TIMEOUT_MS);
|
|
3479
|
+
}
|
|
3480
|
+
return record.killTimeout;
|
|
3481
|
+
}
|
|
3482
|
+
function supportsPmProxyReload(record) {
|
|
3483
|
+
return Boolean(record.proxy) && record.instances === 1;
|
|
3484
|
+
}
|
|
3485
|
+
function buildPmMonitorRecord(record, targetPort) {
|
|
3486
|
+
if (!record.proxy || !targetPort) {
|
|
3487
|
+
return record;
|
|
3488
|
+
}
|
|
3489
|
+
const targetHost = resolvePmProxyTargetHost(record.proxy);
|
|
3490
|
+
return {
|
|
3491
|
+
...record,
|
|
3492
|
+
proxyTargetPort: targetPort,
|
|
3493
|
+
healthCheck: record.healthCheck ? {
|
|
3494
|
+
...record.healthCheck,
|
|
3495
|
+
url: rewritePmProxyHealthCheckUrl(record.healthCheck.url, targetHost, targetPort)
|
|
3496
|
+
} : void 0
|
|
3497
|
+
};
|
|
3498
|
+
}
|
|
3499
|
+
async function waitForProcessTermination(pid, timeoutMs) {
|
|
3500
|
+
if (!pid || !isProcessAlive(pid)) {
|
|
3501
|
+
return true;
|
|
3502
|
+
}
|
|
3503
|
+
const deadline = Date.now() + timeoutMs;
|
|
3504
|
+
while (Date.now() < deadline) {
|
|
3505
|
+
if (!isProcessAlive(pid)) {
|
|
3506
|
+
return true;
|
|
3507
|
+
}
|
|
3508
|
+
await delay(DEFAULT_PM_STOP_POLL_MS);
|
|
3509
|
+
}
|
|
3510
|
+
return !isProcessAlive(pid);
|
|
3511
|
+
}
|
|
3512
|
+
async function waitForManagedChildExit(child) {
|
|
3513
|
+
return await new Promise((resolvePromise) => {
|
|
3514
|
+
let resolved = false;
|
|
3515
|
+
child.once("error", (error) => {
|
|
3516
|
+
if (resolved) {
|
|
3517
|
+
return;
|
|
3518
|
+
}
|
|
3519
|
+
resolved = true;
|
|
3520
|
+
resolvePromise({ code: 1, signal: null, error: error instanceof Error ? error.message : String(error) });
|
|
3521
|
+
});
|
|
3522
|
+
child.once("close", (code, signal) => {
|
|
3523
|
+
if (resolved) {
|
|
3524
|
+
return;
|
|
3525
|
+
}
|
|
3526
|
+
resolved = true;
|
|
3527
|
+
resolvePromise({ code, signal });
|
|
3528
|
+
});
|
|
3529
|
+
});
|
|
3530
|
+
}
|
|
3531
|
+
async function createPmChildControllers(record, child, stdoutLog, stderrLog, requestPlannedRestart, onReady, options) {
|
|
3532
|
+
let healthMonitor = {
|
|
3533
|
+
stop() {
|
|
3534
|
+
}
|
|
3535
|
+
};
|
|
3536
|
+
let memoryMonitor = {
|
|
3537
|
+
stop() {
|
|
3538
|
+
}
|
|
3539
|
+
};
|
|
3540
|
+
let scheduleMonitor = {
|
|
3541
|
+
stop() {
|
|
3542
|
+
}
|
|
3543
|
+
};
|
|
3544
|
+
const startHealthMonitor = () => {
|
|
3545
|
+
healthMonitor = createPmHealthMonitor(
|
|
3546
|
+
record,
|
|
3547
|
+
(message) => requestPlannedRestart("health", message),
|
|
3548
|
+
(message) => writePmLog(stdoutLog, message)
|
|
3549
|
+
);
|
|
3550
|
+
};
|
|
3551
|
+
const needsReadySignal = Boolean(options?.ipcController) || record.waitReady;
|
|
3552
|
+
const readinessMonitor = options?.ready || !needsReadySignal ? { stop() {
|
|
3553
|
+
} } : createPmReadinessMonitor(
|
|
3554
|
+
record,
|
|
3555
|
+
(message) => {
|
|
3556
|
+
onReady(message);
|
|
3557
|
+
startHealthMonitor();
|
|
3558
|
+
},
|
|
3559
|
+
(message) => requestPlannedRestart("startup", message),
|
|
3560
|
+
{ ipcController: options?.ipcController }
|
|
3561
|
+
);
|
|
3562
|
+
const watchController = await createPmWatchController(
|
|
3563
|
+
record,
|
|
3564
|
+
(changedPath) => requestPlannedRestart("watch", changedPath),
|
|
3565
|
+
(message) => writePmLog(stderrLog, `watch error: ${message}`)
|
|
3566
|
+
);
|
|
3567
|
+
memoryMonitor = createPmMemoryMonitor(
|
|
3568
|
+
record,
|
|
3569
|
+
child.pid,
|
|
3570
|
+
(kind, message) => requestPlannedRestart(kind, message)
|
|
3571
|
+
);
|
|
3572
|
+
scheduleMonitor = createPmScheduleMonitor(
|
|
3573
|
+
record,
|
|
3574
|
+
(message) => requestPlannedRestart("cron", message),
|
|
3575
|
+
(message) => writePmLog(stdoutLog, message)
|
|
3576
|
+
);
|
|
3577
|
+
if (options?.ready || !needsReadySignal) {
|
|
3578
|
+
startHealthMonitor();
|
|
3579
|
+
}
|
|
3580
|
+
return {
|
|
3581
|
+
async stop() {
|
|
3582
|
+
await watchController.close();
|
|
3583
|
+
readinessMonitor.stop();
|
|
3584
|
+
healthMonitor.stop();
|
|
3585
|
+
memoryMonitor.stop();
|
|
3586
|
+
scheduleMonitor.stop();
|
|
3587
|
+
}
|
|
3588
|
+
};
|
|
3589
|
+
}
|
|
3590
|
+
async function waitForPmChildReady(record, child, ipcController) {
|
|
3591
|
+
if (!ipcController && (!record.waitReady || !record.healthCheck)) {
|
|
3592
|
+
return { ready: true };
|
|
3593
|
+
}
|
|
3594
|
+
let readyMessage;
|
|
3595
|
+
let failureMessage;
|
|
3596
|
+
let exitResult;
|
|
3597
|
+
const readinessMonitor = createPmReadinessMonitor(
|
|
3598
|
+
record,
|
|
3599
|
+
(message) => {
|
|
3600
|
+
readyMessage = message;
|
|
3601
|
+
},
|
|
3602
|
+
(message) => {
|
|
3603
|
+
failureMessage = message;
|
|
3604
|
+
},
|
|
3605
|
+
{ ipcController }
|
|
3606
|
+
);
|
|
3607
|
+
void waitForManagedChildExit(child).then((result) => {
|
|
3608
|
+
exitResult = result;
|
|
3609
|
+
});
|
|
3610
|
+
while (!readyMessage && !failureMessage && !exitResult) {
|
|
3611
|
+
await delay(25);
|
|
3612
|
+
}
|
|
3613
|
+
readinessMonitor.stop();
|
|
3614
|
+
if (readyMessage) {
|
|
3615
|
+
return { ready: true, message: readyMessage };
|
|
3616
|
+
}
|
|
3617
|
+
return {
|
|
3618
|
+
ready: false,
|
|
3619
|
+
message: failureMessage,
|
|
3620
|
+
exitResult
|
|
3621
|
+
};
|
|
3622
|
+
}
|
|
3623
|
+
async function stopProxyManagedChild(child, record, stderrLog) {
|
|
3624
|
+
if (!child.pid || !isProcessAlive(child.pid)) {
|
|
3625
|
+
return;
|
|
3626
|
+
}
|
|
3627
|
+
terminateProcessTree(child.pid);
|
|
3628
|
+
const stopTimeout = resolvePmStopTimeout(record);
|
|
3629
|
+
const stopped = await waitForProcessTermination(child.pid, stopTimeout);
|
|
3630
|
+
if (!stopped && child.pid && isProcessAlive(child.pid)) {
|
|
3631
|
+
writePmLog(stderrLog, `proxy handoff shutdown timed out after ${stopTimeout}ms; forcing process termination`);
|
|
3632
|
+
terminateProcessTree(child.pid, { force: true });
|
|
3633
|
+
await waitForProcessTermination(child.pid, DEFAULT_PM_STOP_POLL_MS);
|
|
3634
|
+
}
|
|
3635
|
+
}
|
|
3636
|
+
async function createPmWatchController(record, onChange, onError) {
|
|
3637
|
+
if (!record.watch || record.watchPaths.length === 0) {
|
|
3638
|
+
return {
|
|
3639
|
+
async close() {
|
|
3640
|
+
}
|
|
3641
|
+
};
|
|
3642
|
+
}
|
|
3643
|
+
const watcher = watch(record.watchPaths);
|
|
3644
|
+
let debounceTimer = null;
|
|
3645
|
+
const scheduleRestart = (filePath) => {
|
|
3646
|
+
const normalizedPath = filePath.replace(/\\/g, "/");
|
|
3647
|
+
if (isIgnoredWatchPath(normalizedPath, record.watchIgnore)) {
|
|
3648
|
+
return;
|
|
3649
|
+
}
|
|
3650
|
+
if (debounceTimer) {
|
|
3651
|
+
clearTimeout(debounceTimer);
|
|
3652
|
+
}
|
|
3653
|
+
debounceTimer = setTimeout(() => {
|
|
3654
|
+
debounceTimer = null;
|
|
3655
|
+
onChange(normalizedPath);
|
|
3656
|
+
}, record.watchDebounce);
|
|
3657
|
+
debounceTimer.unref?.();
|
|
3658
|
+
};
|
|
3659
|
+
watcher.on("add", scheduleRestart);
|
|
3660
|
+
watcher.on("change", scheduleRestart);
|
|
3661
|
+
watcher.on("unlink", scheduleRestart);
|
|
3662
|
+
watcher.on("error", (error) => onError(error instanceof Error ? error.message : String(error)));
|
|
3663
|
+
return {
|
|
3664
|
+
async close() {
|
|
3665
|
+
if (debounceTimer) {
|
|
2314
3666
|
clearTimeout(debounceTimer);
|
|
2315
3667
|
debounceTimer = null;
|
|
2316
3668
|
}
|
|
@@ -2344,11 +3696,17 @@ function createPmHealthMonitor(record, onFailure, onLog) {
|
|
|
2344
3696
|
method: "GET",
|
|
2345
3697
|
signal: controller.signal
|
|
2346
3698
|
});
|
|
3699
|
+
if (stopped) {
|
|
3700
|
+
return;
|
|
3701
|
+
}
|
|
2347
3702
|
if (!response.ok) {
|
|
2348
3703
|
throw new Error(`health check returned ${response.status}`);
|
|
2349
3704
|
}
|
|
2350
3705
|
failureCount = 0;
|
|
2351
3706
|
} catch (error) {
|
|
3707
|
+
if (stopped) {
|
|
3708
|
+
return;
|
|
3709
|
+
}
|
|
2352
3710
|
failureCount += 1;
|
|
2353
3711
|
const message = error instanceof Error ? error.message : String(error);
|
|
2354
3712
|
onLog(`health check failed (${failureCount}/${healthCheck.maxFailures}): ${message}`);
|
|
@@ -2383,6 +3741,90 @@ function createPmHealthMonitor(record, onFailure, onLog) {
|
|
|
2383
3741
|
}
|
|
2384
3742
|
};
|
|
2385
3743
|
}
|
|
3744
|
+
function createPmMemoryMonitor(record, pid, onFailure) {
|
|
3745
|
+
if (!record.maxMemoryBytes || !pid) {
|
|
3746
|
+
return {
|
|
3747
|
+
stop() {
|
|
3748
|
+
}
|
|
3749
|
+
};
|
|
3750
|
+
}
|
|
3751
|
+
const memoryLimit = record.maxMemoryBytes;
|
|
3752
|
+
let stopped = false;
|
|
3753
|
+
const timer = setInterval(() => {
|
|
3754
|
+
if (stopped) {
|
|
3755
|
+
return;
|
|
3756
|
+
}
|
|
3757
|
+
const memoryRssBytes = samplePmProcessMetrics(pid).memoryRssBytes;
|
|
3758
|
+
if (memoryRssBytes === void 0 || memoryRssBytes <= memoryLimit) {
|
|
3759
|
+
return;
|
|
3760
|
+
}
|
|
3761
|
+
stopped = true;
|
|
3762
|
+
onFailure(record.memoryAction === "stop" ? "memory-stop" : "memory", `memory usage ${memoryRssBytes} exceeded limit ${memoryLimit}`);
|
|
3763
|
+
}, DEFAULT_PM_MEMORY_CHECK_INTERVAL);
|
|
3764
|
+
timer.unref?.();
|
|
3765
|
+
return {
|
|
3766
|
+
stop() {
|
|
3767
|
+
stopped = true;
|
|
3768
|
+
clearInterval(timer);
|
|
3769
|
+
}
|
|
3770
|
+
};
|
|
3771
|
+
}
|
|
3772
|
+
function createPmScheduleMonitor(record, onTrigger, onLog) {
|
|
3773
|
+
if (!record.cronRestart) {
|
|
3774
|
+
return {
|
|
3775
|
+
stop() {
|
|
3776
|
+
}
|
|
3777
|
+
};
|
|
3778
|
+
}
|
|
3779
|
+
const schedule = parsePmRestartSchedule(record.cronRestart, "pm cronRestart");
|
|
3780
|
+
let stopped = false;
|
|
3781
|
+
let timer = null;
|
|
3782
|
+
const armTimer = (from = /* @__PURE__ */ new Date()) => {
|
|
3783
|
+
const nextOccurrence = resolveNextPmScheduleOccurrence(schedule, from);
|
|
3784
|
+
if (!nextOccurrence) {
|
|
3785
|
+
onLog(`schedule has no next occurrence: ${record.cronRestart}`);
|
|
3786
|
+
return;
|
|
3787
|
+
}
|
|
3788
|
+
const delayMs = Math.max(0, nextOccurrence.getTime() - Date.now());
|
|
3789
|
+
timer = setTimeout(() => {
|
|
3790
|
+
timer = null;
|
|
3791
|
+
if (stopped) {
|
|
3792
|
+
return;
|
|
3793
|
+
}
|
|
3794
|
+
onTrigger(`restart schedule matched: ${record.cronRestart}`);
|
|
3795
|
+
}, delayMs);
|
|
3796
|
+
timer.unref?.();
|
|
3797
|
+
};
|
|
3798
|
+
armTimer();
|
|
3799
|
+
return {
|
|
3800
|
+
stop() {
|
|
3801
|
+
stopped = true;
|
|
3802
|
+
if (timer) {
|
|
3803
|
+
clearTimeout(timer);
|
|
3804
|
+
timer = null;
|
|
3805
|
+
}
|
|
3806
|
+
}
|
|
3807
|
+
};
|
|
3808
|
+
}
|
|
3809
|
+
function resolvePmRestartDelay(record, restartCount, shouldApplyBackoff) {
|
|
3810
|
+
if (!shouldApplyBackoff || !record.expBackoffRestartDelay) {
|
|
3811
|
+
return record.restartDelay;
|
|
3812
|
+
}
|
|
3813
|
+
const exponent = Math.max(0, restartCount - 1);
|
|
3814
|
+
return Math.min(record.expBackoffRestartDelay * 2 ** exponent, record.expBackoffRestartMaxDelay ?? DEFAULT_PM_EXP_BACKOFF_MAX_DELAY);
|
|
3815
|
+
}
|
|
3816
|
+
function resolvePmRestartCountBase(record, wasStable, restartKind) {
|
|
3817
|
+
if (wasStable) {
|
|
3818
|
+
return 0;
|
|
3819
|
+
}
|
|
3820
|
+
if (record.restartWindow && record.lastRestartAt) {
|
|
3821
|
+
const lastRestartTime = Date.parse(record.lastRestartAt);
|
|
3822
|
+
if (!Number.isNaN(lastRestartTime) && Date.now() - lastRestartTime > record.restartWindow) {
|
|
3823
|
+
return 0;
|
|
3824
|
+
}
|
|
3825
|
+
}
|
|
3826
|
+
return restartKind === "watch" ? record.restartCount ?? 0 : record.restartCount ?? 0;
|
|
3827
|
+
}
|
|
2386
3828
|
function readPlannedRestartRequest(state) {
|
|
2387
3829
|
return state.request;
|
|
2388
3830
|
}
|
|
@@ -2396,6 +3838,16 @@ async function runManagedProcessLoop(filePath, initialRecord) {
|
|
|
2396
3838
|
(0, import_node_fs4.mkdirSync)((0, import_node_path6.dirname)(initialRecord.logFiles.err), { recursive: true });
|
|
2397
3839
|
const stdoutLog = (0, import_node_fs4.createWriteStream)(initialRecord.logFiles.out, { flags: "a" });
|
|
2398
3840
|
const stderrLog = (0, import_node_fs4.createWriteStream)(initialRecord.logFiles.err, { flags: "a" });
|
|
3841
|
+
let proxyController = null;
|
|
3842
|
+
let proxyTargetSyncTimer = null;
|
|
3843
|
+
let inheritedListener = null;
|
|
3844
|
+
const runnerPaths = resolveRunnerPathsFromRecordFile(filePath);
|
|
3845
|
+
const syncOwnedProxyTargets = (baseName) => {
|
|
3846
|
+
if (!proxyController) {
|
|
3847
|
+
return;
|
|
3848
|
+
}
|
|
3849
|
+
proxyController.setTargets(resolvePmProxyTargetUrls(runnerPaths, baseName));
|
|
3850
|
+
};
|
|
2399
3851
|
const persist = (mutator) => {
|
|
2400
3852
|
const current = readLatestPmRecord(filePath, record);
|
|
2401
3853
|
record = mutator(current);
|
|
@@ -2408,26 +3860,30 @@ async function runManagedProcessLoop(filePath, initialRecord) {
|
|
|
2408
3860
|
activeChildStopTimer = null;
|
|
2409
3861
|
}
|
|
2410
3862
|
};
|
|
3863
|
+
const scheduleForcedActiveChildStop = (timeoutMs, reason) => {
|
|
3864
|
+
if (!activeChild?.pid || process.platform === "win32") {
|
|
3865
|
+
return;
|
|
3866
|
+
}
|
|
3867
|
+
clearActiveChildStopTimer();
|
|
3868
|
+
activeChildStopTimer = setTimeout(() => {
|
|
3869
|
+
if (activeChild?.pid && isProcessAlive(activeChild.pid)) {
|
|
3870
|
+
writePmLog(stderrLog, `${reason} after ${timeoutMs}ms; forcing process termination`);
|
|
3871
|
+
terminateProcessTree(activeChild.pid, { force: true });
|
|
3872
|
+
}
|
|
3873
|
+
}, timeoutMs);
|
|
3874
|
+
activeChildStopTimer.unref?.();
|
|
3875
|
+
};
|
|
2411
3876
|
const stopActiveChild = () => {
|
|
2412
3877
|
if (!activeChild?.pid || !isProcessAlive(activeChild.pid)) {
|
|
2413
3878
|
return;
|
|
2414
3879
|
}
|
|
2415
3880
|
const current = readLatestPmRecord(filePath, record);
|
|
3881
|
+
const stopTimeout = resolvePmStopTimeout(current);
|
|
2416
3882
|
if (isPmOnlineWapkRecord(current) && activeChild.stdin && !activeChild.stdin.destroyed && activeChild.stdin.writable) {
|
|
2417
3883
|
try {
|
|
2418
3884
|
activeChild.stdin.end(`${PM_WAPK_ONLINE_SHUTDOWN_COMMAND}
|
|
2419
3885
|
`);
|
|
2420
|
-
|
|
2421
|
-
activeChildStopTimer = setTimeout(() => {
|
|
2422
|
-
if (activeChild?.pid && isProcessAlive(activeChild.pid)) {
|
|
2423
|
-
writePmLog(
|
|
2424
|
-
stderrLog,
|
|
2425
|
-
`graceful WAPK online shutdown timed out after ${PM_WAPK_ONLINE_SHUTDOWN_TIMEOUT_MS}ms; forcing process termination`
|
|
2426
|
-
);
|
|
2427
|
-
terminateProcessTree(activeChild.pid);
|
|
2428
|
-
}
|
|
2429
|
-
}, PM_WAPK_ONLINE_SHUTDOWN_TIMEOUT_MS);
|
|
2430
|
-
activeChildStopTimer.unref?.();
|
|
3886
|
+
scheduleForcedActiveChildStop(stopTimeout, "graceful WAPK online shutdown timed out");
|
|
2431
3887
|
return;
|
|
2432
3888
|
} catch (error) {
|
|
2433
3889
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -2435,6 +3891,7 @@ async function runManagedProcessLoop(filePath, initialRecord) {
|
|
|
2435
3891
|
}
|
|
2436
3892
|
}
|
|
2437
3893
|
terminateProcessTree(activeChild.pid);
|
|
3894
|
+
scheduleForcedActiveChildStop(stopTimeout, "graceful shutdown timed out");
|
|
2438
3895
|
};
|
|
2439
3896
|
const requestManagedStop = (reason) => {
|
|
2440
3897
|
if (stopRequested) {
|
|
@@ -2472,6 +3929,17 @@ async function runManagedProcessLoop(filePath, initialRecord) {
|
|
|
2472
3929
|
let command;
|
|
2473
3930
|
try {
|
|
2474
3931
|
command = buildPmCommand(latest);
|
|
3932
|
+
if (latest.proxy && isPmProxyOwner(latest) && !proxyController) {
|
|
3933
|
+
proxyController = await createPmProxyController(latest.proxy);
|
|
3934
|
+
syncOwnedProxyTargets(latest.baseName);
|
|
3935
|
+
if (!proxyTargetSyncTimer) {
|
|
3936
|
+
proxyTargetSyncTimer = setInterval(() => syncOwnedProxyTargets(latest.baseName), 50);
|
|
3937
|
+
proxyTargetSyncTimer.unref?.();
|
|
3938
|
+
}
|
|
3939
|
+
}
|
|
3940
|
+
if (latest.proxy && usesPmInheritedListener(latest) && !inheritedListener) {
|
|
3941
|
+
inheritedListener = await createPmInheritedListener(latest.proxy);
|
|
3942
|
+
}
|
|
2475
3943
|
} catch (error) {
|
|
2476
3944
|
const message = error instanceof Error ? error.message : String(error);
|
|
2477
3945
|
writePmLog(stderrLog, message);
|
|
@@ -2481,25 +3949,24 @@ async function runManagedProcessLoop(filePath, initialRecord) {
|
|
|
2481
3949
|
error: message,
|
|
2482
3950
|
runnerPid: void 0,
|
|
2483
3951
|
childPid: void 0,
|
|
3952
|
+
proxyTargetPort: void 0,
|
|
3953
|
+
proxyReadyAt: void 0,
|
|
2484
3954
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2485
3955
|
}));
|
|
2486
3956
|
return;
|
|
2487
3957
|
}
|
|
2488
3958
|
const onlineStdinShutdownEnabled = isPmOnlineWapkRecord(latest);
|
|
2489
|
-
const
|
|
3959
|
+
const initialTargetPort = usesPmProxyController(latest) ? await allocatePmProxyTargetPort(resolvePmProxyTargetHost(latest.proxy)) : void 0;
|
|
3960
|
+
const monitorRecord = buildPmMonitorRecord(latest, initialTargetPort);
|
|
3961
|
+
let child = (0, import_node_child_process2.spawn)(command.command, command.args, {
|
|
2490
3962
|
cwd: latest.cwd,
|
|
2491
|
-
env:
|
|
2492
|
-
|
|
2493
|
-
...latest.env,
|
|
2494
|
-
...command.env,
|
|
2495
|
-
ELIT_PM_NAME: latest.name,
|
|
2496
|
-
ELIT_PM_ID: latest.id
|
|
2497
|
-
},
|
|
2498
|
-
stdio: [onlineStdinShutdownEnabled ? "pipe" : "ignore", "pipe", "pipe"],
|
|
3963
|
+
env: buildPmChildEnv(latest, command, initialTargetPort),
|
|
3964
|
+
stdio: buildPmChildStdio(command, onlineStdinShutdownEnabled),
|
|
2499
3965
|
windowsHide: true,
|
|
2500
3966
|
shell: command.shell
|
|
2501
3967
|
});
|
|
2502
|
-
|
|
3968
|
+
let childStartedAt = Date.now();
|
|
3969
|
+
let childIpcState = command.ipc ? createPmChildIpcState(child, inheritedListener) : void 0;
|
|
2503
3970
|
activeChild = child;
|
|
2504
3971
|
if (child.stdout) {
|
|
2505
3972
|
child.stdout.pipe(stdoutLog, { end: false });
|
|
@@ -2507,26 +3974,38 @@ async function runManagedProcessLoop(filePath, initialRecord) {
|
|
|
2507
3974
|
if (child.stderr) {
|
|
2508
3975
|
child.stderr.pipe(stderrLog, { end: false });
|
|
2509
3976
|
}
|
|
3977
|
+
let childWaitState = { settled: false, result: void 0 };
|
|
3978
|
+
void waitForManagedChildExit(child).then((result) => {
|
|
3979
|
+
childWaitState.result = result;
|
|
3980
|
+
childWaitState.settled = true;
|
|
3981
|
+
});
|
|
2510
3982
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3983
|
+
const waitingForReady = latest.waitReady || Boolean(childIpcState);
|
|
2511
3984
|
persist((current2) => ({
|
|
2512
3985
|
...current2,
|
|
2513
|
-
status: "online",
|
|
3986
|
+
status: waitingForReady ? "starting" : "online",
|
|
2514
3987
|
commandPreview: command.preview,
|
|
2515
3988
|
runtime: command.runtime ?? current2.runtime,
|
|
2516
3989
|
runnerPid: process.pid,
|
|
2517
3990
|
childPid: child.pid,
|
|
3991
|
+
proxyTargetPort: initialTargetPort,
|
|
3992
|
+
proxyReadyAt: !waitingForReady && usesPmProxyController(latest) ? startedAt : void 0,
|
|
2518
3993
|
startedAt,
|
|
2519
3994
|
stoppedAt: void 0,
|
|
3995
|
+
reloadRequestedAt: void 0,
|
|
2520
3996
|
error: void 0,
|
|
2521
3997
|
updatedAt: startedAt
|
|
2522
3998
|
}));
|
|
2523
3999
|
writePmLog(stdoutLog, `started ${command.preview}${child.pid ? ` (pid ${child.pid})` : ""}`);
|
|
4000
|
+
if (isPmProxyOwner(latest) && !waitingForReady) {
|
|
4001
|
+
syncOwnedProxyTargets(latest.baseName);
|
|
4002
|
+
}
|
|
2524
4003
|
const requestPlannedRestart = (kind, detail) => {
|
|
2525
4004
|
if (stopRequested || restartState.request) {
|
|
2526
4005
|
return;
|
|
2527
4006
|
}
|
|
2528
4007
|
restartState.request = { kind, detail };
|
|
2529
|
-
writePmLog(kind === "
|
|
4008
|
+
writePmLog(kind === "watch" || kind === "cron" ? stdoutLog : stderrLog, `${kind} restart requested: ${detail}`);
|
|
2530
4009
|
persist((current2) => ({
|
|
2531
4010
|
...current2,
|
|
2532
4011
|
status: "restarting",
|
|
@@ -2534,27 +4013,124 @@ async function runManagedProcessLoop(filePath, initialRecord) {
|
|
|
2534
4013
|
}));
|
|
2535
4014
|
stopActiveChild();
|
|
2536
4015
|
};
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
4016
|
+
let controllers = await createPmChildControllers(
|
|
4017
|
+
monitorRecord,
|
|
4018
|
+
child,
|
|
4019
|
+
stdoutLog,
|
|
4020
|
+
stderrLog,
|
|
4021
|
+
requestPlannedRestart,
|
|
4022
|
+
(message) => {
|
|
4023
|
+
const readyAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
4024
|
+
if (isPmProxyOwner(latest)) {
|
|
4025
|
+
syncOwnedProxyTargets(latest.baseName);
|
|
4026
|
+
}
|
|
4027
|
+
persist((current2) => ({
|
|
4028
|
+
...current2,
|
|
4029
|
+
status: "online",
|
|
4030
|
+
proxyTargetPort: initialTargetPort,
|
|
4031
|
+
proxyReadyAt: readyAt,
|
|
4032
|
+
updatedAt: readyAt
|
|
4033
|
+
}));
|
|
4034
|
+
writePmLog(stdoutLog, message);
|
|
4035
|
+
},
|
|
4036
|
+
{ ready: !waitingForReady, ipcController: childIpcState }
|
|
2546
4037
|
);
|
|
2547
|
-
|
|
4038
|
+
let handledReloadAt = latest.reloadRequestedAt;
|
|
4039
|
+
while (!childWaitState.settled) {
|
|
2548
4040
|
const latestRecord = readLatestPmRecord(filePath, record);
|
|
2549
|
-
if (
|
|
4041
|
+
if (latestRecord.desiredState === "stopped" && !stopRequested) {
|
|
2550
4042
|
requestManagedStop("stop requested by PM control state");
|
|
2551
4043
|
}
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
4044
|
+
const reloadRequestedAt = latestRecord.reloadRequestedAt;
|
|
4045
|
+
if (!stopRequested && supportsPmProxyReload(latestRecord) && reloadRequestedAt && reloadRequestedAt !== handledReloadAt && latestRecord.proxy) {
|
|
4046
|
+
handledReloadAt = reloadRequestedAt;
|
|
4047
|
+
const replacementTargetPort = usesPmProxyController(latestRecord) ? await allocatePmProxyTargetPort(resolvePmProxyTargetHost(latestRecord.proxy)) : void 0;
|
|
4048
|
+
const replacementMonitorRecord = buildPmMonitorRecord(latestRecord, replacementTargetPort);
|
|
4049
|
+
const replacementChild = (0, import_node_child_process2.spawn)(command.command, command.args, {
|
|
4050
|
+
cwd: latestRecord.cwd,
|
|
4051
|
+
env: buildPmChildEnv(latestRecord, command, replacementTargetPort),
|
|
4052
|
+
stdio: buildPmChildStdio(command, onlineStdinShutdownEnabled),
|
|
4053
|
+
windowsHide: true,
|
|
4054
|
+
shell: command.shell
|
|
4055
|
+
});
|
|
4056
|
+
const replacementIpcState = command.ipc ? createPmChildIpcState(replacementChild, inheritedListener) : void 0;
|
|
4057
|
+
if (replacementChild.stdout) {
|
|
4058
|
+
replacementChild.stdout.pipe(stdoutLog, { end: false });
|
|
4059
|
+
}
|
|
4060
|
+
if (replacementChild.stderr) {
|
|
4061
|
+
replacementChild.stderr.pipe(stderrLog, { end: false });
|
|
4062
|
+
}
|
|
4063
|
+
writePmLog(stdoutLog, `starting ${usesPmInheritedListener(latestRecord) ? "shared-listener" : "proxy handoff"} replacement${replacementChild.pid ? ` (pid ${replacementChild.pid})` : ""}`);
|
|
4064
|
+
const readyResult = await waitForPmChildReady(replacementMonitorRecord, replacementChild, replacementIpcState);
|
|
4065
|
+
if (!readyResult.ready) {
|
|
4066
|
+
writePmLog(stderrLog, readyResult.message ?? "replacement exited before becoming ready");
|
|
4067
|
+
replacementIpcState?.stop();
|
|
4068
|
+
await stopProxyManagedChild(replacementChild, latestRecord, stderrLog);
|
|
4069
|
+
persist((current2) => ({
|
|
4070
|
+
...current2,
|
|
4071
|
+
status: "online",
|
|
4072
|
+
proxyReadyAt: current2.proxyReadyAt,
|
|
4073
|
+
reloadRequestedAt: void 0,
|
|
4074
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4075
|
+
}));
|
|
4076
|
+
continue;
|
|
4077
|
+
}
|
|
4078
|
+
const previousChild = child;
|
|
4079
|
+
const previousControllers = controllers;
|
|
4080
|
+
const previousIpcState = childIpcState;
|
|
4081
|
+
const handoffAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
4082
|
+
if (usesPmProxyController(latestRecord) && replacementTargetPort) {
|
|
4083
|
+
proxyController?.setTarget(buildPmProxyTargetUrl(latestRecord.proxy, replacementTargetPort));
|
|
4084
|
+
}
|
|
4085
|
+
persist((current2) => ({
|
|
4086
|
+
...current2,
|
|
4087
|
+
status: "online",
|
|
4088
|
+
childPid: replacementChild.pid,
|
|
4089
|
+
proxyTargetPort: replacementTargetPort,
|
|
4090
|
+
proxyReadyAt: handoffAt,
|
|
4091
|
+
startedAt: handoffAt,
|
|
4092
|
+
reloadRequestedAt: void 0,
|
|
4093
|
+
error: void 0,
|
|
4094
|
+
updatedAt: handoffAt
|
|
4095
|
+
}));
|
|
4096
|
+
if (readyResult.message) {
|
|
4097
|
+
writePmLog(stdoutLog, readyResult.message);
|
|
4098
|
+
}
|
|
4099
|
+
writePmLog(stdoutLog, `${usesPmInheritedListener(latestRecord) ? "shared listener" : "proxy handoff"} activated on ${latestRecord.proxy.host ?? "0.0.0.0"}:${latestRecord.proxy.port}`);
|
|
4100
|
+
child = replacementChild;
|
|
4101
|
+
childIpcState = replacementIpcState;
|
|
4102
|
+
childStartedAt = Date.now();
|
|
4103
|
+
activeChild = replacementChild;
|
|
4104
|
+
childWaitState = { settled: false, result: void 0 };
|
|
4105
|
+
void waitForManagedChildExit(replacementChild).then((result) => {
|
|
4106
|
+
childWaitState.result = result;
|
|
4107
|
+
childWaitState.settled = true;
|
|
4108
|
+
});
|
|
4109
|
+
controllers = await createPmChildControllers(
|
|
4110
|
+
replacementMonitorRecord,
|
|
4111
|
+
replacementChild,
|
|
4112
|
+
stdoutLog,
|
|
4113
|
+
stderrLog,
|
|
4114
|
+
requestPlannedRestart,
|
|
4115
|
+
() => {
|
|
4116
|
+
},
|
|
4117
|
+
{ ready: true, ipcController: replacementIpcState }
|
|
4118
|
+
);
|
|
4119
|
+
await delay(250);
|
|
4120
|
+
await previousControllers.stop();
|
|
4121
|
+
previousIpcState?.stop();
|
|
4122
|
+
await stopProxyManagedChild(previousChild, latestRecord, stderrLog);
|
|
4123
|
+
clearActiveChildStopTimer();
|
|
4124
|
+
if (isPmProxyOwner(latestRecord)) {
|
|
4125
|
+
syncOwnedProxyTargets(latestRecord.baseName);
|
|
4126
|
+
}
|
|
4127
|
+
continue;
|
|
4128
|
+
}
|
|
4129
|
+
await delay(25);
|
|
4130
|
+
}
|
|
4131
|
+
const exitResult = childWaitState.result;
|
|
4132
|
+
await controllers.stop();
|
|
4133
|
+
childIpcState?.stop();
|
|
2558
4134
|
clearActiveChildStopTimer();
|
|
2559
4135
|
activeChild = null;
|
|
2560
4136
|
const exitCode = waitForExit(exitResult.code, exitResult.signal);
|
|
@@ -2570,56 +4146,77 @@ async function runManagedProcessLoop(filePath, initialRecord) {
|
|
|
2570
4146
|
if (stopRequested || current.desiredState === "stopped") {
|
|
2571
4147
|
break;
|
|
2572
4148
|
}
|
|
2573
|
-
const shouldRestartForExit = plannedRestart ? true : current.restartPolicy === "always" ? true : current.restartPolicy === "on-failure" ? exitCode !== 0 || Boolean(exitResult.error) : false;
|
|
4149
|
+
const shouldRestartForExit = plannedRestart?.kind === "memory-stop" ? false : plannedRestart ? true : current.restartPolicy === "always" ? true : current.restartPolicy === "on-failure" ? exitCode !== 0 || Boolean(exitResult.error) : false;
|
|
2574
4150
|
if (!shouldRestartForExit) {
|
|
2575
4151
|
persist((latestRecord) => ({
|
|
2576
4152
|
...latestRecord,
|
|
2577
|
-
status: exitCode === 0 && !exitResult.error ? "exited" : "errored",
|
|
4153
|
+
status: plannedRestart?.kind === "memory-stop" ? "errored" : exitCode === 0 && !exitResult.error ? "exited" : "errored",
|
|
2578
4154
|
childPid: void 0,
|
|
4155
|
+
proxyTargetPort: void 0,
|
|
4156
|
+
proxyReadyAt: void 0,
|
|
2579
4157
|
runnerPid: void 0,
|
|
2580
4158
|
lastExitCode: exitCode,
|
|
2581
|
-
|
|
4159
|
+
reloadRequestedAt: void 0,
|
|
4160
|
+
error: plannedRestart?.kind === "memory-stop" ? plannedRestart.detail : exitCode === 0 && !exitResult.error ? void 0 : exitResult.error ?? `Process exited with code ${exitCode}.`,
|
|
2582
4161
|
stoppedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2583
4162
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2584
4163
|
}));
|
|
4164
|
+
if (isPmProxyOwner(current)) {
|
|
4165
|
+
syncOwnedProxyTargets(current.baseName);
|
|
4166
|
+
}
|
|
2585
4167
|
return;
|
|
2586
4168
|
}
|
|
2587
4169
|
const shouldCountRestart = plannedRestart?.kind !== "watch";
|
|
2588
|
-
const baseRestartCount =
|
|
4170
|
+
const baseRestartCount = resolvePmRestartCountBase(current, wasStable, plannedRestart?.kind);
|
|
2589
4171
|
const nextRestartCount = shouldCountRestart ? baseRestartCount + 1 : current.restartCount ?? 0;
|
|
2590
4172
|
if (nextRestartCount > current.maxRestarts) {
|
|
2591
4173
|
persist((latestRecord) => ({
|
|
2592
4174
|
...latestRecord,
|
|
2593
4175
|
status: "errored",
|
|
2594
4176
|
childPid: void 0,
|
|
4177
|
+
proxyTargetPort: void 0,
|
|
4178
|
+
proxyReadyAt: void 0,
|
|
2595
4179
|
runnerPid: void 0,
|
|
2596
4180
|
restartCount: nextRestartCount,
|
|
4181
|
+
lastRestartAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2597
4182
|
lastExitCode: exitCode,
|
|
4183
|
+
reloadRequestedAt: void 0,
|
|
2598
4184
|
error: plannedRestart ? `Reached max restart attempts (${current.maxRestarts}) after ${plannedRestart.kind} restart requests.` : `Reached max restart attempts (${current.maxRestarts}).`,
|
|
2599
4185
|
stoppedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2600
4186
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2601
4187
|
}));
|
|
2602
4188
|
writePmLog(stderrLog, `max restart attempts reached (${current.maxRestarts})`);
|
|
4189
|
+
if (isPmProxyOwner(current)) {
|
|
4190
|
+
syncOwnedProxyTargets(current.baseName);
|
|
4191
|
+
}
|
|
2603
4192
|
return;
|
|
2604
4193
|
}
|
|
2605
4194
|
persist((latestRecord) => ({
|
|
2606
4195
|
...latestRecord,
|
|
2607
4196
|
status: "restarting",
|
|
2608
4197
|
childPid: void 0,
|
|
4198
|
+
proxyTargetPort: void 0,
|
|
4199
|
+
proxyReadyAt: void 0,
|
|
2609
4200
|
lastExitCode: exitCode,
|
|
2610
4201
|
restartCount: nextRestartCount,
|
|
4202
|
+
lastRestartAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4203
|
+
reloadRequestedAt: void 0,
|
|
2611
4204
|
error: void 0,
|
|
2612
4205
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2613
4206
|
}));
|
|
4207
|
+
if (isPmProxyOwner(current)) {
|
|
4208
|
+
syncOwnedProxyTargets(current.baseName);
|
|
4209
|
+
}
|
|
4210
|
+
const resolvedRestartDelay = resolvePmRestartDelay(current, nextRestartCount, shouldCountRestart && !wasStable && plannedRestart?.kind !== "watch");
|
|
2614
4211
|
if (plannedRestart) {
|
|
2615
4212
|
writePmLog(
|
|
2616
|
-
plannedRestart.kind === "health" ? stderrLog : stdoutLog,
|
|
2617
|
-
`restarting in ${
|
|
4213
|
+
plannedRestart.kind === "health" || plannedRestart.kind === "memory" || plannedRestart.kind === "memory-stop" || plannedRestart.kind === "startup" ? stderrLog : stdoutLog,
|
|
4214
|
+
`restarting in ${resolvedRestartDelay}ms after ${plannedRestart.kind}: ${plannedRestart.detail}`
|
|
2618
4215
|
);
|
|
2619
4216
|
} else {
|
|
2620
|
-
writePmLog(stdoutLog, `restarting in ${
|
|
4217
|
+
writePmLog(stdoutLog, `restarting in ${resolvedRestartDelay}ms`);
|
|
2621
4218
|
}
|
|
2622
|
-
await delay(
|
|
4219
|
+
await delay(resolvedRestartDelay);
|
|
2623
4220
|
}
|
|
2624
4221
|
} finally {
|
|
2625
4222
|
stopRequested = true;
|
|
@@ -2632,11 +4229,26 @@ async function runManagedProcessLoop(filePath, initialRecord) {
|
|
|
2632
4229
|
status: finalRecord.status === "errored" ? "errored" : finalRecord.status === "exited" ? "exited" : "stopped",
|
|
2633
4230
|
runnerPid: void 0,
|
|
2634
4231
|
childPid: void 0,
|
|
4232
|
+
proxyTargetPort: void 0,
|
|
4233
|
+
proxyReadyAt: void 0,
|
|
4234
|
+
reloadRequestedAt: void 0,
|
|
2635
4235
|
stoppedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2636
4236
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2637
4237
|
});
|
|
2638
4238
|
process.off("SIGINT", handleStopSignal);
|
|
2639
4239
|
process.off("SIGTERM", handleStopSignal);
|
|
4240
|
+
if (proxyTargetSyncTimer) {
|
|
4241
|
+
clearInterval(proxyTargetSyncTimer);
|
|
4242
|
+
proxyTargetSyncTimer = null;
|
|
4243
|
+
}
|
|
4244
|
+
if (proxyController) {
|
|
4245
|
+
await proxyController.close().catch(() => void 0);
|
|
4246
|
+
proxyController = null;
|
|
4247
|
+
}
|
|
4248
|
+
if (inheritedListener) {
|
|
4249
|
+
await new Promise((resolvePromise) => inheritedListener?.close(() => resolvePromise()));
|
|
4250
|
+
inheritedListener = null;
|
|
4251
|
+
}
|
|
2640
4252
|
await new Promise((resolvePromise) => stdoutLog.end(resolvePromise));
|
|
2641
4253
|
await new Promise((resolvePromise) => stderrLog.end(resolvePromise));
|
|
2642
4254
|
}
|
|
@@ -2690,23 +4302,26 @@ async function stopPmMatches(matches) {
|
|
|
2690
4302
|
stoppedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2691
4303
|
};
|
|
2692
4304
|
writePmRecord(match.filePath, updated);
|
|
2693
|
-
const
|
|
4305
|
+
const stopTimeout = resolvePmStopTimeout(match.record);
|
|
4306
|
+
const runnerStopped = await waitForProcessTermination(match.record.runnerPid, stopTimeout);
|
|
2694
4307
|
const childStopped = await waitForProcessTermination(
|
|
2695
4308
|
match.record.childPid,
|
|
2696
|
-
runnerStopped ? DEFAULT_PM_STOP_POLL_MS :
|
|
4309
|
+
runnerStopped ? DEFAULT_PM_STOP_POLL_MS : stopTimeout
|
|
2697
4310
|
);
|
|
2698
4311
|
if (!runnerStopped && match.record.runnerPid && isProcessAlive(match.record.runnerPid)) {
|
|
2699
|
-
terminateProcessTree(match.record.runnerPid);
|
|
4312
|
+
terminateProcessTree(match.record.runnerPid, { force: true });
|
|
2700
4313
|
await waitForProcessTermination(match.record.runnerPid, DEFAULT_PM_STOP_POLL_MS);
|
|
2701
4314
|
}
|
|
2702
4315
|
if (!childStopped && match.record.childPid && isProcessAlive(match.record.childPid)) {
|
|
2703
|
-
terminateProcessTree(match.record.childPid);
|
|
4316
|
+
terminateProcessTree(match.record.childPid, { force: true });
|
|
2704
4317
|
await waitForProcessTermination(match.record.childPid, DEFAULT_PM_STOP_POLL_MS);
|
|
2705
4318
|
}
|
|
2706
4319
|
writePmRecord(match.filePath, {
|
|
2707
4320
|
...updated,
|
|
2708
4321
|
runnerPid: void 0,
|
|
2709
4322
|
childPid: void 0,
|
|
4323
|
+
proxyTargetPort: void 0,
|
|
4324
|
+
reloadRequestedAt: void 0,
|
|
2710
4325
|
status: "stopped",
|
|
2711
4326
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2712
4327
|
});
|
|
@@ -2775,13 +4390,13 @@ function readPackageJson(filePath) {
|
|
|
2775
4390
|
|
|
2776
4391
|
// src/shares/workspace-package/roots.ts
|
|
2777
4392
|
function findPackageDirectory(startDir, resolveMatch) {
|
|
2778
|
-
let currentDir =
|
|
4393
|
+
let currentDir = resolve5(startDir);
|
|
2779
4394
|
while (true) {
|
|
2780
4395
|
const match = resolveMatch(currentDir);
|
|
2781
4396
|
if (match) {
|
|
2782
4397
|
return match;
|
|
2783
4398
|
}
|
|
2784
|
-
const parentDir =
|
|
4399
|
+
const parentDir = dirname3(currentDir);
|
|
2785
4400
|
if (parentDir === currentDir) {
|
|
2786
4401
|
return void 0;
|
|
2787
4402
|
}
|
|
@@ -2809,17 +4424,17 @@ init_fs();
|
|
|
2809
4424
|
function getWorkspacePackageImportCandidates(packageRoot, specifier, options = {}) {
|
|
2810
4425
|
const subpath = specifier === "elit" ? "index" : specifier.slice("elit/".length);
|
|
2811
4426
|
const builtCandidates = options.preferredBuiltFormat === "cjs" ? [
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
|
|
4427
|
+
resolve5(packageRoot, "dist", `${subpath}.cjs`),
|
|
4428
|
+
resolve5(packageRoot, "dist", `${subpath}.js`),
|
|
4429
|
+
resolve5(packageRoot, "dist", `${subpath}.mjs`)
|
|
2815
4430
|
] : [
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
4431
|
+
resolve5(packageRoot, "dist", `${subpath}.mjs`),
|
|
4432
|
+
resolve5(packageRoot, "dist", `${subpath}.js`),
|
|
4433
|
+
resolve5(packageRoot, "dist", `${subpath}.cjs`)
|
|
2819
4434
|
];
|
|
2820
4435
|
const sourceCandidates = [
|
|
2821
|
-
|
|
2822
|
-
|
|
4436
|
+
resolve5(packageRoot, "src", `${subpath}.ts`),
|
|
4437
|
+
resolve5(packageRoot, "src", `${subpath}.tsx`)
|
|
2823
4438
|
];
|
|
2824
4439
|
return options.preferBuilt ? [...builtCandidates, ...sourceCandidates] : [...sourceCandidates, ...builtCandidates];
|
|
2825
4440
|
}
|
|
@@ -2854,7 +4469,7 @@ function resolveWorkspacePackageImport(specifier, startDir, options = {}) {
|
|
|
2854
4469
|
// src/shares/config/loader.ts
|
|
2855
4470
|
function resolveConfigPath(cwd = process.cwd()) {
|
|
2856
4471
|
for (const configFile of ELIT_CONFIG_FILES) {
|
|
2857
|
-
const configPath =
|
|
4472
|
+
const configPath = resolve5(cwd, configFile);
|
|
2858
4473
|
if (existsSync4(configPath)) {
|
|
2859
4474
|
return configPath;
|
|
2860
4475
|
}
|
|
@@ -2883,7 +4498,7 @@ async function loadConfigFile(configPath) {
|
|
|
2883
4498
|
if (ext === "ts" || ext === "mts") {
|
|
2884
4499
|
try {
|
|
2885
4500
|
const { build } = await import("esbuild");
|
|
2886
|
-
const configDir =
|
|
4501
|
+
const configDir = dirname3(configPath);
|
|
2887
4502
|
const tempFile = join5(configDir, `.elit-config-${Date.now()}.mjs`);
|
|
2888
4503
|
const externalAllPlugin = {
|
|
2889
4504
|
name: "external-all",
|
|
@@ -2957,6 +4572,43 @@ async function loadConfigFile(configPath) {
|
|
|
2957
4572
|
}
|
|
2958
4573
|
|
|
2959
4574
|
// src/cli/pm/commands.ts
|
|
4575
|
+
var PM_SIGNAL_NAMES = /* @__PURE__ */ new Set([
|
|
4576
|
+
"SIGABRT",
|
|
4577
|
+
"SIGALRM",
|
|
4578
|
+
"SIGBREAK",
|
|
4579
|
+
"SIGBUS",
|
|
4580
|
+
"SIGCHLD",
|
|
4581
|
+
"SIGCONT",
|
|
4582
|
+
"SIGFPE",
|
|
4583
|
+
"SIGHUP",
|
|
4584
|
+
"SIGILL",
|
|
4585
|
+
"SIGINT",
|
|
4586
|
+
"SIGIO",
|
|
4587
|
+
"SIGIOT",
|
|
4588
|
+
"SIGKILL",
|
|
4589
|
+
"SIGPIPE",
|
|
4590
|
+
"SIGPOLL",
|
|
4591
|
+
"SIGPROF",
|
|
4592
|
+
"SIGPWR",
|
|
4593
|
+
"SIGQUIT",
|
|
4594
|
+
"SIGSEGV",
|
|
4595
|
+
"SIGSTKFLT",
|
|
4596
|
+
"SIGSTOP",
|
|
4597
|
+
"SIGSYS",
|
|
4598
|
+
"SIGTERM",
|
|
4599
|
+
"SIGTRAP",
|
|
4600
|
+
"SIGTSTP",
|
|
4601
|
+
"SIGTTIN",
|
|
4602
|
+
"SIGTTOU",
|
|
4603
|
+
"SIGUNUSED",
|
|
4604
|
+
"SIGURG",
|
|
4605
|
+
"SIGUSR1",
|
|
4606
|
+
"SIGUSR2",
|
|
4607
|
+
"SIGVTALRM",
|
|
4608
|
+
"SIGWINCH",
|
|
4609
|
+
"SIGXCPU",
|
|
4610
|
+
"SIGXFSZ"
|
|
4611
|
+
]);
|
|
2960
4612
|
async function runPmStart(args) {
|
|
2961
4613
|
const parsed = parsePmStartArgs(args);
|
|
2962
4614
|
const workspaceRoot = process.cwd();
|
|
@@ -2989,9 +4641,130 @@ function resolveNamedMatches(paths, value) {
|
|
|
2989
4641
|
if (value === "all") {
|
|
2990
4642
|
return listPmRecordMatches(paths).map(syncPmRecordLiveness);
|
|
2991
4643
|
}
|
|
4644
|
+
const groupMatches = findPmGroupMatches(paths, value);
|
|
4645
|
+
if (groupMatches.length > 0) {
|
|
4646
|
+
return groupMatches.map(syncPmRecordLiveness);
|
|
4647
|
+
}
|
|
2992
4648
|
const match = findPmRecordMatch(paths, value);
|
|
2993
4649
|
return match ? [syncPmRecordLiveness(match)] : [];
|
|
2994
4650
|
}
|
|
4651
|
+
function sortPmMatchesByInstance(matches) {
|
|
4652
|
+
return [...matches].sort((left, right) => left.record.instanceIndex - right.record.instanceIndex);
|
|
4653
|
+
}
|
|
4654
|
+
function resolveInspectableMatch(paths, value) {
|
|
4655
|
+
const exactMatch = findPmRecordMatch(paths, value);
|
|
4656
|
+
const groupMatches = findPmGroupMatches(paths, value).map(syncPmRecordLiveness);
|
|
4657
|
+
if (groupMatches.length > 1 && exactMatch?.record.baseName === value && exactMatch.record.name === value) {
|
|
4658
|
+
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}.`);
|
|
4659
|
+
}
|
|
4660
|
+
if (exactMatch) {
|
|
4661
|
+
return syncPmRecordLiveness(exactMatch);
|
|
4662
|
+
}
|
|
4663
|
+
return groupMatches[0];
|
|
4664
|
+
}
|
|
4665
|
+
function rebuildPmRecordDefinition(record, targetInstances = record.instances) {
|
|
4666
|
+
const definition = resolvePmAppDefinition(
|
|
4667
|
+
toPmAppConfig(record),
|
|
4668
|
+
{ name: record.baseName, env: {}, watchPaths: [], watchIgnore: [], instances: targetInstances },
|
|
4669
|
+
process.cwd(),
|
|
4670
|
+
record.source
|
|
4671
|
+
);
|
|
4672
|
+
return {
|
|
4673
|
+
...definition,
|
|
4674
|
+
name: record.name,
|
|
4675
|
+
baseName: record.baseName,
|
|
4676
|
+
instanceIndex: record.instanceIndex,
|
|
4677
|
+
instances: targetInstances
|
|
4678
|
+
};
|
|
4679
|
+
}
|
|
4680
|
+
function rebuildPmSavedDefinition(app) {
|
|
4681
|
+
const definition = resolvePmAppDefinition(
|
|
4682
|
+
toSavedPmAppConfig(app),
|
|
4683
|
+
{ name: app.baseName, env: {}, watchPaths: [], watchIgnore: [], instances: app.instances },
|
|
4684
|
+
process.cwd(),
|
|
4685
|
+
"cli"
|
|
4686
|
+
);
|
|
4687
|
+
return {
|
|
4688
|
+
...definition,
|
|
4689
|
+
name: app.name,
|
|
4690
|
+
baseName: app.baseName,
|
|
4691
|
+
instanceIndex: app.instanceIndex,
|
|
4692
|
+
instances: app.instances
|
|
4693
|
+
};
|
|
4694
|
+
}
|
|
4695
|
+
function deletePmMatches(matches) {
|
|
4696
|
+
for (const match of matches) {
|
|
4697
|
+
if ((0, import_node_fs5.existsSync)(match.record.logFiles.out)) {
|
|
4698
|
+
(0, import_node_fs5.rmSync)(match.record.logFiles.out, { force: true });
|
|
4699
|
+
}
|
|
4700
|
+
if ((0, import_node_fs5.existsSync)(match.record.logFiles.err)) {
|
|
4701
|
+
(0, import_node_fs5.rmSync)(match.record.logFiles.err, { force: true });
|
|
4702
|
+
}
|
|
4703
|
+
(0, import_node_fs5.rmSync)(match.filePath, { force: true });
|
|
4704
|
+
}
|
|
4705
|
+
}
|
|
4706
|
+
function updatePmInstanceCount(matches, instances) {
|
|
4707
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4708
|
+
for (const match of matches) {
|
|
4709
|
+
writePmRecord(match.filePath, {
|
|
4710
|
+
...match.record,
|
|
4711
|
+
instances,
|
|
4712
|
+
updatedAt: now
|
|
4713
|
+
});
|
|
4714
|
+
}
|
|
4715
|
+
}
|
|
4716
|
+
function normalizePmSignalName(value) {
|
|
4717
|
+
const trimmed = value.trim().toUpperCase();
|
|
4718
|
+
const signalName = trimmed.startsWith("SIG") ? trimmed : `SIG${trimmed}`;
|
|
4719
|
+
if (!PM_SIGNAL_NAMES.has(signalName)) {
|
|
4720
|
+
throw new Error(`Unsupported pm signal: ${value}`);
|
|
4721
|
+
}
|
|
4722
|
+
return signalName;
|
|
4723
|
+
}
|
|
4724
|
+
function resolveSignalablePid(record) {
|
|
4725
|
+
if (record.childPid && record.childPid > 0) {
|
|
4726
|
+
return record.childPid;
|
|
4727
|
+
}
|
|
4728
|
+
if (record.runnerPid && record.runnerPid > 0) {
|
|
4729
|
+
return record.runnerPid;
|
|
4730
|
+
}
|
|
4731
|
+
return void 0;
|
|
4732
|
+
}
|
|
4733
|
+
function groupPmMatchesByBaseName(matches) {
|
|
4734
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
4735
|
+
for (const match of matches) {
|
|
4736
|
+
const group = grouped.get(match.record.baseName);
|
|
4737
|
+
if (group) {
|
|
4738
|
+
group.push(match);
|
|
4739
|
+
continue;
|
|
4740
|
+
}
|
|
4741
|
+
grouped.set(match.record.baseName, [match]);
|
|
4742
|
+
}
|
|
4743
|
+
return [...grouped.entries()].sort((left, right) => left[0].localeCompare(right[0])).map(([, group]) => sortPmMatchesByInstance(group));
|
|
4744
|
+
}
|
|
4745
|
+
async function waitForPmRecordOnline(filePath, timeoutMs) {
|
|
4746
|
+
const deadline = Date.now() + timeoutMs;
|
|
4747
|
+
while (Date.now() < deadline) {
|
|
4748
|
+
if (!(0, import_node_fs5.existsSync)(filePath)) {
|
|
4749
|
+
break;
|
|
4750
|
+
}
|
|
4751
|
+
const record = readPmRecord(filePath);
|
|
4752
|
+
if (record.status === "online") {
|
|
4753
|
+
return record;
|
|
4754
|
+
}
|
|
4755
|
+
if (record.status === "errored" || record.status === "exited" || record.status === "stopped") {
|
|
4756
|
+
throw new Error(record.error ?? `Process ${record.name} failed while reloading.`);
|
|
4757
|
+
}
|
|
4758
|
+
await new Promise((resolvePromise) => setTimeout(resolvePromise, 50));
|
|
4759
|
+
}
|
|
4760
|
+
throw new Error(`Timed out waiting for the reloaded process to become online after ${timeoutMs}ms.`);
|
|
4761
|
+
}
|
|
4762
|
+
function resolvePmReloadReadyTimeout(record) {
|
|
4763
|
+
return record.waitReady || record.proxy?.strategy === "inherit" ? Math.max(record.listenTimeout + 1e3, 2e3) : Math.max(record.restartDelay + 1e3, 2e3);
|
|
4764
|
+
}
|
|
4765
|
+
function supportsPmProxyReload2(record) {
|
|
4766
|
+
return Boolean(record.proxy) && record.instances === 1 && Boolean(record.runnerPid);
|
|
4767
|
+
}
|
|
2995
4768
|
function padCell(value, width) {
|
|
2996
4769
|
return value.length >= width ? value : `${value}${" ".repeat(width - value.length)}`;
|
|
2997
4770
|
}
|
|
@@ -3002,8 +4775,255 @@ function tailLogFile(filePath, lineCount) {
|
|
|
3002
4775
|
const lines = (0, import_node_fs5.readFileSync)(filePath, "utf8").split(/\r?\n/).filter((line) => line.length > 0);
|
|
3003
4776
|
return lines.slice(-lineCount).join(import_node_os2.EOL);
|
|
3004
4777
|
}
|
|
3005
|
-
function
|
|
3006
|
-
|
|
4778
|
+
function listPmMatches(paths) {
|
|
4779
|
+
return listPmRecordMatches(paths).map(syncPmRecordLiveness);
|
|
4780
|
+
}
|
|
4781
|
+
function isPmRecordActive(record) {
|
|
4782
|
+
return record.desiredState === "running" && (record.status === "starting" || record.status === "online" || record.status === "restarting");
|
|
4783
|
+
}
|
|
4784
|
+
function resolvePmUptimeMs(record) {
|
|
4785
|
+
if (!isPmRecordActive(record) || !record.startedAt) {
|
|
4786
|
+
return void 0;
|
|
4787
|
+
}
|
|
4788
|
+
const startedTime = Date.parse(record.startedAt);
|
|
4789
|
+
if (Number.isNaN(startedTime)) {
|
|
4790
|
+
return void 0;
|
|
4791
|
+
}
|
|
4792
|
+
return Math.max(0, Date.now() - startedTime);
|
|
4793
|
+
}
|
|
4794
|
+
function resolvePmLiveMetrics(record) {
|
|
4795
|
+
const uptimeMs = resolvePmUptimeMs(record);
|
|
4796
|
+
if (!isPmRecordActive(record) || !record.childPid) {
|
|
4797
|
+
return { uptimeMs };
|
|
4798
|
+
}
|
|
4799
|
+
const sampledMetrics = samplePmProcessMetrics(record.childPid);
|
|
4800
|
+
return {
|
|
4801
|
+
...sampledMetrics,
|
|
4802
|
+
uptimeMs,
|
|
4803
|
+
updatedAt: sampledMetrics.cpuPercent !== void 0 || sampledMetrics.memoryRssBytes !== void 0 ? (/* @__PURE__ */ new Date()).toISOString() : void 0
|
|
4804
|
+
};
|
|
4805
|
+
}
|
|
4806
|
+
function toPmDisplayRecord(record) {
|
|
4807
|
+
return {
|
|
4808
|
+
record,
|
|
4809
|
+
liveMetrics: resolvePmLiveMetrics(record)
|
|
4810
|
+
};
|
|
4811
|
+
}
|
|
4812
|
+
function serializePmRecord(record) {
|
|
4813
|
+
return {
|
|
4814
|
+
...record,
|
|
4815
|
+
liveMetrics: resolvePmLiveMetrics(record)
|
|
4816
|
+
};
|
|
4817
|
+
}
|
|
4818
|
+
function parsePmFormatOption(args, index, option) {
|
|
4819
|
+
let value;
|
|
4820
|
+
if (option.startsWith("--format=")) {
|
|
4821
|
+
value = option.slice("--format=".length);
|
|
4822
|
+
} else {
|
|
4823
|
+
value = readRequiredValue(args, index + 1, "--format");
|
|
4824
|
+
index += 1;
|
|
4825
|
+
}
|
|
4826
|
+
if (value !== "table" && value !== "json") {
|
|
4827
|
+
throw new Error(`Unsupported pm output format: ${value}`);
|
|
4828
|
+
}
|
|
4829
|
+
return {
|
|
4830
|
+
format: value,
|
|
4831
|
+
nextIndex: index
|
|
4832
|
+
};
|
|
4833
|
+
}
|
|
4834
|
+
function parsePmListArgs(args) {
|
|
4835
|
+
let format = "table";
|
|
4836
|
+
for (let index = 0; index < args.length; index++) {
|
|
4837
|
+
const arg = args[index];
|
|
4838
|
+
switch (arg) {
|
|
4839
|
+
case "--json":
|
|
4840
|
+
format = "json";
|
|
4841
|
+
break;
|
|
4842
|
+
case "--format":
|
|
4843
|
+
default:
|
|
4844
|
+
if (arg === "--format" || arg.startsWith("--format=")) {
|
|
4845
|
+
const parsed = parsePmFormatOption(args, index, arg);
|
|
4846
|
+
format = parsed.format;
|
|
4847
|
+
index = parsed.nextIndex;
|
|
4848
|
+
break;
|
|
4849
|
+
}
|
|
4850
|
+
throw new Error(`Unknown pm list option: ${arg}`);
|
|
4851
|
+
}
|
|
4852
|
+
}
|
|
4853
|
+
return { format };
|
|
4854
|
+
}
|
|
4855
|
+
function parsePmShowArgs(args) {
|
|
4856
|
+
let format = "text";
|
|
4857
|
+
let name;
|
|
4858
|
+
for (let index = 0; index < args.length; index++) {
|
|
4859
|
+
const arg = args[index];
|
|
4860
|
+
switch (arg) {
|
|
4861
|
+
case "--json":
|
|
4862
|
+
format = "json";
|
|
4863
|
+
break;
|
|
4864
|
+
case "--format":
|
|
4865
|
+
default:
|
|
4866
|
+
if (arg === "--format" || arg.startsWith("--format=")) {
|
|
4867
|
+
const parsed = parsePmFormatOption(args, index, arg);
|
|
4868
|
+
format = parsed.format === "json" ? "json" : "text";
|
|
4869
|
+
index = parsed.nextIndex;
|
|
4870
|
+
break;
|
|
4871
|
+
}
|
|
4872
|
+
if (arg.startsWith("-")) {
|
|
4873
|
+
throw new Error(`Unknown pm show option: ${arg}`);
|
|
4874
|
+
}
|
|
4875
|
+
if (name) {
|
|
4876
|
+
throw new Error("pm show accepts exactly one process name.");
|
|
4877
|
+
}
|
|
4878
|
+
name = arg;
|
|
4879
|
+
break;
|
|
4880
|
+
}
|
|
4881
|
+
}
|
|
4882
|
+
if (!name) {
|
|
4883
|
+
throw new Error("Usage: elit pm show <name> [--json]");
|
|
4884
|
+
}
|
|
4885
|
+
return { name, format };
|
|
4886
|
+
}
|
|
4887
|
+
function formatPmDuration(durationMs) {
|
|
4888
|
+
if (durationMs < 1e3) {
|
|
4889
|
+
return `${durationMs}ms`;
|
|
4890
|
+
}
|
|
4891
|
+
const totalSeconds = Math.floor(durationMs / 1e3);
|
|
4892
|
+
const days = Math.floor(totalSeconds / 86400);
|
|
4893
|
+
const hours = Math.floor(totalSeconds % 86400 / 3600);
|
|
4894
|
+
const minutes = Math.floor(totalSeconds % 3600 / 60);
|
|
4895
|
+
const seconds = totalSeconds % 60;
|
|
4896
|
+
const parts = [];
|
|
4897
|
+
if (days > 0) {
|
|
4898
|
+
parts.push(`${days}d`);
|
|
4899
|
+
}
|
|
4900
|
+
if (hours > 0) {
|
|
4901
|
+
parts.push(`${hours}h`);
|
|
4902
|
+
}
|
|
4903
|
+
if (minutes > 0) {
|
|
4904
|
+
parts.push(`${minutes}m`);
|
|
4905
|
+
}
|
|
4906
|
+
if (seconds > 0 || parts.length === 0) {
|
|
4907
|
+
parts.push(`${seconds}s`);
|
|
4908
|
+
}
|
|
4909
|
+
return parts.slice(0, 2).join(" ");
|
|
4910
|
+
}
|
|
4911
|
+
function formatPmCpuPercent(cpuPercent) {
|
|
4912
|
+
if (cpuPercent === void 0 || !Number.isFinite(cpuPercent)) {
|
|
4913
|
+
return "-";
|
|
4914
|
+
}
|
|
4915
|
+
return `${cpuPercent >= 100 ? cpuPercent.toFixed(0) : cpuPercent.toFixed(1)}%`;
|
|
4916
|
+
}
|
|
4917
|
+
function formatPmMemory(memoryRssBytes) {
|
|
4918
|
+
if (memoryRssBytes === void 0 || !Number.isFinite(memoryRssBytes) || memoryRssBytes < 0) {
|
|
4919
|
+
return "-";
|
|
4920
|
+
}
|
|
4921
|
+
const units = ["B", "KB", "MB", "GB", "TB"];
|
|
4922
|
+
let value = memoryRssBytes;
|
|
4923
|
+
let unitIndex = 0;
|
|
4924
|
+
while (value >= 1024 && unitIndex < units.length - 1) {
|
|
4925
|
+
value /= 1024;
|
|
4926
|
+
unitIndex += 1;
|
|
4927
|
+
}
|
|
4928
|
+
const formatted = value >= 10 || unitIndex === 0 ? value.toFixed(0) : value.toFixed(1);
|
|
4929
|
+
return `${formatted}${units[unitIndex]}`;
|
|
4930
|
+
}
|
|
4931
|
+
function formatPmUptime(uptimeMs) {
|
|
4932
|
+
if (uptimeMs === void 0 || !Number.isFinite(uptimeMs)) {
|
|
4933
|
+
return "-";
|
|
4934
|
+
}
|
|
4935
|
+
return formatPmDuration(Math.max(0, uptimeMs));
|
|
4936
|
+
}
|
|
4937
|
+
function formatPmTarget(record) {
|
|
4938
|
+
if (record.script) {
|
|
4939
|
+
return record.script;
|
|
4940
|
+
}
|
|
4941
|
+
if (record.file) {
|
|
4942
|
+
return record.file;
|
|
4943
|
+
}
|
|
4944
|
+
if (record.wapk) {
|
|
4945
|
+
return record.wapk;
|
|
4946
|
+
}
|
|
4947
|
+
return "-";
|
|
4948
|
+
}
|
|
4949
|
+
function pushPmDetail(lines, label, value) {
|
|
4950
|
+
lines.push(`${padCell(`${label}:`, 18)} ${value}`);
|
|
4951
|
+
}
|
|
4952
|
+
function pushPmDetailList(lines, label, values) {
|
|
4953
|
+
if (values.length === 0) {
|
|
4954
|
+
pushPmDetail(lines, label, "-");
|
|
4955
|
+
return;
|
|
4956
|
+
}
|
|
4957
|
+
pushPmDetail(lines, label, values[0] ?? "-");
|
|
4958
|
+
for (const value of values.slice(1)) {
|
|
4959
|
+
lines.push(`${" ".repeat(20)} ${value}`);
|
|
4960
|
+
}
|
|
4961
|
+
}
|
|
4962
|
+
function formatPmRecordDetails(record, liveMetrics) {
|
|
4963
|
+
const lines = [`Process: ${record.name}`];
|
|
4964
|
+
pushPmDetail(lines, "id", record.id);
|
|
4965
|
+
pushPmDetail(lines, "status", record.status);
|
|
4966
|
+
pushPmDetail(lines, "desired state", record.desiredState);
|
|
4967
|
+
pushPmDetail(lines, "instance", `${record.instanceIndex}/${record.instances}`);
|
|
4968
|
+
pushPmDetail(lines, "cpu", formatPmCpuPercent(liveMetrics.cpuPercent));
|
|
4969
|
+
pushPmDetail(lines, "memory", formatPmMemory(liveMetrics.memoryRssBytes));
|
|
4970
|
+
pushPmDetail(lines, "uptime", formatPmUptime(liveMetrics.uptimeMs));
|
|
4971
|
+
pushPmDetail(lines, "type", record.type);
|
|
4972
|
+
pushPmDetail(lines, "source", record.source);
|
|
4973
|
+
pushPmDetail(lines, "runtime", record.runtime ?? "-");
|
|
4974
|
+
pushPmDetail(lines, "cwd", record.cwd);
|
|
4975
|
+
pushPmDetail(lines, "target", formatPmTarget(record));
|
|
4976
|
+
pushPmDetail(lines, "command", record.commandPreview || "-");
|
|
4977
|
+
pushPmDetail(lines, "runner pid", record.runnerPid ? String(record.runnerPid) : "-");
|
|
4978
|
+
pushPmDetail(lines, "child pid", record.childPid ? String(record.childPid) : "-");
|
|
4979
|
+
pushPmDetail(lines, "restart count", `${record.restartCount}/${record.maxRestarts}`);
|
|
4980
|
+
pushPmDetail(lines, "restart policy", record.restartPolicy);
|
|
4981
|
+
pushPmDetail(lines, "proxy", record.proxy ? `http://${record.proxy.host ?? "0.0.0.0"}:${record.proxy.port}` : "-");
|
|
4982
|
+
pushPmDetail(lines, "proxy strategy", record.proxy?.strategy ?? "-");
|
|
4983
|
+
pushPmDetail(lines, "proxy target", record.proxy && record.proxyTargetPort ? `${record.proxy.targetHost ?? "127.0.0.1"}:${record.proxyTargetPort}` : "-");
|
|
4984
|
+
pushPmDetail(lines, "max memory", record.maxMemoryBytes ? formatPmMemory(record.maxMemoryBytes) : "-");
|
|
4985
|
+
pushPmDetail(lines, "memory action", record.memoryAction ?? "-");
|
|
4986
|
+
pushPmDetail(lines, "cron restart", record.cronRestart ?? "-");
|
|
4987
|
+
pushPmDetail(lines, "exp backoff", record.expBackoffRestartDelay ? formatPmDuration(record.expBackoffRestartDelay) : "-");
|
|
4988
|
+
pushPmDetail(lines, "exp backoff max", record.expBackoffRestartMaxDelay ? formatPmDuration(record.expBackoffRestartMaxDelay) : "-");
|
|
4989
|
+
pushPmDetail(lines, "restart window", record.restartWindow ? formatPmDuration(record.restartWindow) : "-");
|
|
4990
|
+
pushPmDetail(lines, "wait ready", record.waitReady ? "enabled" : "disabled");
|
|
4991
|
+
pushPmDetail(lines, "listen timeout", record.waitReady ? formatPmDuration(record.listenTimeout) : "-");
|
|
4992
|
+
pushPmDetail(lines, "restart delay", formatPmDuration(record.restartDelay));
|
|
4993
|
+
pushPmDetail(lines, "kill timeout", formatPmDuration(record.killTimeout));
|
|
4994
|
+
pushPmDetail(lines, "min uptime", formatPmDuration(record.minUptime));
|
|
4995
|
+
pushPmDetail(lines, "autorestart", record.autorestart ? "enabled" : "disabled");
|
|
4996
|
+
pushPmDetail(lines, "watch", record.watch ? "enabled" : "disabled");
|
|
4997
|
+
pushPmDetail(lines, "watch debounce", record.watch ? formatPmDuration(record.watchDebounce) : "-");
|
|
4998
|
+
pushPmDetailList(lines, "watch paths", record.watchPaths);
|
|
4999
|
+
pushPmDetailList(lines, "watch ignore", record.watchIgnore);
|
|
5000
|
+
if (record.healthCheck) {
|
|
5001
|
+
pushPmDetail(lines, "health check", record.healthCheck.url);
|
|
5002
|
+
pushPmDetail(lines, "health grace", formatPmDuration(record.healthCheck.gracePeriod));
|
|
5003
|
+
pushPmDetail(lines, "health interval", formatPmDuration(record.healthCheck.interval));
|
|
5004
|
+
pushPmDetail(lines, "health timeout", formatPmDuration(record.healthCheck.timeout));
|
|
5005
|
+
pushPmDetail(lines, "health failures", String(record.healthCheck.maxFailures));
|
|
5006
|
+
} else {
|
|
5007
|
+
pushPmDetail(lines, "health check", "-");
|
|
5008
|
+
}
|
|
5009
|
+
pushPmDetailList(lines, "env", Object.entries(record.env).map(([key, value]) => `${key}=${value}`));
|
|
5010
|
+
pushPmDetail(lines, "stdout log", record.logFiles.out);
|
|
5011
|
+
pushPmDetail(lines, "stderr log", record.logFiles.err);
|
|
5012
|
+
pushPmDetail(lines, "created at", record.createdAt);
|
|
5013
|
+
pushPmDetail(lines, "updated at", record.updatedAt);
|
|
5014
|
+
pushPmDetail(lines, "metrics at", liveMetrics.updatedAt ?? "-");
|
|
5015
|
+
pushPmDetail(lines, "started at", record.startedAt ?? "-");
|
|
5016
|
+
pushPmDetail(lines, "stopped at", record.stoppedAt ?? "-");
|
|
5017
|
+
pushPmDetail(lines, "last exit", record.lastExitCode === void 0 ? "-" : String(record.lastExitCode));
|
|
5018
|
+
pushPmDetail(lines, "error", record.error ?? "-");
|
|
5019
|
+
return lines.join(import_node_os2.EOL);
|
|
5020
|
+
}
|
|
5021
|
+
function printPmList(paths, format = "table") {
|
|
5022
|
+
const matches = listPmMatches(paths).map((match) => toPmDisplayRecord(match.record));
|
|
5023
|
+
if (format === "json") {
|
|
5024
|
+
console.log(JSON.stringify(matches.map((match) => ({ ...match.record, liveMetrics: match.liveMetrics })), null, 2));
|
|
5025
|
+
return;
|
|
5026
|
+
}
|
|
3007
5027
|
if (matches.length === 0) {
|
|
3008
5028
|
console.log("No managed processes found.");
|
|
3009
5029
|
return;
|
|
@@ -3012,22 +5032,47 @@ function printPmList(paths) {
|
|
|
3012
5032
|
padCell("name", 20),
|
|
3013
5033
|
padCell("status", 12),
|
|
3014
5034
|
padCell("pid", 8),
|
|
5035
|
+
padCell("cpu", 8),
|
|
5036
|
+
padCell("memory", 10),
|
|
5037
|
+
padCell("uptime", 10),
|
|
3015
5038
|
padCell("restarts", 10),
|
|
3016
5039
|
padCell("type", 8),
|
|
3017
5040
|
"runtime"
|
|
3018
5041
|
];
|
|
3019
5042
|
console.log(headers.join(" "));
|
|
3020
|
-
for (const { record } of matches) {
|
|
5043
|
+
for (const { record, liveMetrics } of matches) {
|
|
3021
5044
|
console.log([
|
|
3022
5045
|
padCell(record.name, 20),
|
|
3023
5046
|
padCell(record.status, 12),
|
|
3024
5047
|
padCell(record.childPid ? String(record.childPid) : "-", 8),
|
|
5048
|
+
padCell(formatPmCpuPercent(liveMetrics.cpuPercent), 8),
|
|
5049
|
+
padCell(formatPmMemory(liveMetrics.memoryRssBytes), 10),
|
|
5050
|
+
padCell(formatPmUptime(liveMetrics.uptimeMs), 10),
|
|
3025
5051
|
padCell(String(record.restartCount ?? 0), 10),
|
|
3026
5052
|
padCell(record.type, 8),
|
|
3027
5053
|
record.runtime ?? "-"
|
|
3028
5054
|
].join(" "));
|
|
3029
5055
|
}
|
|
3030
5056
|
}
|
|
5057
|
+
async function runPmList(args) {
|
|
5058
|
+
const options = parsePmListArgs(args);
|
|
5059
|
+
const { paths } = await loadPmContext();
|
|
5060
|
+
printPmList(paths, options.format);
|
|
5061
|
+
}
|
|
5062
|
+
async function runPmShow(args) {
|
|
5063
|
+
const options = parsePmShowArgs(args);
|
|
5064
|
+
const { paths } = await loadPmContext();
|
|
5065
|
+
const match = resolveInspectableMatch(paths, options.name);
|
|
5066
|
+
if (!match) {
|
|
5067
|
+
throw new Error(`No managed process found for: ${options.name}`);
|
|
5068
|
+
}
|
|
5069
|
+
const synced = syncPmRecordLiveness(match);
|
|
5070
|
+
if (options.format === "json") {
|
|
5071
|
+
console.log(JSON.stringify(serializePmRecord(synced.record), null, 2));
|
|
5072
|
+
return;
|
|
5073
|
+
}
|
|
5074
|
+
console.log(formatPmRecordDetails(synced.record, resolvePmLiveMetrics(synced.record)));
|
|
5075
|
+
}
|
|
3031
5076
|
async function runPmStop(args) {
|
|
3032
5077
|
const target = args[0];
|
|
3033
5078
|
if (!target) {
|
|
@@ -3054,17 +5099,62 @@ async function runPmRestart(args) {
|
|
|
3054
5099
|
await stopPmMatches(matches);
|
|
3055
5100
|
const restarted = [];
|
|
3056
5101
|
for (const match of matches) {
|
|
3057
|
-
const definition =
|
|
3058
|
-
toPmAppConfig(match.record),
|
|
3059
|
-
{ name: match.record.name, env: {}, watchPaths: [], watchIgnore: [] },
|
|
3060
|
-
process.cwd(),
|
|
3061
|
-
match.record.source
|
|
3062
|
-
);
|
|
5102
|
+
const definition = rebuildPmRecordDefinition(match.record);
|
|
3063
5103
|
await startManagedProcess(definition, paths);
|
|
3064
5104
|
restarted.push(match.record.name);
|
|
3065
5105
|
}
|
|
3066
5106
|
console.log(`[pm] restarted ${restarted.join(", ")}`);
|
|
3067
5107
|
}
|
|
5108
|
+
async function runPmReload(args) {
|
|
5109
|
+
const target = args[0];
|
|
5110
|
+
if (!target) {
|
|
5111
|
+
throw new Error("Usage: elit pm reload <name|all>");
|
|
5112
|
+
}
|
|
5113
|
+
const { paths } = await loadPmContext();
|
|
5114
|
+
const matches = resolveNamedMatches(paths, target);
|
|
5115
|
+
if (matches.length === 0) {
|
|
5116
|
+
throw new Error(`No managed process found for: ${target}`);
|
|
5117
|
+
}
|
|
5118
|
+
const reloaded = [];
|
|
5119
|
+
const errors = [];
|
|
5120
|
+
for (const group of groupPmMatchesByBaseName(matches)) {
|
|
5121
|
+
for (const match of group) {
|
|
5122
|
+
try {
|
|
5123
|
+
if (supportsPmProxyReload2(match.record)) {
|
|
5124
|
+
const reloadRequestedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
5125
|
+
writePmRecord(match.filePath, {
|
|
5126
|
+
...match.record,
|
|
5127
|
+
status: "restarting",
|
|
5128
|
+
reloadRequestedAt,
|
|
5129
|
+
updatedAt: reloadRequestedAt,
|
|
5130
|
+
error: void 0
|
|
5131
|
+
});
|
|
5132
|
+
await waitForPmRecordOnline(
|
|
5133
|
+
getPmRecordPath(paths, match.record.id),
|
|
5134
|
+
resolvePmReloadReadyTimeout(match.record)
|
|
5135
|
+
);
|
|
5136
|
+
reloaded.push(match.record.name);
|
|
5137
|
+
continue;
|
|
5138
|
+
}
|
|
5139
|
+
await stopPmMatches([match]);
|
|
5140
|
+
const definition = rebuildPmRecordDefinition(match.record);
|
|
5141
|
+
const startedRecord = await startManagedProcess(definition, paths);
|
|
5142
|
+
await waitForPmRecordOnline(
|
|
5143
|
+
getPmRecordPath(paths, startedRecord.id),
|
|
5144
|
+
resolvePmReloadReadyTimeout(startedRecord)
|
|
5145
|
+
);
|
|
5146
|
+
reloaded.push(match.record.name);
|
|
5147
|
+
} catch (error) {
|
|
5148
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
5149
|
+
errors.push(`[pm] ${match.record.name}: ${message}`);
|
|
5150
|
+
}
|
|
5151
|
+
}
|
|
5152
|
+
}
|
|
5153
|
+
if (errors.length > 0) {
|
|
5154
|
+
throw new Error([`[pm] reloaded ${reloaded.length} process${reloaded.length === 1 ? "" : "es"}`, ...errors].join(import_node_os2.EOL));
|
|
5155
|
+
}
|
|
5156
|
+
console.log(`[pm] reloaded ${reloaded.join(", ")}`);
|
|
5157
|
+
}
|
|
3068
5158
|
async function runPmSave() {
|
|
3069
5159
|
const { paths } = await loadPmContext();
|
|
3070
5160
|
ensurePmDirectories(paths);
|
|
@@ -3086,12 +5176,7 @@ async function runPmResurrect() {
|
|
|
3086
5176
|
let restored = 0;
|
|
3087
5177
|
for (const app of dump.apps) {
|
|
3088
5178
|
try {
|
|
3089
|
-
const definition =
|
|
3090
|
-
toSavedPmAppConfig(app),
|
|
3091
|
-
{ name: app.name, env: {}, watchPaths: [], watchIgnore: [] },
|
|
3092
|
-
process.cwd(),
|
|
3093
|
-
"cli"
|
|
3094
|
-
);
|
|
5179
|
+
const definition = rebuildPmSavedDefinition(app);
|
|
3095
5180
|
await startManagedProcess(definition, paths);
|
|
3096
5181
|
restored += 1;
|
|
3097
5182
|
} catch (error) {
|
|
@@ -3115,16 +5200,127 @@ async function runPmDelete(args) {
|
|
|
3115
5200
|
throw new Error(`No managed process found for: ${target}`);
|
|
3116
5201
|
}
|
|
3117
5202
|
await stopPmMatches(matches);
|
|
5203
|
+
deletePmMatches(matches);
|
|
5204
|
+
console.log(`[pm] deleted ${matches.length} process${matches.length === 1 ? "" : "es"}`);
|
|
5205
|
+
}
|
|
5206
|
+
async function runPmScale(args) {
|
|
5207
|
+
const target = args[0];
|
|
5208
|
+
const countArg = args[1];
|
|
5209
|
+
if (!target || countArg === void 0 || args.length > 2) {
|
|
5210
|
+
throw new Error("Usage: elit pm scale <name> <count>");
|
|
5211
|
+
}
|
|
5212
|
+
const desiredCount = normalizeIntegerOption(countArg, "pm scale <count>", 0);
|
|
5213
|
+
const { config, paths } = await loadPmContext();
|
|
5214
|
+
const exactMatch = findPmRecordMatch(paths, target);
|
|
5215
|
+
const currentMatches = sortPmMatchesByInstance(resolveNamedMatches(paths, exactMatch?.record.baseName ?? target));
|
|
5216
|
+
if (currentMatches.length === 0) {
|
|
5217
|
+
if (desiredCount === 0) {
|
|
5218
|
+
console.log(`[pm] ${target} already scaled to 0 instances`);
|
|
5219
|
+
return;
|
|
5220
|
+
}
|
|
5221
|
+
const definitions = resolvePmStartDefinitions(
|
|
5222
|
+
{ name: target, env: {}, watchPaths: [], watchIgnore: [], instances: desiredCount },
|
|
5223
|
+
config,
|
|
5224
|
+
process.cwd()
|
|
5225
|
+
);
|
|
5226
|
+
for (const definition of definitions) {
|
|
5227
|
+
await startManagedProcess(definition, paths);
|
|
5228
|
+
}
|
|
5229
|
+
console.log(`[pm] scaled ${target} to ${desiredCount} instance${desiredCount === 1 ? "" : "s"}`);
|
|
5230
|
+
return;
|
|
5231
|
+
}
|
|
5232
|
+
const baseName = currentMatches[0]?.record.baseName ?? target;
|
|
5233
|
+
if (desiredCount === currentMatches.length) {
|
|
5234
|
+
console.log(`[pm] ${baseName} already scaled to ${desiredCount} instance${desiredCount === 1 ? "" : "s"}`);
|
|
5235
|
+
return;
|
|
5236
|
+
}
|
|
5237
|
+
if (desiredCount === 0) {
|
|
5238
|
+
await stopPmMatches(currentMatches);
|
|
5239
|
+
deletePmMatches(currentMatches);
|
|
5240
|
+
console.log(`[pm] scaled ${baseName} to 0 instances`);
|
|
5241
|
+
return;
|
|
5242
|
+
}
|
|
5243
|
+
if (desiredCount < currentMatches.length) {
|
|
5244
|
+
const toRemove = [...currentMatches].sort((left, right) => right.record.instanceIndex - left.record.instanceIndex).slice(0, currentMatches.length - desiredCount);
|
|
5245
|
+
const remaining = currentMatches.filter((match) => !toRemove.some((removal) => removal.record.id === match.record.id));
|
|
5246
|
+
await stopPmMatches(toRemove);
|
|
5247
|
+
deletePmMatches(toRemove);
|
|
5248
|
+
updatePmInstanceCount(remaining, desiredCount);
|
|
5249
|
+
console.log(`[pm] scaled ${baseName} to ${desiredCount} instance${desiredCount === 1 ? "" : "s"}`);
|
|
5250
|
+
return;
|
|
5251
|
+
}
|
|
5252
|
+
updatePmInstanceCount(currentMatches, desiredCount);
|
|
5253
|
+
const baseRecord = currentMatches[0]?.record;
|
|
5254
|
+
if (!baseRecord) {
|
|
5255
|
+
throw new Error(`No managed process found for: ${target}`);
|
|
5256
|
+
}
|
|
5257
|
+
const baseDefinition = rebuildPmRecordDefinition(baseRecord, desiredCount);
|
|
5258
|
+
const expandedDefinitions = expandPmInstanceDefinitions(baseDefinition, desiredCount);
|
|
5259
|
+
const existingNames = new Set(currentMatches.map((match) => match.record.name));
|
|
5260
|
+
for (const definition of expandedDefinitions) {
|
|
5261
|
+
if (existingNames.has(definition.name)) {
|
|
5262
|
+
continue;
|
|
5263
|
+
}
|
|
5264
|
+
await startManagedProcess(definition, paths);
|
|
5265
|
+
}
|
|
5266
|
+
console.log(`[pm] scaled ${baseName} to ${desiredCount} instance${desiredCount === 1 ? "" : "s"}`);
|
|
5267
|
+
}
|
|
5268
|
+
async function runPmSendSignal(args) {
|
|
5269
|
+
const signalArg = args[0];
|
|
5270
|
+
const target = args[1];
|
|
5271
|
+
if (!signalArg || !target || args.length > 2) {
|
|
5272
|
+
throw new Error("Usage: elit pm send-signal <signal> <name|all>");
|
|
5273
|
+
}
|
|
5274
|
+
const signalName = normalizePmSignalName(signalArg);
|
|
5275
|
+
const { paths } = await loadPmContext();
|
|
5276
|
+
const matches = resolveNamedMatches(paths, target);
|
|
5277
|
+
if (matches.length === 0) {
|
|
5278
|
+
throw new Error(`No managed process found for: ${target}`);
|
|
5279
|
+
}
|
|
5280
|
+
let signaled = 0;
|
|
5281
|
+
const errors = [];
|
|
3118
5282
|
for (const match of matches) {
|
|
3119
|
-
|
|
3120
|
-
|
|
5283
|
+
const pid = resolveSignalablePid(match.record);
|
|
5284
|
+
if (!pid) {
|
|
5285
|
+
continue;
|
|
3121
5286
|
}
|
|
3122
|
-
|
|
3123
|
-
(
|
|
5287
|
+
try {
|
|
5288
|
+
sendPmSignal(pid, signalName);
|
|
5289
|
+
signaled += 1;
|
|
5290
|
+
} catch (error) {
|
|
5291
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
5292
|
+
errors.push(`[pm] ${match.record.name}: ${message}`);
|
|
3124
5293
|
}
|
|
3125
|
-
(0, import_node_fs5.rmSync)(match.filePath, { force: true });
|
|
3126
5294
|
}
|
|
3127
|
-
|
|
5295
|
+
if (errors.length > 0) {
|
|
5296
|
+
throw new Error([`[pm] sent ${signalName} to ${signaled} process${signaled === 1 ? "" : "es"}`, ...errors].join(import_node_os2.EOL));
|
|
5297
|
+
}
|
|
5298
|
+
if (signaled === 0) {
|
|
5299
|
+
throw new Error(`No running managed process found for: ${target}`);
|
|
5300
|
+
}
|
|
5301
|
+
console.log(`[pm] sent ${signalName} to ${signaled} process${signaled === 1 ? "" : "es"}`);
|
|
5302
|
+
}
|
|
5303
|
+
async function runPmReset(args) {
|
|
5304
|
+
const target = args[0];
|
|
5305
|
+
if (!target) {
|
|
5306
|
+
throw new Error("Usage: elit pm reset <name|all>");
|
|
5307
|
+
}
|
|
5308
|
+
const { paths } = await loadPmContext();
|
|
5309
|
+
const matches = resolveNamedMatches(paths, target);
|
|
5310
|
+
if (matches.length === 0) {
|
|
5311
|
+
throw new Error(`No managed process found for: ${target}`);
|
|
5312
|
+
}
|
|
5313
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
5314
|
+
for (const match of matches) {
|
|
5315
|
+
writePmRecord(match.filePath, {
|
|
5316
|
+
...match.record,
|
|
5317
|
+
restartCount: 0,
|
|
5318
|
+
lastExitCode: void 0,
|
|
5319
|
+
error: void 0,
|
|
5320
|
+
updatedAt: now
|
|
5321
|
+
});
|
|
5322
|
+
}
|
|
5323
|
+
console.log(`[pm] reset ${matches.length} process${matches.length === 1 ? "" : "es"}`);
|
|
3128
5324
|
}
|
|
3129
5325
|
async function runPmLogs(args) {
|
|
3130
5326
|
if (args.length === 0) {
|
|
@@ -3157,7 +5353,7 @@ async function runPmLogs(args) {
|
|
|
3157
5353
|
throw new Error("Usage: elit pm logs <name> [--lines <n>] [--stderr]");
|
|
3158
5354
|
}
|
|
3159
5355
|
const { paths } = await loadPmContext();
|
|
3160
|
-
const match =
|
|
5356
|
+
const match = resolveInspectableMatch(paths, name);
|
|
3161
5357
|
if (!match) {
|
|
3162
5358
|
throw new Error(`No managed process found for: ${name}`);
|
|
3163
5359
|
}
|
|
@@ -3181,17 +5377,36 @@ async function runPmCommand(args) {
|
|
|
3181
5377
|
await runPmStart(args.slice(1));
|
|
3182
5378
|
return;
|
|
3183
5379
|
case "list":
|
|
3184
|
-
case "ls":
|
|
3185
|
-
|
|
3186
|
-
|
|
5380
|
+
case "ls":
|
|
5381
|
+
await runPmList(args.slice(1));
|
|
5382
|
+
return;
|
|
5383
|
+
case "jlist":
|
|
5384
|
+
await runPmList(["--json", ...args.slice(1)]);
|
|
5385
|
+
return;
|
|
5386
|
+
case "show":
|
|
5387
|
+
case "describe":
|
|
5388
|
+
await runPmShow(args.slice(1));
|
|
3187
5389
|
return;
|
|
3188
|
-
}
|
|
3189
5390
|
case "stop":
|
|
3190
5391
|
await runPmStop(args.slice(1));
|
|
3191
5392
|
return;
|
|
3192
5393
|
case "restart":
|
|
3193
5394
|
await runPmRestart(args.slice(1));
|
|
3194
5395
|
return;
|
|
5396
|
+
case "reload":
|
|
5397
|
+
await runPmReload(args.slice(1));
|
|
5398
|
+
return;
|
|
5399
|
+
case "scale":
|
|
5400
|
+
await runPmScale(args.slice(1));
|
|
5401
|
+
return;
|
|
5402
|
+
case "send-signal":
|
|
5403
|
+
case "signal":
|
|
5404
|
+
case "sendSignal":
|
|
5405
|
+
await runPmSendSignal(args.slice(1));
|
|
5406
|
+
return;
|
|
5407
|
+
case "reset":
|
|
5408
|
+
await runPmReset(args.slice(1));
|
|
5409
|
+
return;
|
|
3195
5410
|
case "delete":
|
|
3196
5411
|
case "remove":
|
|
3197
5412
|
case "rm":
|
|
@@ -3224,6 +5439,12 @@ async function runPmCommand(args) {
|
|
|
3224
5439
|
DEFAULT_MIN_UPTIME,
|
|
3225
5440
|
DEFAULT_PM_DATA_DIR,
|
|
3226
5441
|
DEFAULT_PM_DUMP_FILE,
|
|
5442
|
+
DEFAULT_PM_EXP_BACKOFF_MAX_DELAY,
|
|
5443
|
+
DEFAULT_PM_KILL_TIMEOUT,
|
|
5444
|
+
DEFAULT_PM_LISTEN_TIMEOUT,
|
|
5445
|
+
DEFAULT_PM_MEMORY_CHECK_INTERVAL,
|
|
5446
|
+
DEFAULT_PM_PROXY_STRATEGY,
|
|
5447
|
+
DEFAULT_PM_RESTART_WINDOW,
|
|
3227
5448
|
DEFAULT_PM_STOP_GRACE_PERIOD_MS,
|
|
3228
5449
|
DEFAULT_PM_STOP_POLL_MS,
|
|
3229
5450
|
DEFAULT_RESTART_DELAY,
|
|
@@ -3242,7 +5463,10 @@ async function runPmCommand(args) {
|
|
|
3242
5463
|
countDefinedPmWapkSources,
|
|
3243
5464
|
deriveDefaultWatchPaths,
|
|
3244
5465
|
ensurePmDirectories,
|
|
5466
|
+
expandPmInstanceDefinitions,
|
|
5467
|
+
findPmGroupMatches,
|
|
3245
5468
|
findPmRecordMatch,
|
|
5469
|
+
formatPmInstanceName,
|
|
3246
5470
|
getPmRecordPath,
|
|
3247
5471
|
hasPmGoogleDriveConfig,
|
|
3248
5472
|
hasPmWapkRunConfig,
|
|
@@ -3261,6 +5485,10 @@ async function runPmCommand(args) {
|
|
|
3261
5485
|
normalizeHealthCheckConfig,
|
|
3262
5486
|
normalizeIntegerOption,
|
|
3263
5487
|
normalizeNonEmptyString,
|
|
5488
|
+
normalizePmMemoryAction,
|
|
5489
|
+
normalizePmMemoryLimit,
|
|
5490
|
+
normalizePmProxyConfig,
|
|
5491
|
+
normalizePmProxyStrategy,
|
|
3264
5492
|
normalizePmRestartPolicy,
|
|
3265
5493
|
normalizePmRuntime,
|
|
3266
5494
|
normalizeResolvedWatchIgnorePaths,
|
|
@@ -3284,7 +5512,9 @@ async function runPmCommand(args) {
|
|
|
3284
5512
|
runManagedProcessLoop,
|
|
3285
5513
|
runPmCommand,
|
|
3286
5514
|
runPmRunner,
|
|
5515
|
+
samplePmProcessMetrics,
|
|
3287
5516
|
sanitizePmProcessName,
|
|
5517
|
+
sendPmSignal,
|
|
3288
5518
|
startManagedProcess,
|
|
3289
5519
|
stopPmMatches,
|
|
3290
5520
|
stripPmWapkSourceFromRunConfig,
|