@contentful/experience-design-system-cli 2.12.4-dev-build-ff57340.0 → 2.13.1-dev-build-fa478b3.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
package/dist/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contentful/experience-design-system-cli",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.13.1-dev-build-fa478b3.0",
|
|
4
4
|
"description": "Contentful Experiences design system import CLI",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -37,16 +37,13 @@
|
|
|
37
37
|
"lint:fix": "nx lint:fix experience-design-system-cli"
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
+
"@contentful/experience-design-system-extraction": "workspace:*",
|
|
40
41
|
"@contentful/experience-design-system-types": "workspace:*",
|
|
41
|
-
"@vue/compiler-sfc": "^3.5.31",
|
|
42
42
|
"commander": "^13.1.0",
|
|
43
43
|
"ink": "^4.4.1",
|
|
44
44
|
"react": "^18.3.1",
|
|
45
45
|
"react-devtools-core": "^4.19.1",
|
|
46
|
-
"react-dom": "^18.3.1"
|
|
47
|
-
"svelte": "^5.56.4",
|
|
48
|
-
"ts-morph": "^27.0.2",
|
|
49
|
-
"typescript": "^5.9.3"
|
|
46
|
+
"react-dom": "^18.3.1"
|
|
50
47
|
},
|
|
51
48
|
"devDependencies": {
|
|
52
49
|
"@tsconfig/node24": "^24.0.3",
|
|
@@ -2,17 +2,11 @@ import { createElement } from 'react';
|
|
|
2
2
|
import { render } from 'ink';
|
|
3
3
|
import { mkdir, readdir, stat } from 'node:fs/promises';
|
|
4
4
|
import { isAbsolute, join, relative, resolve } from 'node:path';
|
|
5
|
-
import { extractComponents } from '
|
|
5
|
+
import { extractComponents, preClassifyComponent, isNonAuthorableComponent, computeExtractionScore, deriveNeedsReview, describeReviewReasons, inspectComponentSource, validateExtractedComponents, } from '@contentful/experience-design-system-extraction';
|
|
6
6
|
import { AnalyzeView } from './tui/AnalyzeView.js';
|
|
7
7
|
import { registerAnalyzeEditCommand } from './select/command.js';
|
|
8
8
|
import { registerAnalyzeSelectAgentCommand } from './select-agent/command.js';
|
|
9
|
-
import { openPipelineDb, getOrCreateSession, createStep, updateStep, storeRawComponents, storeScannedFiles,
|
|
10
|
-
import { findSlotCycles, suggestCycleBreakEdge } from './cycle-detection.js';
|
|
11
|
-
import { preClassifyComponent } from './pre-classify.js';
|
|
12
|
-
import { isNonAuthorableComponent } from './extract/non-authorable-filter.js';
|
|
13
|
-
import { computeExtractionScore, deriveNeedsReview } from './extract/scoring.js';
|
|
14
|
-
import { describeReviewReasons, inspectComponentSource } from './extract/source-inspection.js';
|
|
15
|
-
import { validateExtractedComponents } from './extract/validate.js';
|
|
9
|
+
import { openPipelineDb, getOrCreateSession, createStep, updateStep, storeRawComponents, storeScannedFiles, } from '../session/db.js';
|
|
16
10
|
import { buildAnalyzeViewRows, partitionGlobalWarnings } from './build-analyze-view-rows.js';
|
|
17
11
|
const SCANNED_FILE_EXTENSIONS = new Set(['.astro', '.js', '.jsx', '.svelte', '.ts', '.tsx', '.vue']);
|
|
18
12
|
const IGNORED_DIRECTORY_NAMES = new Set([
|
|
@@ -192,20 +186,6 @@ export function registerAnalyzeCommand(program) {
|
|
|
192
186
|
}
|
|
193
187
|
const validatedComponents = validateExtractedComponents(filteredComponents);
|
|
194
188
|
storeRawComponents(db, sessionId, validatedComponents);
|
|
195
|
-
// INTEG-4401: post-extract slot-dependency cycle detection. The apply
|
|
196
|
-
// worker rejects cyclic slot graphs at push time, so we surface the
|
|
197
|
-
// same violation locally as a soft warning here (TUI reads
|
|
198
|
-
// slot_cycles) and as a hard block at manifest finalization.
|
|
199
|
-
const cycleInput = validatedComponents.map((c) => ({
|
|
200
|
-
name: c.name,
|
|
201
|
-
slots: c.slots.map((s) => ({ name: s.name, allowedComponents: s.allowedComponents })),
|
|
202
|
-
}));
|
|
203
|
-
const cycles = findSlotCycles(cycleInput);
|
|
204
|
-
const withBreaks = cycles.map((cycle) => ({
|
|
205
|
-
...cycle,
|
|
206
|
-
suggestedBreak: suggestCycleBreakEdge(cycle, cycles),
|
|
207
|
-
}));
|
|
208
|
-
storeSlotCycles(db, sessionId, withBreaks);
|
|
209
189
|
storeScannedFiles(db, sessionId, sourceFiles.map((f) => relative(projectRoot, f)));
|
|
210
190
|
updateStep(db, stepId, 'complete', { sessionId });
|
|
211
191
|
db.close();
|
|
@@ -6,7 +6,7 @@ import { getRefineArtifactsRoot, ensureRefineSession, getRefineSessionPaths, sav
|
|
|
6
6
|
import { loadReviewInput } from './parser.js';
|
|
7
7
|
import { App } from './tui/App.js';
|
|
8
8
|
import { openPipelineDb, loadRawComponents, storeRawComponents, createStep, updateStep } from '../../session/db.js';
|
|
9
|
-
import { validateExtractedComponents, shouldExcludeDueToValidation, formatExclusionWarning, } from '
|
|
9
|
+
import { validateExtractedComponents, shouldExcludeDueToValidation, formatExclusionWarning, } from '@contentful/experience-design-system-extraction';
|
|
10
10
|
const SAFE_PATH_RE = /^[a-zA-Z0-9_.$[\]=]+$/;
|
|
11
11
|
const PROTO_KEYS = new Set(['__proto__', 'constructor', 'prototype']);
|
|
12
12
|
function applyDotPath(obj, path, value) {
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { findSlotCycles, type ComponentSlotInfo } from '../../../cycle-detection.js';
|
|
3
2
|
/** Per-prop / per-component metadata captured by the extractor + generate phase.
|
|
4
3
|
* Optional; when omitted, the FieldEditor renders without rationale/source
|
|
5
4
|
* affordances (backwards-compatible with mounts that pre-date Feature 1). */
|
|
@@ -64,28 +63,6 @@ type FieldEditorProps = {
|
|
|
64
63
|
* description editors, string default editors, and value-list text entry.
|
|
65
64
|
*/
|
|
66
65
|
onTextEntryActiveChange?: (active: boolean) => void;
|
|
67
|
-
/**
|
|
68
|
-
* INTEG-4401: project-wide slot graph. When supplied, the
|
|
69
|
-
* $allowedComponents add-mode shows a cycle-filtered picker. Optional.
|
|
70
|
-
*/
|
|
71
|
-
projectSlotGraph?: ComponentSlotInfo[];
|
|
72
|
-
/**
|
|
73
|
-
* INTEG-4401: name of the component being edited. Self-loop guard +
|
|
74
|
-
* self-entry replacement in the cycle simulation.
|
|
75
|
-
*/
|
|
76
|
-
currentComponentName?: string;
|
|
77
66
|
};
|
|
78
|
-
|
|
79
|
-
export declare function simulateGraphWithCandidate(projectSlotGraph: ComponentSlotInfo[], selfName: string, currentSlots: {
|
|
80
|
-
name: string;
|
|
81
|
-
allowedComponents: string[];
|
|
82
|
-
}[], targetSlotName: string, candidate: string): ComponentSlotInfo[];
|
|
83
|
-
/** True iff `next` contains a cycle whose canonical edge set is absent in `before`. */
|
|
84
|
-
export declare function introducesNewCycle(before: ReturnType<typeof findSlotCycles>, next: ReturnType<typeof findSlotCycles>): boolean;
|
|
85
|
-
/** Candidate names that can be added to a slot without creating a new cycle. */
|
|
86
|
-
export declare function computeAllowedComponentCandidates(projectSlotGraph: ComponentSlotInfo[], selfName: string, currentSlots: {
|
|
87
|
-
name: string;
|
|
88
|
-
allowedComponents: string[];
|
|
89
|
-
}[], targetSlotName: string): string[];
|
|
90
|
-
export declare function FieldEditor({ value, width, height, active, onChange, onSave, onDiscard, onExit, metadata, onTogglePropRationale, onToggleComponentRationale, onToggleSourceExternal, onTextEntryActiveChange, projectSlotGraph, currentComponentName, }: FieldEditorProps): React.ReactElement;
|
|
67
|
+
export declare function FieldEditor({ value, width, height, active, onChange, onSave, onDiscard, onExit, metadata, onTogglePropRationale, onToggleComponentRationale, onToggleSourceExternal, onTextEntryActiveChange, }: FieldEditorProps): React.ReactElement;
|
|
91
68
|
export {};
|
|
@@ -4,7 +4,6 @@ import { Box, Text } from 'ink';
|
|
|
4
4
|
import { CDF_PROPERTY_TYPES, CDF_PROPERTY_CATEGORIES, DESIGN_TOKEN_TYPES, } from '@contentful/experience-design-system-types';
|
|
5
5
|
import { useImmediateInput } from '../hooks/useImmediateInput.js';
|
|
6
6
|
import { computeNextScrollOffset } from '../hooks/scroll-offset.js';
|
|
7
|
-
import { findSlotCycles } from '../../../cycle-detection.js';
|
|
8
7
|
// Section indices for top-level navigation
|
|
9
8
|
// 0 = component $description row
|
|
10
9
|
// 1 = $properties header (then props[0..n-1])
|
|
@@ -202,34 +201,18 @@ function PropRow({ prop, selected, activeField, textCursor, valueCursor, cursorV
|
|
|
202
201
|
return (_jsx(Box, { gap: 1, paddingLeft: 2, children: _jsx(Text, { color: isActiveCursor ? 'cyan' : 'white', children: isActiveCursor ? `▶ ${v}` : ` ${v}` }) }, i));
|
|
203
202
|
}), editingValue?.mode === 'add' && (_jsxs(Box, { paddingLeft: 2, children: [_jsx(Text, { color: "cyan", children: '+ ' }), _jsx(Text, { children: valueText }), _jsx(Text, { inverse: cursorVisible, children: " " })] }))] }))] }));
|
|
204
203
|
}
|
|
205
|
-
function SlotRow({ slot, selected, activeField, textCursor, valueCursor, cursorVisible, editingValue, valueText, width,
|
|
204
|
+
function SlotRow({ slot, selected, activeField, textCursor, valueCursor, cursorVisible, editingValue, valueText, width, }) {
|
|
206
205
|
const cursor = cursorVisible ? '█' : ' ';
|
|
207
206
|
const bg = selected ? 'blue' : undefined;
|
|
208
207
|
const nameDisplay = slot.name.length > 14 ? slot.name.slice(0, 13) + '…' : slot.name.padEnd(14);
|
|
209
|
-
return (_jsxs(Box, { flexDirection: "column", width: width, children: [_jsxs(Box, { gap: 1, children: [_jsxs(Text, { color: selected ? 'white' : 'cyan', bold: selected, backgroundColor: bg, children: [' ', nameDisplay, ' '] }), _jsx(Text, { dimColor: !selected, children: "req:" }), activeField === 'required' ? (_jsx(Toggle, { value: slot.required, active: true })) : (_jsx(Toggle, { value: slot.required, active: false }))] }),
|
|
208
|
+
return (_jsxs(Box, { flexDirection: "column", width: width, children: [_jsxs(Box, { gap: 1, children: [_jsxs(Text, { color: selected ? 'white' : 'cyan', bold: selected, backgroundColor: bg, children: [' ', nameDisplay, ' '] }), _jsx(Text, { dimColor: !selected, children: "req:" }), activeField === 'required' ? (_jsx(Toggle, { value: slot.required, active: true })) : (_jsx(Toggle, { value: slot.required, active: false }))] }), selected && (_jsxs(Box, { paddingLeft: 2, flexDirection: "column", children: [_jsxs(Box, { children: [_jsx(Text, { dimColor: true, children: "allowed:" }), activeField === 'allowedComponents' && (_jsx(Text, { dimColor: true, children: ' [a]dd [e]dit [r]emove [↑↓] navigate [K/J] reorder' }))] }), slot.allowedComponents.length === 0 && !editingValue && (_jsx(Box, { paddingLeft: 2, children: _jsx(Text, { dimColor: true, children: activeField === 'allowedComponents' ? '(any — press [a] to add)' : '(any)' }) })), slot.allowedComponents.map((v, i) => {
|
|
210
209
|
const isActiveCursor = activeField === 'allowedComponents' && valueCursor === i;
|
|
211
210
|
const isBeingEdited = editingValue?.mode === 'edit' && editingValue.index === i;
|
|
212
211
|
if (isBeingEdited) {
|
|
213
212
|
return (_jsxs(Box, { paddingLeft: 2, children: [_jsx(Text, { color: "cyan", children: '✎ ' }), _jsx(Text, { children: valueText }), _jsx(Text, { inverse: cursorVisible, children: " " })] }, i));
|
|
214
213
|
}
|
|
215
214
|
return (_jsx(Box, { gap: 1, paddingLeft: 2, children: _jsx(Text, { color: isActiveCursor ? 'cyan' : 'white', children: isActiveCursor ? `▶ ${v}` : ` ${v}` }) }, i));
|
|
216
|
-
}), editingValue?.mode === 'add' && activeField === 'allowedComponents' && (_jsxs(Box, { paddingLeft: 2, children: [_jsx(Text, { color: "cyan", children: '+ ' }), _jsx(Text, { children: valueText }), _jsx(Text, { inverse: cursorVisible, children: " " })] })),
|
|
217
|
-
const filtered = valueText.length === 0
|
|
218
|
-
? pickerCandidates
|
|
219
|
-
: pickerCandidates.filter((n) => n.toLowerCase().includes(valueText.toLowerCase()));
|
|
220
|
-
if (filtered.length === 0) {
|
|
221
|
-
return _jsx(Text, { dimColor: true, children: '(no candidates match — Enter to add as free text)' });
|
|
222
|
-
}
|
|
223
|
-
const cursor = pickerCursor % filtered.length;
|
|
224
|
-
const MAX_VISIBLE = 5;
|
|
225
|
-
const start = Math.max(0, Math.min(filtered.length - MAX_VISIBLE, cursor - 2));
|
|
226
|
-
const slice = filtered.slice(start, start + MAX_VISIBLE);
|
|
227
|
-
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { dimColor: true, children: ' candidates (↑↓ cycle, Enter to add):' }), slice.map((name, i) => {
|
|
228
|
-
const absIdx = start + i;
|
|
229
|
-
const isCursor = absIdx === cursor;
|
|
230
|
-
return (_jsx(Text, { color: isCursor ? 'cyan' : undefined, dimColor: !isCursor, children: isCursor ? ` ▶ ${name}` : ` ${name}` }, name));
|
|
231
|
-
})] }));
|
|
232
|
-
})()) }))] })), selected && activeField === 'description' && (_jsxs(Box, { paddingLeft: 2, flexDirection: "row", children: [_jsx(Text, { dimColor: true, children: "desc:" }), _jsxs(Box, { flexGrow: 1, borderStyle: "round", borderColor: "cyan", paddingX: 1, children: [_jsx(Text, { children: slot.description.slice(0, textCursor) }), _jsx(Text, { inverse: cursorVisible, children: slot.description[textCursor] ?? cursor }), _jsx(Text, { children: slot.description.slice(textCursor + 1) })] })] })), selected && activeField !== 'description' && (_jsxs(Box, { paddingLeft: 2, gap: 1, children: [_jsx(Text, { dimColor: true, children: "desc:" }), _jsx(Text, { color: "green", children: slot.description || '—' })] }))] }));
|
|
215
|
+
}), editingValue?.mode === 'add' && activeField === 'allowedComponents' && (_jsxs(Box, { paddingLeft: 2, children: [_jsx(Text, { color: "cyan", children: '+ ' }), _jsx(Text, { children: valueText }), _jsx(Text, { inverse: cursorVisible, children: " " })] }))] })), selected && activeField === 'description' && (_jsxs(Box, { paddingLeft: 2, flexDirection: "row", children: [_jsx(Text, { dimColor: true, children: "desc:" }), _jsxs(Box, { flexGrow: 1, borderStyle: "round", borderColor: "cyan", paddingX: 1, children: [_jsx(Text, { children: slot.description.slice(0, textCursor) }), _jsx(Text, { inverse: cursorVisible, children: slot.description[textCursor] ?? cursor }), _jsx(Text, { children: slot.description.slice(textCursor + 1) })] })] })), selected && activeField !== 'description' && (_jsxs(Box, { paddingLeft: 2, gap: 1, children: [_jsx(Text, { dimColor: true, children: "desc:" }), _jsx(Text, { color: "green", children: slot.description || '—' })] }))] }));
|
|
233
216
|
}
|
|
234
217
|
// Field order is intentional: description is LAST so it's the "edge" of the
|
|
235
218
|
// field cycle. The user reaches description by walking through type → category
|
|
@@ -255,59 +238,8 @@ function propFields(prop) {
|
|
|
255
238
|
// components is a list-typed field that swallows j/k similarly to enum $values
|
|
256
239
|
// — placing it before description preserves description-as-last.
|
|
257
240
|
const SLOT_FIELDS = ['required', 'allowedComponents', 'description'];
|
|
258
|
-
// ── INTEG-4401: cycle-aware $allowedComponents picker helpers ─────────────
|
|
259
|
-
/** Simulate adding a candidate to a slot; replaces self-entry with live edits. */
|
|
260
|
-
export function simulateGraphWithCandidate(projectSlotGraph, selfName, currentSlots, targetSlotName, candidate) {
|
|
261
|
-
const selfEntry = {
|
|
262
|
-
name: selfName,
|
|
263
|
-
slots: currentSlots.map((s) => ({
|
|
264
|
-
name: s.name,
|
|
265
|
-
allowedComponents: s.name === targetSlotName ? [...s.allowedComponents, candidate] : [...s.allowedComponents],
|
|
266
|
-
})),
|
|
267
|
-
};
|
|
268
|
-
const withoutSelf = projectSlotGraph.filter((c) => c.name !== selfName);
|
|
269
|
-
return [...withoutSelf, selfEntry];
|
|
270
|
-
}
|
|
271
|
-
/** True iff `next` contains a cycle whose canonical edge set is absent in `before`. */
|
|
272
|
-
export function introducesNewCycle(before, next) {
|
|
273
|
-
const key = (edges) => {
|
|
274
|
-
if (edges.length === 0)
|
|
275
|
-
return '';
|
|
276
|
-
const encoded = edges.map((e) => `${e.fromComponent}|${e.slotName}|${e.toComponent}`);
|
|
277
|
-
let minIdx = 0;
|
|
278
|
-
for (let i = 1; i < encoded.length; i += 1)
|
|
279
|
-
if (encoded[i] < encoded[minIdx])
|
|
280
|
-
minIdx = i;
|
|
281
|
-
return [...encoded.slice(minIdx), ...encoded.slice(0, minIdx)].join(';');
|
|
282
|
-
};
|
|
283
|
-
const beforeSet = new Set(before.map((c) => key(c.edges)));
|
|
284
|
-
for (const cycle of next)
|
|
285
|
-
if (!beforeSet.has(key(cycle.edges)))
|
|
286
|
-
return true;
|
|
287
|
-
return false;
|
|
288
|
-
}
|
|
289
|
-
/** Candidate names that can be added to a slot without creating a new cycle. */
|
|
290
|
-
export function computeAllowedComponentCandidates(projectSlotGraph, selfName, currentSlots, targetSlotName) {
|
|
291
|
-
const targetSlot = currentSlots.find((s) => s.name === targetSlotName);
|
|
292
|
-
const alreadyAdded = new Set(targetSlot?.allowedComponents ?? []);
|
|
293
|
-
const universe = new Set();
|
|
294
|
-
for (const c of projectSlotGraph)
|
|
295
|
-
universe.add(c.name);
|
|
296
|
-
universe.delete(selfName);
|
|
297
|
-
for (const added of alreadyAdded)
|
|
298
|
-
universe.delete(added);
|
|
299
|
-
const baseline = findSlotCycles(simulateGraphWithCandidate(projectSlotGraph, selfName, currentSlots, '', ''));
|
|
300
|
-
const safe = [];
|
|
301
|
-
for (const candidate of universe) {
|
|
302
|
-
const nextCycles = findSlotCycles(simulateGraphWithCandidate(projectSlotGraph, selfName, currentSlots, targetSlotName, candidate));
|
|
303
|
-
if (!introducesNewCycle(baseline, nextCycles))
|
|
304
|
-
safe.push(candidate);
|
|
305
|
-
}
|
|
306
|
-
safe.sort((a, b) => a.localeCompare(b));
|
|
307
|
-
return safe;
|
|
308
|
-
}
|
|
309
241
|
// ── Main component ────────────────────────────────────────────────────────────
|
|
310
|
-
export function FieldEditor({ value, width, height, active = true, onChange, onSave, onDiscard, onExit, metadata, onTogglePropRationale, onToggleComponentRationale, onToggleSourceExternal, onTextEntryActiveChange,
|
|
242
|
+
export function FieldEditor({ value, width, height, active = true, onChange, onSave, onDiscard, onExit, metadata, onTogglePropRationale, onToggleComponentRationale, onToggleSourceExternal, onTextEntryActiveChange, }) {
|
|
311
243
|
const { state: initialState, error: parseError } = parseToState(value);
|
|
312
244
|
const [editorState, setEditorState] = useState(initialState);
|
|
313
245
|
const [parseErr] = useState(parseError);
|
|
@@ -359,8 +291,6 @@ export function FieldEditor({ value, width, height, active = true, onChange, onS
|
|
|
359
291
|
// mode='add' — append on Enter; mode='edit' — replace at index on Enter.
|
|
360
292
|
const [editingValue, setEditingValue] = useState(null);
|
|
361
293
|
const [valueText, setValueText] = useState('');
|
|
362
|
-
// INTEG-4401: picker cursor for the cycle-filtered $allowedComponents list.
|
|
363
|
-
const [pickerCursor, setPickerCursor] = useState(0);
|
|
364
294
|
const [validationError, setValidationError] = useState(null);
|
|
365
295
|
const [cursorVisible] = useState(true);
|
|
366
296
|
// Feature 1: source-view panel toggle. Opens with `s`, closes with `s` or Esc.
|
|
@@ -429,57 +359,11 @@ export function FieldEditor({ value, width, height, active = true, onChange, onS
|
|
|
429
359
|
// same add/edit/remove/reorder list shape.
|
|
430
360
|
const isPropValuesEntry = editingValue && currentProp && activeField === 'values' && !inSlots;
|
|
431
361
|
const isSlotAllowedEntry = editingValue && currentSlot && activeField === 'allowedComponents' && inSlots;
|
|
432
|
-
// INTEG-4401: cycle-filtered candidates for slot-add.
|
|
433
|
-
const slotAddCandidates = isSlotAllowedEntry && editingValue?.mode === 'add' && projectSlotGraph && currentSlot && currentComponentName
|
|
434
|
-
? computeAllowedComponentCandidates(projectSlotGraph, currentComponentName, slots, currentSlot.name)
|
|
435
|
-
: null;
|
|
436
|
-
const filteredCandidates = slotAddCandidates
|
|
437
|
-
? valueText.length === 0
|
|
438
|
-
? slotAddCandidates
|
|
439
|
-
: slotAddCandidates.filter((n) => n.toLowerCase().includes(valueText.toLowerCase()))
|
|
440
|
-
: null;
|
|
441
362
|
if (isPropValuesEntry || isSlotAllowedEntry) {
|
|
442
363
|
const vals = isPropValuesEntry ? currentProp.values : currentSlot.allowedComponents;
|
|
443
|
-
const pickerActive = isSlotAllowedEntry && editingValue?.mode === 'add' && slotAddCandidates !== null;
|
|
444
|
-
if (pickerActive && (key.upArrow || key.downArrow)) {
|
|
445
|
-
const len = filteredCandidates.length;
|
|
446
|
-
if (len > 0) {
|
|
447
|
-
setPickerCursor((c) => (key.upArrow ? (c - 1 + len) % len : (c + 1) % len));
|
|
448
|
-
}
|
|
449
|
-
return;
|
|
450
|
-
}
|
|
451
364
|
if (key.return) {
|
|
452
|
-
|
|
453
|
-
if (pickerActive && filteredCandidates && filteredCandidates.length > 0) {
|
|
454
|
-
if (valueText.trim() === '') {
|
|
455
|
-
chosen = filteredCandidates[pickerCursor % filteredCandidates.length] ?? null;
|
|
456
|
-
}
|
|
457
|
-
else {
|
|
458
|
-
const exact = filteredCandidates.find((n) => n === valueText.trim());
|
|
459
|
-
if (exact)
|
|
460
|
-
chosen = exact;
|
|
461
|
-
}
|
|
462
|
-
}
|
|
463
|
-
const trimmed = chosen ?? valueText.trim();
|
|
365
|
+
const trimmed = valueText.trim();
|
|
464
366
|
if (trimmed) {
|
|
465
|
-
// Cycle-aware free-text validation. Picker path is safe by construction.
|
|
466
|
-
if (isSlotAllowedEntry &&
|
|
467
|
-
editingValue?.mode === 'add' &&
|
|
468
|
-
projectSlotGraph &&
|
|
469
|
-
currentSlot &&
|
|
470
|
-
currentComponentName &&
|
|
471
|
-
chosen === null) {
|
|
472
|
-
if (trimmed === currentComponentName) {
|
|
473
|
-
setValidationError(`"${trimmed}" cannot be added to its own slot (self-loop).`);
|
|
474
|
-
return;
|
|
475
|
-
}
|
|
476
|
-
const baseline = findSlotCycles(simulateGraphWithCandidate(projectSlotGraph, currentComponentName, slots, '', ''));
|
|
477
|
-
const next = findSlotCycles(simulateGraphWithCandidate(projectSlotGraph, currentComponentName, slots, currentSlot.name, trimmed));
|
|
478
|
-
if (introducesNewCycle(baseline, next)) {
|
|
479
|
-
setValidationError(`Adding "${trimmed}" to slot "${currentSlot.name}" would create a slot-dependency cycle. Choose a different component.`);
|
|
480
|
-
return;
|
|
481
|
-
}
|
|
482
|
-
}
|
|
483
367
|
let nextVals;
|
|
484
368
|
let cursorAfter;
|
|
485
369
|
if (editingValue.mode === 'add') {
|
|
@@ -500,28 +384,22 @@ export function FieldEditor({ value, width, height, active = true, onChange, onS
|
|
|
500
384
|
commit({ ...editorState, slots: nextSlots });
|
|
501
385
|
}
|
|
502
386
|
setValueCursor(cursorAfter);
|
|
503
|
-
setValidationError(null);
|
|
504
387
|
}
|
|
505
388
|
setEditingValue(null);
|
|
506
389
|
setValueText('');
|
|
507
|
-
setPickerCursor(0);
|
|
508
390
|
return;
|
|
509
391
|
}
|
|
510
392
|
if (key.escape) {
|
|
511
393
|
setEditingValue(null);
|
|
512
394
|
setValueText('');
|
|
513
|
-
setPickerCursor(0);
|
|
514
|
-
setValidationError(null);
|
|
515
395
|
return;
|
|
516
396
|
}
|
|
517
397
|
if (key.backspace) {
|
|
518
398
|
setValueText((t) => t.slice(0, -1));
|
|
519
|
-
setPickerCursor(0);
|
|
520
399
|
return;
|
|
521
400
|
}
|
|
522
401
|
if (input && input.length === 1 && !key.ctrl && !key.meta) {
|
|
523
402
|
setValueText((t) => t + input);
|
|
524
|
-
setPickerCursor(0);
|
|
525
403
|
return;
|
|
526
404
|
}
|
|
527
405
|
return;
|
|
@@ -1046,7 +924,10 @@ export function FieldEditor({ value, width, height, active = true, onChange, onS
|
|
|
1046
924
|
if (props.length === 0 && slots.length === 0) {
|
|
1047
925
|
return (_jsxs(Box, { flexDirection: "column", width: width, borderStyle: "single", borderColor: "yellow", children: [_jsx(Text, { bold: true, color: "yellow", children: "FIELD EDITOR \u2014 no fields" }), _jsx(Text, { color: "yellow", children: "⚠ No properties classified for this component. The LLM didn't find anything to classify." }), _jsx(Text, { dimColor: true, children: "You can add fields manually below or reject this component." }), _jsx(Text, { dimColor: true, children: "Ctrl+S to save \u00B7 Esc to discard" })] }));
|
|
1048
926
|
}
|
|
1049
|
-
|
|
927
|
+
// When $properties is empty but $slots exist, surface the same warning
|
|
928
|
+
// prominently in the panel so the user understands why the component looks
|
|
929
|
+
// sparse — and can act on it (manually add a prop or reject).
|
|
930
|
+
const hasEmptyProperties = props.length === 0;
|
|
1050
931
|
const modeLabel = (() => {
|
|
1051
932
|
if (editingValue) {
|
|
1052
933
|
return editingValue.mode === 'add' ? 'Enter to add · Esc to cancel' : 'Enter to save edit · Esc to cancel';
|
|
@@ -1107,15 +988,7 @@ export function FieldEditor({ value, width, height, active = true, onChange, onS
|
|
|
1107
988
|
// slot row
|
|
1108
989
|
const s = slots[row.idx];
|
|
1109
990
|
const isSelected = inSlots && row.idx === slotIdx;
|
|
1110
|
-
|
|
1111
|
-
const slotPickerCandidates = isSelected &&
|
|
1112
|
-
editingValue?.mode === 'add' &&
|
|
1113
|
-
activeField === 'allowedComponents' &&
|
|
1114
|
-
projectSlotGraph &&
|
|
1115
|
-
currentComponentName
|
|
1116
|
-
? computeAllowedComponentCandidates(projectSlotGraph, currentComponentName, slots, s.name)
|
|
1117
|
-
: null;
|
|
1118
|
-
return (_jsx(SlotRow, { slot: s, selected: isSelected, activeField: isSelected && focusLevel === 'field' ? activeField : null, textCursor: textCursor, valueCursor: valueCursor, cursorVisible: cursorVisible, editingValue: isSelected ? editingValue : null, valueText: isSelected ? valueText : '', width: innerWidth, pickerCandidates: slotPickerCandidates, pickerCursor: pickerCursor }, `slot-${row.idx}`));
|
|
991
|
+
return (_jsx(SlotRow, { slot: s, selected: isSelected, activeField: isSelected && focusLevel === 'field' ? activeField : null, textCursor: textCursor, valueCursor: valueCursor, cursorVisible: cursorVisible, editingValue: isSelected ? editingValue : null, valueText: isSelected ? valueText : '', width: innerWidth }, `slot-${row.idx}`));
|
|
1119
992
|
}) }), sourceOpen &&
|
|
1120
993
|
!onToggleSourceExternal &&
|
|
1121
994
|
(() => {
|
|
@@ -11,7 +11,7 @@ import { OutputFormatter, c } from '../../output/format.js';
|
|
|
11
11
|
import { buildRepoContextIndex, buildSelectionContext } from './context-builder.js';
|
|
12
12
|
import { runShowRationale } from './show-rationale.js';
|
|
13
13
|
import { isAbsolute, resolve } from 'node:path';
|
|
14
|
-
import { validateExtractedComponents, shouldExcludeDueToValidation, formatExclusionWarning, } from '
|
|
14
|
+
import { validateExtractedComponents, shouldExcludeDueToValidation, formatExclusionWarning, } from '@contentful/experience-design-system-extraction';
|
|
15
15
|
const VALID_AGENTS = new Set(['claude', 'codex', 'opencode', 'cursor']);
|
|
16
16
|
const DEFAULT_TIMEOUT_MS = Number(process.env.EDS_AGENT_TIMEOUT_MS ?? 3 * 60 * 1000);
|
|
17
17
|
export const DEFAULT_CONCURRENCY = 10;
|
|
@@ -1,39 +1,6 @@
|
|
|
1
1
|
import type { Command } from 'commander';
|
|
2
|
-
import type {
|
|
3
|
-
import { findSlotCycles } from '../analyze/cycle-detection.js';
|
|
2
|
+
import type { DTCGTokenEntry } from '@contentful/experience-design-system-types';
|
|
4
3
|
import type { ServerPreviewResponse } from '@contentful/experience-design-system-types';
|
|
5
4
|
export declare function readTokensFromPath(flag: string, p: string): Promise<DTCGTokenEntry[]>;
|
|
6
|
-
/**
|
|
7
|
-
* Pure cycle detection — returns [] when the graph is acyclic. Callers own
|
|
8
|
-
* how to react (CLI standalone uses `assertNoSlotCycles` which exits 1; the
|
|
9
|
-
* wizard uses `detectSlotCycles` directly to route to an in-TUI error step
|
|
10
|
-
* without ever POSTing to EDSI).
|
|
11
|
-
*/
|
|
12
|
-
export declare function detectSlotCycles(components: Array<{
|
|
13
|
-
key: string;
|
|
14
|
-
entry: CDFComponentEntry;
|
|
15
|
-
}>): ReturnType<typeof findSlotCycles>;
|
|
16
|
-
/**
|
|
17
|
-
* Build a stderr-style message block for a set of cycles. Extracted so the
|
|
18
|
-
* wizard can render the same text in an in-TUI error panel and any future
|
|
19
|
-
* headless surface can log identical output.
|
|
20
|
-
*/
|
|
21
|
-
export declare function formatSlotCycleReport(cycles: ReturnType<typeof findSlotCycles>): string[];
|
|
22
|
-
export declare function assertNoSlotCycles(components: Array<{
|
|
23
|
-
key: string;
|
|
24
|
-
entry: CDFComponentEntry;
|
|
25
|
-
}>): void;
|
|
26
|
-
/**
|
|
27
|
-
* Reconstruct the `{key, entry}` shape from a built `ManifestPayload`. The
|
|
28
|
-
* wizard's push path holds a serialized manifest rather than the underlying
|
|
29
|
-
* CDF records, so we unwrap it here to feed `detectSlotCycles`. Skips the
|
|
30
|
-
* `$schema` sentinel key. Safe on undefined/empty manifests (returns []).
|
|
31
|
-
*/
|
|
32
|
-
export declare function extractComponentsFromManifest(manifest: {
|
|
33
|
-
componentsManifest?: Record<string, unknown>;
|
|
34
|
-
} | null | undefined): Array<{
|
|
35
|
-
key: string;
|
|
36
|
-
entry: CDFComponentEntry;
|
|
37
|
-
}>;
|
|
38
5
|
export declare function hasBreakingChangesWithImpact(preview: ServerPreviewResponse): boolean;
|
|
39
6
|
export declare function registerApplyCommand(program: Command): void;
|
|
@@ -5,7 +5,6 @@ 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
7
|
import { openPipelineDb, loadCDFComponents } from '../session/db.js';
|
|
8
|
-
import { findSlotCycles, suggestCycleBreakEdge, formatCyclePath } from '../analyze/cycle-detection.js';
|
|
9
8
|
import { isEmptyPreview } from './preview-utils.js';
|
|
10
9
|
import { ServerPreviewApp, ServerPreviewConfirm, ServerApplyProgress, ServerApplyDone } from './tui/ServerApplyView.js';
|
|
11
10
|
import { SelectView, makeSelectKey } from './tui/SelectView.js';
|
|
@@ -171,75 +170,6 @@ async function resolveSharedInputs(opts) {
|
|
|
171
170
|
});
|
|
172
171
|
return { components, tokens, client };
|
|
173
172
|
}
|
|
174
|
-
// --- INTEG-4401: pre-push slot-cycle hard block ---
|
|
175
|
-
//
|
|
176
|
-
// The backend apply worker rejects cyclic slot graphs at topo-sort time, but
|
|
177
|
-
// by then the request has already been serialized, transported, and partially
|
|
178
|
-
// applied — leading to confusing partial-failure states. Detecting the same
|
|
179
|
-
// violation locally lets us fail fast with a clear message and zero
|
|
180
|
-
// side-effects. Called from the `apply push` and `apply select` flows AND
|
|
181
|
-
// from the wizard's `experiences import` push path (via `detectSlotCycles`).
|
|
182
|
-
// `apply preview` is read-only and still runs (its diff is used to warn but
|
|
183
|
-
// not to block).
|
|
184
|
-
/**
|
|
185
|
-
* Pure cycle detection — returns [] when the graph is acyclic. Callers own
|
|
186
|
-
* how to react (CLI standalone uses `assertNoSlotCycles` which exits 1; the
|
|
187
|
-
* wizard uses `detectSlotCycles` directly to route to an in-TUI error step
|
|
188
|
-
* without ever POSTing to EDSI).
|
|
189
|
-
*/
|
|
190
|
-
export function detectSlotCycles(components) {
|
|
191
|
-
const cycleInput = components.map(({ key, entry }) => ({
|
|
192
|
-
name: key,
|
|
193
|
-
slots: Object.entries(entry.$slots ?? {}).map(([slotName, slotDef]) => ({
|
|
194
|
-
name: slotName,
|
|
195
|
-
allowedComponents: slotDef.$allowedComponents ?? [],
|
|
196
|
-
})),
|
|
197
|
-
}));
|
|
198
|
-
return findSlotCycles(cycleInput);
|
|
199
|
-
}
|
|
200
|
-
/**
|
|
201
|
-
* Build a stderr-style message block for a set of cycles. Extracted so the
|
|
202
|
-
* wizard can render the same text in an in-TUI error panel and any future
|
|
203
|
-
* headless surface can log identical output.
|
|
204
|
-
*/
|
|
205
|
-
export function formatSlotCycleReport(cycles) {
|
|
206
|
-
const lines = [];
|
|
207
|
-
lines.push(`Error: manifest:components/slot-cycles — ${cycles.length} slot dependency cycle(s) detected. Push refused.`);
|
|
208
|
-
for (let i = 0; i < cycles.length; i += 1) {
|
|
209
|
-
const cycle = cycles[i];
|
|
210
|
-
lines.push(` Cycle ${i + 1}: ${formatCyclePath(cycle)}`);
|
|
211
|
-
const suggested = suggestCycleBreakEdge(cycle, cycles);
|
|
212
|
-
lines.push(` Fix: remove '${suggested.toComponent}' from ${suggested.fromComponent}.$slots.${suggested.slotName}.$allowedComponents`);
|
|
213
|
-
}
|
|
214
|
-
return lines;
|
|
215
|
-
}
|
|
216
|
-
export function assertNoSlotCycles(components) {
|
|
217
|
-
const cycles = detectSlotCycles(components);
|
|
218
|
-
if (cycles.length === 0)
|
|
219
|
-
return;
|
|
220
|
-
process.stderr.write(formatSlotCycleReport(cycles).join('\n') + '\n');
|
|
221
|
-
process.exit(1);
|
|
222
|
-
}
|
|
223
|
-
/**
|
|
224
|
-
* Reconstruct the `{key, entry}` shape from a built `ManifestPayload`. The
|
|
225
|
-
* wizard's push path holds a serialized manifest rather than the underlying
|
|
226
|
-
* CDF records, so we unwrap it here to feed `detectSlotCycles`. Skips the
|
|
227
|
-
* `$schema` sentinel key. Safe on undefined/empty manifests (returns []).
|
|
228
|
-
*/
|
|
229
|
-
export function extractComponentsFromManifest(manifest) {
|
|
230
|
-
const componentsManifest = manifest?.componentsManifest;
|
|
231
|
-
if (!componentsManifest)
|
|
232
|
-
return [];
|
|
233
|
-
const out = [];
|
|
234
|
-
for (const [key, value] of Object.entries(componentsManifest)) {
|
|
235
|
-
if (key === '$schema')
|
|
236
|
-
continue;
|
|
237
|
-
if (!value || typeof value !== 'object')
|
|
238
|
-
continue;
|
|
239
|
-
out.push({ key, entry: value });
|
|
240
|
-
}
|
|
241
|
-
return out;
|
|
242
|
-
}
|
|
243
173
|
// --- Output helpers ---
|
|
244
174
|
export function hasBreakingChangesWithImpact(preview) {
|
|
245
175
|
const allChanged = [...preview.components.changed, ...preview.tokens.changed];
|
|
@@ -515,12 +445,6 @@ export function registerApplyCommand(program) {
|
|
|
515
445
|
throw e;
|
|
516
446
|
}
|
|
517
447
|
const { components, tokens, client } = inputs;
|
|
518
|
-
// INTEG-4401: refuse before spending any credentials or bytes on a
|
|
519
|
-
// manifest the backend will reject. Cycle detection is deterministic
|
|
520
|
-
// and cheap so we run it here (not just at extract time) — this also
|
|
521
|
-
// guards `apply push --components` where extract-time cycles are not
|
|
522
|
-
// in the session DB.
|
|
523
|
-
assertNoSlotCycles(components);
|
|
524
448
|
try {
|
|
525
449
|
await client.validateToken();
|
|
526
450
|
}
|
|
@@ -699,11 +623,6 @@ export function registerApplyCommand(program) {
|
|
|
699
623
|
throw e;
|
|
700
624
|
}
|
|
701
625
|
const { components, tokens, client } = inputs;
|
|
702
|
-
// INTEG-4401: same pre-push cycle block as `apply push`. Running the
|
|
703
|
-
// check on the FULL component set (not the filtered selection) is
|
|
704
|
-
// deliberate: partial selection cannot resolve a cycle whose
|
|
705
|
-
// participants are all still present in the target space.
|
|
706
|
-
assertNoSlotCycles(components);
|
|
707
626
|
try {
|
|
708
627
|
await client.validateToken();
|
|
709
628
|
}
|
|
@@ -33,8 +33,6 @@ import { nextStateAfterPrint } from './run-print-files-helpers.js';
|
|
|
33
33
|
import { PushDecisionGateStep } from './steps/PushDecisionGateStep.js';
|
|
34
34
|
import { chooseGateAction } from './push-decision-gate-helpers.js';
|
|
35
35
|
import { ImportApiClient, ApiError } from '../../apply/api-client.js';
|
|
36
|
-
import { detectSlotCycles, extractComponentsFromManifest, formatSlotCycleReport } from '../../apply/command.js';
|
|
37
|
-
import { parseEdsiError, formatParsedEdsiError } from '../../apply/error-parser.js';
|
|
38
36
|
import { handlePreview422, applySkipValidationErrors, clearedValidationErrorState } from './wizard-422-helpers.js';
|
|
39
37
|
import { parseGenerateStderrChunk } from './wizard-generate-progress.js';
|
|
40
38
|
import { spawnGenerateChild } from './spawn-generate.js';
|
|
@@ -1036,23 +1034,6 @@ export function WizardApp({ initialSpaceId = '', initialEnvironmentId = 'master'
|
|
|
1036
1034
|
return;
|
|
1037
1035
|
}
|
|
1038
1036
|
}
|
|
1039
|
-
// INTEG-4401 Fix A — pre-push slot-cycle hard block for the wizard's
|
|
1040
|
-
// direct-API push path. The standalone `apply push` / `apply select`
|
|
1041
|
-
// commands run `assertNoSlotCycles` before ever constructing an API
|
|
1042
|
-
// client, but `experiences import` calls `client.applyImport` from here
|
|
1043
|
-
// without shelling out — so the guard has to run again on this path.
|
|
1044
|
-
// Otherwise a cyclic graph reaches EDSI and the operator sees a raw
|
|
1045
|
-
// Lambda error dump (see Fix C) instead of the clear local report.
|
|
1046
|
-
const cycles = detectSlotCycles(extractComponentsFromManifest(manifest));
|
|
1047
|
-
if (cycles.length > 0) {
|
|
1048
|
-
update({
|
|
1049
|
-
step: 'error',
|
|
1050
|
-
errorStep: 'apply push',
|
|
1051
|
-
errorMessage: formatSlotCycleReport(cycles).join('\n'),
|
|
1052
|
-
errorAllowCredentialRetry: false,
|
|
1053
|
-
});
|
|
1054
|
-
return;
|
|
1055
|
-
}
|
|
1056
1037
|
update({ step: 'pushing', pushProgress: null });
|
|
1057
1038
|
try {
|
|
1058
1039
|
const resolvedHost = resolveWizardHost(host);
|
|
@@ -1143,57 +1124,27 @@ export function WizardApp({ initialSpaceId = '', initialEnvironmentId = 'master'
|
|
|
1143
1124
|
};
|
|
1144
1125
|
}
|
|
1145
1126
|
else {
|
|
1146
|
-
// API didn't return items
|
|
1147
|
-
// counts as "created": if the server said something failed, echo it
|
|
1148
|
-
// truthfully so the summary can't lie. Historically this branch used
|
|
1149
|
-
// preview.new/changed/removed as success counts and only surfaced
|
|
1150
|
-
// failed:0, which meant a cycle rejection (summary: 0 succeeded /
|
|
1151
|
-
// 11 failed, empty items) rendered as "Done ✓ 2 Component Types
|
|
1152
|
-
// created" — the exact regression this fix guards against.
|
|
1153
|
-
const summary = operation.summary ?? { total: 0, pending: 0, succeeded: 0, failed: 0 };
|
|
1154
|
-
const anyFailure = summary.failed > 0 || operation.sys.status === 'failed' || operation.sys.status === 'partial';
|
|
1127
|
+
// API didn't return items — fall back to summary + preview counts
|
|
1155
1128
|
pushResult = {
|
|
1156
1129
|
componentTypes: {
|
|
1157
|
-
created:
|
|
1158
|
-
updated:
|
|
1159
|
-
removed:
|
|
1160
|
-
|
|
1161
|
-
// per-item breakdown we can't split components vs tokens, and
|
|
1162
|
-
// components are the dominant entity in an import.
|
|
1163
|
-
failed: summary.failed,
|
|
1130
|
+
created: preview?.components.new.length ?? 0,
|
|
1131
|
+
updated: preview?.components.changed.length ?? 0,
|
|
1132
|
+
removed: preview?.components.removed.length ?? 0,
|
|
1133
|
+
failed: 0,
|
|
1164
1134
|
},
|
|
1165
1135
|
designTokens: {
|
|
1166
|
-
created:
|
|
1167
|
-
updated:
|
|
1168
|
-
removed:
|
|
1136
|
+
created: preview?.tokens.new.length ?? 0,
|
|
1137
|
+
updated: preview?.tokens.changed.length ?? 0,
|
|
1138
|
+
removed: preview?.tokens.removed.length ?? 0,
|
|
1169
1139
|
failed: 0,
|
|
1170
1140
|
},
|
|
1171
|
-
summary,
|
|
1141
|
+
summary: operation.summary,
|
|
1172
1142
|
};
|
|
1173
1143
|
}
|
|
1174
1144
|
update({ step: 'done', pushResult });
|
|
1175
1145
|
}
|
|
1176
1146
|
catch (e) {
|
|
1177
|
-
|
|
1178
|
-
// block before handing off to ErrorStep, so cycle rejections and other
|
|
1179
|
-
// structured failures don't render as raw Lambda log lines
|
|
1180
|
-
// (timestamp / request-id / dd.trace_id / etc.).
|
|
1181
|
-
let msg;
|
|
1182
|
-
if (e instanceof ApiError) {
|
|
1183
|
-
const parsed = parseEdsiError(e.body || e.message);
|
|
1184
|
-
msg = formatParsedEdsiError(parsed, {
|
|
1185
|
-
verbose: process.env['EDSI_VERBOSE_ERRORS'] === '1',
|
|
1186
|
-
raw: e.body,
|
|
1187
|
-
});
|
|
1188
|
-
if (!msg)
|
|
1189
|
-
msg = e.message;
|
|
1190
|
-
}
|
|
1191
|
-
else if (e instanceof Error) {
|
|
1192
|
-
msg = e.message;
|
|
1193
|
-
}
|
|
1194
|
-
else {
|
|
1195
|
-
msg = 'Push failed';
|
|
1196
|
-
}
|
|
1147
|
+
const msg = e instanceof ApiError ? e.message : e instanceof Error ? e.message : 'Push failed';
|
|
1197
1148
|
update({
|
|
1198
1149
|
step: 'error',
|
|
1199
1150
|
errorStep: 'apply push',
|
|
@@ -37,6 +37,6 @@ type GenerateReviewStepProps = {
|
|
|
37
37
|
export declare function sortComponentsForSidebar<T extends {
|
|
38
38
|
key: string;
|
|
39
39
|
entry: CDFComponentEntry;
|
|
40
|
-
}>(components: T[]
|
|
40
|
+
}>(components: T[]): T[];
|
|
41
41
|
export declare function GenerateReviewStep({ extractSessionId, onFinalize, onQuit, livePreview, spaceId, environmentId, cmaToken, host, tokensPath, initialFinalizeError, }: GenerateReviewStepProps): React.ReactElement;
|
|
42
42
|
export {};
|