pid1 0.0.0-dev.0
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.d.mts +1 -0
- package/dist/cli.mjs +119 -0
- package/dist/cli.mjs.map +1 -0
- package/dist/client-Bdt88RU-.d.mts +217 -0
- package/dist/client-Bdt88RU-.d.mts.map +1 -0
- package/dist/client.d.mts +2 -0
- package/dist/client.mjs +12 -0
- package/dist/client.mjs.map +1 -0
- package/dist/config-BgRb4pSG.mjs +186 -0
- package/dist/config-BgRb4pSG.mjs.map +1 -0
- package/dist/config.d.mts +84 -0
- package/dist/config.d.mts.map +1 -0
- package/dist/config.mjs +3 -0
- package/dist/index.d.mts +36 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +5 -0
- package/dist/process-manager-gO56266Q.mjs +643 -0
- package/dist/process-manager-gO56266Q.mjs.map +1 -0
- package/package.json +52 -0
- package/src/api/client.ts +20 -0
- package/src/api/router.ts +150 -0
- package/src/api/server.ts +66 -0
- package/src/cli.ts +168 -0
- package/src/config.ts +89 -0
- package/src/env.ts +74 -0
- package/src/exec.ts +85 -0
- package/src/index.ts +14 -0
- package/src/logger.ts +155 -0
- package/src/process-manager.ts +632 -0
package/src/env.ts
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { readFileSync, existsSync } from "node:fs";
|
|
2
|
+
import { join, dirname } from "node:path";
|
|
3
|
+
import { parse } from "dotenv";
|
|
4
|
+
import { globalLogger } from "./logger";
|
|
5
|
+
|
|
6
|
+
export type EnvFileConfig = {
|
|
7
|
+
envFile?: string | false;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
const logger = globalLogger.child("env");
|
|
11
|
+
|
|
12
|
+
function loadEnvFile(path: string): Record<string, string> {
|
|
13
|
+
if (!existsSync(path)) {
|
|
14
|
+
return {};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
try {
|
|
18
|
+
const content = readFileSync(path, "utf-8");
|
|
19
|
+
const parsed = parse(content);
|
|
20
|
+
logger.debug(`Loaded env file: ${path}`);
|
|
21
|
+
return parsed;
|
|
22
|
+
} catch (error) {
|
|
23
|
+
logger.warn(`Failed to parse env file ${path}: ${error}`);
|
|
24
|
+
return {};
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export type LoadEnvOptions = {
|
|
29
|
+
configDir: string;
|
|
30
|
+
processName: string;
|
|
31
|
+
globalEnvFile?: string | false;
|
|
32
|
+
processEnvFile?: string | false;
|
|
33
|
+
configEnv?: Record<string, string>;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export function loadEnvForProcess(
|
|
37
|
+
options: LoadEnvOptions,
|
|
38
|
+
): Record<string, string> {
|
|
39
|
+
const {
|
|
40
|
+
configDir,
|
|
41
|
+
processName,
|
|
42
|
+
globalEnvFile,
|
|
43
|
+
processEnvFile,
|
|
44
|
+
configEnv = {},
|
|
45
|
+
} = options;
|
|
46
|
+
|
|
47
|
+
const baseEnv = Object.fromEntries(
|
|
48
|
+
Object.entries(process.env).filter(([, value]) => value !== undefined),
|
|
49
|
+
) as Record<string, string>;
|
|
50
|
+
|
|
51
|
+
let globalEnv: Record<string, string> = {};
|
|
52
|
+
if (globalEnvFile !== false) {
|
|
53
|
+
const globalPath = globalEnvFile ?? join(configDir, ".env");
|
|
54
|
+
globalEnv = loadEnvFile(globalPath);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
let processEnv: Record<string, string> = {};
|
|
58
|
+
if (processEnvFile !== false) {
|
|
59
|
+
const processPath =
|
|
60
|
+
processEnvFile ?? join(configDir, `.env.${processName}`);
|
|
61
|
+
processEnv = loadEnvFile(processPath);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
...baseEnv,
|
|
66
|
+
...globalEnv,
|
|
67
|
+
...configEnv,
|
|
68
|
+
...processEnv,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function getConfigDir(configPath: string): string {
|
|
73
|
+
return dirname(configPath);
|
|
74
|
+
}
|
package/src/exec.ts
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { processLogger } from "./logger";
|
|
2
|
+
import { x } from "tinyexec";
|
|
3
|
+
|
|
4
|
+
type ExecType = "task" | "process";
|
|
5
|
+
|
|
6
|
+
type ExecWithLoggingParams = {
|
|
7
|
+
name: string;
|
|
8
|
+
command: string;
|
|
9
|
+
args: string[];
|
|
10
|
+
env: Record<string, string>;
|
|
11
|
+
cwd: string;
|
|
12
|
+
logFile: string;
|
|
13
|
+
abortSignal?: AbortSignal;
|
|
14
|
+
type: ExecType;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
type TinyExecProcess = ReturnType<typeof x>;
|
|
18
|
+
|
|
19
|
+
export type SpawnedProcess = {
|
|
20
|
+
proc: TinyExecProcess;
|
|
21
|
+
done: Promise<number | null>;
|
|
22
|
+
kill: (signal?: NodeJS.Signals) => boolean;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export function spawnWithLogging({
|
|
26
|
+
name,
|
|
27
|
+
command,
|
|
28
|
+
args,
|
|
29
|
+
env,
|
|
30
|
+
cwd,
|
|
31
|
+
logFile,
|
|
32
|
+
abortSignal,
|
|
33
|
+
type,
|
|
34
|
+
}: ExecWithLoggingParams): SpawnedProcess {
|
|
35
|
+
const proc = x(command, args, {
|
|
36
|
+
nodeOptions: { cwd, env },
|
|
37
|
+
signal: abortSignal,
|
|
38
|
+
throwOnError: true,
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
const kill = (signal: NodeJS.Signals = "SIGTERM"): boolean => {
|
|
42
|
+
if (proc.pid) {
|
|
43
|
+
try {
|
|
44
|
+
process.kill(proc.pid, signal);
|
|
45
|
+
return true;
|
|
46
|
+
} catch {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return false;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const done = (async () => {
|
|
54
|
+
const logger = processLogger({ name: `${type}:${name}`, logFile });
|
|
55
|
+
|
|
56
|
+
logger.info(
|
|
57
|
+
`Starting: command=${command}, args=[${args.join(", ")}], cwd=${cwd}`,
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
for await (const line of proc) {
|
|
61
|
+
logger.info(line);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
await proc;
|
|
65
|
+
|
|
66
|
+
const exitCode = proc.exitCode;
|
|
67
|
+
|
|
68
|
+
if (
|
|
69
|
+
proc.aborted ||
|
|
70
|
+
proc.killed ||
|
|
71
|
+
exitCode === null ||
|
|
72
|
+
exitCode === undefined
|
|
73
|
+
) {
|
|
74
|
+
logger.info(`${name} was terminated`);
|
|
75
|
+
} else if (exitCode === 0) {
|
|
76
|
+
logger.info(`${name} completed successfully`);
|
|
77
|
+
} else {
|
|
78
|
+
logger.info(`${name} exited with code ${exitCode}`);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return proc.exitCode ?? null;
|
|
82
|
+
})();
|
|
83
|
+
|
|
84
|
+
return { proc, done, kill };
|
|
85
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export {
|
|
2
|
+
ProcessManager,
|
|
3
|
+
type ProcessManagerOptions,
|
|
4
|
+
type ProcessStatus,
|
|
5
|
+
type ProcessConfig,
|
|
6
|
+
type ManagedProcess,
|
|
7
|
+
type ProcessInfo,
|
|
8
|
+
} from "./process-manager.ts";
|
|
9
|
+
export { defineConfig, loadConfigFile } from "./config.ts";
|
|
10
|
+
export { globalLogger } from "./logger.ts";
|
|
11
|
+
export { spawnWithLogging, type SpawnedProcess } from "./exec.ts";
|
|
12
|
+
export { createServer, type ServerOptions, type Router } from "./api/server.ts";
|
|
13
|
+
export { createClient, type Client, type ClientOptions } from "./api/client.ts";
|
|
14
|
+
export { router } from "./api/router.ts";
|
package/src/logger.ts
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { appendFileSync, mkdirSync } from "node:fs";
|
|
2
|
+
import { dirname } from "node:path";
|
|
3
|
+
|
|
4
|
+
const ANSI_COLORS = {
|
|
5
|
+
GREEN: "\x1b[32m",
|
|
6
|
+
CYAN: "\x1b[36m",
|
|
7
|
+
YELLOW: "\x1b[33m",
|
|
8
|
+
RED: "\x1b[31m",
|
|
9
|
+
RESET: "\x1b[0m",
|
|
10
|
+
BOLD: "\x1b[1m",
|
|
11
|
+
GRAY: "\x1b[90m",
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const COLORS = {
|
|
15
|
+
debug: (text: string) => `${ANSI_COLORS.GREEN}${text}${ANSI_COLORS.RESET}`,
|
|
16
|
+
info: (text: string) => `${ANSI_COLORS.CYAN}${text}${ANSI_COLORS.RESET}`,
|
|
17
|
+
warn: (text: string) => `${ANSI_COLORS.YELLOW}${text}${ANSI_COLORS.RESET}`,
|
|
18
|
+
error: (text: string) => `${ANSI_COLORS.RED}${text}${ANSI_COLORS.RESET}`,
|
|
19
|
+
gray: (text: string) => `${ANSI_COLORS.GRAY}${text}${ANSI_COLORS.RESET}`,
|
|
20
|
+
bold: (text: string) => `${ANSI_COLORS.BOLD}${text}${ANSI_COLORS.RESET}`,
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const LOG_LEVELS = {
|
|
24
|
+
debug: 0,
|
|
25
|
+
info: 1,
|
|
26
|
+
warn: 2,
|
|
27
|
+
error: 3,
|
|
28
|
+
} as const;
|
|
29
|
+
|
|
30
|
+
type LogLevel = keyof typeof LOG_LEVELS;
|
|
31
|
+
|
|
32
|
+
function formatTime() {
|
|
33
|
+
return Intl.DateTimeFormat("en-US", {
|
|
34
|
+
hour: "2-digit",
|
|
35
|
+
minute: "2-digit",
|
|
36
|
+
second: "2-digit",
|
|
37
|
+
timeZone: "UTC",
|
|
38
|
+
hourCycle: "h23",
|
|
39
|
+
}).format(new Date());
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function formatLevel(level: LogLevel, color: boolean): string {
|
|
43
|
+
const upper = level.toUpperCase().padStart(5);
|
|
44
|
+
return color ? `${COLORS[level](upper)}` : upper;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export type LoggerOptions = {
|
|
48
|
+
name: string;
|
|
49
|
+
level?: LogLevel;
|
|
50
|
+
prettyLogFile?: string | null;
|
|
51
|
+
jsonLogFile?: string | null;
|
|
52
|
+
stdout?: boolean;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export class Logger {
|
|
56
|
+
private name: string;
|
|
57
|
+
private level: number;
|
|
58
|
+
private prettyLogFile?: string | null;
|
|
59
|
+
private jsonLogFile?: string | null;
|
|
60
|
+
private stdout: boolean;
|
|
61
|
+
|
|
62
|
+
constructor(options: LoggerOptions) {
|
|
63
|
+
this.name = options.name;
|
|
64
|
+
this.level = LOG_LEVELS[options.level ?? "info"];
|
|
65
|
+
this.prettyLogFile = options.prettyLogFile;
|
|
66
|
+
this.jsonLogFile = options.jsonLogFile;
|
|
67
|
+
this.stdout = options.stdout ?? true;
|
|
68
|
+
|
|
69
|
+
if (this.prettyLogFile) {
|
|
70
|
+
mkdirSync(dirname(this.prettyLogFile), { recursive: true });
|
|
71
|
+
}
|
|
72
|
+
if (this.jsonLogFile) {
|
|
73
|
+
mkdirSync(dirname(this.jsonLogFile), { recursive: true });
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
private log(level: LogLevel, message: string): void {
|
|
78
|
+
if (LOG_LEVELS[level] < this.level) return;
|
|
79
|
+
|
|
80
|
+
const time = formatTime();
|
|
81
|
+
|
|
82
|
+
if (this.stdout) {
|
|
83
|
+
const colorLevel = formatLevel(level, true);
|
|
84
|
+
console.log(
|
|
85
|
+
`${COLORS.gray(`[${time}]`)} ${colorLevel} ${COLORS.bold(`(${this.name})`)} ${message}`,
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (this.prettyLogFile) {
|
|
90
|
+
const plainLevel = formatLevel(level, false);
|
|
91
|
+
const line = `[${time}] ${plainLevel} (${this.name}) ${message}\n`;
|
|
92
|
+
appendFileSync(this.prettyLogFile, line);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (this.jsonLogFile) {
|
|
96
|
+
const logObj = {
|
|
97
|
+
time: new Date().toISOString(),
|
|
98
|
+
level,
|
|
99
|
+
name: this.name,
|
|
100
|
+
msg: message,
|
|
101
|
+
};
|
|
102
|
+
appendFileSync(this.jsonLogFile, JSON.stringify(logObj) + "\n");
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
debug(message: string): void {
|
|
107
|
+
this.log("debug", message);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
info(message: string): void {
|
|
111
|
+
this.log("info", message);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
warn(message: string): void {
|
|
115
|
+
this.log("warn", message);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
error(message: string): void {
|
|
119
|
+
this.log("error", message);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
child(name: string, options: Partial<LoggerOptions> = {}): Logger {
|
|
123
|
+
const hasPrettyLogFile = "prettyLogFile" in options;
|
|
124
|
+
const hasJsonLogFile = "jsonLogFile" in options;
|
|
125
|
+
const hasStdout = "stdout" in options;
|
|
126
|
+
|
|
127
|
+
return new Logger({
|
|
128
|
+
name: `${this.name}:${name}`,
|
|
129
|
+
level: options.level ?? (Object.keys(LOG_LEVELS)[this.level] as LogLevel),
|
|
130
|
+
prettyLogFile: hasPrettyLogFile ? options.prettyLogFile : this.prettyLogFile,
|
|
131
|
+
jsonLogFile: hasJsonLogFile ? options.jsonLogFile : this.jsonLogFile,
|
|
132
|
+
stdout: hasStdout ? options.stdout : this.stdout,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
mkdirSync("logs", { recursive: true });
|
|
138
|
+
export const globalLogger = new Logger({
|
|
139
|
+
name: "pid1",
|
|
140
|
+
level: "debug",
|
|
141
|
+
jsonLogFile: "logs/pid1.log",
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
type ProcessLoggerParams = {
|
|
145
|
+
name: string;
|
|
146
|
+
logFile: string;
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
export function processLogger({ name, logFile }: ProcessLoggerParams): Logger {
|
|
150
|
+
mkdirSync(dirname(logFile), { recursive: true });
|
|
151
|
+
return globalLogger.child(name, {
|
|
152
|
+
prettyLogFile: logFile,
|
|
153
|
+
jsonLogFile: null,
|
|
154
|
+
});
|
|
155
|
+
}
|