@contentful/experience-design-system-cli 2.12.1-dev-build-bae15c7.0 → 2.12.1-dev-build-c47114b.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 +1 -1
- package/dist/src/apply/api-client.js +45 -4
- package/dist/src/apply/command.js +1 -12
- package/dist/src/apply/preview-utils.d.ts +12 -0
- package/dist/src/apply/preview-utils.js +22 -0
- package/dist/src/credentials-store.d.ts +16 -0
- package/dist/src/credentials-store.js +22 -5
- package/dist/src/generate/agent-runner.js +40 -5
- package/dist/src/generate/command.js +2 -2
- package/dist/src/import/auto-filter-resolve.d.ts +0 -9
- package/dist/src/import/auto-filter-resolve.js +5 -5
- package/dist/src/import/command.js +25 -37
- package/dist/src/import/orchestrator.js +15 -2
- package/dist/src/import/picker-dispatch.d.ts +26 -0
- package/dist/src/import/picker-dispatch.js +44 -0
- package/dist/src/import/tui/WizardApp.d.ts +8 -2
- package/dist/src/import/tui/WizardApp.js +86 -19
- package/dist/src/import/tui/final-review-host.d.ts +8 -1
- package/dist/src/import/tui/final-review-host.js +2 -2
- package/dist/src/import/tui/push-progress.d.ts +0 -14
- package/dist/src/import/tui/push-progress.js +1 -14
- package/dist/src/import/tui/steps/GenerateReviewStep.d.ts +8 -1
- package/dist/src/import/tui/steps/GenerateReviewStep.js +16 -3
- package/dist/src/import/tui/steps/PushingStep.d.ts +2 -3
- package/dist/src/import/tui/steps/PushingStep.js +3 -20
- package/dist/src/lib/debug-logger.d.ts +49 -0
- package/dist/src/lib/debug-logger.js +229 -0
- package/dist/src/lib/debug-preamble.d.ts +14 -0
- package/dist/src/lib/debug-preamble.js +33 -0
- package/dist/src/program.js +26 -0
- package/dist/src/runs/modify-launcher.d.ts +2 -0
- package/dist/src/runs/modify-launcher.js +2 -0
- package/dist/src/runs/push-launcher.d.ts +52 -0
- package/dist/src/runs/push-launcher.js +77 -0
- package/dist/src/runs/replay-helpers.js +19 -7
- package/dist/src/setup/command.js +31 -1
- package/dist/src/setup/debug-mode-prompt.d.ts +11 -0
- package/dist/src/setup/debug-mode-prompt.js +22 -0
- package/package.json +2 -2
|
@@ -9,6 +9,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
9
9
|
import { execFile, spawn } from 'node:child_process';
|
|
10
10
|
import { mkdir } from 'node:fs/promises';
|
|
11
11
|
import { buildRunTeaserLine } from './run-teaser.js';
|
|
12
|
+
import { getDebugLogger } from '../../lib/debug-logger.js';
|
|
12
13
|
import { PathPrompt } from '../../runs/path-prompt.js';
|
|
13
14
|
import { RunPicker } from '../../runs/run-picker.js';
|
|
14
15
|
import { SaveConflictGate } from '../../runs/save-conflict.js';
|
|
@@ -28,7 +29,6 @@ import { ErrorStep } from './steps/ErrorStep.js';
|
|
|
28
29
|
import { TokenInputStep } from './steps/TokenInputStep.js';
|
|
29
30
|
import { PreviewValidationErrorStep } from './steps/PreviewValidationErrorStep.js';
|
|
30
31
|
import { PushingStep } from './steps/PushingStep.js';
|
|
31
|
-
import { computePushExpected } from './push-progress.js';
|
|
32
32
|
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';
|
|
@@ -37,6 +37,7 @@ import { handlePreview422, applySkipValidationErrors, clearedValidationErrorStat
|
|
|
37
37
|
import { parseGenerateStderrChunk } from './wizard-generate-progress.js';
|
|
38
38
|
import { spawnGenerateChild } from './spawn-generate.js';
|
|
39
39
|
import { readTokensFromPath, hasBreakingChangesWithImpact } from '../../apply/manifest.js';
|
|
40
|
+
import { isEmptyPreview } from '../../apply/preview-utils.js';
|
|
40
41
|
import { buildManifest } from '@contentful/experience-design-system-types';
|
|
41
42
|
import { openPipelineDb, loadCDFComponents, loadScopeComponents, seedCDFFromPreviewResponse, seedDefaultsFromChangedItems, backfillUnclassifiedProps, } from '../../session/db.js';
|
|
42
43
|
import { ScopeGateHost } from './scope-gate-host.js';
|
|
@@ -147,8 +148,10 @@ const WIZARD_LOG = join(tmpdir(), 'experiences-import-wizard.log');
|
|
|
147
148
|
function logStep(entry) {
|
|
148
149
|
const line = JSON.stringify({ ts: new Date().toISOString(), ...entry }) + '\n';
|
|
149
150
|
appendFileSync(WIZARD_LOG, line);
|
|
151
|
+
// Tee into the unified debug log when enabled — cheap no-op otherwise.
|
|
152
|
+
getDebugLogger().event('wizard', 'step', entry);
|
|
150
153
|
}
|
|
151
|
-
export function WizardApp({ initialSpaceId = '', initialEnvironmentId = 'master', initialCmaToken = '', initialHost, initialAgent, initialModel, initialProjectPath, host, autoAcceptScope = false, noCache = false, autoFilter = true, livePreview = true, noPush = false, noSave = false, outDirOverride, onConflictMode, selectPromptPath, generatePromptPath, seedExtractSessionId, seedGenerateSessionId, seedTokenSessionId, initialStep, initialRawTokensPath, initialRuns, onRunPicked, } = {}) {
|
|
154
|
+
export function WizardApp({ initialSpaceId = '', initialEnvironmentId = 'master', initialCmaToken = '', initialHost, initialAgent, initialModel, initialProjectPath, host, autoAcceptScope = false, noCache = false, autoFilter = true, livePreview = true, noPush = false, noSave = false, outDirOverride, onConflictMode, selectPromptPath, generatePromptPath, seedExtractSessionId, seedGenerateSessionId, seedTokenSessionId, seedTokensPath, initialStep, initialRawTokensPath, initialRuns, onRunPicked, } = {}) {
|
|
152
155
|
const defaultConfiguredHost = toConfiguredHost(host || process.env['EDS_HOST']) ?? DEFAULT_CONFIGURED_HOST;
|
|
153
156
|
const resolveWizardHost = (hostValue) => hostValue || defaultConfiguredHost;
|
|
154
157
|
const { stdout } = useStdout();
|
|
@@ -182,22 +185,31 @@ export function WizardApp({ initialSpaceId = '', initialEnvironmentId = 'master'
|
|
|
182
185
|
// GenerateReviewStep loads its data off `state.extractSessionId`, so all
|
|
183
186
|
// we need to do here is seed the IDs and the step.
|
|
184
187
|
const modifyEntryReady = !!seedExtractSessionId && initialStep === 'final-review';
|
|
188
|
+
// Push-from-picker entry: the run-picker's Push action mounts the wizard
|
|
189
|
+
// with seeded session IDs and `initialStep: 'push-from-picker'` so the
|
|
190
|
+
// operator sees the same preview + push UX as a fresh import. Skips
|
|
191
|
+
// welcome, token-input, scope-gate, final-review, and push-decision-gate;
|
|
192
|
+
// an effect below dispatches `runPreview` on mount which drives
|
|
193
|
+
// previewing → pushing → done.
|
|
194
|
+
const pushFromPickerReady = !!seedExtractSessionId && initialStep === 'push-from-picker';
|
|
185
195
|
// Headless raw-tokens entry: when the operator passed `--raw-tokens <path>`
|
|
186
196
|
// the CLI seeds this prop. Skip welcome + token-input and land on the
|
|
187
197
|
// `generating-tokens` step which already drives the token-classification
|
|
188
198
|
// subprocess off `state.rawTokensPath`. Modify-entry wins if both are set.
|
|
189
|
-
const rawTokensEntryReady = !modifyEntryReady && !!initialRawTokensPath;
|
|
199
|
+
const rawTokensEntryReady = !modifyEntryReady && !pushFromPickerReady && !!initialRawTokensPath;
|
|
190
200
|
const initialStepResolved = modifyEntryReady
|
|
191
201
|
? 'final-review'
|
|
192
|
-
:
|
|
193
|
-
? '
|
|
194
|
-
:
|
|
195
|
-
? '
|
|
196
|
-
:
|
|
202
|
+
: pushFromPickerReady
|
|
203
|
+
? 'push-from-picker'
|
|
204
|
+
: rawTokensEntryReady
|
|
205
|
+
? 'generating-tokens'
|
|
206
|
+
: initialProjectPath
|
|
207
|
+
? 'token-input'
|
|
208
|
+
: 'welcome';
|
|
197
209
|
const initialOutDir = initialProjectPath ? join(resolve(initialProjectPath), '.contentful') : '';
|
|
198
|
-
const initialTokensPath = modifyEntryReady && initialOutDir ? join(initialOutDir, 'tokens.json') : '';
|
|
210
|
+
const initialTokensPath = (modifyEntryReady || pushFromPickerReady) && initialOutDir ? join(initialOutDir, 'tokens.json') : '';
|
|
199
211
|
const [state, setState] = useState({
|
|
200
|
-
step: modifyEntryReady || rawTokensEntryReady
|
|
212
|
+
step: modifyEntryReady || rawTokensEntryReady || pushFromPickerReady
|
|
201
213
|
? initialStepResolved
|
|
202
214
|
: initialRuns && initialRuns.length > 0
|
|
203
215
|
? 'run-picker'
|
|
@@ -207,7 +219,7 @@ export function WizardApp({ initialSpaceId = '', initialEnvironmentId = 'master'
|
|
|
207
219
|
projectPath: initialProjectPath ?? '',
|
|
208
220
|
outDir: initialOutDir,
|
|
209
221
|
rawTokensPath: rawTokensEntryReady ? initialRawTokensPath : '',
|
|
210
|
-
tokensPath: initialTokensPath,
|
|
222
|
+
tokensPath: seedTokensPath ?? initialTokensPath,
|
|
211
223
|
tokenSourceChanged: null,
|
|
212
224
|
skipComponents: false,
|
|
213
225
|
tokenSessionId: seedTokenSessionId ?? null,
|
|
@@ -231,7 +243,6 @@ export function WizardApp({ initialSpaceId = '', initialEnvironmentId = 'master'
|
|
|
231
243
|
serverPreview: null,
|
|
232
244
|
manifest: null,
|
|
233
245
|
pushProgress: null,
|
|
234
|
-
pushExpected: null,
|
|
235
246
|
pushResult: {
|
|
236
247
|
componentTypes: { created: 0, updated: 0, removed: 0, failed: 0 },
|
|
237
248
|
designTokens: { created: 0, updated: 0, removed: 0, failed: 0 },
|
|
@@ -251,6 +262,7 @@ export function WizardApp({ initialSpaceId = '', initialEnvironmentId = 'master'
|
|
|
251
262
|
generatePrefetchError: null,
|
|
252
263
|
credentialsSkipped: false,
|
|
253
264
|
lastRunId: null,
|
|
265
|
+
finalizeErrorBanner: null,
|
|
254
266
|
});
|
|
255
267
|
useEffect(() => {
|
|
256
268
|
sessionRef.current = {
|
|
@@ -357,7 +369,7 @@ export function WizardApp({ initialSpaceId = '', initialEnvironmentId = 'master'
|
|
|
357
369
|
});
|
|
358
370
|
return;
|
|
359
371
|
}
|
|
360
|
-
const sessionMatch = /^session
|
|
372
|
+
const sessionMatch = /^session=(.+)$/m.exec(result.stdout);
|
|
361
373
|
const tokenSessionId = sessionMatch ? sessionMatch[1].trim() : null;
|
|
362
374
|
const tokensPath = join(outDir, 'tokens.json');
|
|
363
375
|
const printArgs = ['print', 'tokens', '--out', tokensPath];
|
|
@@ -906,7 +918,27 @@ export function WizardApp({ initialSpaceId = '', initialEnvironmentId = 'master'
|
|
|
906
918
|
db.close();
|
|
907
919
|
}
|
|
908
920
|
}
|
|
909
|
-
|
|
921
|
+
// INTEG-4411 refined: preview-aware finalize guard. If the resulting
|
|
922
|
+
// push would be a pure no-op across every diff bucket, don't send an
|
|
923
|
+
// empty manifest to EDSI — route back to `final-review` with an inline
|
|
924
|
+
// banner. Accepted components, rejections that remove server-side
|
|
925
|
+
// components, and token-only diffs all keep this branch from firing.
|
|
926
|
+
if (isEmptyPreview(preview)) {
|
|
927
|
+
update({
|
|
928
|
+
step: 'final-review',
|
|
929
|
+
finalizeErrorBanner: 'Nothing to push — accept a component, reject a component that exists in Contentful, or quit.',
|
|
930
|
+
serverPreview: preview,
|
|
931
|
+
...clearedValidationErrorState(),
|
|
932
|
+
});
|
|
933
|
+
return;
|
|
934
|
+
}
|
|
935
|
+
update({
|
|
936
|
+
step: 'preview-gate',
|
|
937
|
+
serverPreview: preview,
|
|
938
|
+
manifest,
|
|
939
|
+
finalizeErrorBanner: null,
|
|
940
|
+
...clearedValidationErrorState(),
|
|
941
|
+
});
|
|
910
942
|
}
|
|
911
943
|
catch (e) {
|
|
912
944
|
if (e instanceof ApiError) {
|
|
@@ -1002,8 +1034,7 @@ export function WizardApp({ initialSpaceId = '', initialEnvironmentId = 'master'
|
|
|
1002
1034
|
return;
|
|
1003
1035
|
}
|
|
1004
1036
|
}
|
|
1005
|
-
|
|
1006
|
-
update({ step: 'pushing', pushExpected, pushProgress: null });
|
|
1037
|
+
update({ step: 'pushing', pushProgress: null });
|
|
1007
1038
|
try {
|
|
1008
1039
|
const resolvedHost = resolveWizardHost(host);
|
|
1009
1040
|
const client = new ImportApiClient({
|
|
@@ -1303,6 +1334,22 @@ export function WizardApp({ initialSpaceId = '', initialEnvironmentId = 'master'
|
|
|
1303
1334
|
})();
|
|
1304
1335
|
}
|
|
1305
1336
|
}, [state.step]); // intentional: only re-run when step changes
|
|
1337
|
+
// Push-from-picker entry: on mount, dispatch runPreview to jump through
|
|
1338
|
+
// previewing → preview-gate → pushing → done. We DON'T wait for the
|
|
1339
|
+
// operator to interact with any pre-preview screen; the run-picker Push
|
|
1340
|
+
// action is a "click Push and watch it happen" flow. `preview-gate` still
|
|
1341
|
+
// renders after runPreview resolves — that's the diff-review screen where
|
|
1342
|
+
// the operator can confirm/quit/edit. We route straight past
|
|
1343
|
+
// push-decision-gate (which is for save-vs-push decisions on a fresh run).
|
|
1344
|
+
const pushFromPickerDispatched = useRef(false);
|
|
1345
|
+
useEffect(() => {
|
|
1346
|
+
if (state.step !== 'push-from-picker')
|
|
1347
|
+
return;
|
|
1348
|
+
if (pushFromPickerDispatched.current)
|
|
1349
|
+
return;
|
|
1350
|
+
pushFromPickerDispatched.current = true;
|
|
1351
|
+
void runPreview(state.extractSessionId, state.tokensPath, state.spaceId, state.environmentId, state.cmaToken, state.host);
|
|
1352
|
+
}, [state.step]);
|
|
1306
1353
|
// ── Render ────────────────────────────────────────────────────────────────────────────
|
|
1307
1354
|
const noQuitSteps = [
|
|
1308
1355
|
'run-picker',
|
|
@@ -1313,6 +1360,7 @@ export function WizardApp({ initialSpaceId = '', initialEnvironmentId = 'master'
|
|
|
1313
1360
|
'generating',
|
|
1314
1361
|
'printing',
|
|
1315
1362
|
'previewing',
|
|
1363
|
+
'push-from-picker',
|
|
1316
1364
|
'pushing',
|
|
1317
1365
|
];
|
|
1318
1366
|
const hints = noQuitSteps.includes(state.step) ? [] : [{ key: 'q', label: 'quit' }];
|
|
@@ -1454,8 +1502,16 @@ export function WizardApp({ initialSpaceId = '', initialEnvironmentId = 'master'
|
|
|
1454
1502
|
return (_jsx(RunningStep, { stepNumber: stepNum, totalSteps: totalSteps, title: "Generating definitions", description: `${formatAcceptanceSummary({ accepted: state.acceptedCount, autoRejected: state.autoRejectedCount })} ${state.agent} is mapping your TypeScript types to Contentful's CDF format.${hasTokens ? ' Using your design tokens for prop resolution.' : ''}`, detail: progressDetail }));
|
|
1455
1503
|
}
|
|
1456
1504
|
case 'final-review': {
|
|
1457
|
-
return (_jsx(FinalReviewHost, { extractSessionId: state.extractSessionId, generatedCount: state.generatedCount, autoAccept: autoAcceptScope, livePreview: livePreview, spaceId: state.spaceId, environmentId: state.environmentId, cmaToken: state.cmaToken, host: state.host, tokensPath: state.tokensPath, onFinalize: (accepted, rejected, unresolved) => {
|
|
1505
|
+
return (_jsx(FinalReviewHost, { extractSessionId: state.extractSessionId, generatedCount: state.generatedCount, autoAccept: autoAcceptScope, livePreview: livePreview, spaceId: state.spaceId, environmentId: state.environmentId, cmaToken: state.cmaToken, host: state.host, tokensPath: state.tokensPath, initialFinalizeError: state.finalizeErrorBanner, onFinalize: (accepted, rejected, unresolved) => {
|
|
1458
1506
|
process.stderr.write(`Accepted: ${accepted} Rejected: ${rejected} Unresolved: ${unresolved}\n`);
|
|
1507
|
+
// INTEG-4411 refined: no `accepted === 0` up-front block here.
|
|
1508
|
+
// A zero-accepted finalize can still be a valid push when the
|
|
1509
|
+
// operator explicitly rejected component(s) that exist server-
|
|
1510
|
+
// side (→ REMOVALS) or when tokens carry a diff. The load-
|
|
1511
|
+
// bearing no-op check consults the preview response inside
|
|
1512
|
+
// `runPreview` below (see `isEmptyPreview` branch) and routes
|
|
1513
|
+
// back to `final-review` with `finalizeErrorBanner` set when
|
|
1514
|
+
// the diff is truly empty.
|
|
1459
1515
|
if (noPush) {
|
|
1460
1516
|
update({ generatedAcceptedCount: accepted });
|
|
1461
1517
|
void startSaveFlow();
|
|
@@ -1507,7 +1563,17 @@ export function WizardApp({ initialSpaceId = '', initialEnvironmentId = 'master'
|
|
|
1507
1563
|
case 'credentials':
|
|
1508
1564
|
return (_jsx(CredentialsStep, { initialSpaceId: state.spaceId, initialEnvironmentId: state.environmentId, initialCmaToken: state.cmaToken, initialHost: state.host, error: state.credentialsError || undefined, validating: state.credentialsValidating, generatePrefetchStatus: state.generatePrefetchStatus, generatePrefetchError: state.generatePrefetchError, onConfirm: (spaceId, environmentId, cmaToken, host) => {
|
|
1509
1565
|
void confirmCredentials(spaceId, environmentId, cmaToken, host);
|
|
1510
|
-
},
|
|
1566
|
+
},
|
|
1567
|
+
// INTEG-4410: unify the unchanged-form path with the changed-form
|
|
1568
|
+
// path so credentials are ALWAYS persisted to disk on submit.
|
|
1569
|
+
// The pre-fix wiring routed `onContinue` at
|
|
1570
|
+
// `advanceWithCredentials`, which only mutated state — so on
|
|
1571
|
+
// `--modify` (where the wizard seeds `run.pushedTo` into the
|
|
1572
|
+
// form, not disk) an operator pressing Enter never wrote to
|
|
1573
|
+
// ~/.config/experiences/credentials.json and disk stayed stale.
|
|
1574
|
+
onContinue: (spaceId, environmentId, cmaToken, host) => {
|
|
1575
|
+
void confirmCredentials(spaceId, environmentId, cmaToken, host);
|
|
1576
|
+
}, onRetryPrefetch: state.generatePrefetchStatus === 'failed' && sessionRef.current.extractSessionId
|
|
1511
1577
|
? () => {
|
|
1512
1578
|
const sid = sessionRef.current.extractSessionId;
|
|
1513
1579
|
void startGeneratePrefetch(sid, state.tokensPath);
|
|
@@ -1535,6 +1601,7 @@ export function WizardApp({ initialSpaceId = '', initialEnvironmentId = 'master'
|
|
|
1535
1601
|
// value is kept in the union for back-compat with existing imports/tests
|
|
1536
1602
|
// but should never actually be set — `validateCredentials` keeps the
|
|
1537
1603
|
// step on 'credentials' and toggles `credentialsValidating` instead.
|
|
1604
|
+
case 'push-from-picker':
|
|
1538
1605
|
case 'previewing':
|
|
1539
1606
|
return (_jsx(RunningStep, { stepNumber: totalSteps, totalSteps: totalSteps, title: "Computing diff", description: "Computing diff against your Contentful space..." }));
|
|
1540
1607
|
case 'preview-gate':
|
|
@@ -1546,7 +1613,7 @@ export function WizardApp({ initialSpaceId = '', initialEnvironmentId = 'master'
|
|
|
1546
1613
|
void startSaveFlow();
|
|
1547
1614
|
}, onQuit: () => process.exit(0) }));
|
|
1548
1615
|
case 'pushing':
|
|
1549
|
-
return
|
|
1616
|
+
return _jsx(PushingStep, { stepNumber: totalSteps, totalSteps: totalSteps, progress: state.pushProgress });
|
|
1550
1617
|
case 'path-prompt':
|
|
1551
1618
|
return (_jsx(PathPrompt, { defaultPath: state.outDir, onSubmit: (submitted) => {
|
|
1552
1619
|
void (async () => {
|
|
@@ -11,5 +11,12 @@ export type FinalReviewHostProps = {
|
|
|
11
11
|
cmaToken?: string;
|
|
12
12
|
host?: string;
|
|
13
13
|
tokensPath?: string;
|
|
14
|
+
/**
|
|
15
|
+
* INTEG-4411 refined: message displayed as an inline banner when the wizard
|
|
16
|
+
* routes back here after the preview API returned an empty diff (pure
|
|
17
|
+
* no-op push). Cleared on the next `a` / `A` keystroke inside
|
|
18
|
+
* GenerateReviewStep.
|
|
19
|
+
*/
|
|
20
|
+
initialFinalizeError?: string | null;
|
|
14
21
|
};
|
|
15
|
-
export declare function FinalReviewHost({ extractSessionId, generatedCount, autoAccept, onFinalize, onQuit, livePreview, spaceId, environmentId, cmaToken, host, tokensPath, }: FinalReviewHostProps): React.ReactElement;
|
|
22
|
+
export declare function FinalReviewHost({ extractSessionId, generatedCount, autoAccept, onFinalize, onQuit, livePreview, spaceId, environmentId, cmaToken, host, tokensPath, initialFinalizeError, }: FinalReviewHostProps): React.ReactElement;
|
|
@@ -2,14 +2,14 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { Box, Text } from 'ink';
|
|
3
3
|
import React from 'react';
|
|
4
4
|
import { GenerateReviewStep } from './steps/GenerateReviewStep.js';
|
|
5
|
-
export function FinalReviewHost({ extractSessionId, generatedCount, autoAccept, onFinalize, onQuit, livePreview, spaceId, environmentId, cmaToken, host, tokensPath, }) {
|
|
5
|
+
export function FinalReviewHost({ extractSessionId, generatedCount, autoAccept, onFinalize, onQuit, livePreview, spaceId, environmentId, cmaToken, host, tokensPath, initialFinalizeError, }) {
|
|
6
6
|
if (!extractSessionId) {
|
|
7
7
|
return (_jsx(Box, { paddingX: 2, paddingY: 1, children: _jsx(Text, { color: "red", children: "Error: no session ID \u2014 cannot load generated definitions." }) }));
|
|
8
8
|
}
|
|
9
9
|
if (autoAccept) {
|
|
10
10
|
return _jsx(FinalReviewAutoAccept, { generatedCount: generatedCount, onFinalize: onFinalize });
|
|
11
11
|
}
|
|
12
|
-
return (_jsx(GenerateReviewStep, { extractSessionId: extractSessionId, onFinalize: onFinalize, onQuit: onQuit, livePreview: livePreview, spaceId: spaceId, environmentId: environmentId, cmaToken: cmaToken, host: host, tokensPath: tokensPath }));
|
|
12
|
+
return (_jsx(GenerateReviewStep, { extractSessionId: extractSessionId, onFinalize: onFinalize, onQuit: onQuit, livePreview: livePreview, spaceId: spaceId, environmentId: environmentId, cmaToken: cmaToken, host: host, tokensPath: tokensPath, initialFinalizeError: initialFinalizeError }));
|
|
13
13
|
}
|
|
14
14
|
function FinalReviewAutoAccept({ generatedCount, onFinalize, }) {
|
|
15
15
|
React.useEffect(() => {
|
|
@@ -1,16 +1,3 @@
|
|
|
1
|
-
import type { ServerPreviewResponse } from '@contentful/experience-design-system-types';
|
|
2
|
-
export type PushExpected = {
|
|
3
|
-
componentTypes: {
|
|
4
|
-
create: number;
|
|
5
|
-
update: number;
|
|
6
|
-
remove: number;
|
|
7
|
-
};
|
|
8
|
-
designTokens: {
|
|
9
|
-
create: number;
|
|
10
|
-
update: number;
|
|
11
|
-
remove: number;
|
|
12
|
-
};
|
|
13
|
-
};
|
|
14
1
|
export type PushProgress = {
|
|
15
2
|
kind: 'queued';
|
|
16
3
|
operationId: string;
|
|
@@ -20,4 +7,3 @@ export type PushProgress = {
|
|
|
20
7
|
total: number;
|
|
21
8
|
current: string | null;
|
|
22
9
|
} | null;
|
|
23
|
-
export declare function computePushExpected(preview: ServerPreviewResponse): PushExpected;
|
|
@@ -1,14 +1 @@
|
|
|
1
|
-
export
|
|
2
|
-
return {
|
|
3
|
-
componentTypes: {
|
|
4
|
-
create: preview.components?.new?.length ?? 0,
|
|
5
|
-
update: preview.components?.changed?.length ?? 0,
|
|
6
|
-
remove: preview.components?.removed?.length ?? 0,
|
|
7
|
-
},
|
|
8
|
-
designTokens: {
|
|
9
|
-
create: preview.tokens?.new?.length ?? 0,
|
|
10
|
-
update: preview.tokens?.changed?.length ?? 0,
|
|
11
|
-
remove: preview.tokens?.removed?.length ?? 0,
|
|
12
|
-
},
|
|
13
|
-
};
|
|
14
|
-
}
|
|
1
|
+
export {};
|
|
@@ -16,6 +16,13 @@ type GenerateReviewStepProps = {
|
|
|
16
16
|
cmaToken?: string;
|
|
17
17
|
host?: string;
|
|
18
18
|
tokensPath?: string;
|
|
19
|
+
/**
|
|
20
|
+
* INTEG-4411 refined: initial value for the inline `finalizeError` banner.
|
|
21
|
+
* The wizard sets this when it routes back to `final-review` after the
|
|
22
|
+
* preview API returned an empty diff (pure no-op push). Cleared on the
|
|
23
|
+
* next `a` / `A` keystroke.
|
|
24
|
+
*/
|
|
25
|
+
initialFinalizeError?: string | null;
|
|
19
26
|
};
|
|
20
27
|
/**
|
|
21
28
|
* Sort components for the final-review sidebar so the underlying data array
|
|
@@ -31,5 +38,5 @@ export declare function sortComponentsForSidebar<T extends {
|
|
|
31
38
|
key: string;
|
|
32
39
|
entry: CDFComponentEntry;
|
|
33
40
|
}>(components: T[]): T[];
|
|
34
|
-
export declare function GenerateReviewStep({ extractSessionId, onFinalize, onQuit, livePreview, spaceId, environmentId, cmaToken, host, tokensPath, }: GenerateReviewStepProps): React.ReactElement;
|
|
41
|
+
export declare function GenerateReviewStep({ extractSessionId, onFinalize, onQuit, livePreview, spaceId, environmentId, cmaToken, host, tokensPath, initialFinalizeError, }: GenerateReviewStepProps): React.ReactElement;
|
|
35
42
|
export {};
|
|
@@ -35,7 +35,7 @@ export function sortComponentsForSidebar(components) {
|
|
|
35
35
|
}
|
|
36
36
|
const VISIBLE_COUNT = 20;
|
|
37
37
|
const PANEL_HEIGHT = 22;
|
|
38
|
-
export function GenerateReviewStep({ extractSessionId, onFinalize, onQuit, livePreview = true, spaceId = '', environmentId = '', cmaToken = '', host = '', tokensPath = '', }) {
|
|
38
|
+
export function GenerateReviewStep({ extractSessionId, onFinalize, onQuit, livePreview = true, spaceId = '', environmentId = '', cmaToken = '', host = '', tokensPath = '', initialFinalizeError = null, }) {
|
|
39
39
|
const { stdout } = useStdout();
|
|
40
40
|
const terminalWidth = stdout?.columns ?? 80;
|
|
41
41
|
const [components, setComponents] = useState([]);
|
|
@@ -51,6 +51,9 @@ export function GenerateReviewStep({ extractSessionId, onFinalize, onQuit, liveP
|
|
|
51
51
|
const [showJson, setShowJson] = useState(false);
|
|
52
52
|
const [draftValue, setDraftValue] = useState('');
|
|
53
53
|
const [saveError, setSaveError] = useState(null);
|
|
54
|
+
// INTEG-4411: inline banner shown when the operator tries to finalize
|
|
55
|
+
// with zero accepted components. Cleared on the next 'a' or 'A' press.
|
|
56
|
+
const [finalizeError, setFinalizeError] = useState(initialFinalizeError);
|
|
54
57
|
// Feature 1: per-component review metadata (rationale + source location)
|
|
55
58
|
// for the currently-selected component. Reloaded when selection changes.
|
|
56
59
|
const [reviewMetadata, setReviewMetadata] = useState(null);
|
|
@@ -192,6 +195,15 @@ export function GenerateReviewStep({ extractSessionId, onFinalize, onQuit, liveP
|
|
|
192
195
|
// 'generate-rejected' so loadCDFComponents excludes it from the manifest.
|
|
193
196
|
// The operator told us they want accept-to-ship semantics — leaving a
|
|
194
197
|
// component unresolved should NOT silently push it (Pilot-2026-06-24 R2).
|
|
198
|
+
const acceptedCount = components.filter((c) => c.status === 'accepted').length;
|
|
199
|
+
// INTEG-4411 refined: DO NOT block on `acceptedCount === 0` up-front.
|
|
200
|
+
// A push with zero accepted but one or more rejections targeting a
|
|
201
|
+
// component that exists server-side still produces REMOVALS — a valid
|
|
202
|
+
// push, not a no-op. Same for token-only diffs. The load-bearing no-op
|
|
203
|
+
// check lives downstream in WizardApp.runPreview, which consults the
|
|
204
|
+
// preview response and only blocks when every diff bucket is empty.
|
|
205
|
+
// We keep the `finalizeError` state so the wizard can route back here
|
|
206
|
+
// with an inline banner when that downstream check fires.
|
|
195
207
|
const explicitlyRejected = components.filter((c) => c.status === 'rejected').map((c) => c.key);
|
|
196
208
|
const unresolved = components.filter((c) => c.status === 'needs-review').map((c) => c.key);
|
|
197
209
|
const toReject = [...explicitlyRejected, ...unresolved];
|
|
@@ -215,7 +227,6 @@ export function GenerateReviewStep({ extractSessionId, onFinalize, onQuit, liveP
|
|
|
215
227
|
db.close();
|
|
216
228
|
}
|
|
217
229
|
}
|
|
218
|
-
const acceptedCount = components.filter((c) => c.status === 'accepted').length;
|
|
219
230
|
onFinalize(acceptedCount, explicitlyRejected.length, unresolved.length);
|
|
220
231
|
};
|
|
221
232
|
const handleEditSave = () => {
|
|
@@ -405,6 +416,7 @@ export function GenerateReviewStep({ extractSessionId, onFinalize, onQuit, liveP
|
|
|
405
416
|
}
|
|
406
417
|
if (input === 'a') {
|
|
407
418
|
updateStatus(selectedIdx, 'accepted');
|
|
419
|
+
setFinalizeError(null);
|
|
408
420
|
return;
|
|
409
421
|
}
|
|
410
422
|
if (input === 'r') {
|
|
@@ -413,6 +425,7 @@ export function GenerateReviewStep({ extractSessionId, onFinalize, onQuit, liveP
|
|
|
413
425
|
}
|
|
414
426
|
if (input === 'A') {
|
|
415
427
|
setComponents((prev) => prev.map((c) => (c.status === 'needs-review' ? { ...c, status: 'accepted' } : c)));
|
|
428
|
+
setFinalizeError(null);
|
|
416
429
|
return;
|
|
417
430
|
}
|
|
418
431
|
if (input === 'J') {
|
|
@@ -513,7 +526,7 @@ export function GenerateReviewStep({ extractSessionId, onFinalize, onQuit, liveP
|
|
|
513
526
|
if (!hasCounts)
|
|
514
527
|
return null;
|
|
515
528
|
return (_jsxs(Box, { children: [_jsx(Text, { children: 'Preview: ' }), _jsx(Text, { color: "green", children: `${counts.new} new` }), _jsx(Text, { children: ' · ' }), _jsx(Text, { color: "yellow", children: `${counts.changed} changed` }), _jsx(Text, { children: ' · ' }), _jsx(Text, { dimColor: true, children: `${counts.removed} removed` }), removedComponents.length > 0 && _jsx(Text, { dimColor: true, children: ' ([d] removed list)' }), _jsx(Text, { children: ' · ' }), _jsx(Text, { color: "red", bold: true, children: `${counts.breaking} breaking` })] }));
|
|
516
|
-
})(), !dialogOpen && emptyCount > 0 && (_jsx(Text, { color: "yellow", children: `⚠ ${emptyCount} component${emptyCount === 1 ? '' : 's'} had no classifiable props — review with care` })), !dialogOpen && (_jsxs(Box, { children: [_jsx(Sidebar, { components: sidebarItems, selectedId: selected?.key ?? null, focused: sidebarFocused, scrollOffset: sidebarScrollOffset, visibleCount: VISIBLE_COUNT, onSelect: (id) => {
|
|
529
|
+
})(), !dialogOpen && emptyCount > 0 && (_jsx(Text, { color: "yellow", children: `⚠ ${emptyCount} component${emptyCount === 1 ? '' : 's'} had no classifiable props — review with care` })), !dialogOpen && finalizeError && _jsx(Text, { color: "red", children: `⚠ ${finalizeError}` }), !dialogOpen && (_jsxs(Box, { children: [_jsx(Sidebar, { components: sidebarItems, selectedId: selected?.key ?? null, focused: sidebarFocused, scrollOffset: sidebarScrollOffset, visibleCount: VISIBLE_COUNT, onSelect: (id) => {
|
|
517
530
|
const idx = components.findIndex((c) => c.key === id);
|
|
518
531
|
if (idx >= 0) {
|
|
519
532
|
setSelectedIdx(idx);
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import type {
|
|
2
|
+
import type { PushProgress } from '../push-progress.js';
|
|
3
3
|
type PushingStepProps = {
|
|
4
4
|
stepNumber: number;
|
|
5
5
|
totalSteps: number;
|
|
6
|
-
expected: PushExpected | null;
|
|
7
6
|
progress: PushProgress;
|
|
8
7
|
};
|
|
9
|
-
export declare function PushingStep({ stepNumber, totalSteps,
|
|
8
|
+
export declare function PushingStep({ stepNumber, totalSteps, progress }: PushingStepProps): React.ReactElement;
|
|
10
9
|
export {};
|
|
@@ -1,25 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useState } from 'react';
|
|
3
3
|
import { Box, Text } from 'ink';
|
|
4
4
|
const SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
5
|
-
|
|
6
|
-
create: 'Creating',
|
|
7
|
-
update: 'Updating',
|
|
8
|
-
remove: 'Deleting',
|
|
9
|
-
};
|
|
10
|
-
function ActionRow({ label, expectedCount, progress, }) {
|
|
11
|
-
// Per spec: do NOT fake proportional tallies. Left side stays "?/N" until
|
|
12
|
-
// terminal status surfaces real per-action counts.
|
|
13
|
-
const left = progress && progress.kind === 'progress' ? '?' : '?';
|
|
14
|
-
return (_jsxs(Box, { gap: 1, children: [_jsxs(Text, { children: [" ", label] }), _jsxs(Text, { dimColor: true, children: [left, "/", expectedCount] })] }));
|
|
15
|
-
}
|
|
16
|
-
function EntitySection({ title, counts, progress, }) {
|
|
17
|
-
const visibleActions = ['create', 'update', 'remove'].filter((k) => counts[k] > 0);
|
|
18
|
-
if (visibleActions.length === 0)
|
|
19
|
-
return null;
|
|
20
|
-
return (_jsxs(Box, { flexDirection: "column", gap: 0, children: [_jsx(Text, { bold: true, children: title }), visibleActions.map((k) => (_jsx(ActionRow, { label: ACTION_LABELS[k], expectedCount: counts[k], progress: progress }, k)))] }));
|
|
21
|
-
}
|
|
22
|
-
export function PushingStep({ stepNumber, totalSteps, expected, progress }) {
|
|
5
|
+
export function PushingStep({ stepNumber, totalSteps, progress }) {
|
|
23
6
|
const [frame, setFrame] = useState(0);
|
|
24
7
|
const [elapsed, setElapsed] = useState(0);
|
|
25
8
|
useEffect(() => {
|
|
@@ -36,5 +19,5 @@ export function PushingStep({ stepNumber, totalSteps, expected, progress }) {
|
|
|
36
19
|
const operationId = progress && progress.kind === 'queued' ? progress.operationId : null;
|
|
37
20
|
const showGlobal = progress && progress.kind === 'progress';
|
|
38
21
|
const showCurrent = progress && progress.kind === 'progress' && progress.current ? progress.current : null;
|
|
39
|
-
return (_jsxs(Box, { flexDirection: "column", gap: 1, paddingX: 2, paddingY: 1, children: [_jsxs(Box, { flexDirection: "column", gap: 0, children: [_jsx(Text, { dimColor: true, children: '─'.repeat(40) }), _jsxs(Box, { gap: 1, children: [_jsxs(Text, { bold: true, children: ["Step ", stepNumber, " of ", totalSteps] }), _jsx(Text, { bold: true, children: "\u2014" }), _jsx(Text, { bold: true, children: "Push to Contentful" })] }), _jsx(Text, { dimColor: true, children: '─'.repeat(40) })] }), _jsx(Text, { children: "Writing component types and design tokens to your Contentful space..." }), operationId && (_jsxs(Box, { gap: 1, children: [_jsx(Text, { dimColor: true, children: "Operation:" }), _jsx(Text, { children: operationId })] })),
|
|
22
|
+
return (_jsxs(Box, { flexDirection: "column", gap: 1, paddingX: 2, paddingY: 1, children: [_jsxs(Box, { flexDirection: "column", gap: 0, children: [_jsx(Text, { dimColor: true, children: '─'.repeat(40) }), _jsxs(Box, { gap: 1, children: [_jsxs(Text, { bold: true, children: ["Step ", stepNumber, " of ", totalSteps] }), _jsx(Text, { bold: true, children: "\u2014" }), _jsx(Text, { bold: true, children: "Push to Contentful" })] }), _jsx(Text, { dimColor: true, children: '─'.repeat(40) })] }), _jsx(Text, { children: "Writing component types and design tokens to your Contentful space..." }), operationId && (_jsxs(Box, { gap: 1, children: [_jsx(Text, { dimColor: true, children: "Operation:" }), _jsx(Text, { children: operationId })] })), showGlobal && progress && progress.kind === 'progress' && (_jsxs(Box, { gap: 1, children: [_jsx(Text, { color: "cyan", children: SPINNER_FRAMES[frame] }), _jsxs(Text, { dimColor: true, children: [progress.processed, "/", progress.total, " entities"] })] })), showCurrent && (_jsxs(Box, { gap: 1, children: [_jsx(Text, { dimColor: true, children: "Now processing:" }), _jsx(Text, { children: showCurrent })] })), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { dimColor: true, children: ["Elapsed: ", elapsedStr] }) })] }));
|
|
40
23
|
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
export type DebugCategory = 'config' | 'agent' | 'apply' | 'import' | 'analyze' | 'filter' | 'session' | 'runs' | 'tui' | 'wizard' | 'setup' | 'output' | 'other';
|
|
2
|
+
export interface DebugLogger {
|
|
3
|
+
readonly enabled: boolean;
|
|
4
|
+
readonly path: string | null;
|
|
5
|
+
event(category: DebugCategory, name: string, payload?: Record<string, unknown>): void;
|
|
6
|
+
}
|
|
7
|
+
export declare function redactForDebug(payload: unknown): unknown;
|
|
8
|
+
export interface InitDebugLoggerOptions {
|
|
9
|
+
/** Effective on/off decision (flag > env > config, resolved by caller). */
|
|
10
|
+
enabled: boolean;
|
|
11
|
+
/** Command name — recorded in the session-open event for filtering. */
|
|
12
|
+
command?: string;
|
|
13
|
+
/** Override log root (defaults to ~/.contentful/experience-design-system-cli/debug/). */
|
|
14
|
+
root?: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Initialize the process-wide debug logger. Safe to call multiple times —
|
|
18
|
+
* subsequent calls return the existing instance.
|
|
19
|
+
*
|
|
20
|
+
* If EDSI_DEBUG_LOG is already set in the environment, this process joins
|
|
21
|
+
* that log file (used by spawned subprocesses so a whole `experiences import`
|
|
22
|
+
* flow lands in a single file).
|
|
23
|
+
*/
|
|
24
|
+
export declare function initDebugLogger(opts: InitDebugLoggerOptions): DebugLogger;
|
|
25
|
+
export declare function getDebugLogger(): DebugLogger;
|
|
26
|
+
/** Test-only: reset the singleton. */
|
|
27
|
+
export declare function __resetDebugLoggerForTest(): void;
|
|
28
|
+
/**
|
|
29
|
+
* Resolve effective debug-mode setting from three sources.
|
|
30
|
+
*
|
|
31
|
+
* Precedence (highest first):
|
|
32
|
+
* 1. `--debug` / `--no-debug` CLI flag
|
|
33
|
+
* 2. `EDSI_DEBUG` env var (truthy: 1, true, yes, on)
|
|
34
|
+
* 3. Persisted `debug` field in credentials.json
|
|
35
|
+
* 4. Default: OFF
|
|
36
|
+
*/
|
|
37
|
+
export declare function resolveDebugMode(opts: {
|
|
38
|
+
debug?: boolean;
|
|
39
|
+
}, configDebug?: boolean): boolean;
|
|
40
|
+
/**
|
|
41
|
+
* Emit the bright-green "debug logs at <path>" banner to stderr.
|
|
42
|
+
* No-op when debug is disabled or stderr is not a TTY-friendly stream.
|
|
43
|
+
* Callers should invoke this once at start and once at end of the command.
|
|
44
|
+
*/
|
|
45
|
+
export declare function printDebugBanner(logger: DebugLogger, phase: 'start' | 'end'): void;
|
|
46
|
+
/** Directory that contains the current process's debug log, if any. */
|
|
47
|
+
export declare function debugLogPath(): string | null;
|
|
48
|
+
/** Ensure spawned children join the same debug log by including EDSI_DEBUG_LOG in their env. */
|
|
49
|
+
export declare function debugEnvForSubprocess(env?: NodeJS.ProcessEnv): NodeJS.ProcessEnv;
|