@contentful/experience-design-system-cli 2.15.2-dev-build-bdc75c4.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 +2 -2
- package/dist/src/apply/command.js +18 -19
- package/dist/src/apply/error-parser.d.ts +16 -0
- package/dist/src/apply/error-parser.js +117 -0
- package/dist/src/apply/tui/ServerApplyView.js +5 -2
- package/dist/src/import/orchestrator.js +7 -11
- package/dist/src/import/tui/WizardApp.js +3 -13
- package/dist/src/import/tui/steps/DoneStep.d.ts +1 -6
- package/dist/src/import/tui/steps/DoneStep.js +2 -2
- package/dist/src/import/tui/steps/PreviewValidationErrorStep.js +1 -10
- package/package.json +4 -4
- package/dist/src/lib/error-parser.d.ts +0 -38
- package/dist/src/lib/error-parser.js +0 -264
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-
|
|
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.
|
|
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:
|
|
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: ${
|
|
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: ${
|
|
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: ${
|
|
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: ${
|
|
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: ${
|
|
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: ${
|
|
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: ${
|
|
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: ${
|
|
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:
|
|
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:
|
|
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: ${
|
|
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: ${
|
|
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: ${
|
|
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: ${
|
|
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: ${
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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,
|
|
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:
|
|
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,
|
|
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,
|
|
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,
|
|
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"] })] }))] })),
|
|
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-
|
|
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-
|
|
38
|
-
"@contentful/experience-design-system-
|
|
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.
|
|
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
|
-
}
|