@contentful/experience-design-system-cli 2.15.2-dev-build-a4dea7f.0 → 2.15.2-dev-build-31eb989.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.
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contentful/experience-design-system-cli",
3
- "version": "2.15.2-dev-build-a4dea7f.0",
3
+ "version": "2.15.2-dev-build-31eb989.0",
4
4
  "description": "Contentful Experiences design system import CLI",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -55,7 +55,7 @@
55
55
  "eslint-plugin-prettier": "^5.5.4",
56
56
  "ink-testing-library": "^4.0.0",
57
57
  "typescript-eslint": "^8.52.0",
58
- "vitest": "^4.1.10"
58
+ "vitest": "^4.0.16"
59
59
  },
60
60
  "repository": {
61
61
  "type": "git",
@@ -4,6 +4,7 @@ import { access, readFile, readdir, stat } from 'node:fs/promises';
4
4
  import { join } from 'node:path';
5
5
  import { validateCDF, flattenDTCG, validateDTCG, buildManifest, buildFilteredManifest, } from '@contentful/experience-design-system-types';
6
6
  import { ApiError, ImportApiClient } from './api-client.js';
7
+ import { formatEdsiError } from './error-parser.js';
7
8
  import { openPipelineDb, loadCDFComponents } from '../session/db.js';
8
9
  import { findSlotCycles, suggestCycleBreakEdge, formatCyclePath } from '../analyze/cycle-detection.js';
9
10
  import { isEmptyPreview } from './preview-utils.js';
@@ -17,6 +18,13 @@ function die(message) {
17
18
  process.stderr.write(`${message}\n`);
18
19
  process.exit(1);
19
20
  }
21
+ function formatApiError(error, verbose = false) {
22
+ const formatted = formatEdsiError(error.body || error.message, { verbose, raw: error.body }) || error.message;
23
+ const phase = error.message.split('\n', 1)[0];
24
+ return /^(?:apply|preview|poll) failed: \d+$/.test(phase) && formatted !== phase
25
+ ? `${phase}\n${formatted}`
26
+ : formatted;
27
+ }
20
28
  async function pathExists(p) {
21
29
  return access(p)
22
30
  .then(() => true)
@@ -289,7 +297,7 @@ function buildApplyOutput(operation, spaceId, environmentId, host) {
289
297
  .map((item) => ({
290
298
  entityType: item.entityType,
291
299
  entityId: item.id,
292
- error: item.error,
300
+ error: formatEdsiError(item.error),
293
301
  })),
294
302
  };
295
303
  }
@@ -435,7 +443,7 @@ export function registerApplyCommand(program) {
435
443
  }
436
444
  catch (e) {
437
445
  if (e instanceof ApiError)
438
- die(`Error: ${e.message}`);
446
+ die(`Error: ${formatApiError(e)}`);
439
447
  throw e;
440
448
  }
441
449
  const { components, tokens, client } = inputs;
@@ -444,7 +452,7 @@ export function registerApplyCommand(program) {
444
452
  }
445
453
  catch (e) {
446
454
  if (e instanceof ApiError)
447
- die(`Error: ${e.message}`);
455
+ die(`Error: ${formatApiError(e)}`);
448
456
  const cause = e instanceof Error && e.cause instanceof Error ? e.cause.message : '';
449
457
  die(`Error: unable to connect to API host${cause ? `: ${cause}` : ''}`);
450
458
  }
@@ -455,7 +463,7 @@ export function registerApplyCommand(program) {
455
463
  }
456
464
  catch (e) {
457
465
  if (e instanceof ApiError)
458
- die(`Error: ${e.message}`);
466
+ die(`Error: ${formatApiError(e)}`);
459
467
  throw e;
460
468
  }
461
469
  const spaceId = opts.spaceId;
@@ -501,7 +509,7 @@ export function registerApplyCommand(program) {
501
509
  }
502
510
  catch (e) {
503
511
  if (e instanceof ApiError)
504
- die(`Error: ${e.message}`);
512
+ die(`Error: ${formatApiError(e, opts.verbose)}`);
505
513
  throw e;
506
514
  }
507
515
  const { components, tokens, client } = inputs;
@@ -511,7 +519,7 @@ export function registerApplyCommand(program) {
511
519
  }
512
520
  catch (e) {
513
521
  if (e instanceof ApiError)
514
- die(`Error: ${e.message}`);
522
+ die(`Error: ${formatApiError(e, opts.verbose)}`);
515
523
  throw e;
516
524
  }
517
525
  const manifest = buildManifest(components, tokens);
@@ -521,7 +529,7 @@ export function registerApplyCommand(program) {
521
529
  }
522
530
  catch (e) {
523
531
  if (e instanceof ApiError)
524
- die(`Error: ${e.message}`);
532
+ die(`Error: ${formatApiError(e, opts.verbose)}`);
525
533
  throw e;
526
534
  }
527
535
  const spaceId = opts.spaceId;
@@ -566,7 +574,7 @@ export function registerApplyCommand(program) {
566
574
  }
567
575
  catch (e) {
568
576
  if (e instanceof ApiError)
569
- die(`Error: ${e.message}`);
577
+ die(`Error: ${formatApiError(e, opts.verbose)}`);
570
578
  throw e;
571
579
  }
572
580
  process.stderr.write(`Apply operation started: ${operation.sys.id}\n`);
@@ -575,7 +583,7 @@ export function registerApplyCommand(program) {
575
583
  }
576
584
  catch (e) {
577
585
  if (e instanceof ApiError)
578
- die(`Error: ${e.message}`);
586
+ die(`Error: ${formatApiError(e, opts.verbose)}`);
579
587
  throw e;
580
588
  }
581
589
  const summary = buildApplyOutput(operation, spaceId, environmentId, opts.host);
@@ -600,7 +608,7 @@ export function registerApplyCommand(program) {
600
608
  spaceId,
601
609
  environmentId,
602
610
  status: 'error',
603
- error: e.message,
611
+ error: formatApiError(e, opts.verbose),
604
612
  }));
