relife2 1.0.0 → 1.0.2
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/README.md +4 -4
- package/dist/cli.js +504 -227
- 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,82 @@ var RpcClient = class {
|
|
|
318
319
|
async function ensureDaemonClient() {
|
|
319
320
|
const runtime = runtimeDir();
|
|
320
321
|
const data = dataDir();
|
|
322
|
+
ensureDirs(runtime, data);
|
|
321
323
|
let sock = await tryConnect(runtime);
|
|
324
|
+
if (sock !== null) {
|
|
325
|
+
return await validateClient(sock);
|
|
326
|
+
}
|
|
327
|
+
cleanStaleLockIfNeeded(runtime);
|
|
328
|
+
spawnDaemon();
|
|
329
|
+
for (let i = 0; i < 80 && sock === null; i++) {
|
|
330
|
+
await sleep(100);
|
|
331
|
+
sock = await tryConnect(runtime);
|
|
332
|
+
}
|
|
322
333
|
if (sock === null) {
|
|
334
|
+
cleanStaleLockIfNeeded(runtime);
|
|
323
335
|
spawnDaemon();
|
|
324
|
-
for (let i = 0; i <
|
|
336
|
+
for (let i = 0; i < 40 && sock === null; i++) {
|
|
325
337
|
await sleep(100);
|
|
326
338
|
sock = await tryConnect(runtime);
|
|
327
339
|
}
|
|
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;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
function ensureDirs(runtime, data) {
|
|
368
|
+
for (const dir of [runtime, data, path3.join(data, "logs"), path3.join(data, "state")]) {
|
|
369
|
+
try {
|
|
370
|
+
fs3.mkdirSync(dir, { recursive: true });
|
|
371
|
+
} catch {
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
function cleanStaleLockIfNeeded(runtime) {
|
|
376
|
+
const file = lockPath(runtime);
|
|
377
|
+
try {
|
|
378
|
+
const raw = fs3.readFileSync(file, "utf8");
|
|
379
|
+
const parsed = JSON.parse(raw);
|
|
380
|
+
const pid = typeof parsed.pid === "number" ? parsed.pid : null;
|
|
381
|
+
if (pid === null) {
|
|
382
|
+
fs3.unlinkSync(file);
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
try {
|
|
386
|
+
process.kill(pid, 0);
|
|
387
|
+
} catch (e) {
|
|
388
|
+
const code = e.code;
|
|
389
|
+
if (code === "ESRCH") {
|
|
390
|
+
try {
|
|
391
|
+
fs3.unlinkSync(file);
|
|
392
|
+
} catch {
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
} catch {
|
|
341
397
|
}
|
|
342
|
-
return client;
|
|
343
398
|
}
|
|
344
399
|
function spawnDaemon() {
|
|
345
400
|
const arg1 = process.argv[1];
|
|
@@ -493,7 +548,7 @@ async function run2(args) {
|
|
|
493
548
|
|
|
494
549
|
// src/commands/dev.ts
|
|
495
550
|
import { spawn as spawn2, spawnSync } from "node:child_process";
|
|
496
|
-
import * as
|
|
551
|
+
import * as fs4 from "node:fs";
|
|
497
552
|
import path4 from "node:path";
|
|
498
553
|
var DEV_VALUE_OPTS = /* @__PURE__ */ new Set(["watch", "interpreter", "env", "name", "w"]);
|
|
499
554
|
function hasBun() {
|
|
@@ -511,7 +566,7 @@ function isBunProject(cwd) {
|
|
|
511
566
|
let cur = path4.resolve(cwd);
|
|
512
567
|
const home = path4.resolve(process.env.HOME ?? "");
|
|
513
568
|
for (let i = 0; i < 12; i++) {
|
|
514
|
-
if (
|
|
569
|
+
if (fs4.existsSync(path4.join(cur, "bun.lockb")) || fs4.existsSync(path4.join(cur, "bun.lock")))
|
|
515
570
|
return true;
|
|
516
571
|
if (cur === home || cur === path4.dirname(cur)) break;
|
|
517
572
|
cur = path4.dirname(cur);
|
|
@@ -523,9 +578,10 @@ function collectSubDirs(root) {
|
|
|
523
578
|
const stack = [root];
|
|
524
579
|
while (stack.length > 0) {
|
|
525
580
|
const dir = stack.pop();
|
|
581
|
+
if (!dir) continue;
|
|
526
582
|
let entries = [];
|
|
527
583
|
try {
|
|
528
|
-
entries =
|
|
584
|
+
entries = fs4.readdirSync(dir, { withFileTypes: true });
|
|
529
585
|
} catch {
|
|
530
586
|
continue;
|
|
531
587
|
}
|
|
@@ -555,7 +611,7 @@ async function run3(args) {
|
|
|
555
611
|
return 1;
|
|
556
612
|
}
|
|
557
613
|
const script = path4.resolve(scriptArg);
|
|
558
|
-
if (!
|
|
614
|
+
if (!fs4.existsSync(script)) {
|
|
559
615
|
console.error(`dev: script not found: ${script}`);
|
|
560
616
|
return 1;
|
|
561
617
|
}
|
|
@@ -584,7 +640,7 @@ async function run3(args) {
|
|
|
584
640
|
return runManualWatch(script, cwd, interpreter, passthrough, watchRaw);
|
|
585
641
|
}
|
|
586
642
|
async function runBunHot(script, cwd, appArgs, watchRaw) {
|
|
587
|
-
const
|
|
643
|
+
const _extraWatch = watchRaw ? ["--watch"] : [];
|
|
588
644
|
const bunArgs = ["--hot", script, ...appArgs];
|
|
589
645
|
console.log(`dev (bun --hot): bun ${bunArgs.join(" ")} [cwd ${cwd}]`);
|
|
590
646
|
if (watchRaw)
|
|
@@ -663,16 +719,16 @@ async function runManualWatch(script, cwd, interpreter, appArgs, watchRaw) {
|
|
|
663
719
|
}, 200);
|
|
664
720
|
};
|
|
665
721
|
for (const target of targets) {
|
|
666
|
-
if (!
|
|
722
|
+
if (!fs4.existsSync(target)) {
|
|
667
723
|
console.error(`dev: watch path does not exist: ${target} \u2014 skipping`);
|
|
668
724
|
continue;
|
|
669
725
|
}
|
|
670
|
-
const stat =
|
|
726
|
+
const stat = fs4.statSync(target);
|
|
671
727
|
if (!stat.isDirectory()) {
|
|
672
728
|
const dir = path4.dirname(target);
|
|
673
729
|
const base = path4.basename(target);
|
|
674
730
|
try {
|
|
675
|
-
const w =
|
|
731
|
+
const w = fs4.watch(dir, (_ev, filename) => {
|
|
676
732
|
if (filename && filename !== base) return;
|
|
677
733
|
scheduleRestart(target);
|
|
678
734
|
});
|
|
@@ -684,7 +740,7 @@ async function runManualWatch(script, cwd, interpreter, appArgs, watchRaw) {
|
|
|
684
740
|
}
|
|
685
741
|
if (process.platform === "win32" || process.platform === "darwin") {
|
|
686
742
|
try {
|
|
687
|
-
const w =
|
|
743
|
+
const w = fs4.watch(target, { recursive: true }, () => scheduleRestart(target));
|
|
688
744
|
watchers.push(w);
|
|
689
745
|
} catch (err) {
|
|
690
746
|
console.error(`dev: cannot watch ${target}: ${err.message}`);
|
|
@@ -693,7 +749,7 @@ async function runManualWatch(script, cwd, interpreter, appArgs, watchRaw) {
|
|
|
693
749
|
const dirs = collectSubDirs(target);
|
|
694
750
|
for (const dir of dirs) {
|
|
695
751
|
try {
|
|
696
|
-
const w =
|
|
752
|
+
const w = fs4.watch(dir, () => scheduleRestart(dir));
|
|
697
753
|
watchers.push(w);
|
|
698
754
|
} catch {
|
|
699
755
|
}
|
|
@@ -788,8 +844,32 @@ Examples:
|
|
|
788
844
|
}
|
|
789
845
|
|
|
790
846
|
// src/commands/doctor.ts
|
|
791
|
-
|
|
792
|
-
|
|
847
|
+
import fs5 from "node:fs";
|
|
848
|
+
import os2 from "node:os";
|
|
849
|
+
import path5 from "node:path";
|
|
850
|
+
async function run4(args) {
|
|
851
|
+
const fix = args.includes("--fix") || args.includes("-f");
|
|
852
|
+
if (fix) {
|
|
853
|
+
const healed = healLocally();
|
|
854
|
+
if (healed.length > 0) {
|
|
855
|
+
for (const m of healed) console.log(`healed: ${m}`);
|
|
856
|
+
} else {
|
|
857
|
+
console.log("heal: nothing to fix locally");
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
let client;
|
|
861
|
+
try {
|
|
862
|
+
client = await ensureDaemonClient();
|
|
863
|
+
} catch (err) {
|
|
864
|
+
if (fix) {
|
|
865
|
+
console.error(`doctor: daemon still not reachable after heal: ${err.message}`);
|
|
866
|
+
console.error("try: rm -rf ~/.relife2/state/* && relife2 kill; relife2 list");
|
|
867
|
+
} else {
|
|
868
|
+
console.error(err.message);
|
|
869
|
+
console.error("hint: relife2 doctor --fix (cleans stale lock + *.tmp.cjs)");
|
|
870
|
+
}
|
|
871
|
+
return 1;
|
|
872
|
+
}
|
|
793
873
|
try {
|
|
794
874
|
const report = await client.call("doctor", {});
|
|
795
875
|
let hadErrors = false;
|
|
@@ -806,21 +886,143 @@ async function run4(_args) {
|
|
|
806
886
|
console.log(
|
|
807
887
|
`checks: ${report.summary.total} total, ${report.summary.ok} ok, ${report.summary.warnings} warnings, ${report.summary.errors} errors`
|
|
808
888
|
);
|
|
889
|
+
if (hadErrors && !fix) {
|
|
890
|
+
console.log("hint: relife2 doctor --fix (cleans stale lock + *.tmp.cjs)");
|
|
891
|
+
}
|
|
809
892
|
return hadErrors ? 1 : 0;
|
|
810
893
|
} finally {
|
|
811
894
|
client.close();
|
|
812
895
|
}
|
|
813
896
|
}
|
|
897
|
+
function healLocally() {
|
|
898
|
+
const out = [];
|
|
899
|
+
const runtime = runtimeDir();
|
|
900
|
+
const data = dataDir();
|
|
901
|
+
for (const dir of [runtime, data, path5.join(data, "logs"), path5.join(data, "state")]) {
|
|
902
|
+
try {
|
|
903
|
+
fs5.mkdirSync(dir, { recursive: true });
|
|
904
|
+
} catch {
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
const lockFile = path5.join(runtime, "daemon.lock");
|
|
908
|
+
try {
|
|
909
|
+
const raw = fs5.readFileSync(lockFile, "utf8");
|
|
910
|
+
const parsed = JSON.parse(raw);
|
|
911
|
+
const pid = typeof parsed.pid === "number" ? parsed.pid : null;
|
|
912
|
+
let alive = false;
|
|
913
|
+
if (pid !== null) {
|
|
914
|
+
try {
|
|
915
|
+
process.kill(pid, 0);
|
|
916
|
+
alive = true;
|
|
917
|
+
} catch (e) {
|
|
918
|
+
const code = e.code;
|
|
919
|
+
if (code === "EPERM") alive = true;
|
|
920
|
+
else if (code === "ESRCH") alive = false;
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
if (pid === null || !alive) {
|
|
924
|
+
fs5.unlinkSync(lockFile);
|
|
925
|
+
out.push(`removed stale lock ${lockFile} (pid=${pid})`);
|
|
926
|
+
}
|
|
927
|
+
} catch {
|
|
928
|
+
}
|
|
929
|
+
const home = os2.homedir();
|
|
930
|
+
if (home && fs5.existsSync(home)) {
|
|
931
|
+
try {
|
|
932
|
+
const tmpLeftovers = collectTmpLeftovers(home, 6, 50);
|
|
933
|
+
for (const f of tmpLeftovers) {
|
|
934
|
+
try {
|
|
935
|
+
fs5.unlinkSync(f);
|
|
936
|
+
out.push(`removed leftover ${path5.relative(home, f)}`);
|
|
937
|
+
} catch {
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
} catch {
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
try {
|
|
944
|
+
const tmpDir = os2.tmpdir();
|
|
945
|
+
const entries = fs5.readdirSync(tmpDir);
|
|
946
|
+
const now = Date.now();
|
|
947
|
+
for (const name of entries) {
|
|
948
|
+
if (!name.startsWith("relife2-") || !name.endsWith(".tmp.cjs")) continue;
|
|
949
|
+
const full = path5.join(tmpDir, name);
|
|
950
|
+
try {
|
|
951
|
+
const st = fs5.statSync(full);
|
|
952
|
+
if (now - st.mtimeMs > 60 * 60 * 1e3) {
|
|
953
|
+
fs5.unlinkSync(full);
|
|
954
|
+
out.push(`removed old tmp ${name}`);
|
|
955
|
+
}
|
|
956
|
+
} catch {
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
} catch {
|
|
960
|
+
}
|
|
961
|
+
return out;
|
|
962
|
+
}
|
|
963
|
+
function collectTmpLeftovers(root, maxDepth, limit) {
|
|
964
|
+
const out = [];
|
|
965
|
+
const stack = [{ dir: root, depth: 0 }];
|
|
966
|
+
const ignored = /* @__PURE__ */ new Set([
|
|
967
|
+
"node_modules",
|
|
968
|
+
".git",
|
|
969
|
+
".hg",
|
|
970
|
+
".svn",
|
|
971
|
+
"vendor",
|
|
972
|
+
".next",
|
|
973
|
+
".output",
|
|
974
|
+
"dist",
|
|
975
|
+
"build",
|
|
976
|
+
"target",
|
|
977
|
+
"__pycache__",
|
|
978
|
+
".cache",
|
|
979
|
+
".pnpm",
|
|
980
|
+
".yarn",
|
|
981
|
+
".turbo",
|
|
982
|
+
".parcel-cache",
|
|
983
|
+
"Library",
|
|
984
|
+
"AppData",
|
|
985
|
+
".local",
|
|
986
|
+
"proc",
|
|
987
|
+
"sys",
|
|
988
|
+
"dev"
|
|
989
|
+
]);
|
|
990
|
+
while (stack.length > 0 && out.length < limit) {
|
|
991
|
+
const cur = stack.pop();
|
|
992
|
+
if (!cur) break;
|
|
993
|
+
if (cur.depth > maxDepth) continue;
|
|
994
|
+
let entries;
|
|
995
|
+
try {
|
|
996
|
+
entries = fs5.readdirSync(cur.dir, { withFileTypes: true });
|
|
997
|
+
} catch {
|
|
998
|
+
continue;
|
|
999
|
+
}
|
|
1000
|
+
for (const ent of entries) {
|
|
1001
|
+
if (out.length >= limit) break;
|
|
1002
|
+
const full = path5.join(cur.dir, ent.name);
|
|
1003
|
+
if (ent.isFile() && ent.name.includes(".tmp.") && ent.name.endsWith(".cjs")) {
|
|
1004
|
+
if (ent.name.includes(".config.")) out.push(full);
|
|
1005
|
+
continue;
|
|
1006
|
+
}
|
|
1007
|
+
if (!ent.isDirectory()) continue;
|
|
1008
|
+
if (ignored.has(ent.name)) continue;
|
|
1009
|
+
if (ent.name.startsWith(".") && cur.depth < 2) continue;
|
|
1010
|
+
if (ent.isSymbolicLink()) continue;
|
|
1011
|
+
if (cur.depth + 1 <= maxDepth) stack.push({ dir: full, depth: cur.depth + 1 });
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
return out;
|
|
1015
|
+
}
|
|
814
1016
|
|
|
815
1017
|
// src/commands/find.ts
|
|
816
|
-
import * as
|
|
817
|
-
import
|
|
818
|
-
import
|
|
1018
|
+
import * as fs9 from "node:fs";
|
|
1019
|
+
import os5 from "node:os";
|
|
1020
|
+
import path9 from "node:path";
|
|
819
1021
|
|
|
820
1022
|
// src/config/loader.ts
|
|
821
|
-
import * as
|
|
822
|
-
import
|
|
823
|
-
import
|
|
1023
|
+
import * as fs8 from "node:fs";
|
|
1024
|
+
import os4 from "node:os";
|
|
1025
|
+
import path8 from "node:path";
|
|
824
1026
|
|
|
825
1027
|
// src/util/cron.ts
|
|
826
1028
|
var FIELD_RANGES = [
|
|
@@ -939,19 +1141,19 @@ function parseMemory(v) {
|
|
|
939
1141
|
}
|
|
940
1142
|
|
|
941
1143
|
// src/util/portable.ts
|
|
942
|
-
import * as
|
|
943
|
-
import
|
|
944
|
-
import
|
|
1144
|
+
import * as fs6 from "node:fs";
|
|
1145
|
+
import os3 from "node:os";
|
|
1146
|
+
import path6 from "node:path";
|
|
945
1147
|
function isDir(p) {
|
|
946
1148
|
try {
|
|
947
|
-
return
|
|
1149
|
+
return fs6.statSync(p).isDirectory();
|
|
948
1150
|
} catch {
|
|
949
1151
|
return false;
|
|
950
1152
|
}
|
|
951
1153
|
}
|
|
952
1154
|
function isFile(p) {
|
|
953
1155
|
try {
|
|
954
|
-
return
|
|
1156
|
+
return fs6.statSync(p).isFile();
|
|
955
1157
|
} catch {
|
|
956
1158
|
return false;
|
|
957
1159
|
}
|
|
@@ -973,18 +1175,18 @@ function searchByBasename(root, basename, maxDepth = 4) {
|
|
|
973
1175
|
if (cur.depth > maxDepth) continue;
|
|
974
1176
|
let entries;
|
|
975
1177
|
try {
|
|
976
|
-
entries =
|
|
1178
|
+
entries = fs6.readdirSync(cur.dir, { withFileTypes: true });
|
|
977
1179
|
} catch {
|
|
978
1180
|
continue;
|
|
979
1181
|
}
|
|
980
1182
|
for (const e of entries) {
|
|
981
|
-
const full =
|
|
1183
|
+
const full = path6.join(cur.dir, e.name);
|
|
982
1184
|
if (e.isFile() && e.name === basename) return full;
|
|
983
1185
|
if (e.isDirectory() && !e.isSymbolicLink()) {
|
|
984
1186
|
if (e.name === "node_modules" || e.name === ".git") continue;
|
|
985
1187
|
const real = (() => {
|
|
986
1188
|
try {
|
|
987
|
-
return
|
|
1189
|
+
return fs6.realpathSync(full);
|
|
988
1190
|
} catch {
|
|
989
1191
|
return full;
|
|
990
1192
|
}
|
|
@@ -1002,20 +1204,20 @@ function fixCwd(staleCwd, sourceDir) {
|
|
|
1002
1204
|
const candidates = [];
|
|
1003
1205
|
const tail = stripHomePrefix(staleCwd);
|
|
1004
1206
|
if (tail !== null) {
|
|
1005
|
-
candidates.push(
|
|
1006
|
-
candidates.push(
|
|
1007
|
-
candidates.push(
|
|
1008
|
-
if (isDir(sourceDir) && !staleCwd.startsWith(
|
|
1207
|
+
candidates.push(path6.join(os3.homedir(), tail));
|
|
1208
|
+
candidates.push(path6.join(path6.dirname(os3.homedir()), tail));
|
|
1209
|
+
candidates.push(path6.join(path6.dirname(sourceDir), tail));
|
|
1210
|
+
if (isDir(sourceDir) && !staleCwd.startsWith(os3.homedir())) candidates.push(sourceDir);
|
|
1009
1211
|
}
|
|
1010
|
-
const proj =
|
|
1212
|
+
const proj = path6.basename(sourceDir);
|
|
1011
1213
|
if (proj) {
|
|
1012
|
-
const marker = `${
|
|
1214
|
+
const marker = `${path6.sep}${proj}${path6.sep}`;
|
|
1013
1215
|
const altMarker = `/${proj}/`;
|
|
1014
1216
|
const idx = staleCwd.includes(marker) ? staleCwd.lastIndexOf(marker) : staleCwd.lastIndexOf(altMarker);
|
|
1015
1217
|
if (idx >= 0) {
|
|
1016
1218
|
const suffix = staleCwd.slice(idx + proj.length + 2);
|
|
1017
|
-
candidates.push(
|
|
1018
|
-
candidates.push(
|
|
1219
|
+
candidates.push(path6.join(sourceDir, suffix));
|
|
1220
|
+
candidates.push(path6.join(path6.dirname(sourceDir), proj, suffix));
|
|
1019
1221
|
}
|
|
1020
1222
|
}
|
|
1021
1223
|
for (const c of candidates) {
|
|
@@ -1030,33 +1232,33 @@ function fixCwd(staleCwd, sourceDir) {
|
|
|
1030
1232
|
}
|
|
1031
1233
|
function fixScript(staleScript, cwd, sourceDir) {
|
|
1032
1234
|
if (isFile(staleScript)) return { path: staleScript, fixed: false };
|
|
1033
|
-
const base =
|
|
1235
|
+
const base = path6.basename(staleScript);
|
|
1034
1236
|
const candidates = [];
|
|
1035
|
-
candidates.push(
|
|
1036
|
-
candidates.push(
|
|
1237
|
+
candidates.push(path6.join(cwd, base));
|
|
1238
|
+
candidates.push(path6.join(sourceDir, base));
|
|
1037
1239
|
const tail = stripHomePrefix(staleScript);
|
|
1038
1240
|
if (tail !== null) {
|
|
1039
|
-
candidates.push(
|
|
1040
|
-
candidates.push(
|
|
1041
|
-
candidates.push(
|
|
1241
|
+
candidates.push(path6.join(os3.homedir(), tail));
|
|
1242
|
+
candidates.push(path6.join(path6.dirname(os3.homedir()), tail));
|
|
1243
|
+
candidates.push(path6.join(sourceDir, tail));
|
|
1042
1244
|
const tailParts = tail.split("/");
|
|
1043
|
-
if (tailParts.length > 1) candidates.push(
|
|
1245
|
+
if (tailParts.length > 1) candidates.push(path6.join(sourceDir, tailParts.slice(1).join("/")));
|
|
1044
1246
|
}
|
|
1045
|
-
const proj =
|
|
1247
|
+
const proj = path6.basename(sourceDir);
|
|
1046
1248
|
if (proj) {
|
|
1047
1249
|
const marker = `/${proj}/`;
|
|
1048
1250
|
const idx = staleScript.lastIndexOf(marker);
|
|
1049
1251
|
if (idx >= 0) {
|
|
1050
1252
|
const suffix = staleScript.slice(idx + proj.length + 2);
|
|
1051
|
-
candidates.push(
|
|
1052
|
-
candidates.push(
|
|
1253
|
+
candidates.push(path6.join(sourceDir, suffix));
|
|
1254
|
+
candidates.push(path6.join(cwd, suffix));
|
|
1053
1255
|
}
|
|
1054
1256
|
}
|
|
1055
1257
|
const parts = staleScript.split(/[/\\]/).filter(Boolean);
|
|
1056
1258
|
if (parts.length >= 2) {
|
|
1057
|
-
const lastTwo = parts.slice(-2).join(
|
|
1058
|
-
candidates.push(
|
|
1059
|
-
candidates.push(
|
|
1259
|
+
const lastTwo = parts.slice(-2).join(path6.sep);
|
|
1260
|
+
candidates.push(path6.join(sourceDir, lastTwo));
|
|
1261
|
+
candidates.push(path6.join(cwd, lastTwo));
|
|
1060
1262
|
}
|
|
1061
1263
|
for (const c of candidates) {
|
|
1062
1264
|
if (c && isFile(c))
|
|
@@ -1136,9 +1338,17 @@ var LATER_KEYS = {
|
|
|
1136
1338
|
uid: "M6",
|
|
1137
1339
|
gid: "M6"
|
|
1138
1340
|
};
|
|
1139
|
-
var
|
|
1341
|
+
var RELIFE2_CONFIG_RE = /^(?:ecosystem|pm2|relife2)[\w.-]*\.config\.(?:c?js|mjs|json|ts)$/i;
|
|
1342
|
+
var ANY_CONFIG_RE = /^[\w.-]*\.config\.(?:c?js|mjs|json|ts)$/i;
|
|
1140
1343
|
function looksLikeConfigFile(file) {
|
|
1141
|
-
|
|
1344
|
+
const base = path8.basename(file);
|
|
1345
|
+
if (base.includes(".tmp.")) return false;
|
|
1346
|
+
return ANY_CONFIG_RE.test(base);
|
|
1347
|
+
}
|
|
1348
|
+
function isRelife2ConfigFile(file) {
|
|
1349
|
+
const base = path8.basename(file);
|
|
1350
|
+
if (base.includes(".tmp.")) return false;
|
|
1351
|
+
return RELIFE2_CONFIG_RE.test(base);
|
|
1142
1352
|
}
|
|
1143
1353
|
function findDefaultConfig(cwd) {
|
|
1144
1354
|
const candidates = [
|
|
@@ -1155,16 +1365,16 @@ function findDefaultConfig(cwd) {
|
|
|
1155
1365
|
"pm2.config.cjs"
|
|
1156
1366
|
];
|
|
1157
1367
|
for (const name of candidates) {
|
|
1158
|
-
const full =
|
|
1159
|
-
if (
|
|
1368
|
+
const full = path8.join(cwd, name);
|
|
1369
|
+
if (fs8.existsSync(full)) return full;
|
|
1160
1370
|
}
|
|
1161
1371
|
return null;
|
|
1162
1372
|
}
|
|
1163
1373
|
async function loadConfigFile(filePath, opts) {
|
|
1164
|
-
const ext =
|
|
1374
|
+
const ext = path8.extname(filePath).toLowerCase();
|
|
1165
1375
|
let exported;
|
|
1166
1376
|
if (ext === ".json") {
|
|
1167
|
-
exported = JSON.parse(
|
|
1377
|
+
exported = JSON.parse(fs8.readFileSync(filePath, "utf8"));
|
|
1168
1378
|
} else if (ext === ".ts") {
|
|
1169
1379
|
exported = await loadTsFile(filePath);
|
|
1170
1380
|
} else {
|
|
@@ -1174,7 +1384,7 @@ async function loadConfigFile(filePath, opts) {
|
|
|
1174
1384
|
}
|
|
1175
1385
|
if (exported instanceof Promise) exported = await exported;
|
|
1176
1386
|
}
|
|
1177
|
-
return normalizeExported(exported,
|
|
1387
|
+
return normalizeExported(exported, path8.dirname(filePath), opts);
|
|
1178
1388
|
}
|
|
1179
1389
|
async function loadTsFile(file) {
|
|
1180
1390
|
try {
|
|
@@ -1183,16 +1393,19 @@ async function loadTsFile(file) {
|
|
|
1183
1393
|
const mod = await import(url);
|
|
1184
1394
|
return mod.default ?? mod;
|
|
1185
1395
|
} catch {
|
|
1186
|
-
const raw =
|
|
1396
|
+
const raw = fs8.readFileSync(file, "utf8");
|
|
1187
1397
|
const stripped = raw.replace(/^\s*import\s+type\s+.*$/gm, "").replace(/:\s*[\w<>[\]|&\s,?]+(?=[=;,\n)}])/g, "").replace(/\s+as\s+const/g, "");
|
|
1188
|
-
const tmp =
|
|
1398
|
+
const tmp = path8.join(
|
|
1399
|
+
os4.tmpdir(),
|
|
1400
|
+
`relife2-${path8.basename(file).replace(/[^a-zA-Z0-9._-]/g, "_")}-${Date.now()}-${Math.random().toString(36).slice(2, 6)}.tmp.cjs`
|
|
1401
|
+
);
|
|
1189
1402
|
try {
|
|
1190
|
-
|
|
1403
|
+
fs8.writeFileSync(tmp, stripped, "utf8");
|
|
1191
1404
|
const { loadJsFile: lf } = await Promise.resolve().then(() => (init_jsloader(), jsloader_exports));
|
|
1192
1405
|
return await lf(tmp);
|
|
1193
1406
|
} finally {
|
|
1194
1407
|
try {
|
|
1195
|
-
|
|
1408
|
+
fs8.unlinkSync(tmp);
|
|
1196
1409
|
} catch {
|
|
1197
1410
|
}
|
|
1198
1411
|
}
|
|
@@ -1248,20 +1461,20 @@ function normalizeApp(raw, sourceDir, warnings, index, envName) {
|
|
|
1248
1461
|
const instances = parseInstances(input.instances, index);
|
|
1249
1462
|
const execMode = parseExecMode(input.exec_mode, index);
|
|
1250
1463
|
const rawCwd = typeof input.cwd === "string" ? input.cwd : void 0;
|
|
1251
|
-
const cwdRaw =
|
|
1464
|
+
const cwdRaw = path8.resolve(sourceDir, rawCwd ?? ".");
|
|
1252
1465
|
const cwdFix = fixCwd(cwdRaw, sourceDir);
|
|
1253
1466
|
const cwd = cwdFix.path;
|
|
1254
1467
|
if (cwdFix.fixed && cwdFix.reason) warnings.push(cwdFix.reason);
|
|
1255
1468
|
const hasSep = scriptRaw.includes("/") || scriptRaw.includes("\\");
|
|
1256
1469
|
const isJsExt = /\.(?:c?js|mjs|ts|mts|cts)$/i.test(scriptRaw);
|
|
1257
|
-
const scriptRawResolved = hasSep || isJsExt ?
|
|
1470
|
+
const scriptRawResolved = hasSep || isJsExt ? path8.resolve(cwd, scriptRaw) : scriptRaw;
|
|
1258
1471
|
let script = scriptRawResolved;
|
|
1259
|
-
if (
|
|
1472
|
+
if (path8.isAbsolute(scriptRawResolved)) {
|
|
1260
1473
|
const f = fixScript(scriptRawResolved, cwd, sourceDir);
|
|
1261
1474
|
if (f.fixed) {
|
|
1262
1475
|
warnings.push(f.reason ?? `script '${scriptRawResolved}' \u2192 '${f.path}'`);
|
|
1263
1476
|
script = f.path;
|
|
1264
|
-
} else if (!
|
|
1477
|
+
} else if (!fs8.existsSync(scriptRawResolved) && f.reason) {
|
|
1265
1478
|
warnings.push(f.reason);
|
|
1266
1479
|
}
|
|
1267
1480
|
}
|
|
@@ -1278,11 +1491,11 @@ function normalizeApp(raw, sourceDir, warnings, index, envName) {
|
|
|
1278
1491
|
}
|
|
1279
1492
|
const envFile = typeof input.env_file === "string" && input.env_file !== "" ? input.env_file : void 0;
|
|
1280
1493
|
if (envFile !== void 0) {
|
|
1281
|
-
const envPath =
|
|
1494
|
+
const envPath = path8.resolve(cwd, envFile);
|
|
1282
1495
|
let parsed = null;
|
|
1283
|
-
if (
|
|
1496
|
+
if (fs8.existsSync(envPath) && fs8.statSync(envPath).isFile()) {
|
|
1284
1497
|
try {
|
|
1285
|
-
parsed = parseDotenv(
|
|
1498
|
+
parsed = parseDotenv(fs8.readFileSync(envPath, "utf8"));
|
|
1286
1499
|
} catch (err) {
|
|
1287
1500
|
warnings.push(
|
|
1288
1501
|
`apps[${index}] env_file '${envFile}' could not be read: ${err.message}`
|
|
@@ -1405,7 +1618,7 @@ function parseWatchField(v, field, index) {
|
|
|
1405
1618
|
}
|
|
1406
1619
|
function parseInstances(v, index) {
|
|
1407
1620
|
if (v === void 0 || v === null) return 1;
|
|
1408
|
-
if (v === "max" || v === "MAX") return
|
|
1621
|
+
if (v === "max" || v === "MAX") return os4.cpus().length || 1;
|
|
1409
1622
|
const n = Number(v);
|
|
1410
1623
|
if (!Number.isFinite(n) || n < 1) {
|
|
1411
1624
|
throw new ConfigError(`apps[${index}] 'instances' must be a positive number or 'max'`);
|
|
@@ -1420,7 +1633,7 @@ function parseExecMode(v, index) {
|
|
|
1420
1633
|
throw new ConfigError(`apps[${index}] 'exec_mode' must be 'fork' or 'cluster'`);
|
|
1421
1634
|
}
|
|
1422
1635
|
function defaultName(script) {
|
|
1423
|
-
const base =
|
|
1636
|
+
const base = path8.basename(script);
|
|
1424
1637
|
return base.replace(/\.(?:c?js|mjs|ts|mts|cts)$/i, "") || base;
|
|
1425
1638
|
}
|
|
1426
1639
|
function normalizeSingleApp(input, sourceDir, envName) {
|
|
@@ -1468,7 +1681,7 @@ function findConfigs(root, maxDepth) {
|
|
|
1468
1681
|
if (depth > maxDepth) continue;
|
|
1469
1682
|
let real;
|
|
1470
1683
|
try {
|
|
1471
|
-
real =
|
|
1684
|
+
real = fs9.realpathSync(dir);
|
|
1472
1685
|
} catch {
|
|
1473
1686
|
continue;
|
|
1474
1687
|
}
|
|
@@ -1476,13 +1689,13 @@ function findConfigs(root, maxDepth) {
|
|
|
1476
1689
|
visited.add(real);
|
|
1477
1690
|
let entries;
|
|
1478
1691
|
try {
|
|
1479
|
-
entries =
|
|
1692
|
+
entries = fs9.readdirSync(dir, { withFileTypes: true });
|
|
1480
1693
|
} catch {
|
|
1481
1694
|
continue;
|
|
1482
1695
|
}
|
|
1483
1696
|
for (const ent of entries) {
|
|
1484
|
-
const full =
|
|
1485
|
-
if (ent.isFile() &&
|
|
1697
|
+
const full = path9.join(dir, ent.name);
|
|
1698
|
+
if (ent.isFile() && isRelife2ConfigFile(ent.name)) {
|
|
1486
1699
|
out.push(full);
|
|
1487
1700
|
continue;
|
|
1488
1701
|
}
|
|
@@ -1511,8 +1724,8 @@ async function enrich(found) {
|
|
|
1511
1724
|
return entries;
|
|
1512
1725
|
}
|
|
1513
1726
|
function relToRoot(p, root) {
|
|
1514
|
-
const rel =
|
|
1515
|
-
return rel === "" ?
|
|
1727
|
+
const rel = path9.relative(root, p);
|
|
1728
|
+
return rel === "" ? path9.basename(p) : rel;
|
|
1516
1729
|
}
|
|
1517
1730
|
async function run5(args) {
|
|
1518
1731
|
const { positionals, opts } = parseCliArgs(args, VALUE_OPTS);
|
|
@@ -1522,12 +1735,12 @@ async function run5(args) {
|
|
|
1522
1735
|
let root = opts.get("root");
|
|
1523
1736
|
if (root === void 0) {
|
|
1524
1737
|
if (allFlag) {
|
|
1525
|
-
root = process.platform === "win32" ?
|
|
1738
|
+
root = process.platform === "win32" ? path9.parse(process.cwd()).root : "/";
|
|
1526
1739
|
} else {
|
|
1527
|
-
root =
|
|
1740
|
+
root = os5.homedir() || process.cwd();
|
|
1528
1741
|
}
|
|
1529
1742
|
}
|
|
1530
|
-
root =
|
|
1743
|
+
root = path9.resolve(root);
|
|
1531
1744
|
let depth = 6;
|
|
1532
1745
|
const depthRaw = opts.get("depth");
|
|
1533
1746
|
if (depthRaw !== void 0) {
|
|
@@ -1539,7 +1752,7 @@ async function run5(args) {
|
|
|
1539
1752
|
depth = Math.round(n);
|
|
1540
1753
|
}
|
|
1541
1754
|
if (allFlag) depth = Math.max(depth, 8);
|
|
1542
|
-
if (!
|
|
1755
|
+
if (!fs9.existsSync(root) || !fs9.statSync(root).isDirectory()) {
|
|
1543
1756
|
console.error(`find: root '${root}' does not exist or is not a directory`);
|
|
1544
1757
|
return 1;
|
|
1545
1758
|
}
|
|
@@ -1630,10 +1843,10 @@ Starting ${toStart.length} config(s) not yet running\u2026
|
|
|
1630
1843
|
const res = await client.call("start", {
|
|
1631
1844
|
target: { type: "config", path: r.path }
|
|
1632
1845
|
});
|
|
1633
|
-
for (const w of res.warnings ?? []) console.error(`warning ${
|
|
1846
|
+
for (const w of res.warnings ?? []) console.error(`warning ${path9.basename(r.path)}: ${w}`);
|
|
1634
1847
|
for (const a of res.apps) {
|
|
1635
1848
|
if (a.status === "errored") {
|
|
1636
|
-
console.error(`x ${a.name} (${
|
|
1849
|
+
console.error(`x ${a.name} (${path9.basename(r.path)}): ${a.message ?? "failed"}`);
|
|
1637
1850
|
failed++;
|
|
1638
1851
|
} else {
|
|
1639
1852
|
console.log(
|
|
@@ -1643,7 +1856,7 @@ Starting ${toStart.length} config(s) not yet running\u2026
|
|
|
1643
1856
|
}
|
|
1644
1857
|
}
|
|
1645
1858
|
} catch (err) {
|
|
1646
|
-
console.error(`x ${
|
|
1859
|
+
console.error(`x ${path9.basename(r.path)}: ${err.message}`);
|
|
1647
1860
|
failed++;
|
|
1648
1861
|
}
|
|
1649
1862
|
}
|
|
@@ -1677,9 +1890,9 @@ async function run6(args) {
|
|
|
1677
1890
|
}
|
|
1678
1891
|
|
|
1679
1892
|
// src/commands/fromCaddy.ts
|
|
1680
|
-
import * as
|
|
1681
|
-
import
|
|
1682
|
-
import
|
|
1893
|
+
import * as fs10 from "node:fs";
|
|
1894
|
+
import os6 from "node:os";
|
|
1895
|
+
import path10 from "node:path";
|
|
1683
1896
|
var LOOPBACK_RE = /(?:127\.0\.0\.1|localhost|::1):(\d{2,5})/g;
|
|
1684
1897
|
var ENTRY_PATTERNS = [
|
|
1685
1898
|
".output/server/index.mjs",
|
|
@@ -1852,37 +2065,37 @@ function parseCaddyfile(content) {
|
|
|
1852
2065
|
}
|
|
1853
2066
|
function findEntry(dir) {
|
|
1854
2067
|
for (const pat of ENTRY_PATTERNS) {
|
|
1855
|
-
const p =
|
|
1856
|
-
if (
|
|
2068
|
+
const p = path10.join(dir, pat);
|
|
2069
|
+
if (fs10.existsSync(p)) return { script: pat, exists: true };
|
|
1857
2070
|
}
|
|
1858
2071
|
return null;
|
|
1859
2072
|
}
|
|
1860
2073
|
function guessDir(site, _port, hint, caddyfileDir) {
|
|
1861
2074
|
const warnings = [];
|
|
1862
2075
|
if (hint?.dir) {
|
|
1863
|
-
const d =
|
|
2076
|
+
const d = path10.isAbsolute(hint.dir) ? hint.dir : path10.resolve(caddyfileDir, hint.dir);
|
|
1864
2077
|
return { dir: d, warnings };
|
|
1865
2078
|
}
|
|
1866
2079
|
const searchRoots = [];
|
|
1867
|
-
const homedir =
|
|
2080
|
+
const homedir = os6.homedir();
|
|
1868
2081
|
if (homedir) searchRoots.push(homedir);
|
|
1869
2082
|
if (caddyfileDir) searchRoots.push(caddyfileDir);
|
|
1870
|
-
if (caddyfileDir) searchRoots.push(
|
|
2083
|
+
if (caddyfileDir) searchRoots.push(path10.resolve(caddyfileDir, ".."));
|
|
1871
2084
|
const uniqRoots = [...new Set(searchRoots)].filter(Boolean);
|
|
1872
2085
|
const siteName = sanitizeName(site);
|
|
1873
2086
|
const candidates = [];
|
|
1874
|
-
if (homedir) candidates.push(
|
|
1875
|
-
if (homedir) candidates.push(
|
|
1876
|
-
for (const r of uniqRoots) candidates.push(
|
|
2087
|
+
if (homedir) candidates.push(path10.join(homedir, siteName));
|
|
2088
|
+
if (homedir) candidates.push(path10.join(homedir, siteName.split("-")[0]));
|
|
2089
|
+
for (const r of uniqRoots) candidates.push(path10.join(r, siteName));
|
|
1877
2090
|
const rawDir = site.split(".")[0].replace(/[^a-zA-Z0-9_-]/g, "");
|
|
1878
|
-
if (rawDir && homedir) candidates.push(
|
|
2091
|
+
if (rawDir && homedir) candidates.push(path10.join(homedir, rawDir));
|
|
1879
2092
|
for (const cand of candidates) {
|
|
1880
|
-
if (
|
|
2093
|
+
if (fs10.existsSync(cand) && fs10.statSync(cand).isDirectory()) {
|
|
1881
2094
|
const e = findEntry(cand);
|
|
1882
2095
|
if (e) return { dir: cand, warnings };
|
|
1883
2096
|
}
|
|
1884
2097
|
}
|
|
1885
|
-
const fallback = homedir ?
|
|
2098
|
+
const fallback = homedir ? path10.join(homedir, siteName) : path10.join(caddyfileDir || process.cwd(), siteName);
|
|
1886
2099
|
warnings.push(
|
|
1887
2100
|
`no existing dir found for ${site} \u2192 using fallback ${fallback} (create or set via '# relife2: dir=...')`
|
|
1888
2101
|
);
|
|
@@ -1914,15 +2127,15 @@ async function run7(args) {
|
|
|
1914
2127
|
caddyfilePath ? null : "./Caddyfile",
|
|
1915
2128
|
caddyfilePath ? null : "Caddyfile",
|
|
1916
2129
|
caddyfilePath ? null : "/etc/caddy/Caddyfile",
|
|
1917
|
-
caddyfilePath ? null :
|
|
2130
|
+
caddyfilePath ? null : path10.join(os6.homedir(), "Caddyfile")
|
|
1918
2131
|
].filter(Boolean);
|
|
1919
2132
|
let resolvedCaddy = null;
|
|
1920
2133
|
let content = null;
|
|
1921
2134
|
for (const cand of candidates) {
|
|
1922
|
-
const p =
|
|
1923
|
-
if (
|
|
2135
|
+
const p = path10.resolve(cand);
|
|
2136
|
+
if (fs10.existsSync(p)) {
|
|
1924
2137
|
resolvedCaddy = p;
|
|
1925
|
-
content =
|
|
2138
|
+
content = fs10.readFileSync(p, "utf8");
|
|
1926
2139
|
break;
|
|
1927
2140
|
}
|
|
1928
2141
|
}
|
|
@@ -1933,7 +2146,7 @@ async function run7(args) {
|
|
|
1933
2146
|
);
|
|
1934
2147
|
return 1;
|
|
1935
2148
|
}
|
|
1936
|
-
const caddyfileDir =
|
|
2149
|
+
const caddyfileDir = path10.dirname(resolvedCaddy);
|
|
1937
2150
|
const { sites } = parseCaddyfile(content);
|
|
1938
2151
|
if (sites.length === 0) {
|
|
1939
2152
|
console.error(`from-caddy: no sites found in ${resolvedCaddy}`);
|
|
@@ -1981,7 +2194,7 @@ async function run7(args) {
|
|
|
1981
2194
|
let relScript = "";
|
|
1982
2195
|
if (entry) {
|
|
1983
2196
|
relScript = entry;
|
|
1984
|
-
exists =
|
|
2197
|
+
exists = fs10.existsSync(path10.isAbsolute(entry) ? entry : path10.join(dir, entry));
|
|
1985
2198
|
if (!exists) warnings.push(`hint script '${entry}' for ${name} not found under ${dir}`);
|
|
1986
2199
|
} else {
|
|
1987
2200
|
const found = findEntry(dir);
|
|
@@ -1999,7 +2212,7 @@ async function run7(args) {
|
|
|
1999
2212
|
let interpreter = site.hint?.interpreter;
|
|
2000
2213
|
if (!interpreter) {
|
|
2001
2214
|
if (relScript.endsWith(".mjs") || relScript.endsWith(".ts")) {
|
|
2002
|
-
const hasBun2 =
|
|
2215
|
+
const hasBun2 = fs10.existsSync(path10.join(dir, "bun.lockb")) || fs10.existsSync(path10.join(dir, "bun.lock"));
|
|
2003
2216
|
if (hasBun2) interpreter = "bun";
|
|
2004
2217
|
}
|
|
2005
2218
|
}
|
|
@@ -2080,7 +2293,7 @@ Would write ${planned.length} app(s) to ${outPath ?? "relife2.config.cjs (dry-ru
|
|
|
2080
2293
|
console.log(`Run without --dry-run to generate.`);
|
|
2081
2294
|
return 0;
|
|
2082
2295
|
}
|
|
2083
|
-
const resolvedOut =
|
|
2296
|
+
const resolvedOut = path10.resolve(outPath ?? "relife2.config.cjs");
|
|
2084
2297
|
const appsCode = planned.map((p) => {
|
|
2085
2298
|
const interp = p._interpreter;
|
|
2086
2299
|
const isCaddy = p.name === "caddy";
|
|
@@ -2100,8 +2313,8 @@ Would write ${planned.length} app(s) to ${outPath ?? "relife2.config.cjs (dry-ru
|
|
|
2100
2313
|
lines.push(` cwd: '${p.dir.replace(/'/g, "\\'")}',`);
|
|
2101
2314
|
lines.push(` interpreter: 'none',`);
|
|
2102
2315
|
} else {
|
|
2103
|
-
const
|
|
2104
|
-
const
|
|
2316
|
+
const _scriptEsc = p.script.replace(/'/g, "\\'");
|
|
2317
|
+
const _dirEsc = p.dir.replace(/'/g, "\\'");
|
|
2105
2318
|
lines.push(` script: ${JSON.stringify(p.script)},`);
|
|
2106
2319
|
lines.push(` cwd: ${JSON.stringify(p.dir)},`);
|
|
2107
2320
|
if (interp) lines.push(` interpreter: ${JSON.stringify(interp)},`);
|
|
@@ -2137,7 +2350,7 @@ ${appsCode}
|
|
|
2137
2350
|
]
|
|
2138
2351
|
};
|
|
2139
2352
|
`;
|
|
2140
|
-
|
|
2353
|
+
fs10.writeFileSync(resolvedOut, fileContent, "utf8");
|
|
2141
2354
|
console.log(
|
|
2142
2355
|
`from-caddy: parsed ${sites.length} site(s), ${planned.length} upstream(s) from ${resolvedCaddy}`
|
|
2143
2356
|
);
|
|
@@ -2155,7 +2368,7 @@ Warnings:`);
|
|
|
2155
2368
|
console.log(`
|
|
2156
2369
|
Wrote ${resolvedOut} (${planned.length} app(s))`);
|
|
2157
2370
|
console.log(
|
|
2158
|
-
`Next: relife2 inspect-config ${
|
|
2371
|
+
`Next: relife2 inspect-config ${path10.basename(resolvedOut)} \u2192 relife2 start ${path10.basename(resolvedOut)}`
|
|
2159
2372
|
);
|
|
2160
2373
|
return 0;
|
|
2161
2374
|
}
|
|
@@ -2184,7 +2397,7 @@ function printUsage2() {
|
|
|
2184
2397
|
}
|
|
2185
2398
|
|
|
2186
2399
|
// src/version.ts
|
|
2187
|
-
var VERSION = "1.0.
|
|
2400
|
+
var VERSION = "1.0.2";
|
|
2188
2401
|
|
|
2189
2402
|
// src/commands/help.ts
|
|
2190
2403
|
var HELP_TEXT = `relife2 ${VERSION} \u2014 process manager for Node.js & Bun apps (PM2 alternative, done right)
|
|
@@ -2210,7 +2423,7 @@ Commands:
|
|
|
2210
2423
|
flush [name|all] Empty the app log file(s)
|
|
2211
2424
|
rotate [--max-size <bytes>] [--retain <n>] [name|all]
|
|
2212
2425
|
Rotate log files exceeding the size threshold (M3)
|
|
2213
|
-
doctor
|
|
2426
|
+
doctor [--fix] Run integrity checks on the running daemon (M3) \u2014 --fix heals stale lock + *.tmp.cjs
|
|
2214
2427
|
metrics Show daemon self-metrics + per-app summary (M3)
|
|
2215
2428
|
monit [--once] Live dashboard (M6): cpu/mem/uptime per app, q to quit
|
|
2216
2429
|
find [start] [--root <dir>] [--depth <n>] [--json] [--all]
|
|
@@ -2242,13 +2455,13 @@ function printHelp() {
|
|
|
2242
2455
|
}
|
|
2243
2456
|
|
|
2244
2457
|
// src/commands/importpm2.ts
|
|
2245
|
-
import * as
|
|
2246
|
-
import
|
|
2247
|
-
import
|
|
2458
|
+
import * as fs11 from "node:fs";
|
|
2459
|
+
import os7 from "node:os";
|
|
2460
|
+
import path11 from "node:path";
|
|
2248
2461
|
async function run8(args) {
|
|
2249
2462
|
const target = args[0];
|
|
2250
|
-
const dumpPath = target !== void 0 ?
|
|
2251
|
-
if (!
|
|
2463
|
+
const dumpPath = target !== void 0 ? path11.resolve(target) : path11.join(os7.homedir(), ".pm2", "dump.pm2");
|
|
2464
|
+
if (!fs11.existsSync(dumpPath)) {
|
|
2252
2465
|
console.error(`import-pm2: dump file not found: ${dumpPath}`);
|
|
2253
2466
|
console.error(
|
|
2254
2467
|
" pm2 saves to ~/.pm2/dump.pm2 via `pm2 save` \u2014 run that first, or pass a path:"
|
|
@@ -2258,7 +2471,7 @@ async function run8(args) {
|
|
|
2258
2471
|
}
|
|
2259
2472
|
let raw;
|
|
2260
2473
|
try {
|
|
2261
|
-
raw = JSON.parse(
|
|
2474
|
+
raw = JSON.parse(fs11.readFileSync(dumpPath, "utf8"));
|
|
2262
2475
|
} catch (err) {
|
|
2263
2476
|
console.error(`import-pm2: cannot parse ${dumpPath}: ${err.message}`);
|
|
2264
2477
|
return 1;
|
|
@@ -2301,11 +2514,11 @@ async function run8(args) {
|
|
|
2301
2514
|
}
|
|
2302
2515
|
]
|
|
2303
2516
|
};
|
|
2304
|
-
const tmpPath =
|
|
2305
|
-
|
|
2517
|
+
const tmpPath = path11.join(
|
|
2518
|
+
os7.tmpdir(),
|
|
2306
2519
|
`relife2-import-${Date.now()}-${Math.random().toString(36).slice(2)}.cjs`
|
|
2307
2520
|
);
|
|
2308
|
-
|
|
2521
|
+
fs11.writeFileSync(tmpPath, `module.exports = ${JSON.stringify(tmpConfig)}`, "utf8");
|
|
2309
2522
|
try {
|
|
2310
2523
|
const res = await client.call("start", {
|
|
2311
2524
|
target: { type: "config", path: tmpPath }
|
|
@@ -2321,7 +2534,7 @@ async function run8(args) {
|
|
|
2321
2534
|
console.error(`import ${name}: ${err.message}`);
|
|
2322
2535
|
} finally {
|
|
2323
2536
|
try {
|
|
2324
|
-
|
|
2537
|
+
fs11.unlinkSync(tmpPath);
|
|
2325
2538
|
} catch {
|
|
2326
2539
|
}
|
|
2327
2540
|
}
|
|
@@ -2338,7 +2551,7 @@ imported ${imported} app(s) from ${dumpPath}`);
|
|
|
2338
2551
|
}
|
|
2339
2552
|
|
|
2340
2553
|
// src/commands/inspectconfig.ts
|
|
2341
|
-
import
|
|
2554
|
+
import path12 from "node:path";
|
|
2342
2555
|
async function run9(args) {
|
|
2343
2556
|
const { positionals } = parseCliArgs(args);
|
|
2344
2557
|
const file = positionals[0];
|
|
@@ -2348,7 +2561,7 @@ async function run9(args) {
|
|
|
2348
2561
|
}
|
|
2349
2562
|
let loaded;
|
|
2350
2563
|
try {
|
|
2351
|
-
loaded = await loadConfigFile(
|
|
2564
|
+
loaded = await loadConfigFile(path12.resolve(file));
|
|
2352
2565
|
} catch (err) {
|
|
2353
2566
|
console.error(`relife2: ${err.message}`);
|
|
2354
2567
|
return 1;
|
|
@@ -2417,7 +2630,7 @@ function printTable(rows) {
|
|
|
2417
2630
|
}
|
|
2418
2631
|
|
|
2419
2632
|
// src/commands/logs.ts
|
|
2420
|
-
import * as
|
|
2633
|
+
import * as fs12 from "node:fs";
|
|
2421
2634
|
var VALUE_OPTS2 = /* @__PURE__ */ new Set(["lines"]);
|
|
2422
2635
|
async function run12(args) {
|
|
2423
2636
|
const { positionals, opts } = parseCliArgs(args, VALUE_OPTS2);
|
|
@@ -2457,7 +2670,7 @@ function parseLines(v) {
|
|
|
2457
2670
|
}
|
|
2458
2671
|
function printTail(file, lines) {
|
|
2459
2672
|
try {
|
|
2460
|
-
const text =
|
|
2673
|
+
const text = fs12.readFileSync(file, "utf8");
|
|
2461
2674
|
const all = text.split(/\r?\n/);
|
|
2462
2675
|
if (all.length > 0 && all[all.length - 1] === "") all.pop();
|
|
2463
2676
|
const slice = all.slice(Math.max(0, all.length - lines));
|
|
@@ -2483,13 +2696,13 @@ async function followFiles(files) {
|
|
|
2483
2696
|
const from = positions.get(f) ?? 0;
|
|
2484
2697
|
if (size > from) {
|
|
2485
2698
|
try {
|
|
2486
|
-
const fd =
|
|
2699
|
+
const fd = fs12.openSync(f, "r");
|
|
2487
2700
|
try {
|
|
2488
2701
|
const buf = Buffer.alloc(size - from);
|
|
2489
|
-
|
|
2702
|
+
fs12.readSync(fd, buf, 0, buf.length, from);
|
|
2490
2703
|
process.stdout.write(buf);
|
|
2491
2704
|
} finally {
|
|
2492
|
-
|
|
2705
|
+
fs12.closeSync(fd);
|
|
2493
2706
|
}
|
|
2494
2707
|
} catch {
|
|
2495
2708
|
}
|
|
@@ -2500,7 +2713,7 @@ async function followFiles(files) {
|
|
|
2500
2713
|
}
|
|
2501
2714
|
function fileSize(file) {
|
|
2502
2715
|
try {
|
|
2503
|
-
return
|
|
2716
|
+
return fs12.statSync(file).size;
|
|
2504
2717
|
} catch {
|
|
2505
2718
|
return 0;
|
|
2506
2719
|
}
|
|
@@ -2790,7 +3003,7 @@ async function run20(_args) {
|
|
|
2790
3003
|
}
|
|
2791
3004
|
|
|
2792
3005
|
// src/commands/start.ts
|
|
2793
|
-
import
|
|
3006
|
+
import path13 from "node:path";
|
|
2794
3007
|
var VALUE_OPTS4 = /* @__PURE__ */ new Set([
|
|
2795
3008
|
"name",
|
|
2796
3009
|
"env",
|
|
@@ -2839,7 +3052,7 @@ async function run21(args) {
|
|
|
2839
3052
|
const res = await client.call("start", {
|
|
2840
3053
|
target: {
|
|
2841
3054
|
type: isConfig ? "config" : "script",
|
|
2842
|
-
path:
|
|
3055
|
+
path: path13.resolve(target),
|
|
2843
3056
|
name,
|
|
2844
3057
|
args: passthrough.length > 0 ? passthrough : void 0,
|
|
2845
3058
|
envName: envName ?? void 0,
|
|
@@ -2866,11 +3079,11 @@ async function run21(args) {
|
|
|
2866
3079
|
|
|
2867
3080
|
// src/commands/startup.ts
|
|
2868
3081
|
import { execSync, spawnSync as spawnSync2 } from "node:child_process";
|
|
2869
|
-
import * as
|
|
2870
|
-
import
|
|
2871
|
-
import
|
|
3082
|
+
import * as fs13 from "node:fs";
|
|
3083
|
+
import os8 from "node:os";
|
|
3084
|
+
import path14 from "node:path";
|
|
2872
3085
|
import { fileURLToPath } from "node:url";
|
|
2873
|
-
var SYSTEMD_USER_DIR =
|
|
3086
|
+
var SYSTEMD_USER_DIR = path14.join(os8.homedir(), ".config", "systemd", "user");
|
|
2874
3087
|
var UNIT_NAME = "relife2.service";
|
|
2875
3088
|
function getRelife2Bin() {
|
|
2876
3089
|
const which = spawnSync2(process.platform === "win32" ? "where" : "which", ["relife2"], {
|
|
@@ -2888,13 +3101,13 @@ function getRelife2Bin() {
|
|
|
2888
3101
|
}
|
|
2889
3102
|
try {
|
|
2890
3103
|
const thisFile = fileURLToPath(import.meta.url);
|
|
2891
|
-
const cliPath =
|
|
2892
|
-
if (
|
|
3104
|
+
const cliPath = path14.resolve(path14.dirname(thisFile), "..", "cli.js");
|
|
3105
|
+
if (fs13.existsSync(cliPath)) {
|
|
2893
3106
|
return `node ${cliPath}`;
|
|
2894
3107
|
}
|
|
2895
3108
|
} catch {
|
|
2896
3109
|
}
|
|
2897
|
-
return `node ${
|
|
3110
|
+
return `node ${path14.resolve(arg1 ?? process.cwd())}`;
|
|
2898
3111
|
}
|
|
2899
3112
|
function generateUnit(bin) {
|
|
2900
3113
|
const envHint = process.env.RELIFE2_DIR !== void 0 ? `
|
|
@@ -2915,6 +3128,49 @@ Environment=RELIFE2_DAEMON=1${envHint}
|
|
|
2915
3128
|
WantedBy=default.target
|
|
2916
3129
|
`;
|
|
2917
3130
|
}
|
|
3131
|
+
function isLingerEnabled(user) {
|
|
3132
|
+
try {
|
|
3133
|
+
const out = execSync(`loginctl show-user ${user} -p Linger --value`, {
|
|
3134
|
+
encoding: "utf8",
|
|
3135
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
3136
|
+
timeout: 3e3
|
|
3137
|
+
});
|
|
3138
|
+
const v = out.trim().toLowerCase();
|
|
3139
|
+
if (v === "yes") return true;
|
|
3140
|
+
if (v === "no") return false;
|
|
3141
|
+
} catch {
|
|
3142
|
+
}
|
|
3143
|
+
try {
|
|
3144
|
+
const out = execSync(`loginctl show-user ${user}`, {
|
|
3145
|
+
encoding: "utf8",
|
|
3146
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
3147
|
+
timeout: 3e3
|
|
3148
|
+
});
|
|
3149
|
+
if (/Linger=yes/i.test(out)) return true;
|
|
3150
|
+
if (/Linger=no/i.test(out)) return false;
|
|
3151
|
+
} catch {
|
|
3152
|
+
}
|
|
3153
|
+
return null;
|
|
3154
|
+
}
|
|
3155
|
+
function tryEnableLinger(user) {
|
|
3156
|
+
const attempts = [
|
|
3157
|
+
{ cmd: `loginctl enable-linger ${user}`, label: "loginctl" },
|
|
3158
|
+
{ cmd: `sudo -n loginctl enable-linger ${user}`, label: "sudo -n" },
|
|
3159
|
+
{ cmd: `sudo loginctl enable-linger ${user}`, label: "sudo" }
|
|
3160
|
+
];
|
|
3161
|
+
for (const a of attempts) {
|
|
3162
|
+
try {
|
|
3163
|
+
execSync(a.cmd, { stdio: ["ignore", "pipe", "pipe"], timeout: 8e3 });
|
|
3164
|
+
const enabled = isLingerEnabled(user);
|
|
3165
|
+
if (enabled === true || enabled === null) {
|
|
3166
|
+
console.log(`enabled linger for user ${user} via ${a.label}`);
|
|
3167
|
+
return true;
|
|
3168
|
+
}
|
|
3169
|
+
} catch {
|
|
3170
|
+
}
|
|
3171
|
+
}
|
|
3172
|
+
return false;
|
|
3173
|
+
}
|
|
2918
3174
|
async function run22(_args) {
|
|
2919
3175
|
if (process.platform === "win32") {
|
|
2920
3176
|
console.error(
|
|
@@ -2928,47 +3184,67 @@ async function run22(_args) {
|
|
|
2928
3184
|
}
|
|
2929
3185
|
const bin = getRelife2Bin();
|
|
2930
3186
|
const unitContent = generateUnit(bin);
|
|
2931
|
-
const unitPath =
|
|
3187
|
+
const unitPath = path14.join(SYSTEMD_USER_DIR, UNIT_NAME);
|
|
2932
3188
|
try {
|
|
2933
|
-
|
|
3189
|
+
fs13.mkdirSync(SYSTEMD_USER_DIR, { recursive: true });
|
|
2934
3190
|
} catch (err) {
|
|
2935
3191
|
console.error(`cannot create ${SYSTEMD_USER_DIR}: ${err.message}`);
|
|
2936
3192
|
return 1;
|
|
2937
3193
|
}
|
|
2938
3194
|
try {
|
|
2939
|
-
|
|
3195
|
+
fs13.writeFileSync(unitPath, unitContent);
|
|
2940
3196
|
console.log(`written systemd user unit: ${unitPath}`);
|
|
2941
3197
|
} catch (err) {
|
|
2942
3198
|
console.error(`cannot write ${unitPath}: ${err.message}`);
|
|
2943
3199
|
return 1;
|
|
2944
3200
|
}
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
console.log(`enabled
|
|
2949
|
-
}
|
|
2950
|
-
console.
|
|
2951
|
-
|
|
2952
|
-
)
|
|
2953
|
-
|
|
2954
|
-
|
|
3201
|
+
const user = process.env.USER ?? process.env.LOGNAME ?? os8.userInfo().username ?? "opc";
|
|
3202
|
+
const lingerState = isLingerEnabled(user);
|
|
3203
|
+
if (lingerState === true) {
|
|
3204
|
+
console.log(`linger already enabled for user ${user}`);
|
|
3205
|
+
} else if (lingerState === false) {
|
|
3206
|
+
console.log(`linger is disabled for ${user} \u2014 enabling\u2026`);
|
|
3207
|
+
const ok = tryEnableLinger(user);
|
|
3208
|
+
if (!ok) {
|
|
3209
|
+
console.error(`warning: could not enable linger (is systemd-logind running?)`);
|
|
3210
|
+
console.error(`your apps will only start after you log in. Run manually:`);
|
|
3211
|
+
console.error(` sudo loginctl enable-linger ${user}`);
|
|
3212
|
+
}
|
|
3213
|
+
} else {
|
|
3214
|
+
const ok = tryEnableLinger(user);
|
|
3215
|
+
if (!ok) {
|
|
3216
|
+
console.error(`note: could not verify linger status (no logind?) \u2014 skipping`);
|
|
3217
|
+
}
|
|
2955
3218
|
}
|
|
2956
3219
|
try {
|
|
2957
|
-
execSync("systemctl --user daemon-reload", {
|
|
2958
|
-
|
|
3220
|
+
execSync("systemctl --user daemon-reload", {
|
|
3221
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
3222
|
+
timeout: 8e3
|
|
3223
|
+
});
|
|
3224
|
+
execSync(`systemctl --user enable ${UNIT_NAME}`, {
|
|
3225
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
3226
|
+
timeout: 8e3
|
|
3227
|
+
});
|
|
2959
3228
|
console.log(`enabled and reloaded: systemctl --user enable ${UNIT_NAME}`);
|
|
3229
|
+
try {
|
|
3230
|
+
execSync(`systemctl --user start ${UNIT_NAME}`, {
|
|
3231
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
3232
|
+
timeout: 8e3
|
|
3233
|
+
});
|
|
3234
|
+
console.log(`started: systemctl --user start ${UNIT_NAME}`);
|
|
3235
|
+
} catch {
|
|
3236
|
+
}
|
|
2960
3237
|
} catch (err) {
|
|
2961
3238
|
console.error(
|
|
2962
3239
|
`warning: could not enable unit (is systemd available?): ${err instanceof Error ? err.message : String(err)}`
|
|
2963
3240
|
);
|
|
2964
3241
|
console.error("run manually:");
|
|
2965
3242
|
console.error(" systemctl --user daemon-reload");
|
|
2966
|
-
console.error(` systemctl --user enable ${UNIT_NAME}`);
|
|
3243
|
+
console.error(` systemctl --user enable --now ${UNIT_NAME}`);
|
|
2967
3244
|
}
|
|
2968
3245
|
console.log("");
|
|
2969
3246
|
console.log("relife2 will now start automatically on boot.");
|
|
2970
3247
|
console.log(`ExecStart is: ${bin}`);
|
|
2971
|
-
console.log("To start now, run: systemctl --user start relife2.service");
|
|
2972
3248
|
console.log("To check status: systemctl --user status relife2.service");
|
|
2973
3249
|
return 0;
|
|
2974
3250
|
}
|
|
@@ -3164,14 +3440,14 @@ function detectRuntime() {
|
|
|
3164
3440
|
}
|
|
3165
3441
|
|
|
3166
3442
|
// src/daemon/daemon.ts
|
|
3167
|
-
import * as
|
|
3168
|
-
import
|
|
3169
|
-
import
|
|
3443
|
+
import * as fs20 from "node:fs";
|
|
3444
|
+
import os9 from "node:os";
|
|
3445
|
+
import path20 from "node:path";
|
|
3170
3446
|
import * as zlib from "node:zlib";
|
|
3171
3447
|
|
|
3172
3448
|
// src/daemon/applog.ts
|
|
3173
|
-
import * as
|
|
3174
|
-
import
|
|
3449
|
+
import * as fs14 from "node:fs";
|
|
3450
|
+
import path15 from "node:path";
|
|
3175
3451
|
|
|
3176
3452
|
// src/util/datefmt.ts
|
|
3177
3453
|
function formatDate(pattern, date = /* @__PURE__ */ new Date()) {
|
|
@@ -3196,12 +3472,12 @@ function formatDate(pattern, date = /* @__PURE__ */ new Date()) {
|
|
|
3196
3472
|
|
|
3197
3473
|
// src/daemon/applog.ts
|
|
3198
3474
|
var DEFAULT_LOG_FORMAT = "YYYY-MM-DD HH:mm:ss.SSS Z";
|
|
3199
|
-
var DEFAULT_LOGS_DIR = (base) =>
|
|
3475
|
+
var DEFAULT_LOGS_DIR = (base) => path15.join(base, "logs");
|
|
3200
3476
|
function resolveLogPaths(app, base) {
|
|
3201
3477
|
const safe = app.name.replace(/[^\w.-]/g, "_");
|
|
3202
|
-
const out = app.outFile ?
|
|
3478
|
+
const out = app.outFile ? path15.resolve(app.cwd, app.outFile) : path15.join(DEFAULT_LOGS_DIR(base), `${safe}.out.log`);
|
|
3203
3479
|
if (app.mergeLogs) return { out, err: null };
|
|
3204
|
-
const err = app.errFile ?
|
|
3480
|
+
const err = app.errFile ? path15.resolve(app.cwd, app.errFile) : path15.join(DEFAULT_LOGS_DIR(base), `${safe}.err.log`);
|
|
3205
3481
|
return { out, err };
|
|
3206
3482
|
}
|
|
3207
3483
|
var AppLogSink = class {
|
|
@@ -3246,8 +3522,8 @@ var AppLogSink = class {
|
|
|
3246
3522
|
const out = `${this.prefix}${line}
|
|
3247
3523
|
`;
|
|
3248
3524
|
try {
|
|
3249
|
-
ensureDirSync(
|
|
3250
|
-
|
|
3525
|
+
ensureDirSync(path15.dirname(target));
|
|
3526
|
+
fs14.appendFileSync(target, out);
|
|
3251
3527
|
} catch {
|
|
3252
3528
|
}
|
|
3253
3529
|
}
|
|
@@ -3255,18 +3531,18 @@ var AppLogSink = class {
|
|
|
3255
3531
|
};
|
|
3256
3532
|
|
|
3257
3533
|
// src/daemon/logger.ts
|
|
3258
|
-
import
|
|
3259
|
-
import
|
|
3534
|
+
import fs15 from "node:fs";
|
|
3535
|
+
import path16 from "node:path";
|
|
3260
3536
|
function createDaemonLogger(base) {
|
|
3261
|
-
const logsDir =
|
|
3537
|
+
const logsDir = path16.join(base, "logs");
|
|
3262
3538
|
ensureDirSync(logsDir);
|
|
3263
|
-
const mainFile =
|
|
3539
|
+
const mainFile = path16.join(logsDir, "daemon.log");
|
|
3264
3540
|
const outRoot = logsDir;
|
|
3265
3541
|
function log(msg, extra) {
|
|
3266
3542
|
const line = `${JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), msg, ...extra ?? {} })}
|
|
3267
3543
|
`;
|
|
3268
3544
|
try {
|
|
3269
|
-
|
|
3545
|
+
fs15.appendFileSync(mainFile, line);
|
|
3270
3546
|
} catch {
|
|
3271
3547
|
}
|
|
3272
3548
|
}
|
|
@@ -3275,13 +3551,13 @@ function createDaemonLogger(base) {
|
|
|
3275
3551
|
}
|
|
3276
3552
|
function appOut(name, chunk) {
|
|
3277
3553
|
try {
|
|
3278
|
-
|
|
3554
|
+
fs15.appendFileSync(path16.join(outRoot, `${safeName(name)}.out.log`), chunk);
|
|
3279
3555
|
} catch {
|
|
3280
3556
|
}
|
|
3281
3557
|
}
|
|
3282
3558
|
function appErr(name, chunk) {
|
|
3283
3559
|
try {
|
|
3284
|
-
|
|
3560
|
+
fs15.appendFileSync(path16.join(outRoot, `${safeName(name)}.err.log`), chunk);
|
|
3285
3561
|
} catch {
|
|
3286
3562
|
}
|
|
3287
3563
|
}
|
|
@@ -3290,12 +3566,12 @@ function createDaemonLogger(base) {
|
|
|
3290
3566
|
|
|
3291
3567
|
// src/daemon/pm.ts
|
|
3292
3568
|
import { spawn as spawn3 } from "node:child_process";
|
|
3293
|
-
import * as
|
|
3294
|
-
import
|
|
3569
|
+
import * as fs17 from "node:fs";
|
|
3570
|
+
import path18 from "node:path";
|
|
3295
3571
|
|
|
3296
3572
|
// src/daemon/command.ts
|
|
3297
3573
|
import { existsSync as existsSync8 } from "node:fs";
|
|
3298
|
-
import
|
|
3574
|
+
import path17 from "node:path";
|
|
3299
3575
|
import process2 from "node:process";
|
|
3300
3576
|
var JS_RE = /\.(?:c?js|mjs|ts|mts|cts)$/i;
|
|
3301
3577
|
function isJsScript(p) {
|
|
@@ -3305,10 +3581,10 @@ function isBunProject2(scriptDir) {
|
|
|
3305
3581
|
let dir = scriptDir;
|
|
3306
3582
|
const home = process2.env.HOME ?? "";
|
|
3307
3583
|
while (true) {
|
|
3308
|
-
if (existsSync8(
|
|
3584
|
+
if (existsSync8(path17.join(dir, "bun.lockb")) || existsSync8(path17.join(dir, "bun.lock"))) {
|
|
3309
3585
|
return true;
|
|
3310
3586
|
}
|
|
3311
|
-
const parent =
|
|
3587
|
+
const parent = path17.dirname(dir);
|
|
3312
3588
|
if (parent === dir) break;
|
|
3313
3589
|
if (home !== "" && dir === home) break;
|
|
3314
3590
|
dir = parent;
|
|
@@ -3344,7 +3620,7 @@ function buildSpawnSpec(app) {
|
|
|
3344
3620
|
|
|
3345
3621
|
// src/daemon/memproc.ts
|
|
3346
3622
|
import { execFileSync } from "node:child_process";
|
|
3347
|
-
import * as
|
|
3623
|
+
import * as fs16 from "node:fs";
|
|
3348
3624
|
function processRss(pid) {
|
|
3349
3625
|
if (pid <= 0) return void 0;
|
|
3350
3626
|
try {
|
|
@@ -3360,7 +3636,7 @@ function processRss(pid) {
|
|
|
3360
3636
|
}
|
|
3361
3637
|
}
|
|
3362
3638
|
function rssFromProc(pid) {
|
|
3363
|
-
const status =
|
|
3639
|
+
const status = fs16.readFileSync(`/proc/${pid}/status`, "utf8");
|
|
3364
3640
|
const match = /^VmRSS:\s+(\d+)\s+kB/m.exec(status);
|
|
3365
3641
|
if (match?.[1] === void 0) return void 0;
|
|
3366
3642
|
return Number(match[1]) * 1024;
|
|
@@ -3435,7 +3711,7 @@ var ProcessManager = class {
|
|
|
3435
3711
|
});
|
|
3436
3712
|
record.config.cwd = cwdFix.path;
|
|
3437
3713
|
}
|
|
3438
|
-
if (
|
|
3714
|
+
if (path18.isAbsolute(record.config.script) && !fs17.existsSync(record.config.script)) {
|
|
3439
3715
|
const sFix = fixScript(record.config.script, record.config.cwd, record.config.sourceDir);
|
|
3440
3716
|
if (sFix.fixed && sFix.reason) {
|
|
3441
3717
|
this.logger.log(sFix.reason);
|
|
@@ -3449,7 +3725,7 @@ var ProcessManager = class {
|
|
|
3449
3725
|
} else if (!sFix.fixed) {
|
|
3450
3726
|
this.fail(
|
|
3451
3727
|
record,
|
|
3452
|
-
`script not found: '${record.config.script}'. ${sFix.reason ?? ""} Run 'relife2 find --root ${
|
|
3728
|
+
`script not found: '${record.config.script}'. ${sFix.reason ?? ""} Run 'relife2 find --root ${path18.dirname(record.config.sourceDir)}' to locate configs or fix the config.`
|
|
3453
3729
|
);
|
|
3454
3730
|
this.store.saveSync();
|
|
3455
3731
|
return;
|
|
@@ -3765,19 +4041,19 @@ var ProcessManager = class {
|
|
|
3765
4041
|
const watch3 = record.config.watch;
|
|
3766
4042
|
if (watch3 === void 0 || watch3 === false) return;
|
|
3767
4043
|
if (this.watchers.has(record.name)) return;
|
|
3768
|
-
const rawPaths = Array.isArray(watch3) ? watch3.map((p) =>
|
|
4044
|
+
const rawPaths = Array.isArray(watch3) ? watch3.map((p) => path18.resolve(record.config.cwd, p)) : [record.config.cwd];
|
|
3769
4045
|
const watchers = [];
|
|
3770
4046
|
for (const target of rawPaths) {
|
|
3771
4047
|
try {
|
|
3772
|
-
if (!
|
|
4048
|
+
if (!fs17.existsSync(target)) {
|
|
3773
4049
|
this.logger.log(`watch: path '${target}' does not exist yet; skipping`);
|
|
3774
4050
|
continue;
|
|
3775
4051
|
}
|
|
3776
|
-
const stat =
|
|
4052
|
+
const stat = fs17.statSync(target);
|
|
3777
4053
|
if (!stat.isDirectory()) {
|
|
3778
|
-
const dir =
|
|
3779
|
-
const base =
|
|
3780
|
-
const watcher =
|
|
4054
|
+
const dir = path18.dirname(target);
|
|
4055
|
+
const base = path18.basename(target);
|
|
4056
|
+
const watcher = fs17.watch(dir, (_eventType, filename) => {
|
|
3781
4057
|
if (filename !== null && filename !== base) return;
|
|
3782
4058
|
this.onWatchChange(record.name, target);
|
|
3783
4059
|
});
|
|
@@ -3785,7 +4061,7 @@ var ProcessManager = class {
|
|
|
3785
4061
|
continue;
|
|
3786
4062
|
}
|
|
3787
4063
|
if (process.platform === "win32" || process.platform === "darwin") {
|
|
3788
|
-
const watcher =
|
|
4064
|
+
const watcher = fs17.watch(target, { recursive: true }, () => {
|
|
3789
4065
|
this.onWatchChange(record.name, target);
|
|
3790
4066
|
});
|
|
3791
4067
|
watchers.push(watcher);
|
|
@@ -3793,7 +4069,7 @@ var ProcessManager = class {
|
|
|
3793
4069
|
const dirs = this.collectSubDirs(target);
|
|
3794
4070
|
for (const dir of dirs) {
|
|
3795
4071
|
try {
|
|
3796
|
-
const watcher =
|
|
4072
|
+
const watcher = fs17.watch(dir, () => {
|
|
3797
4073
|
this.onWatchChange(record.name, target);
|
|
3798
4074
|
});
|
|
3799
4075
|
watchers.push(watcher);
|
|
@@ -3820,7 +4096,7 @@ var ProcessManager = class {
|
|
|
3820
4096
|
const dir = queue.shift();
|
|
3821
4097
|
let entries;
|
|
3822
4098
|
try {
|
|
3823
|
-
entries =
|
|
4099
|
+
entries = fs17.readdirSync(dir, { withFileTypes: true });
|
|
3824
4100
|
} catch {
|
|
3825
4101
|
continue;
|
|
3826
4102
|
}
|
|
@@ -3828,7 +4104,7 @@ var ProcessManager = class {
|
|
|
3828
4104
|
if (!entry.isDirectory()) continue;
|
|
3829
4105
|
if (entry.name.startsWith(".")) continue;
|
|
3830
4106
|
if (entry.name === "node_modules" || entry.name === ".git") continue;
|
|
3831
|
-
const full =
|
|
4107
|
+
const full = path18.join(dir, entry.name);
|
|
3832
4108
|
if (seen.has(full)) continue;
|
|
3833
4109
|
seen.add(full);
|
|
3834
4110
|
out.push(full);
|
|
@@ -3928,17 +4204,17 @@ var ProcessManager = class {
|
|
|
3928
4204
|
};
|
|
3929
4205
|
|
|
3930
4206
|
// src/daemon/state.ts
|
|
3931
|
-
import
|
|
3932
|
-
import
|
|
4207
|
+
import fs18 from "node:fs";
|
|
4208
|
+
import path19 from "node:path";
|
|
3933
4209
|
var StateStore = class {
|
|
3934
4210
|
apps = /* @__PURE__ */ new Map();
|
|
3935
4211
|
snapshotPath;
|
|
3936
4212
|
journalPath;
|
|
3937
4213
|
constructor(base) {
|
|
3938
|
-
const stateDir =
|
|
4214
|
+
const stateDir = path19.join(base, "state");
|
|
3939
4215
|
ensureDirSync(stateDir);
|
|
3940
|
-
this.snapshotPath =
|
|
3941
|
-
this.journalPath =
|
|
4216
|
+
this.snapshotPath = path19.join(stateDir, "snapshot.json");
|
|
4217
|
+
this.journalPath = path19.join(stateDir, "journal.jsonl");
|
|
3942
4218
|
}
|
|
3943
4219
|
load() {
|
|
3944
4220
|
const data = readJsonFile(this.snapshotPath);
|
|
@@ -3975,7 +4251,7 @@ var StateStore = class {
|
|
|
3975
4251
|
const line = `${JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), type, ...data ?? {} })}
|
|
3976
4252
|
`;
|
|
3977
4253
|
try {
|
|
3978
|
-
|
|
4254
|
+
fs18.appendFileSync(this.journalPath, line);
|
|
3979
4255
|
} catch {
|
|
3980
4256
|
}
|
|
3981
4257
|
}
|
|
@@ -3987,7 +4263,7 @@ var StateStore = class {
|
|
|
3987
4263
|
};
|
|
3988
4264
|
|
|
3989
4265
|
// src/daemon/transport.ts
|
|
3990
|
-
import
|
|
4266
|
+
import fs19 from "node:fs";
|
|
3991
4267
|
import net2 from "node:net";
|
|
3992
4268
|
async function createDaemonTransport(base) {
|
|
3993
4269
|
return process.platform === "win32" ? createWinTransport(base) : createPosixTransport(base);
|
|
@@ -4001,7 +4277,7 @@ async function createPosixTransport(base) {
|
|
|
4001
4277
|
if (err.code !== "EADDRINUSE") return null;
|
|
4002
4278
|
if (await probeActive(sockPath)) return null;
|
|
4003
4279
|
try {
|
|
4004
|
-
|
|
4280
|
+
fs19.unlinkSync(sockPath);
|
|
4005
4281
|
} catch {
|
|
4006
4282
|
}
|
|
4007
4283
|
}
|
|
@@ -4054,7 +4330,7 @@ function probeActive(sockPath) {
|
|
|
4054
4330
|
}
|
|
4055
4331
|
function readPortFile(base) {
|
|
4056
4332
|
try {
|
|
4057
|
-
const raw =
|
|
4333
|
+
const raw = fs19.readFileSync(portFilePath(base), "utf8");
|
|
4058
4334
|
return Number.parseInt(raw.trim(), 10) || null;
|
|
4059
4335
|
} catch {
|
|
4060
4336
|
return null;
|
|
@@ -4062,7 +4338,7 @@ function readPortFile(base) {
|
|
|
4062
4338
|
}
|
|
4063
4339
|
function writePortFile(base, port) {
|
|
4064
4340
|
try {
|
|
4065
|
-
|
|
4341
|
+
fs19.writeFileSync(portFilePath(base), String(port));
|
|
4066
4342
|
} catch {
|
|
4067
4343
|
}
|
|
4068
4344
|
}
|
|
@@ -4071,7 +4347,7 @@ function writePortFile(base, port) {
|
|
|
4071
4347
|
async function runDaemon() {
|
|
4072
4348
|
const data = dataDir();
|
|
4073
4349
|
const runtime = runtimeDir();
|
|
4074
|
-
ensureDirSync(
|
|
4350
|
+
ensureDirSync(path20.join(data, "logs"));
|
|
4075
4351
|
const logger = createDaemonLogger(data);
|
|
4076
4352
|
logger.log("daemon starting", { pid: process.pid, version: VERSION, platform: process.platform });
|
|
4077
4353
|
const lock = acquireLock(runtime);
|
|
@@ -4286,8 +4562,8 @@ var METHODS = {
|
|
|
4286
4562
|
const files = paths.err === null ? [paths.out] : [paths.out, paths.err];
|
|
4287
4563
|
for (const f of files) {
|
|
4288
4564
|
try {
|
|
4289
|
-
ensureDirSync(
|
|
4290
|
-
|
|
4565
|
+
ensureDirSync(path20.dirname(f));
|
|
4566
|
+
fs20.writeFileSync(f, "");
|
|
4291
4567
|
} catch (err) {
|
|
4292
4568
|
logger.log(`flush ${rec.name}: cannot truncate ${f}: ${err.message}`);
|
|
4293
4569
|
}
|
|
@@ -4320,7 +4596,7 @@ var METHODS = {
|
|
|
4320
4596
|
for (const f of files) {
|
|
4321
4597
|
if (!pathExists(f)) continue;
|
|
4322
4598
|
try {
|
|
4323
|
-
const stat =
|
|
4599
|
+
const stat = fs20.statSync(f);
|
|
4324
4600
|
if (stat.size >= max) {
|
|
4325
4601
|
rotateFile(f, n);
|
|
4326
4602
|
rotated.push(f);
|
|
@@ -4374,11 +4650,11 @@ var METHODS = {
|
|
|
4374
4650
|
},
|
|
4375
4651
|
doctor: (_params, { store, base, transport }) => {
|
|
4376
4652
|
const checks = [];
|
|
4377
|
-
const snapshotPath =
|
|
4653
|
+
const snapshotPath = path20.join(base, "state", "snapshot.json");
|
|
4378
4654
|
const stateIssues = [];
|
|
4379
4655
|
if (pathExists(snapshotPath)) {
|
|
4380
4656
|
try {
|
|
4381
|
-
const data = JSON.parse(
|
|
4657
|
+
const data = JSON.parse(fs20.readFileSync(snapshotPath, "utf8"));
|
|
4382
4658
|
if (data.schema === 1 && typeof data.apps === "object") {
|
|
4383
4659
|
stateIssues.push({
|
|
4384
4660
|
severity: "ok",
|
|
@@ -4411,11 +4687,11 @@ var METHODS = {
|
|
|
4411
4687
|
ok: !stateIssues.some((i) => i.severity === "error"),
|
|
4412
4688
|
issues: stateIssues
|
|
4413
4689
|
});
|
|
4414
|
-
const lockFile =
|
|
4690
|
+
const lockFile = path20.join(runtimeDir(), "daemon.lock");
|
|
4415
4691
|
const lockIssues = [];
|
|
4416
4692
|
if (pathExists(lockFile)) {
|
|
4417
4693
|
try {
|
|
4418
|
-
const lock = JSON.parse(
|
|
4694
|
+
const lock = JSON.parse(fs20.readFileSync(lockFile, "utf8"));
|
|
4419
4695
|
if (typeof lock.pid === "number" && isAlive(lock.pid)) {
|
|
4420
4696
|
lockIssues.push({
|
|
4421
4697
|
severity: "ok",
|
|
@@ -4526,7 +4802,7 @@ async function startApps(target, ctx) {
|
|
|
4526
4802
|
if (target.interpreter !== void 0) input.interpreter = target.interpreter;
|
|
4527
4803
|
if (target.cwd !== void 0) input.cwd = target.cwd;
|
|
4528
4804
|
if (target.cliOverrides) Object.assign(input, target.cliOverrides);
|
|
4529
|
-
const single = normalizeSingleApp(input,
|
|
4805
|
+
const single = normalizeSingleApp(input, path20.dirname(target.path), target.envName);
|
|
4530
4806
|
apps = [single.app];
|
|
4531
4807
|
warnings = single.warnings;
|
|
4532
4808
|
}
|
|
@@ -4591,7 +4867,7 @@ function expandTargets(store, name) {
|
|
|
4591
4867
|
function applyCliOverrides(app, overrides) {
|
|
4592
4868
|
if (overrides.instances !== void 0) {
|
|
4593
4869
|
const v = overrides.instances;
|
|
4594
|
-
if (v === "max" || v === "MAX") app.instances =
|
|
4870
|
+
if (v === "max" || v === "MAX") app.instances = os9.cpus().length || 1;
|
|
4595
4871
|
else {
|
|
4596
4872
|
const n = Number(v);
|
|
4597
4873
|
if (Number.isFinite(n) && n >= 1) app.instances = Math.round(n);
|
|
@@ -4651,15 +4927,15 @@ function rotateFile(file, retain) {
|
|
|
4651
4927
|
const newName = `${file}.${i + 1}.gz`;
|
|
4652
4928
|
if (pathExists(oldName)) {
|
|
4653
4929
|
if (i + 1 >= retain) {
|
|
4654
|
-
|
|
4930
|
+
fs20.unlinkSync(oldName);
|
|
4655
4931
|
} else {
|
|
4656
4932
|
if (i === 0) {
|
|
4657
|
-
const content =
|
|
4658
|
-
|
|
4933
|
+
const content = fs20.readFileSync(file);
|
|
4934
|
+
fs20.unlinkSync(file);
|
|
4659
4935
|
const gz = zlib.gzipSync(content);
|
|
4660
|
-
|
|
4936
|
+
fs20.writeFileSync(newName, gz);
|
|
4661
4937
|
} else {
|
|
4662
|
-
|
|
4938
|
+
fs20.renameSync(oldName, newName);
|
|
4663
4939
|
}
|
|
4664
4940
|
}
|
|
4665
4941
|
}
|
|
@@ -4747,6 +5023,7 @@ async function run25(argv) {
|
|
|
4747
5023
|
return 0;
|
|
4748
5024
|
}
|
|
4749
5025
|
const impl = COMMANDS[command];
|
|
5026
|
+
if (!impl) return 1;
|
|
4750
5027
|
return impl(args.slice(1));
|
|
4751
5028
|
}
|
|
4752
5029
|
if (hasFlag(args, "v", "version")) {
|