@contentful/experience-design-system-cli 2.15.2-dev-build-96d8bad.0 → 2.15.2-dev-build-ca4c411.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-96d8bad.0",
3
+ "version": "2.15.2-dev-build-ca4c411.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.0.16"
58
+ "vitest": "^4.1.10"
59
59
  },
60
60
  "repository": {
61
61
  "type": "git",
@@ -4,7 +4,6 @@ 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 { formatApiError, formatEdsiError } from '../lib/error-parser.js';
8
7
  import { openPipelineDb, loadCDFComponents } from '../session/db.js';
9
8
  import { findSlotCycles, suggestCycleBreakEdge, formatCyclePath } from '../analyze/cycle-detection.js';
10
9
  import { isEmptyPreview } from './preview-utils.js';
@@ -290,7 +289,7 @@ function buildApplyOutput(operation, spaceId, environmentId, host) {
290
289
  .map((item) => ({
291
290
  entityType: item.entityType,
292
291
  entityId: item.id,
293
- error: formatEdsiError(item.error),
292
+ error: item.error,
294
293
  })),
295
294
  };
296
295
  }
@@ -436,7 +435,7 @@ export function registerApplyCommand(program) {
436
435
  }
437
436
  catch (e) {
438
437
  if (e instanceof ApiError)
439
- die(`Error: ${formatApiError(e)}`);
438
+ die(`Error: ${e.message}`);
440
439
  throw e;
441
440
  }
442
441
  const { components, tokens, client } = inputs;
@@ -445,7 +444,7 @@ export function registerApplyCommand(program) {
445
444
  }
446
445
  catch (e) {
447
446
  if (e instanceof ApiError)
448
- die(`Error: ${formatApiError(e)}`);
447
+ die(`Error: ${e.message}`);
449
448
  const cause = e instanceof Error && e.cause instanceof Error ? e.cause.message : '';
450
449
  die(`Error: unable to connect to API host${cause ? `: ${cause}` : ''}`);
451
450
  }
@@ -456,7 +455,7 @@ export function registerApplyCommand(program) {
456
455
  }
457
456
  catch (e) {
458
457
  if (e instanceof ApiError)
459
- die(`Error: ${formatApiError(e)}`);
458
+ die(`Error: ${e.message}`);
460
459
  throw e;
461
460
  }
462
461
  const spaceId = opts.spaceId;
@@ -502,7 +501,7 @@ export function registerApplyCommand(program) {
502
501
  }
503
502
  catch (e) {
504
503
  if (e instanceof ApiError)
505
- die(`Error: ${formatApiError(e, opts.verbose)}`);
504
+ die(`Error: ${e.message}`);
506
505
  throw e;
507
506
  }
508
507
  const { components, tokens, client } = inputs;
@@ -512,7 +511,7 @@ export function registerApplyCommand(program) {
512
511
  }
513
512
  catch (e) {
514
513
  if (e instanceof ApiError)
515
- die(`Error: ${formatApiError(e, opts.verbose)}`);
514
+ die(`Error: ${e.message}`);
516
515
  throw e;
517
516
  }
518
517
  const manifest = buildManifest(components, tokens);
@@ -522,7 +521,7 @@ export function registerApplyCommand(program) {
522
521
  }
523
522
  catch (e) {
524
523
  if (e instanceof ApiError)
525
- die(`Error: ${formatApiError(e, opts.verbose)}`);
524
+ die(`Error: ${e.message}`);
526
525
  throw e;
527
526
  }
528
527
  const spaceId = opts.spaceId;
@@ -567,7 +566,7 @@ export function registerApplyCommand(program) {
567
566
  }
568
567
  catch (e) {
569
568
  if (e instanceof ApiError)
570
- die(`Error: ${formatApiError(e, opts.verbose)}`);
569
+ die(`Error: ${e.message}`);
571
570
  throw e;
572
571
  }
573
572
  process.stderr.write(`Apply operation started: ${operation.sys.id}\n`);
@@ -576,7 +575,7 @@ export function registerApplyCommand(program) {
576
575
  }
