@actiondock/cli 2.0.9 → 2.0.11-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -13,6 +13,7 @@ import {
13
13
  type ConfigItemDefinition,
14
14
  } from "@actiondock/core";
15
15
  import { Command } from "commander";
16
+ import { ArgumentError, ExecutionError, getEffectiveOptions, renderResult } from "@actiondock/runtime-cli";
16
17
  import { resolveIntent } from "../utils/filter";
17
18
 
18
19
  export function registerConfigCommands(program: Command): void {
@@ -26,26 +27,37 @@ export function registerConfigCommands(program: Command): void {
26
27
  .alias("check")
27
28
  .description("Inspect declared configuration requirements and check resolution status")
28
29
  .option("-P, --package <id>", "Target package ID or path")
30
+ .option("--data-dir <path>", "Custom database storage directory")
29
31
  .option("--json", "Output as JSON")
30
- .action((identifier, options) => {
32
+ .option("--envelope", "Wrap JSON output in standard envelope")
33
+ .action((identifier, rawOptions, cmd) => {
31
34
  try {
32
- const root = resolvePackageRoot(identifier || options.package);
35
+ const options = getEffectiveOptions(rawOptions, cmd);
36
+ const targetPkg = identifier || options.package;
37
+ const root = resolvePackageRoot(targetPkg);
33
38
  if (!root) {
34
- console.error(
35
- "Error: Not in an ActionDock project.\nUsage: ad config schema [package-id] or cd into a project directory."
39
+ if (targetPkg) {
40
+ throw new ArgumentError(
41
+ `Package '${targetPkg}' not found in linked packages or path`
42
+ );
43
+ }
44
+ throw new ArgumentError(
45
+ "Not in an ActionDock project.\nUsage: ad config schema [package-id] or cd into a project directory."
36
46
  );
37
- process.exit(1);
38
47
  }
39
48
 
40
49
  const projConfig = loadProjectConfig(root);
41
50
  const declared = projConfig.config || {};
42
51
  const declaredKeys = Object.keys(declared);
43
52
 
44
- const globalStorage = createGlobalStorage();
53
+ const globalStorage = createGlobalStorage(options.dataDir);
45
54
  const globalConfig = globalStorage.listConfig();
46
55
  globalStorage.close();
47
56
 
48
- const projectStorage = createStorage(projConfig.id, { projectRoot: root });
57
+ const projectStorage = createStorage(projConfig.id, {
58
+ projectRoot: root,
59
+ dataDir: options.dataDir,
60
+ });
49
61
  const projectConfig = projectStorage.listConfig();
50
62
  projectStorage.close();
51
63
 
@@ -89,20 +101,15 @@ export function registerConfigCommands(program: Command): void {
89
101
  });
90
102
 
91
103
  const missingRequired = items.filter((i) => i.status === "MISSING");
92
-
93
- if (options.json) {
94
- console.log(
95
- JSON.stringify(
96
- {
97
- packageId: projConfig.id,
98
- projectRoot: root,
99
- allReady: missingRequired.length === 0,
100
- configs: items,
101
- },
102
- null,
103
- 2
104
- )
105
- );
104
+ const payload = {
105
+ packageId: projConfig.id,
106
+ projectRoot: root,
107
+ allReady: missingRequired.length === 0,
108
+ configs: items,
109
+ };
110
+
111
+ if (options.json || options.envelope) {
112
+ renderResult(payload, { json: options.json, envelope: options.envelope });
106
113
  } else {
107
114
  console.log(`Configuration Requirements for ${projConfig.id} (${root}):\n`);
108
115
  if (items.length === 0) {
@@ -133,8 +140,10 @@ export function registerConfigCommands(program: Command): void {
133
140
  }
134
141
  }
135
142
  } catch (err: any) {
136
- console.error(`Error: ${err.message}`);
137
- process.exit(1);
143
+ if (err instanceof ArgumentError || err instanceof ExecutionError) {
144
+ throw err;
145
+ }
146
+ throw new ExecutionError(err.message);
138
147
  }
139
148
  });
140
149
 
@@ -150,11 +159,16 @@ export function registerConfigCommands(program: Command): void {
150
159
  .option("-i, --intent <pattern>", "Regex or fuzzy intent filter; falls back to full list when no match")
151
160
  .option("--reveal, --show-secrets", "Reveal plain text values for secrets")
152
161
  .option("--no-fallback", "Disable fallback to full list when no items match intent")
162
+ .option("--data-dir <path>", "Custom database storage directory")
153
163
  .option("--json", "Output as JSON")
154
- .action(async (patterns, options) => {
164
+ .option("--envelope", "Wrap JSON output in standard envelope")
165
+ .action(async (patterns, rawOptions, cmd) => {
155
166
  try {
167
+ const options = getEffectiveOptions(rawOptions, cmd);
156
168
  const effectiveIntent = resolveIntent(options.intent, patterns);
157
- const shouldFallback = options.fallback !== false;
169
+ const isMachine = Boolean(options.json || options.envelope);
170
+ const fallbackExplicit = options.fallback === true || (Array.isArray(process.argv) && process.argv.includes("--fallback"));
171
+ const shouldFallback = isMachine ? fallbackExplicit : options.fallback !== false;
158
172
  const reveal = options.reveal || options.showSecrets;
159
173
 
160
174
  const target = resolveTarget({
@@ -165,8 +179,8 @@ export function registerConfigCommands(program: Command): void {
165
179
 
166
180
  if (target.type === "remote") {
167
181
  const res = await fetchRemoteConfig(target.serverUrl!, target.token, options.package);
168
- if (options.json) {
169
- console.log(JSON.stringify(res, null, 2));
182
+ if (isMachine) {
183
+ renderResult(res, { json: options.json, envelope: options.envelope });
170
184
  return;
171
185
  }
172
186
  console.log(
@@ -183,7 +197,16 @@ export function registerConfigCommands(program: Command): void {
183
197
  return;
184
198
  }
185
199
 
186
- const globalStorage = createGlobalStorage();
200
+ if (options.package && !options.global) {
201
+ const directRoot = resolvePackageRoot(options.package);
202
+ if (!directRoot) {
203
+ throw new ArgumentError(
204
+ `Package '${options.package}' not found in linked packages or path`
205
+ );
206
+ }
207
+ }
208
+
209
+ const globalStorage = createGlobalStorage(options.dataDir);
187
210
  const globalConfig = globalStorage.listConfig();
188
211
 
189
212
  const projectRoot = !options.global ? resolvePackageRoot(options.package) : null;
@@ -195,7 +218,10 @@ export function registerConfigCommands(program: Command): void {
195
218
  try {
196
219
  const projConfig = loadProjectConfig(projectRoot);
197
220
  packageId = projConfig.id;
198
- const projectStorage = createStorage(projConfig.id, { projectRoot });
221
+ const projectStorage = createStorage(projConfig.id, {
222
+ projectRoot,
223
+ dataDir: options.dataDir,
224
+ });
199
225
  projectStored = projectStorage.listConfig();
200
226
  declaredDefaults = projConfig.config || {};
201
227
  projectStorage.close();
@@ -249,8 +275,8 @@ export function registerConfigCommands(program: Command): void {
249
275
  shouldFallback
250
276
  );
251
277
 
252
- if (options.json) {
253
- console.log(JSON.stringify(filterRes.items, null, 2));
278
+ if (isMachine) {
279
+ renderResult(filterRes.items, { json: options.json, envelope: options.envelope });
254
280
  } else {
255
281
  const scopeLabel = projectRoot ? `${packageId} (${projectRoot})` : "Global Scope";
256
282
  console.log(`Configurations [${scopeLabel}]:\n`);
@@ -267,8 +293,10 @@ export function registerConfigCommands(program: Command): void {
267
293
  }
268
294
  }
269
295
  } catch (err: any) {
270
- console.error(`Error: ${err.message}`);
271
- process.exit(1);
296
+ if (err instanceof ArgumentError || err instanceof ExecutionError) {
297
+ throw err;
298
+ }
299
+ throw new ExecutionError(err.message);
272
300
  }
273
301
  });
274
302
 
@@ -282,9 +310,13 @@ export function registerConfigCommands(program: Command): void {
282
310
  .option("-s, --server <url>", "Remote server URL")
283
311
  .option("-t, --token <token>", "Auth token for remote server")
284
312
  .option("--reveal, --show-secrets", "Reveal plain text value for secret")
313
+ .option("--data-dir <path>", "Custom database storage directory")
285
314
  .option("--json", "Output as JSON")
286
- .action(async (key, options) => {
315
+ .option("--envelope", "Wrap JSON output in standard envelope")
316
+ .action(async (key, rawOptions, cmd) => {
287
317
  try {
318
+ const options = getEffectiveOptions(rawOptions, cmd);
319
+ const isMachine = Boolean(options.json || options.envelope);
288
320
  const target = resolveTarget({
289
321
  profile: options.profile,
290
322
  server: options.server,
@@ -294,16 +326,25 @@ export function registerConfigCommands(program: Command): void {
294
326
  if (target.type === "remote") {
295
327
  const res = await fetchRemoteConfig(target.serverUrl!, target.token, options.package);
296
328
  const val = res.values?.[key];
297
- if (options.json) {
298
- console.log(JSON.stringify({ key, value: val }, null, 2));
329
+ if (isMachine) {
330
+ renderResult({ key, value: val }, { json: options.json, envelope: options.envelope });
299
331
  } else {
300
332
  console.log(val !== undefined ? JSON.stringify(val) : "undefined");
301
333
  }
302
334
  return;
303
335
  }
304
336
 
337
+ if (options.package && !options.global) {
338
+ const directRoot = resolvePackageRoot(options.package);
339
+ if (!directRoot) {
340
+ throw new ArgumentError(
341
+ `Package '${options.package}' not found in linked packages or path`
342
+ );
343
+ }
344
+ }
345
+
305
346
  const reveal = options.reveal || options.showSecrets;
306
- const globalStorage = createGlobalStorage();
347
+ const globalStorage = createGlobalStorage(options.dataDir);
307
348
  const globalVal = globalStorage.getConfig(key);
308
349
  globalStorage.close();
309
350
 
@@ -316,7 +357,10 @@ export function registerConfigCommands(program: Command): void {
316
357
  try {
317
358
  const projConfig = loadProjectConfig(projectRoot);
318
359
  declaredItem = projConfig.config?.[key];
319
- const projectStorage = createStorage(projConfig.id, { projectRoot });
360
+ const projectStorage = createStorage(projConfig.id, {
361
+ projectRoot,
362
+ dataDir: options.dataDir,
363
+ });
320
364
  projVal = projectStorage.getConfig(key);
321
365
  fallbackVal = projConfig.config?.[key]?.default;
322
366
  projectStorage.close();
@@ -350,25 +394,24 @@ export function registerConfigCommands(program: Command): void {
350
394
  const isSecret = isSecretConfigKey(key, declaredItem);
351
395
  const effective = !reveal && isSecret && rawEffective !== undefined ? maskSecretValue(rawEffective) : rawEffective;
352
396
 
353
- if (options.json) {
354
- console.log(
355
- JSON.stringify(
356
- {
357
- key,
358
- value: effective,
359
- source,
360
- secret: isSecret,
361
- },
362
- null,
363
- 2
364
- )
397
+ if (isMachine) {
398
+ renderResult(
399
+ {
400
+ key,
401
+ value: effective,
402
+ source,
403
+ secret: isSecret,
404
+ },
405
+ { json: options.json, envelope: options.envelope }
365
406
  );
366
407
  } else {
367
408
  console.log(effective !== undefined ? (typeof effective === "string" && isSecret && !reveal ? effective : JSON.stringify(effective)) : "undefined");
368
409
  }
369
410
  } catch (err: any) {
370
- console.error(`Error: ${err.message}`);
371
- process.exit(1);
411
+ if (err instanceof ArgumentError || err instanceof ExecutionError) {
412
+ throw err;
413
+ }
414
+ throw new ExecutionError(err.message);
372
415
  }
373
416
  });
374
417
 
@@ -381,8 +424,13 @@ export function registerConfigCommands(program: Command): void {
381
424
  .option("-p, --profile <name>", "Set config on a remote target")
382
425
  .option("-s, --server <url>", "Remote server URL")
383
426
  .option("-t, --token <token>", "Auth token for remote server")
384
- .action(async (key, rawValue, options) => {
427
+ .option("--data-dir <path>", "Custom database storage directory")
428
+ .option("--json", "Output as JSON")
429
+ .option("--envelope", "Wrap JSON output in standard envelope")
430
+ .action(async (key, rawValue, rawOptions, cmd) => {
385
431
  try {
432
+ const options = getEffectiveOptions(rawOptions, cmd);
433
+ const isMachine = Boolean(options.json || options.envelope);
386
434
  let parsed: unknown = rawValue;
387
435
  try {
388
436
  parsed = JSON.parse(rawValue);
@@ -398,31 +446,57 @@ export function registerConfigCommands(program: Command): void {
398
446
 
399
447
  if (target.type === "remote") {
400
448
  await setRemoteConfig(target.serverUrl!, key, parsed, target.token, options.package);
401
- console.log(`[OK] Remote config '${key}' updated on ${target.serverUrl}`);
449
+ if (isMachine) {
450
+ renderResult({ ok: true, key, value: parsed }, { json: options.json, envelope: options.envelope });
451
+ } else {
452
+ console.log(`[OK] Remote config '${key}' updated on ${target.serverUrl}`);
453
+ }
402
454
  return;
403
455
  }
404
456
 
457
+ if (options.package && !options.global) {
458
+ const directRoot = resolvePackageRoot(options.package);
459
+ if (!directRoot) {
460
+ throw new ArgumentError(
461
+ `Package '${options.package}' not found in linked packages or path`
462
+ );
463
+ }
464
+ }
465
+
405
466
  const projectRoot = !options.global ? resolvePackageRoot(options.package) : null;
406
467
  const isSecret = isSecretConfigKey(key);
407
468
  const displayVal = isSecret ? maskSecretValue(parsed) : JSON.stringify(parsed);
408
469
 
409
470
  if (options.global || !projectRoot) {
410
- // Set in Global storage (~/.actiondock/global.db)
411
- const globalStorage = createGlobalStorage();
471
+ // Set in Global storage
472
+ const globalStorage = createGlobalStorage(options.dataDir);
412
473
  globalStorage.setConfig(key, parsed);
413
474
  globalStorage.close();
414
- console.log(`[OK] Global config '${key}' set to ${displayVal}`);
475
+ if (isMachine) {
476
+ renderResult({ ok: true, key, value: parsed, scope: "global" }, { json: options.json, envelope: options.envelope });
477
+ } else {
478
+ console.log(`[OK] Global config '${key}' set to ${displayVal}`);
479
+ }
415
480
  } else {
416
481
  // Set in Project storage
417
482
  const projConfig = loadProjectConfig(projectRoot);
418
- const storage = createStorage(projConfig.id, { projectRoot });
483
+ const storage = createStorage(projConfig.id, {
484
+ projectRoot,
485
+ dataDir: options.dataDir,
486
+ });
419
487
  storage.setConfig(key, parsed);
420
488
  storage.close();
421
- console.log(`[OK] Config '${key}' set to ${displayVal} in ${projConfig.id}`);
489
+ if (isMachine) {
490
+ renderResult({ ok: true, key, value: parsed, packageId: projConfig.id }, { json: options.json, envelope: options.envelope });
491
+ } else {
492
+ console.log(`[OK] Config '${key}' set to ${displayVal} in ${projConfig.id}`);
493
+ }
422
494
  }
423
495
  } catch (err: any) {
424
- console.error(`Error: ${err.message}`);
425
- process.exit(1);
496
+ if (err instanceof ArgumentError || err instanceof ExecutionError) {
497
+ throw err;
498
+ }
499
+ throw new ExecutionError(err.message);
426
500
  }
427
501
  });
428
502
 
@@ -436,8 +510,13 @@ export function registerConfigCommands(program: Command): void {
436
510
  .option("-p, --profile <name>", "Delete config on a remote target")
437
511
  .option("-s, --server <url>", "Remote server URL")
438
512
  .option("-t, --token <token>", "Auth token for remote server")
439
- .action(async (key, options) => {
513
+ .option("--data-dir <path>", "Custom database storage directory")
514
+ .option("--json", "Output as JSON")
515
+ .option("--envelope", "Wrap JSON output in standard envelope")
516
+ .action(async (key, rawOptions, cmd) => {
440
517
  try {
518
+ const options = getEffectiveOptions(rawOptions, cmd);
519
+ const isMachine = Boolean(options.json || options.envelope);
441
520
  const target = resolveTarget({
442
521
  profile: options.profile,
443
522
  server: options.server,
@@ -446,7 +525,9 @@ export function registerConfigCommands(program: Command): void {
446
525
 
447
526
  if (target.type === "remote") {
448
527
  const res = await deleteRemoteConfig(target.serverUrl!, key, target.token, options.package);
449
- if (res.deleted) {
528
+ if (isMachine) {
529
+ renderResult(res, { json: options.json, envelope: options.envelope });
530
+ } else if (res.deleted) {
450
531
  console.log(`[OK] Remote config '${key}' deleted from ${target.serverUrl}`);
451
532
  } else {
452
533
  console.log(`Remote config '${key}' not found on ${target.serverUrl}`);
@@ -454,31 +535,49 @@ export function registerConfigCommands(program: Command): void {
454
535
  return;
455
536
  }
456
537
 
538
+ if (options.package && !options.global) {
539
+ const directRoot = resolvePackageRoot(options.package);
540
+ if (!directRoot) {
541
+ throw new ArgumentError(
542
+ `Package '${options.package}' not found in linked packages or path`
543
+ );
544
+ }
545
+ }
546
+
457
547
  const projectRoot = !options.global ? resolvePackageRoot(options.package) : null;
458
548
 
459
549
  if (options.global || !projectRoot) {
460
- const globalStorage = createGlobalStorage();
550
+ const globalStorage = createGlobalStorage(options.dataDir);
461
551
  const deleted = globalStorage.deleteConfig(key);
462
552
  globalStorage.close();
463
- if (deleted) {
553
+ if (isMachine) {
554
+ renderResult({ ok: true, key, deleted, scope: "global" }, { json: options.json, envelope: options.envelope });
555
+ } else if (deleted) {
464
556
  console.log(`[OK] Global config '${key}' deleted`);
465
557
  } else {
466
558
  console.log(`Global config '${key}' was not found`);
467
559
  }
468
560
  } else {
469
561
  const projConfig = loadProjectConfig(projectRoot);
470
- const storage = createStorage(projConfig.id, { projectRoot });
562
+ const storage = createStorage(projConfig.id, {
563
+ projectRoot,
564
+ dataDir: options.dataDir,
565
+ });
471
566
  const deleted = storage.deleteConfig(key);
472
567
  storage.close();
473
- if (deleted) {
568
+ if (isMachine) {
569
+ renderResult({ ok: true, key, deleted, packageId: projConfig.id }, { json: options.json, envelope: options.envelope });
570
+ } else if (deleted) {
474
571
  console.log(`[OK] Config '${key}' deleted from ${projConfig.id}`);
475
572
  } else {
476
573
  console.log(`Config '${key}' was not set in database for ${projConfig.id}`);
477
574
  }
478
575
  }
479
576
  } catch (err: any) {
480
- console.error(`Error: ${err.message}`);
481
- process.exit(1);
577
+ if (err instanceof ArgumentError || err instanceof ExecutionError) {
578
+ throw err;
579
+ }
580
+ throw new ExecutionError(err.message);
482
581
  }
483
582
  });
484
583
  }
@@ -1,4 +1,5 @@
1
1
  import { fetchRemoteDoctor, resolveTarget, runDoctorChecks } from "@actiondock/core";
2
+ import { ExecutionError, getEffectiveOptions, renderResult } from "@actiondock/runtime-cli";
2
3
  import { Command } from "commander";
3
4
 
4
5
  export function registerDoctorCommand(program: Command): void {
@@ -10,8 +11,10 @@ export function registerDoctorCommand(program: Command): void {
10
11
  .option("-s, --server <url>", "Remote server URL")
11
12
  .option("-t, --token <token>", "Auth token for remote server")
12
13
  .option("--json", "Output diagnostics report in JSON format")
13
- .action(async (options) => {
14
+ .option("--envelope", "Wrap JSON output in standard envelope")
15
+ .action(async (rawOptions, cmd) => {
14
16
  try {
17
+ const options = getEffectiveOptions(rawOptions, cmd);
15
18
  const target = resolveTarget({
16
19
  profile: options.profile,
17
20
  server: options.server,
@@ -28,55 +31,54 @@ export function registerDoctorCommand(program: Command): void {
28
31
  });
29
32
  }
30
33
 
31
- if (options.json) {
32
- console.log(JSON.stringify(report, null, 2));
33
- if (!report.ok) {
34
- process.exit(1);
35
- }
36
- return;
37
- }
38
-
39
- const title = target.type === "remote"
40
- ? `[DOCTOR] Remote ActionDock Server Diagnostics (${target.serverUrl}${target.profileName ? ` - Profile: ${target.profileName}` : ""})\n`
41
- : "[DOCTOR] ActionDock System & Project Diagnostics\n";
42
- console.log(title);
34
+ renderResult(report, {
35
+ json: options.json,
36
+ envelope: options.envelope,
37
+ humanFormatter: () => {
38
+ const lines: string[] = [];
39
+ const title = target.type === "remote"
40
+ ? `[DOCTOR] Remote ActionDock Server Diagnostics (${target.serverUrl}${target.profileName ? ` - Profile: ${target.profileName}` : ""})\n`
41
+ : "[DOCTOR] ActionDock System & Project Diagnostics\n";
42
+ lines.push(title);
43
43
 
44
- // 1. Runtime & Environment Group
45
- console.log("[Runtime & Environment]");
46
- const envChecks = (report.checks || []).filter((c: any) => c.category === "runtime" || c.category === "storage" || c.category === "registry");
47
- for (const c of envChecks) {
48
- const tag = c.status === "ok" ? "[OK]" : c.status === "warn" ? "[WARN]" : "[ERROR]";
49
- console.log(` ${tag} ${c.name}: ${c.message}`);
50
- if (c.fix) {
51
- console.log(` Fix: ${c.fix}`);
52
- }
53
- }
44
+ // 1. Runtime & Environment Group
45
+ lines.push("[Runtime & Environment]");
46
+ const envChecks = (report.checks || []).filter((c: any) => c.category === "runtime" || c.category === "storage" || c.category === "registry");
47
+ for (const c of envChecks) {
48
+ const tag = c.status === "ok" ? "[OK]" : c.status === "warn" ? "[WARN]" : "[ERROR]";
49
+ lines.push(` ${tag} ${c.name}: ${c.message}`);
50
+ if (c.fix) {
51
+ lines.push(` Fix: ${c.fix}`);
52
+ }
53
+ }
54
54
 
55
- // 2. Project Group
56
- if (report.hasProject) {
57
- console.log(`\n[Project: ${report.packageId || "unknown"}] (${report.projectRoot})`);
58
- const projChecks = (report.checks || []).filter((c: any) => c.category === "project");
59
- for (const c of projChecks) {
60
- const tag = c.status === "ok" ? "[OK]" : c.status === "warn" ? "[WARN]" : "[ERROR]";
61
- console.log(` ${tag} ${c.name}: ${c.message}`);
62
- if (c.fix) {
63
- console.log(` Fix: ${c.fix}`);
55
+ // 2. Project Group
56
+ if (report.hasProject) {
57
+ lines.push(`\n[Project: ${report.packageId || "unknown"}] (${report.projectRoot})`);
58
+ const projChecks = (report.checks || []).filter((c: any) => c.category === "project");
59
+ for (const c of projChecks) {
60
+ const tag = c.status === "ok" ? "[OK]" : c.status === "warn" ? "[WARN]" : "[ERROR]";
61
+ lines.push(` ${tag} ${c.name}: ${c.message}`);
62
+ if (c.fix) {
63
+ lines.push(` Fix: ${c.fix}`);
64
+ }
65
+ }
66
+ } else {
67
+ lines.push("\n[Project Context]");
68
+ lines.push(" [INFO] Not inside an ActionDock project directory (skipped project checks)");
64
69
  }
65
- }
66
- } else {
67
- console.log("\n[Project Context]");
68
- console.log(" [INFO] Not inside an ActionDock project directory (skipped project checks)");
69
- }
70
70
 
71
- // 3. Summary
72
- console.log(`\n[Summary] ${report.summary.ok} passed, ${report.summary.warn} warning(s), ${report.summary.error} error(s)`);
71
+ // 3. Summary
72
+ lines.push(`\n[Summary] ${report.summary.ok} passed, ${report.summary.warn} warning(s), ${report.summary.error} error(s)`);
73
+ return lines.join("\n");
74
+ },
75
+ });
73
76
 
74
77
  if (!report.ok) {
75
- process.exit(1);
78
+ process.exitCode = 1;
76
79
  }
77
80
  } catch (err: any) {
78
- console.error(`[ERROR] Doctor failed to run diagnostics: ${err.message}`);
79
- process.exit(1);
81
+ throw new ExecutionError(`Doctor failed to run diagnostics: ${err.message}`);
80
82
  }
81
83
  });
82
84
  }