605
613
  return;
606
614
  }
@@ -621,7 +629,7 @@ export function registerApplyCommand(program) {
621
629
  spaceId,
622
630
  environmentId,
623
631
  status: 'error',
624
- error: e.message,
632
+ error: formatApiError(e, opts.verbose),
625
633
  }));
626
634
  return;
627
635
  }
@@ -677,7 +685,7 @@ export function registerApplyCommand(program) {
677
685
  }
678
686
  catch (e) {
679
687
  if (e instanceof ApiError)
680
- die(`Error: ${e.message}`);
688
+ die(`Error: ${formatApiError(e)}`);
681
689
  throw e;
682
690
  }
683
691
  const { components, tokens, client } = inputs;
@@ -687,7 +695,7 @@ export function registerApplyCommand(program) {
687
695
  }
688
696
  catch (e) {
689
697
  if (e instanceof ApiError)
690
- die(`Error: ${e.message}`);
698
+ die(`Error: ${formatApiError(e)}`);
691
699
  throw e;
692
700
  }
693
701
  const fullManifest = buildManifest(components, tokens);
@@ -697,7 +705,7 @@ export function registerApplyCommand(program) {
697
705
  }
698
706
  catch (e) {
699
707
  if (e instanceof ApiError)
700
- die(`Error: ${e.message}`);
708
+ die(`Error: ${formatApiError(e)}`);
701
709
  throw e;
702
710
  }
703
711
  const spaceId = opts.spaceId;
@@ -735,7 +743,7 @@ export function registerApplyCommand(program) {
735
743
  }
736
744
  catch (e) {
737
745
  if (e instanceof ApiError)
738
- die(`Error: ${e.message}`);
746
+ die(`Error: ${formatApiError(e)}`);
739
747
  throw e;
740
748
  }
741
749
  process.stderr.write(`Apply operation started: ${operation.sys.id}\n`);
@@ -744,7 +752,7 @@ export function registerApplyCommand(program) {
744
752
  }
745
753
  catch (e) {
746
754
  if (e instanceof ApiError)
747
- die(`Error: ${e.message}`);
755
+ die(`Error: ${formatApiError(e)}`);
748
756
  throw e;
749
757
  }
750
758
  const summary = buildApplyOutput(operation, spaceId, environmentId, opts.host);
@@ -781,7 +789,7 @@ export function registerApplyCommand(program) {
781
789
  spaceId,
782
790
  environmentId,
783
791
  status: 'error',
784
- error: e.message,
792
+ error: formatApiError(e),
785
793
  }));
786
794
  return;
787
795
  }
@@ -802,7 +810,7 @@ export function registerApplyCommand(program) {
802
810
  spaceId,
803
811
  environmentId,
804
812
  status: 'error',
805
- error: e.message,
813
+ error: formatApiError(e),
806
814
  }));
807
815
  return;
808
816
  }
