@cardstack/boxel-cli 0.4.0-unstable.0 → 0.4.0-unstable.1

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 (101) hide show
  1. package/bundled-realms/base/card-api.gts +2 -0
  2. package/bundled-realms/base/components/cards-grid-layout.gts +24 -2
  3. package/bundled-realms/base/field-component.gts +16 -4
  4. package/bundled-realms/base/links-to-editor.gts +27 -3
  5. package/bundled-realms/base/realm-config.gts +18 -1
  6. package/bundled-test-harness/assets/{add-workspace-tMjv_Ar4.js → add-workspace-DBDTvtP2.js} +3 -3
  7. package/bundled-test-harness/assets/{add-workspace-test-B_ih3mkL.js → add-workspace-test-CE21v7jK.js} +1 -1
  8. package/bundled-test-harness/assets/{ai-assistant-test-CrQxlWpZ.js → ai-assistant-test-DSyMR0hY.js} +1 -1
  9. package/bundled-test-harness/assets/{ai-module-creation-test-CMLVMpYm.js → ai-module-creation-test-zY83EN93.js} +1 -1
  10. package/bundled-test-harness/assets/{aibot-message-CiwsV-h8.js → aibot-message-BfRah4Lm.js} +2 -2
  11. package/bundled-test-harness/assets/{app-BQLXfOdp.js → app-CPRVydcd.js} +14 -14
  12. package/bundled-test-harness/assets/{binary-upload-dedupe-test-B7dPaGQN.js → binary-upload-dedupe-test-nnaYqiTh.js} +1 -1
  13. package/bundled-test-harness/assets/{broken-link-template-test-BAc48VK4.js → broken-link-template-test-BHXrPxQ6.js} +1 -1
  14. package/bundled-test-harness/assets/{card-api-test-BmiwtCFR.js → card-api-test-C0HrvAuC.js} +1 -1
  15. package/bundled-test-harness/assets/{card-basics-test-DZs6Zh6Z.js → card-basics-test-CvITmKK-.js} +1 -1
  16. package/bundled-test-harness/assets/{card-catalog-test-D2mEHOpL.js → card-catalog-test-CWkfBRuQ.js} +1 -1
  17. package/bundled-test-harness/assets/{card-copy-test-DpSavuvN.js → card-copy-test-Co_MPCIJ.js} +1 -1
  18. package/bundled-test-harness/assets/{card-def-field-def-relationships-test-DYX_ubhX.js → card-def-field-def-relationships-test-7e-g4qJs.js} +1 -1
  19. package/bundled-test-harness/assets/{card-delete-test-Bmp8Ue48.js → card-delete-test-BcOtJYm4.js} +1 -1
  20. package/bundled-test-harness/assets/{card-error-detail-DuFoktdn.js → card-error-detail-Q7wtuRcv.js} +2 -2
  21. package/bundled-test-harness/assets/{card-search-adorn-test-CEA5iAcD.js → card-search-adorn-test-CbsnFvDF.js} +1 -1
  22. package/bundled-test-harness/assets/{card-search-selection-menu-test-DOLM6HU0.js → card-search-selection-menu-test-CY77b1kz.js} +1 -1
  23. package/bundled-test-harness/assets/{check-correctness-test-DR1rUhtU.js → check-correctness-test-D0EYMlP7.js} +1 -1
  24. package/bundled-test-harness/assets/{codeblocks-test-B-i0v3er.js → codeblocks-test-CFR0u5-j.js} +1 -1
  25. package/bundled-test-harness/assets/{commands-DoigkJc8.js → commands-BOwQjc8N.js} +3 -3
  26. package/bundled-test-harness/assets/{commands-test-D1X-Kikc.js → commands-test-BpFjXsiC.js} +1 -1
  27. package/bundled-test-harness/assets/{commands-test-C13_nhjT.js → commands-test-QSW7zRvO.js} +1 -1
  28. package/bundled-test-harness/assets/{components-CBfd9MIo.js → components-DYC4nC4r.js} +184 -184
  29. package/bundled-test-harness/assets/{components-8vaKSlYm.css → components-DgK6vTbM.css} +17 -14
  30. package/bundled-test-harness/assets/{container-DYJ5z0jP.js → container-DRblzvCW.js} +16 -16
  31. package/bundled-test-harness/assets/{convert-to-class-name-test-B7ILkHei.js → convert-to-class-name-test-BiCrnGxA.js} +1 -1
  32. package/bundled-test-harness/assets/{create-file-modal-BNJHELMz.js → create-file-modal-Cfvn0u5s.js} +4 -4
  33. package/bundled-test-harness/assets/{create-listing-modal-test-BIG3NbX-.js → create-listing-modal-test-W6LuCzaK.js} +2 -2
  34. package/bundled-test-harness/assets/{debug-message-test-CAzyW7l3.js → debug-message-test-CXVfUuec.js} +1 -1
  35. package/bundled-test-harness/assets/{error-display-test-Btk0RcqS.js → error-display-test-B6I8CYXw.js} +1 -1
  36. package/bundled-test-harness/assets/{externals-C65aJ_7H.js → externals-DiG6hYU9.js} +2 -2
  37. package/bundled-test-harness/assets/{fallback-banner-live-delete-test-I7o8HPay.js → fallback-banner-live-delete-test-Bv_OBmzY.js} +1 -1
  38. package/bundled-test-harness/assets/{fallback-banner-test-Bh-BDHwS.js → fallback-banner-test-TL8jNe4f.js} +1 -1
  39. package/bundled-test-harness/assets/{fallback-models-test-GjvpcpvS.js → fallback-models-test-DEhvKsfY.js} +1 -1
  40. package/bundled-test-harness/assets/{file-DUbLArQi.js → file-D8hnXC1_.js} +8 -2
  41. package/bundled-test-harness/assets/{file-attachment-test-CtV6jG8f.js → file-attachment-test-CkL6IaWf.js} +1 -1
  42. package/bundled-test-harness/assets/{file-chooser-test-C9VwTWSW.js → file-chooser-test-M1rOgltw.js} +1 -1
  43. package/bundled-test-harness/assets/{formatted-aibot-message-test-DRdKZ8lg.js → formatted-aibot-message-test-CNk1lVYO.js} +1 -1
  44. package/bundled-test-harness/assets/{general-test-pIZFE9Ii.js → general-test-B_QwZC0y.js} +1 -1
  45. package/bundled-test-harness/assets/{index-query-engine-test-DvgGBuTy.js → index-query-engine-test-x77vbwML.js} +1 -1
  46. package/bundled-test-harness/assets/{inspector-test-COfLlQnO.js → inspector-test-CtCAKlpG.js} +39 -2
  47. package/bundled-test-harness/assets/{instance-filter-matcher-test-DUeZ6eiv.js → instance-filter-matcher-test-YKzjZAa3.js} +1 -1
  48. package/bundled-test-harness/assets/{interact-submode-creation-and-permissions-test-Dr-clTrg.js → interact-submode-creation-and-permissions-test-2_4tHla6.js} +1 -1
  49. package/bundled-test-harness/assets/{interact-submode-setup-D23oc2q6.js → interact-submode-setup-DI2HM3S-.js} +1 -1
  50. package/bundled-test-harness/assets/{interact-submode-test-t8HEq7Bp.js → interact-submode-test-BUKZnm-y.js} +1 -1
  51. package/bundled-test-harness/assets/{item-button-CAhhwhRh.js → item-button-CepLHUN-.js} +1 -1
  52. package/bundled-test-harness/assets/{live-prerendered-search-kxxMxyHS.js → live-prerendered-search-DiFs_MHd.js} +1 -1
  53. package/bundled-test-harness/assets/{login-ou9E1DG9.js → login-DWNfbapF.js} +1 -1
  54. package/bundled-test-harness/assets/{login-error-text-test-BsNV9TOq.js → login-error-text-test-3lQ69Gme.js} +1 -1
  55. package/bundled-test-harness/assets/{main-CrtBvhng.js → main-CDat3XsY.js} +1 -1
  56. package/bundled-test-harness/assets/{markdown-preview-CKlpPkbc.js → markdown-preview-o2AI98EN.js} +1 -1
  57. package/bundled-test-harness/assets/{markdown-preview-test-BTdN_033.js → markdown-preview-test-D2YoXqUY.js} +1 -1
  58. package/bundled-test-harness/assets/{modal-container-wzIBYeE_.js → modal-container-BzEkpeIx.js} +1 -1
  59. package/bundled-test-harness/assets/{on-key-CpWVcRaS.js → on-key-C8yMINOQ.js} +1 -1
  60. package/bundled-test-harness/assets/{open-image-in-new-tab-test-B-jsojF0.js → open-image-in-new-tab-test-3iRCTOjd.js} +1 -1
  61. package/bundled-test-harness/assets/{operator-mode-acceptance-test-CXQkU0xV.js → operator-mode-acceptance-test-DoQ5Klyi.js} +2 -2
  62. package/bundled-test-harness/assets/{operator-mode-card-catalog-test-DjTCHu2f.js → operator-mode-card-catalog-test-Came_pKy.js} +2 -2
  63. package/bundled-test-harness/assets/{operator-mode-links-test-EeNC7Or8.js → operator-mode-links-test-DoTvJvpI.js} +2 -2
  64. package/bundled-test-harness/assets/{operator-mode-test-Cdm2eu3n.js → operator-mode-test-DeUtV1XR.js} +2 -2
  65. package/bundled-test-harness/assets/{operator-mode-ui-test-yWX9IF_q.js → operator-mode-ui-test-DTUMZBlw.js} +2 -2
  66. package/bundled-test-harness/assets/{overlay-menu-items-test-D2yM2xsl.js → overlay-menu-items-test-DhPIUwmz.js} +1 -1
  67. package/bundled-test-harness/assets/{panel-DBvohlmr.js → panel-DcMU_CGT.js} +5 -5
  68. package/bundled-test-harness/assets/{past-sessions-test-DqEv1zfI.js → past-sessions-test-BrdqlYxe.js} +1 -1
  69. package/bundled-test-harness/assets/{patch-code-ClPL6B_d.js → patch-code-F_7JKtTQ.js} +1 -1
  70. package/bundled-test-harness/assets/{patch-code-test-Y34298iq.js → patch-code-test-O5_p8vp4.js} +3 -3
  71. package/bundled-test-harness/assets/{prerendered-card-search-test-BTgiRYnI.js → prerendered-card-search-test-DLYUamOp.js} +1 -1
  72. package/bundled-test-harness/assets/{preview-format-Bb5FIkOY.js → preview-format-BqY_j3zZ.js} +1 -1
  73. package/bundled-test-harness/assets/{preview-format-test-DjS1vxlt.js → preview-format-test-CublqRWy.js} +1 -1
  74. package/bundled-test-harness/assets/{realm-config-routing-rule-edit-test-E5AhFXfq.js → realm-config-routing-rule-edit-test-T8S_2Lwq.js} +47 -4
  75. package/bundled-test-harness/assets/{reasoning-test-ChZJb8sD.js → reasoning-test-DcQkF04r.js} +1 -1
  76. package/bundled-test-harness/assets/{room-message-test-DQ2x7ymr.js → room-message-test-DuSkABnU.js} +2 -2
  77. package/bundled-test-harness/assets/{schema-generation-test-DxhNMqOe.js → schema-generation-test-z90uBA7r.js} +1 -1
  78. package/bundled-test-harness/assets/{scrolling-test-Drdch9wf.js → scrolling-test-jCKormjp.js} +2 -2
  79. package/bundled-test-harness/assets/{search-result-header-5VQHm_Ev.js → search-result-header-Ch7TKEzn.js} +1 -1
  80. package/bundled-test-harness/assets/{sending-test-6i0F6n4v.js → sending-test-Bocu5vf9.js} +2 -2
  81. package/bundled-test-harness/assets/{setup-DeQyrqyx.js → setup-CN-HHlT_.js} +1 -1
  82. package/bundled-test-harness/assets/{skills-test-Dl7uEBNz.js → skills-test-Cut0gpNU.js} +1 -1
  83. package/bundled-test-harness/assets/{store-test-Dkf9wBLg.js → store-test-C747NJ3O.js} +2 -2
  84. package/bundled-test-harness/assets/{submode-switcher-Popj4iN5.js → submode-switcher-iR4m0GzD.js} +1 -1
  85. package/bundled-test-harness/assets/{switch-submode-DOdRs5QD.js → switch-submode--ZVbWG3S.js} +1 -1
  86. package/bundled-test-harness/assets/{switch-submode-test-CX0YxbVu.js → switch-submode-test--Ncx8g_l.js} +1 -1
  87. package/bundled-test-harness/assets/{tests-D1v0wSnN.js → tests-Dw_agY8k.js} +60 -60
  88. package/bundled-test-harness/assets/{text-input-validator-test-CT3c7m8B.js → text-input-validator-test-CAlDivoV.js} +1 -1
  89. package/bundled-test-harness/assets/{theme-card-test-DyOWEJRn.js → theme-card-test-Q5eP-lEF.js} +1 -1
  90. package/bundled-test-harness/assets/{workspace-DvhB0Ltb.js → workspace-PBphyb9N.js} +2 -2
  91. package/bundled-test-harness/assets/{workspace-delete-summary-test-B7rnzX95.js → workspace-delete-summary-test-iqD1ae55.js} +1 -1
  92. package/bundled-test-harness/boxel-ui-checksum.txt +1 -1
  93. package/bundled-test-harness/index.html +26 -26
  94. package/bundled-test-harness/tests/index.html +26 -26
  95. package/bundled-types/base/field-component.d.ts +15 -4
  96. package/bundled-types/base/links-to-editor.d.ts +9 -0
  97. package/bundled-types/boxel-ui/components/filter-list/index.gts +4 -1
  98. package/dist/index.js +241 -241
  99. package/package.json +3 -3
  100. package/src/commands/realm/publish.ts +87 -16
  101. package/src/commands/realm/unpublish.ts +11 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cardstack/boxel-cli",