577
576
  catch (e) {
578
577
  if (e instanceof ApiError)
579
- die(`Error: ${formatApiError(e, opts.verbose)}`);
578
+ die(`Error: ${e.message}`);
580
579
  throw e;
581
580
  }
582
581
  const summary = buildApplyOutput(operation, spaceId, environmentId, opts.host);
@@ -601,7 +600,7 @@ export function registerApplyCommand(program) {
601
600
  spaceId,
602
601
  environmentId,
603
602
  status: 'error',
604
- error: formatApiError(e, opts.verbose),
603
+ error: e.message,
605
604
  }));
606
605
  return;
607
606
  }
@@ -622,7 +621,7 @@ export function registerApplyCommand(program) {
622
621
  spaceId,
623
622
  environmentId,
624
623
  status: 'error',
625
- error: formatApiError(e, opts.verbose),
624
+ error: e.message,
626
625
  }));
627
626
  return;
628
627
  }
@@ -678,7 +677,7 @@ export function registerApplyCommand(program) {
678
677
  }
679
678
  catch (e) {
680
679
  if (e instanceof ApiError)
681
- die(`Error: ${formatApiError(e)}`);
680
+ die(`Error: ${e.message}`);
682
681
  throw e;
683
682
  }
684
683
  const { components, tokens, client } = inputs;
@@ -688,7 +687,7 @@ export function registerApplyCommand(program) {
688
687
  }
689
688
  catch (e) {
690
689
  if (e instanceof ApiError)
691
- die(`Error: ${formatApiError(e)}`);
690
+ die(`Error: ${e.message}`);
692
691
  throw e;
693
692
  }
694
693
  const fullManifest = buildManifest(components, tokens);
@@ -698,7 +697,7 @@ export function registerApplyCommand(program) {
698
697
  }
699
698
  catch (e) {
700
699
  if (e instanceof ApiError)
701
- die(`Error: ${formatApiError(e)}`);
700
+ die(`Error: ${e.message}`);
702
701
  throw e;
703
702
  }
704
703
  const spaceId = opts.spaceId;
@@ -736,7 +735,7 @@ export function registerApplyCommand(program) {
736
735
  }
737
736
  catch (e) {
738
737
  if (e instanceof ApiError)
739
- die(`Error: ${formatApiError(e)}`);
738
+ die(`Error: ${e.message}`);
740
739
  throw e;
741
740
  }
742
741
  process.stderr.write(`Apply operation started: ${operation.sys.id}\n`);
@@ -745,7 +744,7 @@ export function registerApplyCommand(program) {
745
744
  }
746
745
  catch (e) {
747
746
  if (e instanceof ApiError)
748
- die(`Error: ${formatApiError(e)}`);
747
+ die(`Error: ${e.message}`);
749
748
  throw e;
750
749
  }
751
750
  const summary = buildApplyOutput(operation, spaceId, environmentId, opts.host);
@@ -782,7 +781,7 @@ export function registerApplyCommand(program) {
782
781
  spaceId,
783
782
  environmentId,
784
783
  status: 'error',
785
- error: formatApiError(e),
784
+ error: e.message,
786
785
  }));
787
786
  return;
788
787
  }
@@ -803,7 +802,7 @@ export function registerApplyCommand(program) {
803
802
  spaceId,
804
803
  environmentId,
805
804
  status: 'error',
806
- error: formatApiError(e),
805
+ error: e.message,
807
806
  }));
808
807
  return;
809
808
  }