@@ -7,6 +7,14 @@ export interface ParsedEdsiError {
7
7
  cycle: string[] | null;
8
8
  /** True when the message survived cleaning as-is (no parseable structure). */
9
9
  raw: boolean;
10
+ /** Validation details supplied by the API, normalized for terminal output. */
11
+ diagnostics?: ErrorDiagnostic[];
12
+ }
13
+ export interface ErrorDiagnostic {
14
+ message: string;
15
+ component?: string;
16
+ path?: string;
17
+ pathMissing?: boolean;
10
18
  }
11
19
  export declare function stripLambdaLogPrefix(body: string): string;
12
20
  export declare function parseEdsiError(rawInput: string | undefined | null): ParsedEdsiError;
@@ -14,3 +22,7 @@ export declare function formatParsedEdsiError(parsed: ParsedEdsiError, opts?: {
14
22
  verbose?: boolean;
15
23
  raw?: string;
16
24
  }): string;
25
+ export declare function formatEdsiError(raw: unknown, opts?: {
26
+ verbose?: boolean;
27
+ raw?: string;
28
+ }): string;
@@ -26,6 +26,66 @@ function parseObjectLiteralTail(body) {
26
26
  }
27
27
  return { code, cycle };
28
28
  }
29
+ function asRecord(value) {
30
+ return typeof value === 'object' && value !== null && !Array.isArray(value)
31
+ ? value
32
+ : null;
33
+ }
34
+ function displayValue(value) {
35
+ if (typeof value === 'string')
36
+ return value;
37
+ if (typeof value === 'number' || typeof value === 'boolean')
38
+ return String(value);
39
+ if (value === null || value === undefined)
40
+ return null;
41
+ try {
42
+ return JSON.stringify(value);
43
+ }
44
+ catch {
45
+ return null;
46
+ }
47
+ }
48
+ function formatPath(value) {
49
+ if (typeof value === 'string')
50
+ return value ? { path: value } : { pathMissing: true };
51
+ if (Array.isArray(value)) {
52
+ const parts = value.filter((part) => typeof part === 'string' || typeof part === 'number');
53
+ return parts.length > 0 ? { path: parts.join(' › ') } : { pathMissing: true };
54
+ }
55
+ return {};
56
+ }
57
+ function componentFromPath(path) {
58
+ if (!path)
59
+ return undefined;
60
+ const manifestMatch = path.match(/manifest:components\/([^/›]+)/);
61
+ if (manifestMatch?.[1])
62
+ return manifestMatch[1];
63
+ const parts = path
64
+ .split(/[›/]/)
65
+ .map((part) => part.trim())
66
+ .filter(Boolean);
67
+ const componentsIndex = parts.findIndex((part) => part === 'components');
68
+ return componentsIndex === -1 ? undefined : parts[componentsIndex + 1];
69
+ }
70
+ function parseValidationDiagnostics(details) {
71
+ if (!Array.isArray(details.errors))
72
+ return [];
73
+ const diagnostics = [];
74
+ for (const rawError of details.errors) {
75
+ const error = asRecord(rawError);
76
+ if (!error)
77
+ continue;
78
+ const location = formatPath(error.path);
79
+ const component = displayValue(error.component ?? error.componentName ?? error.componentId) ?? componentFromPath(location.path);
80
+ const message = displayValue(error.message) ??
81
+ displayValue(error.details) ??
82
+ displayValue(error.error) ??
83
+ displayValue(error.name) ??
84
+ 'Validation failed';
85
+ diagnostics.push({ message, ...location, ...(component ? { component } : {}) });
86
+ }
87
+ return diagnostics;
88
+ }
29
89
  function parseJsonBody(body) {
30
90
  let parsed;
31
91
  try {
@@ -34,12 +94,12 @@ function parseJsonBody(body) {
34
94
  catch {
35
95
  return null;
36
96
  }
37
- if (!parsed || typeof parsed !== 'object')
97
+ const p = asRecord(parsed);
98
+ if (!p)
38
99
  return null;
39
- const p = parsed;
40
- const details = (p.details && typeof p.details === 'object' ? p.details : {});
100
+ const details = asRecord(p.details) ?? {};
41
101
  const pick = (k) => (p[k] !== undefined ? p[k] : details[k]);
42
- const codeRaw = pick('code');
102
+ const codeRaw = pick('code') ?? asRecord(p.sys)?.id;
43
103
  const messageRaw = pick('message');
44
104
  const cycleRaw = pick('cycle');
45
105
  const out = {};
@@ -52,8 +112,30 @@ function parseJsonBody(body) {
52
112
  if (strs.length > 0)
53
113
  out.cycle = strs;
54
114
  }
115
+ const diagnostics = parseValidationDiagnostics(details);
116
+ if (diagnostics.length > 0)
117
+ out.diagnostics = diagnostics;
55
118
  return out;
56
119
  }
120
+ function parseBindingDiagnostics(body) {
121
+ if (!/binding configurations are invalid|Pointer path does not exist/i.test(body))
122
+ return null;
123
+ const locationMatches = [...body.matchAll(/(?:^|\.\s)([A-Za-z0-9_$-]+(?:\s*›\s*[A-Za-z0-9_$-]+){2,})/g)];
124
+ const location = locationMatches.at(-1)?.[1];
125
+ const pointerMessage = body.match(/Pointer path does not exist for '\[object Object\]':\s*(.+?)(?=\.\s*Default\s*›|$)/i)?.[1];
126
+ const graphQlMessage = body.match(/return GraphQL validation error:\s*(.+?)(?=\.\s*Default\s*›|$)/i)?.[1];
127
+ const heading = body.match(/^(.*?)(?=\.?\s*Pointer path does not exist|\.?\s*Default\s*›)/i)?.[1]?.trim();
128
+ const diagnostics = [];
129
+ if (pointerMessage)
130
+ diagnostics.push({ message: pointerMessage.trim(), ...(location ? { path: location } : {}) });
131
+ if (graphQlMessage)
132
+ diagnostics.push({ message: graphQlMessage.trim(), ...(location ? { path: location } : {}) });
133
+ return {
134
+ code: 'BindingValidationFailed',
135
+ message: heading || 'One or more binding configurations are invalid.',
136
+ ...(diagnostics.length > 0 ? { diagnostics } : {}),
137
+ };
138
+ }
57
139
  // ApiError.message shape: `${phasePrefix}\n${body}` where phasePrefix looks
58
140
  // like `apply failed: 400`, `preview failed: 422`, `poll failed: 500`. We
59
141
  // only strip the prefix line when the first line matches this shape —
@@ -75,12 +157,33 @@ export function parseEdsiError(rawInput) {
75
157
  }
76
158
  const cleaned = stripLambdaLogPrefix(body);
77
159
  const json = parseJsonBody(cleaned) ?? parseJsonBody(body);
160
+ const bindingFromJson = typeof json?.message === 'string' ? parseBindingDiagnostics(json.message) : null;
161
+ if (bindingFromJson) {
162
+ return {
163
+ code: bindingFromJson.code ?? json?.code ?? null,
164
+ message: bindingFromJson.message ?? cleaned,
165
+ cycle: null,
166
+ raw: false,
167
+ ...(bindingFromJson.diagnostics ? { diagnostics: bindingFromJson.diagnostics } : {}),
168
+ };
169
+ }
78
170
  if (json && (json.code || json.message || json.cycle)) {
79
171
  return {
80
172
  code: json.code ?? null,
81
173
  message: json.message ?? (cleaned || prefix),
82
174
  cycle: json.cycle ?? null,
83
175
  raw: false,
176
+ ...(json.diagnostics ? { diagnostics: json.diagnostics } : {}),
177
+ };
178
+ }
179
+ const binding = parseBindingDiagnostics(cleaned);
180
+ if (binding) {
181
+ return {
182
+ code: binding.code ?? null,
183
+ message: binding.message ?? cleaned,
184
+ cycle: null,
185
+ raw: false,
186
+ ...(binding.diagnostics ? { diagnostics: binding.diagnostics } : {}),
84
187
  };
85
188
  }
86
189
  const literal = parseObjectLiteralTail(cleaned);
@@ -104,6 +207,22 @@ export function formatParsedEdsiError(parsed, opts = {}) {
104
207
  if (parsed.message) {
105
208
  lines.push(parsed.message);
106
209
  }
210
+ for (const diagnostic of parsed.diagnostics ?? []) {
211
+ const context = [
212
+ diagnostic.component ? `Component: ${diagnostic.component}` : null,
213
+ diagnostic.path ? `Path: ${diagnostic.path}` : null,
214
+ ].filter(Boolean);
215
+ lines.push(`- ${diagnostic.message}${context.length > 0 ? ` (${context.join('; ')})` : ''}`);
216
+ if (diagnostic.pathMissing) {
217
+ lines.push(' Location: not provided by the server. Review the submitted manifest; no component or field was identified.');
218
+ }
219
+ }
220
+ if (parsed.code === 'BindingValidationFailed') {
221
+ const location = parsed.diagnostics?.find((diagnostic) => diagnostic.path)?.path;
222
+ lines.push(location
223
+ ? `Next action: review the binding at ${location} and correct its pointer or GraphQL selection.`
224
+ : 'Next action: review the binding pointer and GraphQL selection.');
225
+ }
107
226
  if (parsed.cycle && parsed.cycle.length > 0) {
108
227
  lines.push(`Cycle: ${parsed.cycle.join(' → ')} → ${parsed.cycle[0]}`);
109
228
  lines.push('Break the cycle by removing at least one $allowedComponents entry.');
@@ -115,3 +234,9 @@ export function formatParsedEdsiError(parsed, opts = {}) {
115
234
  }
116
235
  return lines.filter(Boolean).join('\n');
117
236
  }
237
+ export function formatEdsiError(raw, opts = {}) {
238
+ const input = typeof raw === 'string' ? raw : displayValue(raw);
239
+ if (!input)
240
+ return '';
241
+ return formatParsedEdsiError(parseEdsiError(input), { ...opts, raw: opts.raw ?? input });
242
+ }
@@ -2,6 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Box, Text, useInput } from 'ink';
3
3
  import { ServerPreviewView } from './ServerPreviewView.js';
4
4
  import { buildPostPushUrl } from '../../lib/contentful-urls.js';
5
+ import { formatEdsiError } from '../error-parser.js';
5
6
  export function ServerPreviewConfirm({ preview, spaceId, environmentId, breakingWithImpact, onConfirm, onCancel, }) {
6
7
  useInput((input, key) => {
7
8
  if (key.return)
@@ -35,9 +36,5 @@ export function ServerApplyDone({ operation, spaceId, environmentId, host }) {
35
36
  return (_jsxs(Box, { flexDirection: "column", paddingX: 2, paddingY: 1, children: [_jsxs(Text, { bold: true, children: ["Import complete \u2014 ", environmentId, " @ ", spaceId] }), _jsx(Text, { children: " " }), _jsxs(Text, { color: "green", children: [" \u2713 ", operation.summary.succeeded, " succeeded"] }), operation.summary.failed > 0 && _jsxs(Text, { color: "red", children: [" \u2717 ", operation.summary.failed, " failed"] }), operation.summary.failed === 0 && _jsx(Text, { dimColor: true, children: " All entities imported successfully." }), failures.length > 0 && (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { children: " " }), _jsx(Text, { bold: true, children: " Failures:" }), failures.map((item, i) => (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { color: "red", children: [' ', "\u2717 ", item.entityType, ": ", item.id] }), item.error && _jsxs(Text, { dimColor: true, children: [" ", formatItemError(item.error)] })] }, i)))] })), operation.sys.status === 'succeeded' && operation.summary.succeeded > 0 && (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { children: " " }), _jsx(Text, { dimColor: true, children: " View your design system:" }), _jsxs(Text, { color: "cyan", children: [" ", buildPostPushUrl({ host: host ?? 'api.contentful.com', spaceId, environmentId })] })] })), _jsx(Text, { children: " " }), _jsx(Text, { dimColor: true, children: " Press Q to exit." })] }));
