@contentful/experience-design-system-cli 2.12.4-dev-build-ff57340.0 → 2.13.1-dev-build-847dead.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 +3 -6
- package/dist/src/analyze/command.js +2 -22
- package/dist/src/analyze/select/command.js +1 -1
- package/dist/src/analyze/select/tui/components/FieldEditor.d.ts +1 -24
- package/dist/src/analyze/select/tui/components/FieldEditor.js +10 -137
- package/dist/src/analyze/select-agent/command.js +1 -1
- package/dist/src/apply/command.d.ts +1 -34
- package/dist/src/apply/command.js +0 -81
- package/dist/src/import/tui/WizardApp.js +10 -59
- package/dist/src/import/tui/steps/GenerateReviewStep.d.ts +1 -1
- package/dist/src/import/tui/steps/GenerateReviewStep.js +23 -213
- package/dist/src/import/tui/steps/WizardPreviewStep.d.ts +0 -10
- package/dist/src/import/tui/steps/WizardPreviewStep.js +51 -95
- package/dist/src/import/tui/steps/preview-diff.js +0 -48
- package/dist/src/session/db.d.ts +0 -9
- package/dist/src/session/db.js +0 -46
- package/dist/src/types.d.ts +2 -76
- package/dist/src/types.js +1 -4
- package/package.json +3 -6
- package/dist/src/analyze/cycle-detection.d.ts +0 -93
- package/dist/src/analyze/cycle-detection.js +0 -326
- package/dist/src/analyze/extract/astro.d.ts +0 -5
- package/dist/src/analyze/extract/astro.js +0 -289
- package/dist/src/analyze/extract/non-authorable-filter.d.ts +0 -16
- package/dist/src/analyze/extract/non-authorable-filter.js +0 -60
- package/dist/src/analyze/extract/pipeline.d.ts +0 -6
- package/dist/src/analyze/extract/pipeline.js +0 -314
- package/dist/src/analyze/extract/react.d.ts +0 -2
- package/dist/src/analyze/extract/react.js +0 -2041
- package/dist/src/analyze/extract/scoring.d.ts +0 -12
- package/dist/src/analyze/extract/scoring.js +0 -108
- package/dist/src/analyze/extract/slot-allowed-components.d.ts +0 -8
- package/dist/src/analyze/extract/slot-allowed-components.js +0 -40
- package/dist/src/analyze/extract/slot-detection.d.ts +0 -35
- package/dist/src/analyze/extract/slot-detection.js +0 -101
- package/dist/src/analyze/extract/source-inspection.d.ts +0 -13
- package/dist/src/analyze/extract/source-inspection.js +0 -189
- package/dist/src/analyze/extract/stencil.d.ts +0 -2
- package/dist/src/analyze/extract/stencil.js +0 -296
- package/dist/src/analyze/extract/svelte.d.ts +0 -5
- package/dist/src/analyze/extract/svelte.js +0 -1626
- package/dist/src/analyze/extract/tsx-shared.d.ts +0 -8
- package/dist/src/analyze/extract/tsx-shared.js +0 -263
- package/dist/src/analyze/extract/validate.d.ts +0 -16
- package/dist/src/analyze/extract/validate.js +0 -94
- package/dist/src/analyze/extract/vue-tsx.d.ts +0 -2
- package/dist/src/analyze/extract/vue-tsx.js +0 -498
- package/dist/src/analyze/extract/vue.d.ts +0 -5
- package/dist/src/analyze/extract/vue.js +0 -653
- package/dist/src/analyze/extract/web-components.d.ts +0 -2
- package/dist/src/analyze/extract/web-components.js +0 -876
- package/dist/src/analyze/pre-classify.d.ts +0 -17
- package/dist/src/analyze/pre-classify.js +0 -211
- package/dist/src/apply/error-parser.d.ts +0 -43
- package/dist/src/apply/error-parser.js +0 -163
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import type { RawPropDefinition, RawComponentDefinition } from '../types.js';
|
|
2
|
-
export interface PreClassification {
|
|
3
|
-
category: 'content' | 'design' | 'state' | 'exclude';
|
|
4
|
-
cdfTypeHint?: 'string' | 'enum' | 'richtext' | 'media' | 'boolean';
|
|
5
|
-
}
|
|
6
|
-
/**
|
|
7
|
-
* Deterministic pre-classification rule engine.
|
|
8
|
-
* Applies rules in priority order and returns on the first match.
|
|
9
|
-
*/
|
|
10
|
-
export declare function preClassifyProp(prop: RawPropDefinition): PreClassification | undefined;
|
|
11
|
-
/**
|
|
12
|
-
* Applies pre-classification to all props in a component definition.
|
|
13
|
-
* - Leaves existing category values unchanged
|
|
14
|
-
* - Sets category for content/design/state matches
|
|
15
|
-
* - Does NOT set category for 'exclude' results (leaves undefined)
|
|
16
|
-
*/
|
|
17
|
-
export declare function preClassifyComponent(component: RawComponentDefinition): RawComponentDefinition;
|
|
@@ -1,211 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Determines whether a type string represents a simple/primitive type
|
|
3
|
-
* (string, boolean, number, or string literal union) vs a complex type
|
|
4
|
-
* (object, function, array, generic, etc.).
|
|
5
|
-
*/
|
|
6
|
-
function isSimpleType(type) {
|
|
7
|
-
const t = type.trim();
|
|
8
|
-
if (t === 'string' || t === 'boolean' || t === 'number')
|
|
9
|
-
return true;
|
|
10
|
-
// String literal union (e.g., "'a' | 'b'")
|
|
11
|
-
if (isStringLiteralUnion(t))
|
|
12
|
-
return true;
|
|
13
|
-
return false;
|
|
14
|
-
}
|
|
15
|
-
function isStringLiteralUnion(type) {
|
|
16
|
-
return type.includes('|') && type.includes("'");
|
|
17
|
-
}
|
|
18
|
-
function isBooleanType(type) {
|
|
19
|
-
return type.trim() === 'boolean';
|
|
20
|
-
}
|
|
21
|
-
function isStringType(type) {
|
|
22
|
-
return type.trim() === 'string';
|
|
23
|
-
}
|
|
24
|
-
function isNumberType(type) {
|
|
25
|
-
return type.trim() === 'number';
|
|
26
|
-
}
|
|
27
|
-
function isComplexType(type) {
|
|
28
|
-
return !isSimpleType(type);
|
|
29
|
-
}
|
|
30
|
-
const DOM_PASS_THROUGH_PROPS = new Set([
|
|
31
|
-
// Bare HTML / framework styling pass-through
|
|
32
|
-
'className',
|
|
33
|
-
'class',
|
|
34
|
-
'classes',
|
|
35
|
-
'classNames',
|
|
36
|
-
'rootClassName',
|
|
37
|
-
'prefixCls',
|
|
38
|
-
'style',
|
|
39
|
-
'styles',
|
|
40
|
-
// Bare HTML attributes
|
|
41
|
-
'id',
|
|
42
|
-
'role',
|
|
43
|
-
'tabIndex',
|
|
44
|
-
'tabindex',
|
|
45
|
-
'name',
|
|
46
|
-
'htmlFor',
|
|
47
|
-
'for',
|
|
48
|
-
'slot',
|
|
49
|
-
'is',
|
|
50
|
-
'lang',
|
|
51
|
-
'dir',
|
|
52
|
-
'hidden',
|
|
53
|
-
'draggable',
|
|
54
|
-
'spellCheck',
|
|
55
|
-
'spellcheck',
|
|
56
|
-
'contentEditable',
|
|
57
|
-
'contenteditable',
|
|
58
|
-
'inputMode',
|
|
59
|
-
'inputmode',
|
|
60
|
-
'autoComplete',
|
|
61
|
-
'autocomplete',
|
|
62
|
-
'autoFocus',
|
|
63
|
-
'autofocus',
|
|
64
|
-
'translate',
|
|
65
|
-
'part',
|
|
66
|
-
'exportparts',
|
|
67
|
-
'aria',
|
|
68
|
-
// Framework theming / pass-through escape hatches — dev-facing, never marketer-configurable
|
|
69
|
-
'dt',
|
|
70
|
-
'pt',
|
|
71
|
-
'ptOptions',
|
|
72
|
-
'unstyled',
|
|
73
|
-
// Polymorphic component props — change rendered HTML/component, not marketer-visible behavior
|
|
74
|
-
'as',
|
|
75
|
-
'element',
|
|
76
|
-
'component',
|
|
77
|
-
// QA / vendor test attributes
|
|
78
|
-
'dataQa',
|
|
79
|
-
'data-qa',
|
|
80
|
-
// Vue v-model internals — framework wiring, never marketer-configurable
|
|
81
|
-
'modelValue',
|
|
82
|
-
'modelModifiers',
|
|
83
|
-
]);
|
|
84
|
-
function isDomPassThroughProp(name) {
|
|
85
|
-
if (DOM_PASS_THROUGH_PROPS.has(name))
|
|
86
|
-
return true;
|
|
87
|
-
// aria-label, aria-hidden, ariaLabel, ariaHidden — both kebab and camel forms
|
|
88
|
-
if (/^aria[-A-Z]/.test(name))
|
|
89
|
-
return true;
|
|
90
|
-
if (name.startsWith('data-'))
|
|
91
|
-
return true;
|
|
92
|
-
return false;
|
|
93
|
-
}
|
|
94
|
-
/**
|
|
95
|
-
* Deterministic pre-classification rule engine.
|
|
96
|
-
* Applies rules in priority order and returns on the first match.
|
|
97
|
-
*/
|
|
98
|
-
export function preClassifyProp(prop) {
|
|
99
|
-
const { name, type } = prop;
|
|
100
|
-
// Rule 1: Event handlers
|
|
101
|
-
// name starts with `on` + uppercase, OR type contains `=> void` or `EventHandler`
|
|
102
|
-
if (/^on[A-Z]/.test(name) || type.includes('=> void') || type.includes('EventHandler')) {
|
|
103
|
-
return { category: 'exclude' };
|
|
104
|
-
}
|
|
105
|
-
// Rule 2: Refs
|
|
106
|
-
if (name === 'ref' || name === 'innerRef' || type.includes('Ref<') || type.includes('RefObject<')) {
|
|
107
|
-
return { category: 'exclude' };
|
|
108
|
-
}
|
|
109
|
-
// Rule 3: Test IDs
|
|
110
|
-
if (name === 'testId' || name === 'data-testid' || name === 'dataTestId') {
|
|
111
|
-
return { category: 'exclude' };
|
|
112
|
-
}
|
|
113
|
-
// Rule 4: Key prop
|
|
114
|
-
if (name === 'key') {
|
|
115
|
-
return { category: 'exclude' };
|
|
116
|
-
}
|
|
117
|
-
// Rule 5: Dispatch/setter
|
|
118
|
-
if (type.includes('Dispatch<') || type.includes('SetStateAction')) {
|
|
119
|
-
return { category: 'exclude' };
|
|
120
|
-
}
|
|
121
|
-
// Rule 6: DOM / a11y / framework pass-through props
|
|
122
|
-
// These are escape hatches developers use to wire components into the DOM.
|
|
123
|
-
// Marketers should never configure them in the ExO editor; exposing them
|
|
124
|
-
// generates noise that obscures the props that actually carry intent.
|
|
125
|
-
if (isDomPassThroughProp(name)) {
|
|
126
|
-
return { category: 'exclude' };
|
|
127
|
-
}
|
|
128
|
-
// Rule 7: String literal union
|
|
129
|
-
if (isStringLiteralUnion(type)) {
|
|
130
|
-
return { category: 'design', cdfTypeHint: 'enum' };
|
|
131
|
-
}
|
|
132
|
-
// Rule 8: Design name patterns (only for simple types)
|
|
133
|
-
if (!isComplexType(type)) {
|
|
134
|
-
const designNameStart = /^(variant|size|spacing|gap|color|bg|theme|align|layout|orientation|position)/i;
|
|
135
|
-
const designNameEnd = /(Color|Size|Variant|Style)$/;
|
|
136
|
-
if (designNameStart.test(name) || designNameEnd.test(name)) {
|
|
137
|
-
return { category: 'design', cdfTypeHint: 'string' };
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
// Rule 10: Boolean + state names (checked before rule 9 since state names
|
|
141
|
-
// like "disabled" would otherwise match the visual toggle prefix "disable")
|
|
142
|
-
if (isBooleanType(type)) {
|
|
143
|
-
const stateNames = ['disabled', 'loading', 'expanded', 'isOpen', 'selected', 'checked', 'active', 'preview'];
|
|
144
|
-
if (stateNames.includes(name)) {
|
|
145
|
-
return { category: 'state', cdfTypeHint: 'boolean' };
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
// Rule 9: Boolean + visual toggle name
|
|
149
|
-
if (isBooleanType(type)) {
|
|
150
|
-
const visualToggle = /^(hide|show|enable|disable|vertical|horizontal|reverse|bold|italic|imageOn|with)/i;
|
|
151
|
-
if (visualToggle.test(name)) {
|
|
152
|
-
return { category: 'design', cdfTypeHint: 'boolean' };
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
// Rule 11: State identifiers
|
|
156
|
-
if (name === 'componentId' || name === 'sectionKey' || name === 'locale' || name === 'variantIndex') {
|
|
157
|
-
return { category: 'state', cdfTypeHint: 'string' };
|
|
158
|
-
}
|
|
159
|
-
// Rule 12: URL patterns (string type only)
|
|
160
|
-
if (isStringType(type)) {
|
|
161
|
-
const urlNameStart = /^(href|url|link|src)/i;
|
|
162
|
-
const urlNameEnd = /(Url|Href|Link|Src)$/;
|
|
163
|
-
if (urlNameStart.test(name) || urlNameEnd.test(name)) {
|
|
164
|
-
return { category: 'content', cdfTypeHint: 'string' };
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
// Rule 13: Text patterns (string type only)
|
|
168
|
-
if (isStringType(type)) {
|
|
169
|
-
const textNameStart = /^(label|title|text|description|caption|heading|subheading|body|alt|name|placeholder|summary)/i;
|
|
170
|
-
const textNameEnd = /(Text|Label|Title|Name)$/;
|
|
171
|
-
if (textNameStart.test(name) || textNameEnd.test(name)) {
|
|
172
|
-
return { category: 'content', cdfTypeHint: 'string' };
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
// Rule 14: Remaining strings
|
|
176
|
-
if (isStringType(type)) {
|
|
177
|
-
return { category: 'content', cdfTypeHint: 'string' };
|
|
178
|
-
}
|
|
179
|
-
// Rule 15: Remaining booleans
|
|
180
|
-
if (isBooleanType(type)) {
|
|
181
|
-
return { category: 'design', cdfTypeHint: 'boolean' };
|
|
182
|
-
}
|
|
183
|
-
// Rule 16: Remaining numbers
|
|
184
|
-
if (isNumberType(type)) {
|
|
185
|
-
return { category: 'design', cdfTypeHint: 'string' };
|
|
186
|
-
}
|
|
187
|
-
// Rule 17: Complex/object/function/array types — no hint
|
|
188
|
-
return undefined;
|
|
189
|
-
}
|
|
190
|
-
/**
|
|
191
|
-
* Applies pre-classification to all props in a component definition.
|
|
192
|
-
* - Leaves existing category values unchanged
|
|
193
|
-
* - Sets category for content/design/state matches
|
|
194
|
-
* - Does NOT set category for 'exclude' results (leaves undefined)
|
|
195
|
-
*/
|
|
196
|
-
export function preClassifyComponent(component) {
|
|
197
|
-
const props = component.props.map((prop) => {
|
|
198
|
-
// If category is already set, leave unchanged
|
|
199
|
-
if (prop.category) {
|
|
200
|
-
return prop;
|
|
201
|
-
}
|
|
202
|
-
const result = preClassifyProp(prop);
|
|
203
|
-
// If no result or excluded, leave unchanged
|
|
204
|
-
if (!result || result.category === 'exclude') {
|
|
205
|
-
return prop;
|
|
206
|
-
}
|
|
207
|
-
// Set category hint
|
|
208
|
-
return { ...prop, category: result.category };
|
|
209
|
-
});
|
|
210
|
-
return { ...component, props };
|
|
211
|
-
}
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* INTEG-4401 Fix C — parse EDSI failure bodies into a structured shape the
|
|
3
|
-
* TUI can render without dumping timestamps + trace ids at operators.
|
|
4
|
-
*
|
|
5
|
-
* EDSI failure bodies come in three broad shapes today:
|
|
6
|
-
*
|
|
7
|
-
* 1. Plain JSON error:
|
|
8
|
-
* `{"code":"TopoSortCycleError","message":"...","cycle":["CycleA","CycleB"]}`
|
|
9
|
-
*
|
|
10
|
-
* 2. Wrapper JSON with `details`:
|
|
11
|
-
* `{"sys":{"type":"Error"},"message":"...","details":{"code":"...","cycle":[...]}}`
|
|
12
|
-
*
|
|
13
|
-
* 3. Lambda log-line spill (the response body is the CloudWatch log
|
|
14
|
-
* formatting the worker's uncaught exception):
|
|
15
|
-
* `2026-07-07T22:26:26.479Z\t<request-id>\tERROR\t[dd.trace_id=... dd.span_id=...] <message> {\n operationId: '...',\n code: 'TopoSortCycleError',\n cycle: [ 'CycleA', 'CycleB' ]\n}`
|
|
16
|
-
*
|
|
17
|
-
* We parse each shape best-effort and fall back to a cleaned raw message so
|
|
18
|
-
* the ErrorStep never has to show the operator a timestamp + trace id.
|
|
19
|
-
*/
|
|
20
|
-
export interface ParsedEdsiError {
|
|
21
|
-
/** Server-side error code, if we could extract one. */
|
|
22
|
-
code: string | null;
|
|
23
|
-
/** Human-readable message, stripped of log/trace decoration. */
|
|
24
|
-
message: string;
|
|
25
|
-
/** Cycle participants, when `code === 'TopoSortCycleError'`. */
|
|
26
|
-
cycle: string[] | null;
|
|
27
|
-
/** True when the message survived cleaning as-is (no parseable structure). */
|
|
28
|
-
raw: boolean;
|
|
29
|
-
}
|
|
30
|
-
/**
|
|
31
|
-
* Strip the Lambda/CloudWatch log-line decoration from the head of a body so
|
|
32
|
-
* the operator-facing message reads as an error sentence, not a log entry.
|
|
33
|
-
*/
|
|
34
|
-
export declare function stripLambdaLogPrefix(body: string): string;
|
|
35
|
-
export declare function parseEdsiError(rawInput: string | undefined | null): ParsedEdsiError;
|
|
36
|
-
/**
|
|
37
|
-
* Render a parsed error into a short multi-line block for the ErrorStep.
|
|
38
|
-
* Callers can pass `verbose: true` to include the raw body underneath.
|
|
39
|
-
*/
|
|
40
|
-
export declare function formatParsedEdsiError(parsed: ParsedEdsiError, opts?: {
|
|
41
|
-
verbose?: boolean;
|
|
42
|
-
raw?: string;
|
|
43
|
-
}): string;
|
|
@@ -1,163 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* INTEG-4401 Fix C — parse EDSI failure bodies into a structured shape the
|
|
3
|
-
* TUI can render without dumping timestamps + trace ids at operators.
|
|
4
|
-
*
|
|
5
|
-
* EDSI failure bodies come in three broad shapes today:
|
|
6
|
-
*
|
|
7
|
-
* 1. Plain JSON error:
|
|
8
|
-
* `{"code":"TopoSortCycleError","message":"...","cycle":["CycleA","CycleB"]}`
|
|
9
|
-
*
|
|
10
|
-
* 2. Wrapper JSON with `details`:
|
|
11
|
-
* `{"sys":{"type":"Error"},"message":"...","details":{"code":"...","cycle":[...]}}`
|
|
12
|
-
*
|
|
13
|
-
* 3. Lambda log-line spill (the response body is the CloudWatch log
|
|
14
|
-
* formatting the worker's uncaught exception):
|
|
15
|
-
* `2026-07-07T22:26:26.479Z\t<request-id>\tERROR\t[dd.trace_id=... dd.span_id=...] <message> {\n operationId: '...',\n code: 'TopoSortCycleError',\n cycle: [ 'CycleA', 'CycleB' ]\n}`
|
|
16
|
-
*
|
|
17
|
-
* We parse each shape best-effort and fall back to a cleaned raw message so
|
|
18
|
-
* the ErrorStep never has to show the operator a timestamp + trace id.
|
|
19
|
-
*/
|
|
20
|
-
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*)?/;
|
|
21
|
-
const DD_TAG_RE = /\[dd\.(?:trace_id|span_id)=[^\]]*\]\s*/g;
|
|
22
|
-
/**
|
|
23
|
-
* Strip the Lambda/CloudWatch log-line decoration from the head of a body so
|
|
24
|
-
* the operator-facing message reads as an error sentence, not a log entry.
|
|
25
|
-
*/
|
|
26
|
-
export function stripLambdaLogPrefix(body) {
|
|
27
|
-
let out = body.replace(LAMBDA_LOG_PREFIX_RE, '');
|
|
28
|
-
out = out.replace(DD_TAG_RE, '');
|
|
29
|
-
return out.trim();
|
|
30
|
-
}
|
|
31
|
-
/**
|
|
32
|
-
* Extract a JS-object-literal tail like `{ operationId: '...', code: '...', cycle: [ 'A', 'B' ] }`
|
|
33
|
-
* that Lambda-spilled errors trail with. Returns `null` if no such structure
|
|
34
|
-
* is present or the fields we care about can't be recovered.
|
|
35
|
-
*/
|
|
36
|
-
function parseObjectLiteralTail(body) {
|
|
37
|
-
const braceStart = body.lastIndexOf('{');
|
|
38
|
-
if (braceStart === -1)
|
|
39
|
-
return null;
|
|
40
|
-
const tail = body.slice(braceStart);
|
|
41
|
-
const codeMatch = tail.match(/code:\s*['"]([^'"]+)['"]/);
|
|
42
|
-
const cycleMatch = tail.match(/cycle:\s*\[\s*([^\]]*)\s*\]/);
|
|
43
|
-
if (!codeMatch && !cycleMatch)
|
|
44
|
-
return null;
|
|
45
|
-
const code = codeMatch ? codeMatch[1] : null;
|
|
46
|
-
let cycle = null;
|
|
47
|
-
if (cycleMatch) {
|
|
48
|
-
cycle = cycleMatch[1]
|
|
49
|
-
.split(',')
|
|
50
|
-
.map((s) => s.trim().replace(/^['"]|['"]$/g, ''))
|
|
51
|
-
.filter((s) => s.length > 0);
|
|
52
|
-
if (cycle.length === 0)
|
|
53
|
-
cycle = null;
|
|
54
|
-
}
|
|
55
|
-
return { code, cycle };
|
|
56
|
-
}
|
|
57
|
-
/**
|
|
58
|
-
* Best-effort JSON parse walking one level deep into `details`. Fields we
|
|
59
|
-
* accept anywhere in the top-level or `details` object: `code`, `message`,
|
|
60
|
-
* `cycle`. Returns `null` when the input isn't JSON.
|
|
61
|
-
*/
|
|
62
|
-
function parseJsonBody(body) {
|
|
63
|
-
let parsed;
|
|
64
|
-
try {
|
|
65
|
-
parsed = JSON.parse(body);
|
|
66
|
-
}
|
|
67
|
-
catch {
|
|
68
|
-
return null;
|
|
69
|
-
}
|
|
70
|
-
if (!parsed || typeof parsed !== 'object')
|
|
71
|
-
return null;
|
|
72
|
-
const p = parsed;
|
|
73
|
-
const details = (p.details && typeof p.details === 'object' ? p.details : {});
|
|
74
|
-
const pick = (k) => (p[k] !== undefined ? p[k] : details[k]);
|
|
75
|
-
const codeRaw = pick('code');
|
|
76
|
-
const messageRaw = pick('message');
|
|
77
|
-
const cycleRaw = pick('cycle');
|
|
78
|
-
const out = {};
|
|
79
|
-
if (typeof codeRaw === 'string')
|
|
80
|
-
out.code = codeRaw;
|
|
81
|
-
if (typeof messageRaw === 'string')
|
|
82
|
-
out.message = messageRaw;
|
|
83
|
-
if (Array.isArray(cycleRaw)) {
|
|
84
|
-
const strs = cycleRaw.filter((x) => typeof x === 'string');
|
|
85
|
-
if (strs.length > 0)
|
|
86
|
-
out.cycle = strs;
|
|
87
|
-
}
|
|
88
|
-
return out;
|
|
89
|
-
}
|
|
90
|
-
/**
|
|
91
|
-
* Parse an EDSI error body (the raw response body, or an `ApiError.message`
|
|
92
|
-
* that has the body appended). Always returns a value — the fallback is the
|
|
93
|
-
* cleaned raw message with `code: null`.
|
|
94
|
-
*/
|
|
95
|
-
// ApiError.message shape: `${phasePrefix}\n${body}` where phasePrefix looks
|
|
96
|
-
// like `apply failed: 400`, `preview failed: 422`, `poll failed: 500`. We
|
|
97
|
-
// only strip the prefix line when the first line matches this shape —
|
|
98
|
-
// otherwise a raw body that happens to contain a newline (e.g. a Lambda log
|
|
99
|
-
// spill with a multi-line object literal) gets truncated mid-structure.
|
|
100
|
-
const API_ERROR_PREFIX_RE = /^(?:apply|preview|poll) failed: \d+$/;
|
|
101
|
-
export function parseEdsiError(rawInput) {
|
|
102
|
-
if (!rawInput)
|
|
103
|
-
return { code: null, message: '', cycle: null, raw: true };
|
|
104
|
-
const nlIndex = rawInput.indexOf('\n');
|
|
105
|
-
let body = rawInput;
|
|
106
|
-
let prefix = '';
|
|
107
|
-
if (nlIndex !== -1) {
|
|
108
|
-
const firstLine = rawInput.slice(0, nlIndex);
|
|
109
|
-
if (API_ERROR_PREFIX_RE.test(firstLine)) {
|
|
110
|
-
prefix = firstLine;
|
|
111
|
-
body = rawInput.slice(nlIndex + 1);
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
const cleaned = stripLambdaLogPrefix(body);
|
|
115
|
-
const json = parseJsonBody(cleaned) ?? parseJsonBody(body);
|
|
116
|
-
if (json && (json.code || json.message || json.cycle)) {
|
|
117
|
-
return {
|
|
118
|
-
code: json.code ?? null,
|
|
119
|
-
message: json.message ?? (cleaned || prefix),
|
|
120
|
-
cycle: json.cycle ?? null,
|
|
121
|
-
raw: false,
|
|
122
|
-
};
|
|
123
|
-
}
|
|
124
|
-
const literal = parseObjectLiteralTail(cleaned);
|
|
125
|
-
if (literal && (literal.code || literal.cycle)) {
|
|
126
|
-
// Grab the human-readable head — text before the trailing `{`. This is
|
|
127
|
-
// the sentence part of a Lambda log-line spill (e.g. "Apply operation
|
|
128
|
-
// <id> rejected: ComponentType slot dependency cycle detected among:
|
|
129
|
-
// CycleA, CycleB. Break the cycle by...").
|
|
130
|
-
const braceStart = cleaned.lastIndexOf('{');
|
|
131
|
-
const head = braceStart === -1 ? cleaned : cleaned.slice(0, braceStart).trim();
|
|
132
|
-
return {
|
|
133
|
-
code: literal.code ?? null,
|
|
134
|
-
message: head || cleaned,
|
|
135
|
-
cycle: literal.cycle ?? null,
|
|
136
|
-
raw: false,
|
|
137
|
-
};
|
|
138
|
-
}
|
|
139
|
-
return { code: null, message: cleaned || prefix || rawInput, cycle: null, raw: true };
|
|
140
|
-
}
|
|
141
|
-
/**
|
|
142
|
-
* Render a parsed error into a short multi-line block for the ErrorStep.
|
|
143
|
-
* Callers can pass `verbose: true` to include the raw body underneath.
|
|
144
|
-
*/
|
|
145
|
-
export function formatParsedEdsiError(parsed, opts = {}) {
|
|
146
|
-
const lines = [];
|
|
147
|
-
if (parsed.code) {
|
|
148
|
-
lines.push(`[${parsed.code}]`);
|
|
149
|
-
}
|
|
150
|
-
if (parsed.message) {
|
|
151
|
-
lines.push(parsed.message);
|
|
152
|
-
}
|
|
153
|
-
if (parsed.cycle && parsed.cycle.length > 0) {
|
|
154
|
-
lines.push(`Cycle: ${parsed.cycle.join(' → ')} → ${parsed.cycle[0]}`);
|
|
155
|
-
lines.push('Break the cycle by removing at least one $allowedComponents entry.');
|
|
156
|
-
}
|
|
157
|
-
if (opts.verbose && opts.raw) {
|
|
158
|
-
lines.push('');
|
|
159
|
-
lines.push('--- raw ---');
|
|
160
|
-
lines.push(opts.raw);
|
|
161
|
-
}
|
|
162
|
-
return lines.filter(Boolean).join('\n');
|
|
163
|
-
}
|