@@ -0,0 +1,16 @@
1
+ export interface ParsedEdsiError {
2
+ /** Server-side error code, if we could extract one. */
3
+ code: string | null;
4
+ /** Human-readable message, stripped of log/trace decoration. */
5
+ message: string;
6
+ /** Cycle participants, when `code === 'TopoSortCycleError'`. */
7
+ cycle: string[] | null;
8
+ /** True when the message survived cleaning as-is (no parseable structure). */
9
+ raw: boolean;
10
+ }
11
+ export declare function stripLambdaLogPrefix(body: string): string;
12
+ export declare function parseEdsiError(rawInput: string | undefined | null): ParsedEdsiError;
13
+ export declare function formatParsedEdsiError(parsed: ParsedEdsiError, opts?: {
14
+ verbose?: boolean;
15
+ raw?: string;
16
+ }): string;
@@ -0,0 +1,117 @@
1
+ const LAMBDA_LOG_PREFIX_RE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z\s+[0-9a-f-]+\s+ERROR\s+(?:\[dd\.[^\]]*\]\s*)?/;
2
+ const DD_TAG_RE = /\[dd\.(?:trace_id|span_id)=[^\]]*\]\s*/g;
3
+ export function stripLambdaLogPrefix(body) {
4
+ let out = body.replace(LAMBDA_LOG_PREFIX_RE, '');
5
+ out = out.replace(DD_TAG_RE, '');
6
+ return out.trim();
7
+ }
8
+ function parseObjectLiteralTail(body) {
9
+ const braceStart = body.lastIndexOf('{');
10
+ if (braceStart === -1)
11
+ return null;
12
+ const tail = body.slice(braceStart);
13
+ const codeMatch = tail.match(/code:\s*['"]([^'"]+)['"]/);
14
+ const cycleMatch = tail.match(/cycle:\s*\[\s*([^\]]*)\s*\]/);
15
+ if (!codeMatch && !cycleMatch)
16
+ return null;
17
+ const code = codeMatch ? codeMatch[1] : null;
18
+ let cycle = null;
19
+ if (cycleMatch) {
20
+ cycle = cycleMatch[1]
21
+ .split(',')
22
+ .map((s) => s.trim().replace(/^['"]|['"]$/g, ''))
23
+ .filter((s) => s.length > 0);
24
+ if (cycle.length === 0)
25
+ cycle = null;
26
+ }
27
+ return { code, cycle };
28
+ }
29
+ function parseJsonBody(body) {
30
+ let parsed;
31
+ try {
32
+ parsed = JSON.parse(body);
33
+ }
34
+ catch {
35
+ return null;
36
+ }
37
+ if (!parsed || typeof parsed !== 'object')
38
+ return null;
39
+ const p = parsed;
40
+ const details = (p.details && typeof p.details === 'object' ? p.details : {});
41
+ const pick = (k) => (p[k] !== undefined ? p[k] : details[k]);
42
+ const codeRaw = pick('code');
43
+ const messageRaw = pick('message');
44
+ const cycleRaw = pick('cycle');
45
+ const out = {};
46
+ if (typeof codeRaw === 'string')
47
+ out.code = codeRaw;
48
+ if (typeof messageRaw === 'string')
49
+ out.message = messageRaw;
50
+ if (Array.isArray(cycleRaw)) {
51
+ const strs = cycleRaw.filter((x) => typeof x === 'string');
52
+ if (strs.length > 0)
53
+ out.cycle = strs;
54
+ }
55
+ return out;
56
+ }
57
+ // ApiError.message shape: `${phasePrefix}\n${body}` where phasePrefix looks
58
+ // like `apply failed: 400`, `preview failed: 422`, `poll failed: 500`. We
59
+ // only strip the prefix line when the first line matches this shape —
60
+ // otherwise a raw body that happens to contain a newline (e.g. a Lambda log
61
+ // spill with a multi-line object literal) gets truncated mid-structure.
62
+ const API_ERROR_PREFIX_RE = /^(?:apply|preview|poll) failed: \d+$/;
63
+ export function parseEdsiError(rawInput) {
64
+ if (!rawInput)
65
+ return { code: null, message: '', cycle: null, raw: true };
66
+ const nlIndex = rawInput.indexOf('\n');
67
+ let body = rawInput;
68
+ let prefix = '';
69
+ if (nlIndex !== -1) {
70
+ const firstLine = rawInput.slice(0, nlIndex);
71
+ if (API_ERROR_PREFIX_RE.test(firstLine)) {
72
+ prefix = firstLine;
73
+ body = rawInput.slice(nlIndex + 1);
74
+ }
75
+ }
76
+ const cleaned = stripLambdaLogPrefix(body);
77
+ const json = parseJsonBody(cleaned) ?? parseJsonBody(body);
78
+ if (json && (json.code || json.message || json.cycle)) {
79
+ return {
80
+ code: json.code ?? null,
81
+ message: json.message ?? (cleaned || prefix),
82
+ cycle: json.cycle ?? null,
83
+ raw: false,
84
+ };
85
+ }
86
+ const literal = parseObjectLiteralTail(cleaned);
87
+ if (literal && (literal.code || literal.cycle)) {
88
+ const braceStart = cleaned.lastIndexOf('{');
89
+ const head = braceStart === -1 ? cleaned : cleaned.slice(0, braceStart).trim();
90
+ return {
91
+ code: literal.code ?? null,
92
+ message: head || cleaned,
93
+ cycle: literal.cycle ?? null,
94
+ raw: false,
95
+ };
96
+ }
97
+ return { code: null, message: cleaned || prefix || rawInput, cycle: null, raw: true };
98
+ }
99
+ export function formatParsedEdsiError(parsed, opts = {}) {
100
+ const lines = [];
101
+ if (parsed.code) {
102
+ lines.push(`[${parsed.code}]`);
103
+ }
104
+ if (parsed.message) {
105
+ lines.push(parsed.message);
106
+ }
107
+ if (parsed.cycle && parsed.cycle.length > 0) {
108
+ lines.push(`Cycle: ${parsed.cycle.join(' → ')} → ${parsed.cycle[0]}`);
109
+ lines.push('Break the cycle by removing at least one $allowedComponents entry.');
110
+ }
111
+ if (opts.verbose && opts.raw) {
112
+ lines.push('');
113
+ lines.push('--- raw ---');
114
+ lines.push(opts.raw);
115
+ }
116
+ return lines.filter(Boolean).join('\n');
117
+ }
@@ -2,7 +2,6 @@ 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 '../../lib/error-parser.js';
6
5
  export function ServerPreviewConfirm({ preview, spaceId, environmentId, breakingWithImpact, onConfirm, onCancel, }) {
7
6
  useInput((input, key) => {
8
7
  if (key.return)
@@ -36,5 +35,9 @@ export function ServerApplyDone({ operation, spaceId, environmentId, host }) {
36
35
  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." })] }));
37
36
  }
38
37
  function formatItemError(error) {
39
- return formatEdsiError(error);
38
+ if (!error)
39
+ return '';
40
+ if (typeof error === 'string')
41
+ return error;
42
+ return `${error.code}: ${error.message}`;
40
43
  }
@@ -61,20 +61,16 @@ export function parseCycleComponentNames(report) {
61
61
  return [...new Set(names)];
62
62
  }
63
63
  export function isPreviewValidationError(result) {
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]');
64
+ return (result.exitCode !== 0 &&
65
+ result.stderr.includes(`${PREVIEW_ERROR_PREFIX} 422`) &&
66
+ result.stderr.includes(VALIDATION_FAILED_CODE));
67
67
  }
68
68
  export function parseOffendingComponentNames(output) {
69
69
  const jsonStart = output.indexOf('{');
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)];
70
+ if (jsonStart === -1)
71
+ return [];
72
+ const errors = parsePreviewValidationErrors(output.slice(jsonStart));
73
+ return [...new Set(errors.map((e) => e.componentName))];
78
74
  }
79
75
  export function buildPushStepResult(args) {
80
76
  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, formatEdsiError, formatParsedEdsiError } from '../../lib/error-parser.js';
40
+ import { parseEdsiError, 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,10 +927,7 @@ export function WizardApp({ initialSpaceId = '', initialEnvironmentId = 'master'
927
927
  update({
928
928
  step: 'error',
929
929
  errorStep: 'apply preview',
930
- errorMessage: formatParsedEdsiError(parseEdsiError(e.body || e.message), {
931
- verbose: process.env['EDSI_VERBOSE_ERRORS'] === '1',
932
- raw: e.body,
933
- }) || e.message,
930
+ errorMessage: e.message,
934
931
  errorAllowCredentialRetry: true,
935
932
  });
936
933
  return;
@@ -1059,13 +1056,6 @@ export function WizardApp({ initialSpaceId = '', initialEnvironmentId = 'master'
1059
1056
  failed: items.filter((i) => i.entityType === 'DesignToken' && i.status === 'failed').length,
1060
1057
  },
1061
1058
  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
- })),
1069
1059
  };
1070
1060
  }
1071
1061
  else {
@@ -1655,7 +1645,7 @@ export function WizardApp({ initialSpaceId = '', initialEnvironmentId = 'master'
1655
1645
  case 'done': {
1656
1646
  const totalFailed = state.pushResult.componentTypes.failed + state.pushResult.designTokens.failed;
1657
1647
  const teaser = buildRunTeaserLine(state.lastRunId);
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) }));
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) }));
1659
1649
  }
