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 CHANGED
@@ -1,24 +1,22 @@
1
1
  #!/usr/bin/env node
2
- import { _ as ProcessDefinitionSchema, a as TaskStateSchema, c as CronProcessStateSchema, h as RestartingProcessStateSchema, i as TaskList, m as RestartingProcessOptionsSchema, n as EnvManager, o as CronProcess, p as RestartingProcess, s as CronProcessOptionsSchema, t as logger, y as treeKill } from "./logger-BIJzGqk3.mjs";
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 { promisify } from "node:util";
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: RestartingProcessStateSchema,
35
+ state: RestartingProcessState,
38
36
  restarts: v.number()
39
37
  });
40
38
  const CronProcessInfoSchema = v.object({
41
39
  name: v.string(),
42
- state: CronProcessStateSchema,
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$1 = {
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: ProcessDefinitionSchema
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: ProcessDefinitionSchema,
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$1 = {
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: ProcessDefinitionSchema
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$1 = {
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: processes$1,
92
- tasks: tasks$1,
93
- crons: crons$1
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 HttpServerConfigSchema = v.object({
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 CronProcessEntrySchema = v.object({
221
+ const CronProcessEntry = v.object({
244
222
  name: v.string(),
245
- definition: ProcessDefinitionSchema,
246
- options: CronProcessOptionsSchema,
223
+ definition: ProcessDefinition,
224
+ options: CronProcessOptions,
247
225
  envFile: v.optional(v.string())
248
226
  });
249
- const EnvReloadDelaySchema = v.union([
227
+ const EnvReloadDelay = v.union([
250
228
  v.number(),
251
229
  v.boolean(),
252
230
  v.literal("immediately")
253
231
  ]);
254
- const RestartingProcessEntrySchema = v.object({
232
+ const RestartingProcessEntry = v.object({
255
233
  name: v.string(),
256
- definition: ProcessDefinitionSchema,
257
- options: v.optional(RestartingProcessOptionsSchema),
234
+ definition: ProcessDefinition,
235
+ options: v.optional(RestartingProcessOptions),
258
236
  envFile: v.optional(v.string()),
259
- envReloadDelay: v.optional(EnvReloadDelaySchema),
260
- port: v.optional(v.number())
237
+ envReloadDelay: v.optional(EnvReloadDelay)
261
238
  });
262
- const TaskEntrySchema = v.object({
239
+ const TaskEntry = v.object({
263
240
  name: v.string(),
264
- definition: ProcessDefinitionSchema,
241
+ definition: ProcessDefinition,
265
242
  envFile: v.optional(v.string())
266
243
  });
267
- const ManagerConfigSchema = v.object({
268
- http: v.optional(HttpServerConfigSchema),
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
- envFiles: v.optional(v.record(v.string(), v.string())),
273
- tasks: v.optional(v.array(TaskEntrySchema)),
274
- crons: v.optional(v.array(CronProcessEntrySchema)),
275
- processes: v.optional(v.array(RestartingProcessEntrySchema))
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(), "logs");
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: config.cwd,
305
- files: config.envFiles,
306
- watch: true
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
- this.envChangeUnsubscribe = this.envManager.onChange((changedKeys) => {
309
- this.handleEnvChange(changedKeys);
300
+ for (const cron of this.config.crons ?? []) allNames.push({
301
+ name: cron.name,
302
+ type: "cron"
310
303
  });
311
- this.registerShutdownHandlers();
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
- * Merge global env with process-specific env and apply cwd inheritance
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
- applyDefaults(processName, definition, envFile) {
318
- const envFromFiles = this.envManager.getEnvVars(processName);
319
- let envFromCustomFile = {};
320
- if (envFile) {
321
- let key = this.envFileKeyByProcess.get(processName);
322
- if (!key) {
323
- key = `custom:${processName}`;
324
- this.envFileKeyByProcess.set(processName, key);
325
- this.envManager.registerFile(key, envFile);
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
- ...envFromFiles,
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(changedKeys) {
377
+ handleEnvChange(event) {
367
378
  if (this._state !== "running") return;
368
- this.logger.info(`Env files changed for keys: ${changedKeys.join(", ")}`);
369
- const affectedProcesses = /* @__PURE__ */ new Set();
370
- for (const key of changedKeys) if (key === "global") for (const processName of this.restartingProcesses.keys()) affectedProcesses.add(processName);
371
- else {
372
- if (this.restartingProcesses.has(key)) affectedProcesses.add(key);
373
- for (const [processName, customKey] of this.envFileKeyByProcess.entries()) if (customKey === key && this.restartingProcesses.has(processName)) affectedProcesses.add(processName);
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
- for (const processName of affectedProcesses) {
388
+ if (event.type === "process") {
389
+ const processName = event.key;
376
390
  const reloadDelay = this.processEnvReloadConfig.get(processName);
377
- if (reloadDelay === false) continue;
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, processConfig.envFile);
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, port) {
571
- if (this.restartingProcesses.has(name)) throw new Error(`Process "${name}" already exists`);
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, task.envFile)
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, entry.envFile), entry.options, processLogger);
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, entry.envFile), entry.options ?? DEFAULT_RESTART_OPTIONS, processLogger);
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.dispose();
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 ${SHUTDOWN_EXIT_CODE}`);
757
- process.exit(SHUTDOWN_EXIT_CODE);
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/cli.ts
785
- const program = new Command();
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
- const configUrl = pathToFileURL(resolve(process.cwd(), options.config)).href;
791
- const configModule = await tsImport(configUrl, { parentURL: import.meta.url });
792
- const rawConfig = configModule.default.default || configModule.default || configModule.config || {};
793
- const config = v.parse(ManagerConfigSchema, rawConfig);
794
- const host = config.http?.host ?? "localhost";
795
- const port = config.http?.port ?? 3e3;
796
- const authToken = config.http?.authToken;
797
- const managerLogger = logger({
798
- name: "pidnap",
799
- logFile: resolve(config.logDir ?? resolve(process.cwd(), "logs"), "pidnap.log")
800
- });
801
- const manager = new Manager(config, managerLogger);
802
- const handler = new RPCHandler(router, { interceptors: [onError((error) => {
803
- managerLogger.error(error);
804
- })] });
805
- const server = createServer(async (req, res) => {
806
- if (authToken) {
807
- if (req.headers["authorization"]?.replace("Bearer ", "") !== authToken) {
808
- res.statusCode = 401;
809
- res.end("Unauthorized");
810
- return;
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
- const { matched } = await handler.handle(req, res, {
814
- prefix: "/rpc",
815
- context: { manager }
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
- if (matched) return;
818
- res.statusCode = 404;
819
- res.end("Not found");
820
- });
821
- server.listen(port, host, async () => {
822
- managerLogger.info(`pidnap RPC server running on http://${host}:${port}`);
823
- if (authToken) managerLogger.info("Auth token required for API access");
824
- try {
825
- await manager.start();
826
- } catch (err) {
827
- managerLogger.error("Failed to start manager:", err);
828
- server.close();
829
- process.exit(1);
830
- }
831
- });
832
- await manager.waitForShutdown();
833
- server.close();
834
- } catch (error) {
835
- initLogger.error("Failed to start pidnap:", error);
836
- process.exit(1);
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
- } catch (error) {
856
- console.error("Failed to fetch status:", error);
857
- process.exit(1);
858
- }
859
- });
860
- const processes = program.command("processes").description("Manage restarting processes");
861
- processes.command("list").description("List restarting processes").option("-u, --url <url>", "RPC server URL", "http://localhost:3000/rpc").action(async (options) => {
862
- try {
863
- const processes = await createClient(options.url).processes.list();
864
- const table = new Table({ head: [
865
- "Name",
866
- "State",
867
- "Restarts"
868
- ] });
869
- for (const proc of processes) table.push([
870
- proc.name,
871
- proc.state,
872
- proc.restarts
873
- ]);
874
- console.log(table.toString());
875
- } catch (error) {
876
- console.error("Failed to list processes:", error);
877
- process.exit(1);
878
- }
879
- });
880
- processes.command("get").description("Get a restarting process by name or index").argument("<target>", "Process name or index").option("-u, --url <url>", "RPC server URL", "http://localhost:3000/rpc").action(async (target, options) => {
881
- try {
882
- const proc = await createClient(options.url).processes.get({ target: parseTarget(target) });
883
- const table = new Table({ head: [
884
- "Name",
885
- "State",
886
- "Restarts"
887
- ] });
888
- table.push([
889
- proc.name,
890
- proc.state,
891
- proc.restarts
892
- ]);
893
- console.log(table.toString());
894
- } catch (error) {
895
- console.error("Failed to get process:", error);
896
- process.exit(1);
897
- }
898
- });
899
- processes.command("add").description("Add a restarting process").requiredOption("-n, --name <name>", "Process name").requiredOption("-d, --definition <json>", "Process definition JSON").option("-u, --url <url>", "RPC server URL", "http://localhost:3000/rpc").action(async (options) => {
900
- try {
901
- const client = createClient(options.url);
902
- const definition = parseDefinition(options.definition);
903
- const proc = await client.processes.add({
904
- name: options.name,
905
- definition
906
- });
907
- const table = new Table({ head: [
908
- "Name",
909
- "State",
910
- "Restarts"
911
- ] });
912
- table.push([
913
- proc.name,
914
- proc.state,
915
- proc.restarts
916
- ]);
917
- console.log(table.toString());
918
- } catch (error) {
919
- console.error("Failed to add process:", error);
920
- process.exit(1);
921
- }
922
- });
923
- processes.command("start").description("Start a restarting process").argument("<target>", "Process name or index").option("-u, --url <url>", "RPC server URL", "http://localhost:3000/rpc").action(async (target, options) => {
924
- try {
925
- const proc = await createClient(options.url).processes.start({ target: parseTarget(target) });
926
- const table = new Table({ head: [
927
- "Name",
928
- "State",
929
- "Restarts"
930
- ] });
931
- table.push([
932
- proc.name,
933
- proc.state,
934
- proc.restarts
935
- ]);
936
- console.log(table.toString());
937
- } catch (error) {
938
- console.error("Failed to start process:", error);
939
- process.exit(1);
940
- }
941
- });
942
- processes.command("stop").description("Stop a restarting process").argument("<target>", "Process name or index").option("-u, --url <url>", "RPC server URL", "http://localhost:3000/rpc").action(async (target, options) => {
943
- try {
944
- const proc = await createClient(options.url).processes.stop({ target: parseTarget(target) });
945
- const table = new Table({ head: [
946
- "Name",
947
- "State",
948
- "Restarts"
949
- ] });
950
- table.push([
951
- proc.name,
952
- proc.state,
953
- proc.restarts
954
- ]);
955
- console.log(table.toString());
956
- } catch (error) {
957
- console.error("Failed to stop process:", error);
958
- process.exit(1);
959
- }
960
- });
961
- processes.command("restart").description("Restart a restarting process").argument("<target>", "Process name or index").option("-f, --force", "Force restart").option("-u, --url <url>", "RPC server URL", "http://localhost:3000/rpc").action(async (target, options) => {
962
- try {
963
- const proc = await createClient(options.url).processes.restart({
964
- target: parseTarget(target),
965
- force: options.force
966
- });
967
- const table = new Table({ head: [
968
- "Name",
969
- "State",
970
- "Restarts"
971
- ] });
972
- table.push([
973
- proc.name,
974
- proc.state,
975
- proc.restarts
976
- ]);
977
- console.log(table.toString());
978
- } catch (error) {
979
- console.error("Failed to restart process:", error);
980
- process.exit(1);
981
- }
982
- });
983
- processes.command("remove").description("Remove a restarting process").argument("<target>", "Process name or index").option("-u, --url <url>", "RPC server URL", "http://localhost:3000/rpc").action(async (target, options) => {
984
- try {
985
- await createClient(options.url).processes.remove({ target: parseTarget(target) });
986
- console.log("Process removed");
987
- } catch (error) {
988
- console.error("Failed to remove process:", error);
989
- process.exit(1);
990
- }
991
- });
992
- const crons = program.command("crons").description("Manage cron processes");
993
- crons.command("list").description("List cron processes").option("-u, --url <url>", "RPC server URL", "http://localhost:3000/rpc").action(async (options) => {
994
- try {
995
- const crons = await createClient(options.url).crons.list();
996
- const table = new Table({ head: [
997
- "Name",
998
- "State",
999
- "Runs",
1000
- "Fails",
1001
- "Next Run"
1002
- ] });
1003
- for (const cron of crons) table.push([
1004
- cron.name,
1005
- cron.state,
1006
- cron.runCount,
1007
- cron.failCount,
1008
- cron.nextRun ?? "-"
1009
- ]);
1010
- console.log(table.toString());
1011
- } catch (error) {
1012
- console.error("Failed to list crons:", error);
1013
- process.exit(1);
1014
- }
1015
- });
1016
- crons.command("get").description("Get a cron process by name or index").argument("<target>", "Cron name or index").option("-u, --url <url>", "RPC server URL", "http://localhost:3000/rpc").action(async (target, options) => {
1017
- try {
1018
- const cron = await createClient(options.url).crons.get({ target: parseTarget(target) });
1019
- const table = new Table({ head: [
1020
- "Name",
1021
- "State",
1022
- "Runs",
1023
- "Fails",
1024
- "Next Run"
1025
- ] });
1026
- table.push([
1027
- cron.name,
1028
- cron.state,
1029
- cron.runCount,
1030
- cron.failCount,
1031
- cron.nextRun ?? "-"
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
- tasks.command("remove").description("Remove 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) => {
1172
- try {
1173
- const task = await createClient(options.url).tasks.remove({ target: parseTarget(target) });
1174
- const table = new Table({ head: [
1175
- "Id",
1176
- "State",
1177
- "Processes"
1178
- ] });
1179
- table.push([
1180
- task.id,
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 { };