elit 3.6.7 → 3.6.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Cargo.lock +1 -1
- package/Cargo.toml +1 -1
- package/README.md +20 -1
- package/dist/cli.cjs +2496 -303
- package/dist/cli.mjs +2501 -308
- package/dist/config.d.ts +6 -6
- package/dist/{contracts-BeW9k0yZ.d.ts → contracts-_0p1-15U.d.ts} +1 -1
- package/dist/coverage.d.ts +1 -1
- package/dist/dev-build.d.ts +3 -3
- package/dist/http.cjs +160 -41
- package/dist/http.d.ts +5 -11
- package/dist/http.js +160 -41
- package/dist/http.mjs +160 -41
- package/dist/https.cjs +194 -46
- package/dist/https.d.ts +3 -6
- package/dist/https.js +194 -46
- package/dist/https.mjs +194 -46
- package/dist/pm-node-shared-listener-bootstrap.cjs +75 -0
- package/dist/pm.cjs +2101 -134
- package/dist/pm.d.ts +83 -8
- package/dist/pm.js +2095 -158
- package/dist/pm.mjs +2091 -139
- package/dist/preview-build.d.ts +3 -3
- package/dist/server.cjs +417 -168
- package/dist/server.d.ts +4 -4
- package/dist/server.js +453 -179
- package/dist/server.mjs +417 -168
- package/dist/smtp-server.js +37 -12
- package/dist/test-reporter.d.ts +1 -1
- package/dist/test-runtime.d.ts +1 -1
- package/dist/{types-tJn88E1N.d.ts → types-BayMVo_k.d.ts} +39 -3
- package/dist/{types-CIhpN1-K.d.ts → types-C70T-42Z.d.ts} +1 -1
- package/dist/{types-DAisuVr5.d.ts → types-DPOgoGs-.d.ts} +7 -1
- package/dist/{state-DvEkDehk.d.ts → types-fiLday0L.d.ts} +96 -92
- package/dist/types.d.ts +4 -0
- package/dist/{websocket-XfyK23zD.d.ts → websocket-BLBEAnhp.d.ts} +1 -1
- package/dist/ws.d.ts +3 -3
- package/dist/wss.cjs +194 -46
- package/dist/wss.d.ts +3 -3
- package/dist/wss.js +194 -46
- package/dist/wss.mjs +194 -46
- package/package.json +11 -6
package/dist/pm.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,9 +2792,210 @@ function watch(paths, options) {
|
|
|
2233
2792
|
return watcher;
|
|
2234
2793
|
}
|
|
2235
2794
|
|
|
2795
|
+
// src/cli/pm/proxy.ts
|
|
2796
|
+
var import_node_http = require("http");
|
|
2797
|
+
var import_node_https = require("https");
|
|
2798
|
+
var import_node_net = require("net");
|
|
2799
|
+
function resolvePmProxyHost(proxy) {
|
|
2800
|
+
return proxy.host?.trim() || "0.0.0.0";
|
|
2801
|
+
}
|
|
2802
|
+
function resolvePmProxyTargetHost(proxy) {
|
|
2803
|
+
return proxy.targetHost?.trim() || "127.0.0.1";
|
|
2804
|
+
}
|
|
2805
|
+
function resolvePmProxyEnvVar(proxy) {
|
|
2806
|
+
return proxy.envVar?.trim() || "PORT";
|
|
2807
|
+
}
|
|
2808
|
+
function buildPmProxyTargetUrl(proxy, targetPort) {
|
|
2809
|
+
return `http://${resolvePmProxyTargetHost(proxy)}:${targetPort}`;
|
|
2810
|
+
}
|
|
2811
|
+
function rewritePmProxyHealthCheckUrl(url, targetHost, targetPort) {
|
|
2812
|
+
const targetUrl = new URL(url);
|
|
2813
|
+
targetUrl.hostname = targetHost;
|
|
2814
|
+
targetUrl.port = String(targetPort);
|
|
2815
|
+
return targetUrl.toString();
|
|
2816
|
+
}
|
|
2817
|
+
async function allocatePmProxyTargetPort(host = "127.0.0.1") {
|
|
2818
|
+
const server = (0, import_node_net.createServer)();
|
|
2819
|
+
return await new Promise((resolve7, reject) => {
|
|
2820
|
+
server.once("error", reject);
|
|
2821
|
+
server.listen(0, host, () => {
|
|
2822
|
+
const address = server.address();
|
|
2823
|
+
if (!address || typeof address === "string") {
|
|
2824
|
+
server.close(() => reject(new Error("Failed to allocate an internal PM proxy port.")));
|
|
2825
|
+
return;
|
|
2826
|
+
}
|
|
2827
|
+
const port = address.port;
|
|
2828
|
+
server.close((error) => {
|
|
2829
|
+
if (error) {
|
|
2830
|
+
reject(error);
|
|
2831
|
+
return;
|
|
2832
|
+
}
|
|
2833
|
+
resolve7(port);
|
|
2834
|
+
});
|
|
2835
|
+
});
|
|
2836
|
+
});
|
|
2837
|
+
}
|
|
2838
|
+
function buildPmProxyHeaders(headersInput, host) {
|
|
2839
|
+
const headers = {};
|
|
2840
|
+
for (const [key, value] of Object.entries(headersInput)) {
|
|
2841
|
+
if (value !== void 0) {
|
|
2842
|
+
headers[key] = value;
|
|
2843
|
+
}
|
|
2844
|
+
}
|
|
2845
|
+
headers.host = host;
|
|
2846
|
+
return headers;
|
|
2847
|
+
}
|
|
2848
|
+
function writeRawHttpResponse(socket, statusCode, statusMessage, headers) {
|
|
2849
|
+
const lines = [`HTTP/1.1 ${statusCode} ${statusMessage}`];
|
|
2850
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
2851
|
+
if (value === void 0) {
|
|
2852
|
+
continue;
|
|
2853
|
+
}
|
|
2854
|
+
if (Array.isArray(value)) {
|
|
2855
|
+
for (const item of value) {
|
|
2856
|
+
lines.push(`${key}: ${item}`);
|
|
2857
|
+
}
|
|
2858
|
+
continue;
|
|
2859
|
+
}
|
|
2860
|
+
lines.push(`${key}: ${value}`);
|
|
2861
|
+
}
|
|
2862
|
+
socket.write(`${lines.join("\r\n")}\r
|
|
2863
|
+
\r
|
|
2864
|
+
`);
|
|
2865
|
+
}
|
|
2866
|
+
async function createPmProxyController(proxy) {
|
|
2867
|
+
let targets = [];
|
|
2868
|
+
let nextTargetIndex = 0;
|
|
2869
|
+
const setResolvedTargets = (nextTargets) => {
|
|
2870
|
+
const unchanged = nextTargets.length === targets.length && nextTargets.every((target, index) => targets[index]?.href === target.href);
|
|
2871
|
+
targets = nextTargets;
|
|
2872
|
+
if (targets.length === 0) {
|
|
2873
|
+
nextTargetIndex = 0;
|
|
2874
|
+
return;
|
|
2875
|
+
}
|
|
2876
|
+
if (!unchanged) {
|
|
2877
|
+
nextTargetIndex = nextTargetIndex % targets.length;
|
|
2878
|
+
}
|
|
2879
|
+
};
|
|
2880
|
+
const pickTarget = () => {
|
|
2881
|
+
if (targets.length === 0) {
|
|
2882
|
+
return null;
|
|
2883
|
+
}
|
|
2884
|
+
const target = targets[nextTargetIndex % targets.length];
|
|
2885
|
+
nextTargetIndex = (nextTargetIndex + 1) % targets.length;
|
|
2886
|
+
return target;
|
|
2887
|
+
};
|
|
2888
|
+
const server = (0, import_node_http.createServer)((req, res) => {
|
|
2889
|
+
const target = pickTarget();
|
|
2890
|
+
if (!target) {
|
|
2891
|
+
res.statusCode = 503;
|
|
2892
|
+
res.end("PM proxy target is not ready.");
|
|
2893
|
+
return;
|
|
2894
|
+
}
|
|
2895
|
+
const requestLib = target.protocol === "https:" ? import_node_https.request : import_node_http.request;
|
|
2896
|
+
const targetUrl = new URL(req.url || "/", target);
|
|
2897
|
+
const headers = buildPmProxyHeaders(req.headers, target.host);
|
|
2898
|
+
const proxyReq = requestLib(targetUrl, {
|
|
2899
|
+
method: req.method,
|
|
2900
|
+
headers
|
|
2901
|
+
}, (proxyRes) => {
|
|
2902
|
+
const outgoingHeaders = {};
|
|
2903
|
+
for (const [key, value] of Object.entries(proxyRes.headers)) {
|
|
2904
|
+
if (value !== void 0) {
|
|
2905
|
+
outgoingHeaders[key] = value;
|
|
2906
|
+
}
|
|
2907
|
+
}
|
|
2908
|
+
res.writeHead(proxyRes.statusCode || 200, outgoingHeaders);
|
|
2909
|
+
proxyRes.pipe(res);
|
|
2910
|
+
});
|
|
2911
|
+
proxyReq.on("error", (error) => {
|
|
2912
|
+
if (!res.headersSent) {
|
|
2913
|
+
res.statusCode = 502;
|
|
2914
|
+
}
|
|
2915
|
+
res.end(`PM proxy error: ${error.message}`);
|
|
2916
|
+
});
|
|
2917
|
+
req.pipe(proxyReq);
|
|
2918
|
+
});
|
|
2919
|
+
server.on("upgrade", (req, socket, head) => {
|
|
2920
|
+
const target = pickTarget();
|
|
2921
|
+
if (!target) {
|
|
2922
|
+
writeRawHttpResponse(socket, 503, "Service Unavailable", {
|
|
2923
|
+
connection: "close",
|
|
2924
|
+
"content-length": 0
|
|
2925
|
+
});
|
|
2926
|
+
socket.destroy();
|
|
2927
|
+
return;
|
|
2928
|
+
}
|
|
2929
|
+
const requestLib = target.protocol === "https:" ? import_node_https.request : import_node_http.request;
|
|
2930
|
+
const targetUrl = new URL(req.url || "/", target);
|
|
2931
|
+
const proxyReq = requestLib(targetUrl, {
|
|
2932
|
+
method: req.method,
|
|
2933
|
+
headers: buildPmProxyHeaders(req.headers, target.host)
|
|
2934
|
+
});
|
|
2935
|
+
proxyReq.on("upgrade", (proxyRes, proxySocket, proxyHead) => {
|
|
2936
|
+
writeRawHttpResponse(socket, proxyRes.statusCode || 101, proxyRes.statusMessage || "Switching Protocols", proxyRes.headers);
|
|
2937
|
+
if (head.length > 0) {
|
|
2938
|
+
proxySocket.write(head);
|
|
2939
|
+
}
|
|
2940
|
+
if (proxyHead.length > 0) {
|
|
2941
|
+
socket.write(proxyHead);
|
|
2942
|
+
}
|
|
2943
|
+
socket.on("error", () => proxySocket.destroy());
|
|
2944
|
+
proxySocket.on("error", () => socket.destroy());
|
|
2945
|
+
proxySocket.pipe(socket);
|
|
2946
|
+
socket.pipe(proxySocket);
|
|
2947
|
+
});
|
|
2948
|
+
proxyReq.on("response", (proxyRes) => {
|
|
2949
|
+
writeRawHttpResponse(socket, proxyRes.statusCode || 502, proxyRes.statusMessage || "Bad Gateway", proxyRes.headers);
|
|
2950
|
+
proxyRes.pipe(socket);
|
|
2951
|
+
});
|
|
2952
|
+
proxyReq.on("error", (error) => {
|
|
2953
|
+
writeRawHttpResponse(socket, 502, "Bad Gateway", {
|
|
2954
|
+
connection: "close",
|
|
2955
|
+
"content-type": "text/plain; charset=utf-8",
|
|
2956
|
+
"content-length": Buffer.byteLength(`PM proxy error: ${error.message}`)
|
|
2957
|
+
});
|
|
2958
|
+
socket.end(`PM proxy error: ${error.message}`);
|
|
2959
|
+
});
|
|
2960
|
+
proxyReq.end();
|
|
2961
|
+
});
|
|
2962
|
+
await new Promise((resolve7, reject) => {
|
|
2963
|
+
server.once("error", reject);
|
|
2964
|
+
server.listen(proxy.port, resolvePmProxyHost(proxy), () => resolve7());
|
|
2965
|
+
});
|
|
2966
|
+
return {
|
|
2967
|
+
setTarget(targetUrl) {
|
|
2968
|
+
setResolvedTargets(targetUrl ? [new URL(targetUrl)] : []);
|
|
2969
|
+
},
|
|
2970
|
+
setTargets(targetUrls) {
|
|
2971
|
+
setResolvedTargets(targetUrls.map((targetUrl) => new URL(targetUrl)));
|
|
2972
|
+
},
|
|
2973
|
+
close() {
|
|
2974
|
+
return new Promise((resolve7, reject) => {
|
|
2975
|
+
server.close((error) => {
|
|
2976
|
+
if (error) {
|
|
2977
|
+
reject(error);
|
|
2978
|
+
return;
|
|
2979
|
+
}
|
|
2980
|
+
resolve7();
|
|
2981
|
+
});
|
|
2982
|
+
});
|
|
2983
|
+
}
|
|
2984
|
+
};
|
|
2985
|
+
}
|
|
2986
|
+
|
|
2236
2987
|
// src/cli/pm/runner.ts
|
|
2237
2988
|
function writePmLog(stream, message) {
|
|
2238
|
-
stream.
|
|
2989
|
+
if (stream.writableEnded || stream.destroyed) {
|
|
2990
|
+
return;
|
|
2991
|
+
}
|
|
2992
|
+
try {
|
|
2993
|
+
stream.write(`[elit pm] ${(/* @__PURE__ */ new Date()).toISOString()} ${message}${import_node_os.EOL}`);
|
|
2994
|
+
} catch (error) {
|
|
2995
|
+
if (error.code !== "ERR_STREAM_WRITE_AFTER_END") {
|
|
2996
|
+
throw error;
|
|
2997
|
+
}
|
|
2998
|
+
}
|
|
2239
2999
|
}
|
|
2240
3000
|
function waitForExit(code, signal) {
|
|
2241
3001
|
if (typeof code === "number") {
|
|
@@ -2249,40 +3009,369 @@ function waitForExit(code, signal) {
|
|
|
2249
3009
|
async function delay(milliseconds) {
|
|
2250
3010
|
await new Promise((resolvePromise) => setTimeout(resolvePromise, milliseconds));
|
|
2251
3011
|
}
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
await delay(DEFAULT_PM_STOP_POLL_MS);
|
|
2262
|
-
}
|
|
2263
|
-
return !isProcessAlive(pid);
|
|
3012
|
+
function resolveRunnerPathsFromRecordFile(filePath) {
|
|
3013
|
+
const appsDir = (0, import_node_path6.dirname)(filePath);
|
|
3014
|
+
const dataDir = (0, import_node_path6.dirname)(appsDir);
|
|
3015
|
+
return {
|
|
3016
|
+
dataDir,
|
|
3017
|
+
appsDir,
|
|
3018
|
+
logsDir: (0, import_node_path6.join)(dataDir, "logs"),
|
|
3019
|
+
dumpFile: (0, import_node_path6.join)(dataDir, DEFAULT_PM_DUMP_FILE)
|
|
3020
|
+
};
|
|
2264
3021
|
}
|
|
2265
|
-
|
|
2266
|
-
return
|
|
2267
|
-
let resolved = false;
|
|
2268
|
-
child.once("error", (error) => {
|
|
2269
|
-
if (resolved) {
|
|
2270
|
-
return;
|
|
2271
|
-
}
|
|
2272
|
-
resolved = true;
|
|
2273
|
-
resolvePromise({ code: 1, signal: null, error: error instanceof Error ? error.message : String(error) });
|
|
2274
|
-
});
|
|
2275
|
-
child.once("close", (code, signal) => {
|
|
2276
|
-
if (resolved) {
|
|
2277
|
-
return;
|
|
2278
|
-
}
|
|
2279
|
-
resolved = true;
|
|
2280
|
-
resolvePromise({ code, signal });
|
|
2281
|
-
});
|
|
2282
|
-
});
|
|
3022
|
+
function usesPmProxyController(record) {
|
|
3023
|
+
return Boolean(record.proxy) && (record.proxy?.strategy ?? DEFAULT_PM_PROXY_STRATEGY) === "proxy";
|
|
2283
3024
|
}
|
|
2284
|
-
|
|
2285
|
-
|
|
3025
|
+
function usesPmInheritedListener(record) {
|
|
3026
|
+
return Boolean(record.proxy) && (record.proxy?.strategy ?? DEFAULT_PM_PROXY_STRATEGY) === "inherit";
|
|
3027
|
+
}
|
|
3028
|
+
function isPmProxyOwner(record) {
|
|
3029
|
+
return usesPmProxyController(record) && record.instanceIndex === 1;
|
|
3030
|
+
}
|
|
3031
|
+
function resolvePmProxyTargetUrls(paths, baseName) {
|
|
3032
|
+
return listPmRecordMatches(paths).filter((match) => match.record.baseName === baseName).filter((match) => usesPmProxyController(match.record)).filter((match) => match.record.desiredState === "running" && Boolean(match.record.proxyReadyAt)).filter((match) => typeof match.record.proxyTargetPort === "number" && match.record.proxyTargetPort > 0).sort((left, right) => left.record.instanceIndex - right.record.instanceIndex).map((match) => buildPmProxyTargetUrl(match.record.proxy, match.record.proxyTargetPort));
|
|
3033
|
+
}
|
|
3034
|
+
async function createPmInheritedListener(proxy) {
|
|
3035
|
+
const server = (0, import_node_http2.createServer)();
|
|
3036
|
+
await new Promise((resolvePromise, reject) => {
|
|
3037
|
+
server.once("error", reject);
|
|
3038
|
+
server.listen(proxy.port, resolvePmProxyHost(proxy), () => resolvePromise());
|
|
3039
|
+
});
|
|
3040
|
+
return server;
|
|
3041
|
+
}
|
|
3042
|
+
function createPmChildIpcState(child, sharedListener) {
|
|
3043
|
+
let bootstrapReady = false;
|
|
3044
|
+
let listenerReady = false;
|
|
3045
|
+
let sharedHandleSent = false;
|
|
3046
|
+
const sendSharedHandle = () => {
|
|
3047
|
+
if (!sharedListener || !bootstrapReady || sharedHandleSent || !child.connected) {
|
|
3048
|
+
return;
|
|
3049
|
+
}
|
|
3050
|
+
sharedHandleSent = true;
|
|
3051
|
+
child.send?.({ type: "elit:pm:listen-handle" }, sharedListener);
|
|
3052
|
+
};
|
|
3053
|
+
const onMessage = (message) => {
|
|
3054
|
+
if (!message || typeof message !== "object") {
|
|
3055
|
+
return;
|
|
3056
|
+
}
|
|
3057
|
+
if (message.type === "elit:pm:bootstrap-ready") {
|
|
3058
|
+
bootstrapReady = true;
|
|
3059
|
+
sendSharedHandle();
|
|
3060
|
+
return;
|
|
3061
|
+
}
|
|
3062
|
+
if (message.type === "elit:pm:listener-ready") {
|
|
3063
|
+
listenerReady = true;
|
|
3064
|
+
}
|
|
3065
|
+
};
|
|
3066
|
+
child.on("message", onMessage);
|
|
3067
|
+
return {
|
|
3068
|
+
get bootstrapReady() {
|
|
3069
|
+
return bootstrapReady;
|
|
3070
|
+
},
|
|
3071
|
+
get listenerReady() {
|
|
3072
|
+
return listenerReady;
|
|
3073
|
+
},
|
|
3074
|
+
stop() {
|
|
3075
|
+
child.off("message", onMessage);
|
|
3076
|
+
}
|
|
3077
|
+
};
|
|
3078
|
+
}
|
|
3079
|
+
function buildPmChildEnv(record, command, targetPort) {
|
|
3080
|
+
return {
|
|
3081
|
+
...process.env,
|
|
3082
|
+
...record.env,
|
|
3083
|
+
...command.env,
|
|
3084
|
+
...usesPmProxyController(record) && targetPort ? {
|
|
3085
|
+
[resolvePmProxyEnvVar(record.proxy)]: String(targetPort),
|
|
3086
|
+
ELIT_PM_PUBLIC_PORT: String(record.proxy.port)
|
|
3087
|
+
} : {},
|
|
3088
|
+
ELIT_PM_NAME: record.name,
|
|
3089
|
+
ELIT_PM_ID: record.id
|
|
3090
|
+
};
|
|
3091
|
+
}
|
|
3092
|
+
function buildPmChildStdio(command, onlineStdinShutdownEnabled) {
|
|
3093
|
+
return [
|
|
3094
|
+
onlineStdinShutdownEnabled ? "pipe" : "ignore",
|
|
3095
|
+
"pipe",
|
|
3096
|
+
"pipe",
|
|
3097
|
+
...command.ipc ? ["ipc"] : []
|
|
3098
|
+
];
|
|
3099
|
+
}
|
|
3100
|
+
function createPmReadinessMonitor(record, onReady, onFailure, options) {
|
|
3101
|
+
if (options?.ipcController) {
|
|
3102
|
+
let stopped2 = false;
|
|
3103
|
+
let timer2 = null;
|
|
3104
|
+
let timeoutTimer2 = null;
|
|
3105
|
+
const clearTimers2 = () => {
|
|
3106
|
+
if (timer2) {
|
|
3107
|
+
clearInterval(timer2);
|
|
3108
|
+
timer2 = null;
|
|
3109
|
+
}
|
|
3110
|
+
if (timeoutTimer2) {
|
|
3111
|
+
clearTimeout(timeoutTimer2);
|
|
3112
|
+
timeoutTimer2 = null;
|
|
3113
|
+
}
|
|
3114
|
+
};
|
|
3115
|
+
const host = record.proxy?.host ?? "0.0.0.0";
|
|
3116
|
+
const port = record.proxy?.port ?? 0;
|
|
3117
|
+
timer2 = setInterval(() => {
|
|
3118
|
+
if (stopped2 || !options.ipcController?.listenerReady) {
|
|
3119
|
+
return;
|
|
3120
|
+
}
|
|
3121
|
+
stopped2 = true;
|
|
3122
|
+
clearTimers2();
|
|
3123
|
+
onReady(`shared listener ready on ${host}:${port}`);
|
|
3124
|
+
}, 25);
|
|
3125
|
+
timer2.unref?.();
|
|
3126
|
+
timeoutTimer2 = setTimeout(() => {
|
|
3127
|
+
if (stopped2) {
|
|
3128
|
+
return;
|
|
3129
|
+
}
|
|
3130
|
+
stopped2 = true;
|
|
3131
|
+
clearTimers2();
|
|
3132
|
+
onFailure(`listen timeout reached after ${record.listenTimeout}ms while waiting for shared listener ${host}:${port}`);
|
|
3133
|
+
}, record.listenTimeout);
|
|
3134
|
+
timeoutTimer2.unref?.();
|
|
3135
|
+
return {
|
|
3136
|
+
stop() {
|
|
3137
|
+
stopped2 = true;
|
|
3138
|
+
clearTimers2();
|
|
3139
|
+
}
|
|
3140
|
+
};
|
|
3141
|
+
}
|
|
3142
|
+
if (!record.waitReady || !record.healthCheck) {
|
|
3143
|
+
return {
|
|
3144
|
+
stop() {
|
|
3145
|
+
}
|
|
3146
|
+
};
|
|
3147
|
+
}
|
|
3148
|
+
const healthCheck = record.healthCheck;
|
|
3149
|
+
const pollInterval = Math.max(50, Math.min(healthCheck.interval, 250));
|
|
3150
|
+
let stopped = false;
|
|
3151
|
+
let timer = null;
|
|
3152
|
+
let timeoutTimer = null;
|
|
3153
|
+
let inFlight = false;
|
|
3154
|
+
const clearTimers = () => {
|
|
3155
|
+
if (timer) {
|
|
3156
|
+
clearInterval(timer);
|
|
3157
|
+
timer = null;
|
|
3158
|
+
}
|
|
3159
|
+
if (timeoutTimer) {
|
|
3160
|
+
clearTimeout(timeoutTimer);
|
|
3161
|
+
timeoutTimer = null;
|
|
3162
|
+
}
|
|
3163
|
+
};
|
|
3164
|
+
const runHealthCheck = async () => {
|
|
3165
|
+
if (stopped || inFlight) {
|
|
3166
|
+
return;
|
|
3167
|
+
}
|
|
3168
|
+
inFlight = true;
|
|
3169
|
+
const controller = new AbortController();
|
|
3170
|
+
const timeoutId = setTimeout(() => controller.abort(), healthCheck.timeout);
|
|
3171
|
+
timeoutId.unref?.();
|
|
3172
|
+
try {
|
|
3173
|
+
const response = await fetch(healthCheck.url, {
|
|
3174
|
+
method: "GET",
|
|
3175
|
+
signal: controller.signal
|
|
3176
|
+
});
|
|
3177
|
+
if (stopped) {
|
|
3178
|
+
return;
|
|
3179
|
+
}
|
|
3180
|
+
if (!response.ok) {
|
|
3181
|
+
throw new Error(`health check returned ${response.status}`);
|
|
3182
|
+
}
|
|
3183
|
+
stopped = true;
|
|
3184
|
+
clearTimers();
|
|
3185
|
+
onReady(`readiness check passed: ${healthCheck.url}`);
|
|
3186
|
+
} catch {
|
|
3187
|
+
} finally {
|
|
3188
|
+
clearTimeout(timeoutId);
|
|
3189
|
+
inFlight = false;
|
|
3190
|
+
}
|
|
3191
|
+
};
|
|
3192
|
+
timeoutTimer = setTimeout(() => {
|
|
3193
|
+
if (stopped) {
|
|
3194
|
+
return;
|
|
3195
|
+
}
|
|
3196
|
+
stopped = true;
|
|
3197
|
+
clearTimers();
|
|
3198
|
+
onFailure(`listen timeout reached after ${record.listenTimeout}ms while waiting for ${healthCheck.url}`);
|
|
3199
|
+
}, record.listenTimeout);
|
|
3200
|
+
timeoutTimer.unref?.();
|
|
3201
|
+
void runHealthCheck();
|
|
3202
|
+
timer = setInterval(() => {
|
|
3203
|
+
void runHealthCheck();
|
|
3204
|
+
}, pollInterval);
|
|
3205
|
+
timer.unref?.();
|
|
3206
|
+
return {
|
|
3207
|
+
stop() {
|
|
3208
|
+
stopped = true;
|
|
3209
|
+
clearTimers();
|
|
3210
|
+
}
|
|
3211
|
+
};
|
|
3212
|
+
}
|
|
3213
|
+
function resolvePmStopTimeout(record) {
|
|
3214
|
+
if (isPmOnlineWapkRecord(record)) {
|
|
3215
|
+
return Math.max(record.killTimeout, PM_WAPK_ONLINE_SHUTDOWN_TIMEOUT_MS);
|
|
3216
|
+
}
|
|
3217
|
+
return record.killTimeout;
|
|
3218
|
+
}
|
|
3219
|
+
function supportsPmProxyReload(record) {
|
|
3220
|
+
return Boolean(record.proxy) && record.instances === 1;
|
|
3221
|
+
}
|
|
3222
|
+
function buildPmMonitorRecord(record, targetPort) {
|
|
3223
|
+
if (!record.proxy || !targetPort) {
|
|
3224
|
+
return record;
|
|
3225
|
+
}
|
|
3226
|
+
const targetHost = resolvePmProxyTargetHost(record.proxy);
|
|
3227
|
+
return {
|
|
3228
|
+
...record,
|
|
3229
|
+
proxyTargetPort: targetPort,
|
|
3230
|
+
healthCheck: record.healthCheck ? {
|
|
3231
|
+
...record.healthCheck,
|
|
3232
|
+
url: rewritePmProxyHealthCheckUrl(record.healthCheck.url, targetHost, targetPort)
|
|
3233
|
+
} : void 0
|
|
3234
|
+
};
|
|
3235
|
+
}
|
|
3236
|
+
async function waitForProcessTermination(pid, timeoutMs) {
|
|
3237
|
+
if (!pid || !isProcessAlive(pid)) {
|
|
3238
|
+
return true;
|
|
3239
|
+
}
|
|
3240
|
+
const deadline = Date.now() + timeoutMs;
|
|
3241
|
+
while (Date.now() < deadline) {
|
|
3242
|
+
if (!isProcessAlive(pid)) {
|
|
3243
|
+
return true;
|
|
3244
|
+
}
|
|
3245
|
+
await delay(DEFAULT_PM_STOP_POLL_MS);
|
|
3246
|
+
}
|
|
3247
|
+
return !isProcessAlive(pid);
|
|
3248
|
+
}
|
|
3249
|
+
async function waitForManagedChildExit(child) {
|
|
3250
|
+
return await new Promise((resolvePromise) => {
|
|
3251
|
+
let resolved = false;
|
|
3252
|
+
child.once("error", (error) => {
|
|
3253
|
+
if (resolved) {
|
|
3254
|
+
return;
|
|
3255
|
+
}
|
|
3256
|
+
resolved = true;
|
|
3257
|
+
resolvePromise({ code: 1, signal: null, error: error instanceof Error ? error.message : String(error) });
|
|
3258
|
+
});
|
|
3259
|
+
child.once("close", (code, signal) => {
|
|
3260
|
+
if (resolved) {
|
|
3261
|
+
return;
|
|
3262
|
+
}
|
|
3263
|
+
resolved = true;
|
|
3264
|
+
resolvePromise({ code, signal });
|
|
3265
|
+
});
|
|
3266
|
+
});
|
|
3267
|
+
}
|
|
3268
|
+
async function createPmChildControllers(record, child, stdoutLog, stderrLog, requestPlannedRestart, onReady, options) {
|
|
3269
|
+
let healthMonitor = {
|
|
3270
|
+
stop() {
|
|
3271
|
+
}
|
|
3272
|
+
};
|
|
3273
|
+
let memoryMonitor = {
|
|
3274
|
+
stop() {
|
|
3275
|
+
}
|
|
3276
|
+
};
|
|
3277
|
+
let scheduleMonitor = {
|
|
3278
|
+
stop() {
|
|
3279
|
+
}
|
|
3280
|
+
};
|
|
3281
|
+
const startHealthMonitor = () => {
|
|
3282
|
+
healthMonitor = createPmHealthMonitor(
|
|
3283
|
+
record,
|
|
3284
|
+
(message) => requestPlannedRestart("health", message),
|
|
3285
|
+
(message) => writePmLog(stdoutLog, message)
|
|
3286
|
+
);
|
|
3287
|
+
};
|
|
3288
|
+
const needsReadySignal = Boolean(options?.ipcController) || record.waitReady;
|
|
3289
|
+
const readinessMonitor = options?.ready || !needsReadySignal ? { stop() {
|
|
3290
|
+
} } : createPmReadinessMonitor(
|
|
3291
|
+
record,
|
|
3292
|
+
(message) => {
|
|
3293
|
+
onReady(message);
|
|
3294
|
+
startHealthMonitor();
|
|
3295
|
+
},
|
|
3296
|
+
(message) => requestPlannedRestart("startup", message),
|
|
3297
|
+
{ ipcController: options?.ipcController }
|
|
3298
|
+
);
|
|
3299
|
+
const watchController = await createPmWatchController(
|
|
3300
|
+
record,
|
|
3301
|
+
(changedPath) => requestPlannedRestart("watch", changedPath),
|
|
3302
|
+
(message) => writePmLog(stderrLog, `watch error: ${message}`)
|
|
3303
|
+
);
|
|
3304
|
+
memoryMonitor = createPmMemoryMonitor(
|
|
3305
|
+
record,
|
|
3306
|
+
child.pid,
|
|
3307
|
+
(kind, message) => requestPlannedRestart(kind, message)
|
|
3308
|
+
);
|
|
3309
|
+
scheduleMonitor = createPmScheduleMonitor(
|
|
3310
|
+
record,
|
|
3311
|
+
(message) => requestPlannedRestart("cron", message),
|
|
3312
|
+
(message) => writePmLog(stdoutLog, message)
|
|
3313
|
+
);
|
|
3314
|
+
if (options?.ready || !needsReadySignal) {
|
|
3315
|
+
startHealthMonitor();
|
|
3316
|
+
}
|
|
3317
|
+
return {
|
|
3318
|
+
async stop() {
|
|
3319
|
+
await watchController.close();
|
|
3320
|
+
readinessMonitor.stop();
|
|
3321
|
+
healthMonitor.stop();
|
|
3322
|
+
memoryMonitor.stop();
|
|
3323
|
+
scheduleMonitor.stop();
|
|
3324
|
+
}
|
|
3325
|
+
};
|
|
3326
|
+
}
|
|
3327
|
+
async function waitForPmChildReady(record, child, ipcController) {
|
|
3328
|
+
if (!ipcController && (!record.waitReady || !record.healthCheck)) {
|
|
3329
|
+
return { ready: true };
|
|
3330
|
+
}
|
|
3331
|
+
let readyMessage;
|
|
3332
|
+
let failureMessage;
|
|
3333
|
+
let exitResult;
|
|
3334
|
+
const readinessMonitor = createPmReadinessMonitor(
|
|
3335
|
+
record,
|
|
3336
|
+
(message) => {
|
|
3337
|
+
readyMessage = message;
|
|
3338
|
+
},
|
|
3339
|
+
(message) => {
|
|
3340
|
+
failureMessage = message;
|
|
3341
|
+
},
|
|
3342
|
+
{ ipcController }
|
|
3343
|
+
);
|
|
3344
|
+
void waitForManagedChildExit(child).then((result) => {
|
|
3345
|
+
exitResult = result;
|
|
3346
|
+
});
|
|
3347
|
+
while (!readyMessage && !failureMessage && !exitResult) {
|
|
3348
|
+
await delay(25);
|
|
3349
|
+
}
|
|
3350
|
+
readinessMonitor.stop();
|
|
3351
|
+
if (readyMessage) {
|
|
3352
|
+
return { ready: true, message: readyMessage };
|
|
3353
|
+
}
|
|
3354
|
+
return {
|
|
3355
|
+
ready: false,
|
|
3356
|
+
message: failureMessage,
|
|
3357
|
+
exitResult
|
|
3358
|
+
};
|
|
3359
|
+
}
|
|
3360
|
+
async function stopProxyManagedChild(child, record, stderrLog) {
|
|
3361
|
+
if (!child.pid || !isProcessAlive(child.pid)) {
|
|
3362
|
+
return;
|
|
3363
|
+
}
|
|
3364
|
+
terminateProcessTree(child.pid);
|
|
3365
|
+
const stopTimeout = resolvePmStopTimeout(record);
|
|
3366
|
+
const stopped = await waitForProcessTermination(child.pid, stopTimeout);
|
|
3367
|
+
if (!stopped && child.pid && isProcessAlive(child.pid)) {
|
|
3368
|
+
writePmLog(stderrLog, `proxy handoff shutdown timed out after ${stopTimeout}ms; forcing process termination`);
|
|
3369
|
+
terminateProcessTree(child.pid, { force: true });
|
|
3370
|
+
await waitForProcessTermination(child.pid, DEFAULT_PM_STOP_POLL_MS);
|
|
3371
|
+
}
|
|
3372
|
+
}
|
|
3373
|
+
async function createPmWatchController(record, onChange, onError) {
|
|
3374
|
+
if (!record.watch || record.watchPaths.length === 0) {
|
|
2286
3375
|
return {
|
|
2287
3376
|
async close() {
|
|
2288
3377
|
}
|
|
@@ -2344,11 +3433,17 @@ function createPmHealthMonitor(record, onFailure, onLog) {
|
|
|
2344
3433
|
method: "GET",
|
|
2345
3434
|
signal: controller.signal
|
|
2346
3435
|
});
|
|
3436
|
+
if (stopped) {
|
|
3437
|
+
return;
|
|
3438
|
+
}
|
|
2347
3439
|
if (!response.ok) {
|
|
2348
3440
|
throw new Error(`health check returned ${response.status}`);
|
|
2349
3441
|
}
|
|
2350
3442
|
failureCount = 0;
|
|
2351
3443
|
} catch (error) {
|
|
3444
|
+
if (stopped) {
|
|
3445
|
+
return;
|
|
3446
|
+
}
|
|
2352
3447
|
failureCount += 1;
|
|
2353
3448
|
const message = error instanceof Error ? error.message : String(error);
|
|
2354
3449
|
onLog(`health check failed (${failureCount}/${healthCheck.maxFailures}): ${message}`);
|
|
@@ -2383,6 +3478,90 @@ function createPmHealthMonitor(record, onFailure, onLog) {
|
|
|
2383
3478
|
}
|
|
2384
3479
|
};
|
|
2385
3480
|
}
|
|
3481
|
+
function createPmMemoryMonitor(record, pid, onFailure) {
|
|
3482
|
+
if (!record.maxMemoryBytes || !pid) {
|
|
3483
|
+
return {
|
|
3484
|
+
stop() {
|
|
3485
|
+
}
|
|
3486
|
+
};
|
|
3487
|
+
}
|
|
3488
|
+
const memoryLimit = record.maxMemoryBytes;
|
|
3489
|
+
let stopped = false;
|
|
3490
|
+
const timer = setInterval(() => {
|
|
3491
|
+
if (stopped) {
|
|
3492
|
+
return;
|
|
3493
|
+
}
|
|
3494
|
+
const memoryRssBytes = samplePmProcessMetrics(pid).memoryRssBytes;
|
|
3495
|
+
if (memoryRssBytes === void 0 || memoryRssBytes <= memoryLimit) {
|
|
3496
|
+
return;
|
|
3497
|
+
}
|
|
3498
|
+
stopped = true;
|
|
3499
|
+
onFailure(record.memoryAction === "stop" ? "memory-stop" : "memory", `memory usage ${memoryRssBytes} exceeded limit ${memoryLimit}`);
|
|
3500
|
+
}, DEFAULT_PM_MEMORY_CHECK_INTERVAL);
|
|
3501
|
+
timer.unref?.();
|
|
3502
|
+
return {
|
|
3503
|
+
stop() {
|
|
3504
|
+
stopped = true;
|
|
3505
|
+
clearInterval(timer);
|
|
3506
|
+
}
|
|
3507
|
+
};
|
|
3508
|
+
}
|
|
3509
|
+
function createPmScheduleMonitor(record, onTrigger, onLog) {
|
|
3510
|
+
if (!record.cronRestart) {
|
|
3511
|
+
return {
|
|
3512
|
+
stop() {
|
|
3513
|
+
}
|
|
3514
|
+
};
|
|
3515
|
+
}
|
|
3516
|
+
const schedule = parsePmRestartSchedule(record.cronRestart, "pm cronRestart");
|
|
3517
|
+
let stopped = false;
|
|
3518
|
+
let timer = null;
|
|
3519
|
+
const armTimer = (from = /* @__PURE__ */ new Date()) => {
|
|
3520
|
+
const nextOccurrence = resolveNextPmScheduleOccurrence(schedule, from);
|
|
3521
|
+
if (!nextOccurrence) {
|
|
3522
|
+
onLog(`schedule has no next occurrence: ${record.cronRestart}`);
|
|
3523
|
+
return;
|
|
3524
|
+
}
|
|
3525
|
+
const delayMs = Math.max(0, nextOccurrence.getTime() - Date.now());
|
|
3526
|
+
timer = setTimeout(() => {
|
|
3527
|
+
timer = null;
|
|
3528
|
+
if (stopped) {
|
|
3529
|
+
return;
|
|
3530
|
+
}
|
|
3531
|
+
onTrigger(`restart schedule matched: ${record.cronRestart}`);
|
|
3532
|
+
}, delayMs);
|
|
3533
|
+
timer.unref?.();
|
|
3534
|
+
};
|
|
3535
|
+
armTimer();
|
|
3536
|
+
return {
|
|
3537
|
+
stop() {
|
|
3538
|
+
stopped = true;
|
|
3539
|
+
if (timer) {
|
|
3540
|
+
clearTimeout(timer);
|
|
3541
|
+
timer = null;
|
|
3542
|
+
}
|
|
3543
|
+
}
|
|
3544
|
+
};
|
|
3545
|
+
}
|
|
3546
|
+
function resolvePmRestartDelay(record, restartCount, shouldApplyBackoff) {
|
|
3547
|
+
if (!shouldApplyBackoff || !record.expBackoffRestartDelay) {
|
|
3548
|
+
return record.restartDelay;
|
|
3549
|
+
}
|
|
3550
|
+
const exponent = Math.max(0, restartCount - 1);
|
|
3551
|
+
return Math.min(record.expBackoffRestartDelay * 2 ** exponent, record.expBackoffRestartMaxDelay ?? DEFAULT_PM_EXP_BACKOFF_MAX_DELAY);
|
|
3552
|
+
}
|
|
3553
|
+
function resolvePmRestartCountBase(record, wasStable, restartKind) {
|
|
3554
|
+
if (wasStable) {
|
|
3555
|
+
return 0;
|
|
3556
|
+
}
|
|
3557
|
+
if (record.restartWindow && record.lastRestartAt) {
|
|
3558
|
+
const lastRestartTime = Date.parse(record.lastRestartAt);
|
|
3559
|
+
if (!Number.isNaN(lastRestartTime) && Date.now() - lastRestartTime > record.restartWindow) {
|
|
3560
|
+
return 0;
|
|
3561
|
+
}
|
|
3562
|
+
}
|
|
3563
|
+
return restartKind === "watch" ? record.restartCount ?? 0 : record.restartCount ?? 0;
|
|
3564
|
+
}
|
|
2386
3565
|
function readPlannedRestartRequest(state) {
|
|
2387
3566
|
return state.request;
|
|
2388
3567
|
}
|
|
@@ -2396,6 +3575,16 @@ async function runManagedProcessLoop(filePath, initialRecord) {
|
|
|
2396
3575
|
(0, import_node_fs4.mkdirSync)((0, import_node_path6.dirname)(initialRecord.logFiles.err), { recursive: true });
|
|
2397
3576
|
const stdoutLog = (0, import_node_fs4.createWriteStream)(initialRecord.logFiles.out, { flags: "a" });
|
|
2398
3577
|
const stderrLog = (0, import_node_fs4.createWriteStream)(initialRecord.logFiles.err, { flags: "a" });
|
|
3578
|
+
let proxyController = null;
|
|
3579
|
+
let proxyTargetSyncTimer = null;
|
|
3580
|
+
let inheritedListener = null;
|
|
3581
|
+
const runnerPaths = resolveRunnerPathsFromRecordFile(filePath);
|
|
3582
|
+
const syncOwnedProxyTargets = (baseName) => {
|
|
3583
|
+
if (!proxyController) {
|
|
3584
|
+
return;
|
|
3585
|
+
}
|
|
3586
|
+
proxyController.setTargets(resolvePmProxyTargetUrls(runnerPaths, baseName));
|
|
3587
|
+
};
|
|
2399
3588
|
const persist = (mutator) => {
|
|
2400
3589
|
const current = readLatestPmRecord(filePath, record);
|
|
2401
3590
|
record = mutator(current);
|
|
@@ -2408,26 +3597,30 @@ async function runManagedProcessLoop(filePath, initialRecord) {
|
|
|
2408
3597
|
activeChildStopTimer = null;
|
|
2409
3598
|
}
|
|
2410
3599
|
};
|
|
3600
|
+
const scheduleForcedActiveChildStop = (timeoutMs, reason) => {
|
|
3601
|
+
if (!activeChild?.pid || process.platform === "win32") {
|
|
3602
|
+
return;
|
|
3603
|
+
}
|
|
3604
|
+
clearActiveChildStopTimer();
|
|
3605
|
+
activeChildStopTimer = setTimeout(() => {
|
|
3606
|
+
if (activeChild?.pid && isProcessAlive(activeChild.pid)) {
|
|
3607
|
+
writePmLog(stderrLog, `${reason} after ${timeoutMs}ms; forcing process termination`);
|
|
3608
|
+
terminateProcessTree(activeChild.pid, { force: true });
|
|
3609
|
+
}
|
|
3610
|
+
}, timeoutMs);
|
|
3611
|
+
activeChildStopTimer.unref?.();
|
|
3612
|
+
};
|
|
2411
3613
|
const stopActiveChild = () => {
|
|
2412
3614
|
if (!activeChild?.pid || !isProcessAlive(activeChild.pid)) {
|
|
2413
3615
|
return;
|
|
2414
3616
|
}
|
|
2415
3617
|
const current = readLatestPmRecord(filePath, record);
|
|
3618
|
+
const stopTimeout = resolvePmStopTimeout(current);
|
|
2416
3619
|
if (isPmOnlineWapkRecord(current) && activeChild.stdin && !activeChild.stdin.destroyed && activeChild.stdin.writable) {
|
|
2417
3620
|
try {
|
|
2418
3621
|
activeChild.stdin.end(`${PM_WAPK_ONLINE_SHUTDOWN_COMMAND}
|
|
2419
3622
|
`);
|
|
2420
|
-
|
|
2421
|
-
activeChildStopTimer = setTimeout(() => {
|
|
2422
|
-
if (activeChild?.pid && isProcessAlive(activeChild.pid)) {
|
|
2423
|
-
writePmLog(
|
|
2424
|
-
stderrLog,
|
|
2425
|
-
`graceful WAPK online shutdown timed out after ${PM_WAPK_ONLINE_SHUTDOWN_TIMEOUT_MS}ms; forcing process termination`
|
|
2426
|
-
);
|
|
2427
|
-
terminateProcessTree(activeChild.pid);
|
|
2428
|
-
}
|
|
2429
|
-
}, PM_WAPK_ONLINE_SHUTDOWN_TIMEOUT_MS);
|
|
2430
|
-
activeChildStopTimer.unref?.();
|
|
3623
|
+
scheduleForcedActiveChildStop(stopTimeout, "graceful WAPK online shutdown timed out");
|
|
2431
3624
|
return;
|
|
2432
3625
|
} catch (error) {
|
|
2433
3626
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -2435,6 +3628,7 @@ async function runManagedProcessLoop(filePath, initialRecord) {
|
|
|
2435
3628
|
}
|
|
2436
3629
|
}
|
|
2437
3630
|
terminateProcessTree(activeChild.pid);
|
|
3631
|
+
scheduleForcedActiveChildStop(stopTimeout, "graceful shutdown timed out");
|
|
2438
3632
|
};
|
|
2439
3633
|
const requestManagedStop = (reason) => {
|
|
2440
3634
|
if (stopRequested) {
|
|
@@ -2472,6 +3666,17 @@ async function runManagedProcessLoop(filePath, initialRecord) {
|
|
|
2472
3666
|
let command;
|
|
2473
3667
|
try {
|
|
2474
3668
|
command = buildPmCommand(latest);
|
|
3669
|
+
if (latest.proxy && isPmProxyOwner(latest) && !proxyController) {
|
|
3670
|
+
proxyController = await createPmProxyController(latest.proxy);
|
|
3671
|
+
syncOwnedProxyTargets(latest.baseName);
|
|
3672
|
+
if (!proxyTargetSyncTimer) {
|
|
3673
|
+
proxyTargetSyncTimer = setInterval(() => syncOwnedProxyTargets(latest.baseName), 50);
|
|
3674
|
+
proxyTargetSyncTimer.unref?.();
|
|
3675
|
+
}
|
|
3676
|
+
}
|
|
3677
|
+
if (latest.proxy && usesPmInheritedListener(latest) && !inheritedListener) {
|
|
3678
|
+
inheritedListener = await createPmInheritedListener(latest.proxy);
|
|
3679
|
+
}
|
|
2475
3680
|
} catch (error) {
|
|
2476
3681
|
const message = error instanceof Error ? error.message : String(error);
|
|
2477
3682
|
writePmLog(stderrLog, message);
|
|
@@ -2481,25 +3686,24 @@ async function runManagedProcessLoop(filePath, initialRecord) {
|
|
|
2481
3686
|
error: message,
|
|
2482
3687
|
runnerPid: void 0,
|
|
2483
3688
|
childPid: void 0,
|
|
3689
|
+
proxyTargetPort: void 0,
|
|
3690
|
+
proxyReadyAt: void 0,
|
|
2484
3691
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2485
3692
|
}));
|
|
2486
3693
|
return;
|
|
2487
3694
|
}
|
|
2488
3695
|
const onlineStdinShutdownEnabled = isPmOnlineWapkRecord(latest);
|
|
2489
|
-
const
|
|
3696
|
+
const initialTargetPort = usesPmProxyController(latest) ? await allocatePmProxyTargetPort(resolvePmProxyTargetHost(latest.proxy)) : void 0;
|
|
3697
|
+
const monitorRecord = buildPmMonitorRecord(latest, initialTargetPort);
|
|
3698
|
+
let child = (0, import_node_child_process2.spawn)(command.command, command.args, {
|
|
2490
3699
|
cwd: latest.cwd,
|
|
2491
|
-
env:
|
|
2492
|
-
|
|
2493
|
-
...latest.env,
|
|
2494
|
-
...command.env,
|
|
2495
|
-
ELIT_PM_NAME: latest.name,
|
|
2496
|
-
ELIT_PM_ID: latest.id
|
|
2497
|
-
},
|
|
2498
|
-
stdio: [onlineStdinShutdownEnabled ? "pipe" : "ignore", "pipe", "pipe"],
|
|
3700
|
+
env: buildPmChildEnv(latest, command, initialTargetPort),
|
|
3701
|
+
stdio: buildPmChildStdio(command, onlineStdinShutdownEnabled),
|
|
2499
3702
|
windowsHide: true,
|
|
2500
3703
|
shell: command.shell
|
|
2501
3704
|
});
|
|
2502
|
-
|
|
3705
|
+
let childStartedAt = Date.now();
|
|
3706
|
+
let childIpcState = command.ipc ? createPmChildIpcState(child, inheritedListener) : void 0;
|
|
2503
3707
|
activeChild = child;
|
|
2504
3708
|
if (child.stdout) {
|
|
2505
3709
|
child.stdout.pipe(stdoutLog, { end: false });
|
|
@@ -2507,26 +3711,38 @@ async function runManagedProcessLoop(filePath, initialRecord) {
|
|
|
2507
3711
|
if (child.stderr) {
|
|
2508
3712
|
child.stderr.pipe(stderrLog, { end: false });
|
|
2509
3713
|
}
|
|
3714
|
+
let childWaitState = { settled: false, result: void 0 };
|
|
3715
|
+
void waitForManagedChildExit(child).then((result) => {
|
|
3716
|
+
childWaitState.result = result;
|
|
3717
|
+
childWaitState.settled = true;
|
|
3718
|
+
});
|
|
2510
3719
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3720
|
+
const waitingForReady = latest.waitReady || Boolean(childIpcState);
|
|
2511
3721
|
persist((current2) => ({
|
|
2512
3722
|
...current2,
|
|
2513
|
-
status: "online",
|
|
3723
|
+
status: waitingForReady ? "starting" : "online",
|
|
2514
3724
|
commandPreview: command.preview,
|
|
2515
3725
|
runtime: command.runtime ?? current2.runtime,
|
|
2516
3726
|
runnerPid: process.pid,
|
|
2517
3727
|
childPid: child.pid,
|
|
3728
|
+
proxyTargetPort: initialTargetPort,
|
|
3729
|
+
proxyReadyAt: !waitingForReady && usesPmProxyController(latest) ? startedAt : void 0,
|
|
2518
3730
|
startedAt,
|
|
2519
3731
|
stoppedAt: void 0,
|
|
3732
|
+
reloadRequestedAt: void 0,
|
|
2520
3733
|
error: void 0,
|
|
2521
3734
|
updatedAt: startedAt
|
|
2522
3735
|
}));
|
|
2523
3736
|
writePmLog(stdoutLog, `started ${command.preview}${child.pid ? ` (pid ${child.pid})` : ""}`);
|
|
3737
|
+
if (isPmProxyOwner(latest) && !waitingForReady) {
|
|
3738
|
+
syncOwnedProxyTargets(latest.baseName);
|
|
3739
|
+
}
|
|
2524
3740
|
const requestPlannedRestart = (kind, detail) => {
|
|
2525
3741
|
if (stopRequested || restartState.request) {
|
|
2526
3742
|
return;
|
|
2527
3743
|
}
|
|
2528
3744
|
restartState.request = { kind, detail };
|
|
2529
|
-
writePmLog(kind === "
|
|
3745
|
+
writePmLog(kind === "watch" || kind === "cron" ? stdoutLog : stderrLog, `${kind} restart requested: ${detail}`);
|
|
2530
3746
|
persist((current2) => ({
|
|
2531
3747
|
...current2,
|
|
2532
3748
|
status: "restarting",
|
|
@@ -2534,27 +3750,124 @@ async function runManagedProcessLoop(filePath, initialRecord) {
|
|
|
2534
3750
|
}));
|
|
2535
3751
|
stopActiveChild();
|
|
2536
3752
|
};
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
3753
|
+
let controllers = await createPmChildControllers(
|
|
3754
|
+
monitorRecord,
|
|
3755
|
+
child,
|
|
3756
|
+
stdoutLog,
|
|
3757
|
+
stderrLog,
|
|
3758
|
+
requestPlannedRestart,
|
|
3759
|
+
(message) => {
|
|
3760
|
+
const readyAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3761
|
+
if (isPmProxyOwner(latest)) {
|
|
3762
|
+
syncOwnedProxyTargets(latest.baseName);
|
|
3763
|
+
}
|
|
3764
|
+
persist((current2) => ({
|
|
3765
|
+
...current2,
|
|
3766
|
+
status: "online",
|
|
3767
|
+
proxyTargetPort: initialTargetPort,
|
|
3768
|
+
proxyReadyAt: readyAt,
|
|
3769
|
+
updatedAt: readyAt
|
|
3770
|
+
}));
|
|
3771
|
+
writePmLog(stdoutLog, message);
|
|
3772
|
+
},
|
|
3773
|
+
{ ready: !waitingForReady, ipcController: childIpcState }
|
|
2546
3774
|
);
|
|
2547
|
-
|
|
3775
|
+
let handledReloadAt = latest.reloadRequestedAt;
|
|
3776
|
+
while (!childWaitState.settled) {
|
|
2548
3777
|
const latestRecord = readLatestPmRecord(filePath, record);
|
|
2549
|
-
if (
|
|
3778
|
+
if (latestRecord.desiredState === "stopped" && !stopRequested) {
|
|
2550
3779
|
requestManagedStop("stop requested by PM control state");
|
|
2551
3780
|
}
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
3781
|
+
const reloadRequestedAt = latestRecord.reloadRequestedAt;
|
|
3782
|
+
if (!stopRequested && supportsPmProxyReload(latestRecord) && reloadRequestedAt && reloadRequestedAt !== handledReloadAt && latestRecord.proxy) {
|
|
3783
|
+
handledReloadAt = reloadRequestedAt;
|
|
3784
|
+
const replacementTargetPort = usesPmProxyController(latestRecord) ? await allocatePmProxyTargetPort(resolvePmProxyTargetHost(latestRecord.proxy)) : void 0;
|
|
3785
|
+
const replacementMonitorRecord = buildPmMonitorRecord(latestRecord, replacementTargetPort);
|
|
3786
|
+
const replacementChild = (0, import_node_child_process2.spawn)(command.command, command.args, {
|
|
3787
|
+
cwd: latestRecord.cwd,
|
|
3788
|
+
env: buildPmChildEnv(latestRecord, command, replacementTargetPort),
|
|
3789
|
+
stdio: buildPmChildStdio(command, onlineStdinShutdownEnabled),
|
|
3790
|
+
windowsHide: true,
|
|
3791
|
+
shell: command.shell
|
|
3792
|
+
});
|
|
3793
|
+
const replacementIpcState = command.ipc ? createPmChildIpcState(replacementChild, inheritedListener) : void 0;
|
|
3794
|
+
if (replacementChild.stdout) {
|
|
3795
|
+
replacementChild.stdout.pipe(stdoutLog, { end: false });
|
|
3796
|
+
}
|
|
3797
|
+
if (replacementChild.stderr) {
|
|
3798
|
+
replacementChild.stderr.pipe(stderrLog, { end: false });
|
|
3799
|
+
}
|
|
3800
|
+
writePmLog(stdoutLog, `starting ${usesPmInheritedListener(latestRecord) ? "shared-listener" : "proxy handoff"} replacement${replacementChild.pid ? ` (pid ${replacementChild.pid})` : ""}`);
|
|
3801
|
+
const readyResult = await waitForPmChildReady(replacementMonitorRecord, replacementChild, replacementIpcState);
|
|
3802
|
+
if (!readyResult.ready) {
|
|
3803
|
+
writePmLog(stderrLog, readyResult.message ?? "replacement exited before becoming ready");
|
|
3804
|
+
replacementIpcState?.stop();
|
|
3805
|
+
await stopProxyManagedChild(replacementChild, latestRecord, stderrLog);
|
|
3806
|
+
persist((current2) => ({
|
|
3807
|
+
...current2,
|
|
3808
|
+
status: "online",
|
|
3809
|
+
proxyReadyAt: current2.proxyReadyAt,
|
|
3810
|
+
reloadRequestedAt: void 0,
|
|
3811
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3812
|
+
}));
|
|
3813
|
+
continue;
|
|
3814
|
+
}
|
|
3815
|
+
const previousChild = child;
|
|
3816
|
+
const previousControllers = controllers;
|
|
3817
|
+
const previousIpcState = childIpcState;
|
|
3818
|
+
const handoffAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3819
|
+
if (usesPmProxyController(latestRecord) && replacementTargetPort) {
|
|
3820
|
+
proxyController?.setTarget(buildPmProxyTargetUrl(latestRecord.proxy, replacementTargetPort));
|
|
3821
|
+
}
|
|
3822
|
+
persist((current2) => ({
|
|
3823
|
+
...current2,
|
|
3824
|
+
status: "online",
|
|
3825
|
+
childPid: replacementChild.pid,
|
|
3826
|
+
proxyTargetPort: replacementTargetPort,
|
|
3827
|
+
proxyReadyAt: handoffAt,
|
|
3828
|
+
startedAt: handoffAt,
|
|
3829
|
+
reloadRequestedAt: void 0,
|
|
3830
|
+
error: void 0,
|
|
3831
|
+
updatedAt: handoffAt
|
|
3832
|
+
}));
|
|
3833
|
+
if (readyResult.message) {
|
|
3834
|
+
writePmLog(stdoutLog, readyResult.message);
|
|
3835
|
+
}
|
|
3836
|
+
writePmLog(stdoutLog, `${usesPmInheritedListener(latestRecord) ? "shared listener" : "proxy handoff"} activated on ${latestRecord.proxy.host ?? "0.0.0.0"}:${latestRecord.proxy.port}`);
|
|
3837
|
+
child = replacementChild;
|
|
3838
|
+
childIpcState = replacementIpcState;
|
|
3839
|
+
childStartedAt = Date.now();
|
|
3840
|
+
activeChild = replacementChild;
|
|
3841
|
+
childWaitState = { settled: false, result: void 0 };
|
|
3842
|
+
void waitForManagedChildExit(replacementChild).then((result) => {
|
|
3843
|
+
childWaitState.result = result;
|
|
3844
|
+
childWaitState.settled = true;
|
|
3845
|
+
});
|
|
3846
|
+
controllers = await createPmChildControllers(
|
|
3847
|
+
replacementMonitorRecord,
|
|
3848
|
+
replacementChild,
|
|
3849
|
+
stdoutLog,
|
|
3850
|
+
stderrLog,
|
|
3851
|
+
requestPlannedRestart,
|
|
3852
|
+
() => {
|
|
3853
|
+
},
|
|
3854
|
+
{ ready: true, ipcController: replacementIpcState }
|
|
3855
|
+
);
|
|
3856
|
+
await delay(250);
|
|
3857
|
+
await previousControllers.stop();
|
|
3858
|
+
previousIpcState?.stop();
|
|
3859
|
+
await stopProxyManagedChild(previousChild, latestRecord, stderrLog);
|
|
3860
|
+
clearActiveChildStopTimer();
|
|
3861
|
+
if (isPmProxyOwner(latestRecord)) {
|
|
3862
|
+
syncOwnedProxyTargets(latestRecord.baseName);
|
|
3863
|
+
}
|
|
3864
|
+
continue;
|
|
3865
|
+
}
|
|
3866
|
+
await delay(25);
|
|
3867
|
+
}
|
|
3868
|
+
const exitResult = childWaitState.result;
|
|
3869
|
+
await controllers.stop();
|
|
3870
|
+
childIpcState?.stop();
|
|
2558
3871
|
clearActiveChildStopTimer();
|
|
2559
3872
|
activeChild = null;
|
|
2560
3873
|
const exitCode = waitForExit(exitResult.code, exitResult.signal);
|
|
@@ -2570,56 +3883,77 @@ async function runManagedProcessLoop(filePath, initialRecord) {
|
|
|
2570
3883
|
if (stopRequested || current.desiredState === "stopped") {
|
|
2571
3884
|
break;
|
|
2572
3885
|
}
|
|
2573
|
-
const shouldRestartForExit = plannedRestart ? true : current.restartPolicy === "always" ? true : current.restartPolicy === "on-failure" ? exitCode !== 0 || Boolean(exitResult.error) : false;
|
|
3886
|
+
const shouldRestartForExit = plannedRestart?.kind === "memory-stop" ? false : plannedRestart ? true : current.restartPolicy === "always" ? true : current.restartPolicy === "on-failure" ? exitCode !== 0 || Boolean(exitResult.error) : false;
|
|
2574
3887
|
if (!shouldRestartForExit) {
|
|
2575
3888
|
persist((latestRecord) => ({
|
|
2576
3889
|
...latestRecord,
|
|
2577
|
-
status: exitCode === 0 && !exitResult.error ? "exited" : "errored",
|
|
3890
|
+
status: plannedRestart?.kind === "memory-stop" ? "errored" : exitCode === 0 && !exitResult.error ? "exited" : "errored",
|
|
2578
3891
|
childPid: void 0,
|
|
3892
|
+
proxyTargetPort: void 0,
|
|
3893
|
+
proxyReadyAt: void 0,
|
|
2579
3894
|
runnerPid: void 0,
|
|
2580
3895
|
lastExitCode: exitCode,
|
|
2581
|
-
|
|
3896
|
+
reloadRequestedAt: void 0,
|
|
3897
|
+
error: plannedRestart?.kind === "memory-stop" ? plannedRestart.detail : exitCode === 0 && !exitResult.error ? void 0 : exitResult.error ?? `Process exited with code ${exitCode}.`,
|
|
2582
3898
|
stoppedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2583
3899
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2584
3900
|
}));
|
|
3901
|
+
if (isPmProxyOwner(current)) {
|
|
3902
|
+
syncOwnedProxyTargets(current.baseName);
|
|
3903
|
+
}
|
|
2585
3904
|
return;
|
|
2586
3905
|
}
|
|
2587
3906
|
const shouldCountRestart = plannedRestart?.kind !== "watch";
|
|
2588
|
-
const baseRestartCount =
|
|
3907
|
+
const baseRestartCount = resolvePmRestartCountBase(current, wasStable, plannedRestart?.kind);
|
|
2589
3908
|
const nextRestartCount = shouldCountRestart ? baseRestartCount + 1 : current.restartCount ?? 0;
|
|
2590
3909
|
if (nextRestartCount > current.maxRestarts) {
|
|
2591
3910
|
persist((latestRecord) => ({
|
|
2592
3911
|
...latestRecord,
|
|
2593
3912
|
status: "errored",
|
|
2594
3913
|
childPid: void 0,
|
|
3914
|
+
proxyTargetPort: void 0,
|
|
3915
|
+
proxyReadyAt: void 0,
|
|
2595
3916
|
runnerPid: void 0,
|
|
2596
3917
|
restartCount: nextRestartCount,
|
|
3918
|
+
lastRestartAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2597
3919
|
lastExitCode: exitCode,
|
|
3920
|
+
reloadRequestedAt: void 0,
|
|
2598
3921
|
error: plannedRestart ? `Reached max restart attempts (${current.maxRestarts}) after ${plannedRestart.kind} restart requests.` : `Reached max restart attempts (${current.maxRestarts}).`,
|
|
2599
3922
|
stoppedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2600
3923
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2601
3924
|
}));
|
|
2602
3925
|
writePmLog(stderrLog, `max restart attempts reached (${current.maxRestarts})`);
|
|
3926
|
+
if (isPmProxyOwner(current)) {
|
|
3927
|
+
syncOwnedProxyTargets(current.baseName);
|
|
3928
|
+
}
|
|
2603
3929
|
return;
|
|
2604
3930
|
}
|
|
2605
3931
|
persist((latestRecord) => ({
|
|
2606
3932
|
...latestRecord,
|
|
2607
3933
|
status: "restarting",
|
|
2608
3934
|
childPid: void 0,
|
|
3935
|
+
proxyTargetPort: void 0,
|
|
3936
|
+
proxyReadyAt: void 0,
|
|
2609
3937
|
lastExitCode: exitCode,
|
|
2610
3938
|
restartCount: nextRestartCount,
|
|
3939
|
+
lastRestartAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3940
|
+
reloadRequestedAt: void 0,
|
|
2611
3941
|
error: void 0,
|
|
2612
3942
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2613
3943
|
}));
|
|
3944
|
+
if (isPmProxyOwner(current)) {
|
|
3945
|
+
syncOwnedProxyTargets(current.baseName);
|
|
3946
|
+
}
|
|
3947
|
+
const resolvedRestartDelay = resolvePmRestartDelay(current, nextRestartCount, shouldCountRestart && !wasStable && plannedRestart?.kind !== "watch");
|
|
2614
3948
|
if (plannedRestart) {
|
|
2615
3949
|
writePmLog(
|
|
2616
|
-
plannedRestart.kind === "health" ? stderrLog : stdoutLog,
|
|
2617
|
-
`restarting in ${
|
|
3950
|
+
plannedRestart.kind === "health" || plannedRestart.kind === "memory" || plannedRestart.kind === "memory-stop" || plannedRestart.kind === "startup" ? stderrLog : stdoutLog,
|
|
3951
|
+
`restarting in ${resolvedRestartDelay}ms after ${plannedRestart.kind}: ${plannedRestart.detail}`
|
|
2618
3952
|
);
|
|
2619
3953
|
} else {
|
|
2620
|
-
writePmLog(stdoutLog, `restarting in ${
|
|
3954
|
+
writePmLog(stdoutLog, `restarting in ${resolvedRestartDelay}ms`);
|
|
2621
3955
|
}
|
|
2622
|
-
await delay(
|
|
3956
|
+
await delay(resolvedRestartDelay);
|
|
2623
3957
|
}
|
|
2624
3958
|
} finally {
|
|
2625
3959
|
stopRequested = true;
|
|
@@ -2632,11 +3966,26 @@ async function runManagedProcessLoop(filePath, initialRecord) {
|
|
|
2632
3966
|
status: finalRecord.status === "errored" ? "errored" : finalRecord.status === "exited" ? "exited" : "stopped",
|
|
2633
3967
|
runnerPid: void 0,
|
|
2634
3968
|
childPid: void 0,
|
|
3969
|
+
proxyTargetPort: void 0,
|
|
3970
|
+
proxyReadyAt: void 0,
|
|
3971
|
+
reloadRequestedAt: void 0,
|
|
2635
3972
|
stoppedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2636
3973
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2637
3974
|
});
|
|
2638
3975
|
process.off("SIGINT", handleStopSignal);
|
|
2639
3976
|
process.off("SIGTERM", handleStopSignal);
|
|
3977
|
+
if (proxyTargetSyncTimer) {
|
|
3978
|
+
clearInterval(proxyTargetSyncTimer);
|
|
3979
|
+
proxyTargetSyncTimer = null;
|
|
3980
|
+
}
|
|
3981
|
+
if (proxyController) {
|
|
3982
|
+
await proxyController.close().catch(() => void 0);
|
|
3983
|
+
proxyController = null;
|
|
3984
|
+
}
|
|
3985
|
+
if (inheritedListener) {
|
|
3986
|
+
await new Promise((resolvePromise) => inheritedListener?.close(() => resolvePromise()));
|
|
3987
|
+
inheritedListener = null;
|
|
3988
|
+
}
|
|
2640
3989
|
await new Promise((resolvePromise) => stdoutLog.end(resolvePromise));
|
|
2641
3990
|
await new Promise((resolvePromise) => stderrLog.end(resolvePromise));
|
|
2642
3991
|
}
|
|
@@ -2690,23 +4039,26 @@ async function stopPmMatches(matches) {
|
|
|
2690
4039
|
stoppedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2691
4040
|
};
|
|
2692
4041
|
writePmRecord(match.filePath, updated);
|
|
2693
|
-
const
|
|
4042
|
+
const stopTimeout = resolvePmStopTimeout(match.record);
|
|
4043
|
+
const runnerStopped = await waitForProcessTermination(match.record.runnerPid, stopTimeout);
|
|
2694
4044
|
const childStopped = await waitForProcessTermination(
|
|
2695
4045
|
match.record.childPid,
|
|
2696
|
-
runnerStopped ? DEFAULT_PM_STOP_POLL_MS :
|
|
4046
|
+
runnerStopped ? DEFAULT_PM_STOP_POLL_MS : stopTimeout
|
|
2697
4047
|
);
|
|
2698
4048
|
if (!runnerStopped && match.record.runnerPid && isProcessAlive(match.record.runnerPid)) {
|
|
2699
|
-
terminateProcessTree(match.record.runnerPid);
|
|
4049
|
+
terminateProcessTree(match.record.runnerPid, { force: true });
|
|
2700
4050
|
await waitForProcessTermination(match.record.runnerPid, DEFAULT_PM_STOP_POLL_MS);
|
|
2701
4051
|
}
|
|
2702
4052
|
if (!childStopped && match.record.childPid && isProcessAlive(match.record.childPid)) {
|
|
2703
|
-
terminateProcessTree(match.record.childPid);
|
|
4053
|
+
terminateProcessTree(match.record.childPid, { force: true });
|
|
2704
4054
|
await waitForProcessTermination(match.record.childPid, DEFAULT_PM_STOP_POLL_MS);
|
|
2705
4055
|
}
|
|
2706
4056
|
writePmRecord(match.filePath, {
|
|
2707
4057
|
...updated,
|
|
2708
4058
|
runnerPid: void 0,
|
|
2709
4059
|
childPid: void 0,
|
|
4060
|
+
proxyTargetPort: void 0,
|
|
4061
|
+
reloadRequestedAt: void 0,
|
|
2710
4062
|
status: "stopped",
|
|
2711
4063
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2712
4064
|
});
|
|
@@ -2775,13 +4127,13 @@ function readPackageJson(filePath) {
|
|
|
2775
4127
|
|
|
2776
4128
|
// src/shares/workspace-package/roots.ts
|
|
2777
4129
|
function findPackageDirectory(startDir, resolveMatch) {
|
|
2778
|
-
let currentDir =
|
|
4130
|
+
let currentDir = resolve5(startDir);
|
|
2779
4131
|
while (true) {
|
|
2780
4132
|
const match = resolveMatch(currentDir);
|
|
2781
4133
|
if (match) {
|
|
2782
4134
|
return match;
|
|
2783
4135
|
}
|
|
2784
|
-
const parentDir =
|
|
4136
|
+
const parentDir = dirname3(currentDir);
|
|
2785
4137
|
if (parentDir === currentDir) {
|
|
2786
4138
|
return void 0;
|
|
2787
4139
|
}
|
|
@@ -2809,17 +4161,17 @@ init_fs();
|
|
|
2809
4161
|
function getWorkspacePackageImportCandidates(packageRoot, specifier, options = {}) {
|
|
2810
4162
|
const subpath = specifier === "elit" ? "index" : specifier.slice("elit/".length);
|
|
2811
4163
|
const builtCandidates = options.preferredBuiltFormat === "cjs" ? [
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
|
|
4164
|
+
resolve5(packageRoot, "dist", `${subpath}.cjs`),
|
|
4165
|
+
resolve5(packageRoot, "dist", `${subpath}.js`),
|
|
4166
|
+
resolve5(packageRoot, "dist", `${subpath}.mjs`)
|
|
2815
4167
|
] : [
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
4168
|
+
resolve5(packageRoot, "dist", `${subpath}.mjs`),
|
|
4169
|
+
resolve5(packageRoot, "dist", `${subpath}.js`),
|
|
4170
|
+
resolve5(packageRoot, "dist", `${subpath}.cjs`)
|
|
2819
4171
|
];
|
|
2820
4172
|
const sourceCandidates = [
|
|
2821
|
-
|
|
2822
|
-
|
|
4173
|
+
resolve5(packageRoot, "src", `${subpath}.ts`),
|
|
4174
|
+
resolve5(packageRoot, "src", `${subpath}.tsx`)
|
|
2823
4175
|
];
|
|
2824
4176
|
return options.preferBuilt ? [...builtCandidates, ...sourceCandidates] : [...sourceCandidates, ...builtCandidates];
|
|
2825
4177
|
}
|
|
@@ -2854,7 +4206,7 @@ function resolveWorkspacePackageImport(specifier, startDir, options = {}) {
|
|
|
2854
4206
|
// src/shares/config/loader.ts
|
|
2855
4207
|
function resolveConfigPath(cwd = process.cwd()) {
|
|
2856
4208
|
for (const configFile of ELIT_CONFIG_FILES) {
|
|
2857
|
-
const configPath =
|
|
4209
|
+
const configPath = resolve5(cwd, configFile);
|
|
2858
4210
|
if (existsSync4(configPath)) {
|
|
2859
4211
|
return configPath;
|
|
2860
4212
|
}
|
|
@@ -2883,7 +4235,7 @@ async function loadConfigFile(configPath) {
|
|
|
2883
4235
|
if (ext === "ts" || ext === "mts") {
|
|
2884
4236
|
try {
|
|
2885
4237
|
const { build } = await import("esbuild");
|
|
2886
|
-
const configDir =
|
|
4238
|
+
const configDir = dirname3(configPath);
|
|
2887
4239
|
const tempFile = join5(configDir, `.elit-config-${Date.now()}.mjs`);
|
|
2888
4240
|
const externalAllPlugin = {
|
|
2889
4241
|
name: "external-all",
|
|
@@ -2957,6 +4309,43 @@ async function loadConfigFile(configPath) {
|
|
|
2957
4309
|
}
|
|
2958
4310
|
|
|
2959
4311
|
// src/cli/pm/commands.ts
|
|
4312
|
+
var PM_SIGNAL_NAMES = /* @__PURE__ */ new Set([
|
|
4313
|
+
"SIGABRT",
|
|
4314
|
+
"SIGALRM",
|
|
4315
|
+
"SIGBREAK",
|
|
4316
|
+
"SIGBUS",
|
|
4317
|
+
"SIGCHLD",
|
|
4318
|
+
"SIGCONT",
|
|
4319
|
+
"SIGFPE",
|
|
4320
|
+
"SIGHUP",
|
|
4321
|
+
"SIGILL",
|
|
4322
|
+
"SIGINT",
|
|
4323
|
+
"SIGIO",
|
|
4324
|
+
"SIGIOT",
|
|
4325
|
+
"SIGKILL",
|
|
4326
|
+
"SIGPIPE",
|
|
4327
|
+
"SIGPOLL",
|
|
4328
|
+
"SIGPROF",
|
|
4329
|
+
"SIGPWR",
|
|
4330
|
+
"SIGQUIT",
|
|
4331
|
+
"SIGSEGV",
|
|
4332
|
+
"SIGSTKFLT",
|
|
4333
|
+
"SIGSTOP",
|
|
4334
|
+
"SIGSYS",
|
|
4335
|
+
"SIGTERM",
|
|
4336
|
+
"SIGTRAP",
|
|
4337
|
+
"SIGTSTP",
|
|
4338
|
+
"SIGTTIN",
|
|
4339
|
+
"SIGTTOU",
|
|
4340
|
+
"SIGUNUSED",
|
|
4341
|
+
"SIGURG",
|
|
4342
|
+
"SIGUSR1",
|
|
4343
|
+
"SIGUSR2",
|
|
4344
|
+
"SIGVTALRM",
|
|
4345
|
+
"SIGWINCH",
|
|
4346
|
+
"SIGXCPU",
|
|
4347
|
+
"SIGXFSZ"
|
|
4348
|
+
]);
|
|
2960
4349
|
async function runPmStart(args) {
|
|
2961
4350
|
const parsed = parsePmStartArgs(args);
|
|
2962
4351
|
const workspaceRoot = process.cwd();
|
|
@@ -2989,9 +4378,130 @@ function resolveNamedMatches(paths, value) {
|
|
|
2989
4378
|
if (value === "all") {
|
|
2990
4379
|
return listPmRecordMatches(paths).map(syncPmRecordLiveness);
|
|
2991
4380
|
}
|
|
4381
|
+
const groupMatches = findPmGroupMatches(paths, value);
|
|
4382
|
+
if (groupMatches.length > 0) {
|
|
4383
|
+
return groupMatches.map(syncPmRecordLiveness);
|
|
4384
|
+
}
|
|
2992
4385
|
const match = findPmRecordMatch(paths, value);
|
|
2993
4386
|
return match ? [syncPmRecordLiveness(match)] : [];
|
|
2994
4387
|
}
|
|
4388
|
+
function sortPmMatchesByInstance(matches) {
|
|
4389
|
+
return [...matches].sort((left, right) => left.record.instanceIndex - right.record.instanceIndex);
|
|
4390
|
+
}
|
|
4391
|
+
function resolveInspectableMatch(paths, value) {
|
|
4392
|
+
const exactMatch = findPmRecordMatch(paths, value);
|
|
4393
|
+
const groupMatches = findPmGroupMatches(paths, value).map(syncPmRecordLiveness);
|
|
4394
|
+
if (groupMatches.length > 1 && exactMatch?.record.baseName === value && exactMatch.record.name === value) {
|
|
4395
|
+
throw new Error(`Multiple managed processes found for: ${value}. Use a specific instance name such as ${groupMatches[0]?.record.name} or ${groupMatches[1]?.record.name}.`);
|
|
4396
|
+
}
|
|
4397
|
+
if (exactMatch) {
|
|
4398
|
+
return syncPmRecordLiveness(exactMatch);
|
|
4399
|
+
}
|
|
4400
|
+
return groupMatches[0];
|
|
4401
|
+
}
|
|
4402
|
+
function rebuildPmRecordDefinition(record, targetInstances = record.instances) {
|
|
4403
|
+
const definition = resolvePmAppDefinition(
|
|
4404
|
+
toPmAppConfig(record),
|
|
4405
|
+
{ name: record.baseName, env: {}, watchPaths: [], watchIgnore: [], instances: targetInstances },
|
|
4406
|
+
process.cwd(),
|
|
4407
|
+
record.source
|
|
4408
|
+
);
|
|
4409
|
+
return {
|
|
4410
|
+
...definition,
|
|
4411
|
+
name: record.name,
|
|
4412
|
+
baseName: record.baseName,
|
|
4413
|
+
instanceIndex: record.instanceIndex,
|
|
4414
|
+
instances: targetInstances
|
|
4415
|
+
};
|
|
4416
|
+
}
|
|
4417
|
+
function rebuildPmSavedDefinition(app) {
|
|
4418
|
+
const definition = resolvePmAppDefinition(
|
|
4419
|
+
toSavedPmAppConfig(app),
|
|
4420
|
+
{ name: app.baseName, env: {}, watchPaths: [], watchIgnore: [], instances: app.instances },
|
|
4421
|
+
process.cwd(),
|
|
4422
|
+
"cli"
|
|
4423
|
+
);
|
|
4424
|
+
return {
|
|
4425
|
+
...definition,
|
|
4426
|
+
name: app.name,
|
|
4427
|
+
baseName: app.baseName,
|
|
4428
|
+
instanceIndex: app.instanceIndex,
|
|
4429
|
+
instances: app.instances
|
|
4430
|
+
};
|
|
4431
|
+
}
|
|
4432
|
+
function deletePmMatches(matches) {
|
|
4433
|
+
for (const match of matches) {
|
|
4434
|
+
if ((0, import_node_fs5.existsSync)(match.record.logFiles.out)) {
|
|
4435
|
+
(0, import_node_fs5.rmSync)(match.record.logFiles.out, { force: true });
|
|
4436
|
+
}
|
|
4437
|
+
if ((0, import_node_fs5.existsSync)(match.record.logFiles.err)) {
|
|
4438
|
+
(0, import_node_fs5.rmSync)(match.record.logFiles.err, { force: true });
|
|
4439
|
+
}
|
|
4440
|
+
(0, import_node_fs5.rmSync)(match.filePath, { force: true });
|
|
4441
|
+
}
|
|
4442
|
+
}
|
|
4443
|
+
function updatePmInstanceCount(matches, instances) {
|
|
4444
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4445
|
+
for (const match of matches) {
|
|
4446
|
+
writePmRecord(match.filePath, {
|
|
4447
|
+
...match.record,
|
|
4448
|
+
instances,
|
|
4449
|
+
updatedAt: now
|
|
4450
|
+
});
|
|
4451
|
+
}
|
|
4452
|
+
}
|
|
4453
|
+
function normalizePmSignalName(value) {
|
|
4454
|
+
const trimmed = value.trim().toUpperCase();
|
|
4455
|
+
const signalName = trimmed.startsWith("SIG") ? trimmed : `SIG${trimmed}`;
|
|
4456
|
+
if (!PM_SIGNAL_NAMES.has(signalName)) {
|
|
4457
|
+
throw new Error(`Unsupported pm signal: ${value}`);
|
|
4458
|
+
}
|
|
4459
|
+
return signalName;
|
|
4460
|
+
}
|
|
4461
|
+
function resolveSignalablePid(record) {
|
|
4462
|
+
if (record.childPid && record.childPid > 0) {
|
|
4463
|
+
return record.childPid;
|
|
4464
|
+
}
|
|
4465
|
+
if (record.runnerPid && record.runnerPid > 0) {
|
|
4466
|
+
return record.runnerPid;
|
|
4467
|
+
}
|
|
4468
|
+
return void 0;
|
|
4469
|
+
}
|
|
4470
|
+
function groupPmMatchesByBaseName(matches) {
|
|
4471
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
4472
|
+
for (const match of matches) {
|
|
4473
|
+
const group = grouped.get(match.record.baseName);
|
|
4474
|
+
if (group) {
|
|
4475
|
+
group.push(match);
|
|
4476
|
+
continue;
|
|
4477
|
+
}
|
|
4478
|
+
grouped.set(match.record.baseName, [match]);
|
|
4479
|
+
}
|
|
4480
|
+
return [...grouped.entries()].sort((left, right) => left[0].localeCompare(right[0])).map(([, group]) => sortPmMatchesByInstance(group));
|
|
4481
|
+
}
|
|
4482
|
+
async function waitForPmRecordOnline(filePath, timeoutMs) {
|
|
4483
|
+
const deadline = Date.now() + timeoutMs;
|
|
4484
|
+
while (Date.now() < deadline) {
|
|
4485
|
+
if (!(0, import_node_fs5.existsSync)(filePath)) {
|
|
4486
|
+
break;
|
|
4487
|
+
}
|
|
4488
|
+
const record = readPmRecord(filePath);
|
|
4489
|
+
if (record.status === "online") {
|
|
4490
|
+
return record;
|
|
4491
|
+
}
|
|
4492
|
+
if (record.status === "errored" || record.status === "exited" || record.status === "stopped") {
|
|
4493
|
+
throw new Error(record.error ?? `Process ${record.name} failed while reloading.`);
|
|
4494
|
+
}
|
|
4495
|
+
await new Promise((resolvePromise) => setTimeout(resolvePromise, 50));
|
|
4496
|
+
}
|
|
4497
|
+
throw new Error(`Timed out waiting for the reloaded process to become online after ${timeoutMs}ms.`);
|
|
4498
|
+
}
|
|
4499
|
+
function resolvePmReloadReadyTimeout(record) {
|
|
4500
|
+
return record.waitReady || record.proxy?.strategy === "inherit" ? Math.max(record.listenTimeout + 1e3, 2e3) : Math.max(record.restartDelay + 1e3, 2e3);
|
|
4501
|
+
}
|
|
4502
|
+
function supportsPmProxyReload2(record) {
|
|
4503
|
+
return Boolean(record.proxy) && record.instances === 1 && Boolean(record.runnerPid);
|
|
4504
|
+
}
|
|
2995
4505
|
function padCell(value, width) {
|
|
2996
4506
|
return value.length >= width ? value : `${value}${" ".repeat(width - value.length)}`;
|
|
2997
4507
|
}
|
|
@@ -3002,8 +4512,255 @@ function tailLogFile(filePath, lineCount) {
|
|
|
3002
4512
|
const lines = (0, import_node_fs5.readFileSync)(filePath, "utf8").split(/\r?\n/).filter((line) => line.length > 0);
|
|
3003
4513
|
return lines.slice(-lineCount).join(import_node_os2.EOL);
|
|
3004
4514
|
}
|
|
3005
|
-
function
|
|
3006
|
-
|
|
4515
|
+
function listPmMatches(paths) {
|
|
4516
|
+
return listPmRecordMatches(paths).map(syncPmRecordLiveness);
|
|
4517
|
+
}
|
|
4518
|
+
function isPmRecordActive(record) {
|
|
4519
|
+
return record.desiredState === "running" && (record.status === "starting" || record.status === "online" || record.status === "restarting");
|
|
4520
|
+
}
|
|
4521
|
+
function resolvePmUptimeMs(record) {
|
|
4522
|
+
if (!isPmRecordActive(record) || !record.startedAt) {
|
|
4523
|
+
return void 0;
|
|
4524
|
+
}
|
|
4525
|
+
const startedTime = Date.parse(record.startedAt);
|
|
4526
|
+
if (Number.isNaN(startedTime)) {
|
|
4527
|
+
return void 0;
|
|
4528
|
+
}
|
|
4529
|
+
return Math.max(0, Date.now() - startedTime);
|
|
4530
|
+
}
|
|
4531
|
+
function resolvePmLiveMetrics(record) {
|
|
4532
|
+
const uptimeMs = resolvePmUptimeMs(record);
|
|
4533
|
+
if (!isPmRecordActive(record) || !record.childPid) {
|
|
4534
|
+
return { uptimeMs };
|
|
4535
|
+
}
|
|
4536
|
+
const sampledMetrics = samplePmProcessMetrics(record.childPid);
|
|
4537
|
+
return {
|
|
4538
|
+
...sampledMetrics,
|
|
4539
|
+
uptimeMs,
|
|
4540
|
+
updatedAt: sampledMetrics.cpuPercent !== void 0 || sampledMetrics.memoryRssBytes !== void 0 ? (/* @__PURE__ */ new Date()).toISOString() : void 0
|
|
4541
|
+
};
|
|
4542
|
+
}
|
|
4543
|
+
function toPmDisplayRecord(record) {
|
|
4544
|
+
return {
|
|
4545
|
+
record,
|
|
4546
|
+
liveMetrics: resolvePmLiveMetrics(record)
|
|
4547
|
+
};
|
|
4548
|
+
}
|
|
4549
|
+
function serializePmRecord(record) {
|
|
4550
|
+
return {
|
|
4551
|
+
...record,
|
|
4552
|
+
liveMetrics: resolvePmLiveMetrics(record)
|
|
4553
|
+
};
|
|
4554
|
+
}
|
|
4555
|
+
function parsePmFormatOption(args, index, option) {
|
|
4556
|
+
let value;
|
|
4557
|
+
if (option.startsWith("--format=")) {
|
|
4558
|
+
value = option.slice("--format=".length);
|
|
4559
|
+
} else {
|
|
4560
|
+
value = readRequiredValue(args, index + 1, "--format");
|
|
4561
|
+
index += 1;
|
|
4562
|
+
}
|
|
4563
|
+
if (value !== "table" && value !== "json") {
|
|
4564
|
+
throw new Error(`Unsupported pm output format: ${value}`);
|
|
4565
|
+
}
|
|
4566
|
+
return {
|
|
4567
|
+
format: value,
|
|
4568
|
+
nextIndex: index
|
|
4569
|
+
};
|
|
4570
|
+
}
|
|
4571
|
+
function parsePmListArgs(args) {
|
|
4572
|
+
let format = "table";
|
|
4573
|
+
for (let index = 0; index < args.length; index++) {
|
|
4574
|
+
const arg = args[index];
|
|
4575
|
+
switch (arg) {
|
|
4576
|
+
case "--json":
|
|
4577
|
+
format = "json";
|
|
4578
|
+
break;
|
|
4579
|
+
case "--format":
|
|
4580
|
+
default:
|
|
4581
|
+
if (arg === "--format" || arg.startsWith("--format=")) {
|
|
4582
|
+
const parsed = parsePmFormatOption(args, index, arg);
|
|
4583
|
+
format = parsed.format;
|
|
4584
|
+
index = parsed.nextIndex;
|
|
4585
|
+
break;
|
|
4586
|
+
}
|
|
4587
|
+
throw new Error(`Unknown pm list option: ${arg}`);
|
|
4588
|
+
}
|
|
4589
|
+
}
|
|
4590
|
+
return { format };
|
|
4591
|
+
}
|
|
4592
|
+
function parsePmShowArgs(args) {
|
|
4593
|
+
let format = "text";
|
|
4594
|
+
let name;
|
|
4595
|
+
for (let index = 0; index < args.length; index++) {
|
|
4596
|
+
const arg = args[index];
|
|
4597
|
+
switch (arg) {
|
|
4598
|
+
case "--json":
|
|
4599
|
+
format = "json";
|
|
4600
|
+
break;
|
|
4601
|
+
case "--format":
|
|
4602
|
+
default:
|
|
4603
|
+
if (arg === "--format" || arg.startsWith("--format=")) {
|
|
4604
|
+
const parsed = parsePmFormatOption(args, index, arg);
|
|
4605
|
+
format = parsed.format === "json" ? "json" : "text";
|
|
4606
|
+
index = parsed.nextIndex;
|
|
4607
|
+
break;
|
|
4608
|
+
}
|
|
4609
|
+
if (arg.startsWith("-")) {
|
|
4610
|
+
throw new Error(`Unknown pm show option: ${arg}`);
|
|
4611
|
+
}
|
|
4612
|
+
if (name) {
|
|
4613
|
+
throw new Error("pm show accepts exactly one process name.");
|
|
4614
|
+
}
|
|
4615
|
+
name = arg;
|
|
4616
|
+
break;
|
|
4617
|
+
}
|
|
4618
|
+
}
|
|
4619
|
+
if (!name) {
|
|
4620
|
+
throw new Error("Usage: elit pm show <name> [--json]");
|
|
4621
|
+
}
|
|
4622
|
+
return { name, format };
|
|
4623
|
+
}
|
|
4624
|
+
function formatPmDuration(durationMs) {
|
|
4625
|
+
if (durationMs < 1e3) {
|
|
4626
|
+
return `${durationMs}ms`;
|
|
4627
|
+
}
|
|
4628
|
+
const totalSeconds = Math.floor(durationMs / 1e3);
|
|
4629
|
+
const days = Math.floor(totalSeconds / 86400);
|
|
4630
|
+
const hours = Math.floor(totalSeconds % 86400 / 3600);
|
|
4631
|
+
const minutes = Math.floor(totalSeconds % 3600 / 60);
|
|
4632
|
+
const seconds = totalSeconds % 60;
|
|
4633
|
+
const parts = [];
|
|
4634
|
+
if (days > 0) {
|
|
4635
|
+
parts.push(`${days}d`);
|
|
4636
|
+
}
|
|
4637
|
+
if (hours > 0) {
|
|
4638
|
+
parts.push(`${hours}h`);
|
|
4639
|
+
}
|
|
4640
|
+
if (minutes > 0) {
|
|
4641
|
+
parts.push(`${minutes}m`);
|
|
4642
|
+
}
|
|
4643
|
+
if (seconds > 0 || parts.length === 0) {
|
|
4644
|
+
parts.push(`${seconds}s`);
|
|
4645
|
+
}
|
|
4646
|
+
return parts.slice(0, 2).join(" ");
|
|
4647
|
+
}
|
|
4648
|
+
function formatPmCpuPercent(cpuPercent) {
|
|
4649
|
+
if (cpuPercent === void 0 || !Number.isFinite(cpuPercent)) {
|
|
4650
|
+
return "-";
|
|
4651
|
+
}
|
|
4652
|
+
return `${cpuPercent >= 100 ? cpuPercent.toFixed(0) : cpuPercent.toFixed(1)}%`;
|
|
4653
|
+
}
|
|
4654
|
+
function formatPmMemory(memoryRssBytes) {
|
|
4655
|
+
if (memoryRssBytes === void 0 || !Number.isFinite(memoryRssBytes) || memoryRssBytes < 0) {
|
|
4656
|
+
return "-";
|
|
4657
|
+
}
|
|
4658
|
+
const units = ["B", "KB", "MB", "GB", "TB"];
|
|
4659
|
+
let value = memoryRssBytes;
|
|
4660
|
+
let unitIndex = 0;
|
|
4661
|
+
while (value >= 1024 && unitIndex < units.length - 1) {
|
|
4662
|
+
value /= 1024;
|
|
4663
|
+
unitIndex += 1;
|
|
4664
|
+
}
|
|
4665
|
+
const formatted = value >= 10 || unitIndex === 0 ? value.toFixed(0) : value.toFixed(1);
|
|
4666
|
+
return `${formatted}${units[unitIndex]}`;
|
|
4667
|
+
}
|
|
4668
|
+
function formatPmUptime(uptimeMs) {
|
|
4669
|
+
if (uptimeMs === void 0 || !Number.isFinite(uptimeMs)) {
|
|
4670
|
+
return "-";
|
|
4671
|
+
}
|
|
4672
|
+
return formatPmDuration(Math.max(0, uptimeMs));
|
|
4673
|
+
}
|
|
4674
|
+
function formatPmTarget(record) {
|
|
4675
|
+
if (record.script) {
|
|
4676
|
+
return record.script;
|
|
4677
|
+
}
|
|
4678
|
+
if (record.file) {
|
|
4679
|
+
return record.file;
|
|
4680
|
+
}
|
|
4681
|
+
if (record.wapk) {
|
|
4682
|
+
return record.wapk;
|
|
4683
|
+
}
|
|
4684
|
+
return "-";
|
|
4685
|
+
}
|
|
4686
|
+
function pushPmDetail(lines, label, value) {
|
|
4687
|
+
lines.push(`${padCell(`${label}:`, 18)} ${value}`);
|
|
4688
|
+
}
|
|
4689
|
+
function pushPmDetailList(lines, label, values) {
|
|
4690
|
+
if (values.length === 0) {
|
|
4691
|
+
pushPmDetail(lines, label, "-");
|
|
4692
|
+
return;
|
|
4693
|
+
}
|
|
4694
|
+
pushPmDetail(lines, label, values[0] ?? "-");
|
|
4695
|
+
for (const value of values.slice(1)) {
|
|
4696
|
+
lines.push(`${" ".repeat(20)} ${value}`);
|
|
4697
|
+
}
|
|
4698
|
+
}
|
|
4699
|
+
function formatPmRecordDetails(record, liveMetrics) {
|
|
4700
|
+
const lines = [`Process: ${record.name}`];
|
|
4701
|
+
pushPmDetail(lines, "id", record.id);
|
|
4702
|
+
pushPmDetail(lines, "status", record.status);
|
|
4703
|
+
pushPmDetail(lines, "desired state", record.desiredState);
|
|
4704
|
+
pushPmDetail(lines, "instance", `${record.instanceIndex}/${record.instances}`);
|
|
4705
|
+
pushPmDetail(lines, "cpu", formatPmCpuPercent(liveMetrics.cpuPercent));
|
|
4706
|
+
pushPmDetail(lines, "memory", formatPmMemory(liveMetrics.memoryRssBytes));
|
|
4707
|
+
pushPmDetail(lines, "uptime", formatPmUptime(liveMetrics.uptimeMs));
|
|
4708
|
+
pushPmDetail(lines, "type", record.type);
|
|
4709
|
+
pushPmDetail(lines, "source", record.source);
|
|
4710
|
+
pushPmDetail(lines, "runtime", record.runtime ?? "-");
|
|
4711
|
+
pushPmDetail(lines, "cwd", record.cwd);
|
|
4712
|
+
pushPmDetail(lines, "target", formatPmTarget(record));
|
|
4713
|
+
pushPmDetail(lines, "command", record.commandPreview || "-");
|
|
4714
|
+
pushPmDetail(lines, "runner pid", record.runnerPid ? String(record.runnerPid) : "-");
|
|
4715
|
+
pushPmDetail(lines, "child pid", record.childPid ? String(record.childPid) : "-");
|
|
4716
|
+
pushPmDetail(lines, "restart count", `${record.restartCount}/${record.maxRestarts}`);
|
|
4717
|
+
pushPmDetail(lines, "restart policy", record.restartPolicy);
|
|
4718
|
+
pushPmDetail(lines, "proxy", record.proxy ? `http://${record.proxy.host ?? "0.0.0.0"}:${record.proxy.port}` : "-");
|
|
4719
|
+
pushPmDetail(lines, "proxy strategy", record.proxy?.strategy ?? "-");
|
|
4720
|
+
pushPmDetail(lines, "proxy target", record.proxy && record.proxyTargetPort ? `${record.proxy.targetHost ?? "127.0.0.1"}:${record.proxyTargetPort}` : "-");
|
|
4721
|
+
pushPmDetail(lines, "max memory", record.maxMemoryBytes ? formatPmMemory(record.maxMemoryBytes) : "-");
|
|
4722
|
+
pushPmDetail(lines, "memory action", record.memoryAction ?? "-");
|
|
4723
|
+
pushPmDetail(lines, "cron restart", record.cronRestart ?? "-");
|
|
4724
|
+
pushPmDetail(lines, "exp backoff", record.expBackoffRestartDelay ? formatPmDuration(record.expBackoffRestartDelay) : "-");
|
|
4725
|
+
pushPmDetail(lines, "exp backoff max", record.expBackoffRestartMaxDelay ? formatPmDuration(record.expBackoffRestartMaxDelay) : "-");
|
|
4726
|
+
pushPmDetail(lines, "restart window", record.restartWindow ? formatPmDuration(record.restartWindow) : "-");
|
|
4727
|
+
pushPmDetail(lines, "wait ready", record.waitReady ? "enabled" : "disabled");
|
|
4728
|
+
pushPmDetail(lines, "listen timeout", record.waitReady ? formatPmDuration(record.listenTimeout) : "-");
|
|
4729
|
+
pushPmDetail(lines, "restart delay", formatPmDuration(record.restartDelay));
|
|
4730
|
+
pushPmDetail(lines, "kill timeout", formatPmDuration(record.killTimeout));
|
|
4731
|
+
pushPmDetail(lines, "min uptime", formatPmDuration(record.minUptime));
|
|
4732
|
+
pushPmDetail(lines, "autorestart", record.autorestart ? "enabled" : "disabled");
|
|
4733
|
+
pushPmDetail(lines, "watch", record.watch ? "enabled" : "disabled");
|
|
4734
|
+
pushPmDetail(lines, "watch debounce", record.watch ? formatPmDuration(record.watchDebounce) : "-");
|
|
4735
|
+
pushPmDetailList(lines, "watch paths", record.watchPaths);
|
|
4736
|
+
pushPmDetailList(lines, "watch ignore", record.watchIgnore);
|
|
4737
|
+
if (record.healthCheck) {
|
|
4738
|
+
pushPmDetail(lines, "health check", record.healthCheck.url);
|
|
4739
|
+
pushPmDetail(lines, "health grace", formatPmDuration(record.healthCheck.gracePeriod));
|
|
4740
|
+
pushPmDetail(lines, "health interval", formatPmDuration(record.healthCheck.interval));
|
|
4741
|
+
pushPmDetail(lines, "health timeout", formatPmDuration(record.healthCheck.timeout));
|
|
4742
|
+
pushPmDetail(lines, "health failures", String(record.healthCheck.maxFailures));
|
|
4743
|
+
} else {
|
|
4744
|
+
pushPmDetail(lines, "health check", "-");
|
|
4745
|
+
}
|
|
4746
|
+
pushPmDetailList(lines, "env", Object.entries(record.env).map(([key, value]) => `${key}=${value}`));
|
|
4747
|
+
pushPmDetail(lines, "stdout log", record.logFiles.out);
|
|
4748
|
+
pushPmDetail(lines, "stderr log", record.logFiles.err);
|
|
4749
|
+
pushPmDetail(lines, "created at", record.createdAt);
|
|
4750
|
+
pushPmDetail(lines, "updated at", record.updatedAt);
|
|
4751
|
+
pushPmDetail(lines, "metrics at", liveMetrics.updatedAt ?? "-");
|
|
4752
|
+
pushPmDetail(lines, "started at", record.startedAt ?? "-");
|
|
4753
|
+
pushPmDetail(lines, "stopped at", record.stoppedAt ?? "-");
|
|
4754
|
+
pushPmDetail(lines, "last exit", record.lastExitCode === void 0 ? "-" : String(record.lastExitCode));
|
|
4755
|
+
pushPmDetail(lines, "error", record.error ?? "-");
|
|
4756
|
+
return lines.join(import_node_os2.EOL);
|
|
4757
|
+
}
|
|
4758
|
+
function printPmList(paths, format = "table") {
|
|
4759
|
+
const matches = listPmMatches(paths).map((match) => toPmDisplayRecord(match.record));
|
|
4760
|
+
if (format === "json") {
|
|
4761
|
+
console.log(JSON.stringify(matches.map((match) => ({ ...match.record, liveMetrics: match.liveMetrics })), null, 2));
|
|
4762
|
+
return;
|
|
4763
|
+
}
|
|
3007
4764
|
if (matches.length === 0) {
|
|
3008
4765
|
console.log("No managed processes found.");
|
|
3009
4766
|
return;
|
|
@@ -3012,22 +4769,47 @@ function printPmList(paths) {
|
|
|
3012
4769
|
padCell("name", 20),
|
|
3013
4770
|
padCell("status", 12),
|
|
3014
4771
|
padCell("pid", 8),
|
|
4772
|
+
padCell("cpu", 8),
|
|
4773
|
+
padCell("memory", 10),
|
|
4774
|
+
padCell("uptime", 10),
|
|
3015
4775
|
padCell("restarts", 10),
|
|
3016
4776
|
padCell("type", 8),
|
|
3017
4777
|
"runtime"
|
|
3018
4778
|
];
|
|
3019
4779
|
console.log(headers.join(" "));
|
|
3020
|
-
for (const { record } of matches) {
|
|
4780
|
+
for (const { record, liveMetrics } of matches) {
|
|
3021
4781
|
console.log([
|
|
3022
4782
|
padCell(record.name, 20),
|
|
3023
4783
|
padCell(record.status, 12),
|
|
3024
4784
|
padCell(record.childPid ? String(record.childPid) : "-", 8),
|
|
4785
|
+
padCell(formatPmCpuPercent(liveMetrics.cpuPercent), 8),
|
|
4786
|
+
padCell(formatPmMemory(liveMetrics.memoryRssBytes), 10),
|
|
4787
|
+
padCell(formatPmUptime(liveMetrics.uptimeMs), 10),
|
|
3025
4788
|
padCell(String(record.restartCount ?? 0), 10),
|
|
3026
4789
|
padCell(record.type, 8),
|
|
3027
4790
|
record.runtime ?? "-"
|
|
3028
4791
|
].join(" "));
|
|
3029
4792
|
}
|
|
3030
4793
|
}
|
|
4794
|
+
async function runPmList(args) {
|
|
4795
|
+
const options = parsePmListArgs(args);
|
|
4796
|
+
const { paths } = await loadPmContext();
|
|
4797
|
+
printPmList(paths, options.format);
|
|
4798
|
+
}
|
|
4799
|
+
async function runPmShow(args) {
|
|
4800
|
+
const options = parsePmShowArgs(args);
|
|
4801
|
+
const { paths } = await loadPmContext();
|
|
4802
|
+
const match = resolveInspectableMatch(paths, options.name);
|
|
4803
|
+
if (!match) {
|
|
4804
|
+
throw new Error(`No managed process found for: ${options.name}`);
|
|
4805
|
+
}
|
|
4806
|
+
const synced = syncPmRecordLiveness(match);
|
|
4807
|
+
if (options.format === "json") {
|
|
4808
|
+
console.log(JSON.stringify(serializePmRecord(synced.record), null, 2));
|
|
4809
|
+
return;
|
|
4810
|
+
}
|
|
4811
|
+
console.log(formatPmRecordDetails(synced.record, resolvePmLiveMetrics(synced.record)));
|
|
4812
|
+
}
|
|
3031
4813
|
async function runPmStop(args) {
|
|
3032
4814
|
const target = args[0];
|
|
3033
4815
|
if (!target) {
|
|
@@ -3054,17 +4836,62 @@ async function runPmRestart(args) {
|
|
|
3054
4836
|
await stopPmMatches(matches);
|
|
3055
4837
|
const restarted = [];
|
|
3056
4838
|
for (const match of matches) {
|
|
3057
|
-
const definition =
|
|
3058
|
-
toPmAppConfig(match.record),
|
|
3059
|
-
{ name: match.record.name, env: {}, watchPaths: [], watchIgnore: [] },
|
|
3060
|
-
process.cwd(),
|
|
3061
|
-
match.record.source
|
|
3062
|
-
);
|
|
4839
|
+
const definition = rebuildPmRecordDefinition(match.record);
|
|
3063
4840
|
await startManagedProcess(definition, paths);
|
|
3064
4841
|
restarted.push(match.record.name);
|
|
3065
4842
|
}
|
|
3066
4843
|
console.log(`[pm] restarted ${restarted.join(", ")}`);
|
|
3067
4844
|
}
|
|
4845
|
+
async function runPmReload(args) {
|
|
4846
|
+
const target = args[0];
|
|
4847
|
+
if (!target) {
|
|
4848
|
+
throw new Error("Usage: elit pm reload <name|all>");
|
|
4849
|
+
}
|
|
4850
|
+
const { paths } = await loadPmContext();
|
|
4851
|
+
const matches = resolveNamedMatches(paths, target);
|
|
4852
|
+
if (matches.length === 0) {
|
|
4853
|
+
throw new Error(`No managed process found for: ${target}`);
|
|
4854
|
+
}
|
|
4855
|
+
const reloaded = [];
|
|
4856
|
+
const errors = [];
|
|
4857
|
+
for (const group of groupPmMatchesByBaseName(matches)) {
|
|
4858
|
+
for (const match of group) {
|
|
4859
|
+
try {
|
|
4860
|
+
if (supportsPmProxyReload2(match.record)) {
|
|
4861
|
+
const reloadRequestedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
4862
|
+
writePmRecord(match.filePath, {
|
|
4863
|
+
...match.record,
|
|
4864
|
+
status: "restarting",
|
|
4865
|
+
reloadRequestedAt,
|
|
4866
|
+
updatedAt: reloadRequestedAt,
|
|
4867
|
+
error: void 0
|
|
4868
|
+
});
|
|
4869
|
+
await waitForPmRecordOnline(
|
|
4870
|
+
getPmRecordPath(paths, match.record.id),
|
|
4871
|
+
resolvePmReloadReadyTimeout(match.record)
|
|
4872
|
+
);
|
|
4873
|
+
reloaded.push(match.record.name);
|
|
4874
|
+
continue;
|
|
4875
|
+
}
|
|
4876
|
+
await stopPmMatches([match]);
|
|
4877
|
+
const definition = rebuildPmRecordDefinition(match.record);
|
|
4878
|
+
const startedRecord = await startManagedProcess(definition, paths);
|
|
4879
|
+
await waitForPmRecordOnline(
|
|
4880
|
+
getPmRecordPath(paths, startedRecord.id),
|
|
4881
|
+
resolvePmReloadReadyTimeout(startedRecord)
|
|
4882
|
+
);
|
|
4883
|
+
reloaded.push(match.record.name);
|
|
4884
|
+
} catch (error) {
|
|
4885
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
4886
|
+
errors.push(`[pm] ${match.record.name}: ${message}`);
|
|
4887
|
+
}
|
|
4888
|
+
}
|
|
4889
|
+
}
|
|
4890
|
+
if (errors.length > 0) {
|
|
4891
|
+
throw new Error([`[pm] reloaded ${reloaded.length} process${reloaded.length === 1 ? "" : "es"}`, ...errors].join(import_node_os2.EOL));
|
|
4892
|
+
}
|
|
4893
|
+
console.log(`[pm] reloaded ${reloaded.join(", ")}`);
|
|
4894
|
+
}
|
|
3068
4895
|
async function runPmSave() {
|
|
3069
4896
|
const { paths } = await loadPmContext();
|
|
3070
4897
|
ensurePmDirectories(paths);
|
|
@@ -3086,12 +4913,7 @@ async function runPmResurrect() {
|
|
|
3086
4913
|
let restored = 0;
|
|
3087
4914
|
for (const app of dump.apps) {
|
|
3088
4915
|
try {
|
|
3089
|
-
const definition =
|
|
3090
|
-
toSavedPmAppConfig(app),
|
|
3091
|
-
{ name: app.name, env: {}, watchPaths: [], watchIgnore: [] },
|
|
3092
|
-
process.cwd(),
|
|
3093
|
-
"cli"
|
|
3094
|
-
);
|
|
4916
|
+
const definition = rebuildPmSavedDefinition(app);
|
|
3095
4917
|
await startManagedProcess(definition, paths);
|
|
3096
4918
|
restored += 1;
|
|
3097
4919
|
} catch (error) {
|
|
@@ -3115,16 +4937,127 @@ async function runPmDelete(args) {
|
|
|
3115
4937
|
throw new Error(`No managed process found for: ${target}`);
|
|
3116
4938
|
}
|
|
3117
4939
|
await stopPmMatches(matches);
|
|
4940
|
+
deletePmMatches(matches);
|
|
4941
|
+
console.log(`[pm] deleted ${matches.length} process${matches.length === 1 ? "" : "es"}`);
|
|
4942
|
+
}
|
|
4943
|
+
async function runPmScale(args) {
|
|
4944
|
+
const target = args[0];
|
|
4945
|
+
const countArg = args[1];
|
|
4946
|
+
if (!target || countArg === void 0 || args.length > 2) {
|
|
4947
|
+
throw new Error("Usage: elit pm scale <name> <count>");
|
|
4948
|
+
}
|
|
4949
|
+
const desiredCount = normalizeIntegerOption(countArg, "pm scale <count>", 0);
|
|
4950
|
+
const { config, paths } = await loadPmContext();
|
|
4951
|
+
const exactMatch = findPmRecordMatch(paths, target);
|
|
4952
|
+
const currentMatches = sortPmMatchesByInstance(resolveNamedMatches(paths, exactMatch?.record.baseName ?? target));
|
|
4953
|
+
if (currentMatches.length === 0) {
|
|
4954
|
+
if (desiredCount === 0) {
|
|
4955
|
+
console.log(`[pm] ${target} already scaled to 0 instances`);
|
|
4956
|
+
return;
|
|
4957
|
+
}
|
|
4958
|
+
const definitions = resolvePmStartDefinitions(
|
|
4959
|
+
{ name: target, env: {}, watchPaths: [], watchIgnore: [], instances: desiredCount },
|
|
4960
|
+
config,
|
|
4961
|
+
process.cwd()
|
|
4962
|
+
);
|
|
4963
|
+
for (const definition of definitions) {
|
|
4964
|
+
await startManagedProcess(definition, paths);
|
|
4965
|
+
}
|
|
4966
|
+
console.log(`[pm] scaled ${target} to ${desiredCount} instance${desiredCount === 1 ? "" : "s"}`);
|
|
4967
|
+
return;
|
|
4968
|
+
}
|
|
4969
|
+
const baseName = currentMatches[0]?.record.baseName ?? target;
|
|
4970
|
+
if (desiredCount === currentMatches.length) {
|
|
4971
|
+
console.log(`[pm] ${baseName} already scaled to ${desiredCount} instance${desiredCount === 1 ? "" : "s"}`);
|
|
4972
|
+
return;
|
|
4973
|
+
}
|
|
4974
|
+
if (desiredCount === 0) {
|
|
4975
|
+
await stopPmMatches(currentMatches);
|
|
4976
|
+
deletePmMatches(currentMatches);
|
|
4977
|
+
console.log(`[pm] scaled ${baseName} to 0 instances`);
|
|
4978
|
+
return;
|
|
4979
|
+
}
|
|
4980
|
+
if (desiredCount < currentMatches.length) {
|
|
4981
|
+
const toRemove = [...currentMatches].sort((left, right) => right.record.instanceIndex - left.record.instanceIndex).slice(0, currentMatches.length - desiredCount);
|
|
4982
|
+
const remaining = currentMatches.filter((match) => !toRemove.some((removal) => removal.record.id === match.record.id));
|
|
4983
|
+
await stopPmMatches(toRemove);
|
|
4984
|
+
deletePmMatches(toRemove);
|
|
4985
|
+
updatePmInstanceCount(remaining, desiredCount);
|
|
4986
|
+
console.log(`[pm] scaled ${baseName} to ${desiredCount} instance${desiredCount === 1 ? "" : "s"}`);
|
|
4987
|
+
return;
|
|
4988
|
+
}
|
|
4989
|
+
updatePmInstanceCount(currentMatches, desiredCount);
|
|
4990
|
+
const baseRecord = currentMatches[0]?.record;
|
|
4991
|
+
if (!baseRecord) {
|
|
4992
|
+
throw new Error(`No managed process found for: ${target}`);
|
|
4993
|
+
}
|
|
4994
|
+
const baseDefinition = rebuildPmRecordDefinition(baseRecord, desiredCount);
|
|
4995
|
+
const expandedDefinitions = expandPmInstanceDefinitions(baseDefinition, desiredCount);
|
|
4996
|
+
const existingNames = new Set(currentMatches.map((match) => match.record.name));
|
|
4997
|
+
for (const definition of expandedDefinitions) {
|
|
4998
|
+
if (existingNames.has(definition.name)) {
|
|
4999
|
+
continue;
|
|
5000
|
+
}
|
|
5001
|
+
await startManagedProcess(definition, paths);
|
|
5002
|
+
}
|
|
5003
|
+
console.log(`[pm] scaled ${baseName} to ${desiredCount} instance${desiredCount === 1 ? "" : "s"}`);
|
|
5004
|
+
}
|
|
5005
|
+
async function runPmSendSignal(args) {
|
|
5006
|
+
const signalArg = args[0];
|
|
5007
|
+
const target = args[1];
|
|
5008
|
+
if (!signalArg || !target || args.length > 2) {
|
|
5009
|
+
throw new Error("Usage: elit pm send-signal <signal> <name|all>");
|
|
5010
|
+
}
|
|
5011
|
+
const signalName = normalizePmSignalName(signalArg);
|
|
5012
|
+
const { paths } = await loadPmContext();
|
|
5013
|
+
const matches = resolveNamedMatches(paths, target);
|
|
5014
|
+
if (matches.length === 0) {
|
|
5015
|
+
throw new Error(`No managed process found for: ${target}`);
|
|
5016
|
+
}
|
|
5017
|
+
let signaled = 0;
|
|
5018
|
+
const errors = [];
|
|
3118
5019
|
for (const match of matches) {
|
|
3119
|
-
|
|
3120
|
-
|
|
5020
|
+
const pid = resolveSignalablePid(match.record);
|
|
5021
|
+
if (!pid) {
|
|
5022
|
+
continue;
|
|
3121
5023
|
}
|
|
3122
|
-
|
|
3123
|
-
(
|
|
5024
|
+
try {
|
|
5025
|
+
sendPmSignal(pid, signalName);
|
|
5026
|
+
signaled += 1;
|
|
5027
|
+
} catch (error) {
|
|
5028
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
5029
|
+
errors.push(`[pm] ${match.record.name}: ${message}`);
|
|
3124
5030
|
}
|
|
3125
|
-
(0, import_node_fs5.rmSync)(match.filePath, { force: true });
|
|
3126
5031
|
}
|
|
3127
|
-
|
|
5032
|
+
if (errors.length > 0) {
|
|
5033
|
+
throw new Error([`[pm] sent ${signalName} to ${signaled} process${signaled === 1 ? "" : "es"}`, ...errors].join(import_node_os2.EOL));
|
|
5034
|
+
}
|
|
5035
|
+
if (signaled === 0) {
|
|
5036
|
+
throw new Error(`No running managed process found for: ${target}`);
|
|
5037
|
+
}
|
|
5038
|
+
console.log(`[pm] sent ${signalName} to ${signaled} process${signaled === 1 ? "" : "es"}`);
|
|
5039
|
+
}
|
|
5040
|
+
async function runPmReset(args) {
|
|
5041
|
+
const target = args[0];
|
|
5042
|
+
if (!target) {
|
|
5043
|
+
throw new Error("Usage: elit pm reset <name|all>");
|
|
5044
|
+
}
|
|
5045
|
+
const { paths } = await loadPmContext();
|
|
5046
|
+
const matches = resolveNamedMatches(paths, target);
|
|
5047
|
+
if (matches.length === 0) {
|
|
5048
|
+
throw new Error(`No managed process found for: ${target}`);
|
|
5049
|
+
}
|
|
5050
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
5051
|
+
for (const match of matches) {
|
|
5052
|
+
writePmRecord(match.filePath, {
|
|
5053
|
+
...match.record,
|
|
5054
|
+
restartCount: 0,
|
|
5055
|
+
lastExitCode: void 0,
|
|
5056
|
+
error: void 0,
|
|
5057
|
+
updatedAt: now
|
|
5058
|
+
});
|
|
5059
|
+
}
|
|
5060
|
+
console.log(`[pm] reset ${matches.length} process${matches.length === 1 ? "" : "es"}`);
|
|
3128
5061
|
}
|
|
3129
5062
|
async function runPmLogs(args) {
|
|
3130
5063
|
if (args.length === 0) {
|
|
@@ -3157,7 +5090,7 @@ async function runPmLogs(args) {
|
|
|
3157
5090
|
throw new Error("Usage: elit pm logs <name> [--lines <n>] [--stderr]");
|
|
3158
5091
|
}
|
|
3159
5092
|
const { paths } = await loadPmContext();
|
|
3160
|
-
const match =
|
|
5093
|
+
const match = resolveInspectableMatch(paths, name);
|
|
3161
5094
|
if (!match) {
|
|
3162
5095
|
throw new Error(`No managed process found for: ${name}`);
|
|
3163
5096
|
}
|
|
@@ -3181,17 +5114,36 @@ async function runPmCommand(args) {
|
|
|
3181
5114
|
await runPmStart(args.slice(1));
|
|
3182
5115
|
return;
|
|
3183
5116
|
case "list":
|
|
3184
|
-
case "ls":
|
|
3185
|
-
|
|
3186
|
-
|
|
5117
|
+
case "ls":
|
|
5118
|
+
await runPmList(args.slice(1));
|
|
5119
|
+
return;
|
|
5120
|
+
case "jlist":
|
|
5121
|
+
await runPmList(["--json", ...args.slice(1)]);
|
|
5122
|
+
return;
|
|
5123
|
+
case "show":
|
|
5124
|
+
case "describe":
|
|
5125
|
+
await runPmShow(args.slice(1));
|
|
3187
5126
|
return;
|
|
3188
|
-
}
|
|
3189
5127
|
case "stop":
|
|
3190
5128
|
await runPmStop(args.slice(1));
|
|
3191
5129
|
return;
|
|
3192
5130
|
case "restart":
|
|
3193
5131
|
await runPmRestart(args.slice(1));
|
|
3194
5132
|
return;
|
|
5133
|
+
case "reload":
|
|
5134
|
+
await runPmReload(args.slice(1));
|
|
5135
|
+
return;
|
|
5136
|
+
case "scale":
|
|
5137
|
+
await runPmScale(args.slice(1));
|
|
5138
|
+
return;
|
|
5139
|
+
case "send-signal":
|
|
5140
|
+
case "signal":
|
|
5141
|
+
case "sendSignal":
|
|
5142
|
+
await runPmSendSignal(args.slice(1));
|
|
5143
|
+
return;
|
|
5144
|
+
case "reset":
|
|
5145
|
+
await runPmReset(args.slice(1));
|
|
5146
|
+
return;
|
|
3195
5147
|
case "delete":
|
|
3196
5148
|
case "remove":
|
|
3197
5149
|
case "rm":
|
|
@@ -3224,6 +5176,12 @@ async function runPmCommand(args) {
|
|
|
3224
5176
|
DEFAULT_MIN_UPTIME,
|
|
3225
5177
|
DEFAULT_PM_DATA_DIR,
|
|
3226
5178
|
DEFAULT_PM_DUMP_FILE,
|
|
5179
|
+
DEFAULT_PM_EXP_BACKOFF_MAX_DELAY,
|
|
5180
|
+
DEFAULT_PM_KILL_TIMEOUT,
|
|
5181
|
+
DEFAULT_PM_LISTEN_TIMEOUT,
|
|
5182
|
+
DEFAULT_PM_MEMORY_CHECK_INTERVAL,
|
|
5183
|
+
DEFAULT_PM_PROXY_STRATEGY,
|
|
5184
|
+
DEFAULT_PM_RESTART_WINDOW,
|
|
3227
5185
|
DEFAULT_PM_STOP_GRACE_PERIOD_MS,
|
|
3228
5186
|
DEFAULT_PM_STOP_POLL_MS,
|
|
3229
5187
|
DEFAULT_RESTART_DELAY,
|
|
@@ -3242,7 +5200,10 @@ async function runPmCommand(args) {
|
|
|
3242
5200
|
countDefinedPmWapkSources,
|
|
3243
5201
|
deriveDefaultWatchPaths,
|
|
3244
5202
|
ensurePmDirectories,
|
|
5203
|
+
expandPmInstanceDefinitions,
|
|
5204
|
+
findPmGroupMatches,
|
|
3245
5205
|
findPmRecordMatch,
|
|
5206
|
+
formatPmInstanceName,
|
|
3246
5207
|
getPmRecordPath,
|
|
3247
5208
|
hasPmGoogleDriveConfig,
|
|
3248
5209
|
hasPmWapkRunConfig,
|
|
@@ -3261,6 +5222,10 @@ async function runPmCommand(args) {
|
|
|
3261
5222
|
normalizeHealthCheckConfig,
|
|
3262
5223
|
normalizeIntegerOption,
|
|
3263
5224
|
normalizeNonEmptyString,
|
|
5225
|
+
normalizePmMemoryAction,
|
|
5226
|
+
normalizePmMemoryLimit,
|
|
5227
|
+
normalizePmProxyConfig,
|
|
5228
|
+
normalizePmProxyStrategy,
|
|
3264
5229
|
normalizePmRestartPolicy,
|
|
3265
5230
|
normalizePmRuntime,
|
|
3266
5231
|
normalizeResolvedWatchIgnorePaths,
|
|
@@ -3284,7 +5249,9 @@ async function runPmCommand(args) {
|
|
|
3284
5249
|
runManagedProcessLoop,
|
|
3285
5250
|
runPmCommand,
|
|
3286
5251
|
runPmRunner,
|
|
5252
|
+
samplePmProcessMetrics,
|
|
3287
5253
|
sanitizePmProcessName,
|
|
5254
|
+
sendPmSignal,
|
|
3288
5255
|
startManagedProcess,
|
|
3289
5256
|
stopPmMatches,
|
|
3290
5257
|
stripPmWapkSourceFromRunConfig,
|