1660
1650
  case 'preview-validation-error': {
1661
1651
  return (_jsx(PreviewValidationErrorStep, { errors: state.previewValidationErrors, missingNames: state.previewValidationMissingNames, onEdit: () => {
@@ -17,12 +17,7 @@ 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
- }>;
25
20
  onExit: () => void;
26
21
  };
27
- export declare function DoneStep({ componentTypes, designTokens, summary, spaceId, environmentId, host, runTeaser, failures, onExit, }: DoneStepProps): React.ReactElement;
22
+ export declare function DoneStep({ componentTypes, designTokens, summary, spaceId, environmentId, host, runTeaser, onExit, }: DoneStepProps): React.ReactElement;
28
23
  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, failures = [], onExit, }) {
6
+ export function DoneStep({ componentTypes, designTokens, summary, spaceId, environmentId, host, runTeaser, 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"] })] }))] })), 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" }) })] }));
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" }) })] }));
24
24
  }
@@ -1,18 +1,9 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { GateStep } from './GateStep.js';
3
- import { formatParsedEdsiError } from '../../../lib/error-parser.js';
4
3
  export function PreviewValidationErrorStep({ errors, missingNames, onEdit, onSkip, onQuit, }) {
5
4
  const uniqueNames = [...new Set(errors.map((e) => e.componentName))];
6
5
  const matchedNames = uniqueNames.filter((n) => !missingNames.includes(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');
6
+ const errorLines = errors.map((e) => ` ${e.componentName}: ${e.message}`).join('\n');
16
7
  const missingNote = missingNames.length > 0
17
8
  ? `\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.`
18
9
  : '';
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-96d8bad.0",
3
+ "version": "2.15.2-dev-build-ca4c411.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-96d8bad.0",
38
- "@contentful/experience-design-system-types": "2.15.2-dev-build-96d8bad.0"
37
+ "@contentful/experience-design-system-types": "2.15.2-dev-build-ca4c411.0",
38
+ "@contentful/experience-design-system-extraction": "2.15.2-dev-build-ca4c411.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.0.16"
49
+ "vitest": "^4.1.10"
50
50
  },
51
51
  "repository": {
52
52
  "type": "git",
@@ -1,38 +0,0 @@
1
- export interface ParsedEdsiError {
2
- /** Server-side error code, if we could extract one. */
3
- code: string | null;
4
- /** Human-readable message, stripped of log/trace decoration. */
5
- message: string;
6
- /** Cycle participants, when `code === 'TopoSortCycleError'`. */
7
- cycle: string[] | null;
8
- /** True when the message survived cleaning as-is (no parseable structure). */
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
- /**
17
- * A null path means that the API supplied a location but it contained no
18
- * usable segments. An undefined path means the API did not provide one.
19
- */
20
- path?: string | null;
21
- /** The API field that supplied the diagnostic message, for verbose output. */
22
- messageSource?: 'message' | 'details' | 'error' | 'name';
23
- }
24
- export interface ApiErrorLike {
25
- body?: string;
26
- message: string;
27
- }
28
- export declare function stripLambdaLogPrefix(body: string): string;
29
- export declare function parseEdsiError(rawInput: string | undefined | null): ParsedEdsiError;
30
- export declare function formatParsedEdsiError(parsed: ParsedEdsiError, opts?: {
31
- verbose?: boolean;
32
- raw?: string;
33
- }): string;
34
- export declare function formatEdsiError(raw: unknown, opts?: {
35
- verbose?: boolean;
36
- raw?: string;
37
- }): string;
38
- export declare function formatApiError(error: ApiErrorLike, verbose?: boolean): string;
@@ -1,264 +0,0 @@
1
- const LAMBDA_LOG_PREFIX_RE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z\s+[0-9a-f-]+\s+ERROR\s+(?:\[dd\.[^\]]*\]\s*)?/;
2
- const DD_TAG_RE = /\[dd\.(?:trace_id|span_id)=[^\]]*\]\s*/g;
3
- export function stripLambdaLogPrefix(body) {
4
- let out = body.replace(LAMBDA_LOG_PREFIX_RE, '');
5
- out = out.replace(DD_TAG_RE, '');
6
- return out.trim();
7
- }
8
- function parseObjectLiteralTail(body) {
9
- const braceStart = body.lastIndexOf('{');
10
- if (braceStart === -1)
11
- return null;
12
- const tail = body.slice(braceStart);
13
- const codeMatch = tail.match(/code:\s*['"]([^'"]+)['"]/);
14
- const cycleMatch = tail.match(/cycle:\s*\[\s*([^\]]*)\s*\]/);
15
- if (!codeMatch && !cycleMatch)
16
- return null;
17
- const code = codeMatch ? codeMatch[1] : null;
18
- let cycle = null;
19
- if (cycleMatch) {
20
- cycle = cycleMatch[1]
21
- .split(',')
22
- .map((s) => s.trim().replace(/^['"]|['"]$/g, ''))
23
- .filter((s) => s.length > 0);
24
- if (cycle.length === 0)
25
- cycle = null;
26
- }
27
- return { code, cycle };
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 isPathSegment(value) {
49
- return typeof value === 'string' || (typeof value === 'number' && Number.isFinite(value));
50
- }
51
- function formatPath(value) {
52
- if (isPathSegment(value))
53
- return String(value) ? { path: String(value) } : { path: null };
54
- if (Array.isArray(value)) {
55
- const parts = value.filter(isPathSegment);
56
- return parts.length > 0 ? { path: parts.join(' › ') } : { path: null };
57
- }
58
- return {};
59
- }
60
- function componentFromPath(path) {
61
- if (!path)
62
- return undefined;
63
- const manifestMatch = path.match(/manifest:components\/([^/›]+)/);
64
- if (manifestMatch?.[1])
65
- return manifestMatch[1];
66
- const parts = path
67
- .split(/[›/]/)
68
- .map((part) => part.trim())
69
- .filter(Boolean);
70
- const componentsIndex = parts.findIndex((part) => part === 'components');
71
- return componentsIndex === -1 ? undefined : parts[componentsIndex + 1];
72
- }
73
- function diagnosticMessage(error) {
74
- const candidates = [
75
- ['message', error.message],
76
- ['details', error.details],
77
- ['error', error.error],
78
- ['name', error.name],
79
- ];
80
- for (const [messageSource, value] of candidates) {
81
- const message = displayValue(value);
82
- if (message !== null)
83
- return { message, messageSource };
84
- }
85
- return { message: 'Validation failed' };
86
- }
87
- function parseValidationDiagnostics(details) {
88
- if (!Array.isArray(details.errors))
89
- return [];
90
- const diagnostics = [];
91
- for (const rawError of details.errors) {
92
- const error = asRecord(rawError);
93
- if (!error)
94
- continue;
95
- const location = formatPath(error.path);
96
- const component = displayValue(error.component ?? error.componentName ?? error.componentId) ?? componentFromPath(location.path);
97
- diagnostics.push({ ...diagnosticMessage(error), ...location, ...(component ? { component } : {}) });
98
- }
99
- return diagnostics;
100
- }
101
- function parseJsonBody(body) {
102
- let parsed;
103
- try {
104
- parsed = JSON.parse(body);
105
- }
106
- catch {
107
- return null;
108
- }
109
- const p = asRecord(parsed);
110
- if (!p)
111
- return null;
112
- const details = asRecord(p.details) ?? {};
113
- const pick = (k) => (p[k] !== undefined ? p[k] : details[k]);
114
- const codeRaw = pick('code') ?? asRecord(p.sys)?.id;
115
- const messageRaw = pick('message');
116
- const cycleRaw = pick('cycle');
117
- const out = {};
118
- if (typeof codeRaw === 'string')
119
- out.code = codeRaw;
120
- if (typeof messageRaw === 'string')
121
- out.message = messageRaw;
122
- if (Array.isArray(cycleRaw)) {
123
- const strs = cycleRaw.filter((x) => typeof x === 'string');
124
- if (strs.length > 0)
125
- out.cycle = strs;
126
- }
127
- const diagnostics = parseValidationDiagnostics(details);
128
- if (diagnostics.length > 0)
129
- out.diagnostics = diagnostics;
130
- return out;
131
- }
132
- function parseBindingDiagnostics(body) {
133
- if (!/binding configurations are invalid|Pointer path does not exist/i.test(body))
134
- return null;
135
- const locationMatches = [...body.matchAll(/(?:^|\.\s)([A-Za-z0-9_$-]+(?:\s*›\s*[A-Za-z0-9_$-]+){2,})/g)];
136
- const location = locationMatches.at(-1)?.[1];
137
- const pointerMessage = body.match(/Pointer path does not exist for '\[object Object\]':\s*(.+?)(?=\.\s*Default\s*›|$)/i)?.[1];
138
- const graphQlMessage = body.match(/return GraphQL validation error:\s*(.+?)(?=\.\s*Default\s*›|$)/i)?.[1];
139
- const heading = body.match(/^(.*?)(?=\.?\s*Pointer path does not exist|\.?\s*Default\s*›)/i)?.[1]?.trim();
140
- const diagnostics = [];
141
- if (pointerMessage)
142
- diagnostics.push({ message: pointerMessage.trim(), ...(location ? { path: location } : {}) });
143
- if (graphQlMessage)
144
- diagnostics.push({ message: graphQlMessage.trim(), ...(location ? { path: location } : {}) });
145
- return {
146
- code: 'BindingValidationFailed',
147
- message: heading || 'One or more binding configurations are invalid.',
148
- ...(diagnostics.length > 0 ? { diagnostics } : {}),
149
- };
150
- }
151
- // ApiError.message shape: `${phasePrefix}\n${body}` where phasePrefix looks
152
- // like `apply failed: 400`, `preview failed: 422`, `poll failed: 500`. We
153
- // only strip the prefix line when the first line matches this shape —
154
- // otherwise a raw body that happens to contain a newline (e.g. a Lambda log
155
- // spill with a multi-line object literal) gets truncated mid-structure.
156
- const API_ERROR_PREFIX_RE = /^(?:apply|preview|poll) failed: \d+$/;
157
- export function parseEdsiError(rawInput) {
158
- if (!rawInput)
159
- return { code: null, message: '', cycle: null, raw: true };
160
- const nlIndex = rawInput.indexOf('\n');
161
- let body = rawInput;
162
- let prefix = '';
163
- if (nlIndex !== -1) {
164
- const firstLine = rawInput.slice(0, nlIndex);
165
- if (API_ERROR_PREFIX_RE.test(firstLine)) {
166
- prefix = firstLine;
167
- body = rawInput.slice(nlIndex + 1);
168
- }
169
- }
170
- const cleaned = stripLambdaLogPrefix(body);
171
- const json = parseJsonBody(cleaned) ?? parseJsonBody(body);
172
- const bindingFromJson = typeof json?.message === 'string' ? parseBindingDiagnostics(json.message) : null;
173
- if (bindingFromJson) {
174
- return {
175
- code: bindingFromJson.code ?? json?.code ?? null,
176
- message: bindingFromJson.message ?? cleaned,
177
- cycle: null,
178
- raw: false,
179
- ...(bindingFromJson.diagnostics ? { diagnostics: bindingFromJson.diagnostics } : {}),
180
- };
181
- }
182
- if (json && (json.code || json.message || json.cycle)) {
183
- return {
184
- code: json.code ?? null,
185
- message: json.message ?? (cleaned || prefix),
186
- cycle: json.cycle ?? null,
187
- raw: false,
188
- ...(json.diagnostics ? { diagnostics: json.diagnostics } : {}),
189
- };
190
- }
191
- const binding = parseBindingDiagnostics(cleaned);
192
- if (binding) {
193
- return {
194
- code: binding.code ?? null,
195
- message: binding.message ?? cleaned,
196
- cycle: null,
197
- raw: false,
198
- ...(binding.diagnostics ? { diagnostics: binding.diagnostics } : {}),
199
- };
200
- }
201
- const literal = parseObjectLiteralTail(cleaned);
202
- if (literal && (literal.code || literal.cycle)) {
203
- const braceStart = cleaned.lastIndexOf('{');
204
- const head = braceStart === -1 ? cleaned : cleaned.slice(0, braceStart).trim();
205
- return {
206
- code: literal.code ?? null,
207
- message: head || cleaned,
208
- cycle: literal.cycle ?? null,
209
- raw: false,
210
- };
211
- }
212
- return { code: null, message: cleaned || prefix || rawInput, cycle: null, raw: true };
213
- }
214
- export function formatParsedEdsiError(parsed, opts = {}) {
215
- const lines = [];
216
- if (parsed.code) {
217
- lines.push(`[${parsed.code}]`);
218
- }
219
- if (parsed.message) {
220
- lines.push(parsed.message);
221
- }
222
- for (const diagnostic of parsed.diagnostics ?? []) {
223
- const context = [
224
- diagnostic.component ? `Component: ${diagnostic.component}` : null,
225
- diagnostic.path ? `Path: ${diagnostic.path}` : null,
226
- ].filter(Boolean);
227
- lines.push(`- ${diagnostic.message}${context.length > 0 ? ` (${context.join('; ')})` : ''}`);
228
- if (diagnostic.path === null) {
229
- lines.push(' Location: not provided by the server. Review the submitted manifest; no component or field was identified.');
230
- }
231
- if (opts.verbose && diagnostic.messageSource) {
232
- lines.push(` Message field: ${diagnostic.messageSource}`);
233
- }
234
- }
235
- if (parsed.code === 'BindingValidationFailed') {
236
- const location = parsed.diagnostics?.find((diagnostic) => diagnostic.path)?.path;
237
- lines.push(location
238
- ? `Next action: review the binding at ${location} and correct its pointer or GraphQL selection.`
239
- : 'Next action: review the binding pointer and GraphQL selection.');
240
- }
241
- if (parsed.cycle && parsed.cycle.length > 0) {
242
- lines.push(`Cycle: ${parsed.cycle.join(' → ')} → ${parsed.cycle[0]}`);
243
- lines.push('Break the cycle by removing at least one $allowedComponents entry.');
244
- }
245
- if (opts.verbose && opts.raw) {
246
- lines.push('');
247
- lines.push('--- raw ---');
248
- lines.push(opts.raw);
249
- }
250
- return lines.filter(Boolean).join('\n');
251
- }
252
- export function formatEdsiError(raw, opts = {}) {
253
- const input = typeof raw === 'string' ? raw : displayValue(raw);
254
- if (!input)
255
- return '';
256
- return formatParsedEdsiError(parseEdsiError(input), { ...opts, raw: opts.raw ?? input });
257
- }
258
- export function formatApiError(error, verbose = false) {
259
- const formatted = formatEdsiError(error.body || error.message, { verbose, raw: error.body }) || error.message;
260
- const phase = error.message.split('\n', 1)[0];
261
- return /^(?:apply|preview|poll) failed: \d+$/.test(phase) && formatted !== phase
262
- ? `${phase}\n${formatted}`
263
- : formatted;
264
- }