pid1 0.0.0-dev.1 → 0.0.0-dev.3

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.
Files changed (42) hide show
  1. package/README.md +45 -0
  2. package/dist/cli.mjs +1147 -99
  3. package/dist/cli.mjs.map +1 -1
  4. package/dist/client.d.mts +169 -2
  5. package/dist/client.d.mts.map +1 -0
  6. package/dist/client.mjs +6 -3
  7. package/dist/client.mjs.map +1 -1
  8. package/dist/index.d.mts +226 -32
  9. package/dist/index.d.mts.map +1 -1
  10. package/dist/index.mjs +9 -4
  11. package/dist/index.mjs.map +1 -0
  12. package/dist/logger-BN9KoHBY.mjs +926 -0
  13. package/dist/logger-BN9KoHBY.mjs.map +1 -0
  14. package/dist/task-list-zO8UZG5r.d.mts +231 -0
  15. package/dist/task-list-zO8UZG5r.d.mts.map +1 -0
  16. package/package.json +12 -15
  17. package/src/api/client.ts +9 -14
  18. package/src/api/contract.ts +117 -0
  19. package/src/api/server.ts +171 -55
  20. package/src/cli.ts +444 -146
  21. package/src/cron-process.ts +255 -0
  22. package/src/env-manager.ts +237 -0
  23. package/src/index.ts +12 -14
  24. package/src/lazy-process.ts +198 -0
  25. package/src/logger.ts +90 -137
  26. package/src/manager.ts +859 -0
  27. package/src/restarting-process.ts +397 -0
  28. package/src/task-list.ts +236 -0
  29. package/dist/client-B02fC2Sv.d.mts +0 -216
  30. package/dist/client-B02fC2Sv.d.mts.map +0 -1
  31. package/dist/config-BgRb4pSG.mjs +0 -186
  32. package/dist/config-BgRb4pSG.mjs.map +0 -1
  33. package/dist/config.d.mts +0 -84
  34. package/dist/config.d.mts.map +0 -1
  35. package/dist/config.mjs +0 -3
  36. package/dist/process-manager-Ddk5dALx.mjs +0 -637
  37. package/dist/process-manager-Ddk5dALx.mjs.map +0 -1
  38. package/src/api/router.ts +0 -146
  39. package/src/config.ts +0 -84
  40. package/src/env.ts +0 -65
  41. package/src/exec.ts +0 -78
  42. package/src/process-manager.ts +0 -601
package/src/cli.ts CHANGED
@@ -1,164 +1,462 @@
1
1
  #!/usr/bin/env node
2
- import Table from "cli-table3";
3
2
  import { Command } from "commander";
4
- import { ProcessManager } from "./process-manager.ts";
3
+ import { createServer } from "node:http";
4
+ import { pathToFileURL } from "node:url";
5
+ import { resolve } from "node:path";
6
+ import { RPCHandler } from "@orpc/server/node";
7
+ import { onError } from "@orpc/server";
8
+ import * as v from "valibot";
9
+ import { router } from "./api/server.ts";
10
+ import { Manager, ManagerConfigSchema } from "./manager.ts";
11
+ import { logger } from "./logger.ts";
12
+ import { tsImport } from "tsx/esm/api";
13
+ import Table from "cli-table3";
5
14
  import { createClient } from "./api/client.ts";
6
- import { globalLogger } from "./logger.ts";
15
+ import { ProcessDefinitionSchema } from "./api/contract.ts";
7
16
 
8
- const logger = globalLogger.child("cli");
9
- const defaultPort = Number(process.env.PID1_PORT ?? process.env.PORT ?? 3000);
10
- const envHost = process.env.PID1_HOST ?? process.env.HOST;
17
+ const program = new Command();
11
18
 
