pidnap 0.0.0-dev.4 → 0.0.0-dev.6
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.mjs +466 -585
- package/dist/cli.mjs.map +1 -1
- package/dist/client.d.mts +2 -2
- package/dist/client.d.mts.map +1 -1
- package/dist/client.mjs +2 -2
- package/dist/client.mjs.map +1 -1
- package/dist/index.d.mts +263 -71
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +2 -2
- package/dist/index.mjs.map +1 -1
- package/dist/{logger-BIJzGqk3.mjs → logger-BU2RmetS.mjs} +228 -283
- package/dist/logger-BU2RmetS.mjs.map +1 -0
- package/package.json +6 -3
- package/src/api/client.ts +4 -2
- package/src/api/contract.ts +8 -14
- package/src/cli.ts +318 -429
- package/src/cron-process.ts +9 -18
- package/src/env-manager.ts +170 -163
- package/src/index.ts +0 -1
- package/src/lazy-process.ts +54 -91
- package/src/logger.ts +28 -36
- package/src/manager.ts +160 -212
- package/src/restarting-process.ts +16 -29
- package/src/task-list.ts +3 -5
- package/src/utils.ts +10 -0
- package/dist/logger-BIJzGqk3.mjs.map +0 -1
- package/dist/task-list-CIdbB3wM.d.mts +0 -230
- package/dist/task-list-CIdbB3wM.d.mts.map +0 -1
- package/src/port-utils.ts +0 -39
- package/src/tree-kill.ts +0 -131
|
@@ -1,107 +1,22 @@
|
|
|
1
|
-
import { basename,
|
|
1
|
+
import { basename, isAbsolute, resolve } from "node:path";
|
|
2
2
|
import * as v from "valibot";
|
|
3
|
+
import { setTimeout as setTimeout$1 } from "node:timers/promises";
|
|
4
|
+
import { createInterface } from "node:readline/promises";
|
|
3
5
|
import { spawn } from "node:child_process";
|
|
4
|
-
import readline from "node:readline";
|
|
5
|
-
import { PassThrough } from "node:stream";
|
|
6
6
|
import { Cron } from "croner";
|
|
7
|
-
import { appendFileSync, existsSync, globSync, readFileSync
|
|
7
|
+
import { appendFileSync, existsSync, globSync, readFileSync } from "node:fs";
|
|
8
8
|
import { parse } from "dotenv";
|
|
9
|
-
import {
|
|
9
|
+
import { watch } from "chokidar";
|
|
10
10
|
import { format } from "node:util";
|
|
11
11
|
|
|
12
|
-
//#region src/tree-kill.ts
|
|
13
|
-
/**
|
|
14
|
-
* Kill a process and all its descendants (children, grandchildren, etc.)
|
|
15
|
-
* @param pid - The process ID to kill
|
|
16
|
-
* @param signal - The signal to send (default: SIGTERM)
|
|
17
|
-
* @returns Promise that resolves when all processes have been signaled
|
|
18
|
-
*/
|
|
19
|
-
async function treeKill(pid, signal = "SIGTERM") {
|
|
20
|
-
if (Number.isNaN(pid)) throw new Error("pid must be a number");
|
|
21
|
-
const tree = { [pid]: [] };
|
|
22
|
-
await buildProcessTree(pid, tree, new Set([pid]));
|
|
23
|
-
killAll(tree, signal);
|
|
24
|
-
}
|
|
25
|
-
/**
|
|
26
|
-
* Build a tree of all child processes recursively
|
|
27
|
-
*/
|
|
28
|
-
async function buildProcessTree(parentPid, tree, pidsToProcess) {
|
|
29
|
-
return new Promise((resolve) => {
|
|
30
|
-
const ps = spawn("ps", [
|
|
31
|
-
"-o",
|
|
32
|
-
"pid",
|
|
33
|
-
"--no-headers",
|
|
34
|
-
"--ppid",
|
|
35
|
-
String(parentPid)
|
|
36
|
-
]);
|
|
37
|
-
let allData = "";
|
|
38
|
-
ps.stdout.on("data", (data) => {
|
|
39
|
-
allData += data.toString("ascii");
|
|
40
|
-
});
|
|
41
|
-
ps.on("close", async (code) => {
|
|
42
|
-
pidsToProcess.delete(parentPid);
|
|
43
|
-
if (code !== 0) {
|
|
44
|
-
if (pidsToProcess.size === 0) resolve();
|
|
45
|
-
return;
|
|
46
|
-
}
|
|
47
|
-
const childPids = allData.match(/\d+/g);
|
|
48
|
-
if (!childPids) {
|
|
49
|
-
if (pidsToProcess.size === 0) resolve();
|
|
50
|
-
return;
|
|
51
|
-
}
|
|
52
|
-
const promises = [];
|
|
53
|
-
for (const pidStr of childPids) {
|
|
54
|
-
const childPid = parseInt(pidStr, 10);
|
|
55
|
-
tree[parentPid].push(childPid);
|
|
56
|
-
tree[childPid] = [];
|
|
57
|
-
pidsToProcess.add(childPid);
|
|
58
|
-
promises.push(buildProcessTree(childPid, tree, pidsToProcess));
|
|
59
|
-
}
|
|
60
|
-
await Promise.all(promises);
|
|
61
|
-
if (pidsToProcess.size === 0) resolve();
|
|
62
|
-
});
|
|
63
|
-
ps.on("error", () => {
|
|
64
|
-
pidsToProcess.delete(parentPid);
|
|
65
|
-
if (pidsToProcess.size === 0) resolve();
|
|
66
|
-
});
|
|
67
|
-
});
|
|
68
|
-
}
|
|
69
|
-
/**
|
|
70
|
-
* Kill all processes in the tree
|
|
71
|
-
* Kills children before parents to ensure clean shutdown
|
|
72
|
-
*/
|
|
73
|
-
function killAll(tree, signal) {
|
|
74
|
-
const killed = /* @__PURE__ */ new Set();
|
|
75
|
-
const allPids = Object.keys(tree).map(Number);
|
|
76
|
-
for (const pid of allPids) for (const childPid of tree[pid]) if (!killed.has(childPid)) {
|
|
77
|
-
killPid(childPid, signal);
|
|
78
|
-
killed.add(childPid);
|
|
79
|
-
}
|
|
80
|
-
for (const pid of allPids) if (!killed.has(pid)) {
|
|
81
|
-
killPid(pid, signal);
|
|
82
|
-
killed.add(pid);
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
/**
|
|
86
|
-
* Kill a single process, ignoring ESRCH errors (process already dead)
|
|
87
|
-
*/
|
|
88
|
-
function killPid(pid, signal) {
|
|
89
|
-
try {
|
|
90
|
-
process.kill(pid, signal);
|
|
91
|
-
} catch (err) {
|
|
92
|
-
if (err.code !== "ESRCH") throw err;
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
//#endregion
|
|
97
12
|
//#region src/lazy-process.ts
|
|
98
|
-
const
|
|
13
|
+
const ProcessDefinition = v.object({
|
|
99
14
|
command: v.string(),
|
|
100
15
|
args: v.optional(v.array(v.string())),
|
|
101
16
|
cwd: v.optional(v.string()),
|
|
102
17
|
env: v.optional(v.record(v.string(), v.string()))
|
|
103
18
|
});
|
|
104
|
-
const
|
|
19
|
+
const ProcessState = v.picklist([
|
|
105
20
|
"idle",
|
|
106
21
|
"starting",
|
|
107
22
|
"running",
|
|
@@ -110,21 +25,17 @@ const ProcessStateSchema = v.picklist([
|
|
|
110
25
|
"error"
|
|
111
26
|
]);
|
|
112
27
|
/**
|
|
113
|
-
* Kill a process and all its descendants
|
|
114
|
-
*
|
|
28
|
+
* Kill a process group (the process and all its descendants).
|
|
29
|
+
* Uses negative PID to target the entire process group.
|
|
115
30
|
*/
|
|
116
|
-
|
|
117
|
-
const pid = child.pid;
|
|
118
|
-
if (pid === void 0) return false;
|
|
31
|
+
function killProcessGroup(pid, signal) {
|
|
119
32
|
try {
|
|
120
|
-
|
|
33
|
+
process.kill(-pid, signal);
|
|
121
34
|
return true;
|
|
122
|
-
} catch {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
return false;
|
|
127
|
-
}
|
|
35
|
+
} catch (err) {
|
|
36
|
+
const code = err.code;
|
|
37
|
+
if (code === "ESRCH" || code === "EPERM") return true;
|
|
38
|
+
return false;
|
|
128
39
|
}
|
|
129
40
|
}
|
|
130
41
|
var LazyProcess = class {
|
|
@@ -138,17 +49,17 @@ var LazyProcess = class {
|
|
|
138
49
|
constructor(name, definition, logger) {
|
|
139
50
|
this.name = name;
|
|
140
51
|
this.definition = definition;
|
|
141
|
-
this.logger = logger;
|
|
52
|
+
this.logger = logger.withPrefix("SYS");
|
|
142
53
|
}
|
|
143
54
|
get state() {
|
|
144
55
|
return this._state;
|
|
145
56
|
}
|
|
146
|
-
start() {
|
|
57
|
+
async start() {
|
|
147
58
|
if (this._state === "running" || this._state === "starting") throw new Error(`Process "${this.name}" is already ${this._state}`);
|
|
148
59
|
if (this._state === "stopping") throw new Error(`Process "${this.name}" is currently stopping`);
|
|
149
60
|
this._state = "starting";
|
|
150
61
|
this.processExit = Promise.withResolvers();
|
|
151
|
-
this.logger.
|
|
62
|
+
this.logger.debug(`Starting process: ${this.definition.command}`);
|
|
152
63
|
try {
|
|
153
64
|
const env = this.definition.env ? {
|
|
154
65
|
...process.env,
|
|
@@ -161,28 +72,20 @@ var LazyProcess = class {
|
|
|
161
72
|
"ignore",
|
|
162
73
|
"pipe",
|
|
163
74
|
"pipe"
|
|
164
|
-
]
|
|
75
|
+
],
|
|
76
|
+
detached: true
|
|
165
77
|
});
|
|
166
78
|
this._state = "running";
|
|
167
|
-
const combined = new PassThrough();
|
|
168
|
-
let streamCount = 0;
|
|
169
79
|
if (this.childProcess.stdout) {
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
this.
|
|
173
|
-
if (--streamCount === 0) combined.end();
|
|
174
|
-
});
|
|
80
|
+
const rl = createInterface({ input: this.childProcess.stdout });
|
|
81
|
+
rl.on("line", (line) => this.logger.withPrefix("OUT").info(line));
|
|
82
|
+
this.processExit.promise.then(() => rl.close());
|
|
175
83
|
}
|
|
176
84
|
if (this.childProcess.stderr) {
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
this.
|
|
180
|
-
if (--streamCount === 0) combined.end();
|
|
181
|
-
});
|
|
85
|
+
const rl = createInterface({ input: this.childProcess.stderr });
|
|
86
|
+
rl.on("line", (line) => this.logger.withPrefix("ERR").info(line));
|
|
87
|
+
this.processExit.promise.then(() => rl.close());
|
|
182
88
|
}
|
|
183
|
-
readline.createInterface({ input: combined }).on("line", (line) => {
|
|
184
|
-
this.logger.info(line);
|
|
185
|
-
});
|
|
186
89
|
this.childProcess.on("exit", (code, signal) => {
|
|
187
90
|
this.exitCode = code;
|
|
188
91
|
if (this._state === "running") if (code === 0) {
|
|
@@ -221,28 +124,24 @@ var LazyProcess = class {
|
|
|
221
124
|
return;
|
|
222
125
|
}
|
|
223
126
|
this._state = "stopping";
|
|
224
|
-
this.
|
|
225
|
-
|
|
127
|
+
const pid = this.childProcess.pid;
|
|
128
|
+
if (pid === void 0) {
|
|
129
|
+
this._state = "stopped";
|
|
130
|
+
this.cleanup();
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
this.logger.debug(`Stopping process group (pid: ${pid}) with SIGTERM`);
|
|
226
134
|
const timeoutMs = timeout ?? 5e3;
|
|
227
|
-
const
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
135
|
+
const resultRace = Promise.race([this.processExit.promise.then(() => "exited"), setTimeout$1(timeoutMs, "timeout")]);
|
|
136
|
+
killProcessGroup(pid, "SIGTERM");
|
|
137
|
+
this.logger.debug(`Waiting for process exit (timeout: ${timeoutMs}ms)`);
|
|
138
|
+
const result = await resultRace;
|
|
139
|
+
this.logger.debug(`process exit result: ${result}`);
|
|
140
|
+
if (result === "timeout") {
|
|
233
141
|
this.logger.warn(`Process did not exit within ${timeoutMs}ms, sending SIGKILL (pid: ${pid})`);
|
|
234
|
-
const killed =
|
|
235
|
-
this.logger.info(`SIGKILL sent
|
|
236
|
-
|
|
237
|
-
this.logger.warn(`Tree-kill failed, attempting direct SIGKILL`);
|
|
238
|
-
try {
|
|
239
|
-
this.childProcess.kill("SIGKILL");
|
|
240
|
-
this.logger.info(`Direct SIGKILL sent`);
|
|
241
|
-
} catch (err) {
|
|
242
|
-
this.logger.error(`Direct SIGKILL failed:`, err);
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
const killTimeout = new Promise((resolve) => setTimeout(resolve, 1e3));
|
|
142
|
+
const killed = killProcessGroup(pid, "SIGKILL");
|
|
143
|
+
this.logger.info(`SIGKILL sent to process group: ${killed ? "success" : "failed"}`);
|
|
144
|
+
const killTimeout = setTimeout$1(1e3, "timeout");
|
|
246
145
|
await Promise.race([this.processExit.promise, killTimeout]);
|
|
247
146
|
if (this.childProcess && !this.childProcess.killed) this.logger.error(`Process still alive after SIGKILL (pid: ${pid})`);
|
|
248
147
|
}
|
|
@@ -251,8 +150,8 @@ var LazyProcess = class {
|
|
|
251
150
|
this.logger.info(`Process stopped`);
|
|
252
151
|
}
|
|
253
152
|
async reset() {
|
|
254
|
-
if (this.childProcess) {
|
|
255
|
-
|
|
153
|
+
if (this.childProcess?.pid !== void 0) {
|
|
154
|
+
killProcessGroup(this.childProcess.pid, "SIGKILL");
|
|
256
155
|
await this.processExit.promise;
|
|
257
156
|
this.cleanup();
|
|
258
157
|
}
|
|
@@ -270,8 +169,6 @@ var LazyProcess = class {
|
|
|
270
169
|
}
|
|
271
170
|
cleanup() {
|
|
272
171
|
if (this.childProcess) {
|
|
273
|
-
this.childProcess.stdout?.removeAllListeners();
|
|
274
|
-
this.childProcess.stderr?.removeAllListeners();
|
|
275
172
|
this.childProcess.removeAllListeners();
|
|
276
173
|
this.childProcess = null;
|
|
277
174
|
}
|
|
@@ -281,14 +178,14 @@ var LazyProcess = class {
|
|
|
281
178
|
|
|
282
179
|
//#endregion
|
|
283
180
|
//#region src/restarting-process.ts
|
|
284
|
-
const
|
|
181
|
+
const RestartPolicy = v.picklist([
|
|
285
182
|
"always",
|
|
286
183
|
"on-failure",
|
|
287
184
|
"never",
|
|
288
185
|
"unless-stopped",
|
|
289
186
|
"on-success"
|
|
290
187
|
]);
|
|
291
|
-
const
|
|
188
|
+
const BackoffStrategy = v.union([v.object({
|
|
292
189
|
type: v.literal("fixed"),
|
|
293
190
|
delayMs: v.number()
|
|
294
191
|
}), v.object({
|
|
@@ -297,19 +194,19 @@ const BackoffStrategySchema = v.union([v.object({
|
|
|
297
194
|
maxDelayMs: v.number(),
|
|
298
195
|
multiplier: v.optional(v.number())
|
|
299
196
|
})]);
|
|
300
|
-
const
|
|
197
|
+
const CrashLoopConfig = v.object({
|
|
301
198
|
maxRestarts: v.number(),
|
|
302
199
|
windowMs: v.number(),
|
|
303
200
|
backoffMs: v.number()
|
|
304
201
|
});
|
|
305
|
-
const
|
|
306
|
-
restartPolicy:
|
|
307
|
-
backoff: v.optional(
|
|
308
|
-
crashLoop: v.optional(
|
|
202
|
+
const RestartingProcessOptions = v.object({
|
|
203
|
+
restartPolicy: RestartPolicy,
|
|
204
|
+
backoff: v.optional(BackoffStrategy),
|
|
205
|
+
crashLoop: v.optional(CrashLoopConfig),
|
|
309
206
|
minUptimeMs: v.optional(v.number()),
|
|
310
207
|
maxTotalRestarts: v.optional(v.number())
|
|
311
208
|
});
|
|
312
|
-
const
|
|
209
|
+
const RestartingProcessState = v.picklist([
|
|
313
210
|
"idle",
|
|
314
211
|
"running",
|
|
315
212
|
"restarting",
|
|
@@ -330,7 +227,6 @@ const DEFAULT_CRASH_LOOP = {
|
|
|
330
227
|
var RestartingProcess = class {
|
|
331
228
|
name;
|
|
332
229
|
lazyProcess;
|
|
333
|
-
definition;
|
|
334
230
|
options;
|
|
335
231
|
logger;
|
|
336
232
|
_state = "idle";
|
|
@@ -342,7 +238,6 @@ var RestartingProcess = class {
|
|
|
342
238
|
pendingDelayTimeout = null;
|
|
343
239
|
constructor(name, definition, options, logger) {
|
|
344
240
|
this.name = name;
|
|
345
|
-
this.definition = definition;
|
|
346
241
|
this.logger = logger;
|
|
347
242
|
this.options = {
|
|
348
243
|
restartPolicy: options.restartPolicy,
|
|
@@ -407,7 +302,6 @@ var RestartingProcess = class {
|
|
|
407
302
|
*/
|
|
408
303
|
async reload(newDefinition, restartImmediately = true) {
|
|
409
304
|
this.logger.info(`Reloading process with new definition`);
|
|
410
|
-
this.definition = newDefinition;
|
|
411
305
|
this.lazyProcess.updateDefinition(newDefinition);
|
|
412
306
|
if (restartImmediately) await this.restart(true);
|
|
413
307
|
}
|
|
@@ -433,9 +327,9 @@ var RestartingProcess = class {
|
|
|
433
327
|
startProcess() {
|
|
434
328
|
this.lastStartTime = Date.now();
|
|
435
329
|
this._state = "running";
|
|
436
|
-
this.lazyProcess.reset().then(() => {
|
|
330
|
+
this.lazyProcess.reset().then(async () => {
|
|
437
331
|
if (this.stopRequested) return;
|
|
438
|
-
this.lazyProcess.start();
|
|
332
|
+
await this.lazyProcess.start();
|
|
439
333
|
return this.lazyProcess.waitForExit();
|
|
440
334
|
}).then((exitState) => {
|
|
441
335
|
if (!exitState) return;
|
|
@@ -542,16 +436,16 @@ var RestartingProcess = class {
|
|
|
542
436
|
|
|
543
437
|
//#endregion
|
|
544
438
|
//#region src/cron-process.ts
|
|
545
|
-
const
|
|
439
|
+
const RetryConfig = v.object({
|
|
546
440
|
maxRetries: v.number(),
|
|
547
441
|
delayMs: v.optional(v.number())
|
|
548
442
|
});
|
|
549
|
-
const
|
|
443
|
+
const CronProcessOptions = v.object({
|
|
550
444
|
schedule: v.string(),
|
|
551
|
-
retry: v.optional(
|
|
445
|
+
retry: v.optional(RetryConfig),
|
|
552
446
|
runOnStart: v.optional(v.boolean())
|
|
553
447
|
});
|
|
554
|
-
const
|
|
448
|
+
const CronProcessState = v.picklist([
|
|
555
449
|
"idle",
|
|
556
450
|
"scheduled",
|
|
557
451
|
"running",
|
|
@@ -657,7 +551,7 @@ var CronProcess = class {
|
|
|
657
551
|
async runJobWithRetry() {
|
|
658
552
|
if (this.stopRequested) return;
|
|
659
553
|
await this.lazyProcess.reset();
|
|
660
|
-
this.lazyProcess.start();
|
|
554
|
+
await this.lazyProcess.start();
|
|
661
555
|
const exitState = await this.lazyProcess.waitForExit();
|
|
662
556
|
if (this.stopRequested && exitState === "error") {
|
|
663
557
|
this._state = "stopped";
|
|
@@ -715,7 +609,7 @@ const TaskStateSchema = v.picklist([
|
|
|
715
609
|
]);
|
|
716
610
|
const NamedProcessDefinitionSchema = v.object({
|
|
717
611
|
name: v.string(),
|
|
718
|
-
process:
|
|
612
|
+
process: ProcessDefinition
|
|
719
613
|
});
|
|
720
614
|
var TaskList = class {
|
|
721
615
|
name;
|
|
@@ -828,7 +722,7 @@ var TaskList = class {
|
|
|
828
722
|
});
|
|
829
723
|
this.runningProcesses = lazyProcesses;
|
|
830
724
|
try {
|
|
831
|
-
|
|
725
|
+
await Promise.all(lazyProcesses.map((lp) => lp.start()));
|
|
832
726
|
const anyFailed = (await Promise.all(lazyProcesses.map((lp) => this.waitForProcess(lp)))).some((r) => r === "error");
|
|
833
727
|
if (this.stopRequested) task.state = "skipped";
|
|
834
728
|
else if (anyFailed) {
|
|
@@ -853,67 +747,96 @@ var TaskList = class {
|
|
|
853
747
|
//#endregion
|
|
854
748
|
//#region src/env-manager.ts
|
|
855
749
|
var EnvManager = class {
|
|
750
|
+
globalEnv = {};
|
|
751
|
+
globalEnvPath;
|
|
856
752
|
env = /* @__PURE__ */ new Map();
|
|
857
|
-
cwd;
|
|
858
|
-
watchEnabled;
|
|
859
753
|
watchers = /* @__PURE__ */ new Map();
|
|
860
|
-
|
|
754
|
+
fileToKey = /* @__PURE__ */ new Map();
|
|
861
755
|
changeCallbacks = /* @__PURE__ */ new Set();
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
this.
|
|
756
|
+
cwdWatcher = null;
|
|
757
|
+
customKeys = /* @__PURE__ */ new Set();
|
|
758
|
+
constructor(config) {
|
|
759
|
+
this.config = config;
|
|
760
|
+
this.globalEnvPath = config.globalEnvFile ? isAbsolute(config.globalEnvFile) ? config.globalEnvFile : resolve(config.cwd, config.globalEnvFile) : resolve(config.cwd, ".env");
|
|
761
|
+
this.loadCustomEnvFiles();
|
|
866
762
|
this.loadEnvFilesFromCwd();
|
|
867
|
-
|
|
763
|
+
this.watchCwdForNewFiles();
|
|
868
764
|
}
|
|
765
|
+
/**
|
|
766
|
+
* Register a custom env file for a key.
|
|
767
|
+
* Once registered, auto-discovered .env.{key} files will be ignored for this key.
|
|
768
|
+
*/
|
|
869
769
|
registerFile(key, filePath) {
|
|
870
|
-
this.
|
|
770
|
+
const absolutePath = isAbsolute(filePath) ? filePath : resolve(this.config.cwd, filePath);
|
|
771
|
+
this.customKeys.add(key);
|
|
772
|
+
this.loadEnvFile(key, absolutePath);
|
|
871
773
|
}
|
|
872
|
-
|
|
873
|
-
|
|
774
|
+
/**
|
|
775
|
+
* Check if a key has a custom env file registered
|
|
776
|
+
*/
|
|
777
|
+
hasCustomFile(key) {
|
|
778
|
+
return this.customKeys.has(key);
|
|
874
779
|
}
|
|
875
780
|
/**
|
|
876
|
-
* Load
|
|
781
|
+
* Load custom env files from config
|
|
877
782
|
*/
|
|
783
|
+
loadCustomEnvFiles() {
|
|
784
|
+
if (!this.config.customEnvFiles) return;
|
|
785
|
+
for (const [key, filePath] of Object.entries(this.config.customEnvFiles)) this.registerFile(key, filePath);
|
|
786
|
+
}
|
|
787
|
+
getEnvVars(key) {
|
|
788
|
+
const specificEnv = this.env.get(key);
|
|
789
|
+
return {
|
|
790
|
+
...this.globalEnv,
|
|
791
|
+
...specificEnv
|
|
792
|
+
};
|
|
793
|
+
}
|
|
878
794
|
loadEnvFilesFromCwd() {
|
|
879
|
-
|
|
880
|
-
if (existsSync(dotEnvPath)) this.loadEnvFile("global", dotEnvPath);
|
|
795
|
+
if (existsSync(this.globalEnvPath)) this.loadGlobalEnv(this.globalEnvPath);
|
|
881
796
|
try {
|
|
882
|
-
const envFiles = globSync(
|
|
797
|
+
const envFiles = globSync(".env.*", { cwd: this.config.cwd });
|
|
883
798
|
for (const filePath of envFiles) {
|
|
884
|
-
const
|
|
885
|
-
if (
|
|
886
|
-
const suffix = match[1];
|
|
887
|
-
this.loadEnvFile(suffix, filePath);
|
|
888
|
-
}
|
|
799
|
+
const key = this.getEnvKeySuffix(basename(filePath));
|
|
800
|
+
if (key && !this.customKeys.has(key)) this.loadEnvFile(key, resolve(this.config.cwd, filePath));
|
|
889
801
|
}
|
|
890
802
|
} catch (err) {
|
|
891
803
|
console.warn("Failed to scan env files:", err);
|
|
892
804
|
}
|
|
893
805
|
}
|
|
894
|
-
|
|
895
|
-
* Load a single env file and store it in the map
|
|
896
|
-
*/
|
|
897
|
-
loadEnvFile(key, filePath) {
|
|
898
|
-
const absolutePath = resolve(this.cwd, filePath);
|
|
899
|
-
if (!existsSync(absolutePath)) return;
|
|
806
|
+
parseEnvFile(absolutePath) {
|
|
900
807
|
try {
|
|
901
|
-
|
|
902
|
-
this.env.set(key, parsed);
|
|
903
|
-
if (!this.fileToKeys.has(absolutePath)) this.fileToKeys.set(absolutePath, /* @__PURE__ */ new Set());
|
|
904
|
-
this.fileToKeys.get(absolutePath).add(key);
|
|
905
|
-
if (this.watchEnabled && !this.watchers.has(absolutePath)) this.watchFile(absolutePath);
|
|
808
|
+
return parse(readFileSync(absolutePath, "utf-8")) ?? {};
|
|
906
809
|
} catch (err) {
|
|
907
|
-
console.warn(`Failed to
|
|
810
|
+
console.warn(`Failed to parse env file: ${absolutePath}`, err);
|
|
811
|
+
return null;
|
|
908
812
|
}
|
|
909
813
|
}
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
814
|
+
getEnvKeySuffix(fileName) {
|
|
815
|
+
const match = fileName.match(/^\.env\.(.+)$/);
|
|
816
|
+
return match ? match[1] : null;
|
|
817
|
+
}
|
|
818
|
+
loadGlobalEnv(absolutePath) {
|
|
819
|
+
if (!existsSync(absolutePath)) return;
|
|
820
|
+
const parsed = this.parseEnvFile(absolutePath);
|
|
821
|
+
if (parsed) {
|
|
822
|
+
this.globalEnv = parsed;
|
|
823
|
+
this.watchFile(absolutePath);
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
loadEnvFile(key, absolutePath) {
|
|
827
|
+
const parsed = this.parseEnvFile(absolutePath);
|
|
828
|
+
if (!parsed) return;
|
|
829
|
+
this.env.set(key, parsed);
|
|
830
|
+
this.fileToKey.set(absolutePath, key);
|
|
831
|
+
this.watchFile(absolutePath);
|
|
832
|
+
}
|
|
913
833
|
watchFile(absolutePath) {
|
|
834
|
+
if (this.watchers.has(absolutePath)) return;
|
|
914
835
|
try {
|
|
915
|
-
const watcher = watch(absolutePath, (
|
|
916
|
-
|
|
836
|
+
const watcher = watch(absolutePath, { ignoreInitial: true }).on("change", () => {
|
|
837
|
+
this.handleFileChange(absolutePath);
|
|
838
|
+
}).on("unlink", () => {
|
|
839
|
+
this.handleFileDelete(absolutePath);
|
|
917
840
|
});
|
|
918
841
|
this.watchers.set(absolutePath, watcher);
|
|
919
842
|
} catch (err) {
|
|
@@ -921,74 +844,91 @@ var EnvManager = class {
|
|
|
921
844
|
}
|
|
922
845
|
}
|
|
923
846
|
/**
|
|
924
|
-
*
|
|
847
|
+
* Watch cwd for new .env.* files
|
|
925
848
|
*/
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
849
|
+
watchCwdForNewFiles() {
|
|
850
|
+
try {
|
|
851
|
+
this.cwdWatcher = watch(this.config.cwd, {
|
|
852
|
+
ignoreInitial: true,
|
|
853
|
+
depth: 0
|
|
854
|
+
}).on("add", (filePath) => {
|
|
855
|
+
this.handleNewFile(filePath);
|
|
856
|
+
});
|
|
857
|
+
} catch (err) {
|
|
858
|
+
console.warn(`Failed to watch cwd for new env files:`, err);
|
|
859
|
+
}
|
|
934
860
|
}
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
console.warn(`Failed to reload env file: ${absolutePath}`, err);
|
|
861
|
+
handleFileChange(absolutePath) {
|
|
862
|
+
if (absolutePath === this.globalEnvPath) {
|
|
863
|
+
this.loadGlobalEnv(absolutePath);
|
|
864
|
+
this.notifyCallbacks({ type: "global" });
|
|
865
|
+
return;
|
|
866
|
+
}
|
|
867
|
+
const key = this.fileToKey.get(absolutePath);
|
|
868
|
+
if (!key) return;
|
|
869
|
+
const parsed = this.parseEnvFile(absolutePath);
|
|
870
|
+
if (!parsed) return;
|
|
871
|
+
this.env.set(key, parsed);
|
|
872
|
+
this.notifyCallbacks({
|
|
873
|
+
type: "process",
|
|
874
|
+
key
|
|
950
875
|
});
|
|
951
876
|
}
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
877
|
+
handleFileDelete(absolutePath) {
|
|
878
|
+
const watcher = this.watchers.get(absolutePath);
|
|
879
|
+
if (watcher) {
|
|
880
|
+
watcher.close();
|
|
881
|
+
this.watchers.delete(absolutePath);
|
|
882
|
+
}
|
|
883
|
+
if (absolutePath === this.globalEnvPath) {
|
|
884
|
+
this.globalEnv = {};
|
|
885
|
+
this.notifyCallbacks({ type: "global" });
|
|
886
|
+
return;
|
|
887
|
+
}
|
|
888
|
+
const key = this.fileToKey.get(absolutePath);
|
|
889
|
+
if (key) {
|
|
890
|
+
this.env.delete(key);
|
|
891
|
+
this.fileToKey.delete(absolutePath);
|
|
892
|
+
this.notifyCallbacks({
|
|
893
|
+
type: "process",
|
|
894
|
+
key
|
|
895
|
+
});
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
handleNewFile(filePath) {
|
|
899
|
+
const absolutePath = isAbsolute(filePath) ? filePath : resolve(this.config.cwd, filePath);
|
|
900
|
+
if (absolutePath === this.globalEnvPath) {
|
|
901
|
+
this.loadGlobalEnv(absolutePath);
|
|
902
|
+
this.notifyCallbacks({ type: "global" });
|
|
903
|
+
return;
|
|
904
|
+
}
|
|
905
|
+
const key = this.getEnvKeySuffix(basename(filePath));
|
|
906
|
+
if (key && !this.customKeys.has(key) && !this.env.has(key)) {
|
|
907
|
+
this.loadEnvFile(key, absolutePath);
|
|
908
|
+
this.notifyCallbacks({
|
|
909
|
+
type: "process",
|
|
910
|
+
key
|
|
911
|
+
});
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
notifyCallbacks(event) {
|
|
915
|
+
for (const callback of this.changeCallbacks) callback(event);
|
|
916
|
+
}
|
|
956
917
|
onChange(callback) {
|
|
957
918
|
this.changeCallbacks.add(callback);
|
|
958
919
|
return () => {
|
|
959
920
|
this.changeCallbacks.delete(callback);
|
|
960
921
|
};
|
|
961
922
|
}
|
|
962
|
-
|
|
963
|
-
* Stop watching all files and cleanup
|
|
964
|
-
*/
|
|
965
|
-
dispose() {
|
|
966
|
-
for (const timer of this.reloadDebounceTimers.values()) clearTimeout(timer);
|
|
967
|
-
this.reloadDebounceTimers.clear();
|
|
923
|
+
close() {
|
|
968
924
|
for (const watcher of this.watchers.values()) watcher.close();
|
|
969
925
|
this.watchers.clear();
|
|
926
|
+
if (this.cwdWatcher) {
|
|
927
|
+
this.cwdWatcher.close();
|
|
928
|
+
this.cwdWatcher = null;
|
|
929
|
+
}
|
|
970
930
|
this.changeCallbacks.clear();
|
|
971
931
|
}
|
|
972
|
-
/**
|
|
973
|
-
* Get environment variables for a specific process
|
|
974
|
-
* Merges global env with process-specific env
|
|
975
|
-
* Process-specific env variables override global ones
|
|
976
|
-
*/
|
|
977
|
-
getEnvVars(processKey) {
|
|
978
|
-
const globalEnv = this.env.get("global") ?? {};
|
|
979
|
-
if (!processKey) return { ...globalEnv };
|
|
980
|
-
const processEnv = this.env.get(processKey) ?? {};
|
|
981
|
-
return {
|
|
982
|
-
...globalEnv,
|
|
983
|
-
...processEnv
|
|
984
|
-
};
|
|
985
|
-
}
|
|
986
|
-
/**
|
|
987
|
-
* Get all loaded env maps (for debugging/inspection)
|
|
988
|
-
*/
|
|
989
|
-
getAllEnv() {
|
|
990
|
-
return this.env;
|
|
991
|
-
}
|
|
992
932
|
};
|
|
993
933
|
|
|
994
934
|
//#endregion
|
|
@@ -1025,36 +965,37 @@ const formatPrefixWithColor = (level, name, time) => {
|
|
|
1025
965
|
const levelTint = levelColors[level] ?? "";
|
|
1026
966
|
return `${colors.gray}[${timestamp}]${colors.reset} ${levelTint}${levelFormatted}${colors.reset} (${name})`;
|
|
1027
967
|
};
|
|
1028
|
-
const
|
|
1029
|
-
if (!
|
|
1030
|
-
|
|
968
|
+
const formatMessagePrefix = (prefix, colored) => {
|
|
969
|
+
if (!prefix) return "";
|
|
970
|
+
if (colored) return `${colors.bold}${colors.white}[${prefix}]${colors.reset} `;
|
|
971
|
+
return `[${prefix}]`;
|
|
1031
972
|
};
|
|
1032
973
|
const logLine = (config, level, args) => {
|
|
1033
|
-
const message =
|
|
974
|
+
const message = format(...args);
|
|
1034
975
|
const time = /* @__PURE__ */ new Date();
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
976
|
+
if (config.logFile) {
|
|
977
|
+
const plainLine = [
|
|
978
|
+
formatPrefixNoColor(level, config.name, time),
|
|
979
|
+
formatMessagePrefix(config.prefix, false),
|
|
980
|
+
message
|
|
981
|
+
].filter(Boolean).join(" ");
|
|
982
|
+
try {
|
|
983
|
+
appendFileSync(config.logFile, `${plainLine}\n`);
|
|
984
|
+
} catch {}
|
|
985
|
+
}
|
|
986
|
+
if (config.stdout) {
|
|
987
|
+
const coloredLine = [
|
|
988
|
+
formatPrefixWithColor(level, config.name, time),
|
|
989
|
+
formatMessagePrefix(config.prefix, true),
|
|
990
|
+
message
|
|
991
|
+
].filter(Boolean).join(" ");
|
|
992
|
+
console[level](coloredLine);
|
|
1052
993
|
}
|
|
1053
994
|
};
|
|
1054
|
-
const logger = (
|
|
995
|
+
const logger = (_config) => {
|
|
1055
996
|
const config = {
|
|
1056
997
|
stdout: true,
|
|
1057
|
-
...
|
|
998
|
+
..._config
|
|
1058
999
|
};
|
|
1059
1000
|
return {
|
|
1060
1001
|
info: (...args) => logLine(config, "info", args),
|
|
@@ -1065,10 +1006,14 @@ const logger = (input) => {
|
|
|
1065
1006
|
...config,
|
|
1066
1007
|
...overrides,
|
|
1067
1008
|
name: `${config.name}:${suffix}`
|
|
1009
|
+
}),
|
|
1010
|
+
withPrefix: (prefix) => logger({
|
|
1011
|
+
...config,
|
|
1012
|
+
prefix
|
|
1068
1013
|
})
|
|
1069
1014
|
};
|
|
1070
1015
|
};
|
|
1071
1016
|
|
|
1072
1017
|
//#endregion
|
|
1073
|
-
export {
|
|
1074
|
-
//# sourceMappingURL=logger-
|
|
1018
|
+
export { ProcessDefinition as _, TaskStateSchema as a, CronProcessState as c, CrashLoopConfig as d, RestartPolicy as f, LazyProcess as g, RestartingProcessState as h, TaskList as i, RetryConfig as l, RestartingProcessOptions as m, EnvManager as n, CronProcess as o, RestartingProcess as p, NamedProcessDefinitionSchema as r, CronProcessOptions as s, logger as t, BackoffStrategy as u, ProcessState as v };
|
|
1019
|
+
//# sourceMappingURL=logger-BU2RmetS.mjs.map
|