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/dist/cli.d.mts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|
package/dist/cli.mjs
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { i as globalLogger } from "./config-BgRb4pSG.mjs";
|
|
3
|
+
import { t as ProcessManager } from "./process-manager-gO56266Q.mjs";
|
|
4
|
+
import { createClient } from "./client.mjs";
|
|
5
|
+
import Table from "cli-table3";
|
|
6
|
+
import { Command } from "commander";
|
|
7
|
+
|
|
8
|
+
//#region src/cli.ts
|
|
9
|
+
const logger = globalLogger.child("cli");
|
|
10
|
+
const defaultPort = Number(process.env.PID1_PORT ?? process.env.PORT ?? 3e3);
|
|
11
|
+
const envHost = process.env.PID1_HOST ?? process.env.HOST;
|
|
12
|
+
function formatTable(rows) {
|
|
13
|
+
if (rows.length === 0) {
|
|
14
|
+
console.log("No processes registered");
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
const table = new Table({
|
|
18
|
+
head: [
|
|
19
|
+
"ID",
|
|
20
|
+
"NAME",
|
|
21
|
+
"STATUS",
|
|
22
|
+
"PID",
|
|
23
|
+
"RESTARTS",
|
|
24
|
+
"STARTED"
|
|
25
|
+
],
|
|
26
|
+
style: {
|
|
27
|
+
head: [],
|
|
28
|
+
border: []
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
for (const row of rows) table.push([
|
|
32
|
+
String(row.id),
|
|
33
|
+
String(row.name),
|
|
34
|
+
String(row.status),
|
|
35
|
+
row.pid ? String(row.pid) : "-",
|
|
36
|
+
String(row.restarts),
|
|
37
|
+
row.startedAt ? new Date(row.startedAt).toLocaleString() : "-"
|
|
38
|
+
]);
|
|
39
|
+
console.log(table.toString());
|
|
40
|
+
}
|
|
41
|
+
async function runInit(argv) {
|
|
42
|
+
const host = argv.host ?? envHost ?? "0.0.0.0";
|
|
43
|
+
const pm = new ProcessManager({
|
|
44
|
+
cwd: argv.cwd,
|
|
45
|
+
configPath: argv.config,
|
|
46
|
+
server: {
|
|
47
|
+
port: argv.port ?? defaultPort,
|
|
48
|
+
host
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
pm.serve();
|
|
52
|
+
await pm.init();
|
|
53
|
+
pm.startAll();
|
|
54
|
+
await pm.wait();
|
|
55
|
+
}
|
|
56
|
+
function createRpcClient(argv) {
|
|
57
|
+
return createClient({ url: `http://${argv.host ?? envHost ?? "127.0.0.1"}:${argv.port ?? defaultPort}/rpc` });
|
|
58
|
+
}
|
|
59
|
+
async function main() {
|
|
60
|
+
const program = new Command();
|
|
61
|
+
program.name("pid1").description("Process manager daemon and controller").option("--port <number>", "Port for the HTTP server", (value) => Number(value)).option("--host <host>", "Host for the HTTP server");
|
|
62
|
+
const commonOptions = () => {
|
|
63
|
+
const opts = program.opts();
|
|
64
|
+
return {
|
|
65
|
+
port: opts.port,
|
|
66
|
+
host: opts.host
|
|
67
|
+
};
|
|
68
|
+
};
|
|
69
|
+
program.command("init").description("Start the daemon, run tasks, and start all processes").option("--cwd <path>", "Working directory for config lookup").option("--config <path>", "Path to config file").action(async (options) => {
|
|
70
|
+
await runInit({
|
|
71
|
+
...commonOptions(),
|
|
72
|
+
...options
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
program.command("start [name]").description("Start all processes, or a specific process by name or id").action(async (name) => {
|
|
76
|
+
const result = await createRpcClient(commonOptions()).process.start({ name });
|
|
77
|
+
console.log(result.message);
|
|
78
|
+
});
|
|
79
|
+
program.command("stop [name]").description("Stop all processes, or a specific process by name or id").action(async (name) => {
|
|
80
|
+
const result = await createRpcClient(commonOptions()).process.stop({ name });
|
|
81
|
+
console.log(result.message);
|
|
82
|
+
});
|
|
83
|
+
program.command("restart [name]").description("Restart all processes, or a specific process by name or id").action(async (name) => {
|
|
84
|
+
const result = await createRpcClient(commonOptions()).process.restart({ name });
|
|
85
|
+
console.log(result.message);
|
|
86
|
+
});
|
|
87
|
+
program.command("delete [name]").description("Delete all processes, or a specific process by name or id").action(async (name) => {
|
|
88
|
+
const result = await createRpcClient(commonOptions()).process.delete({ name });
|
|
89
|
+
console.log(result.message);
|
|
90
|
+
});
|
|
91
|
+
program.command("list").description("List all managed processes").action(async () => {
|
|
92
|
+
formatTable(await createRpcClient(commonOptions()).process.list({}));
|
|
93
|
+
});
|
|
94
|
+
program.command("get <name>").description("Get a process by name or id").action(async (name) => {
|
|
95
|
+
const process = await createRpcClient(commonOptions()).process.get({ name });
|
|
96
|
+
if (!process) {
|
|
97
|
+
console.log(`Process not found: ${name}`);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
formatTable([process]);
|
|
101
|
+
});
|
|
102
|
+
if (process.argv.length <= 2) {
|
|
103
|
+
program.outputHelp();
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
await program.parseAsync(process.argv);
|
|
107
|
+
}
|
|
108
|
+
main().catch((error) => {
|
|
109
|
+
if (error.cause?.code === "ECONNREFUSED") {
|
|
110
|
+
console.error("Error: Could not connect to pid1 daemon. Is it running? Start it with: pid1 init");
|
|
111
|
+
process.exit(1);
|
|
112
|
+
}
|
|
113
|
+
logger.error(error instanceof Error ? error.stack ?? error.message : String(error));
|
|
114
|
+
process.exit(1);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
//#endregion
|
|
118
|
+
export { };
|
|
119
|
+
//# sourceMappingURL=cli.mjs.map
|
package/dist/cli.mjs.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.mjs","names":[],"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\nimport Table from \"cli-table3\";\nimport { Command } from \"commander\";\nimport { ProcessManager } from \"./process-manager.ts\";\nimport { createClient } from \"./api/client.ts\";\nimport { globalLogger } from \"./logger.ts\";\n\nconst logger = globalLogger.child(\"cli\");\nconst defaultPort = Number(process.env.PID1_PORT ?? process.env.PORT ?? 3000);\nconst envHost = process.env.PID1_HOST ?? process.env.HOST;\n\nfunction formatTable(rows: Array<Record<string, unknown>>): void {\n if (rows.length === 0) {\n console.log(\"No processes registered\");\n return;\n }\n\n const table = new Table({\n head: [\"ID\", \"NAME\", \"STATUS\", \"PID\", \"RESTARTS\", \"STARTED\"],\n style: { head: [], border: [] },\n });\n\n for (const row of rows) {\n table.push([\n String(row.id),\n String(row.name),\n String(row.status),\n row.pid ? String(row.pid) : \"-\",\n String(row.restarts),\n row.startedAt ? new Date(row.startedAt as string).toLocaleString() : \"-\",\n ]);\n }\n\n console.log(table.toString());\n}\n\ntype CommonArgs = {\n port?: number;\n host?: string;\n};\ntype InitArgs = CommonArgs & {\n cwd?: string;\n config?: string;\n};\n\nasync function runInit(argv: InitArgs) {\n const host = argv.host ?? envHost ?? \"0.0.0.0\";\n const pm = new ProcessManager({\n cwd: argv.cwd,\n configPath: argv.config,\n server: { port: argv.port ?? defaultPort, host },\n });\n pm.serve();\n await pm.init();\n pm.startAll();\n await pm.wait();\n}\n\nfunction createRpcClient(argv: CommonArgs) {\n const host = argv.host ?? envHost ?? \"127.0.0.1\";\n const port = argv.port ?? defaultPort;\n return createClient({ url: `http://${host}:${port}/rpc` });\n}\n\nasync function main() {\n const program = new Command();\n program\n .name(\"pid1\")\n .description(\"Process manager daemon and controller\")\n .option(\"--port <number>\", \"Port for the HTTP server\", (value) =>\n Number(value),\n )\n .option(\"--host <host>\", \"Host for the HTTP server\");\n\n const commonOptions = (): CommonArgs => {\n const opts = program.opts<CommonArgs>();\n return {\n port: opts.port,\n host: opts.host,\n };\n };\n\n program\n .command(\"init\")\n .description(\"Start the daemon, run tasks, and start all processes\")\n .option(\"--cwd <path>\", \"Working directory for config lookup\")\n .option(\"--config <path>\", \"Path to config file\")\n .action(async (options: InitArgs) => {\n await runInit({ ...commonOptions(), ...options });\n });\n\n program\n .command(\"start [name]\")\n .description(\"Start all processes, or a specific process by name or id\")\n .action(async (name?: string) => {\n const client = createRpcClient(commonOptions());\n const result = await client.process.start({ name });\n console.log(result.message);\n });\n\n program\n .command(\"stop [name]\")\n .description(\"Stop all processes, or a specific process by name or id\")\n .action(async (name?: string) => {\n const client = createRpcClient(commonOptions());\n const result = await client.process.stop({ name });\n console.log(result.message);\n });\n\n program\n .command(\"restart [name]\")\n .description(\"Restart all processes, or a specific process by name or id\")\n .action(async (name?: string) => {\n const client = createRpcClient(commonOptions());\n const result = await client.process.restart({ name });\n console.log(result.message);\n });\n\n program\n .command(\"delete [name]\")\n .description(\"Delete all processes, or a specific process by name or id\")\n .action(async (name?: string) => {\n const client = createRpcClient(commonOptions());\n const result = await client.process.delete({ name });\n console.log(result.message);\n });\n\n program\n .command(\"list\")\n .description(\"List all managed processes\")\n .action(async () => {\n const client = createRpcClient(commonOptions());\n const processes = await client.process.list({});\n formatTable(processes);\n });\n\n program\n .command(\"get <name>\")\n .description(\"Get a process by name or id\")\n .action(async (name: string) => {\n const client = createRpcClient(commonOptions());\n const process = await client.process.get({ name });\n if (!process) {\n console.log(`Process not found: ${name}`);\n return;\n }\n formatTable([process]);\n });\n\n if (process.argv.length <= 2) {\n program.outputHelp();\n return;\n }\n\n await program.parseAsync(process.argv);\n}\n\nmain().catch((error) => {\n if (error.cause?.code === \"ECONNREFUSED\") {\n console.error(\n \"Error: Could not connect to pid1 daemon. Is it running? Start it with: pid1 init\",\n );\n process.exit(1);\n }\n logger.error(error instanceof Error ? error.stack ?? error.message : String(error));\n process.exit(1);\n});\n"],"mappings":"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"}
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import * as v from "valibot";
|
|
2
|
+
import { x } from "tinyexec";
|
|
3
|
+
import { Hono } from "hono";
|
|
4
|
+
import * as _hono_node_server0 from "@hono/node-server";
|
|
5
|
+
import * as _orpc_server0 from "@orpc/server";
|
|
6
|
+
import { RouterClient } from "@orpc/server";
|
|
7
|
+
import * as _orpc_contract0 from "@orpc/contract";
|
|
8
|
+
import * as hono_types0 from "hono/types";
|
|
9
|
+
|
|
10
|
+
//#region src/exec.d.ts
|
|
11
|
+
type ExecType = "task" | "process";
|
|
12
|
+
type ExecWithLoggingParams = {
|
|
13
|
+
name: string;
|
|
14
|
+
command: string;
|
|
15
|
+
args: string[];
|
|
16
|
+
env: Record<string, string>;
|
|
17
|
+
cwd: string;
|
|
18
|
+
logFile: string;
|
|
19
|
+
abortSignal?: AbortSignal;
|
|
20
|
+
type: ExecType;
|
|
21
|
+
};
|
|
22
|
+
type TinyExecProcess = ReturnType<typeof x>;
|
|
23
|
+
type SpawnedProcess = {
|
|
24
|
+
proc: TinyExecProcess;
|
|
25
|
+
done: Promise<number | null>;
|
|
26
|
+
kill: (signal?: NodeJS.Signals) => boolean;
|
|
27
|
+
};
|
|
28
|
+
declare function spawnWithLogging({
|
|
29
|
+
name,
|
|
30
|
+
command,
|
|
31
|
+
args,
|
|
32
|
+
env,
|
|
33
|
+
cwd,
|
|
34
|
+
logFile,
|
|
35
|
+
abortSignal,
|
|
36
|
+
type
|
|
37
|
+
}: ExecWithLoggingParams): SpawnedProcess;
|
|
38
|
+
//#endregion
|
|
39
|
+
//#region src/api/server.d.ts
|
|
40
|
+
type ServerOptions = {
|
|
41
|
+
port?: number;
|
|
42
|
+
host?: string;
|
|
43
|
+
};
|
|
44
|
+
declare function createServer(pm: ProcessManager, options?: ServerOptions): {
|
|
45
|
+
app: Hono<hono_types0.BlankEnv, hono_types0.BlankSchema, "/">;
|
|
46
|
+
start: () => _hono_node_server0.ServerType;
|
|
47
|
+
};
|
|
48
|
+
//#endregion
|
|
49
|
+
//#region src/process-manager.d.ts
|
|
50
|
+
type ProcessManagerOptions = {
|
|
51
|
+
cwd?: string;
|
|
52
|
+
configPath?: string;
|
|
53
|
+
server?: ServerOptions & {
|
|
54
|
+
enabled?: boolean;
|
|
55
|
+
};
|
|
56
|
+
};
|
|
57
|
+
type ProcessStatus = "stopped" | "running" | "errored";
|
|
58
|
+
type RestartPolicy = "never" | "always" | "on-failure";
|
|
59
|
+
type ProcessConfig = {
|
|
60
|
+
name: string;
|
|
61
|
+
command: string;
|
|
62
|
+
args: string[];
|
|
63
|
+
env: Record<string, string>;
|
|
64
|
+
cwd?: string;
|
|
65
|
+
envFile?: string | false;
|
|
66
|
+
restartOnEnvChange?: boolean;
|
|
67
|
+
restartPolicy?: RestartPolicy;
|
|
68
|
+
maxRestarts?: number;
|
|
69
|
+
restartMinDelayMs?: number;
|
|
70
|
+
restartMaxDelayMs?: number;
|
|
71
|
+
restartResetSeconds?: number;
|
|
72
|
+
};
|
|
73
|
+
type ManagedProcess = {
|
|
74
|
+
id: number;
|
|
75
|
+
config: ProcessConfig;
|
|
76
|
+
status: ProcessStatus;
|
|
77
|
+
spawned: SpawnedProcess | null;
|
|
78
|
+
exitCode: number | null;
|
|
79
|
+
restarts: number;
|
|
80
|
+
restartAttempts: number;
|
|
81
|
+
restartTimer: NodeJS.Timeout | null;
|
|
82
|
+
stopRequested: boolean;
|
|
83
|
+
startedAt: Date | null;
|
|
84
|
+
};
|
|
85
|
+
type ProcessInfo = {
|
|
86
|
+
id: number;
|
|
87
|
+
name: string;
|
|
88
|
+
status: ProcessStatus;
|
|
89
|
+
pid: number | undefined;
|
|
90
|
+
exitCode: number | null;
|
|
91
|
+
restarts: number;
|
|
92
|
+
startedAt: Date | null;
|
|
93
|
+
command: string;
|
|
94
|
+
args: string[];
|
|
95
|
+
};
|
|
96
|
+
declare class ProcessManager {
|
|
97
|
+
private logger;
|
|
98
|
+
private cwd;
|
|
99
|
+
private configPath?;
|
|
100
|
+
private configDir?;
|
|
101
|
+
private globalEnvFile?;
|
|
102
|
+
private processes;
|
|
103
|
+
private processIds;
|
|
104
|
+
private nextProcessId;
|
|
105
|
+
private signalHandlersSetup;
|
|
106
|
+
private serverOptions?;
|
|
107
|
+
private httpServer?;
|
|
108
|
+
private envWatcher;
|
|
109
|
+
private envChangeTimers;
|
|
110
|
+
private envRestartTimers;
|
|
111
|
+
private restartOnEnvChangeGlobal;
|
|
112
|
+
private restartMinDelayMsGlobal;
|
|
113
|
+
private restartMaxDelayMsGlobal;
|
|
114
|
+
private currentTask;
|
|
115
|
+
private currentTaskName;
|
|
116
|
+
private taskAbortController;
|
|
117
|
+
constructor(options?: ProcessManagerOptions);
|
|
118
|
+
init(): Promise<void>;
|
|
119
|
+
register(config: ProcessConfig): void;
|
|
120
|
+
list(): ProcessInfo[];
|
|
121
|
+
start(name: string): void;
|
|
122
|
+
startAll(): void;
|
|
123
|
+
serve(options?: ServerOptions & {
|
|
124
|
+
enabled?: boolean;
|
|
125
|
+
}): void;
|
|
126
|
+
stop(name: string, timeoutMs?: number): Promise<void>;
|
|
127
|
+
stopAll(): Promise<void>;
|
|
128
|
+
restart(name: string): Promise<void>;
|
|
129
|
+
restartAll(): Promise<void>;
|
|
130
|
+
delete(name: string): Promise<void>;
|
|
131
|
+
deleteAll(): Promise<void>;
|
|
132
|
+
get(name: string): ProcessInfo | undefined;
|
|
133
|
+
wait(): Promise<void>;
|
|
134
|
+
shutdown(): Promise<void>;
|
|
135
|
+
private spawnProcess;
|
|
136
|
+
private runTasks;
|
|
137
|
+
private clearRestartTimer;
|
|
138
|
+
private stopActiveTask;
|
|
139
|
+
private abortActiveTask;
|
|
140
|
+
private setupSignalHandlers;
|
|
141
|
+
private setupEnvWatchers;
|
|
142
|
+
private closeEnvWatcher;
|
|
143
|
+
private scheduleEnvRestart;
|
|
144
|
+
private scheduleEnvRestartForProcess;
|
|
145
|
+
private resolveProcessIdentifier;
|
|
146
|
+
}
|
|
147
|
+
//#endregion
|
|
148
|
+
//#region src/api/router.d.ts
|
|
149
|
+
type RouterContext = {
|
|
150
|
+
pm: ProcessManager;
|
|
151
|
+
};
|
|
152
|
+
declare const router: {
|
|
153
|
+
health: _orpc_server0.DecoratedProcedure<Record<never, never>, Record<never, never>, _orpc_contract0.Schema<unknown, unknown>, v.ObjectSchema<{
|
|
154
|
+
readonly status: v.LiteralSchema<"ok", undefined>;
|
|
155
|
+
}, undefined>, Record<never, never>, Record<never, never>>;
|
|
156
|
+
process: {
|
|
157
|
+
list: _orpc_server0.DecoratedProcedure<RouterContext & Record<never, never>, RouterContext, _orpc_contract0.Schema<unknown, unknown>, v.ArraySchema<v.ObjectSchema<{
|
|
158
|
+
readonly id: v.NumberSchema<undefined>;
|
|
159
|
+
readonly name: v.StringSchema<undefined>;
|
|
160
|
+
readonly status: v.PicklistSchema<["stopped", "running", "errored"], undefined>;
|
|
161
|
+
readonly pid: v.OptionalSchema<v.NumberSchema<undefined>, undefined>;
|
|
162
|
+
readonly exitCode: v.NullableSchema<v.NumberSchema<undefined>, undefined>;
|
|
163
|
+
readonly restarts: v.NumberSchema<undefined>;
|
|
164
|
+
readonly startedAt: v.NullableSchema<v.DateSchema<undefined>, undefined>;
|
|
165
|
+
readonly command: v.StringSchema<undefined>;
|
|
166
|
+
readonly args: v.ArraySchema<v.StringSchema<undefined>, undefined>;
|
|
167
|
+
}, undefined>, undefined>, Record<never, never>, Record<never, never>>;
|
|
168
|
+
get: _orpc_server0.DecoratedProcedure<RouterContext & Record<never, never>, RouterContext, v.ObjectSchema<{
|
|
169
|
+
readonly name: v.StringSchema<undefined>;
|
|
170
|
+
}, undefined>, v.NullableSchema<v.ObjectSchema<{
|
|
171
|
+
readonly id: v.NumberSchema<undefined>;
|
|
172
|
+
readonly name: v.StringSchema<undefined>;
|
|
173
|
+
readonly status: v.PicklistSchema<["stopped", "running", "errored"], undefined>;
|
|
174
|
+
readonly pid: v.OptionalSchema<v.NumberSchema<undefined>, undefined>;
|
|
175
|
+
readonly exitCode: v.NullableSchema<v.NumberSchema<undefined>, undefined>;
|
|
176
|
+
readonly restarts: v.NumberSchema<undefined>;
|
|
177
|
+
readonly startedAt: v.NullableSchema<v.DateSchema<undefined>, undefined>;
|
|
178
|
+
readonly command: v.StringSchema<undefined>;
|
|
179
|
+
readonly args: v.ArraySchema<v.StringSchema<undefined>, undefined>;
|
|
180
|
+
}, undefined>, undefined>, Record<never, never>, Record<never, never>>;
|
|
181
|
+
start: _orpc_server0.DecoratedProcedure<RouterContext & Record<never, never>, RouterContext, v.ObjectSchema<{
|
|
182
|
+
readonly name: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
183
|
+
}, undefined>, v.ObjectSchema<{
|
|
184
|
+
readonly success: v.BooleanSchema<undefined>;
|
|
185
|
+
readonly message: v.StringSchema<undefined>;
|
|
186
|
+
}, undefined>, Record<never, never>, Record<never, never>>;
|
|
187
|
+
stop: _orpc_server0.DecoratedProcedure<RouterContext & Record<never, never>, RouterContext, v.ObjectSchema<{
|
|
188
|
+
readonly name: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
189
|
+
}, undefined>, v.ObjectSchema<{
|
|
190
|
+
readonly success: v.BooleanSchema<undefined>;
|
|
191
|
+
readonly message: v.StringSchema<undefined>;
|
|
192
|
+
}, undefined>, Record<never, never>, Record<never, never>>;
|
|
193
|
+
restart: _orpc_server0.DecoratedProcedure<RouterContext & Record<never, never>, RouterContext, v.ObjectSchema<{
|
|
194
|
+
readonly name: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
195
|
+
}, undefined>, v.ObjectSchema<{
|
|
196
|
+
readonly success: v.BooleanSchema<undefined>;
|
|
197
|
+
readonly message: v.StringSchema<undefined>;
|
|
198
|
+
}, undefined>, Record<never, never>, Record<never, never>>;
|
|
199
|
+
delete: _orpc_server0.DecoratedProcedure<RouterContext & Record<never, never>, RouterContext, v.ObjectSchema<{
|
|
200
|
+
readonly name: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
201
|
+
}, undefined>, v.ObjectSchema<{
|
|
202
|
+
readonly success: v.BooleanSchema<undefined>;
|
|
203
|
+
readonly message: v.StringSchema<undefined>;
|
|
204
|
+
}, undefined>, Record<never, never>, Record<never, never>>;
|
|
205
|
+
};
|
|
206
|
+
};
|
|
207
|
+
type Router = typeof router;
|
|
208
|
+
//#endregion
|
|
209
|
+
//#region src/api/client.d.ts
|
|
210
|
+
type ClientOptions = {
|
|
211
|
+
url?: string;
|
|
212
|
+
};
|
|
213
|
+
declare function createClient(options?: ClientOptions): RouterClient<Router>;
|
|
214
|
+
type Client = RouterClient<Router>;
|
|
215
|
+
//#endregion
|
|
216
|
+
export { router as a, ProcessInfo as c, ProcessStatus as d, ServerOptions as f, spawnWithLogging as h, Router as i, ProcessManager as l, SpawnedProcess as m, ClientOptions as n, ManagedProcess as o, createServer as p, createClient as r, ProcessConfig as s, Client as t, ProcessManagerOptions as u };
|
|
217
|
+
//# sourceMappingURL=client-Bdt88RU-.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client-Bdt88RU-.d.mts","names":[],"sources":["../src/exec.ts","../src/api/server.ts","../src/process-manager.ts","../src/api/router.ts","../src/api/client.ts"],"mappings":"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"}
|
package/dist/client.mjs
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { createORPCClient } from "@orpc/client";
|
|
2
|
+
import { RPCLink } from "@orpc/client/fetch";
|
|
3
|
+
|
|
4
|
+
//#region src/api/client.ts
|
|
5
|
+
function createClient(options = {}) {
|
|
6
|
+
const { url = "http://127.0.0.1:3000/rpc" } = options;
|
|
7
|
+
return createORPCClient(new RPCLink({ url }));
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
//#endregion
|
|
11
|
+
export { createClient };
|
|
12
|
+
//# sourceMappingURL=client.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.mjs","names":[],"sources":["../src/api/client.ts"],"sourcesContent":["import type { RouterClient } from \"@orpc/server\";\nimport { createORPCClient } from \"@orpc/client\";\nimport { RPCLink } from \"@orpc/client/fetch\";\nimport type { Router } from \"./router.ts\";\n\nexport type ClientOptions = {\n url?: string;\n};\n\nexport function createClient(\n options: ClientOptions = {},\n): RouterClient<Router> {\n const { url = \"http://127.0.0.1:3000/rpc\" } = options;\n\n const link = new RPCLink({ url });\n\n return createORPCClient(link);\n}\n\nexport type Client = RouterClient<Router>;\n"],"mappings":";;;;AASA,SAAgB,aACd,UAAyB,EAAE,EACL;CACtB,MAAM,EAAE,MAAM,gCAAgC;AAI9C,QAAO,iBAFM,IAAI,QAAQ,EAAE,KAAK,CAAC,CAEJ"}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { dirname, join } from "node:path";
|
|
2
|
+
import { appendFileSync, globSync, mkdirSync } from "node:fs";
|
|
3
|
+
import { tsImport } from "tsx/esm/api";
|
|
4
|
+
import * as v from "valibot";
|
|
5
|
+
|
|
6
|
+
//#region src/logger.ts
|
|
7
|
+
const ANSI_COLORS = {
|
|
8
|
+
GREEN: "\x1B[32m",
|
|
9
|
+
CYAN: "\x1B[36m",
|
|
10
|
+
YELLOW: "\x1B[33m",
|
|
11
|
+
RED: "\x1B[31m",
|
|
12
|
+
RESET: "\x1B[0m",
|
|
13
|
+
BOLD: "\x1B[1m",
|
|
14
|
+
GRAY: "\x1B[90m"
|
|
15
|
+
};
|
|
16
|
+
const COLORS = {
|
|
17
|
+
debug: (text) => `${ANSI_COLORS.GREEN}${text}${ANSI_COLORS.RESET}`,
|
|
18
|
+
info: (text) => `${ANSI_COLORS.CYAN}${text}${ANSI_COLORS.RESET}`,
|
|
19
|
+
warn: (text) => `${ANSI_COLORS.YELLOW}${text}${ANSI_COLORS.RESET}`,
|
|
20
|
+
error: (text) => `${ANSI_COLORS.RED}${text}${ANSI_COLORS.RESET}`,
|
|
21
|
+
gray: (text) => `${ANSI_COLORS.GRAY}${text}${ANSI_COLORS.RESET}`,
|
|
22
|
+
bold: (text) => `${ANSI_COLORS.BOLD}${text}${ANSI_COLORS.RESET}`
|
|
23
|
+
};
|
|
24
|
+
const LOG_LEVELS = {
|
|
25
|
+
debug: 0,
|
|
26
|
+
info: 1,
|
|
27
|
+
warn: 2,
|
|
28
|
+
error: 3
|
|
29
|
+
};
|
|
30
|
+
function formatTime() {
|
|
31
|
+
return Intl.DateTimeFormat("en-US", {
|
|
32
|
+
hour: "2-digit",
|
|
33
|
+
minute: "2-digit",
|
|
34
|
+
second: "2-digit",
|
|
35
|
+
timeZone: "UTC",
|
|
36
|
+
hourCycle: "h23"
|
|
37
|
+
}).format(/* @__PURE__ */ new Date());
|
|
38
|
+
}
|
|
39
|
+
function formatLevel(level, color) {
|
|
40
|
+
const upper = level.toUpperCase().padStart(5);
|
|
41
|
+
return color ? `${COLORS[level](upper)}` : upper;
|
|
42
|
+
}
|
|
43
|
+
var Logger = class Logger {
|
|
44
|
+
name;
|
|
45
|
+
level;
|
|
46
|
+
prettyLogFile;
|
|
47
|
+
jsonLogFile;
|
|
48
|
+
stdout;
|
|
49
|
+
constructor(options) {
|
|
50
|
+
this.name = options.name;
|
|
51
|
+
this.level = LOG_LEVELS[options.level ?? "info"];
|
|
52
|
+
this.prettyLogFile = options.prettyLogFile;
|
|
53
|
+
this.jsonLogFile = options.jsonLogFile;
|
|
54
|
+
this.stdout = options.stdout ?? true;
|
|
55
|
+
if (this.prettyLogFile) mkdirSync(dirname(this.prettyLogFile), { recursive: true });
|
|
56
|
+
if (this.jsonLogFile) mkdirSync(dirname(this.jsonLogFile), { recursive: true });
|
|
57
|
+
}
|
|
58
|
+
log(level, message) {
|
|
59
|
+
if (LOG_LEVELS[level] < this.level) return;
|
|
60
|
+
const time = formatTime();
|
|
61
|
+
if (this.stdout) {
|
|
62
|
+
const colorLevel = formatLevel(level, true);
|
|
63
|
+
console.log(`${COLORS.gray(`[${time}]`)} ${colorLevel} ${COLORS.bold(`(${this.name})`)} ${message}`);
|
|
64
|
+
}
|
|
65
|
+
if (this.prettyLogFile) {
|
|
66
|
+
const line = `[${time}] ${formatLevel(level, false)} (${this.name}) ${message}\n`;
|
|
67
|
+
appendFileSync(this.prettyLogFile, line);
|
|
68
|
+
}
|
|
69
|
+
if (this.jsonLogFile) {
|
|
70
|
+
const logObj = {
|
|
71
|
+
time: (/* @__PURE__ */ new Date()).toISOString(),
|
|
72
|
+
level,
|
|
73
|
+
name: this.name,
|
|
74
|
+
msg: message
|
|
75
|
+
};
|
|
76
|
+
appendFileSync(this.jsonLogFile, JSON.stringify(logObj) + "\n");
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
debug(message) {
|
|
80
|
+
this.log("debug", message);
|
|
81
|
+
}
|
|
82
|
+
info(message) {
|
|
83
|
+
this.log("info", message);
|
|
84
|
+
}
|
|
85
|
+
warn(message) {
|
|
86
|
+
this.log("warn", message);
|
|
87
|
+
}
|
|
88
|
+
error(message) {
|
|
89
|
+
this.log("error", message);
|
|
90
|
+
}
|
|
91
|
+
child(name, options = {}) {
|
|
92
|
+
const hasPrettyLogFile = "prettyLogFile" in options;
|
|
93
|
+
const hasJsonLogFile = "jsonLogFile" in options;
|
|
94
|
+
const hasStdout = "stdout" in options;
|
|
95
|
+
return new Logger({
|
|
96
|
+
name: `${this.name}:${name}`,
|
|
97
|
+
level: options.level ?? Object.keys(LOG_LEVELS)[this.level],
|
|
98
|
+
prettyLogFile: hasPrettyLogFile ? options.prettyLogFile : this.prettyLogFile,
|
|
99
|
+
jsonLogFile: hasJsonLogFile ? options.jsonLogFile : this.jsonLogFile,
|
|
100
|
+
stdout: hasStdout ? options.stdout : this.stdout
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
mkdirSync("logs", { recursive: true });
|
|
105
|
+
const globalLogger = new Logger({
|
|
106
|
+
name: "pid1",
|
|
107
|
+
level: "debug",
|
|
108
|
+
jsonLogFile: "logs/pid1.log"
|
|
109
|
+
});
|
|
110
|
+
function processLogger({ name, logFile }) {
|
|
111
|
+
mkdirSync(dirname(logFile), { recursive: true });
|
|
112
|
+
return globalLogger.child(name, {
|
|
113
|
+
prettyLogFile: logFile,
|
|
114
|
+
jsonLogFile: null
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
//#endregion
|
|
119
|
+
//#region src/config.ts
|
|
120
|
+
const logger = globalLogger.child("config");
|
|
121
|
+
const EnvFileOption = v.optional(v.union([v.string(), v.literal(false)]));
|
|
122
|
+
const RestartPolicySchema = v.picklist([
|
|
123
|
+
"never",
|
|
124
|
+
"always",
|
|
125
|
+
"on-failure"
|
|
126
|
+
]);
|
|
127
|
+
const ProcessConfigSchema = v.object({
|
|
128
|
+
name: v.string(),
|
|
129
|
+
command: v.string(),
|
|
130
|
+
args: v.optional(v.array(v.string()), []),
|
|
131
|
+
env: v.optional(v.record(v.string(), v.string()), {}),
|
|
132
|
+
cwd: v.optional(v.string()),
|
|
133
|
+
envFile: EnvFileOption,
|
|
134
|
+
restartOnEnvChange: v.optional(v.boolean(), true),
|
|
135
|
+
restartPolicy: v.optional(RestartPolicySchema, "on-failure"),
|
|
136
|
+
maxRestarts: v.optional(v.number(), 10),
|
|
137
|
+
restartMinDelayMs: v.optional(v.number(), 5e3),
|
|
138
|
+
restartMaxDelayMs: v.optional(v.number(), 6e4),
|
|
139
|
+
restartResetSeconds: v.optional(v.number(), 300)
|
|
140
|
+
});
|
|
141
|
+
const Config = v.object({
|
|
142
|
+
envFile: EnvFileOption,
|
|
143
|
+
restartOnEnvChange: v.optional(v.boolean(), true),
|
|
144
|
+
restartMinDelayMs: v.optional(v.number(), 5e3),
|
|
145
|
+
restartMaxDelayMs: v.optional(v.number(), 6e4),
|
|
146
|
+
tasks: v.optional(v.array(ProcessConfigSchema), []),
|
|
147
|
+
processes: v.optional(v.array(ProcessConfigSchema), [])
|
|
148
|
+
});
|
|
149
|
+
async function loadConfigFile(path, cwd) {
|
|
150
|
+
const configFile = path ?? findConfigFile(cwd ?? process.cwd());
|
|
151
|
+
if (!configFile) {
|
|
152
|
+
logger.error(`Failed to find a config file in ${cwd ?? process.cwd()} or any of its parent directories`);
|
|
153
|
+
logger.error("Exiting...");
|
|
154
|
+
process.exit(1);
|
|
155
|
+
}
|
|
156
|
+
logger.info(`Trying to load config from ${configFile}`);
|
|
157
|
+
const config = await tsImport(configFile, { parentURL: import.meta.url }).then((m) => "default" in m.default ? m.default.default : m.default).catch((error) => {
|
|
158
|
+
logger.error(`Failed to load config from ${configFile}`);
|
|
159
|
+
logger.error(error instanceof Error ? error.stack ?? error.message : String(error));
|
|
160
|
+
logger.error("Exiting...");
|
|
161
|
+
process.exit(1);
|
|
162
|
+
});
|
|
163
|
+
const res = await v.safeParseAsync(Config, config);
|
|
164
|
+
if (!res.success) {
|
|
165
|
+
logger.error(`Config validation failed`);
|
|
166
|
+
logger.error(v.summarize(res.issues));
|
|
167
|
+
logger.error("Exiting...");
|
|
168
|
+
process.exit(1);
|
|
169
|
+
}
|
|
170
|
+
return {
|
|
171
|
+
...res.output,
|
|
172
|
+
configPath: configFile
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
function findConfigFile(cwd) {
|
|
176
|
+
const files = globSync("pid1.config.{ts,js,mts,mjs}", { cwd });
|
|
177
|
+
if (files.length === 0 && join(cwd) !== "/") return findConfigFile(join(cwd, ".."));
|
|
178
|
+
return files[0] ? join(cwd, files[0]) : null;
|
|
179
|
+
}
|
|
180
|
+
function defineConfig(config) {
|
|
181
|
+
return config;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
//#endregion
|
|
185
|
+
export { processLogger as a, globalLogger as i, findConfigFile as n, loadConfigFile as r, defineConfig as t };
|
|
186
|
+
//# sourceMappingURL=config-BgRb4pSG.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config-BgRb4pSG.mjs","names":[],"sources":["../src/logger.ts","../src/config.ts"],"sourcesContent":["import { appendFileSync, mkdirSync } from \"node:fs\";\nimport { dirname } from \"node:path\";\n\nconst ANSI_COLORS = {\n GREEN: \"\\x1b[32m\",\n CYAN: \"\\x1b[36m\",\n YELLOW: \"\\x1b[33m\",\n RED: \"\\x1b[31m\",\n RESET: \"\\x1b[0m\",\n BOLD: \"\\x1b[1m\",\n GRAY: \"\\x1b[90m\",\n};\n\nconst COLORS = {\n debug: (text: string) => `${ANSI_COLORS.GREEN}${text}${ANSI_COLORS.RESET}`,\n info: (text: string) => `${ANSI_COLORS.CYAN}${text}${ANSI_COLORS.RESET}`,\n warn: (text: string) => `${ANSI_COLORS.YELLOW}${text}${ANSI_COLORS.RESET}`,\n error: (text: string) => `${ANSI_COLORS.RED}${text}${ANSI_COLORS.RESET}`,\n gray: (text: string) => `${ANSI_COLORS.GRAY}${text}${ANSI_COLORS.RESET}`,\n bold: (text: string) => `${ANSI_COLORS.BOLD}${text}${ANSI_COLORS.RESET}`,\n};\n\nconst LOG_LEVELS = {\n debug: 0,\n info: 1,\n warn: 2,\n error: 3,\n} as const;\n\ntype LogLevel = keyof typeof LOG_LEVELS;\n\nfunction formatTime() {\n return Intl.DateTimeFormat(\"en-US\", {\n hour: \"2-digit\",\n minute: \"2-digit\",\n second: \"2-digit\",\n timeZone: \"UTC\",\n hourCycle: \"h23\",\n }).format(new Date());\n}\n\nfunction formatLevel(level: LogLevel, color: boolean): string {\n const upper = level.toUpperCase().padStart(5);\n return color ? `${COLORS[level](upper)}` : upper;\n}\n\nexport type LoggerOptions = {\n name: string;\n level?: LogLevel;\n prettyLogFile?: string | null;\n jsonLogFile?: string | null;\n stdout?: boolean;\n};\n\nexport class Logger {\n private name: string;\n private level: number;\n private prettyLogFile?: string | null;\n private jsonLogFile?: string | null;\n private stdout: boolean;\n\n constructor(options: LoggerOptions) {\n this.name = options.name;\n this.level = LOG_LEVELS[options.level ?? \"info\"];\n this.prettyLogFile = options.prettyLogFile;\n this.jsonLogFile = options.jsonLogFile;\n this.stdout = options.stdout ?? true;\n\n if (this.prettyLogFile) {\n mkdirSync(dirname(this.prettyLogFile), { recursive: true });\n }\n if (this.jsonLogFile) {\n mkdirSync(dirname(this.jsonLogFile), { recursive: true });\n }\n }\n\n private log(level: LogLevel, message: string): void {\n if (LOG_LEVELS[level] < this.level) return;\n\n const time = formatTime();\n\n if (this.stdout) {\n const colorLevel = formatLevel(level, true);\n console.log(\n `${COLORS.gray(`[${time}]`)} ${colorLevel} ${COLORS.bold(`(${this.name})`)} ${message}`,\n );\n }\n\n if (this.prettyLogFile) {\n const plainLevel = formatLevel(level, false);\n const line = `[${time}] ${plainLevel} (${this.name}) ${message}\\n`;\n appendFileSync(this.prettyLogFile, line);\n }\n\n if (this.jsonLogFile) {\n const logObj = {\n time: new Date().toISOString(),\n level,\n name: this.name,\n msg: message,\n };\n appendFileSync(this.jsonLogFile, JSON.stringify(logObj) + \"\\n\");\n }\n }\n\n debug(message: string): void {\n this.log(\"debug\", message);\n }\n\n info(message: string): void {\n this.log(\"info\", message);\n }\n\n warn(message: string): void {\n this.log(\"warn\", message);\n }\n\n error(message: string): void {\n this.log(\"error\", message);\n }\n\n child(name: string, options: Partial<LoggerOptions> = {}): Logger {\n const hasPrettyLogFile = \"prettyLogFile\" in options;\n const hasJsonLogFile = \"jsonLogFile\" in options;\n const hasStdout = \"stdout\" in options;\n\n return new Logger({\n name: `${this.name}:${name}`,\n level: options.level ?? (Object.keys(LOG_LEVELS)[this.level] as LogLevel),\n prettyLogFile: hasPrettyLogFile ? options.prettyLogFile : this.prettyLogFile,\n jsonLogFile: hasJsonLogFile ? options.jsonLogFile : this.jsonLogFile,\n stdout: hasStdout ? options.stdout : this.stdout,\n });\n }\n}\n\nmkdirSync(\"logs\", { recursive: true });\nexport const globalLogger = new Logger({\n name: \"pid1\",\n level: \"debug\",\n jsonLogFile: \"logs/pid1.log\",\n});\n\ntype ProcessLoggerParams = {\n name: string;\n logFile: string;\n};\n\nexport function processLogger({ name, logFile }: ProcessLoggerParams): Logger {\n mkdirSync(dirname(logFile), { recursive: true });\n return globalLogger.child(name, {\n prettyLogFile: logFile,\n jsonLogFile: null,\n });\n}\n","import { globSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { tsImport } from \"tsx/esm/api\";\nimport * as v from \"valibot\";\nimport { globalLogger } from \"./logger\";\n\nconst logger = globalLogger.child(\"config\");\nconst EnvFileOption = v.optional(v.union([v.string(), v.literal(false)]));\nconst RestartPolicySchema = v.picklist([\"never\", \"always\", \"on-failure\"]);\n\nconst ProcessConfigSchema = v.object({\n name: v.string(),\n command: v.string(),\n args: v.optional(v.array(v.string()), []),\n env: v.optional(v.record(v.string(), v.string()), {}),\n cwd: v.optional(v.string()),\n envFile: EnvFileOption,\n restartOnEnvChange: v.optional(v.boolean(), true),\n restartPolicy: v.optional(RestartPolicySchema, \"on-failure\"),\n maxRestarts: v.optional(v.number(), 10),\n restartMinDelayMs: v.optional(v.number(), 5000),\n restartMaxDelayMs: v.optional(v.number(), 60000),\n restartResetSeconds: v.optional(v.number(), 300),\n});\n\nconst Config = v.object({\n envFile: EnvFileOption,\n restartOnEnvChange: v.optional(v.boolean(), true),\n restartMinDelayMs: v.optional(v.number(), 5000),\n restartMaxDelayMs: v.optional(v.number(), 60000),\n tasks: v.optional(v.array(ProcessConfigSchema), []),\n processes: v.optional(v.array(ProcessConfigSchema), []),\n});\n\ntype ConfigInput = v.InferInput<typeof Config>;\n\nexport type LoadedConfig = v.InferOutput<typeof Config> & {\n configPath: string;\n};\n\nexport async function loadConfigFile(\n path?: string,\n cwd?: string,\n): Promise<LoadedConfig> {\n const configFile = path ?? findConfigFile(cwd ?? process.cwd());\n if (!configFile) {\n logger.error(\n `Failed to find a config file in ${cwd ?? process.cwd()} or any of its parent directories`,\n );\n logger.error(\"Exiting...\");\n process.exit(1);\n }\n\n logger.info(`Trying to load config from ${configFile}`);\n const config = await tsImport(configFile, {\n parentURL: import.meta.url,\n })\n .then((m) =>\n \"default\" in m.default ? m.default.default : m.default,\n )\n .catch((error) => {\n logger.error(`Failed to load config from ${configFile}`);\n logger.error(error instanceof Error ? error.stack ?? error.message : String(error));\n logger.error(\"Exiting...\");\n process.exit(1);\n });\n\n const res = await v.safeParseAsync(Config, config);\n if (!res.success) {\n logger.error(`Config validation failed`);\n logger.error(v.summarize(res.issues));\n logger.error(\"Exiting...\");\n process.exit(1);\n }\n\n return { ...res.output, configPath: configFile };\n}\n\nexport function findConfigFile(cwd: string) {\n const files = globSync(\"pid1.config.{ts,js,mts,mjs}\", { cwd });\n if (files.length === 0 && join(cwd) !== \"/\") {\n return findConfigFile(join(cwd, \"..\"));\n }\n return files[0] ? join(cwd, files[0]) : null;\n}\n\nexport function defineConfig(config: ConfigInput) {\n return config;\n}\n"],"mappings":"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"}
|