36
37
  }
37
38
  function formatItemError(error) {
38
- if (!error)
39
- return '';
40
- if (typeof error === 'string')
41
- return error;
42
- return `${error.code}: ${error.message}`;
39
+ return formatEdsiError(error);
43
40
  }
@@ -61,16 +61,20 @@ export function parseCycleComponentNames(report) {
61
61
  return [...new Set(names)];
62
62
  }
63
63
  export function isPreviewValidationError(result) {
64
- return (result.exitCode !== 0 &&
65
- result.stderr.includes(`${PREVIEW_ERROR_PREFIX} 422`) &&
66
- result.stderr.includes(VALIDATION_FAILED_CODE));
64
+ if (result.exitCode === 0 || !result.stderr.includes(`${PREVIEW_ERROR_PREFIX} 422`))
65
+ return false;
66
+ return result.stderr.includes(VALIDATION_FAILED_CODE) || result.stderr.includes('[ValidationFailed]');
67
67
  }
68
68
  export function parseOffendingComponentNames(output) {
69
69
  const jsonStart = output.indexOf('{');
70
- if (jsonStart === -1)
71
- return [];
72
- const errors = parsePreviewValidationErrors(output.slice(jsonStart));
73
- return [...new Set(errors.map((e) => e.componentName))];
70
+ if (jsonStart !== -1) {
71
+ const errors = parsePreviewValidationErrors(output.slice(jsonStart));
72
+ const names = [...new Set(errors.map((error) => error.componentName))];
73
+ if (names.length > 0)
74
+ return names;
75
+ }
76
+ const names = [...output.matchAll(/Component:\s*([^;\n)]+)/g)].map((match) => match[1].trim()).filter(Boolean);
77
+ return [...new Set(names)];
74
78
  }