3
- "version": "0.4.0-unstable.0",
3
+ "version": "0.4.0-unstable.1",
4
4
  "license": "MIT",
5
5
  "description": "CLI tools for Boxel workspace management",
6
6
  "main": "./dist/index.js",
@@ -62,9 +62,9 @@
62
62
  "ts-node": "^10.9.1",
63
63
  "vite": "^6.3.2",
64
64
  "vitest": "^2.1.9",
65
- "@cardstack/local-types": "0.0.0",
65
+ "@cardstack/postgres": "0.0.0",
66
66
  "@cardstack/runtime-common": "1.0.0",
67
- "@cardstack/postgres": "0.0.0"
67
+ "@cardstack/local-types": "0.0.0"
68
68
  },
69
69
  "publishConfig": {
70
70
  "access": "public",
@@ -1,17 +1,20 @@
1
1
  import type { Command } from 'commander';
2
2
  import { ensureTrailingSlash } from '@cardstack/runtime-common/paths';
3
3
  import {
4
+ fetchPublishabilityReport,
4
5
  publishRealm as publishRealmOperation,
5
6
  type PublishRealmOutput,
6
7
  RealmOperationError,
7
8
  waitForReady,
8
9
  } from '@cardstack/runtime-common/realm-operations';
10
+ import type { PublishabilityViolation } from '@cardstack/runtime-common/publishability';
9
11
  import { buildCliRealmClient } from '../../lib/realm-client.ts';
10
12
  import {
11
13
  getProfileManager,
12
14
  type ProfileManager,
13
15
  } from '../../lib/profile-manager.ts';
14
16
  import { unpublishRealm } from './unpublish.ts';
17
+ import { cliLog } from '../../lib/cli-log.ts';
15
18
  import { FG_CYAN, FG_GREEN, FG_RED, RESET } from '../../lib/colors.ts';
16
19
 
17
20
  const DEFAULT_TIMEOUT_MS = 300_000;
@@ -26,6 +29,11 @@ export interface PublishOptions {
26
29
  * unpublish the target URL first and retry once. Default: true.
27
30
  */
28
31
  republish?: boolean;
32
+ /**
33
+ * Skip the publishability gate (private-dependency / error-document check).
34
+ * Default: false — the gate runs and blocks publishing on violations.
35
+ */
36
+ force?: boolean;
29
37
  profileManager?: ProfileManager;
30
38
  }
31
39
 
@@ -39,7 +47,9 @@ export interface PublishRealmResult {
39
47
  /**
40
48
  * Publish a source realm to a published-realm URL.
41
49
  *
42
- * Speaks the contract documented at
50
+ * Before publishing, runs the publishability gate (private-dependency /
51
+ * error-document check) and refuses to publish on violations unless `force`
52
+ * is set. Then speaks the contract documented at
43
53
  * `packages/realm-server/handlers/handle-publish-realm.ts`: the server
44
54
  * accepts the publish, returns `202 Accepted` with `status: "pending"`,
45
55
  * and the client polls `/<publishedRealmURL>/_readiness-check` until
@@ -57,6 +67,18 @@ export async function publishRealm(
57
67
  let normalizedSource = ensureTrailingSlash(sourceRealmURL);
58
68
  let normalizedPublished = ensureTrailingSlash(publishedRealmURL);
59
69
 
70
+ // Pre-publish gate: refuse to publish a realm with private-dependency or
71
+ // error-document violations (which would break the published site) unless
72
+ // the caller forces it.
73
+ if (!options.force) {
74
+ let report = await fetchPublishabilityReport(client, {
75
+ realmURL: normalizedSource,
76
+ });
77
+ if (!report.publishable) {
78
+ throw new Error(describeViolations(report.violations));
79
+ }
80
+ }
81
+
60
82
  let output: PublishRealmOutput;
61
83
  try {
62
84
  output = await publishRealmOperation(client, {
@@ -71,7 +93,9 @@ export async function publishRealm(
71
93
  (err.status === 400 || err.status === 409) &&
72
94
  options.republish !== false
73
95
  ) {
74
- console.log(
96
+ // Progress, not payload: route to stderr via cliLog.info so it never
97
+ // corrupts stdout when the caller passed --json (stdout is JSON-only).
98
+ cliLog.info(
75
99
  `Publish returned ${err.status} (${(err.body ?? '').slice(0, 200)}). Unpublishing and retrying.`,
76
100
  );
77
101
  let unpublishResult = await unpublishRealm(normalizedPublished, {
@@ -118,6 +142,8 @@ export interface PublishCliOptions {
118
142
  wait?: boolean;
119
143
  timeout?: number;
120
144
  republish?: boolean;
145
+ force?: boolean;
146
+ json?: boolean;
121
147
  }
122
148
 
123
149
  export function publishCliOptsToOptions(
@@ -127,6 +153,7 @@ export function publishCliOptsToOptions(
127
153
  waitForReady: opts.wait !== false,
128
154
  timeoutMs: opts.timeout,
129
155
  republish: opts.republish !== false,
156
+ force: opts.force === true,
130
157
  };
131
158
  }
132
159
 
@@ -134,7 +161,7 @@ export function registerPublishCommand(realm: Command): void {
134
161
  realm
135
162
  .command('publish')
136
163
  .description(
137
- 'Publish a source realm to a published-realm URL, polling readiness until ready',
164
+ 'Publish a source realm to a published-realm URL: runs the publishability gate (use --force to skip), then polls readiness until ready',
138
165
  )
139
166
  .argument('<source-realm-url>', 'URL of the source realm to publish')
140
167
  .argument(
@@ -151,6 +178,11 @@ export function registerPublishCommand(realm: Command): void {
151
178
  '--no-republish',
152
179
  'Do not auto-unpublish + retry when the server returns 400/409',
153
180
  )
181
+ .option(
182
+ '--force',
183
+ 'Publish even if the realm has publishability violations (skips the gate)',
184
+ )
185
+ .option('--json', 'Output the result as JSON')
154
186
  .action(
155
187
  async (
156
188
  sourceRealmURL: string,
@@ -163,20 +195,27 @@ export function registerPublishCommand(realm: Command): void {
163
195
  publishedRealmURL,
164
196
  publishCliOptsToOptions(opts),
165
197
  );
166
- console.log(
167
- `${FG_GREEN}Published:${RESET} ${FG_CYAN}${result.publishedRealmURL}${RESET}`,
168
- );
198
+ if (opts.json) {
199
+ cliLog.output(JSON.stringify(result, null, 2));
200
+ } else {
201
+ console.log(
202
+ `${FG_GREEN}Published:${RESET} ${FG_CYAN}${result.publishedRealmURL}${RESET}`,
203
+ );
204
+ }
169
205
  } catch (err) {
170
- console.error(
171
- `${FG_RED}Error:${RESET} ${err instanceof Error ? err.message : String(err)}`,
172
- );
173
- // Node's fetch surfaces the actual transport error (ECONNRESET,
174
- // TLS failure, undici socket error, etc.) on `error.cause`. Print
175
- // it so opaque "fetch failed" messages don't strand the caller.
176
- // `!= null` rather than a truthy check so we don't drop
177
- // falsy-but-defined causes (`''`, `0`, `false`, `NaN`).
178
- if (err instanceof Error && err.cause != null) {
179
- console.error(`${FG_RED}Caused by:${RESET}`, err.cause);
206
+ let message = err instanceof Error ? err.message : String(err);
207
+ if (opts.json) {
208
+ cliLog.output(JSON.stringify({ error: message }, null, 2));
209
+ } else {
210
+ console.error(`${FG_RED}Error:${RESET} ${message}`);
211
+ // Node's fetch surfaces the actual transport error (ECONNRESET,
212
+ // TLS failure, undici socket error, etc.) on `error.cause`. Print
213
+ // it so opaque "fetch failed" messages don't strand the caller.
214
+ // `!= null` rather than a truthy check so we don't drop
215
+ // falsy-but-defined causes (`''`, `0`, `false`, `NaN`).
216
+ if (err instanceof Error && err.cause != null) {
217
+ console.error(`${FG_RED}Caused by:${RESET}`, err.cause);
218
+ }
180
219
  }
181
220
  process.exit(1);
182
221
  }
@@ -184,6 +223,38 @@ export function registerPublishCommand(realm: Command): void {
184
223
  );
185
224
  }
186
225
 
226
+ // Summarizes publishability violations into a single actionable error message.
227
+ // Mirrors the host PublishRealmCommand's gate messaging.
228
+ export function describeViolations(
229
+ violations: PublishabilityViolation[],
230
+ ): string {
231
+ let privateCount = violations.filter(
232
+ (v) => v.kind === 'private-dependency',
233
+ ).length;
234
+ let errorCount = violations.filter((v) => v.kind === 'error-document').length;
235
+
236
+ let parts: string[] = [];
237
+ if (privateCount) {
238
+ parts.push(`${privateCount} private-dependency violation(s)`);
239
+ }
240
+ if (errorCount) {
241
+ parts.push(`${errorCount} error-document violation(s)`);
242
+ }
243
+ let summary = parts.length
244
+ ? parts.join(', ')
245
+ : `${violations.length} violation(s)`;
246
+
247
+ let resources = violations
248
+ .map((v) => v.resource)
249
+ .filter(Boolean)
250
+ .slice(0, 5)
251
+ .join(', ');
252
+
253
+ return `Realm is not publishable (${summary}). Resolve them or pass --force to override.${
254
+ resources ? ` Affected: ${resources}` : ''
255
+ }`;
256
+ }
257
+
187
258
  function parseTimeoutOption(value: string): number {
188
259
  let n = Number.parseInt(value, 10);
189
260
  if (!Number.isFinite(n) || n < 0 || String(n) !== value.trim()) {
@@ -10,6 +10,7 @@ import {
10
10
  NO_ACTIVE_PROFILE_ERROR,
11
11
  type ProfileManager,
12
12
  } from '../../lib/profile-manager.ts';
13
+ import { cliLog } from '../../lib/cli-log.ts';
13
14
  import { FG_CYAN, FG_GREEN, FG_RED, RESET } from '../../lib/colors.ts';
14
15
  import { describeFetchError } from '../../lib/describe-fetch-error.ts';
15
16
 
@@ -99,6 +100,7 @@ export async function unpublishRealm(
99
100
 
100
101
  interface UnpublishCliOptions {
101
102
  tolerateMissing?: boolean;
103
+ json?: boolean;
102
104
  }
103
105
 
104
106
  export function registerUnpublishCommand(realm: Command): void {
@@ -110,11 +112,20 @@ export function registerUnpublishCommand(realm: Command): void {
110
112
  '--tolerate-missing',
111
113
  'Exit successfully when the realm is already unpublished',
112
114
  )
115
+ .option('--json', 'Output the result as JSON')
113
116
  .action(async (publishedRealmURL: string, opts: UnpublishCliOptions) => {
114
117
  let result = await unpublishRealm(publishedRealmURL, {
115
118
  tolerateMissing: opts.tolerateMissing === true,
116
119
  });
117
120
 
121
+ if (opts.json) {
122
+ cliLog.output(JSON.stringify(result, null, 2));
123
+ if (result.error) {
124
+ process.exit(1);
125
+ }
126
+ return;
127
+ }
128
+
118
129
  if (result.error) {
119
130
  console.error(`${FG_RED}Error:${RESET} ${result.error}`);
120
131
  process.exit(1);