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
package/dist/cli.mjs
CHANGED
|
@@ -1,24 +1,22 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { _ as
|
|
2
|
+
import { _ as ProcessDefinition, a as TaskStateSchema, c as CronProcessState, h as RestartingProcessState, i as TaskList, m as RestartingProcessOptions, n as EnvManager, o as CronProcess, p as RestartingProcess, s as CronProcessOptions, t as logger } from "./logger-BU2RmetS.mjs";
|
|
3
3
|
import { createClient } from "./client.mjs";
|
|
4
|
-
import { Command } from "commander";
|
|
5
4
|
import { createServer } from "node:http";
|
|
6
5
|
import { pathToFileURL } from "node:url";
|
|
7
6
|
import { join, resolve } from "node:path";
|
|
8
7
|
import { RPCHandler } from "@orpc/server/node";
|
|
9
|
-
import { implement, onError } from "@orpc/server";
|
|
8
|
+
import { implement, onError, os } from "@orpc/server";
|
|
10
9
|
import * as v from "valibot";
|
|
11
10
|
import { oc } from "@orpc/contract";
|
|
12
|
-
import { exec } from "node:child_process";
|
|
13
11
|
import { cwd } from "node:process";
|
|
14
12
|
import { mkdirSync } from "node:fs";
|
|
15
|
-
import {
|
|
16
|
-
import { tsImport } from "tsx/esm/api";
|
|
13
|
+
import { format } from "node:util";
|
|
17
14
|
import Table from "cli-table3";
|
|
15
|
+
import { createCli } from "trpc-cli";
|
|
18
16
|
|
|
19
17
|
//#region src/api/contract.ts
|
|
20
18
|
const oc$1 = oc.$input(v.void());
|
|
21
|
-
const ResourceTarget = v.union([v.string(), v.number()]);
|
|
19
|
+
const ResourceTarget$1 = v.union([v.string(), v.number()]);
|
|
22
20
|
const ManagerStateSchema = v.picklist([
|
|
23
21
|
"idle",
|
|
24
22
|
"initializing",
|
|
@@ -34,12 +32,12 @@ const ManagerStatusSchema = v.object({
|
|
|
34
32
|
});
|
|
35
33
|
const RestartingProcessInfoSchema = v.object({
|
|
36
34
|
name: v.string(),
|
|
37
|
-
state:
|
|
35
|
+
state: RestartingProcessState,
|
|
38
36
|
restarts: v.number()
|
|
39
37
|
});
|
|
40
38
|
const CronProcessInfoSchema = v.object({
|
|
41
39
|
name: v.string(),
|
|
42
|
-
state:
|
|
40
|
+
state: CronProcessState,
|
|
43
41
|
runCount: v.number(),
|
|
44
42
|
failCount: v.number(),
|
|
45
43
|
nextRun: v.nullable(v.string())
|
|
@@ -50,52 +48,52 @@ const TaskEntryInfoSchema = v.object({
|
|
|
50
48
|
processNames: v.array(v.string())
|
|
51
49
|
});
|
|
52
50
|
const manager = { status: oc$1.output(ManagerStatusSchema) };
|
|
53
|
-
const processes
|
|
54
|
-
get: oc$1.input(v.object({ target: ResourceTarget })).output(RestartingProcessInfoSchema),
|
|
51
|
+
const processes = {
|
|
52
|
+
get: oc$1.input(v.object({ target: ResourceTarget$1 })).output(RestartingProcessInfoSchema),
|
|
55
53
|
list: oc$1.output(v.array(RestartingProcessInfoSchema)),
|
|
56
54
|
add: oc$1.input(v.object({
|
|
57
55
|
name: v.string(),
|
|
58
|
-
definition:
|
|
56
|
+
definition: ProcessDefinition
|
|
59
57
|
})).output(RestartingProcessInfoSchema),
|
|
60
|
-
start: oc$1.input(v.object({ target: ResourceTarget })).output(RestartingProcessInfoSchema),
|
|
61
|
-
stop: oc$1.input(v.object({ target: ResourceTarget })).output(RestartingProcessInfoSchema),
|
|
58
|
+
start: oc$1.input(v.object({ target: ResourceTarget$1 })).output(RestartingProcessInfoSchema),
|
|
59
|
+
stop: oc$1.input(v.object({ target: ResourceTarget$1 })).output(RestartingProcessInfoSchema),
|
|
62
60
|
restart: oc$1.input(v.object({
|
|
63
|
-
target: ResourceTarget,
|
|
61
|
+
target: ResourceTarget$1,
|
|
64
62
|
force: v.optional(v.boolean())
|
|
65
63
|
})).output(RestartingProcessInfoSchema),
|
|
66
64
|
reload: oc$1.input(v.object({
|
|
67
|
-
target: ResourceTarget,
|
|
68
|
-
definition:
|
|
65
|
+
target: ResourceTarget$1,
|
|
66
|
+
definition: ProcessDefinition,
|
|
69
67
|
restartImmediately: v.optional(v.boolean())
|
|
70
68
|
})).output(RestartingProcessInfoSchema),
|
|
71
|
-
remove: oc$1.input(v.object({ target: ResourceTarget })).output(v.object({ success: v.boolean() }))
|
|
69
|
+
remove: oc$1.input(v.object({ target: ResourceTarget$1 })).output(v.object({ success: v.boolean() }))
|
|
72
70
|
};
|
|
73
|
-
const tasks
|
|
74
|
-
get: oc$1.input(v.object({ target: ResourceTarget })).output(TaskEntryInfoSchema),
|
|
71
|
+
const tasks = {
|
|
72
|
+
get: oc$1.input(v.object({ target: ResourceTarget$1 })).output(TaskEntryInfoSchema),
|
|
75
73
|
list: oc$1.output(v.array(TaskEntryInfoSchema)),
|
|
76
74
|
add: oc$1.input(v.object({
|
|
77
75
|
name: v.string(),
|
|
78
|
-
definition:
|
|
76
|
+
definition: ProcessDefinition
|
|
79
77
|
})).output(TaskEntryInfoSchema),
|
|
80
|
-
remove: oc$1.input(v.object({ target: ResourceTarget })).output(TaskEntryInfoSchema)
|
|
78
|
+
remove: oc$1.input(v.object({ target: ResourceTarget$1 })).output(TaskEntryInfoSchema)
|
|
81
79
|
};
|
|
82
|
-
const crons
|
|
83
|
-
get: oc$1.input(v.object({ target: ResourceTarget })).output(CronProcessInfoSchema),
|
|
80
|
+
const crons = {
|
|
81
|
+
get: oc$1.input(v.object({ target: ResourceTarget$1 })).output(CronProcessInfoSchema),
|
|
84
82
|
list: oc$1.output(v.array(CronProcessInfoSchema)),
|
|
85
|
-
trigger: oc$1.input(v.object({ target: ResourceTarget })).output(CronProcessInfoSchema),
|
|
86
|
-
start: oc$1.input(v.object({ target: ResourceTarget })).output(CronProcessInfoSchema),
|
|
87
|
-
stop: oc$1.input(v.object({ target: ResourceTarget })).output(CronProcessInfoSchema)
|
|
83
|
+
trigger: oc$1.input(v.object({ target: ResourceTarget$1 })).output(CronProcessInfoSchema),
|
|
84
|
+
start: oc$1.input(v.object({ target: ResourceTarget$1 })).output(CronProcessInfoSchema),
|
|
85
|
+
stop: oc$1.input(v.object({ target: ResourceTarget$1 })).output(CronProcessInfoSchema)
|
|
88
86
|
};
|
|
89
87
|
const api = {
|
|
90
88
|
manager,
|
|
91
|
-
processes
|
|
92
|
-
tasks
|
|
93
|
-
crons
|
|
89
|
+
processes,
|
|
90
|
+
tasks,
|
|
91
|
+
crons
|
|
94
92
|
};
|
|
95
93
|
|
|
96
94
|
//#endregion
|
|
97
95
|
//#region src/api/server.ts
|
|
98
|
-
const os = implement(api).$context();
|
|
96
|
+
const os$2 = implement(api).$context();
|
|
99
97
|
function serializeProcess(proc) {
|
|
100
98
|
return {
|
|
101
99
|
name: proc.name,
|
|
@@ -112,7 +110,7 @@ function serializeCron(cron) {
|
|
|
112
110
|
nextRun: cron.nextRun?.toISOString() ?? null
|
|
113
111
|
};
|
|
114
112
|
}
|
|
115
|
-
const managerStatus = os.manager.status.handler(async ({ context }) => {
|
|
113
|
+
const managerStatus = os$2.manager.status.handler(async ({ context }) => {
|
|
116
114
|
const manager = context.manager;
|
|
117
115
|
const taskList = manager.getTaskList();
|
|
118
116
|
return {
|
|
@@ -122,56 +120,56 @@ const managerStatus = os.manager.status.handler(async ({ context }) => {
|
|
|
122
120
|
taskCount: taskList?.tasks.length ?? 0
|
|
123
121
|
};
|
|
124
122
|
});
|
|
125
|
-
const getProcess = os.processes.get.handler(async ({ input, context }) => {
|
|
123
|
+
const getProcess = os$2.processes.get.handler(async ({ input, context }) => {
|
|
126
124
|
const proc = context.manager.getProcessByTarget(input.target);
|
|
127
125
|
if (!proc) throw new Error(`Process not found: ${input.target}`);
|
|
128
126
|
return serializeProcess(proc);
|
|
129
127
|
});
|
|
130
|
-
const listProcesses = os.processes.list.handler(async ({ context }) => {
|
|
128
|
+
const listProcesses = os$2.processes.list.handler(async ({ context }) => {
|
|
131
129
|
return Array.from(context.manager.getRestartingProcesses().values()).map(serializeProcess);
|
|
132
130
|
});
|
|
133
|
-
const addProcess = os.processes.add.handler(async ({ input, context }) => {
|
|
131
|
+
const addProcess = os$2.processes.add.handler(async ({ input, context }) => {
|
|
134
132
|
return serializeProcess(await context.manager.addProcess(input.name, input.definition));
|
|
135
133
|
});
|
|
136
|
-
const startProcess = os.processes.start.handler(async ({ input, context }) => {
|
|
134
|
+
const startProcess = os$2.processes.start.handler(async ({ input, context }) => {
|
|
137
135
|
return serializeProcess(await context.manager.startProcessByTarget(input.target));
|
|
138
136
|
});
|
|
139
|
-
const stopProcess = os.processes.stop.handler(async ({ input, context }) => {
|
|
137
|
+
const stopProcess = os$2.processes.stop.handler(async ({ input, context }) => {
|
|
140
138
|
return serializeProcess(await context.manager.stopProcessByTarget(input.target));
|
|
141
139
|
});
|
|
142
|
-
const restartProcess = os.processes.restart.handler(async ({ input, context }) => {
|
|
140
|
+
const restartProcess = os$2.processes.restart.handler(async ({ input, context }) => {
|
|
143
141
|
return serializeProcess(await context.manager.restartProcessByTarget(input.target, input.force));
|
|
144
142
|
});
|
|
145
|
-
const reloadProcess = os.processes.reload.handler(async ({ input, context }) => {
|
|
143
|
+
const reloadProcess = os$2.processes.reload.handler(async ({ input, context }) => {
|
|
146
144
|
return serializeProcess(await context.manager.reloadProcessByTarget(input.target, input.definition, { restartImmediately: input.restartImmediately }));
|
|
147
145
|
});
|
|
148
|
-
const removeProcess = os.processes.remove.handler(async ({ input, context }) => {
|
|
146
|
+
const removeProcess = os$2.processes.remove.handler(async ({ input, context }) => {
|
|
149
147
|
await context.manager.removeProcessByTarget(input.target);
|
|
150
148
|
return { success: true };
|
|
151
149
|
});
|
|
152
|
-
const getCron = os.crons.get.handler(async ({ input, context }) => {
|
|
150
|
+
const getCron = os$2.crons.get.handler(async ({ input, context }) => {
|
|
153
151
|
const cron = context.manager.getCronByTarget(input.target);
|
|
154
152
|
if (!cron) throw new Error(`Cron not found: ${input.target}`);
|
|
155
153
|
return serializeCron(cron);
|
|
156
154
|
});
|
|
157
|
-
const listCrons = os.crons.list.handler(async ({ context }) => {
|
|
155
|
+
const listCrons = os$2.crons.list.handler(async ({ context }) => {
|
|
158
156
|
return Array.from(context.manager.getCronProcesses().values()).map(serializeCron);
|
|
159
157
|
});
|
|
160
|
-
const triggerCron = os.crons.trigger.handler(async ({ input, context }) => {
|
|
158
|
+
const triggerCron = os$2.crons.trigger.handler(async ({ input, context }) => {
|
|
161
159
|
return serializeCron(await context.manager.triggerCronByTarget(input.target));
|
|
162
160
|
});
|
|
163
|
-
const startCron = os.crons.start.handler(async ({ input, context }) => {
|
|
161
|
+
const startCron = os$2.crons.start.handler(async ({ input, context }) => {
|
|
164
162
|
return serializeCron(context.manager.startCronByTarget(input.target));
|
|
165
163
|
});
|
|
166
|
-
const stopCron = os.crons.stop.handler(async ({ input, context }) => {
|
|
164
|
+
const stopCron = os$2.crons.stop.handler(async ({ input, context }) => {
|
|
167
165
|
return serializeCron(await context.manager.stopCronByTarget(input.target));
|
|
168
166
|
});
|
|
169
|
-
const getTask = os.tasks.get.handler(async ({ input, context }) => {
|
|
167
|
+
const getTask = os$2.tasks.get.handler(async ({ input, context }) => {
|
|
170
168
|
const task = context.manager.getTaskByTarget(input.target);
|
|
171
169
|
if (!task) throw new Error(`Task not found: ${input.target}`);
|
|
172
170
|
return task;
|
|
173
171
|
});
|
|
174
|
-
const listTasks = os.tasks.list.handler(async ({ context }) => {
|
|
172
|
+
const listTasks = os$2.tasks.list.handler(async ({ context }) => {
|
|
175
173
|
const taskList = context.manager.getTaskList();
|
|
176
174
|
if (!taskList) return [];
|
|
177
175
|
return taskList.tasks.map((t) => ({
|
|
@@ -180,13 +178,13 @@ const listTasks = os.tasks.list.handler(async ({ context }) => {
|
|
|
180
178
|
processNames: t.processes.map((p) => p.name)
|
|
181
179
|
}));
|
|
182
180
|
});
|
|
183
|
-
const addTask = os.tasks.add.handler(async ({ input, context }) => {
|
|
181
|
+
const addTask = os$2.tasks.add.handler(async ({ input, context }) => {
|
|
184
182
|
return context.manager.addTask(input.name, input.definition);
|
|
185
183
|
});
|
|
186
|
-
const removeTask = os.tasks.remove.handler(async ({ input, context }) => {
|
|
184
|
+
const removeTask = os$2.tasks.remove.handler(async ({ input, context }) => {
|
|
187
185
|
return context.manager.removeTaskByTarget(input.target);
|
|
188
186
|
});
|
|
189
|
-
const router = os.router({
|
|
187
|
+
const router = os$2.router({
|
|
190
188
|
manager: { status: managerStatus },
|
|
191
189
|
processes: {
|
|
192
190
|
add: addProcess,
|
|
@@ -213,76 +211,52 @@ const router = os.router({
|
|
|
213
211
|
}
|
|
214
212
|
});
|
|
215
213
|
|
|
216
|
-
//#endregion
|
|
217
|
-
//#region src/port-utils.ts
|
|
218
|
-
const execAsync = promisify(exec);
|
|
219
|
-
/**
|
|
220
|
-
* Kill any process listening on the specified port, including all descendants.
|
|
221
|
-
*/
|
|
222
|
-
async function killProcessOnPort(port) {
|
|
223
|
-
try {
|
|
224
|
-
const { stdout } = await execAsync(`lsof -ti tcp:${port}`);
|
|
225
|
-
const pids = stdout.trim().split("\n").filter(Boolean).map((p) => parseInt(p, 10)).filter((p) => !isNaN(p));
|
|
226
|
-
if (pids.length === 0) return false;
|
|
227
|
-
for (const pid of pids) try {
|
|
228
|
-
await treeKill(pid, "SIGKILL");
|
|
229
|
-
} catch {}
|
|
230
|
-
return true;
|
|
231
|
-
} catch {
|
|
232
|
-
return false;
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
|
|
236
214
|
//#endregion
|
|
237
215
|
//#region src/manager.ts
|
|
238
|
-
const
|
|
216
|
+
const HttpServerConfig = v.object({
|
|
239
217
|
host: v.optional(v.string()),
|
|
240
218
|
port: v.optional(v.number()),
|
|
241
219
|
authToken: v.optional(v.string())
|
|
242
220
|
});
|
|
243
|
-
const
|
|
221
|
+
const CronProcessEntry = v.object({
|
|
244
222
|
name: v.string(),
|
|
245
|
-
definition:
|
|
246
|
-
options:
|
|
223
|
+
definition: ProcessDefinition,
|
|
224
|
+
options: CronProcessOptions,
|
|
247
225
|
envFile: v.optional(v.string())
|
|
248
226
|
});
|
|
249
|
-
const
|
|
227
|
+
const EnvReloadDelay = v.union([
|
|
250
228
|
v.number(),
|
|
251
229
|
v.boolean(),
|
|
252
230
|
v.literal("immediately")
|
|
253
231
|
]);
|
|
254
|
-
const
|
|
232
|
+
const RestartingProcessEntry = v.object({
|
|
255
233
|
name: v.string(),
|
|
256
|
-
definition:
|
|
257
|
-
options: v.optional(
|
|
234
|
+
definition: ProcessDefinition,
|
|
235
|
+
options: v.optional(RestartingProcessOptions),
|
|
258
236
|
envFile: v.optional(v.string()),
|
|
259
|
-
envReloadDelay: v.optional(
|
|
260
|
-
port: v.optional(v.number())
|
|
237
|
+
envReloadDelay: v.optional(EnvReloadDelay)
|
|
261
238
|
});
|
|
262
|
-
const
|
|
239
|
+
const TaskEntry = v.object({
|
|
263
240
|
name: v.string(),
|
|
264
|
-
definition:
|
|
241
|
+
definition: ProcessDefinition,
|
|
265
242
|
envFile: v.optional(v.string())
|
|
266
243
|
});
|
|
267
|
-
const
|
|
268
|
-
http: v.optional(
|
|
244
|
+
const ManagerConfig = v.object({
|
|
245
|
+
http: v.optional(HttpServerConfig),
|
|
269
246
|
cwd: v.optional(v.string()),
|
|
270
247
|
logDir: v.optional(v.string()),
|
|
271
248
|
env: v.optional(v.record(v.string(), v.string())),
|
|
272
|
-
|
|
273
|
-
tasks: v.optional(v.array(
|
|
274
|
-
crons: v.optional(v.array(
|
|
275
|
-
processes: v.optional(v.array(
|
|
249
|
+
envFile: v.optional(v.string()),
|
|
250
|
+
tasks: v.optional(v.array(TaskEntry)),
|
|
251
|
+
crons: v.optional(v.array(CronProcessEntry)),
|
|
252
|
+
processes: v.optional(v.array(RestartingProcessEntry))
|
|
276
253
|
});
|
|
277
254
|
const DEFAULT_RESTART_OPTIONS = { restartPolicy: "always" };
|
|
278
255
|
const SHUTDOWN_TIMEOUT_MS = 15e3;
|
|
279
|
-
const SHUTDOWN_SIGNALS = ["SIGINT", "SIGTERM"];
|
|
280
|
-
const SHUTDOWN_EXIT_CODE = 0;
|
|
281
256
|
var Manager = class {
|
|
282
257
|
config;
|
|
283
258
|
logger;
|
|
284
259
|
envManager;
|
|
285
|
-
envFileKeyByProcess = /* @__PURE__ */ new Map();
|
|
286
260
|
_state = "idle";
|
|
287
261
|
taskList = null;
|
|
288
262
|
cronProcesses = /* @__PURE__ */ new Map();
|
|
@@ -291,48 +265,93 @@ var Manager = class {
|
|
|
291
265
|
processEnvReloadConfig = /* @__PURE__ */ new Map();
|
|
292
266
|
envReloadTimers = /* @__PURE__ */ new Map();
|
|
293
267
|
envChangeUnsubscribe = null;
|
|
294
|
-
processPortConfig = /* @__PURE__ */ new Map();
|
|
295
268
|
signalHandlers = /* @__PURE__ */ new Map();
|
|
296
269
|
shutdownPromise = null;
|
|
297
270
|
isShuttingDown = false;
|
|
298
271
|
constructor(config, logger) {
|
|
272
|
+
const cwd$1 = config.cwd ?? cwd();
|
|
299
273
|
this.config = config;
|
|
300
274
|
this.logger = logger;
|
|
301
|
-
this.logDir = config.logDir ?? join(cwd
|
|
275
|
+
this.logDir = config.logDir ?? join(cwd$1, "logs");
|
|
302
276
|
this.ensureLogDirs();
|
|
277
|
+
this.validateConfigNames();
|
|
278
|
+
const customEnvFiles = {};
|
|
279
|
+
for (const task of config.tasks ?? []) if (task.envFile) customEnvFiles[task.name] = task.envFile;
|
|
280
|
+
for (const cron of config.crons ?? []) if (cron.envFile) customEnvFiles[cron.name] = cron.envFile;
|
|
281
|
+
for (const proc of config.processes ?? []) if (proc.envFile) customEnvFiles[proc.name] = proc.envFile;
|
|
303
282
|
this.envManager = new EnvManager({
|
|
304
|
-
cwd:
|
|
305
|
-
|
|
306
|
-
|
|
283
|
+
cwd: cwd$1,
|
|
284
|
+
globalEnvFile: config.envFile,
|
|
285
|
+
customEnvFiles
|
|
286
|
+
});
|
|
287
|
+
this.envChangeUnsubscribe = this.envManager.onChange((event) => this.handleEnvChange(event));
|
|
288
|
+
for (const signal of ["SIGINT", "SIGTERM"]) {
|
|
289
|
+
const handler = () => this.handleSignal(signal);
|
|
290
|
+
this.signalHandlers.set(signal, handler);
|
|
291
|
+
process.on(signal, handler);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
validateConfigNames() {
|
|
295
|
+
const allNames = [];
|
|
296
|
+
for (const task of this.config.tasks ?? []) allNames.push({
|
|
297
|
+
name: task.name,
|
|
298
|
+
type: "task"
|
|
307
299
|
});
|
|
308
|
-
|
|
309
|
-
|
|
300
|
+
for (const cron of this.config.crons ?? []) allNames.push({
|
|
301
|
+
name: cron.name,
|
|
302
|
+
type: "cron"
|
|
310
303
|
});
|
|
311
|
-
this.
|
|
304
|
+
for (const proc of this.config.processes ?? []) allNames.push({
|
|
305
|
+
name: proc.name,
|
|
306
|
+
type: "process"
|
|
307
|
+
});
|
|
308
|
+
const seen = /* @__PURE__ */ new Map();
|
|
309
|
+
for (const { name, type } of allNames) {
|
|
310
|
+
const existingType = seen.get(name);
|
|
311
|
+
if (existingType) throw new Error(`Duplicate name "${name}" found: already used as ${existingType}, cannot use as ${type}. Names must be globally unique across tasks, crons, and processes.`);
|
|
312
|
+
seen.set(name, type);
|
|
313
|
+
}
|
|
312
314
|
}
|
|
313
315
|
/**
|
|
314
|
-
*
|
|
315
|
-
* Merge order: .env (global), config.env (global), .env.<processName>, processEnvFile, definition.env
|
|
316
|
+
* Check if a name is already used by any task, cron, or process
|
|
316
317
|
*/
|
|
317
|
-
|
|
318
|
-
const
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
}
|
|
327
|
-
envFromCustomFile = this.envManager.getEnvForKey(key);
|
|
318
|
+
isNameUsed(name) {
|
|
319
|
+
for (const task of this.config.tasks ?? []) if (task.name === name) return {
|
|
320
|
+
used: true,
|
|
321
|
+
type: "task"
|
|
322
|
+
};
|
|
323
|
+
if (this.taskList) {
|
|
324
|
+
for (const task of this.taskList.tasks) for (const proc of task.processes) if (proc.name === name) return {
|
|
325
|
+
used: true,
|
|
326
|
+
type: "task"
|
|
327
|
+
};
|
|
328
328
|
}
|
|
329
|
+
if (this.cronProcesses.has(name)) return {
|
|
330
|
+
used: true,
|
|
331
|
+
type: "cron"
|
|
332
|
+
};
|
|
333
|
+
for (const cron of this.config.crons ?? []) if (cron.name === name) return {
|
|
334
|
+
used: true,
|
|
335
|
+
type: "cron"
|
|
336
|
+
};
|
|
337
|
+
if (this.restartingProcesses.has(name)) return {
|
|
338
|
+
used: true,
|
|
339
|
+
type: "process"
|
|
340
|
+
};
|
|
341
|
+
for (const proc of this.config.processes ?? []) if (proc.name === name) return {
|
|
342
|
+
used: true,
|
|
343
|
+
type: "process"
|
|
344
|
+
};
|
|
345
|
+
return { used: false };
|
|
346
|
+
}
|
|
347
|
+
applyDefaults(processName, definition) {
|
|
348
|
+
const envVarsFromManager = this.envManager.getEnvVars(processName);
|
|
329
349
|
return {
|
|
330
350
|
...definition,
|
|
331
351
|
cwd: definition.cwd ?? this.config.cwd,
|
|
332
352
|
env: {
|
|
333
|
-
...
|
|
353
|
+
...envVarsFromManager,
|
|
334
354
|
...this.config.env,
|
|
335
|
-
...envFromCustomFile,
|
|
336
355
|
...definition.env
|
|
337
356
|
}
|
|
338
357
|
};
|
|
@@ -346,14 +365,6 @@ var Manager = class {
|
|
|
346
365
|
cronLogFile(name) {
|
|
347
366
|
return join(this.logDir, "cron", `${name}.log`);
|
|
348
367
|
}
|
|
349
|
-
/**
|
|
350
|
-
* Kill any process running on the configured port for a process
|
|
351
|
-
*/
|
|
352
|
-
async killPortForProcess(processName) {
|
|
353
|
-
const port = this.processPortConfig.get(processName);
|
|
354
|
-
if (port === void 0) return;
|
|
355
|
-
if (await killProcessOnPort(port)) this.logger.info(`Killed process on port ${port} before starting "${processName}"`);
|
|
356
|
-
}
|
|
357
368
|
ensureLogDirs() {
|
|
358
369
|
mkdirSync(this.logDir, { recursive: true });
|
|
359
370
|
mkdirSync(join(this.logDir, "process"), { recursive: true });
|
|
@@ -363,18 +374,21 @@ var Manager = class {
|
|
|
363
374
|
/**
|
|
364
375
|
* Handle env file changes
|
|
365
376
|
*/
|
|
366
|
-
handleEnvChange(
|
|
377
|
+
handleEnvChange(event) {
|
|
367
378
|
if (this._state !== "running") return;
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
379
|
+
if (event.type === "global") {
|
|
380
|
+
this.logger.info("Global env file changed, reloading all processes as per their policies");
|
|
381
|
+
for (const processName of this.restartingProcesses.keys()) {
|
|
382
|
+
const reloadDelay = this.processEnvReloadConfig.get(processName);
|
|
383
|
+
if (reloadDelay === false) continue;
|
|
384
|
+
this.scheduleProcessReload(processName, reloadDelay);
|
|
385
|
+
}
|
|
386
|
+
return;
|
|
374
387
|
}
|
|
375
|
-
|
|
388
|
+
if (event.type === "process") {
|
|
389
|
+
const processName = event.key;
|
|
376
390
|
const reloadDelay = this.processEnvReloadConfig.get(processName);
|
|
377
|
-
if (reloadDelay === false)
|
|
391
|
+
if (reloadDelay === false) return;
|
|
378
392
|
this.scheduleProcessReload(processName, reloadDelay);
|
|
379
393
|
}
|
|
380
394
|
}
|
|
@@ -411,7 +425,7 @@ var Manager = class {
|
|
|
411
425
|
this.logger.warn(`Process config for "${processName}" not found`);
|
|
412
426
|
return;
|
|
413
427
|
}
|
|
414
|
-
const updatedDefinition = this.applyDefaults(processName, processConfig.definition
|
|
428
|
+
const updatedDefinition = this.applyDefaults(processName, processConfig.definition);
|
|
415
429
|
await proc.reload(updatedDefinition, true);
|
|
416
430
|
}
|
|
417
431
|
get state() {
|
|
@@ -490,7 +504,6 @@ var Manager = class {
|
|
|
490
504
|
async startProcessByTarget(target) {
|
|
491
505
|
const proc = this.getProcessByTarget(target);
|
|
492
506
|
if (!proc) throw new Error(`Process not found: ${target}`);
|
|
493
|
-
await this.killPortForProcess(proc.name);
|
|
494
507
|
proc.start();
|
|
495
508
|
return proc;
|
|
496
509
|
}
|
|
@@ -509,7 +522,6 @@ var Manager = class {
|
|
|
509
522
|
async restartProcessByTarget(target, force = false) {
|
|
510
523
|
const proc = this.getProcessByTarget(target);
|
|
511
524
|
if (!proc) throw new Error(`Process not found: ${target}`);
|
|
512
|
-
await this.killPortForProcess(proc.name);
|
|
513
525
|
await proc.restart(force);
|
|
514
526
|
return proc;
|
|
515
527
|
}
|
|
@@ -539,7 +551,10 @@ var Manager = class {
|
|
|
539
551
|
* Add a task to the task list
|
|
540
552
|
* Creates the task list if it doesn't exist and starts it
|
|
541
553
|
*/
|
|
542
|
-
addTask(name, definition) {
|
|
554
|
+
addTask(name, definition, envFile) {
|
|
555
|
+
const nameCheck = this.isNameUsed(name);
|
|
556
|
+
if (nameCheck.used) throw new Error(`Name "${name}" is already used as ${nameCheck.type}. Names must be globally unique across tasks, crons, and processes.`);
|
|
557
|
+
if (envFile) this.envManager.registerFile(name, envFile);
|
|
543
558
|
if (!this.taskList) this.taskList = new TaskList("runtime", this.logger.child("tasks", { logFile: this.taskLogFile("tasks") }), void 0, (processName) => {
|
|
544
559
|
return this.taskLogFile(processName);
|
|
545
560
|
});
|
|
@@ -567,15 +582,13 @@ var Manager = class {
|
|
|
567
582
|
/**
|
|
568
583
|
* Add a restarting process at runtime
|
|
569
584
|
*/
|
|
570
|
-
async addProcess(name, definition, options, envReloadDelay,
|
|
571
|
-
|
|
585
|
+
async addProcess(name, definition, options, envReloadDelay, envFile) {
|
|
586
|
+
const nameCheck = this.isNameUsed(name);
|
|
587
|
+
if (nameCheck.used) throw new Error(`Name "${name}" is already used as ${nameCheck.type}. Names must be globally unique across tasks, crons, and processes.`);
|
|
588
|
+
if (envFile) this.envManager.registerFile(name, envFile);
|
|
572
589
|
const processLogger = this.logger.child(name, { logFile: this.processLogFile(name) });
|
|
573
590
|
const restartingProcess = new RestartingProcess(name, this.applyDefaults(name, definition), options ?? DEFAULT_RESTART_OPTIONS, processLogger);
|
|
574
591
|
this.restartingProcesses.set(name, restartingProcess);
|
|
575
|
-
if (port !== void 0) {
|
|
576
|
-
this.processPortConfig.set(name, port);
|
|
577
|
-
await this.killPortForProcess(name);
|
|
578
|
-
}
|
|
579
592
|
restartingProcess.start();
|
|
580
593
|
this.processEnvReloadConfig.set(name, envReloadDelay ?? 5e3);
|
|
581
594
|
this.logger.info(`Added and started restarting process: ${name}`);
|
|
@@ -621,7 +634,7 @@ var Manager = class {
|
|
|
621
634
|
this.logger.info(`Running initialization tasks`);
|
|
622
635
|
this.taskList = new TaskList("init", this.logger.child("tasks"), this.config.tasks.map((task) => ({
|
|
623
636
|
name: task.name,
|
|
624
|
-
process: this.applyDefaults(task.name, task.definition
|
|
637
|
+
process: this.applyDefaults(task.name, task.definition)
|
|
625
638
|
})), (processName) => {
|
|
626
639
|
return this.taskLogFile(processName);
|
|
627
640
|
});
|
|
@@ -637,19 +650,15 @@ var Manager = class {
|
|
|
637
650
|
}
|
|
638
651
|
if (this.config.crons) for (const entry of this.config.crons) {
|
|
639
652
|
const processLogger = this.logger.child(entry.name, { logFile: this.cronLogFile(entry.name) });
|
|
640
|
-
const cronProcess = new CronProcess(entry.name, this.applyDefaults(entry.name, entry.definition
|
|
653
|
+
const cronProcess = new CronProcess(entry.name, this.applyDefaults(entry.name, entry.definition), entry.options, processLogger);
|
|
641
654
|
this.cronProcesses.set(entry.name, cronProcess);
|
|
642
655
|
cronProcess.start();
|
|
643
656
|
this.logger.info(`Started cron process: ${entry.name}`);
|
|
644
657
|
}
|
|
645
658
|
if (this.config.processes) for (const entry of this.config.processes) {
|
|
646
659
|
const processLogger = this.logger.child(entry.name, { logFile: this.processLogFile(entry.name) });
|
|
647
|
-
const restartingProcess = new RestartingProcess(entry.name, this.applyDefaults(entry.name, entry.definition
|
|
660
|
+
const restartingProcess = new RestartingProcess(entry.name, this.applyDefaults(entry.name, entry.definition), entry.options ?? DEFAULT_RESTART_OPTIONS, processLogger);
|
|
648
661
|
this.restartingProcesses.set(entry.name, restartingProcess);
|
|
649
|
-
if (entry.port !== void 0) {
|
|
650
|
-
this.processPortConfig.set(entry.name, entry.port);
|
|
651
|
-
await this.killPortForProcess(entry.name);
|
|
652
|
-
}
|
|
653
662
|
restartingProcess.start();
|
|
654
663
|
this.processEnvReloadConfig.set(entry.name, entry.envReloadDelay ?? 5e3);
|
|
655
664
|
this.logger.info(`Started restarting process: ${entry.name}`);
|
|
@@ -663,7 +672,6 @@ var Manager = class {
|
|
|
663
672
|
async stop(timeout) {
|
|
664
673
|
if (this._state === "idle" || this._state === "stopped") {
|
|
665
674
|
this._state = "stopped";
|
|
666
|
-
this.unregisterShutdownHandlers();
|
|
667
675
|
return;
|
|
668
676
|
}
|
|
669
677
|
this._state = "stopping";
|
|
@@ -674,42 +682,15 @@ var Manager = class {
|
|
|
674
682
|
this.envChangeUnsubscribe();
|
|
675
683
|
this.envChangeUnsubscribe = null;
|
|
676
684
|
}
|
|
677
|
-
this.envManager.
|
|
685
|
+
this.envManager.close();
|
|
686
|
+
for (const [signal, handler] of this.signalHandlers) process.off(signal, handler);
|
|
687
|
+
this.signalHandlers.clear();
|
|
678
688
|
if (this.taskList) await this.taskList.stop(timeout);
|
|
679
689
|
const cronStopPromises = Array.from(this.cronProcesses.values()).map((p) => p.stop(timeout));
|
|
680
690
|
const restartingStopPromises = Array.from(this.restartingProcesses.values()).map((p) => p.stop(timeout));
|
|
681
691
|
await Promise.all([...cronStopPromises, ...restartingStopPromises]);
|
|
682
692
|
this._state = "stopped";
|
|
683
693
|
this.logger.info(`Manager stopped`);
|
|
684
|
-
this.unregisterShutdownHandlers();
|
|
685
|
-
}
|
|
686
|
-
/**
|
|
687
|
-
* Register signal handlers for graceful shutdown
|
|
688
|
-
* Called automatically by constructor
|
|
689
|
-
*/
|
|
690
|
-
registerShutdownHandlers() {
|
|
691
|
-
if (this.signalHandlers.size > 0) {
|
|
692
|
-
this.logger.warn(`Shutdown handlers already registered`);
|
|
693
|
-
return;
|
|
694
|
-
}
|
|
695
|
-
for (const signal of SHUTDOWN_SIGNALS) {
|
|
696
|
-
const handler = () => this.handleSignal(signal);
|
|
697
|
-
this.signalHandlers.set(signal, handler);
|
|
698
|
-
process.on(signal, handler);
|
|
699
|
-
this.logger.debug(`Registered handler for ${signal}`);
|
|
700
|
-
}
|
|
701
|
-
this.logger.info(`Shutdown handlers registered for signals: ${SHUTDOWN_SIGNALS.join(", ")}`);
|
|
702
|
-
}
|
|
703
|
-
/**
|
|
704
|
-
* Unregister signal handlers for graceful shutdown
|
|
705
|
-
*/
|
|
706
|
-
unregisterShutdownHandlers() {
|
|
707
|
-
if (this.signalHandlers.size === 0) return;
|
|
708
|
-
for (const [signal, handler] of this.signalHandlers.entries()) {
|
|
709
|
-
process.off(signal, handler);
|
|
710
|
-
this.logger.debug(`Unregistered handler for ${signal}`);
|
|
711
|
-
}
|
|
712
|
-
this.signalHandlers.clear();
|
|
713
694
|
}
|
|
714
695
|
/**
|
|
715
696
|
* Wait for shutdown to complete (useful for keeping process alive)
|
|
@@ -753,8 +734,8 @@ var Manager = class {
|
|
|
753
734
|
this.isShuttingDown = true;
|
|
754
735
|
this.shutdownPromise = this.performShutdown();
|
|
755
736
|
this.shutdownPromise.then(() => {
|
|
756
|
-
this.logger.info(`Exiting with code
|
|
757
|
-
process.exit(
|
|
737
|
+
this.logger.info(`Exiting with code 0`);
|
|
738
|
+
process.exit();
|
|
758
739
|
}).catch((err) => {
|
|
759
740
|
this.logger.error(`Shutdown error:`, err);
|
|
760
741
|
process.exit(1);
|
|
@@ -781,64 +762,126 @@ var Manager = class {
|
|
|
781
762
|
};
|
|
782
763
|
|
|
783
764
|
//#endregion
|
|
784
|
-
//#region src/
|
|
785
|
-
|
|
786
|
-
program.name("pidnap").description("Process manager with init system capabilities").version("0.0.0-dev.1");
|
|
787
|
-
program.command("init").description("Initialize and run the process manager with config file").option("-c, --config [path]", "Path to config file", "pidnap.config.ts").action(async (options) => {
|
|
788
|
-
const initLogger = logger({ name: "pidnap" });
|
|
765
|
+
//#region src/utils.ts
|
|
766
|
+
async function tImport(path) {
|
|
789
767
|
try {
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
const
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
768
|
+
return await import(path);
|
|
769
|
+
} catch {
|
|
770
|
+
const { tsImport } = await import("tsx/esm/api");
|
|
771
|
+
return await tsImport(path, { parentURL: import.meta.url });
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
//#endregion
|
|
776
|
+
//#region package.json
|
|
777
|
+
var version = "0.0.0-dev.6";
|
|
778
|
+
|
|
779
|
+
//#endregion
|
|
780
|
+
//#region src/cli.ts
|
|
781
|
+
const os$1 = os.$context().$meta({});
|
|
782
|
+
const ResourceTarget = v.union([v.string(), v.number()]);
|
|
783
|
+
function printProcessTable(proc) {
|
|
784
|
+
const table = new Table({ head: [
|
|
785
|
+
"Name",
|
|
786
|
+
"State",
|
|
787
|
+
"Restarts"
|
|
788
|
+
] });
|
|
789
|
+
table.push([
|
|
790
|
+
proc.name,
|
|
791
|
+
proc.state,
|
|
792
|
+
proc.restarts
|
|
793
|
+
]);
|
|
794
|
+
console.log(table.toString());
|
|
795
|
+
}
|
|
796
|
+
function printTaskTable(task) {
|
|
797
|
+
const table = new Table({ head: [
|
|
798
|
+
"Id",
|
|
799
|
+
"State",
|
|
800
|
+
"Processes"
|
|
801
|
+
] });
|
|
802
|
+
table.push([
|
|
803
|
+
task.id,
|
|
804
|
+
task.state,
|
|
805
|
+
task.processNames.join(", ")
|
|
806
|
+
]);
|
|
807
|
+
console.log(table.toString());
|
|
808
|
+
}
|
|
809
|
+
function printCronTable(cron) {
|
|
810
|
+
const table = new Table({ head: [
|
|
811
|
+
"Name",
|
|
812
|
+
"State",
|
|
813
|
+
"Runs",
|
|
814
|
+
"Fails",
|
|
815
|
+
"Next Run"
|
|
816
|
+
] });
|
|
817
|
+
table.push([
|
|
818
|
+
cron.name,
|
|
819
|
+
cron.state,
|
|
820
|
+
cron.runCount,
|
|
821
|
+
cron.failCount,
|
|
822
|
+
cron.nextRun ?? "-"
|
|
823
|
+
]);
|
|
824
|
+
console.log(table.toString());
|
|
825
|
+
}
|
|
826
|
+
const cliRouter = os$1.router({
|
|
827
|
+
init: os.meta({
|
|
828
|
+
description: "Initialize and run the process manager with config file",
|
|
829
|
+
aliases: { options: { config: "c" } }
|
|
830
|
+
}).input(v.object({ config: v.optional(v.pipe(v.string(), v.description("Path to config file")), "pidnap.config.ts") })).handler(async ({ input }) => {
|
|
831
|
+
process.title = "pidnap";
|
|
832
|
+
const initLogger = logger({ name: "pidnap" });
|
|
833
|
+
try {
|
|
834
|
+
const configUrl = pathToFileURL(resolve(process.cwd(), input.config)).href;
|
|
835
|
+
const configModule = await tImport(configUrl);
|
|
836
|
+
const rawConfig = configModule.default.default || configModule.default || configModule.config || {};
|
|
837
|
+
const config = v.parse(ManagerConfig, rawConfig);
|
|
838
|
+
const host = config.http?.host ?? "127.0.0.1";
|
|
839
|
+
const port = config.http?.port ?? 9876;
|
|
840
|
+
const authToken = config.http?.authToken;
|
|
841
|
+
const managerLogger = logger({
|
|
842
|
+
name: "pidnap",
|
|
843
|
+
logFile: resolve(config.logDir ?? resolve(process.cwd(), "logs"), "pidnap.log")
|
|
844
|
+
});
|
|
845
|
+
const manager = new Manager(config, managerLogger);
|
|
846
|
+
const handler = new RPCHandler(router, { interceptors: [onError((error) => {
|
|
847
|
+
managerLogger.error(error);
|
|
848
|
+
})] });
|
|
849
|
+
const server = createServer(async (req, res) => {
|
|
850
|
+
if (authToken) {
|
|
851
|
+
if (req.headers["authorization"]?.replace("Bearer ", "") !== authToken) {
|
|
852
|
+
res.statusCode = 401;
|
|
853
|
+
res.end("Unauthorized");
|
|
854
|
+
return;
|
|
855
|
+
}
|
|
811
856
|
}
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
857
|
+
const { matched } = await handler.handle(req, res, {
|
|
858
|
+
prefix: "/rpc",
|
|
859
|
+
context: { manager }
|
|
860
|
+
});
|
|
861
|
+
if (matched) return;
|
|
862
|
+
res.statusCode = 404;
|
|
863
|
+
res.end("Not found\n");
|
|
816
864
|
});
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
}
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
}
|
|
838
|
-
});
|
|
839
|
-
program.command("status").description("Show manager status").option("-u, --url <url>", "RPC server URL", "http://localhost:3000/rpc").action(async (options) => {
|
|
840
|
-
try {
|
|
841
|
-
const status = await createClient(options.url).manager.status();
|
|
865
|
+
server.listen(port, host, async () => {
|
|
866
|
+
managerLogger.info(`pidnap RPC server running on http://${host}:${port}`);
|
|
867
|
+
if (authToken) managerLogger.info("Auth token required for API access");
|
|
868
|
+
try {
|
|
869
|
+
await manager.start();
|
|
870
|
+
} catch (err) {
|
|
871
|
+
managerLogger.error("Failed to start manager:", err);
|
|
872
|
+
server.close();
|
|
873
|
+
process.exit(1);
|
|
874
|
+
}
|
|
875
|
+
});
|
|
876
|
+
await manager.waitForShutdown();
|
|
877
|
+
server.close();
|
|
878
|
+
} catch (error) {
|
|
879
|
+
initLogger.error("Failed to start pidnap:", error);
|
|
880
|
+
process.exit(1);
|
|
881
|
+
}
|
|
882
|
+
}),
|
|
883
|
+
status: os$1.meta({ description: "Show manager status" }).handler(async ({ context: { client } }) => {
|
|
884
|
+
const status = await client.manager.status();
|
|
842
885
|
const table = new Table({ head: [
|
|
843
886
|
"State",
|
|
844
887
|
"Processes",
|
|
@@ -852,356 +895,194 @@ program.command("status").description("Show manager status").option("-u, --url <
|
|
|
852
895
|
status.taskCount
|
|
853
896
|
]);
|
|
854
897
|
console.log(table.toString());
|
|
855
|
-
}
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
"
|
|
885
|
-
"
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
"
|
|
928
|
-
"
|
|
929
|
-
"
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
});
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
target:
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
}
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
cron
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
}
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
"
|
|
1021
|
-
"
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
]);
|
|
1033
|
-
console.log(table.toString());
|
|
1034
|
-
} catch (error) {
|
|
1035
|
-
console.error("Failed to get cron:", error);
|
|
1036
|
-
process.exit(1);
|
|
1037
|
-
}
|
|
1038
|
-
});
|
|
1039
|
-
crons.command("trigger").description("Trigger a cron process").argument("<target>", "Cron name or index").option("-u, --url <url>", "RPC server URL", "http://localhost:3000/rpc").action(async (target, options) => {
|
|
1040
|
-
try {
|
|
1041
|
-
const cron = await createClient(options.url).crons.trigger({ target: parseTarget(target) });
|
|
1042
|
-
const table = new Table({ head: [
|
|
1043
|
-
"Name",
|
|
1044
|
-
"State",
|
|
1045
|
-
"Runs",
|
|
1046
|
-
"Fails",
|
|
1047
|
-
"Next Run"
|
|
1048
|
-
] });
|
|
1049
|
-
table.push([
|
|
1050
|
-
cron.name,
|
|
1051
|
-
cron.state,
|
|
1052
|
-
cron.runCount,
|
|
1053
|
-
cron.failCount,
|
|
1054
|
-
cron.nextRun ?? "-"
|
|
1055
|
-
]);
|
|
1056
|
-
console.log(table.toString());
|
|
1057
|
-
} catch (error) {
|
|
1058
|
-
console.error("Failed to trigger cron:", error);
|
|
1059
|
-
process.exit(1);
|
|
1060
|
-
}
|
|
1061
|
-
});
|
|
1062
|
-
crons.command("start").description("Start a cron process").argument("<target>", "Cron name or index").option("-u, --url <url>", "RPC server URL", "http://localhost:3000/rpc").action(async (target, options) => {
|
|
1063
|
-
try {
|
|
1064
|
-
const cron = await createClient(options.url).crons.start({ target: parseTarget(target) });
|
|
1065
|
-
const table = new Table({ head: [
|
|
1066
|
-
"Name",
|
|
1067
|
-
"State",
|
|
1068
|
-
"Runs",
|
|
1069
|
-
"Fails",
|
|
1070
|
-
"Next Run"
|
|
1071
|
-
] });
|
|
1072
|
-
table.push([
|
|
1073
|
-
cron.name,
|
|
1074
|
-
cron.state,
|
|
1075
|
-
cron.runCount,
|
|
1076
|
-
cron.failCount,
|
|
1077
|
-
cron.nextRun ?? "-"
|
|
1078
|
-
]);
|
|
1079
|
-
console.log(table.toString());
|
|
1080
|
-
} catch (error) {
|
|
1081
|
-
console.error("Failed to start cron:", error);
|
|
1082
|
-
process.exit(1);
|
|
1083
|
-
}
|
|
1084
|
-
});
|
|
1085
|
-
crons.command("stop").description("Stop a cron process").argument("<target>", "Cron name or index").option("-u, --url <url>", "RPC server URL", "http://localhost:3000/rpc").action(async (target, options) => {
|
|
1086
|
-
try {
|
|
1087
|
-
const cron = await createClient(options.url).crons.stop({ target: parseTarget(target) });
|
|
1088
|
-
const table = new Table({ head: [
|
|
1089
|
-
"Name",
|
|
1090
|
-
"State",
|
|
1091
|
-
"Runs",
|
|
1092
|
-
"Fails",
|
|
1093
|
-
"Next Run"
|
|
1094
|
-
] });
|
|
1095
|
-
table.push([
|
|
1096
|
-
cron.name,
|
|
1097
|
-
cron.state,
|
|
1098
|
-
cron.runCount,
|
|
1099
|
-
cron.failCount,
|
|
1100
|
-
cron.nextRun ?? "-"
|
|
1101
|
-
]);
|
|
1102
|
-
console.log(table.toString());
|
|
1103
|
-
} catch (error) {
|
|
1104
|
-
console.error("Failed to stop cron:", error);
|
|
1105
|
-
process.exit(1);
|
|
1106
|
-
}
|
|
1107
|
-
});
|
|
1108
|
-
const tasks = program.command("tasks").description("Manage tasks");
|
|
1109
|
-
tasks.command("list").description("List tasks").option("-u, --url <url>", "RPC server URL", "http://localhost:3000/rpc").action(async (options) => {
|
|
1110
|
-
try {
|
|
1111
|
-
const tasks = await createClient(options.url).tasks.list();
|
|
1112
|
-
const table = new Table({ head: [
|
|
1113
|
-
"Id",
|
|
1114
|
-
"State",
|
|
1115
|
-
"Processes"
|
|
1116
|
-
] });
|
|
1117
|
-
for (const task of tasks) table.push([
|
|
1118
|
-
task.id,
|
|
1119
|
-
task.state,
|
|
1120
|
-
task.processNames.join(", ")
|
|
1121
|
-
]);
|
|
1122
|
-
console.log(table.toString());
|
|
1123
|
-
} catch (error) {
|
|
1124
|
-
console.error("Failed to list tasks:", error);
|
|
1125
|
-
process.exit(1);
|
|
1126
|
-
}
|
|
1127
|
-
});
|
|
1128
|
-
tasks.command("get").description("Get a task by id or index").argument("<target>", "Task id or index").option("-u, --url <url>", "RPC server URL", "http://localhost:3000/rpc").action(async (target, options) => {
|
|
1129
|
-
try {
|
|
1130
|
-
const task = await createClient(options.url).tasks.get({ target: parseTarget(target) });
|
|
1131
|
-
const table = new Table({ head: [
|
|
1132
|
-
"Id",
|
|
1133
|
-
"State",
|
|
1134
|
-
"Processes"
|
|
1135
|
-
] });
|
|
1136
|
-
table.push([
|
|
1137
|
-
task.id,
|
|
1138
|
-
task.state,
|
|
1139
|
-
task.processNames.join(", ")
|
|
1140
|
-
]);
|
|
1141
|
-
console.log(table.toString());
|
|
1142
|
-
} catch (error) {
|
|
1143
|
-
console.error("Failed to get task:", error);
|
|
1144
|
-
process.exit(1);
|
|
1145
|
-
}
|
|
1146
|
-
});
|
|
1147
|
-
tasks.command("add").description("Add a task").requiredOption("-n, --name <name>", "Task name").requiredOption("-d, --definition <json>", "Process definition JSON").option("-u, --url <url>", "RPC server URL", "http://localhost:3000/rpc").action(async (options) => {
|
|
1148
|
-
try {
|
|
1149
|
-
const client = createClient(options.url);
|
|
1150
|
-
const definition = parseDefinition(options.definition);
|
|
1151
|
-
const task = await client.tasks.add({
|
|
1152
|
-
name: options.name,
|
|
1153
|
-
definition
|
|
1154
|
-
});
|
|
1155
|
-
const table = new Table({ head: [
|
|
1156
|
-
"Id",
|
|
1157
|
-
"State",
|
|
1158
|
-
"Processes"
|
|
1159
|
-
] });
|
|
1160
|
-
table.push([
|
|
1161
|
-
task.id,
|
|
1162
|
-
task.state,
|
|
1163
|
-
task.processNames.join(", ")
|
|
1164
|
-
]);
|
|
1165
|
-
console.log(table.toString());
|
|
1166
|
-
} catch (error) {
|
|
1167
|
-
console.error("Failed to add task:", error);
|
|
1168
|
-
process.exit(1);
|
|
1169
|
-
}
|
|
898
|
+
}),
|
|
899
|
+
process: os$1.router({
|
|
900
|
+
list: os$1.meta({ description: "List restarting processes" }).handler(async ({ context: { client } }) => {
|
|
901
|
+
const processes = await client.processes.list();
|
|
902
|
+
const table = new Table({ head: [
|
|
903
|
+
"Name",
|
|
904
|
+
"State",
|
|
905
|
+
"Restarts"
|
|
906
|
+
] });
|
|
907
|
+
for (const proc of processes) table.push([
|
|
908
|
+
proc.name,
|
|
909
|
+
proc.state,
|
|
910
|
+
proc.restarts
|
|
911
|
+
]);
|
|
912
|
+
console.log(table.toString());
|
|
913
|
+
}),
|
|
914
|
+
get: os$1.meta({
|
|
915
|
+
description: "Get a restarting process by name or index",
|
|
916
|
+
aliases: { options: { target: "t" } }
|
|
917
|
+
}).input(v.object({ target: v.pipe(ResourceTarget, v.description("Process name or index")) })).handler(async ({ input, context: { client } }) => {
|
|
918
|
+
printProcessTable(await client.processes.get({ target: input.target }));
|
|
919
|
+
}),
|
|
920
|
+
add: os$1.meta({
|
|
921
|
+
description: "Add a restarting process",
|
|
922
|
+
aliases: { options: {
|
|
923
|
+
name: "n",
|
|
924
|
+
definition: "d"
|
|
925
|
+
} }
|
|
926
|
+
}).input(v.object({
|
|
927
|
+
name: v.pipe(v.string(), v.description("Process name")),
|
|
928
|
+
definition: v.pipe(ProcessDefinition, v.description("Process definition JSON"))
|
|
929
|
+
})).handler(async ({ input, context: { client } }) => {
|
|
930
|
+
printProcessTable(await client.processes.add({
|
|
931
|
+
name: input.name,
|
|
932
|
+
definition: input.definition
|
|
933
|
+
}));
|
|
934
|
+
}),
|
|
935
|
+
start: os$1.meta({
|
|
936
|
+
description: "Start a restarting process",
|
|
937
|
+
aliases: { options: { target: "t" } }
|
|
938
|
+
}).input(v.object({ target: v.pipe(ResourceTarget, v.description("Process name or index")) })).handler(async ({ input, context: { client } }) => {
|
|
939
|
+
printProcessTable(await client.processes.start({ target: input.target }));
|
|
940
|
+
}),
|
|
941
|
+
stop: os$1.meta({
|
|
942
|
+
description: "Stop a restarting process",
|
|
943
|
+
aliases: { options: { target: "t" } }
|
|
944
|
+
}).input(v.object({ target: v.pipe(ResourceTarget, v.description("Process name or index")) })).handler(async ({ input, context: { client } }) => {
|
|
945
|
+
printProcessTable(await client.processes.stop({ target: input.target }));
|
|
946
|
+
}),
|
|
947
|
+
restart: os$1.meta({
|
|
948
|
+
description: "Restart a restarting process",
|
|
949
|
+
aliases: { options: {
|
|
950
|
+
target: "t",
|
|
951
|
+
force: "f"
|
|
952
|
+
} }
|
|
953
|
+
}).input(v.object({
|
|
954
|
+
target: v.pipe(ResourceTarget, v.description("Process name or index")),
|
|
955
|
+
force: v.optional(v.pipe(v.boolean(), v.description("Force restart")))
|
|
956
|
+
})).handler(async ({ input, context: { client } }) => {
|
|
957
|
+
printProcessTable(await client.processes.restart({
|
|
958
|
+
target: input.target,
|
|
959
|
+
force: input.force
|
|
960
|
+
}));
|
|
961
|
+
}),
|
|
962
|
+
reload: os$1.meta({
|
|
963
|
+
description: "Reload a restarting process definition",
|
|
964
|
+
aliases: { options: {
|
|
965
|
+
target: "t",
|
|
966
|
+
definition: "d",
|
|
967
|
+
restartImmediately: "r"
|
|
968
|
+
} }
|
|
969
|
+
}).input(v.object({
|
|
970
|
+
target: v.pipe(ResourceTarget, v.description("Process name or index")),
|
|
971
|
+
definition: v.pipe(ProcessDefinition, v.description("Process definition JSON")),
|
|
972
|
+
restartImmediately: v.optional(v.pipe(v.boolean(), v.description("Restart immediately after reload")))
|
|
973
|
+
})).handler(async ({ input, context: { client } }) => {
|
|
974
|
+
printProcessTable(await client.processes.reload({
|
|
975
|
+
target: input.target,
|
|
976
|
+
definition: input.definition,
|
|
977
|
+
restartImmediately: input.restartImmediately
|
|
978
|
+
}));
|
|
979
|
+
}),
|
|
980
|
+
remove: os$1.meta({
|
|
981
|
+
description: "Remove a restarting process",
|
|
982
|
+
aliases: { options: { target: "t" } }
|
|
983
|
+
}).input(v.object({ target: v.pipe(ResourceTarget, v.description("Process name or index")) })).handler(async ({ input, context: { client } }) => {
|
|
984
|
+
await client.processes.remove({ target: input.target });
|
|
985
|
+
console.log("Process removed");
|
|
986
|
+
})
|
|
987
|
+
}),
|
|
988
|
+
tasks: os$1.router({
|
|
989
|
+
list: os$1.meta({ description: "List tasks" }).handler(async ({ context: { client } }) => {
|
|
990
|
+
const tasks = await client.tasks.list();
|
|
991
|
+
const table = new Table({ head: [
|
|
992
|
+
"Id",
|
|
993
|
+
"State",
|
|
994
|
+
"Processes"
|
|
995
|
+
] });
|
|
996
|
+
for (const task of tasks) table.push([
|
|
997
|
+
task.id,
|
|
998
|
+
task.state,
|
|
999
|
+
task.processNames.join(", ")
|
|
1000
|
+
]);
|
|
1001
|
+
console.log(table.toString());
|
|
1002
|
+
}),
|
|
1003
|
+
get: os$1.meta({
|
|
1004
|
+
description: "Get a task by id or index",
|
|
1005
|
+
aliases: { options: { target: "t" } }
|
|
1006
|
+
}).input(v.object({ target: v.pipe(ResourceTarget, v.description("Task id or index")) })).handler(async ({ input, context: { client } }) => {
|
|
1007
|
+
printTaskTable(await client.tasks.get({ target: input.target }));
|
|
1008
|
+
}),
|
|
1009
|
+
add: os$1.meta({
|
|
1010
|
+
description: "Add a task",
|
|
1011
|
+
aliases: { options: {
|
|
1012
|
+
name: "n",
|
|
1013
|
+
definition: "d"
|
|
1014
|
+
} }
|
|
1015
|
+
}).input(v.object({
|
|
1016
|
+
name: v.pipe(v.string(), v.description("Task name")),
|
|
1017
|
+
definition: v.pipe(ProcessDefinition, v.description("Process definition JSON"))
|
|
1018
|
+
})).handler(async ({ input, context: { client } }) => {
|
|
1019
|
+
printTaskTable(await client.tasks.add({
|
|
1020
|
+
name: input.name,
|
|
1021
|
+
definition: input.definition
|
|
1022
|
+
}));
|
|
1023
|
+
}),
|
|
1024
|
+
remove: os$1.meta({
|
|
1025
|
+
description: "Remove a task by id or index",
|
|
1026
|
+
aliases: { options: { target: "t" } }
|
|
1027
|
+
}).input(v.object({ target: v.pipe(ResourceTarget, v.description("Task id or index")) })).handler(async ({ input, context: { client } }) => {
|
|
1028
|
+
printTaskTable(await client.tasks.remove({ target: input.target }));
|
|
1029
|
+
})
|
|
1030
|
+
}),
|
|
1031
|
+
crons: os$1.router({
|
|
1032
|
+
list: os$1.meta({ description: "List cron processes" }).handler(async ({ context: { client } }) => {
|
|
1033
|
+
const crons = await client.crons.list();
|
|
1034
|
+
const table = new Table({ head: [
|
|
1035
|
+
"Name",
|
|
1036
|
+
"State",
|
|
1037
|
+
"Runs",
|
|
1038
|
+
"Fails",
|
|
1039
|
+
"Next Run"
|
|
1040
|
+
] });
|
|
1041
|
+
for (const cron of crons) table.push([
|
|
1042
|
+
cron.name,
|
|
1043
|
+
cron.state,
|
|
1044
|
+
cron.runCount,
|
|
1045
|
+
cron.failCount,
|
|
1046
|
+
cron.nextRun ?? "-"
|
|
1047
|
+
]);
|
|
1048
|
+
console.log(table.toString());
|
|
1049
|
+
}),
|
|
1050
|
+
get: os$1.meta({
|
|
1051
|
+
description: "Get a cron process by name or index",
|
|
1052
|
+
aliases: { options: { target: "t" } }
|
|
1053
|
+
}).input(v.object({ target: v.pipe(ResourceTarget, v.description("Cron name or index")) })).handler(async ({ input, context: { client } }) => {
|
|
1054
|
+
printCronTable(await client.crons.get({ target: input.target }));
|
|
1055
|
+
}),
|
|
1056
|
+
trigger: os$1.meta({
|
|
1057
|
+
description: "Trigger a cron process",
|
|
1058
|
+
aliases: { options: { target: "t" } }
|
|
1059
|
+
}).input(v.object({ target: v.pipe(ResourceTarget, v.description("Cron name or index")) })).handler(async ({ input, context: { client } }) => {
|
|
1060
|
+
printCronTable(await client.crons.trigger({ target: input.target }));
|
|
1061
|
+
}),
|
|
1062
|
+
start: os$1.meta({
|
|
1063
|
+
description: "Start a cron process",
|
|
1064
|
+
aliases: { options: { target: "t" } }
|
|
1065
|
+
}).input(v.object({ target: v.pipe(ResourceTarget, v.description("Cron name or index")) })).handler(async ({ input, context: { client } }) => {
|
|
1066
|
+
printCronTable(await client.crons.start({ target: input.target }));
|
|
1067
|
+
}),
|
|
1068
|
+
stop: os$1.meta({
|
|
1069
|
+
description: "Stop a cron process",
|
|
1070
|
+
aliases: { options: { target: "t" } }
|
|
1071
|
+
}).input(v.object({ target: v.pipe(ResourceTarget, v.description("Cron name or index")) })).handler(async ({ input, context: { client } }) => {
|
|
1072
|
+
printCronTable(await client.crons.stop({ target: input.target }));
|
|
1073
|
+
})
|
|
1074
|
+
})
|
|
1170
1075
|
});
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
task.state,
|
|
1182
|
-
task.processNames.join(", ")
|
|
1183
|
-
]);
|
|
1184
|
-
console.log(table.toString());
|
|
1185
|
-
} catch (error) {
|
|
1186
|
-
console.error("Failed to remove task:", error);
|
|
1187
|
-
process.exit(1);
|
|
1188
|
-
}
|
|
1189
|
-
});
|
|
1190
|
-
const TargetSchema = v.union([v.string(), v.number()]);
|
|
1191
|
-
function parseTarget(value) {
|
|
1192
|
-
const asNumber = Number(value);
|
|
1193
|
-
return v.parse(TargetSchema, Number.isNaN(asNumber) ? value : asNumber);
|
|
1194
|
-
}
|
|
1195
|
-
function parseDefinition(raw) {
|
|
1196
|
-
try {
|
|
1197
|
-
const parsed = JSON.parse(raw);
|
|
1198
|
-
return v.parse(ProcessDefinitionSchema, parsed);
|
|
1199
|
-
} catch (error) {
|
|
1200
|
-
console.error("Invalid --definition JSON. Expected a ProcessDefinition.");
|
|
1201
|
-
throw error;
|
|
1202
|
-
}
|
|
1203
|
-
}
|
|
1204
|
-
program.parse();
|
|
1076
|
+
const client = createClient();
|
|
1077
|
+
createCli({
|
|
1078
|
+
name: "pidnap",
|
|
1079
|
+
version,
|
|
1080
|
+
router: cliRouter,
|
|
1081
|
+
context: { client }
|
|
1082
|
+
}).run({ formatError(error) {
|
|
1083
|
+
if (error?.cause?.code === "ECONNREFUSED") return `Failed to connect to RPC server, are you sure the server is running? If the Server is running of different url, use PIDNAP_RPC_URL environment variable to set it.\n` + format(error);
|
|
1084
|
+
return format(error);
|
|
1085
|
+
} });
|
|
1205
1086
|
|
|
1206
1087
|
//#endregion
|
|
1207
1088
|
export { };
|