75
79
  export function buildPushStepResult(args) {
76
80
  const { created, updated, failed, durationMs, stderr, excludedByRetry, totalFailure } = args;
@@ -37,7 +37,7 @@ import { ImportApiClient, ApiError } from '../../apply/api-client.js';
37
37
  import { detectSlotCycles, extractComponentsFromManifest, formatSlotCycleReport } from '../../apply/command.js';
38
38
  import { findSlotCycles } from '../../analyze/cycle-detection.js';
39
39
  import { buildComponentGraph } from '../../analyze/slot-graph.js';
40
- import { parseEdsiError, formatParsedEdsiError } from '../../apply/error-parser.js';
40
+ import { parseEdsiError, formatEdsiError, formatParsedEdsiError } from '../../apply/error-parser.js';
41
41
  import { handlePreview422, applySkipValidationErrors, clearedValidationErrorState } from './wizard-422-helpers.js';
42
42
  import { parseGenerateStderrChunk } from './wizard-generate-progress.js';
43
43
  import { spawnGenerateChild } from './spawn-generate.js';
@@ -927,7 +927,10 @@ export function WizardApp({ initialSpaceId = '', initialEnvironmentId = 'master'
927
927
  update({
928
928
  step: 'error',
929
929
  errorStep: 'apply preview',
930
- errorMessage: e.message,
930
+ errorMessage: formatParsedEdsiError(parseEdsiError(e.body || e.message), {
931
+ verbose: process.env['EDSI_VERBOSE_ERRORS'] === '1',
932
+ raw: e.body,
933
+ }) || e.message,
931
934
  errorAllowCredentialRetry: true,
932
935
  });
933
936
  return;
@@ -1056,6 +1059,13 @@ export function WizardApp({ initialSpaceId = '', initialEnvironmentId = 'master'
1056
1059
  failed: items.filter((i) => i.entityType === 'DesignToken' && i.status === 'failed').length,
1057
1060
  },
1058
1061
  summary: operation.summary,
1062
+ failures: items
1063
+ .filter((item) => item.status === 'failed')
1064
+ .map((item) => ({
1065
+ entityType: item.entityType,
1066
+ entityId: item.id,
1067
+ message: formatEdsiError(item.error),
1068
+ })),
1059
1069
  };
1060
1070
  }
1061
1071
  else {
@@ -1645,7 +1655,7 @@ export function WizardApp({ initialSpaceId = '', initialEnvironmentId = 'master'
1645
1655
  case 'done': {
1646
1656
  const totalFailed = state.pushResult.componentTypes.failed + state.pushResult.designTokens.failed;
1647
1657
  const teaser = buildRunTeaserLine(state.lastRunId);
1648
- return (_jsx(DoneStep, { componentTypes: state.pushResult.componentTypes, designTokens: state.pushResult.designTokens, summary: state.pushResult.summary, spaceId: state.spaceId, environmentId: state.environmentId, host: state.host, ...(teaser ? { runTeaser: teaser } : {}), onExit: () => process.exit(totalFailed > 0 ? 1 : 0) }));
1658
+ return (_jsx(DoneStep, { componentTypes: state.pushResult.componentTypes, designTokens: state.pushResult.designTokens, summary: state.pushResult.summary, failures: state.pushResult.failures, spaceId: state.spaceId, environmentId: state.environmentId, host: state.host, ...(teaser ? { runTeaser: teaser } : {}), onExit: () => process.exit(totalFailed > 0 ? 1 : 0) }));
1649
1659
  }
1650
1660
  case 'preview-validation-error': {
1651
1661
  return (_jsx(PreviewValidationErrorStep, { errors: state.previewValidationErrors, missingNames: state.previewValidationMissingNames, onEdit: () => {
@@ -17,7 +17,12 @@ type DoneStepProps = {
17
17
  environmentId: string;
18
18
  host?: string;
19
19
  runTeaser?: string;
20
+ failures?: Array<{
21
+ entityType: string;
22
+ entityId: string;
23
+ message: string;
24
+ }>;
20
25
  onExit: () => void;
21
26
  };
22
- export declare function DoneStep({ componentTypes, designTokens, summary, spaceId, environmentId, host, runTeaser, onExit, }: DoneStepProps): React.ReactElement;
27
+ export declare function DoneStep({ componentTypes, designTokens, summary, spaceId, environmentId, host, runTeaser, failures, onExit, }: DoneStepProps): React.ReactElement;
23
28
  export {};
@@ -3,7 +3,7 @@ import { PALETTE } from '../../../analyze/select/tui/theme.js';
3
3
  import { Box, Text } from 'ink';
4
4
  import { useImmediateInput } from '../../../analyze/select/tui/hooks/useImmediateInput.js';
5
5
  import { buildPostPushUrl } from '../../../lib/contentful-urls.js';
6
- export function DoneStep({ componentTypes, designTokens, summary, spaceId, environmentId, host, runTeaser, onExit, }) {
6
+ export function DoneStep({ componentTypes, designTokens, summary, spaceId, environmentId, host, runTeaser, failures = [], onExit, }) {
7
7
  useImmediateInput((input, key) => {
8
8
  if (key.return || input === 'q' || key.escape) {
9
9
  onExit();
@@ -20,5 +20,5 @@ export function DoneStep({ componentTypes, designTokens, summary, spaceId, envir
20
20
  function EntityRows({ entity, label }) {
21
21
  return (_jsxs(_Fragment, { children: [entity.created > 0 && (_jsxs(Box, { gap: 1, children: [_jsx(Text, { color: PALETTE.success, children: "\u2713" }), _jsxs(Text, { children: [entity.created, " ", label, entity.created !== 1 ? 's' : '', " created"] })] })), entity.updated > 0 && (_jsxs(Box, { gap: 1, children: [_jsx(Text, { color: PALETTE.success, children: "\u2713" }), _jsxs(Text, { children: [entity.updated, " ", label, entity.updated !== 1 ? 's' : '', " updated"] })] })), entity.removed > 0 && (_jsxs(Box, { gap: 1, children: [_jsx(Text, { color: PALETTE.success, children: "\u2713" }), _jsxs(Text, { children: [entity.removed, " ", label, entity.removed !== 1 ? 's' : '', " removed"] })] })), entity.failed > 0 && (_jsxs(Box, { gap: 1, children: [_jsx(Text, { color: PALETTE.error, children: "\u2717" }), _jsxs(Text, { color: PALETTE.error, children: [entity.failed, " ", label, entity.failed !== 1 ? 's' : '', " failed \u2014 check logs above"] })] }))] }));
22
22
  }
23
- return (_jsxs(Box, { flexDirection: "column", gap: 1, paddingX: 2, paddingY: 1, children: [success ? (_jsx(Text, { bold: true, color: PALETTE.success, children: "Done!" })) : (_jsx(Text, { bold: true, color: PALETTE.warning, children: "\u26A0 Finished with errors" })), totalPushed === 0 && totalFailed === 0 && !summary ? (_jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Nothing was pushed \u2014 everything was already up to date." }) })) : (_jsxs(Box, { flexDirection: "column", gap: 0, marginTop: 1, children: [_jsx(EntityRows, { entity: componentTypes, label: "Component Type" }), _jsx(EntityRows, { entity: designTokens, label: "Design Token" }), summary && (_jsxs(Box, { gap: 1, marginTop: 1, children: [_jsxs(Text, { dimColor: true, children: ["Server: ", summary.succeeded, "/", summary.total, " succeeded"] }), summary.failed > 0 && _jsxs(Text, { color: PALETTE.error, children: [", ", summary.failed, " failed"] })] }))] })), _jsxs(Box, { gap: 1, marginTop: 1, children: [_jsx(Text, { dimColor: true, children: "Space:" }), _jsx(Text, { children: spaceId }), _jsx(Text, { dimColor: true, children: "/" }), _jsx(Text, { dimColor: true, children: "Environment:" }), _jsx(Text, { children: environmentId })] }), success && totalPushed > 0 && (_jsxs(Box, { flexDirection: "column", gap: 1, marginTop: 1, children: [_jsx(Text, { dimColor: true, children: "Your design system is now in Contentful ExO." }), _jsxs(Box, { flexDirection: "column", gap: 0, children: [_jsx(Text, { dimColor: true, children: "View it here:" }), _jsx(Text, { color: PALETTE.info, children: buildPostPushUrl({ host: host ?? 'api.contentful.com', spaceId, environmentId }) })] })] })), runTeaser && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: runTeaser }) })), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "[Enter / q] Exit" }) })] }));
23
+ return (_jsxs(Box, { flexDirection: "column", gap: 1, paddingX: 2, paddingY: 1, children: [success ? (_jsx(Text, { bold: true, color: PALETTE.success, children: "Done!" })) : (_jsx(Text, { bold: true, color: PALETTE.warning, children: "\u26A0 Finished with errors" })), totalPushed === 0 && totalFailed === 0 && !summary ? (_jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Nothing was pushed \u2014 everything was already up to date." }) })) : (_jsxs(Box, { flexDirection: "column", gap: 0, marginTop: 1, children: [_jsx(EntityRows, { entity: componentTypes, label: "Component Type" }), _jsx(EntityRows, { entity: designTokens, label: "Design Token" }), summary && (_jsxs(Box, { gap: 1, marginTop: 1, children: [_jsxs(Text, { dimColor: true, children: ["Server: ", summary.succeeded, "/", summary.total, " succeeded"] }), summary.failed > 0 && _jsxs(Text, { color: PALETTE.error, children: [", ", summary.failed, " failed"] })] }))] })), failures.length > 0 && (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { bold: true, color: PALETTE.error, children: "Failure details" }), failures.map((failure) => (_jsxs(Text, { color: PALETTE.error, children: [failure.entityType, " ", failure.entityId, ": ", failure.message] }, `${failure.entityType}:${failure.entityId}`)))] })), _jsxs(Box, { gap: 1, marginTop: 1, children: [_jsx(Text, { dimColor: true, children: "Space:" }), _jsx(Text, { children: spaceId }), _jsx(Text, { dimColor: true, children: "/" }), _jsx(Text, { dimColor: true, children: "Environment:" }), _jsx(Text, { children: environmentId })] }), success && totalPushed > 0 && (_jsxs(Box, { flexDirection: "column", gap: 1, marginTop: 1, children: [_jsx(Text, { dimColor: true, children: "Your design system is now in Contentful ExO." }), _jsxs(Box, { flexDirection: "column", gap: 0, children: [_jsx(Text, { dimColor: true, children: "View it here:" }), _jsx(Text, { color: PALETTE.info, children: buildPostPushUrl({ host: host ?? 'api.contentful.com', spaceId, environmentId }) })] })] })), runTeaser && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: runTeaser }) })), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "[Enter / q] Exit" }) })] }));
24
24
  }
@@ -1,9 +1,18 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { GateStep } from './GateStep.js';
3
+ import { formatParsedEdsiError } from '../../../apply/error-parser.js';
3
4
  export function PreviewValidationErrorStep({ errors, missingNames, onEdit, onSkip, onQuit, }) {
4
5
  const uniqueNames = [...new Set(errors.map((e) => e.componentName))];
5
6
  const matchedNames = uniqueNames.filter((n) => !missingNames.includes(n));
6
- const errorLines = errors.map((e) => ` ${e.componentName}: ${e.message}`).join('\n');
7
+ const errorLines = errors
8
+ .map((error) => formatParsedEdsiError({
9
+ code: null,
10
+ message: '',
11
+ cycle: null,
12
+ raw: false,
13
+ diagnostics: [{ message: error.message, component: error.componentName, path: error.path }],
14
+ }))
15
+ .join('\n');
7
16
  const missingNote = missingNames.length > 0
8
17
  ? `\n\nNote: ${missingNames.length} component name${missingNames.length === 1 ? '' : 's'} from the server (${missingNames.join(', ')}) ${missingNames.length === 1 ? 'does' : 'do'} not match anything in this session — they cannot be edited or skipped from here.`
9
18
  : '';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contentful/experience-design-system-cli",
3
- "version": "2.15.2-dev-build-a4dea7f.0",
3
+ "version": "2.15.2-dev-build-31eb989.0",
4
4
  "description": "Contentful Experiences design system import CLI",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -34,8 +34,8 @@
34
34
  "react": "^18.3.1",
35
35
  "react-devtools-core": "^4.19.1",
36
36
  "react-dom": "^18.3.1",
37
- "@contentful/experience-design-system-extraction": "2.15.2-dev-build-a4dea7f.0",
38
- "@contentful/experience-design-system-types": "2.15.2-dev-build-a4dea7f.0"
37
+ "@contentful/experience-design-system-extraction": "2.15.2-dev-build-31eb989.0",
38
+ "@contentful/experience-design-system-types": "2.15.2-dev-build-31eb989.0"
39
39
  },
40
40
  "devDependencies": {
41
41
  "@tsconfig/node24": "^24.0.3",
@@ -46,7 +46,7 @@
46
46
  "eslint-plugin-prettier": "^5.5.4",
47
47
  "ink-testing-library": "^4.0.0",
48
48
  "typescript-eslint": "^8.52.0",
49
- "vitest": "^4.1.10"
49
+ "vitest": "^4.0.16"
50
50
  },
51
51
  "repository": {
52
52
  "type": "git",