relife2 1.0.1 → 1.0.3
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/dist/cli.js +471 -228
- package/dist/cli.js.map +3 -3
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -14,13 +14,13 @@ var jsloader_exports = {};
|
|
|
14
14
|
__export(jsloader_exports, {
|
|
15
15
|
loadJsFile: () => loadJsFile
|
|
16
16
|
});
|
|
17
|
-
import * as
|
|
17
|
+
import * as fs7 from "node:fs";
|
|
18
18
|
import { createRequire } from "node:module";
|
|
19
|
-
import
|
|
19
|
+
import path7 from "node:path";
|
|
20
20
|
import { pathToFileURL } from "node:url";
|
|
21
21
|
async function loadJsFile(file) {
|
|
22
|
-
const ext =
|
|
23
|
-
const source =
|
|
22
|
+
const ext = path7.extname(file).toLowerCase();
|
|
23
|
+
const source = fs7.readFileSync(file, "utf8");
|
|
24
24
|
if (ext === ".mjs") return importFreshEs(file);
|
|
25
25
|
if (ext === ".cjs") return loadCjs(file, source);
|
|
26
26
|
if (CJS_MARKER.test(source)) return loadCjs(file, source);
|
|
@@ -51,7 +51,7 @@ function compileCjs(file, source) {
|
|
|
51
51
|
const nodeModule = req("node:module");
|
|
52
52
|
const mod = new nodeModule.Module(file, null);
|
|
53
53
|
mod.filename = file;
|
|
54
|
-
mod.paths = nodeModule._nodeModulePaths(
|
|
54
|
+
mod.paths = nodeModule._nodeModulePaths(path7.dirname(file));
|
|
55
55
|
mod._compile(source, file);
|
|
56
56
|
return mod.exports;
|
|
57
57
|
}
|
|
@@ -65,6 +65,7 @@ var init_jsloader = __esm({
|
|
|
65
65
|
|
|
66
66
|
// src/daemon/client.ts
|
|
67
67
|
import { spawn } from "node:child_process";
|
|
68
|
+
import fs3 from "node:fs";
|
|
68
69
|
import net from "node:net";
|
|
69
70
|
import path3 from "node:path";
|
|
70
71
|
|
|
@@ -318,28 +319,50 @@ var RpcClient = class {
|
|
|
318
319
|
async function ensureDaemonClient() {
|
|
319
320
|
const runtime = runtimeDir();
|
|
320
321
|
const data = dataDir();
|
|
322
|
+
for (const dir of [runtime, data, path3.join(data, "logs"), path3.join(data, "state")]) {
|
|
323
|
+
try {
|
|
324
|
+
fs3.mkdirSync(dir, { recursive: true });
|
|
325
|
+
} catch {
|
|
326
|
+
}
|
|
327
|
+
}
|
|
321
328
|
let sock = await tryConnect(runtime);
|
|
329
|
+
if (sock !== null) {
|
|
330
|
+
return await validateClient(sock);
|
|
331
|
+
}
|
|
332
|
+
spawnDaemon();
|
|
333
|
+
for (let i = 0; i < 80 && sock === null; i++) {
|
|
334
|
+
await sleep(100);
|
|
335
|
+
sock = await tryConnect(runtime);
|
|
336
|
+
}
|
|
322
337
|
if (sock === null) {
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
await sleep(100);
|
|
326
|
-
sock = await tryConnect(runtime);
|
|
327
|
-
}
|
|
338
|
+
await sleep(200);
|
|
339
|
+
sock = await tryConnect(runtime);
|
|
328
340
|
}
|
|
329
341
|
if (sock === null) {
|
|
330
342
|
throw new Error(
|
|
331
|
-
`cannot connect to relife2 daemon (runtime: ${runtime}, data: ${data}); inspect ${path3.join(data, "logs", "daemon.log")}`
|
|
343
|
+
`cannot connect to relife2 daemon (runtime: ${runtime}, data: ${data}); try 'relife2 doctor --fix' or inspect ${path3.join(data, "logs", "daemon.log")}`
|
|
332
344
|
);
|
|
333
345
|
}
|
|
346
|
+
return await validateClient(sock);
|
|
347
|
+
}
|
|
348
|
+
async function validateClient(sock) {
|
|
334
349
|
const client = new RpcClient(sock);
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
350
|
+
try {
|
|
351
|
+
const ping = await client.call("ping");
|
|
352
|
+
if (ping.protocol !== PROTOCOL_VERSION) {
|
|
353
|
+
client.close();
|
|
354
|
+
throw new Error(
|
|
355
|
+
`daemon protocol mismatch (daemon: ${ping.protocol}, cli: ${PROTOCOL_VERSION}); run 'relife2 kill' to stop the stale daemon`
|
|
356
|
+
);
|
|
357
|
+
}
|
|
358
|
+
return client;
|
|
359
|
+
} catch (err) {
|
|
360
|
+
try {
|
|
361
|
+
client.close();
|
|
362
|
+
} catch {
|
|
363
|
+
}
|
|
364
|
+
throw err;
|
|
341
365
|
}
|
|
342
|
-
return client;
|
|
343
366
|
}
|
|
344
367
|
function spawnDaemon() {
|
|
345
368
|
const arg1 = process.argv[1];
|
|
@@ -493,7 +516,7 @@ async function run2(args) {
|
|
|
493
516
|
|
|
494
517
|
// src/commands/dev.ts
|
|
495
518
|
import { spawn as spawn2, spawnSync } from "node:child_process";
|
|
496
|
-
import * as
|
|
519
|
+
import * as fs4 from "node:fs";
|
|
497
520
|
import path4 from "node:path";
|
|
498
521
|
var DEV_VALUE_OPTS = /* @__PURE__ */ new Set(["watch", "interpreter", "env", "name", "w"]);
|
|
499
522
|
function hasBun() {
|
|
@@ -511,7 +534,7 @@ function isBunProject(cwd) {
|
|
|
511
534
|
let cur = path4.resolve(cwd);
|
|
512
535
|
const home = path4.resolve(process.env.HOME ?? "");
|
|
513
536
|
for (let i = 0; i < 12; i++) {
|
|
514
|
-
if (
|
|
537
|
+
if (fs4.existsSync(path4.join(cur, "bun.lockb")) || fs4.existsSync(path4.join(cur, "bun.lock")))
|
|
515
538
|
return true;
|
|
516
539
|
if (cur === home || cur === path4.dirname(cur)) break;
|
|
517
540
|
cur = path4.dirname(cur);
|
|
@@ -526,7 +549,7 @@ function collectSubDirs(root) {
|
|
|
526
549
|
if (!dir) continue;
|
|
527
550
|
let entries = [];
|
|
528
551
|
try {
|
|
529
|
-
entries =
|
|
552
|
+
entries = fs4.readdirSync(dir, { withFileTypes: true });
|
|
530
553
|
} catch {
|
|
531
554
|
continue;
|
|
532
555
|
}
|
|
@@ -556,7 +579,7 @@ async function run3(args) {
|
|
|
556
579
|
return 1;
|
|
557
580
|
}
|
|
558
581
|
const script = path4.resolve(scriptArg);
|
|
559
|
-
if (!
|
|
582
|
+
if (!fs4.existsSync(script)) {
|
|
560
583
|
console.error(`dev: script not found: ${script}`);
|
|
561
584
|
return 1;
|
|
562
585
|
}
|
|
@@ -664,16 +687,16 @@ async function runManualWatch(script, cwd, interpreter, appArgs, watchRaw) {
|
|
|
664
687
|
}, 200);
|
|
665
688
|
};
|
|
666
689
|
for (const target of targets) {
|
|
667
|
-
if (!
|
|
690
|
+
if (!fs4.existsSync(target)) {
|
|
668
691
|
console.error(`dev: watch path does not exist: ${target} \u2014 skipping`);
|
|
669
692
|
continue;
|
|
670
693
|
}
|
|
671
|
-
const stat =
|
|
694
|
+
const stat = fs4.statSync(target);
|
|
672
695
|
if (!stat.isDirectory()) {
|
|
673
696
|
const dir = path4.dirname(target);
|
|
674
697
|
const base = path4.basename(target);
|
|
675
698
|
try {
|
|
676
|
-
const w =
|
|
699
|
+
const w = fs4.watch(dir, (_ev, filename) => {
|
|
677
700
|
if (filename && filename !== base) return;
|
|
678
701
|
scheduleRestart(target);
|
|
679
702
|
});
|
|
@@ -685,7 +708,7 @@ async function runManualWatch(script, cwd, interpreter, appArgs, watchRaw) {
|
|
|
685
708
|
}
|
|
686
709
|
if (process.platform === "win32" || process.platform === "darwin") {
|
|
687
710
|
try {
|
|
688
|
-
const w =
|
|
711
|
+
const w = fs4.watch(target, { recursive: true }, () => scheduleRestart(target));
|
|
689
712
|
watchers.push(w);
|
|
690
713
|
} catch (err) {
|
|
691
714
|
console.error(`dev: cannot watch ${target}: ${err.message}`);
|
|
@@ -694,7 +717,7 @@ async function runManualWatch(script, cwd, interpreter, appArgs, watchRaw) {
|
|
|
694
717
|
const dirs = collectSubDirs(target);
|
|
695
718
|
for (const dir of dirs) {
|
|
696
719
|
try {
|
|
697
|
-
const w =
|
|
720
|
+
const w = fs4.watch(dir, () => scheduleRestart(dir));
|
|
698
721
|
watchers.push(w);
|
|
699
722
|
} catch {
|
|
700
723
|
}
|
|
@@ -789,8 +812,32 @@ Examples:
|
|
|
789
812
|
}
|
|
790
813
|
|
|
791
814
|
// src/commands/doctor.ts
|
|
792
|
-
|
|
793
|
-
|
|
815
|
+
import fs5 from "node:fs";
|
|
816
|
+
import os2 from "node:os";
|
|
817
|
+
import path5 from "node:path";
|
|
818
|
+
async function run4(args) {
|
|
819
|
+
const fix = args.includes("--fix") || args.includes("-f");
|
|
820
|
+
if (fix) {
|
|
821
|
+
const healed = healLocally();
|
|
822
|
+
if (healed.length > 0) {
|
|
823
|
+
for (const m of healed) console.log(`healed: ${m}`);
|
|
824
|
+
} else {
|
|
825
|
+
console.log("heal: nothing to fix locally");
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
let client;
|
|
829
|
+
try {
|
|
830
|
+
client = await ensureDaemonClient();
|
|
831
|
+
} catch (err) {
|
|
832
|
+
if (fix) {
|
|
833
|
+
console.error(`doctor: daemon still not reachable after heal: ${err.message}`);
|
|
834
|
+
console.error("try: rm -rf ~/.relife2/state/* && relife2 kill; relife2 list");
|
|
835
|
+
} else {
|
|
836
|
+
console.error(err.message);
|
|
837
|
+
console.error("hint: relife2 doctor --fix (cleans stale lock + *.tmp.cjs)");
|
|
838
|
+
}
|
|
839
|
+
return 1;
|
|
840
|
+
}
|
|
794
841
|
try {
|
|
795
842
|
const report = await client.call("doctor", {});
|
|
796
843
|
let hadErrors = false;
|
|
@@ -807,21 +854,143 @@ async function run4(_args) {
|
|
|
807
854
|
console.log(
|
|
808
855
|
`checks: ${report.summary.total} total, ${report.summary.ok} ok, ${report.summary.warnings} warnings, ${report.summary.errors} errors`
|
|
809
856
|
);
|
|
857
|
+
if (hadErrors && !fix) {
|
|
858
|
+
console.log("hint: relife2 doctor --fix (cleans stale lock + *.tmp.cjs)");
|
|
859
|
+
}
|
|
810
860
|
return hadErrors ? 1 : 0;
|
|
811
861
|
} finally {
|
|
812
862
|
client.close();
|
|
813
863
|
}
|
|
814
864
|
}
|
|
865
|
+
function healLocally() {
|
|
866
|
+
const out = [];
|
|
867
|
+
const runtime = runtimeDir();
|
|
868
|
+
const data = dataDir();
|
|
869
|
+
for (const dir of [runtime, data, path5.join(data, "logs"), path5.join(data, "state")]) {
|
|
870
|
+
try {
|
|
871
|
+
fs5.mkdirSync(dir, { recursive: true });
|
|
872
|
+
} catch {
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
const lockFile = path5.join(runtime, "daemon.lock");
|
|
876
|
+
try {
|
|
877
|
+
const raw = fs5.readFileSync(lockFile, "utf8");
|
|
878
|
+
const parsed = JSON.parse(raw);
|
|
879
|
+
const pid = typeof parsed.pid === "number" ? parsed.pid : null;
|
|
880
|
+
let alive = false;
|
|
881
|
+
if (pid !== null) {
|
|
882
|
+
try {
|
|
883
|
+
process.kill(pid, 0);
|
|
884
|
+
alive = true;
|
|
885
|
+
} catch (e) {
|
|
886
|
+
const code = e.code;
|
|
887
|
+
if (code === "EPERM") alive = true;
|
|
888
|
+
else if (code === "ESRCH") alive = false;
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
if (pid === null || !alive) {
|
|
892
|
+
fs5.unlinkSync(lockFile);
|
|
893
|
+
out.push(`removed stale lock ${lockFile} (pid=${pid})`);
|
|
894
|
+
}
|
|
895
|
+
} catch {
|
|
896
|
+
}
|
|
897
|
+
const home = os2.homedir();
|
|
898
|
+
if (home && fs5.existsSync(home)) {
|
|
899
|
+
try {
|
|
900
|
+
const tmpLeftovers = collectTmpLeftovers(home, 6, 50);
|
|
901
|
+
for (const f of tmpLeftovers) {
|
|
902
|
+
try {
|
|
903
|
+
fs5.unlinkSync(f);
|
|
904
|
+
out.push(`removed leftover ${path5.relative(home, f)}`);
|
|
905
|
+
} catch {
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
} catch {
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
try {
|
|
912
|
+
const tmpDir = os2.tmpdir();
|
|
913
|
+
const entries = fs5.readdirSync(tmpDir);
|
|
914
|
+
const now = Date.now();
|
|
915
|
+
for (const name of entries) {
|
|
916
|
+
if (!name.startsWith("relife2-") || !name.endsWith(".tmp.cjs")) continue;
|
|
917
|
+
const full = path5.join(tmpDir, name);
|
|
918
|
+
try {
|
|
919
|
+
const st = fs5.statSync(full);
|
|
920
|
+
if (now - st.mtimeMs > 60 * 60 * 1e3) {
|
|
921
|
+
fs5.unlinkSync(full);
|
|
922
|
+
out.push(`removed old tmp ${name}`);
|
|
923
|
+
}
|
|
924
|
+
} catch {
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
} catch {
|
|
928
|
+
}
|
|
929
|
+
return out;
|
|
930
|
+
}
|
|
931
|
+
function collectTmpLeftovers(root, maxDepth, limit) {
|
|
932
|
+
const out = [];
|
|
933
|
+
const stack = [{ dir: root, depth: 0 }];
|
|
934
|
+
const ignored = /* @__PURE__ */ new Set([
|
|
935
|
+
"node_modules",
|
|
936
|
+
".git",
|
|
937
|
+
".hg",
|
|
938
|
+
".svn",
|
|
939
|
+
"vendor",
|
|
940
|
+
".next",
|
|
941
|
+
".output",
|
|
942
|
+
"dist",
|
|
943
|
+
"build",
|
|
944
|
+
"target",
|
|
945
|
+
"__pycache__",
|
|
946
|
+
".cache",
|
|
947
|
+
".pnpm",
|
|
948
|
+
".yarn",
|
|
949
|
+
".turbo",
|
|
950
|
+
".parcel-cache",
|
|
951
|
+
"Library",
|
|
952
|
+
"AppData",
|
|
953
|
+
".local",
|
|
954
|
+
"proc",
|
|
955
|
+
"sys",
|
|
956
|
+
"dev"
|
|
957
|
+
]);
|
|
958
|
+
while (stack.length > 0 && out.length < limit) {
|
|
959
|
+
const cur = stack.pop();
|
|
960
|
+
if (!cur) break;
|
|
961
|
+
if (cur.depth > maxDepth) continue;
|
|
962
|
+
let entries;
|
|
963
|
+
try {
|
|
964
|
+
entries = fs5.readdirSync(cur.dir, { withFileTypes: true });
|
|
965
|
+
} catch {
|
|
966
|
+
continue;
|
|
967
|
+
}
|
|
968
|
+
for (const ent of entries) {
|
|
969
|
+
if (out.length >= limit) break;
|
|
970
|
+
const full = path5.join(cur.dir, ent.name);
|
|
971
|
+
if (ent.isFile() && ent.name.includes(".tmp.") && ent.name.endsWith(".cjs")) {
|
|
972
|
+
if (ent.name.includes(".config.")) out.push(full);
|
|
973
|
+
continue;
|
|
974
|
+
}
|
|
975
|
+
if (!ent.isDirectory()) continue;
|
|
976
|
+
if (ignored.has(ent.name)) continue;
|
|
977
|
+
if (ent.name.startsWith(".") && cur.depth < 2) continue;
|
|
978
|
+
if (ent.isSymbolicLink()) continue;
|
|
979
|
+
if (cur.depth + 1 <= maxDepth) stack.push({ dir: full, depth: cur.depth + 1 });
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
return out;
|
|
983
|
+
}
|
|
815
984
|
|
|
816
985
|
// src/commands/find.ts
|
|
817
|
-
import * as
|
|
818
|
-
import
|
|
819
|
-
import
|
|
986
|
+
import * as fs9 from "node:fs";
|
|
987
|
+
import os5 from "node:os";
|
|
988
|
+
import path9 from "node:path";
|
|
820
989
|
|
|
821
990
|
// src/config/loader.ts
|
|
822
|
-
import * as
|
|
823
|
-
import
|
|
824
|
-
import
|
|
991
|
+
import * as fs8 from "node:fs";
|
|
992
|
+
import os4 from "node:os";
|
|
993
|
+
import path8 from "node:path";
|
|
825
994
|
|
|
826
995
|
// src/util/cron.ts
|
|
827
996
|
var FIELD_RANGES = [
|
|
@@ -940,19 +1109,19 @@ function parseMemory(v) {
|
|
|
940
1109
|
}
|
|
941
1110
|
|
|
942
1111
|
// src/util/portable.ts
|
|
943
|
-
import * as
|
|
944
|
-
import
|
|
945
|
-
import
|
|
1112
|
+
import * as fs6 from "node:fs";
|
|
1113
|
+
import os3 from "node:os";
|
|
1114
|
+
import path6 from "node:path";
|
|
946
1115
|
function isDir(p) {
|
|
947
1116
|
try {
|
|
948
|
-
return
|
|
1117
|
+
return fs6.statSync(p).isDirectory();
|
|
949
1118
|
} catch {
|
|
950
1119
|
return false;
|
|
951
1120
|
}
|
|
952
1121
|
}
|
|
953
1122
|
function isFile(p) {
|
|
954
1123
|
try {
|
|
955
|
-
return
|
|
1124
|
+
return fs6.statSync(p).isFile();
|
|
956
1125
|
} catch {
|
|
957
1126
|
return false;
|
|
958
1127
|
}
|
|
@@ -974,18 +1143,18 @@ function searchByBasename(root, basename, maxDepth = 4) {
|
|
|
974
1143
|
if (cur.depth > maxDepth) continue;
|
|
975
1144
|
let entries;
|
|
976
1145
|
try {
|
|
977
|
-
entries =
|
|
1146
|
+
entries = fs6.readdirSync(cur.dir, { withFileTypes: true });
|
|
978
1147
|
} catch {
|
|
979
1148
|
continue;
|
|
980
1149
|
}
|
|
981
1150
|
for (const e of entries) {
|
|
982
|
-
const full =
|
|
1151
|
+
const full = path6.join(cur.dir, e.name);
|
|
983
1152
|
if (e.isFile() && e.name === basename) return full;
|
|
984
1153
|
if (e.isDirectory() && !e.isSymbolicLink()) {
|
|
985
1154
|
if (e.name === "node_modules" || e.name === ".git") continue;
|
|
986
1155
|
const real = (() => {
|
|
987
1156
|
try {
|
|
988
|
-
return
|
|
1157
|
+
return fs6.realpathSync(full);
|
|
989
1158
|
} catch {
|
|
990
1159
|
return full;
|
|
991
1160
|
}
|
|
@@ -1003,20 +1172,20 @@ function fixCwd(staleCwd, sourceDir) {
|
|
|
1003
1172
|
const candidates = [];
|
|
1004
1173
|
const tail = stripHomePrefix(staleCwd);
|
|
1005
1174
|
if (tail !== null) {
|
|
1006
|
-
candidates.push(
|
|
1007
|
-
candidates.push(
|
|
1008
|
-
candidates.push(
|
|
1009
|
-
if (isDir(sourceDir) && !staleCwd.startsWith(
|
|
1175
|
+
candidates.push(path6.join(os3.homedir(), tail));
|
|
1176
|
+
candidates.push(path6.join(path6.dirname(os3.homedir()), tail));
|
|
1177
|
+
candidates.push(path6.join(path6.dirname(sourceDir), tail));
|
|
1178
|
+
if (isDir(sourceDir) && !staleCwd.startsWith(os3.homedir())) candidates.push(sourceDir);
|
|
1010
1179
|
}
|
|
1011
|
-
const proj =
|
|
1180
|
+
const proj = path6.basename(sourceDir);
|
|
1012
1181
|
if (proj) {
|
|
1013
|
-
const marker = `${
|
|
1182
|
+
const marker = `${path6.sep}${proj}${path6.sep}`;
|
|
1014
1183
|
const altMarker = `/${proj}/`;
|
|
1015
1184
|
const idx = staleCwd.includes(marker) ? staleCwd.lastIndexOf(marker) : staleCwd.lastIndexOf(altMarker);
|
|
1016
1185
|
if (idx >= 0) {
|
|
1017
1186
|
const suffix = staleCwd.slice(idx + proj.length + 2);
|
|
1018
|
-
candidates.push(
|
|
1019
|
-
candidates.push(
|
|
1187
|
+
candidates.push(path6.join(sourceDir, suffix));
|
|
1188
|
+
candidates.push(path6.join(path6.dirname(sourceDir), proj, suffix));
|
|
1020
1189
|
}
|
|
1021
1190
|
}
|
|
1022
1191
|
for (const c of candidates) {
|
|
@@ -1031,33 +1200,33 @@ function fixCwd(staleCwd, sourceDir) {
|
|
|
1031
1200
|
}
|
|
1032
1201
|
function fixScript(staleScript, cwd, sourceDir) {
|
|
1033
1202
|
if (isFile(staleScript)) return { path: staleScript, fixed: false };
|
|
1034
|
-
const base =
|
|
1203
|
+
const base = path6.basename(staleScript);
|
|
1035
1204
|
const candidates = [];
|
|
1036
|
-
candidates.push(
|
|
1037
|
-
candidates.push(
|
|
1205
|
+
candidates.push(path6.join(cwd, base));
|
|
1206
|
+
candidates.push(path6.join(sourceDir, base));
|
|
1038
1207
|
const tail = stripHomePrefix(staleScript);
|
|
1039
1208
|
if (tail !== null) {
|
|
1040
|
-
candidates.push(
|
|
1041
|
-
candidates.push(
|
|
1042
|
-
candidates.push(
|
|
1209
|
+
candidates.push(path6.join(os3.homedir(), tail));
|
|
1210
|
+
candidates.push(path6.join(path6.dirname(os3.homedir()), tail));
|
|
1211
|
+
candidates.push(path6.join(sourceDir, tail));
|
|
1043
1212
|
const tailParts = tail.split("/");
|
|
1044
|
-
if (tailParts.length > 1) candidates.push(
|
|
1213
|
+
if (tailParts.length > 1) candidates.push(path6.join(sourceDir, tailParts.slice(1).join("/")));
|
|
1045
1214
|
}
|
|
1046
|
-
const proj =
|
|
1215
|
+
const proj = path6.basename(sourceDir);
|
|
1047
1216
|
if (proj) {
|
|
1048
1217
|
const marker = `/${proj}/`;
|
|
1049
1218
|
const idx = staleScript.lastIndexOf(marker);
|
|
1050
1219
|
if (idx >= 0) {
|
|
1051
1220
|
const suffix = staleScript.slice(idx + proj.length + 2);
|
|
1052
|
-
candidates.push(
|
|
1053
|
-
candidates.push(
|
|
1221
|
+
candidates.push(path6.join(sourceDir, suffix));
|
|
1222
|
+
candidates.push(path6.join(cwd, suffix));
|
|
1054
1223
|
}
|
|
1055
1224
|
}
|
|
1056
1225
|
const parts = staleScript.split(/[/\\]/).filter(Boolean);
|
|
1057
1226
|
if (parts.length >= 2) {
|
|
1058
|
-
const lastTwo = parts.slice(-2).join(
|
|
1059
|
-
candidates.push(
|
|
1060
|
-
candidates.push(
|
|
1227
|
+
const lastTwo = parts.slice(-2).join(path6.sep);
|
|
1228
|
+
candidates.push(path6.join(sourceDir, lastTwo));
|
|
1229
|
+
candidates.push(path6.join(cwd, lastTwo));
|
|
1061
1230
|
}
|
|
1062
1231
|
for (const c of candidates) {
|
|
1063
1232
|
if (c && isFile(c))
|
|
@@ -1137,9 +1306,17 @@ var LATER_KEYS = {
|
|
|
1137
1306
|
uid: "M6",
|
|
1138
1307
|
gid: "M6"
|
|
1139
1308
|
};
|
|
1140
|
-
var
|
|
1309
|
+
var RELIFE2_CONFIG_RE = /^(?:ecosystem|pm2|relife2)[\w.-]*\.config\.(?:c?js|mjs|json|ts)$/i;
|
|
1310
|
+
var ANY_CONFIG_RE = /^[\w.-]*\.config\.(?:c?js|mjs|json|ts)$/i;
|
|
1141
1311
|
function looksLikeConfigFile(file) {
|
|
1142
|
-
|
|
1312
|
+
const base = path8.basename(file);
|
|
1313
|
+
if (base.includes(".tmp.")) return false;
|
|
1314
|
+
return ANY_CONFIG_RE.test(base);
|
|
1315
|
+
}
|
|
1316
|
+
function isRelife2ConfigFile(file) {
|
|
1317
|
+
const base = path8.basename(file);
|
|
1318
|
+
if (base.includes(".tmp.")) return false;
|
|
1319
|
+
return RELIFE2_CONFIG_RE.test(base);
|
|
1143
1320
|
}
|
|
1144
1321
|
function findDefaultConfig(cwd) {
|
|
1145
1322
|
const candidates = [
|
|
@@ -1156,16 +1333,16 @@ function findDefaultConfig(cwd) {
|
|
|
1156
1333
|
"pm2.config.cjs"
|
|
1157
1334
|
];
|
|
1158
1335
|
for (const name of candidates) {
|
|
1159
|
-
const full =
|
|
1160
|
-
if (
|
|
1336
|
+
const full = path8.join(cwd, name);
|
|
1337
|
+
if (fs8.existsSync(full)) return full;
|
|
1161
1338
|
}
|
|
1162
1339
|
return null;
|
|
1163
1340
|
}
|
|
1164
1341
|
async function loadConfigFile(filePath, opts) {
|
|
1165
|
-
const ext =
|
|
1342
|
+
const ext = path8.extname(filePath).toLowerCase();
|
|
1166
1343
|
let exported;
|
|
1167
1344
|
if (ext === ".json") {
|
|
1168
|
-
exported = JSON.parse(
|
|
1345
|
+
exported = JSON.parse(fs8.readFileSync(filePath, "utf8"));
|
|
1169
1346
|
} else if (ext === ".ts") {
|
|
1170
1347
|
exported = await loadTsFile(filePath);
|
|
1171
1348
|
} else {
|
|
@@ -1175,7 +1352,7 @@ async function loadConfigFile(filePath, opts) {
|
|
|
1175
1352
|
}
|
|
1176
1353
|
if (exported instanceof Promise) exported = await exported;
|
|
1177
1354
|
}
|
|
1178
|
-
return normalizeExported(exported,
|
|
1355
|
+
return normalizeExported(exported, path8.dirname(filePath), opts);
|
|
1179
1356
|
}
|
|
1180
1357
|
async function loadTsFile(file) {
|
|
1181
1358
|
try {
|
|
@@ -1184,16 +1361,19 @@ async function loadTsFile(file) {
|
|
|
1184
1361
|
const mod = await import(url);
|
|
1185
1362
|
return mod.default ?? mod;
|
|
1186
1363
|
} catch {
|
|
1187
|
-
const raw =
|
|
1364
|
+
const raw = fs8.readFileSync(file, "utf8");
|
|
1188
1365
|
const stripped = raw.replace(/^\s*import\s+type\s+.*$/gm, "").replace(/:\s*[\w<>[\]|&\s,?]+(?=[=;,\n)}])/g, "").replace(/\s+as\s+const/g, "");
|
|
1189
|
-
const tmp =
|
|
1366
|
+
const tmp = path8.join(
|
|
1367
|
+
os4.tmpdir(),
|
|
1368
|
+
`relife2-${path8.basename(file).replace(/[^a-zA-Z0-9._-]/g, "_")}-${Date.now()}-${Math.random().toString(36).slice(2, 6)}.tmp.cjs`
|
|
1369
|
+
);
|
|
1190
1370
|
try {
|
|
1191
|
-
|
|
1371
|
+
fs8.writeFileSync(tmp, stripped, "utf8");
|
|
1192
1372
|
const { loadJsFile: lf } = await Promise.resolve().then(() => (init_jsloader(), jsloader_exports));
|
|
1193
1373
|
return await lf(tmp);
|
|
1194
1374
|
} finally {
|
|
1195
1375
|
try {
|
|
1196
|
-
|
|
1376
|
+
fs8.unlinkSync(tmp);
|
|
1197
1377
|
} catch {
|
|
1198
1378
|
}
|
|
1199
1379
|
}
|
|
@@ -1249,20 +1429,20 @@ function normalizeApp(raw, sourceDir, warnings, index, envName) {
|
|
|
1249
1429
|
const instances = parseInstances(input.instances, index);
|
|
1250
1430
|
const execMode = parseExecMode(input.exec_mode, index);
|
|
1251
1431
|
const rawCwd = typeof input.cwd === "string" ? input.cwd : void 0;
|
|
1252
|
-
const cwdRaw =
|
|
1432
|
+
const cwdRaw = path8.resolve(sourceDir, rawCwd ?? ".");
|
|
1253
1433
|
const cwdFix = fixCwd(cwdRaw, sourceDir);
|
|
1254
1434
|
const cwd = cwdFix.path;
|
|
1255
1435
|
if (cwdFix.fixed && cwdFix.reason) warnings.push(cwdFix.reason);
|
|
1256
1436
|
const hasSep = scriptRaw.includes("/") || scriptRaw.includes("\\");
|
|
1257
1437
|
const isJsExt = /\.(?:c?js|mjs|ts|mts|cts)$/i.test(scriptRaw);
|
|
1258
|
-
const scriptRawResolved = hasSep || isJsExt ?
|
|
1438
|
+
const scriptRawResolved = hasSep || isJsExt ? path8.resolve(cwd, scriptRaw) : scriptRaw;
|
|
1259
1439
|
let script = scriptRawResolved;
|
|
1260
|
-
if (
|
|
1440
|
+
if (path8.isAbsolute(scriptRawResolved)) {
|
|
1261
1441
|
const f = fixScript(scriptRawResolved, cwd, sourceDir);
|
|
1262
1442
|
if (f.fixed) {
|
|
1263
1443
|
warnings.push(f.reason ?? `script '${scriptRawResolved}' \u2192 '${f.path}'`);
|
|
1264
1444
|
script = f.path;
|
|
1265
|
-
} else if (!
|
|
1445
|
+
} else if (!fs8.existsSync(scriptRawResolved) && f.reason) {
|
|
1266
1446
|
warnings.push(f.reason);
|
|
1267
1447
|
}
|
|
1268
1448
|
}
|
|
@@ -1279,11 +1459,11 @@ function normalizeApp(raw, sourceDir, warnings, index, envName) {
|
|
|
1279
1459
|
}
|
|
1280
1460
|
const envFile = typeof input.env_file === "string" && input.env_file !== "" ? input.env_file : void 0;
|
|
1281
1461
|
if (envFile !== void 0) {
|
|
1282
|
-
const envPath =
|
|
1462
|
+
const envPath = path8.resolve(cwd, envFile);
|
|
1283
1463
|
let parsed = null;
|
|
1284
|
-
if (
|
|
1464
|
+
if (fs8.existsSync(envPath) && fs8.statSync(envPath).isFile()) {
|
|
1285
1465
|
try {
|
|
1286
|
-
parsed = parseDotenv(
|
|
1466
|
+
parsed = parseDotenv(fs8.readFileSync(envPath, "utf8"));
|
|
1287
1467
|
} catch (err) {
|
|
1288
1468
|
warnings.push(
|
|
1289
1469
|
`apps[${index}] env_file '${envFile}' could not be read: ${err.message}`
|
|
@@ -1406,7 +1586,7 @@ function parseWatchField(v, field, index) {
|
|
|
1406
1586
|
}
|
|
1407
1587
|
function parseInstances(v, index) {
|
|
1408
1588
|
if (v === void 0 || v === null) return 1;
|
|
1409
|
-
if (v === "max" || v === "MAX") return
|
|
1589
|
+
if (v === "max" || v === "MAX") return os4.cpus().length || 1;
|
|
1410
1590
|
const n = Number(v);
|
|
1411
1591
|
if (!Number.isFinite(n) || n < 1) {
|
|
1412
1592
|
throw new ConfigError(`apps[${index}] 'instances' must be a positive number or 'max'`);
|
|
@@ -1421,7 +1601,7 @@ function parseExecMode(v, index) {
|
|
|
1421
1601
|
throw new ConfigError(`apps[${index}] 'exec_mode' must be 'fork' or 'cluster'`);
|
|
1422
1602
|
}
|
|
1423
1603
|
function defaultName(script) {
|
|
1424
|
-
const base =
|
|
1604
|
+
const base = path8.basename(script);
|
|
1425
1605
|
return base.replace(/\.(?:c?js|mjs|ts|mts|cts)$/i, "") || base;
|
|
1426
1606
|
}
|
|
1427
1607
|
function normalizeSingleApp(input, sourceDir, envName) {
|
|
@@ -1469,7 +1649,7 @@ function findConfigs(root, maxDepth) {
|
|
|
1469
1649
|
if (depth > maxDepth) continue;
|
|
1470
1650
|
let real;
|
|
1471
1651
|
try {
|
|
1472
|
-
real =
|
|
1652
|
+
real = fs9.realpathSync(dir);
|
|
1473
1653
|
} catch {
|
|
1474
1654
|
continue;
|
|
1475
1655
|
}
|
|
@@ -1477,13 +1657,13 @@ function findConfigs(root, maxDepth) {
|
|
|
1477
1657
|
visited.add(real);
|
|
1478
1658
|
let entries;
|
|
1479
1659
|
try {
|
|
1480
|
-
entries =
|
|
1660
|
+
entries = fs9.readdirSync(dir, { withFileTypes: true });
|
|
1481
1661
|
} catch {
|
|
1482
1662
|
continue;
|
|
1483
1663
|
}
|
|
1484
1664
|
for (const ent of entries) {
|
|
1485
|
-
const full =
|
|
1486
|
-
if (ent.isFile() &&
|
|
1665
|
+
const full = path9.join(dir, ent.name);
|
|
1666
|
+
if (ent.isFile() && isRelife2ConfigFile(ent.name)) {
|
|
1487
1667
|
out.push(full);
|
|
1488
1668
|
continue;
|
|
1489
1669
|
}
|
|
@@ -1512,8 +1692,8 @@ async function enrich(found) {
|
|
|
1512
1692
|
return entries;
|
|
1513
1693
|
}
|
|
1514
1694
|
function relToRoot(p, root) {
|
|
1515
|
-
const rel =
|
|
1516
|
-
return rel === "" ?
|
|
1695
|
+
const rel = path9.relative(root, p);
|
|
1696
|
+
return rel === "" ? path9.basename(p) : rel;
|
|
1517
1697
|
}
|
|
1518
1698
|
async function run5(args) {
|
|
1519
1699
|
const { positionals, opts } = parseCliArgs(args, VALUE_OPTS);
|
|
@@ -1523,12 +1703,12 @@ async function run5(args) {
|
|
|
1523
1703
|
let root = opts.get("root");
|
|
1524
1704
|
if (root === void 0) {
|
|
1525
1705
|
if (allFlag) {
|
|
1526
|
-
root = process.platform === "win32" ?
|
|
1706
|
+
root = process.platform === "win32" ? path9.parse(process.cwd()).root : "/";
|
|
1527
1707
|
} else {
|
|
1528
|
-
root =
|
|
1708
|
+
root = os5.homedir() || process.cwd();
|
|
1529
1709
|
}
|
|
1530
1710
|
}
|
|
1531
|
-
root =
|
|
1711
|
+
root = path9.resolve(root);
|
|
1532
1712
|
let depth = 6;
|
|
1533
1713
|
const depthRaw = opts.get("depth");
|
|
1534
1714
|
if (depthRaw !== void 0) {
|
|
@@ -1540,7 +1720,7 @@ async function run5(args) {
|
|
|
1540
1720
|
depth = Math.round(n);
|
|
1541
1721
|
}
|
|
1542
1722
|
if (allFlag) depth = Math.max(depth, 8);
|
|
1543
|
-
if (!
|
|
1723
|
+
if (!fs9.existsSync(root) || !fs9.statSync(root).isDirectory()) {
|
|
1544
1724
|
console.error(`find: root '${root}' does not exist or is not a directory`);
|
|
1545
1725
|
return 1;
|
|
1546
1726
|
}
|
|
@@ -1631,10 +1811,10 @@ Starting ${toStart.length} config(s) not yet running\u2026
|
|
|
1631
1811
|
const res = await client.call("start", {
|
|
1632
1812
|
target: { type: "config", path: r.path }
|
|
1633
1813
|
});
|
|
1634
|
-
for (const w of res.warnings ?? []) console.error(`warning ${
|
|
1814
|
+
for (const w of res.warnings ?? []) console.error(`warning ${path9.basename(r.path)}: ${w}`);
|
|
1635
1815
|
for (const a of res.apps) {
|
|
1636
1816
|
if (a.status === "errored") {
|
|
1637
|
-
console.error(`x ${a.name} (${
|
|
1817
|
+
console.error(`x ${a.name} (${path9.basename(r.path)}): ${a.message ?? "failed"}`);
|
|
1638
1818
|
failed++;
|
|
1639
1819
|
} else {
|
|
1640
1820
|
console.log(
|
|
@@ -1644,7 +1824,7 @@ Starting ${toStart.length} config(s) not yet running\u2026
|
|
|
1644
1824
|
}
|
|
1645
1825
|
}
|
|
1646
1826
|
} catch (err) {
|
|
1647
|
-
console.error(`x ${
|
|
1827
|
+
console.error(`x ${path9.basename(r.path)}: ${err.message}`);
|
|
1648
1828
|
failed++;
|
|
1649
1829
|
}
|
|
1650
1830
|
}
|
|
@@ -1678,9 +1858,9 @@ async function run6(args) {
|
|
|
1678
1858
|
}
|
|
1679
1859
|
|
|
1680
1860
|
// src/commands/fromCaddy.ts
|
|
1681
|
-
import * as
|
|
1682
|
-
import
|
|
1683
|
-
import
|
|
1861
|
+
import * as fs10 from "node:fs";
|
|
1862
|
+
import os6 from "node:os";
|
|
1863
|
+
import path10 from "node:path";
|
|
1684
1864
|
var LOOPBACK_RE = /(?:127\.0\.0\.1|localhost|::1):(\d{2,5})/g;
|
|
1685
1865
|
var ENTRY_PATTERNS = [
|
|
1686
1866
|
".output/server/index.mjs",
|
|
@@ -1853,37 +2033,37 @@ function parseCaddyfile(content) {
|
|
|
1853
2033
|
}
|
|
1854
2034
|
function findEntry(dir) {
|
|
1855
2035
|
for (const pat of ENTRY_PATTERNS) {
|
|
1856
|
-
const p =
|
|
1857
|
-
if (
|
|
2036
|
+
const p = path10.join(dir, pat);
|
|
2037
|
+
if (fs10.existsSync(p)) return { script: pat, exists: true };
|
|
1858
2038
|
}
|
|
1859
2039
|
return null;
|
|
1860
2040
|
}
|
|
1861
2041
|
function guessDir(site, _port, hint, caddyfileDir) {
|
|
1862
2042
|
const warnings = [];
|
|
1863
2043
|
if (hint?.dir) {
|
|
1864
|
-
const d =
|
|
2044
|
+
const d = path10.isAbsolute(hint.dir) ? hint.dir : path10.resolve(caddyfileDir, hint.dir);
|
|
1865
2045
|
return { dir: d, warnings };
|
|
1866
2046
|
}
|
|
1867
2047
|
const searchRoots = [];
|
|
1868
|
-
const homedir =
|
|
2048
|
+
const homedir = os6.homedir();
|
|
1869
2049
|
if (homedir) searchRoots.push(homedir);
|
|
1870
2050
|
if (caddyfileDir) searchRoots.push(caddyfileDir);
|
|
1871
|
-
if (caddyfileDir) searchRoots.push(
|
|
2051
|
+
if (caddyfileDir) searchRoots.push(path10.resolve(caddyfileDir, ".."));
|
|
1872
2052
|
const uniqRoots = [...new Set(searchRoots)].filter(Boolean);
|
|
1873
2053
|
const siteName = sanitizeName(site);
|
|
1874
2054
|
const candidates = [];
|
|
1875
|
-
if (homedir) candidates.push(
|
|
1876
|
-
if (homedir) candidates.push(
|
|
1877
|
-
for (const r of uniqRoots) candidates.push(
|
|
2055
|
+
if (homedir) candidates.push(path10.join(homedir, siteName));
|
|
2056
|
+
if (homedir) candidates.push(path10.join(homedir, siteName.split("-")[0]));
|
|
2057
|
+
for (const r of uniqRoots) candidates.push(path10.join(r, siteName));
|
|
1878
2058
|
const rawDir = site.split(".")[0].replace(/[^a-zA-Z0-9_-]/g, "");
|
|
1879
|
-
if (rawDir && homedir) candidates.push(
|
|
2059
|
+
if (rawDir && homedir) candidates.push(path10.join(homedir, rawDir));
|
|
1880
2060
|
for (const cand of candidates) {
|
|
1881
|
-
if (
|
|
2061
|
+
if (fs10.existsSync(cand) && fs10.statSync(cand).isDirectory()) {
|
|
1882
2062
|
const e = findEntry(cand);
|
|
1883
2063
|
if (e) return { dir: cand, warnings };
|
|
1884
2064
|
}
|
|
1885
2065
|
}
|
|
1886
|
-
const fallback = homedir ?
|
|
2066
|
+
const fallback = homedir ? path10.join(homedir, siteName) : path10.join(caddyfileDir || process.cwd(), siteName);
|
|
1887
2067
|
warnings.push(
|
|
1888
2068
|
`no existing dir found for ${site} \u2192 using fallback ${fallback} (create or set via '# relife2: dir=...')`
|
|
1889
2069
|
);
|
|
@@ -1915,15 +2095,15 @@ async function run7(args) {
|
|
|
1915
2095
|
caddyfilePath ? null : "./Caddyfile",
|
|
1916
2096
|
caddyfilePath ? null : "Caddyfile",
|
|
1917
2097
|
caddyfilePath ? null : "/etc/caddy/Caddyfile",
|
|
1918
|
-
caddyfilePath ? null :
|
|
2098
|
+
caddyfilePath ? null : path10.join(os6.homedir(), "Caddyfile")
|
|
1919
2099
|
].filter(Boolean);
|
|
1920
2100
|
let resolvedCaddy = null;
|
|
1921
2101
|
let content = null;
|
|
1922
2102
|
for (const cand of candidates) {
|
|
1923
|
-
const p =
|
|
1924
|
-
if (
|
|
2103
|
+
const p = path10.resolve(cand);
|
|
2104
|
+
if (fs10.existsSync(p)) {
|
|
1925
2105
|
resolvedCaddy = p;
|
|
1926
|
-
content =
|
|
2106
|
+
content = fs10.readFileSync(p, "utf8");
|
|
1927
2107
|
break;
|
|
1928
2108
|
}
|
|
1929
2109
|
}
|
|
@@ -1934,7 +2114,7 @@ async function run7(args) {
|
|
|
1934
2114
|
);
|
|
1935
2115
|
return 1;
|
|
1936
2116
|
}
|
|
1937
|
-
const caddyfileDir =
|
|
2117
|
+
const caddyfileDir = path10.dirname(resolvedCaddy);
|
|
1938
2118
|
const { sites } = parseCaddyfile(content);
|
|
1939
2119
|
if (sites.length === 0) {
|
|
1940
2120
|
console.error(`from-caddy: no sites found in ${resolvedCaddy}`);
|
|
@@ -1982,7 +2162,7 @@ async function run7(args) {
|
|
|
1982
2162
|
let relScript = "";
|
|
1983
2163
|
if (entry) {
|
|
1984
2164
|
relScript = entry;
|
|
1985
|
-
exists =
|
|
2165
|
+
exists = fs10.existsSync(path10.isAbsolute(entry) ? entry : path10.join(dir, entry));
|
|
1986
2166
|
if (!exists) warnings.push(`hint script '${entry}' for ${name} not found under ${dir}`);
|
|
1987
2167
|
} else {
|
|
1988
2168
|
const found = findEntry(dir);
|
|
@@ -2000,7 +2180,7 @@ async function run7(args) {
|
|
|
2000
2180
|
let interpreter = site.hint?.interpreter;
|
|
2001
2181
|
if (!interpreter) {
|
|
2002
2182
|
if (relScript.endsWith(".mjs") || relScript.endsWith(".ts")) {
|
|
2003
|
-
const hasBun2 =
|
|
2183
|
+
const hasBun2 = fs10.existsSync(path10.join(dir, "bun.lockb")) || fs10.existsSync(path10.join(dir, "bun.lock"));
|
|
2004
2184
|
if (hasBun2) interpreter = "bun";
|
|
2005
2185
|
}
|
|
2006
2186
|
}
|
|
@@ -2081,7 +2261,7 @@ Would write ${planned.length} app(s) to ${outPath ?? "relife2.config.cjs (dry-ru
|
|
|
2081
2261
|
console.log(`Run without --dry-run to generate.`);
|
|
2082
2262
|
return 0;
|
|
2083
2263
|
}
|
|
2084
|
-
const resolvedOut =
|
|
2264
|
+
const resolvedOut = path10.resolve(outPath ?? "relife2.config.cjs");
|
|
2085
2265
|
const appsCode = planned.map((p) => {
|
|
2086
2266
|
const interp = p._interpreter;
|
|
2087
2267
|
const isCaddy = p.name === "caddy";
|
|
@@ -2138,7 +2318,7 @@ ${appsCode}
|
|
|
2138
2318
|
]
|
|
2139
2319
|
};
|
|
2140
2320
|
`;
|
|
2141
|
-
|
|
2321
|
+
fs10.writeFileSync(resolvedOut, fileContent, "utf8");
|
|
2142
2322
|
console.log(
|
|
2143
2323
|
`from-caddy: parsed ${sites.length} site(s), ${planned.length} upstream(s) from ${resolvedCaddy}`
|
|
2144
2324
|
);
|
|
@@ -2156,7 +2336,7 @@ Warnings:`);
|
|
|
2156
2336
|
console.log(`
|
|
2157
2337
|
Wrote ${resolvedOut} (${planned.length} app(s))`);
|
|
2158
2338
|
console.log(
|
|
2159
|
-
`Next: relife2 inspect-config ${
|
|
2339
|
+
`Next: relife2 inspect-config ${path10.basename(resolvedOut)} \u2192 relife2 start ${path10.basename(resolvedOut)}`
|
|
2160
2340
|
);
|
|
2161
2341
|
return 0;
|
|
2162
2342
|
}
|
|
@@ -2185,7 +2365,7 @@ function printUsage2() {
|
|
|
2185
2365
|
}
|
|
2186
2366
|
|
|
2187
2367
|
// src/version.ts
|
|
2188
|
-
var VERSION = "1.0.
|
|
2368
|
+
var VERSION = "1.0.3";
|
|
2189
2369
|
|
|
2190
2370
|
// src/commands/help.ts
|
|
2191
2371
|
var HELP_TEXT = `relife2 ${VERSION} \u2014 process manager for Node.js & Bun apps (PM2 alternative, done right)
|
|
@@ -2211,7 +2391,7 @@ Commands:
|
|
|
2211
2391
|
flush [name|all] Empty the app log file(s)
|
|
2212
2392
|
rotate [--max-size <bytes>] [--retain <n>] [name|all]
|
|
2213
2393
|
Rotate log files exceeding the size threshold (M3)
|
|
2214
|
-
doctor
|
|
2394
|
+
doctor [--fix] Run integrity checks on the running daemon (M3) \u2014 --fix heals stale lock + *.tmp.cjs
|
|
2215
2395
|
metrics Show daemon self-metrics + per-app summary (M3)
|
|
2216
2396
|
monit [--once] Live dashboard (M6): cpu/mem/uptime per app, q to quit
|
|
2217
2397
|
find [start] [--root <dir>] [--depth <n>] [--json] [--all]
|
|
@@ -2243,13 +2423,13 @@ function printHelp() {
|
|
|
2243
2423
|
}
|
|
2244
2424
|
|
|
2245
2425
|
// src/commands/importpm2.ts
|
|
2246
|
-
import * as
|
|
2247
|
-
import
|
|
2248
|
-
import
|
|
2426
|
+
import * as fs11 from "node:fs";
|
|
2427
|
+
import os7 from "node:os";
|
|
2428
|
+
import path11 from "node:path";
|
|
2249
2429
|
async function run8(args) {
|
|
2250
2430
|
const target = args[0];
|
|
2251
|
-
const dumpPath = target !== void 0 ?
|
|
2252
|
-
if (!
|
|
2431
|
+
const dumpPath = target !== void 0 ? path11.resolve(target) : path11.join(os7.homedir(), ".pm2", "dump.pm2");
|
|
2432
|
+
if (!fs11.existsSync(dumpPath)) {
|
|
2253
2433
|
console.error(`import-pm2: dump file not found: ${dumpPath}`);
|
|
2254
2434
|
console.error(
|
|
2255
2435
|
" pm2 saves to ~/.pm2/dump.pm2 via `pm2 save` \u2014 run that first, or pass a path:"
|
|
@@ -2259,7 +2439,7 @@ async function run8(args) {
|
|
|
2259
2439
|
}
|
|
2260
2440
|
let raw;
|
|
2261
2441
|
try {
|
|
2262
|
-
raw = JSON.parse(
|
|
2442
|
+
raw = JSON.parse(fs11.readFileSync(dumpPath, "utf8"));
|
|
2263
2443
|
} catch (err) {
|
|
2264
2444
|
console.error(`import-pm2: cannot parse ${dumpPath}: ${err.message}`);
|
|
2265
2445
|
return 1;
|
|
@@ -2302,11 +2482,11 @@ async function run8(args) {
|
|
|
2302
2482
|
}
|
|
2303
2483
|
]
|
|
2304
2484
|
};
|
|
2305
|
-
const tmpPath =
|
|
2306
|
-
|
|
2485
|
+
const tmpPath = path11.join(
|
|
2486
|
+
os7.tmpdir(),
|
|
2307
2487
|
`relife2-import-${Date.now()}-${Math.random().toString(36).slice(2)}.cjs`
|
|
2308
2488
|
);
|
|
2309
|
-
|
|
2489
|
+
fs11.writeFileSync(tmpPath, `module.exports = ${JSON.stringify(tmpConfig)}`, "utf8");
|
|
2310
2490
|
try {
|
|
2311
2491
|
const res = await client.call("start", {
|
|
2312
2492
|
target: { type: "config", path: tmpPath }
|
|
@@ -2322,7 +2502,7 @@ async function run8(args) {
|
|
|
2322
2502
|
console.error(`import ${name}: ${err.message}`);
|
|
2323
2503
|
} finally {
|
|
2324
2504
|
try {
|
|
2325
|
-
|
|
2505
|
+
fs11.unlinkSync(tmpPath);
|
|
2326
2506
|
} catch {
|
|
2327
2507
|
}
|
|
2328
2508
|
}
|
|
@@ -2339,7 +2519,7 @@ imported ${imported} app(s) from ${dumpPath}`);
|
|
|
2339
2519
|
}
|
|
2340
2520
|
|
|
2341
2521
|
// src/commands/inspectconfig.ts
|
|
2342
|
-
import
|
|
2522
|
+
import path12 from "node:path";
|
|
2343
2523
|
async function run9(args) {
|
|
2344
2524
|
const { positionals } = parseCliArgs(args);
|
|
2345
2525
|
const file = positionals[0];
|
|
@@ -2349,7 +2529,7 @@ async function run9(args) {
|
|
|
2349
2529
|
}
|
|
2350
2530
|
let loaded;
|
|
2351
2531
|
try {
|
|
2352
|
-
loaded = await loadConfigFile(
|
|
2532
|
+
loaded = await loadConfigFile(path12.resolve(file));
|
|
2353
2533
|
} catch (err) {
|
|
2354
2534
|
console.error(`relife2: ${err.message}`);
|
|
2355
2535
|
return 1;
|
|
@@ -2418,7 +2598,7 @@ function printTable(rows) {
|
|
|
2418
2598
|
}
|
|
2419
2599
|
|
|
2420
2600
|
// src/commands/logs.ts
|
|
2421
|
-
import * as
|
|
2601
|
+
import * as fs12 from "node:fs";
|
|
2422
2602
|
var VALUE_OPTS2 = /* @__PURE__ */ new Set(["lines"]);
|
|
2423
2603
|
async function run12(args) {
|
|
2424
2604
|
const { positionals, opts } = parseCliArgs(args, VALUE_OPTS2);
|
|
@@ -2458,7 +2638,7 @@ function parseLines(v) {
|
|
|
2458
2638
|
}
|
|
2459
2639
|
function printTail(file, lines) {
|
|
2460
2640
|
try {
|
|
2461
|
-
const text =
|
|
2641
|
+
const text = fs12.readFileSync(file, "utf8");
|
|
2462
2642
|
const all = text.split(/\r?\n/);
|
|
2463
2643
|
if (all.length > 0 && all[all.length - 1] === "") all.pop();
|
|
2464
2644
|
const slice = all.slice(Math.max(0, all.length - lines));
|
|
@@ -2484,13 +2664,13 @@ async function followFiles(files) {
|
|
|
2484
2664
|
const from = positions.get(f) ?? 0;
|
|
2485
2665
|
if (size > from) {
|
|
2486
2666
|
try {
|
|
2487
|
-
const fd =
|
|
2667
|
+
const fd = fs12.openSync(f, "r");
|
|
2488
2668
|
try {
|
|
2489
2669
|
const buf = Buffer.alloc(size - from);
|
|
2490
|
-
|
|
2670
|
+
fs12.readSync(fd, buf, 0, buf.length, from);
|
|
2491
2671
|
process.stdout.write(buf);
|
|
2492
2672
|
} finally {
|
|
2493
|
-
|
|
2673
|
+
fs12.closeSync(fd);
|
|
2494
2674
|
}
|
|
2495
2675
|
} catch {
|
|
2496
2676
|
}
|
|
@@ -2501,7 +2681,7 @@ async function followFiles(files) {
|
|
|
2501
2681
|
}
|
|
2502
2682
|
function fileSize(file) {
|
|
2503
2683
|
try {
|
|
2504
|
-
return
|
|
2684
|
+
return fs12.statSync(file).size;
|
|
2505
2685
|
} catch {
|
|
2506
2686
|
return 0;
|
|
2507
2687
|
}
|
|
@@ -2791,7 +2971,7 @@ async function run20(_args) {
|
|
|
2791
2971
|
}
|
|
2792
2972
|
|
|
2793
2973
|
// src/commands/start.ts
|
|
2794
|
-
import
|
|
2974
|
+
import path13 from "node:path";
|
|
2795
2975
|
var VALUE_OPTS4 = /* @__PURE__ */ new Set([
|
|
2796
2976
|
"name",
|
|
2797
2977
|
"env",
|
|
@@ -2840,7 +3020,7 @@ async function run21(args) {
|
|
|
2840
3020
|
const res = await client.call("start", {
|
|
2841
3021
|
target: {
|
|
2842
3022
|
type: isConfig ? "config" : "script",
|
|
2843
|
-
path:
|
|
3023
|
+
path: path13.resolve(target),
|
|
2844
3024
|
name,
|
|
2845
3025
|
args: passthrough.length > 0 ? passthrough : void 0,
|
|
2846
3026
|
envName: envName ?? void 0,
|
|
@@ -2867,11 +3047,11 @@ async function run21(args) {
|
|
|
2867
3047
|
|
|
2868
3048
|
// src/commands/startup.ts
|
|
2869
3049
|
import { execSync, spawnSync as spawnSync2 } from "node:child_process";
|
|
2870
|
-
import * as
|
|
2871
|
-
import
|
|
2872
|
-
import
|
|
3050
|
+
import * as fs13 from "node:fs";
|
|
3051
|
+
import os8 from "node:os";
|
|
3052
|
+
import path14 from "node:path";
|
|
2873
3053
|
import { fileURLToPath } from "node:url";
|
|
2874
|
-
var SYSTEMD_USER_DIR =
|
|
3054
|
+
var SYSTEMD_USER_DIR = path14.join(os8.homedir(), ".config", "systemd", "user");
|
|
2875
3055
|
var UNIT_NAME = "relife2.service";
|
|
2876
3056
|
function getRelife2Bin() {
|
|
2877
3057
|
const which = spawnSync2(process.platform === "win32" ? "where" : "which", ["relife2"], {
|
|
@@ -2889,13 +3069,13 @@ function getRelife2Bin() {
|
|
|
2889
3069
|
}
|
|
2890
3070
|
try {
|
|
2891
3071
|
const thisFile = fileURLToPath(import.meta.url);
|
|
2892
|
-
const cliPath =
|
|
2893
|
-
if (
|
|
3072
|
+
const cliPath = path14.resolve(path14.dirname(thisFile), "..", "cli.js");
|
|
3073
|
+
if (fs13.existsSync(cliPath)) {
|
|
2894
3074
|
return `node ${cliPath}`;
|
|
2895
3075
|
}
|
|
2896
3076
|
} catch {
|
|
2897
3077
|
}
|
|
2898
|
-
return `node ${
|
|
3078
|
+
return `node ${path14.resolve(arg1 ?? process.cwd())}`;
|
|
2899
3079
|
}
|
|
2900
3080
|
function generateUnit(bin) {
|
|
2901
3081
|
const envHint = process.env.RELIFE2_DIR !== void 0 ? `
|
|
@@ -2916,6 +3096,49 @@ Environment=RELIFE2_DAEMON=1${envHint}
|
|
|
2916
3096
|
WantedBy=default.target
|
|
2917
3097
|
`;
|
|
2918
3098
|
}
|
|
3099
|
+
function isLingerEnabled(user) {
|
|
3100
|
+
try {
|
|
3101
|
+
const out = execSync(`loginctl show-user ${user} -p Linger --value`, {
|
|
3102
|
+
encoding: "utf8",
|
|
3103
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
3104
|
+
timeout: 3e3
|
|
3105
|
+
});
|
|
3106
|
+
const v = out.trim().toLowerCase();
|
|
3107
|
+
if (v === "yes") return true;
|
|
3108
|
+
if (v === "no") return false;
|
|
3109
|
+
} catch {
|
|
3110
|
+
}
|
|
3111
|
+
try {
|
|
3112
|
+
const out = execSync(`loginctl show-user ${user}`, {
|
|
3113
|
+
encoding: "utf8",
|
|
3114
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
3115
|
+
timeout: 3e3
|
|
3116
|
+
});
|
|
3117
|
+
if (/Linger=yes/i.test(out)) return true;
|
|
3118
|
+
if (/Linger=no/i.test(out)) return false;
|
|
3119
|
+
} catch {
|
|
3120
|
+
}
|
|
3121
|
+
return null;
|
|
3122
|
+
}
|
|
3123
|
+
function tryEnableLinger(user) {
|
|
3124
|
+
const attempts = [
|
|
3125
|
+
{ cmd: `loginctl enable-linger ${user}`, label: "loginctl" },
|
|
3126
|
+
{ cmd: `sudo -n loginctl enable-linger ${user}`, label: "sudo -n" },
|
|
3127
|
+
{ cmd: `sudo loginctl enable-linger ${user}`, label: "sudo" }
|
|
3128
|
+
];
|
|
3129
|
+
for (const a of attempts) {
|
|
3130
|
+
try {
|
|
3131
|
+
execSync(a.cmd, { stdio: ["ignore", "pipe", "pipe"], timeout: 8e3 });
|
|
3132
|
+
const enabled = isLingerEnabled(user);
|
|
3133
|
+
if (enabled === true || enabled === null) {
|
|
3134
|
+
console.log(`enabled linger for user ${user} via ${a.label}`);
|
|
3135
|
+
return true;
|
|
3136
|
+
}
|
|
3137
|
+
} catch {
|
|
3138
|
+
}
|
|
3139
|
+
}
|
|
3140
|
+
return false;
|
|
3141
|
+
}
|
|
2919
3142
|
async function run22(_args) {
|
|
2920
3143
|
if (process.platform === "win32") {
|
|
2921
3144
|
console.error(
|
|
@@ -2929,47 +3152,67 @@ async function run22(_args) {
|
|
|
2929
3152
|
}
|
|
2930
3153
|
const bin = getRelife2Bin();
|
|
2931
3154
|
const unitContent = generateUnit(bin);
|
|
2932
|
-
const unitPath =
|
|
3155
|
+
const unitPath = path14.join(SYSTEMD_USER_DIR, UNIT_NAME);
|
|
2933
3156
|
try {
|
|
2934
|
-
|
|
3157
|
+
fs13.mkdirSync(SYSTEMD_USER_DIR, { recursive: true });
|
|
2935
3158
|
} catch (err) {
|
|
2936
3159
|
console.error(`cannot create ${SYSTEMD_USER_DIR}: ${err.message}`);
|
|
2937
3160
|
return 1;
|
|
2938
3161
|
}
|
|
2939
3162
|
try {
|
|
2940
|
-
|
|
3163
|
+
fs13.writeFileSync(unitPath, unitContent);
|
|
2941
3164
|
console.log(`written systemd user unit: ${unitPath}`);
|
|
2942
3165
|
} catch (err) {
|
|
2943
3166
|
console.error(`cannot write ${unitPath}: ${err.message}`);
|
|
2944
3167
|
return 1;
|
|
2945
3168
|
}
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
console.log(`enabled
|
|
2950
|
-
}
|
|
2951
|
-
console.
|
|
2952
|
-
|
|
2953
|
-
)
|
|
2954
|
-
|
|
2955
|
-
|
|
3169
|
+
const user = process.env.USER ?? process.env.LOGNAME ?? os8.userInfo().username ?? "opc";
|
|
3170
|
+
const lingerState = isLingerEnabled(user);
|
|
3171
|
+
if (lingerState === true) {
|
|
3172
|
+
console.log(`linger already enabled for user ${user}`);
|
|
3173
|
+
} else if (lingerState === false) {
|
|
3174
|
+
console.log(`linger is disabled for ${user} \u2014 enabling\u2026`);
|
|
3175
|
+
const ok = tryEnableLinger(user);
|
|
3176
|
+
if (!ok) {
|
|
3177
|
+
console.error(`warning: could not enable linger (is systemd-logind running?)`);
|
|
3178
|
+
console.error(`your apps will only start after you log in. Run manually:`);
|
|
3179
|
+
console.error(` sudo loginctl enable-linger ${user}`);
|
|
3180
|
+
}
|
|
3181
|
+
} else {
|
|
3182
|
+
const ok = tryEnableLinger(user);
|
|
3183
|
+
if (!ok) {
|
|
3184
|
+
console.error(`note: could not verify linger status (no logind?) \u2014 skipping`);
|
|
3185
|
+
}
|
|
2956
3186
|
}
|
|
2957
3187
|
try {
|
|
2958
|
-
execSync("systemctl --user daemon-reload", {
|
|
2959
|
-
|
|
3188
|
+
execSync("systemctl --user daemon-reload", {
|
|
3189
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
3190
|
+
timeout: 8e3
|
|
3191
|
+
});
|
|
3192
|
+
execSync(`systemctl --user enable ${UNIT_NAME}`, {
|
|
3193
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
3194
|
+
timeout: 8e3
|
|
3195
|
+
});
|
|
2960
3196
|
console.log(`enabled and reloaded: systemctl --user enable ${UNIT_NAME}`);
|
|
3197
|
+
try {
|
|
3198
|
+
execSync(`systemctl --user start ${UNIT_NAME}`, {
|
|
3199
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
3200
|
+
timeout: 8e3
|
|
3201
|
+
});
|
|
3202
|
+
console.log(`started: systemctl --user start ${UNIT_NAME}`);
|
|
3203
|
+
} catch {
|
|
3204
|
+
}
|
|
2961
3205
|
} catch (err) {
|
|
2962
3206
|
console.error(
|
|
2963
3207
|
`warning: could not enable unit (is systemd available?): ${err instanceof Error ? err.message : String(err)}`
|
|
2964
3208
|
);
|
|
2965
3209
|
console.error("run manually:");
|
|
2966
3210
|
console.error(" systemctl --user daemon-reload");
|
|
2967
|
-
console.error(` systemctl --user enable ${UNIT_NAME}`);
|
|
3211
|
+
console.error(` systemctl --user enable --now ${UNIT_NAME}`);
|
|
2968
3212
|
}
|
|
2969
3213
|
console.log("");
|
|
2970
3214
|
console.log("relife2 will now start automatically on boot.");
|
|
2971
3215
|
console.log(`ExecStart is: ${bin}`);
|
|
2972
|
-
console.log("To start now, run: systemctl --user start relife2.service");
|
|
2973
3216
|
console.log("To check status: systemctl --user status relife2.service");
|
|
2974
3217
|
return 0;
|
|
2975
3218
|
}
|
|
@@ -3165,14 +3408,14 @@ function detectRuntime() {
|
|
|
3165
3408
|
}
|
|
3166
3409
|
|
|
3167
3410
|
// src/daemon/daemon.ts
|
|
3168
|
-
import * as
|
|
3169
|
-
import
|
|
3170
|
-
import
|
|
3411
|
+
import * as fs20 from "node:fs";
|
|
3412
|
+
import os9 from "node:os";
|
|
3413
|
+
import path20 from "node:path";
|
|
3171
3414
|
import * as zlib from "node:zlib";
|
|
3172
3415
|
|
|
3173
3416
|
// src/daemon/applog.ts
|
|
3174
|
-
import * as
|
|
3175
|
-
import
|
|
3417
|
+
import * as fs14 from "node:fs";
|
|
3418
|
+
import path15 from "node:path";
|
|
3176
3419
|
|
|
3177
3420
|
// src/util/datefmt.ts
|
|
3178
3421
|
function formatDate(pattern, date = /* @__PURE__ */ new Date()) {
|
|
@@ -3197,12 +3440,12 @@ function formatDate(pattern, date = /* @__PURE__ */ new Date()) {
|
|
|
3197
3440
|
|
|
3198
3441
|
// src/daemon/applog.ts
|
|
3199
3442
|
var DEFAULT_LOG_FORMAT = "YYYY-MM-DD HH:mm:ss.SSS Z";
|
|
3200
|
-
var DEFAULT_LOGS_DIR = (base) =>
|
|
3443
|
+
var DEFAULT_LOGS_DIR = (base) => path15.join(base, "logs");
|
|
3201
3444
|
function resolveLogPaths(app, base) {
|
|
3202
3445
|
const safe = app.name.replace(/[^\w.-]/g, "_");
|
|
3203
|
-
const out = app.outFile ?
|
|
3446
|
+
const out = app.outFile ? path15.resolve(app.cwd, app.outFile) : path15.join(DEFAULT_LOGS_DIR(base), `${safe}.out.log`);
|
|
3204
3447
|
if (app.mergeLogs) return { out, err: null };
|
|
3205
|
-
const err = app.errFile ?
|
|
3448
|
+
const err = app.errFile ? path15.resolve(app.cwd, app.errFile) : path15.join(DEFAULT_LOGS_DIR(base), `${safe}.err.log`);
|
|
3206
3449
|
return { out, err };
|
|
3207
3450
|
}
|
|
3208
3451
|
var AppLogSink = class {
|
|
@@ -3247,8 +3490,8 @@ var AppLogSink = class {
|
|
|
3247
3490
|
const out = `${this.prefix}${line}
|
|
3248
3491
|
`;
|
|
3249
3492
|
try {
|
|
3250
|
-
ensureDirSync(
|
|
3251
|
-
|
|
3493
|
+
ensureDirSync(path15.dirname(target));
|
|
3494
|
+
fs14.appendFileSync(target, out);
|
|
3252
3495
|
} catch {
|
|
3253
3496
|
}
|
|
3254
3497
|
}
|
|
@@ -3256,18 +3499,18 @@ var AppLogSink = class {
|
|
|
3256
3499
|
};
|
|
3257
3500
|
|
|
3258
3501
|
// src/daemon/logger.ts
|
|
3259
|
-
import
|
|
3260
|
-
import
|
|
3502
|
+
import fs15 from "node:fs";
|
|
3503
|
+
import path16 from "node:path";
|
|
3261
3504
|
function createDaemonLogger(base) {
|
|
3262
|
-
const logsDir =
|
|
3505
|
+
const logsDir = path16.join(base, "logs");
|
|
3263
3506
|
ensureDirSync(logsDir);
|
|
3264
|
-
const mainFile =
|
|
3507
|
+
const mainFile = path16.join(logsDir, "daemon.log");
|
|
3265
3508
|
const outRoot = logsDir;
|
|
3266
3509
|
function log(msg, extra) {
|
|
3267
3510
|
const line = `${JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), msg, ...extra ?? {} })}
|
|
3268
3511
|
`;
|
|
3269
3512
|
try {
|
|
3270
|
-
|
|
3513
|
+
fs15.appendFileSync(mainFile, line);
|
|
3271
3514
|
} catch {
|
|
3272
3515
|
}
|
|
3273
3516
|
}
|
|
@@ -3276,13 +3519,13 @@ function createDaemonLogger(base) {
|
|
|
3276
3519
|
}
|
|
3277
3520
|
function appOut(name, chunk) {
|
|
3278
3521
|
try {
|
|
3279
|
-
|
|
3522
|
+
fs15.appendFileSync(path16.join(outRoot, `${safeName(name)}.out.log`), chunk);
|
|
3280
3523
|
} catch {
|
|
3281
3524
|
}
|
|
3282
3525
|
}
|
|
3283
3526
|
function appErr(name, chunk) {
|
|
3284
3527
|
try {
|
|
3285
|
-
|
|
3528
|
+
fs15.appendFileSync(path16.join(outRoot, `${safeName(name)}.err.log`), chunk);
|
|
3286
3529
|
} catch {
|
|
3287
3530
|
}
|
|
3288
3531
|
}
|
|
@@ -3291,12 +3534,12 @@ function createDaemonLogger(base) {
|
|
|
3291
3534
|
|
|
3292
3535
|
// src/daemon/pm.ts
|
|
3293
3536
|
import { spawn as spawn3 } from "node:child_process";
|
|
3294
|
-
import * as
|
|
3295
|
-
import
|
|
3537
|
+
import * as fs17 from "node:fs";
|
|
3538
|
+
import path18 from "node:path";
|
|
3296
3539
|
|
|
3297
3540
|
// src/daemon/command.ts
|
|
3298
3541
|
import { existsSync as existsSync8 } from "node:fs";
|
|
3299
|
-
import
|
|
3542
|
+
import path17 from "node:path";
|
|
3300
3543
|
import process2 from "node:process";
|
|
3301
3544
|
var JS_RE = /\.(?:c?js|mjs|ts|mts|cts)$/i;
|
|
3302
3545
|
function isJsScript(p) {
|
|
@@ -3306,10 +3549,10 @@ function isBunProject2(scriptDir) {
|
|
|
3306
3549
|
let dir = scriptDir;
|
|
3307
3550
|
const home = process2.env.HOME ?? "";
|
|
3308
3551
|
while (true) {
|
|
3309
|
-
if (existsSync8(
|
|
3552
|
+
if (existsSync8(path17.join(dir, "bun.lockb")) || existsSync8(path17.join(dir, "bun.lock"))) {
|
|
3310
3553
|
return true;
|
|
3311
3554
|
}
|
|
3312
|
-
const parent =
|
|
3555
|
+
const parent = path17.dirname(dir);
|
|
3313
3556
|
if (parent === dir) break;
|
|
3314
3557
|
if (home !== "" && dir === home) break;
|
|
3315
3558
|
dir = parent;
|
|
@@ -3345,7 +3588,7 @@ function buildSpawnSpec(app) {
|
|
|
3345
3588
|
|
|
3346
3589
|
// src/daemon/memproc.ts
|
|
3347
3590
|
import { execFileSync } from "node:child_process";
|
|
3348
|
-
import * as
|
|
3591
|
+
import * as fs16 from "node:fs";
|
|
3349
3592
|
function processRss(pid) {
|
|
3350
3593
|
if (pid <= 0) return void 0;
|
|
3351
3594
|
try {
|
|
@@ -3361,7 +3604,7 @@ function processRss(pid) {
|
|
|
3361
3604
|
}
|
|
3362
3605
|
}
|
|
3363
3606
|
function rssFromProc(pid) {
|
|
3364
|
-
const status =
|
|
3607
|
+
const status = fs16.readFileSync(`/proc/${pid}/status`, "utf8");
|
|
3365
3608
|
const match = /^VmRSS:\s+(\d+)\s+kB/m.exec(status);
|
|
3366
3609
|
if (match?.[1] === void 0) return void 0;
|
|
3367
3610
|
return Number(match[1]) * 1024;
|
|
@@ -3436,7 +3679,7 @@ var ProcessManager = class {
|
|
|
3436
3679
|
});
|
|
3437
3680
|
record.config.cwd = cwdFix.path;
|
|
3438
3681
|
}
|
|
3439
|
-
if (
|
|
3682
|
+
if (path18.isAbsolute(record.config.script) && !fs17.existsSync(record.config.script)) {
|
|
3440
3683
|
const sFix = fixScript(record.config.script, record.config.cwd, record.config.sourceDir);
|
|
3441
3684
|
if (sFix.fixed && sFix.reason) {
|
|
3442
3685
|
this.logger.log(sFix.reason);
|
|
@@ -3450,7 +3693,7 @@ var ProcessManager = class {
|
|
|
3450
3693
|
} else if (!sFix.fixed) {
|
|
3451
3694
|
this.fail(
|
|
3452
3695
|
record,
|
|
3453
|
-
`script not found: '${record.config.script}'. ${sFix.reason ?? ""} Run 'relife2 find --root ${
|
|
3696
|
+
`script not found: '${record.config.script}'. ${sFix.reason ?? ""} Run 'relife2 find --root ${path18.dirname(record.config.sourceDir)}' to locate configs or fix the config.`
|
|
3454
3697
|
);
|
|
3455
3698
|
this.store.saveSync();
|
|
3456
3699
|
return;
|
|
@@ -3766,19 +4009,19 @@ var ProcessManager = class {
|
|
|
3766
4009
|
const watch3 = record.config.watch;
|
|
3767
4010
|
if (watch3 === void 0 || watch3 === false) return;
|
|
3768
4011
|
if (this.watchers.has(record.name)) return;
|
|
3769
|
-
const rawPaths = Array.isArray(watch3) ? watch3.map((p) =>
|
|
4012
|
+
const rawPaths = Array.isArray(watch3) ? watch3.map((p) => path18.resolve(record.config.cwd, p)) : [record.config.cwd];
|
|
3770
4013
|
const watchers = [];
|
|
3771
4014
|
for (const target of rawPaths) {
|
|
3772
4015
|
try {
|
|
3773
|
-
if (!
|
|
4016
|
+
if (!fs17.existsSync(target)) {
|
|
3774
4017
|
this.logger.log(`watch: path '${target}' does not exist yet; skipping`);
|
|
3775
4018
|
continue;
|
|
3776
4019
|
}
|
|
3777
|
-
const stat =
|
|
4020
|
+
const stat = fs17.statSync(target);
|
|
3778
4021
|
if (!stat.isDirectory()) {
|
|
3779
|
-
const dir =
|
|
3780
|
-
const base =
|
|
3781
|
-
const watcher =
|
|
4022
|
+
const dir = path18.dirname(target);
|
|
4023
|
+
const base = path18.basename(target);
|
|
4024
|
+
const watcher = fs17.watch(dir, (_eventType, filename) => {
|
|
3782
4025
|
if (filename !== null && filename !== base) return;
|
|
3783
4026
|
this.onWatchChange(record.name, target);
|
|
3784
4027
|
});
|
|
@@ -3786,7 +4029,7 @@ var ProcessManager = class {
|
|
|
3786
4029
|
continue;
|
|
3787
4030
|
}
|
|
3788
4031
|
if (process.platform === "win32" || process.platform === "darwin") {
|
|
3789
|
-
const watcher =
|
|
4032
|
+
const watcher = fs17.watch(target, { recursive: true }, () => {
|
|
3790
4033
|
this.onWatchChange(record.name, target);
|
|
3791
4034
|
});
|
|
3792
4035
|
watchers.push(watcher);
|
|
@@ -3794,7 +4037,7 @@ var ProcessManager = class {
|
|
|
3794
4037
|
const dirs = this.collectSubDirs(target);
|
|
3795
4038
|
for (const dir of dirs) {
|
|
3796
4039
|
try {
|
|
3797
|
-
const watcher =
|
|
4040
|
+
const watcher = fs17.watch(dir, () => {
|
|
3798
4041
|
this.onWatchChange(record.name, target);
|
|
3799
4042
|
});
|
|
3800
4043
|
watchers.push(watcher);
|
|
@@ -3821,7 +4064,7 @@ var ProcessManager = class {
|
|
|
3821
4064
|
const dir = queue.shift();
|
|
3822
4065
|
let entries;
|
|
3823
4066
|
try {
|
|
3824
|
-
entries =
|
|
4067
|
+
entries = fs17.readdirSync(dir, { withFileTypes: true });
|
|
3825
4068
|
} catch {
|
|
3826
4069
|
continue;
|
|
3827
4070
|
}
|
|
@@ -3829,7 +4072,7 @@ var ProcessManager = class {
|
|
|
3829
4072
|
if (!entry.isDirectory()) continue;
|
|
3830
4073
|
if (entry.name.startsWith(".")) continue;
|
|
3831
4074
|
if (entry.name === "node_modules" || entry.name === ".git") continue;
|
|
3832
|
-
const full =
|
|
4075
|
+
const full = path18.join(dir, entry.name);
|
|
3833
4076
|
if (seen.has(full)) continue;
|
|
3834
4077
|
seen.add(full);
|
|
3835
4078
|
out.push(full);
|
|
@@ -3929,17 +4172,17 @@ var ProcessManager = class {
|
|
|
3929
4172
|
};
|
|
3930
4173
|
|
|
3931
4174
|
// src/daemon/state.ts
|
|
3932
|
-
import
|
|
3933
|
-
import
|
|
4175
|
+
import fs18 from "node:fs";
|
|
4176
|
+
import path19 from "node:path";
|
|
3934
4177
|
var StateStore = class {
|
|
3935
4178
|
apps = /* @__PURE__ */ new Map();
|
|
3936
4179
|
snapshotPath;
|
|
3937
4180
|
journalPath;
|
|
3938
4181
|
constructor(base) {
|
|
3939
|
-
const stateDir =
|
|
4182
|
+
const stateDir = path19.join(base, "state");
|
|
3940
4183
|
ensureDirSync(stateDir);
|
|
3941
|
-
this.snapshotPath =
|
|
3942
|
-
this.journalPath =
|
|
4184
|
+
this.snapshotPath = path19.join(stateDir, "snapshot.json");
|
|
4185
|
+
this.journalPath = path19.join(stateDir, "journal.jsonl");
|
|
3943
4186
|
}
|
|
3944
4187
|
load() {
|
|
3945
4188
|
const data = readJsonFile(this.snapshotPath);
|
|
@@ -3976,7 +4219,7 @@ var StateStore = class {
|
|
|
3976
4219
|
const line = `${JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), type, ...data ?? {} })}
|
|
3977
4220
|
`;
|
|
3978
4221
|
try {
|
|
3979
|
-
|
|
4222
|
+
fs18.appendFileSync(this.journalPath, line);
|
|
3980
4223
|
} catch {
|
|
3981
4224
|
}
|
|
3982
4225
|
}
|
|
@@ -3988,7 +4231,7 @@ var StateStore = class {
|
|
|
3988
4231
|
};
|
|
3989
4232
|
|
|
3990
4233
|
// src/daemon/transport.ts
|
|
3991
|
-
import
|
|
4234
|
+
import fs19 from "node:fs";
|
|
3992
4235
|
import net2 from "node:net";
|
|
3993
4236
|
async function createDaemonTransport(base) {
|
|
3994
4237
|
return process.platform === "win32" ? createWinTransport(base) : createPosixTransport(base);
|
|
@@ -4002,7 +4245,7 @@ async function createPosixTransport(base) {
|
|
|
4002
4245
|
if (err.code !== "EADDRINUSE") return null;
|
|
4003
4246
|
if (await probeActive(sockPath)) return null;
|
|
4004
4247
|
try {
|
|
4005
|
-
|
|
4248
|
+
fs19.unlinkSync(sockPath);
|
|
4006
4249
|
} catch {
|
|
4007
4250
|
}
|
|
4008
4251
|
}
|
|
@@ -4055,7 +4298,7 @@ function probeActive(sockPath) {
|
|
|
4055
4298
|
}
|
|
4056
4299
|
function readPortFile(base) {
|
|
4057
4300
|
try {
|
|
4058
|
-
const raw =
|
|
4301
|
+
const raw = fs19.readFileSync(portFilePath(base), "utf8");
|
|
4059
4302
|
return Number.parseInt(raw.trim(), 10) || null;
|
|
4060
4303
|
} catch {
|
|
4061
4304
|
return null;
|
|
@@ -4063,7 +4306,7 @@ function readPortFile(base) {
|
|
|
4063
4306
|
}
|
|
4064
4307
|
function writePortFile(base, port) {
|
|
4065
4308
|
try {
|
|
4066
|
-
|
|
4309
|
+
fs19.writeFileSync(portFilePath(base), String(port));
|
|
4067
4310
|
} catch {
|
|
4068
4311
|
}
|
|
4069
4312
|
}
|
|
@@ -4072,7 +4315,7 @@ function writePortFile(base, port) {
|
|
|
4072
4315
|
async function runDaemon() {
|
|
4073
4316
|
const data = dataDir();
|
|
4074
4317
|
const runtime = runtimeDir();
|
|
4075
|
-
ensureDirSync(
|
|
4318
|
+
ensureDirSync(path20.join(data, "logs"));
|
|
4076
4319
|
const logger = createDaemonLogger(data);
|
|
4077
4320
|
logger.log("daemon starting", { pid: process.pid, version: VERSION, platform: process.platform });
|
|
4078
4321
|
const lock = acquireLock(runtime);
|
|
@@ -4287,8 +4530,8 @@ var METHODS = {
|
|
|
4287
4530
|
const files = paths.err === null ? [paths.out] : [paths.out, paths.err];
|
|
4288
4531
|
for (const f of files) {
|
|
4289
4532
|
try {
|
|
4290
|
-
ensureDirSync(
|
|
4291
|
-
|
|
4533
|
+
ensureDirSync(path20.dirname(f));
|
|
4534
|
+
fs20.writeFileSync(f, "");
|
|
4292
4535
|
} catch (err) {
|
|
4293
4536
|
logger.log(`flush ${rec.name}: cannot truncate ${f}: ${err.message}`);
|
|
4294
4537
|
}
|
|
@@ -4321,7 +4564,7 @@ var METHODS = {
|
|
|
4321
4564
|
for (const f of files) {
|
|
4322
4565
|
if (!pathExists(f)) continue;
|
|
4323
4566
|
try {
|
|
4324
|
-
const stat =
|
|
4567
|
+
const stat = fs20.statSync(f);
|
|
4325
4568
|
if (stat.size >= max) {
|
|
4326
4569
|
rotateFile(f, n);
|
|
4327
4570
|
rotated.push(f);
|
|
@@ -4375,11 +4618,11 @@ var METHODS = {
|
|
|
4375
4618
|
},
|
|
4376
4619
|
doctor: (_params, { store, base, transport }) => {
|
|
4377
4620
|
const checks = [];
|
|
4378
|
-
const snapshotPath =
|
|
4621
|
+
const snapshotPath = path20.join(base, "state", "snapshot.json");
|
|
4379
4622
|
const stateIssues = [];
|
|
4380
4623
|
if (pathExists(snapshotPath)) {
|
|
4381
4624
|
try {
|
|
4382
|
-
const data = JSON.parse(
|
|
4625
|
+
const data = JSON.parse(fs20.readFileSync(snapshotPath, "utf8"));
|
|
4383
4626
|
if (data.schema === 1 && typeof data.apps === "object") {
|
|
4384
4627
|
stateIssues.push({
|
|
4385
4628
|
severity: "ok",
|
|
@@ -4412,11 +4655,11 @@ var METHODS = {
|
|
|
4412
4655
|
ok: !stateIssues.some((i) => i.severity === "error"),
|
|
4413
4656
|
issues: stateIssues
|
|
4414
4657
|
});
|
|
4415
|
-
const lockFile =
|
|
4658
|
+
const lockFile = path20.join(runtimeDir(), "daemon.lock");
|
|
4416
4659
|
const lockIssues = [];
|
|
4417
4660
|
if (pathExists(lockFile)) {
|
|
4418
4661
|
try {
|
|
4419
|
-
const lock = JSON.parse(
|
|
4662
|
+
const lock = JSON.parse(fs20.readFileSync(lockFile, "utf8"));
|
|
4420
4663
|
if (typeof lock.pid === "number" && isAlive(lock.pid)) {
|
|
4421
4664
|
lockIssues.push({
|
|
4422
4665
|
severity: "ok",
|
|
@@ -4527,7 +4770,7 @@ async function startApps(target, ctx) {
|
|
|
4527
4770
|
if (target.interpreter !== void 0) input.interpreter = target.interpreter;
|
|
4528
4771
|
if (target.cwd !== void 0) input.cwd = target.cwd;
|
|
4529
4772
|
if (target.cliOverrides) Object.assign(input, target.cliOverrides);
|
|
4530
|
-
const single = normalizeSingleApp(input,
|
|
4773
|
+
const single = normalizeSingleApp(input, path20.dirname(target.path), target.envName);
|
|
4531
4774
|
apps = [single.app];
|
|
4532
4775
|
warnings = single.warnings;
|
|
4533
4776
|
}
|
|
@@ -4592,7 +4835,7 @@ function expandTargets(store, name) {
|
|
|
4592
4835
|
function applyCliOverrides(app, overrides) {
|
|
4593
4836
|
if (overrides.instances !== void 0) {
|
|
4594
4837
|
const v = overrides.instances;
|
|
4595
|
-
if (v === "max" || v === "MAX") app.instances =
|
|
4838
|
+
if (v === "max" || v === "MAX") app.instances = os9.cpus().length || 1;
|
|
4596
4839
|
else {
|
|
4597
4840
|
const n = Number(v);
|
|
4598
4841
|
if (Number.isFinite(n) && n >= 1) app.instances = Math.round(n);
|
|
@@ -4652,15 +4895,15 @@ function rotateFile(file, retain) {
|
|
|
4652
4895
|
const newName = `${file}.${i + 1}.gz`;
|
|
4653
4896
|
if (pathExists(oldName)) {
|
|
4654
4897
|
if (i + 1 >= retain) {
|
|
4655
|
-
|
|
4898
|
+
fs20.unlinkSync(oldName);
|
|
4656
4899
|
} else {
|
|
4657
4900
|
if (i === 0) {
|
|
4658
|
-
const content =
|
|
4659
|
-
|
|
4901
|
+
const content = fs20.readFileSync(file);
|
|
4902
|
+
fs20.unlinkSync(file);
|
|
4660
4903
|
const gz = zlib.gzipSync(content);
|
|
4661
|
-
|
|
4904
|
+
fs20.writeFileSync(newName, gz);
|
|
4662
4905
|
} else {
|
|
4663
|
-
|
|
4906
|
+
fs20.renameSync(oldName, newName);
|
|
4664
4907
|
}
|
|
4665
4908
|
}
|
|
4666
4909
|
}
|