12
- function formatTable(rows: Array<Record<string, unknown>>): void {
13
- if (rows.length === 0) {
14
- console.log("No processes registered");
15
- return;
16
- }
19
+ program
20
+ .name("pid1")
21
+ .description("Process manager with init system capabilities")
22
+ .version("0.0.0-dev.1");
23
+
24
+ program
25
+ .command("init")
26
+ .description("Initialize and run the process manager with config file")
27
+ .option("-c, --config <path>", "Path to config file", "pid1.config.ts")
28
+ .action(async (options) => {
29
+ const initLogger = logger({ name: "pid1" });
30
+ try {
31
+ // Resolve config file path
32
+ const configPath = resolve(process.cwd(), options.config);
33
+ const configUrl = pathToFileURL(configPath).href;
34
+
35
+ // Import the config file
36
+ const configModule = await tsImport(configUrl, { parentURL: import.meta.url });
37
+ const rawConfig =
38
+ configModule.default.default || configModule.default || configModule.config || {};
39
+
40
+ // Parse and validate the config with Valibot
41
+ const config = v.parse(ManagerConfigSchema, rawConfig);
42
+
43
+ // Extract HTTP config with defaults
44
+ const host = config.http?.host ?? "localhost";
45
+ const port = config.http?.port ?? 3000;
46
+ const authToken = config.http?.authToken;
47
+
48
+ // Create manager with config
49
+ const logDir = config.logDir ?? resolve(process.cwd(), "logs");
50
+ const managerLogger = logger({ name: "pid1", logFile: resolve(logDir, "pid1.log") });
51
+ const manager = new Manager(config, managerLogger);
52
+
53
+ // Setup ORPC server with optional auth token middleware
54
+ const handler = new RPCHandler(router, {
55
+ interceptors: [
56
+ onError((error) => {
57
+ managerLogger.error(error);
58
+ }),
59
+ ],
60
+ });
17
61
 
18
- const table = new Table({
19
- head: ["ID", "NAME", "STATUS", "PID", "RESTARTS", "STARTED"],
20
- style: { head: [], border: [] },
62
+ const server = createServer(async (req, res) => {
63
+ // Check auth token if configured
64
+ if (authToken) {
65
+ const providedToken = req.headers["authorization"]?.replace("Bearer ", "");
66
+ if (providedToken !== authToken) {
67
+ res.statusCode = 401;
68
+ res.end("Unauthorized");
69
+ return;
70
+ }
71
+ }
72
+
73
+ const { matched } = await handler.handle(req, res, {
74
+ prefix: "/rpc",
75
+ context: { manager },
76
+ });
77
+ if (matched) return;
78
+ res.statusCode = 404;
79
+ res.end("Not found");
80
+ });
81
+
82
+ server.listen(port, host, async () => {
83
+ managerLogger.info(`pid1 RPC server running on http://${host}:${port}`);
84
+ if (authToken) {
85
+ managerLogger.info("Auth token required for API access");
86
+ }
87
+
88
+ try {
89
+ await manager.start();
90
+ } catch (err) {
91
+ managerLogger.error("Failed to start manager:", err);
92
+ server.close();
93
+ process.exit(1);
94
+ }
95
+ });
96
+
97
+ // Wait for shutdown
98
+ await manager.waitForShutdown();
99
+
100
+ // Close server on shutdown
101
+ server.close();
102
+ } catch (error) {
103
+ initLogger.error("Failed to start pid1:", error);
104
+ process.exit(1);
105
+ }
21
106
  });
22
107
 
23
- for (const row of rows) {
24
- table.push([
25
- String(row.id),
26
- String(row.name),
27
- String(row.status),
28
- row.pid ? String(row.pid) : "-",
29
- String(row.restarts),
30
- row.startedAt ? new Date(row.startedAt as string).toLocaleString() : "-",
31
- ]);
32
- }
108
+ program
109
+ .command("status")
110
+ .description("Show manager status")
111
+ .option("-u, --url <url>", "RPC server URL", "http://localhost:3000/rpc")
112
+ .action(async (options) => {
113
+ try {
114
+ const client = createClient(options.url);
115
+ const status = await client.manager.status();
33
116
 
34
- console.log(table.toString());
35
- }
117
+ const table = new Table({ head: ["State", "Processes", "Crons", "Tasks"] });
118
+ table.push([status.state, status.processCount, status.cronCount, status.taskCount]);
119
+ console.log(table.toString());
120
+ } catch (error) {
121
+ console.error("Failed to fetch status:", error);
122
+ process.exit(1);
123
+ }
124
+ });
36
125
 
37
- type CommonArgs = {
38
- port?: number;
39
- host?: string;
40
- };
41
- type InitArgs = CommonArgs & {
42
- cwd?: string;
43
- config?: string;
44
- };
45
-
46
- async function runInit(argv: InitArgs) {
47
- const host = argv.host ?? envHost ?? "0.0.0.0";
48
- const pm = new ProcessManager({
49
- cwd: argv.cwd,
50
- configPath: argv.config,
51
- server: { port: argv.port ?? defaultPort, host },
52
- });
53
- pm.serve();
54
- await pm.init();
55
- pm.startAll();
56
- }
126
+ const processes = program.command("processes").description("Manage restarting processes");
57
127
 
58
- function createRpcClient(argv: CommonArgs) {
59
- const host = argv.host ?? envHost ?? "127.0.0.1";
60
- const port = argv.port ?? defaultPort;
61
- return createClient({ url: `http://${host}:${port}/rpc` });
62
- }
128
+ processes
129
+ .command("list")
130
+ .description("List restarting processes")
131
+ .option("-u, --url <url>", "RPC server URL", "http://localhost:3000/rpc")
132
+ .action(async (options) => {
133
+ try {
134
+ const client = createClient(options.url);
135
+ const processes = await client.processes.list();
63
136
 
64
- async function main() {
65
- const program = new Command();
66
- program
67
- .name("pid1")
68
- .description("Process manager daemon and controller")
69
- .option("--port <number>", "Port for the HTTP server", (value) => Number(value))
70
- .option("--host <host>", "Host for the HTTP server");
71
-
72
- const commonOptions = (): CommonArgs => {
73
- const opts = program.opts<CommonArgs>();
74
- return {
75
- port: opts.port,
76
- host: opts.host,
77
- };
78
- };
79
-
80
- program
81
- .command("init")
82
- .description("Start the daemon, run tasks, and start all processes")
83
- .option("--cwd <path>", "Working directory for config lookup")
84
- .option("--config <path>", "Path to config file")
85
- .action(async (options: InitArgs) => {
86
- await runInit({ ...commonOptions(), ...options });
87
- });
88
-
89
- program
90
- .command("start [name]")
91
- .description("Start all processes, or a specific process by name or id")
92
- .action(async (name?: string) => {
93
- const client = createRpcClient(commonOptions());
94
- const result = await client.process.start({ name });
95
- console.log(result.message);
96
- });
97
-
98
- program
99
- .command("stop [name]")
100
- .description("Stop all processes, or a specific process by name or id")
101
- .action(async (name?: string) => {
102
- const client = createRpcClient(commonOptions());
103
- const result = await client.process.stop({ name });
104
- console.log(result.message);
105
- });
106
-
107
- program
108
- .command("restart [name]")
109
- .description("Restart all processes, or a specific process by name or id")
110
- .action(async (name?: string) => {
111
- const client = createRpcClient(commonOptions());
112
- const result = await client.process.restart({ name });
113
- console.log(result.message);
114
- });
115
-
116
- program
117
- .command("delete [name]")
118
- .description("Delete all processes, or a specific process by name or id")
119
- .action(async (name?: string) => {
120
- const client = createRpcClient(commonOptions());
121
- const result = await client.process.delete({ name });
122
- console.log(result.message);
123
- });
124
-
125
- program
126
- .command("list")
127
- .description("List all managed processes")
128
- .action(async () => {
129
- const client = createRpcClient(commonOptions());
130
- const processes = await client.process.list({});
131
- formatTable(processes);
132
- });
133
-
134
- program
135
- .command("get <name>")
136
- .description("Get a process by name or id")
137
- .action(async (name: string) => {
138
- const client = createRpcClient(commonOptions());
139
- const process = await client.process.get({ name });
140
- if (!process) {
141
- console.log(`Process not found: ${name}`);
142
- return;
137
+ const table = new Table({ head: ["Name", "State", "Restarts"] });
138
+ for (const proc of processes) {
139
+ table.push([proc.name, proc.state, proc.restarts]);
143
140
  }
144
- formatTable([process]);
145
- });
141
+ console.log(table.toString());
142
+ } catch (error) {
143
+ console.error("Failed to list processes:", error);
144
+ process.exit(1);
145
+ }
146
+ });
146
147
 
147
- if (process.argv.length <= 2) {
148
- program.outputHelp();
149
- return;
150
- }
148
+ processes
149
+ .command("get")
150
+ .description("Get a restarting process by name or index")
151
+ .argument("<target>", "Process name or index")
152
+ .option("-u, --url <url>", "RPC server URL", "http://localhost:3000/rpc")
153
+ .action(async (target, options) => {
154
+ try {
155
+ const client = createClient(options.url);
156
+ const proc = await client.processes.get({ target: parseTarget(target) });
157
+
158
+ const table = new Table({ head: ["Name", "State", "Restarts"] });
159
+ table.push([proc.name, proc.state, proc.restarts]);
160
+ console.log(table.toString());
161
+ } catch (error) {
162
+ console.error("Failed to get process:", error);
163
+ process.exit(1);
164
+ }
165
+ });
166
+
167
+ processes
168
+ .command("add")
169
+ .description("Add a restarting process")
170
+ .requiredOption("-n, --name <name>", "Process name")
171
+ .requiredOption("-d, --definition <json>", "Process definition JSON")
172
+ .option("-u, --url <url>", "RPC server URL", "http://localhost:3000/rpc")
173
+ .action(async (options) => {
174
+ try {
175
+ const client = createClient(options.url);
176
+ const definition = parseDefinition(options.definition);
177
+ const proc = await client.processes.add({ name: options.name, definition });
178
+
179
+ const table = new Table({ head: ["Name", "State", "Restarts"] });
180
+ table.push([proc.name, proc.state, proc.restarts]);
181
+ console.log(table.toString());
182
+ } catch (error) {
183
+ console.error("Failed to add process:", error);
184
+ process.exit(1);
185
+ }
186
+ });
187
+
188
+ processes
189
+ .command("start")
190
+ .description("Start a restarting process")
191
+ .argument("<target>", "Process name or index")
192
+ .option("-u, --url <url>", "RPC server URL", "http://localhost:3000/rpc")
193
+ .action(async (target, options) => {
194
+ try {
195
+ const client = createClient(options.url);
196
+ const proc = await client.processes.start({ target: parseTarget(target) });
151
197
 
152
- await program.parseAsync(process.argv);
198
+ const table = new Table({ head: ["Name", "State", "Restarts"] });
199
+ table.push([proc.name, proc.state, proc.restarts]);
200
+ console.log(table.toString());
201
+ } catch (error) {
202
+ console.error("Failed to start process:", error);
203
+ process.exit(1);
204
+ }
205
+ });
206
+
207
+ processes
208
+ .command("stop")
209
+ .description("Stop a restarting process")
210
+ .argument("<target>", "Process name or index")
211
+ .option("-u, --url <url>", "RPC server URL", "http://localhost:3000/rpc")
212
+ .action(async (target, options) => {
213
+ try {
214
+ const client = createClient(options.url);
215
+ const proc = await client.processes.stop({ target: parseTarget(target) });
216
+
217
+ const table = new Table({ head: ["Name", "State", "Restarts"] });
218
+ table.push([proc.name, proc.state, proc.restarts]);
219
+ console.log(table.toString());
220
+ } catch (error) {
221
+ console.error("Failed to stop process:", error);
222
+ process.exit(1);
223
+ }
224
+ });
225
+
226
+ processes
227
+ .command("restart")
228
+ .description("Restart a restarting process")
229
+ .argument("<target>", "Process name or index")
230
+ .option("-f, --force", "Force restart")
231
+ .option("-u, --url <url>", "RPC server URL", "http://localhost:3000/rpc")
232
+ .action(async (target, options) => {
233
+ try {
234
+ const client = createClient(options.url);
235
+ const proc = await client.processes.restart({
236
+ target: parseTarget(target),
237
+ force: options.force,
238
+ });
239
+
240
+ const table = new Table({ head: ["Name", "State", "Restarts"] });
241
+ table.push([proc.name, proc.state, proc.restarts]);
242
+ console.log(table.toString());
243
+ } catch (error) {
244
+ console.error("Failed to restart process:", error);
245
+ process.exit(1);
246
+ }
247
+ });
248
+
249
+ processes
250
+ .command("remove")
251
+ .description("Remove a restarting process")
252
+ .argument("<target>", "Process name or index")
253
+ .option("-u, --url <url>", "RPC server URL", "http://localhost:3000/rpc")
254
+ .action(async (target, options) => {
255
+ try {
256
+ const client = createClient(options.url);
257
+ await client.processes.remove({ target: parseTarget(target) });
258
+ console.log("Process removed");
259
+ } catch (error) {
260
+ console.error("Failed to remove process:", error);
261
+ process.exit(1);
262
+ }
263
+ });
264
+
265
+ const crons = program.command("crons").description("Manage cron processes");
266
+
267
+ crons
268
+ .command("list")
269
+ .description("List cron processes")
270
+ .option("-u, --url <url>", "RPC server URL", "http://localhost:3000/rpc")
271
+ .action(async (options) => {
272
+ try {
273
+ const client = createClient(options.url);
274
+ const crons = await client.crons.list();
275
+
276
+ const table = new Table({ head: ["Name", "State", "Runs", "Fails", "Next Run"] });
277
+ for (const cron of crons) {
278
+ table.push([cron.name, cron.state, cron.runCount, cron.failCount, cron.nextRun ?? "-"]);
279
+ }
280
+ console.log(table.toString());
281
+ } catch (error) {
282
+ console.error("Failed to list crons:", error);
283
+ process.exit(1);
284
+ }
285
+ });
286
+
287
+ crons
288
+ .command("get")
289
+ .description("Get a cron process by name or index")
290
+ .argument("<target>", "Cron name or index")
291
+ .option("-u, --url <url>", "RPC server URL", "http://localhost:3000/rpc")
292
+ .action(async (target, options) => {
293
+ try {
294
+ const client = createClient(options.url);
295
+ const cron = await client.crons.get({ target: parseTarget(target) });
296
+
297
+ const table = new Table({ head: ["Name", "State", "Runs", "Fails", "Next Run"] });
298
+ table.push([cron.name, cron.state, cron.runCount, cron.failCount, cron.nextRun ?? "-"]);
299
+ console.log(table.toString());
300
+ } catch (error) {
301
+ console.error("Failed to get cron:", error);
302
+ process.exit(1);
303
+ }
304
+ });
305
+
306
+ crons
307
+ .command("trigger")
308
+ .description("Trigger a cron process")
309
+ .argument("<target>", "Cron name or index")
310
+ .option("-u, --url <url>", "RPC server URL", "http://localhost:3000/rpc")
311
+ .action(async (target, options) => {
312
+ try {
313
+ const client = createClient(options.url);
314
+ const cron = await client.crons.trigger({ target: parseTarget(target) });
315
+
316
+ const table = new Table({ head: ["Name", "State", "Runs", "Fails", "Next Run"] });
317
+ table.push([cron.name, cron.state, cron.runCount, cron.failCount, cron.nextRun ?? "-"]);
318
+ console.log(table.toString());
319
+ } catch (error) {
320
+ console.error("Failed to trigger cron:", error);
321
+ process.exit(1);
322
+ }
323
+ });
324
+
325
+ crons
326
+ .command("start")
327
+ .description("Start a cron process")
328
+ .argument("<target>", "Cron name or index")
329
+ .option("-u, --url <url>", "RPC server URL", "http://localhost:3000/rpc")
330
+ .action(async (target, options) => {
331
+ try {
332
+ const client = createClient(options.url);
333
+ const cron = await client.crons.start({ target: parseTarget(target) });
334
+
335
+ const table = new Table({ head: ["Name", "State", "Runs", "Fails", "Next Run"] });
336
+ table.push([cron.name, cron.state, cron.runCount, cron.failCount, cron.nextRun ?? "-"]);
337
+ console.log(table.toString());
338
+ } catch (error) {
339
+ console.error("Failed to start cron:", error);
340
+ process.exit(1);
341
+ }
342
+ });
343
+
344
+ crons
345
+ .command("stop")
346
+ .description("Stop a cron process")
347
+ .argument("<target>", "Cron name or index")
348
+ .option("-u, --url <url>", "RPC server URL", "http://localhost:3000/rpc")
349
+ .action(async (target, options) => {
350
+ try {
351
+ const client = createClient(options.url);
352
+ const cron = await client.crons.stop({ target: parseTarget(target) });
353
+
354
+ const table = new Table({ head: ["Name", "State", "Runs", "Fails", "Next Run"] });
355
+ table.push([cron.name, cron.state, cron.runCount, cron.failCount, cron.nextRun ?? "-"]);
356
+ console.log(table.toString());
357
+ } catch (error) {
358
+ console.error("Failed to stop cron:", error);
359
+ process.exit(1);
360
+ }
361
+ });
362
+
363
+ const tasks = program.command("tasks").description("Manage tasks");
364
+
365
+ tasks
366
+ .command("list")
367
+ .description("List tasks")
368
+ .option("-u, --url <url>", "RPC server URL", "http://localhost:3000/rpc")
369
+ .action(async (options) => {
370
+ try {
371
+ const client = createClient(options.url);
372
+ const tasks = await client.tasks.list();
373
+
374
+ const table = new Table({ head: ["Id", "State", "Processes"] });
375
+ for (const task of tasks) {
376
+ table.push([task.id, task.state, task.processNames.join(", ")]);
377
+ }
378
+ console.log(table.toString());
379
+ } catch (error) {
380
+ console.error("Failed to list tasks:", error);
381
+ process.exit(1);
382
+ }
383
+ });
384
+
385
+ tasks
386
+ .command("get")
387
+ .description("Get a task by id or index")
388
+ .argument("<target>", "Task id or index")
389
+ .option("-u, --url <url>", "RPC server URL", "http://localhost:3000/rpc")
390
+ .action(async (target, options) => {
391
+ try {
392
+ const client = createClient(options.url);
393
+ const task = await client.tasks.get({ target: parseTarget(target) });
394
+
395
+ const table = new Table({ head: ["Id", "State", "Processes"] });
396
+ table.push([task.id, task.state, task.processNames.join(", ")]);
397
+ console.log(table.toString());
398
+ } catch (error) {
399
+ console.error("Failed to get task:", error);
400
+ process.exit(1);
401
+ }
402
+ });
403
+
404
+ tasks
405
+ .command("add")
406
+ .description("Add a task")
407
+ .requiredOption("-n, --name <name>", "Task name")
408
+ .requiredOption("-d, --definition <json>", "Process definition JSON")
409
+ .option("-u, --url <url>", "RPC server URL", "http://localhost:3000/rpc")
410
+ .action(async (options) => {
411
+ try {
412
+ const client = createClient(options.url);
413
+ const definition = parseDefinition(options.definition);
414
+ const task = await client.tasks.add({ name: options.name, definition });
415
+
416
+ const table = new Table({ head: ["Id", "State", "Processes"] });
417
+ table.push([task.id, task.state, task.processNames.join(", ")]);
418
+ console.log(table.toString());
419
+ } catch (error) {
420
+ console.error("Failed to add task:", error);
421
+ process.exit(1);
422
+ }
423
+ });
424
+
425
+ tasks
426
+ .command("remove")
427
+ .description("Remove a task by id or index")
428
+ .argument("<target>", "Task id or index")
429
+ .option("-u, --url <url>", "RPC server URL", "http://localhost:3000/rpc")
430
+ .action(async (target, options) => {
431
+ try {
432
+ const client = createClient(options.url);
433
+ const task = await client.tasks.remove({ target: parseTarget(target) });
434
+
435
+ const table = new Table({ head: ["Id", "State", "Processes"] });
436
+ table.push([task.id, task.state, task.processNames.join(", ")]);
437
+ console.log(table.toString());
438
+ } catch (error) {
439
+ console.error("Failed to remove task:", error);
440
+ process.exit(1);
441
+ }
442
+ });
443
+
444
+ const TargetSchema = v.union([v.string(), v.number()]);
445
+ type ProcessDefinition = v.InferOutput<typeof ProcessDefinitionSchema>;
446
+
447
+ function parseTarget(value: string): string | number {
448
+ const asNumber = Number(value);
449
+ return v.parse(TargetSchema, Number.isNaN(asNumber) ? value : asNumber);
153
450
  }
154
451
 
155
- main().catch((error) => {
156
- if (error.cause?.code === "ECONNREFUSED") {
157
- console.error(
158
- "Error: Could not connect to pid1 daemon. Is it running? Start it with: pid1 init",
159
- );
160
- process.exit(1);
452
+ function parseDefinition(raw: string): ProcessDefinition {
453
+ try {
454
+ const parsed = JSON.parse(raw);
455
+ return v.parse(ProcessDefinitionSchema, parsed);
456
+ } catch (error) {
457
+ console.error("Invalid --definition JSON. Expected a ProcessDefinition.");
458
+ throw error;
161
459
  }
162
- logger.error(error instanceof Error ? (error.stack ?? error.message) : String(error));
163
- process.exit(1);
164
- });
460
+ }
461
+
462
+ program.parse();