@contentful/experience-design-system-cli 2.26.1-dev-build-a852362.0 → 2.26.1-dev-build-f03f014.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.
Files changed (204) hide show
  1. package/dist/package.json +14 -10
  2. package/dist/src/index.js +34937 -9
  3. package/dist/src/lib/cli-path.d.ts +4 -0
  4. package/dist/src/runs/export-helpers.d.ts +1 -12
  5. package/dist/src/runs/push-helpers.d.ts +1 -6
  6. package/package.json +12 -7
  7. package/skills/generate-components.md +427 -0
  8. package/skills/generate-tokens.md +194 -0
  9. package/skills/select-components.md +224 -0
  10. package/dist/src/analytics/apply.js +0 -30
  11. package/dist/src/analytics/client.js +0 -81
  12. package/dist/src/analytics/constants.js +0 -4
  13. package/dist/src/analytics/env.js +0 -14
  14. package/dist/src/analytics/exit.js +0 -24
  15. package/dist/src/analytics/index.js +0 -9
  16. package/dist/src/analytics/normalize.js +0 -18
  17. package/dist/src/analytics/os.js +0 -13
  18. package/dist/src/analytics/session.js +0 -15
  19. package/dist/src/analytics/tracker.js +0 -126
  20. package/dist/src/analytics/types.js +0 -1
  21. package/dist/src/analyze/build-analyze-view-rows.js +0 -63
  22. package/dist/src/analyze/command.js +0 -599
  23. package/dist/src/analyze/composite-closure.js +0 -0
  24. package/dist/src/analyze/composition/agent-parser/author-prompt.js +0 -22
  25. package/dist/src/analyze/composition/agent-parser/extract-parser.js +0 -44
  26. package/dist/src/analyze/composition/agent-parser/load-prompt.js +0 -31
  27. package/dist/src/analyze/composition/agent-parser/resolve-via-parser.js +0 -88
  28. package/dist/src/analyze/composition/agent-parser/sandbox.js +0 -187
  29. package/dist/src/analyze/composition/apply-mapping.js +0 -67
  30. package/dist/src/analyze/composition/candidate-critic-agent.js +0 -39
  31. package/dist/src/analyze/composition/candidate-critic.js +0 -63
  32. package/dist/src/analyze/composition/candidate-files.js +0 -131
  33. package/dist/src/analyze/composition/composition-cache-key.js +0 -19
  34. package/dist/src/analyze/composition/interchange-schema.js +0 -61
  35. package/dist/src/analyze/composition/manifest-doc-evidence.js +0 -136
  36. package/dist/src/analyze/composition/merge-edges.js +0 -70
  37. package/dist/src/analyze/composition/parse-map-edges.js +0 -55
  38. package/dist/src/analyze/composition/resolve-mapping-cli.js +0 -29
  39. package/dist/src/analyze/composition/resolve-mapping.js +0 -111
  40. package/dist/src/analyze/cycle-detection.js +0 -234
  41. package/dist/src/analyze/cycle-view.js +0 -28
  42. package/dist/src/analyze/fuzzy-search.js +0 -55
  43. package/dist/src/analyze/issue-inheritance.js +0 -108
  44. package/dist/src/analyze/lineage.js +0 -0
  45. package/dist/src/analyze/scope-gate-cascade.js +0 -170
  46. package/dist/src/analyze/search-neighborhood.js +0 -56
  47. package/dist/src/analyze/select/command.js +0 -484
  48. package/dist/src/analyze/select/index.js +0 -5
  49. package/dist/src/analyze/select/parser.js +0 -53
  50. package/dist/src/analyze/select/persistence.js +0 -80
  51. package/dist/src/analyze/select/preview-annotations.js +0 -62
  52. package/dist/src/analyze/select/stdout.js +0 -3
  53. package/dist/src/analyze/select/tui/App.js +0 -483
  54. package/dist/src/analyze/select/tui/components/ComponentDetail.js +0 -48
  55. package/dist/src/analyze/select/tui/components/ComponentRationalePanel.js +0 -125
  56. package/dist/src/analyze/select/tui/components/FieldEditor.js +0 -1131
  57. package/dist/src/analyze/select/tui/components/FinalizeDialog.js +0 -36
  58. package/dist/src/analyze/select/tui/components/GotoBanner.js +0 -30
  59. package/dist/src/analyze/select/tui/components/GroupedSidebar.js +0 -517
  60. package/dist/src/analyze/select/tui/components/HelpOverlay.js +0 -17
  61. package/dist/src/analyze/select/tui/components/JsonEditor.js +0 -131
  62. package/dist/src/analyze/select/tui/components/JsonPanel.js +0 -66
  63. package/dist/src/analyze/select/tui/components/LineagePanel.js +0 -22
  64. package/dist/src/analyze/select/tui/components/PreviewSummaryBar.js +0 -29
  65. package/dist/src/analyze/select/tui/components/QuitDialog.js +0 -14
  66. package/dist/src/analyze/select/tui/components/RationalePanel.js +0 -80
  67. package/dist/src/analyze/select/tui/components/Sidebar.js +0 -92
  68. package/dist/src/analyze/select/tui/components/SourcePanel.js +0 -52
  69. package/dist/src/analyze/select/tui/components/StatusBar.js +0 -7
  70. package/dist/src/analyze/select/tui/components/TopBar.js +0 -10
  71. package/dist/src/analyze/select/tui/components/removed-components-text.js +0 -11
  72. package/dist/src/analyze/select/tui/hooks/scroll-offset.js +0 -26
  73. package/dist/src/analyze/select/tui/hooks/useImmediateInput.js +0 -66
  74. package/dist/src/analyze/select/tui/hooks/useKeymap.js +0 -67
  75. package/dist/src/analyze/select/tui/hooks/useSession.js +0 -52
  76. package/dist/src/analyze/select/tui/hooks/useUndo.js +0 -26
  77. package/dist/src/analyze/select/tui/theme.js +0 -28
  78. package/dist/src/analyze/select/types.js +0 -39
  79. package/dist/src/analyze/select-agent/command.js +0 -558
  80. package/dist/src/analyze/select-agent/context-builder.js +0 -197
  81. package/dist/src/analyze/select-agent/show-rationale.js +0 -92
  82. package/dist/src/analyze/selection-cascade.js +0 -19
  83. package/dist/src/analyze/slot-graph.js +0 -17
  84. package/dist/src/analyze/tui/AnalyzeView.js +0 -64
  85. package/dist/src/apply/api-client.js +0 -440
  86. package/dist/src/apply/command.js +0 -845
  87. package/dist/src/apply/manifest.js +0 -1
  88. package/dist/src/apply/preview-utils.js +0 -26
  89. package/dist/src/apply/tui/SelectView.js +0 -34
  90. package/dist/src/apply/tui/ServerApplyView.js +0 -49
  91. package/dist/src/apply/tui/ServerPreviewView.js +0 -23
  92. package/dist/src/credentials-store.js +0 -74
  93. package/dist/src/generate/command.js +0 -597
  94. package/dist/src/generate/edit/command.js +0 -127
  95. package/dist/src/generate/tui/GenerateView.js +0 -10
  96. package/dist/src/host-utils.js +0 -26
  97. package/dist/src/import/agent-model-resolve.js +0 -35
  98. package/dist/src/import/auto-filter-resolve.js +0 -16
  99. package/dist/src/import/command.js +0 -380
  100. package/dist/src/import/cycle-auto-reject.js +0 -14
  101. package/dist/src/import/orchestrator.js +0 -603
  102. package/dist/src/import/path-utils.js +0 -30
  103. package/dist/src/import/picker-dispatch.js +0 -44
  104. package/dist/src/import/print-prompt.js +0 -31
  105. package/dist/src/import/strip-allowed-components.js +0 -22
  106. package/dist/src/import/tui/CustomPromptBanner.js +0 -8
  107. package/dist/src/import/tui/WizardApp.js +0 -1670
  108. package/dist/src/import/tui/ai-flag.js +0 -6
  109. package/dist/src/import/tui/auto-filter-error.js +0 -29
  110. package/dist/src/import/tui/autocomplete.js +0 -32
  111. package/dist/src/import/tui/components/AutoFilterBanner.js +0 -19
  112. package/dist/src/import/tui/components/CounterStrip.js +0 -12
  113. package/dist/src/import/tui/components/LegendEntry.js +0 -6
  114. package/dist/src/import/tui/cycle-panel-scroll.js +0 -33
  115. package/dist/src/import/tui/final-review-host.js +0 -25
  116. package/dist/src/import/tui/group-collapse.js +0 -11
  117. package/dist/src/import/tui/history.js +0 -53
  118. package/dist/src/import/tui/hooks/useLineage.js +0 -50
  119. package/dist/src/import/tui/hooks/useOverlayPanel.js +0 -27
  120. package/dist/src/import/tui/lineage-layout.js +0 -82
  121. package/dist/src/import/tui/merge-ai-decisions.js +0 -22
  122. package/dist/src/import/tui/push-decision-gate-helpers.js +0 -8
  123. package/dist/src/import/tui/push-progress.js +0 -1
  124. package/dist/src/import/tui/run-print-files-helpers.js +0 -8
  125. package/dist/src/import/tui/run-teaser.js +0 -9
  126. package/dist/src/import/tui/runLivePreview.js +0 -82
  127. package/dist/src/import/tui/runScopeGate.js +0 -28
  128. package/dist/src/import/tui/scope-gate-columns.js +0 -94
  129. package/dist/src/import/tui/scope-gate-host.js +0 -28
  130. package/dist/src/import/tui/sidebar-width.js +0 -3
  131. package/dist/src/import/tui/spawn-generate.js +0 -53
  132. package/dist/src/import/tui/step-filters.js +0 -42
  133. package/dist/src/import/tui/steps/AtomicGenerateReviewStep.js +0 -726
  134. package/dist/src/import/tui/steps/AtomicScopeGateStep.js +0 -210
  135. package/dist/src/import/tui/steps/CredentialsStep.js +0 -108
  136. package/dist/src/import/tui/steps/DoneStep.js +0 -24
  137. package/dist/src/import/tui/steps/ErrorStep.js +0 -16
  138. package/dist/src/import/tui/steps/GateStep.js +0 -22
  139. package/dist/src/import/tui/steps/GenerateReviewStep.js +0 -1618
  140. package/dist/src/import/tui/steps/PathValidationStep.js +0 -152
  141. package/dist/src/import/tui/steps/PreviewStep.js +0 -37
  142. package/dist/src/import/tui/steps/PreviewValidationErrorStep.js +0 -23
  143. package/dist/src/import/tui/steps/PushDecisionGateStep.js +0 -73
  144. package/dist/src/import/tui/steps/PushingStep.js +0 -24
  145. package/dist/src/import/tui/steps/RunningStep.js +0 -21
  146. package/dist/src/import/tui/steps/ScopeGateStep.js +0 -907
  147. package/dist/src/import/tui/steps/TokenInputStep.js +0 -88
  148. package/dist/src/import/tui/steps/WelcomeStep.js +0 -34
  149. package/dist/src/import/tui/steps/WizardPreviewStep.js +0 -169
  150. package/dist/src/import/tui/steps/auto-reject-decision.js +0 -9
  151. package/dist/src/import/tui/steps/breaking-change-format.js +0 -24
  152. package/dist/src/import/tui/steps/enumerate-cycle-breaks.js +0 -0
  153. package/dist/src/import/tui/steps/preview-diff.js +0 -175
  154. package/dist/src/import/tui/useFinalizePreview.js +0 -0
  155. package/dist/src/import/tui/useLivePreview.js +0 -122
  156. package/dist/src/import/tui/wizard-422-helpers.js +0 -60
  157. package/dist/src/import/tui/wizard-generate-progress.js +0 -25
  158. package/dist/src/import/tui/wizard-save-flow.js +0 -20
  159. package/dist/src/import/tui/wizard-state-transitions.js +0 -32
  160. package/dist/src/lib/agent-model-options.js +0 -15
  161. package/dist/src/lib/command-options.js +0 -30
  162. package/dist/src/lib/composition-mode.js +0 -39
  163. package/dist/src/lib/contentful-urls.js +0 -27
  164. package/dist/src/lib/debug-logger.js +0 -229
  165. package/dist/src/lib/debug-preamble.js +0 -33
  166. package/dist/src/lib/error-parser.js +0 -269
  167. package/dist/src/lib/prompt-overrides.js +0 -69
  168. package/dist/src/lib/terminal-capabilities.js +0 -51
  169. package/dist/src/lib/user-agent.js +0 -29
  170. package/dist/src/output/format.js +0 -110
  171. package/dist/src/print/command.js +0 -221
  172. package/dist/src/print/validate/tui/ValidateView.js +0 -37
  173. package/dist/src/print/validate/validators/cdf-validator.js +0 -104
  174. package/dist/src/print/validate/validators/dtcg-validator.js +0 -110
  175. package/dist/src/print/validate/validators/format-errors.js +0 -18
  176. package/dist/src/program.js +0 -105
  177. package/dist/src/runs/export-helpers.js +0 -59
  178. package/dist/src/runs/fingerprint.js +0 -75
  179. package/dist/src/runs/ls-command.js +0 -137
  180. package/dist/src/runs/modify-launcher.js +0 -42
  181. package/dist/src/runs/path-prompt.js +0 -72
  182. package/dist/src/runs/push-creds-prompt.js +0 -39
  183. package/dist/src/runs/push-helpers.js +0 -72
  184. package/dist/src/runs/push-launcher.js +0 -77
  185. package/dist/src/runs/replay-helpers.js +0 -164
  186. package/dist/src/runs/resolve-run-target.js +0 -45
  187. package/dist/src/runs/run-picker-mount.js +0 -72
  188. package/dist/src/runs/run-picker.js +0 -129
  189. package/dist/src/runs/save-conflict.js +0 -56
  190. package/dist/src/runs/save-path-resolver.js +0 -75
  191. package/dist/src/runs/staleness.js +0 -140
  192. package/dist/src/runs/store.js +0 -136
  193. package/dist/src/session/cache-keys.js +0 -44
  194. package/dist/src/session/command.js +0 -261
  195. package/dist/src/session/db.js +0 -1538
  196. package/dist/src/session/migration.js +0 -117
  197. package/dist/src/session/session-id.js +0 -212
  198. package/dist/src/session/stats.js +0 -89
  199. package/dist/src/setup/analytics-prompt.js +0 -24
  200. package/dist/src/setup/auto-filter-prompt.js +0 -24
  201. package/dist/src/setup/command.js +0 -966
  202. package/dist/src/setup/composition-mode-prompt.js +0 -37
  203. package/dist/src/setup/debug-mode-prompt.js +0 -22
  204. package/dist/src/types.js +0 -1
@@ -1,1670 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useEffect, useRef, useState } from 'react';
3
- import { PALETTE } from '../../analyze/select/tui/theme.js';
4
- import { Box, Text, useStdout } from 'ink';
5
- import { join, resolve } from 'node:path';
6
- import { appendFileSync, writeFileSync } from 'node:fs';
7
- import { access, readFile, stat } from 'node:fs/promises';
8
- import { tmpdir } from 'node:os';
9
- import { fileURLToPath } from 'node:url';
10
- import { execFile, spawn } from 'node:child_process';
11
- import { mkdir } from 'node:fs/promises';
12
- import { buildRunTeaserLine } from './run-teaser.js';
13
- import { getDebugLogger } from '../../lib/debug-logger.js';
14
- import { PathPrompt } from '../../runs/path-prompt.js';
15
- import { RunPicker } from '../../runs/run-picker.js';
16
- import { SaveConflictGate } from '../../runs/save-conflict.js';
17
- import { detectSaveConflict, buildTimestampedSubdir, resolveSavePath, } from '../../runs/save-path-resolver.js';
18
- import { appendRun } from '../../runs/store.js';
19
- import { buildSourceFingerprint, buildSavedFingerprint } from '../../runs/fingerprint.js';
20
- import { TopBar } from '../../analyze/select/tui/components/TopBar.js';
21
- import { CustomPromptBanner } from './CustomPromptBanner.js';
22
- import { WelcomeStep } from './steps/WelcomeStep.js';
23
- import { PathValidationStep } from './steps/PathValidationStep.js';
24
- import { RunningStep } from './steps/RunningStep.js';
25
- import { GateStep } from './steps/GateStep.js';
26
- import { CredentialsStep } from './steps/CredentialsStep.js';
27
- import { WizardPreviewStep } from './steps/WizardPreviewStep.js';
28
- import { DoneStep } from './steps/DoneStep.js';
29
- import { ErrorStep } from './steps/ErrorStep.js';
30
- import { TokenInputStep } from './steps/TokenInputStep.js';
31
- import { PreviewValidationErrorStep } from './steps/PreviewValidationErrorStep.js';
32
- import { PushingStep } from './steps/PushingStep.js';
33
- import { nextStateAfterPrint } from './run-print-files-helpers.js';
34
- import { PushDecisionGateStep } from './steps/PushDecisionGateStep.js';
35
- import { chooseGateAction } from './push-decision-gate-helpers.js';
36
- import { ImportApiClient, ApiError } from '../../apply/api-client.js';
37
- import { detectSlotCycles, extractComponentsFromManifest, formatSlotCycleReport } from '../../apply/command.js';
38
- import { findSlotCycles } from '../../analyze/cycle-detection.js';
39
- import { buildComponentGraph } from '../../analyze/slot-graph.js';
40
- import { formatApiError, formatEdsiError } from '../../lib/error-parser.js';
41
- import { handlePreview422, applySkipValidationErrors, clearedValidationErrorState } from './wizard-422-helpers.js';
42
- import { parseGenerateStderrChunk } from './wizard-generate-progress.js';
43
- import { spawnGenerateChild } from './spawn-generate.js';
44
- import { readTokensFromPath, hasBreakingChangesWithImpact } from '../../apply/manifest.js';
45
- import { isEmptyPreview } from '../../apply/preview-utils.js';
46
- import { buildManifest } from '@contentful/experience-design-system-types';
47
- import { openPipelineDb, loadCDFComponents, loadScopeComponents, seedCDFFromPreviewResponse, seedDefaultsFromChangedItems, backfillUnclassifiedProps, } from '../../session/db.js';
48
- import { ScopeGateHost } from './scope-gate-host.js';
49
- import { mergeAiDecisions } from './merge-ai-decisions.js';
50
- import { FinalReviewHost } from './final-review-host.js';
51
- import { runScopeGate } from './runScopeGate.js';
52
- import { buildAutoFilterErrorTail } from './auto-filter-error.js';
53
- import { checkAgentAuth } from '@contentful/experience-design-system-generation';
54
- import { normalizePath } from '../path-utils.js';
55
- import { DEFAULT_CONFIGURED_HOST, toConfiguredHost } from '../../host-utils.js';
56
- import { writeExperiencesCredentials } from '../../credentials-store.js';
57
- import { nextStepAfterScopeGate, nextStepAfterCredentialsValidated, shouldBypassPreview, buildSkippedPreviewTransition, shouldRefusePush, buildSkippedPushTransition, shouldSkipFinalReviewAfterCredentials, resolveNoCacheForGenerate, resolveCycleGateAction, } from './wizard-state-transitions.js';
58
- import { computeCycleAutoRejectTargets } from '../cycle-auto-reject.js';
59
- function findCliPath() {
60
- return join(fileURLToPath(import.meta.url), '..', '..', '..', '..', '..', 'bin', 'cli.js');
61
- }
62
- export function buildSelectAgentArgs(opts) {
63
- const args = ['analyze', 'select-agent', '--agent', opts.agent, '--session', opts.sessionId, '--exclude-invalid'];
64
- if (opts.model)
65
- args.push('--model', opts.model);
66
- if (opts.selectPromptPath)
67
- args.push('--select-prompt-path', opts.selectPromptPath);
68
- if (opts.noCache)
69
- args.push('--no-cache');
70
- return args;
71
- }
72
- export function parseAutoFilterProgressLine(line) {
73
- const prefix = 'progress=select-agent:';
74
- if (!line.startsWith(prefix))
75
- return null;
76
- const rest = line.slice(prefix.length);
77
- const parts = rest.split(':');
78
- if (parts.length < 4)
79
- return null;
80
- const [counter, decision, name, ...reasonParts] = parts;
81
- if (!counter)
82
- return null;
83
- const counterMatch = /^(\d+)\/(\d+)$/.exec(counter);
84
- if (!counterMatch)
85
- return null;
86
- if (decision !== 'accepted' && decision !== 'rejected' && decision !== 'failed')
87
- return null;
88
- if (!name)
89
- return null;
90
- const encodedReason = reasonParts.join(':');
91
- let reason = '';
92
- try {
93
- reason = decodeURIComponent(encodedReason);
94
- }
95
- catch {
96
- reason = encodedReason;
97
- }
98
- return {
99
- n: Number(counterMatch[1]),
100
- total: Number(counterMatch[2]),
101
- decision,
102
- name,
103
- reason,
104
- };
105
- }
106
- export function buildGenerateComponentsArgs(opts) {
107
- const args = ['generate', 'components', '--agent', opts.agent, '--session', opts.sessionId];
108
- if (opts.tokensPath)
109
- args.push('--tokens', opts.tokensPath);
110
- if (opts.model)
111
- args.push('--model', opts.model);
112
- if (opts.noCache)
113
- args.push('--no-cache');
114
- if (opts.generatePromptPath)
115
- args.push('--generate-prompt-path', opts.generatePromptPath);
116
- return args;
117
- }
118
- export function formatAcceptanceSummary(opts) {
119
- const acceptedClause = `${opts.accepted} component${opts.accepted === 1 ? '' : 's'} accepted`;
120
- if (opts.autoRejected === 0)
121
- return `${acceptedClause}.`;
122
- return `${acceptedClause}, ${opts.autoRejected} excluded due to validation errors.`;
123
- }
124
- function runCli(args) {
125
- return new Promise((res) => {
126
- execFile('node', [findCliPath(), ...args], (error, stdout, stderr) => {
127
- res({ exitCode: error?.code ? Number(error.code) : 0, stdout, stderr });
128
- });
129
- });
130
- }
131
- export function parsePrintTokensCount(stdout) {
132
- const m = /\((\d+)\s+token/.exec(stdout);
133
- return m ? Number(m[1]) : 0;
134
- }
135
- const WIZARD_LOG = join(tmpdir(), 'experiences-import-wizard.log');
136
- function logStep(entry) {
137
- const line = JSON.stringify({ ts: new Date().toISOString(), ...entry }) + '\n';
138
- appendFileSync(WIZARD_LOG, line);
139
- getDebugLogger().event('wizard', 'step', entry);
140
- }
141
- export function WizardApp({ initialSpaceId = '', initialEnvironmentId = 'master', initialCmaToken = '', initialHost, initialAgent, initialModel, initialProjectPath, host, autoAcceptScope = false, autoRejectCycles = false, compositionMode = 'atomic', compositionMap, compositionAgent = false, compositionAgentMode, compositionRefresh = false, generateMap, promptOverrides, noCache = false, autoFilter = true, livePreview = true, noPush = false, noSave = false, outDirOverride, onConflictMode, selectPromptPath, generatePromptPath, seedExtractSessionId, seedGenerateSessionId, seedTokenSessionId, seedTokensPath, initialStep, initialRawTokensPath, initialRuns, onRunPicked, allowDeletions = false, } = {}) {
142
- const defaultConfiguredHost = toConfiguredHost(host || process.env['EDS_HOST']) ?? DEFAULT_CONFIGURED_HOST;
143
- const resolveWizardHost = (hostValue) => hostValue || defaultConfiguredHost;
144
- const { stdout } = useStdout();
145
- const terminalWidth = stdout?.columns ?? 80;
146
- const logInit = useRef(false);
147
- if (!logInit.current) {
148
- writeFileSync(WIZARD_LOG, `--- experiences import session ${new Date().toISOString()} ---\n`);
149
- logInit.current = true;
150
- }
151
- const credentialsRef = useRef(null);
152
- const sessionRef = useRef({
153
- extractSessionId: null,
154
- tokensPath: '',
155
- });
156
- // Set at finalize when zero components are accepted (confirmed via the
157
- // FinalizeDialog warning). Read by preview/push so buildManifest emits an
158
- // empty-but-present components manifest → server deletes all. A ref (not
159
- // state) so the async preview/push closures see the confirmed value.
160
- const allowEmptyDeleteAllRef = useRef(false);
161
- const autoFilterChildRef = useRef(null);
162
- const autoFilterDonePromiseRef = useRef(null);
163
- const generateChildRef = useRef(null);
164
- const generatePromiseRef = useRef(null);
165
- const modifyEntryReady = !!seedExtractSessionId && initialStep === 'final-review';
166
- const pushFromPickerReady = !!seedExtractSessionId && initialStep === 'push-from-picker';
167
- const rawTokensEntryReady = !modifyEntryReady && !pushFromPickerReady && !!initialRawTokensPath;
168
- const effectiveNoCache = resolveNoCacheForGenerate({ cliNoCache: noCache });
169
- const initialStepResolved = modifyEntryReady
170
- ? 'final-review'
171
- : pushFromPickerReady
172
- ? 'push-from-picker'
173
- : rawTokensEntryReady
174
- ? 'generating-tokens'
175
- : initialProjectPath
176
- ? 'token-input'
177
- : 'welcome';
178
- const initialOutDir = initialProjectPath ? join(resolve(initialProjectPath), '.contentful') : '';
179
- // Only point at tokens.json when the run actually had a tokens session. A run
180
- // saved without tokens has no tokens.json on disk, so assuming one exists made
181
- // modify/push-from-picker fail with "file not found: .../tokens.json".
182
- const initialTokensPath = (modifyEntryReady || pushFromPickerReady) && initialOutDir && seedTokenSessionId
183
- ? join(initialOutDir, 'tokens.json')
184
- : '';
185
- const [state, setState] = useState({
186
- step: modifyEntryReady || rawTokensEntryReady || pushFromPickerReady
187
- ? initialStepResolved
188
- : initialRuns && initialRuns.length > 0
189
- ? 'run-picker'
190
- : initialStepResolved,
191
- agent: initialAgent ?? 'claude',
192
- ...(initialModel ? { agentModel: initialModel } : {}),
193
- projectPath: initialProjectPath ?? '',
194
- outDir: initialOutDir,
195
- rawTokensPath: rawTokensEntryReady ? initialRawTokensPath : '',
196
- tokensPath: seedTokensPath ?? initialTokensPath,
197
- tokenSourceChanged: null,
198
- skipComponents: false,
199
- tokenSessionId: seedTokenSessionId ?? null,
200
- tokenCount: 0,
201
- extractSessionId: seedExtractSessionId ?? null,
202
- generateSessionId: seedGenerateSessionId ?? null,
203
- extractedCount: 0,
204
- acceptedCount: 0,
205
- autoRejectedCount: 0,
206
- generatedCount: 0,
207
- generatedAcceptedCount: 0,
208
- renamedSlotsCount: 0,
209
- generateProgress: null,
210
- extractProgress: null,
211
- compositionPhase: null,
212
- componentsPath: '',
213
- spaceId: initialSpaceId,
214
- environmentId: initialEnvironmentId,
215
- cmaToken: initialCmaToken,
216
- host: resolveWizardHost(toConfiguredHost(initialHost)),
217
- credentialsError: '',
218
- serverPreview: null,
219
- manifest: null,
220
- pushProgress: null,
221
- pushResult: {
222
- componentTypes: { created: 0, updated: 0, removed: 0, failed: 0 },
223
- designTokens: { created: 0, updated: 0, removed: 0, failed: 0 },
224
- },
225
- errorStep: '',
226
- errorMessage: '',
227
- errorAllowCredentialRetry: false,
228
- authCheckStepNumber: 1,
229
- previewValidationErrors: [],
230
- previewValidationMissingNames: [],
231
- aiFilterStatus: 'idle',
232
- aiFilterProgress: null,
233
- aiDecisions: {},
234
- aiFilterError: null,
235
- credentialsValidating: false,
236
- generatePrefetchStatus: 'idle',
237
- generatePrefetchError: null,
238
- credentialsSkipped: false,
239
- lastRunId: null,
240
- finalizeErrorBanner: null,
241
- finalReviewPassed: modifyEntryReady || pushFromPickerReady,
242
- allowEmptyDeleteAll: false,
243
- });
244
- useEffect(() => {
245
- sessionRef.current = {
246
- extractSessionId: state.extractSessionId,
247
- tokensPath: state.tokensPath,
248
- };
249
- }, [state.extractSessionId, state.tokensPath]);
250
- const update = (partial) => {
251
- const sanitized = { ...partial };
252
- if (sanitized.serverPreview) {
253
- const p = sanitized.serverPreview;
254
- sanitized.serverPreview = {
255
- components: {
256
- new: p.components.new.length,
257
- newNames: p.components.new.map((c) => c.name ?? JSON.stringify(c).slice(0, 80)),
258
- changed: p.components.changed.length,
259
- removed: p.components.removed.length,
260
- removedNames: p.components.removed.map((c) => c.name),
261
- unchanged: p.components.unchanged.length,
262
- },
263
- tokens: {
264
- new: p.tokens.new.length,
265
- changed: p.tokens.changed.length,
266
- removed: p.tokens.removed.length,
267
- unchanged: p.tokens.unchanged.length,
268
- },
269
- changedComponentDetails: p.components.changed.map((c) => ({
270
- name: c.current.name,
271
- hasPendingDraftChanges: c.hasPendingDraftChanges,
272
- classification: c.changeClassification?.classification,
273
- breakingChanges: c.changeClassification?.breakingChanges,
274
- impact: c.impact,
275
- })),
276
- changedTokenDetails: p.tokens.changed.slice(0, 5).map((t) => ({
277
- name: t.current.name,
278
- hasPendingDraftChanges: t.hasPendingDraftChanges,
279
- classification: t.changeClassification?.classification,
280
- breakingChanges: t.changeClassification?.breakingChanges,
281
- impact: t.impact,
282
- })),
283
- tokenDiffs: p.tokens.changed.slice(0, 3).map((t) => ({
284
- current: t.current,
285
- proposed: t.proposed,
286
- })),
287
- hasBreakingWithImpact: hasBreakingChangesWithImpact(p),
288
- };
289
- }
290
- if (sanitized.manifest)
291
- sanitized.manifest = '[manifest]';
292
- if (sanitized.cmaToken)
293
- sanitized.cmaToken = '[redacted]';
294
- logStep({ update: sanitized });
295
- setState((prev) => ({ ...prev, ...partial }));
296
- };
297
- const runAgentAuthCheck = async (nextStep) => {
298
- const authCheckStepNumber = nextStep === 'generating-tokens' ? 1 : state.tokensPath ? 4 : 3;
299
- update({ step: 'checking-claude-auth', authCheckStepNumber });
300
- const status = await checkAgentAuth(state.agent);
301
- if (status === 'not-found') {
302
- update({
303
- step: 'error',
304
- errorStep: `${state.agent} auth check`,
305
- errorMessage: `The \`${state.agent}\` CLI was not found on your PATH.\n\nInstall it, then re-run \`experiences import\`.`,
306
- });
307
- return false;
308
- }
309
- if (status === 'unauthenticated') {
310
- update({
311
- step: 'error',
312
- errorStep: `${state.agent} auth check`,
313
- errorMessage: `${state.agent} is not authenticated.\n\n` +
314
- `Run \`${state.agent}\` in your terminal to log in, then re-run \`experiences import\`.\n\n` +
315
- 'If you are using AWS Bedrock, run:\n' +
316
- ' aws sso login --profile <your-profile>',
317
- });
318
- return false;
319
- }
320
- update({ step: nextStep });
321
- return true;
322
- };
323
- const runGenerateTokens = async (rawTokensPath, outDir) => {
324
- const result = await new Promise((res) => {
325
- const tokenArgs = [findCliPath(), 'generate', 'tokens', '--agent', state.agent, '--raw-tokens', rawTokensPath];
326
- if (state.agentModel)
327
- tokenArgs.push('--model', state.agentModel);
328
- const child = spawn('node', tokenArgs);
329
- let stdout = '';
330
- let stderr = '';
331
- child.stdout.on('data', (d) => {
332
- stdout += String(d);
333
- });
334
- child.stderr.on('data', (d) => {
335
- stderr += String(d);
336
- });
337
- child.on('exit', (code) => res({ exitCode: code ?? 0, stdout, stderr }));
338
- });
339
- if (result.exitCode !== 0) {
340
- update({
341
- step: 'error',
342
- errorStep: 'generate tokens',
343
- errorMessage: result.stderr.trim() || 'Unknown error',
344
- });
345
- return;
346
- }
347
- const sessionMatch = /^session=(.+)$/m.exec(result.stdout);
348
- const tokenSessionId = sessionMatch ? sessionMatch[1].trim() : null;
349
- const tokensPath = join(outDir, 'tokens.json');
350
- const printArgs = ['print', 'tokens', '--out', tokensPath];
351
- if (tokenSessionId)
352
- printArgs.push('--session', tokenSessionId);
353
- const r = await runCli(printArgs);
354
- if (r.exitCode !== 0) {
355
- update({
356
- step: 'error',
357
- errorStep: 'print tokens',
358
- errorMessage: r.stderr.trim() || 'Unknown error',
359
- });
360
- return;
361
- }
362
- const tokenCount = parsePrintTokensCount(r.stdout);
363
- update({ step: 'path-validation', tokensPath, tokenSessionId, tokenCount });
364
- };
365
- const runExtract = async (projectPath) => {
366
- const outDir = join(resolve(projectPath), '.contentful');
367
- update({ step: 'extracting', outDir, extractProgress: null, compositionPhase: null });
368
- const r = await new Promise((res) => {
369
- const extractArgs = [findCliPath(), 'analyze', 'extract', '--project', projectPath];
370
- if (compositionMode === 'composite') {
371
- extractArgs.push('--composite');
372
- if (compositionMap)
373
- extractArgs.push('--composition-map', compositionMap);
374
- if (compositionAgent)
375
- extractArgs.push('--composition-agent');
376
- if (compositionAgentMode)
377
- extractArgs.push('--composition-agent-mode', compositionAgentMode);
378
- if (compositionRefresh)
379
- extractArgs.push('--composition-refresh');
380
- if (generateMap)
381
- extractArgs.push('--generate-map', generateMap);
382
- for (const p of promptOverrides ?? [])
383
- extractArgs.push('--prompt', p);
384
- // Composition resolution uses the same agent the user picked for the run.
385
- if (state.agent)
386
- extractArgs.push('--agent', state.agent);
387
- }
388
- const child = spawn('node', extractArgs);
389
- let stdout = '';
390
- let stderr = '';
391
- child.stdout.on('data', (d) => {
392
- stdout += String(d);
393
- });
394
- child.stderr.on('data', (d) => {
395
- const chunk = String(d);
396
- stderr += chunk;
397
- for (const line of chunk.split('\n')) {
398
- const scanMatch = /^progress=scan:(\d+)$/.exec(line.trim());
399
- if (scanMatch) {
400
- const scanned = Number(scanMatch[1]);
401
- setState((prev) => ({
402
- ...prev,
403
- extractProgress: {
404
- scanned,
405
- filesProcessed: prev.extractProgress?.filesProcessed ?? 0,
406
- totalFiles: prev.extractProgress?.totalFiles ?? 0,
407
- componentsFound: prev.extractProgress?.componentsFound ?? 0,
408
- },
409
- }));
410
- continue;
411
- }
412
- const extractMatch = /^progress=extract:(\d+)\/(\d+):(\d+)$/.exec(line.trim());
413
- if (extractMatch) {
414
- const filesProcessed = Number(extractMatch[1]);
415
- const totalFiles = Number(extractMatch[2]);
416
- const componentsFound = Number(extractMatch[3]);
417
- setState((prev) => ({
418
- ...prev,
419
- extractProgress: {
420
- scanned: prev.extractProgress?.scanned ?? 0,
421
- filesProcessed,
422
- totalFiles,
423
- componentsFound,
424
- },
425
- }));
426
- continue;
427
- }
428
- const compositionMatch = /^progress=composition:(.+)$/.exec(line.trim());
429
- if (compositionMatch) {
430
- const phase = compositionMatch[1];
431
- setState((prev) => ({ ...prev, compositionPhase: phase }));
432
- }
433
- }
434
- });
435
- child.on('exit', (code) => res({ exitCode: code ?? 0, stdout, stderr }));
436
- });
437
- if (r.exitCode !== 0) {
438
- update({
439
- step: 'error',
440
- errorStep: 'analyze extract',
441
- errorMessage: r.stderr.trim() || 'Unknown error',
442
- });
443
- return;
444
- }
445
- const sessionMatch = /^session=(.+)$/m.exec(r.stdout);
446
- const extractSessionId = sessionMatch ? sessionMatch[1].trim() : null;
447
- const countMatch = /Extracted (\d+) components?/.exec(r.stderr);
448
- const extractedCount = countMatch ? Number(countMatch[1]) : 0;
449
- if (extractedCount === 0) {
450
- update({
451
- step: 'error',
452
- errorStep: 'analyze extract',
453
- errorMessage: `No components found in ${projectPath}.\n\nMake sure this path contains TypeScript/React/Vue component files (.tsx, .ts, .vue, etc.).`,
454
- });
455
- return;
456
- }
457
- update({
458
- step: 'scope-gate',
459
- extractSessionId,
460
- extractedCount,
461
- aiFilterStatus: autoFilter ? 'running' : 'idle',
462
- aiFilterProgress: autoFilter ? { done: 0, total: extractedCount } : null,
463
- aiDecisions: {},
464
- aiFilterError: null,
465
- });
466
- if (autoFilter && extractSessionId) {
467
- autoFilterDonePromiseRef.current = runAutoFilter(extractSessionId);
468
- }
469
- };
470
- const runAutoFilter = (sessionId) => {
471
- return new Promise((res) => {
472
- const args = buildSelectAgentArgs({
473
- sessionId,
474
- agent: state.agent,
475
- ...(state.agentModel ? { model: state.agentModel } : {}),
476
- selectPromptPath,
477
- noCache,
478
- });
479
- const child = spawn('node', [findCliPath(), ...args]);
480
- autoFilterChildRef.current = child;
481
- let stderr = '';
482
- child.stderr.on('data', (d) => {
483
- const chunk = String(d);
484
- stderr += chunk;
485
- for (const line of chunk.split('\n')) {
486
- const trimmed = line.trim();
487
- if (!trimmed)
488
- continue;
489
- const parsed = parseAutoFilterProgressLine(trimmed);
490
- if (!parsed)
491
- continue;
492
- setState((prev) => ({
493
- ...prev,
494
- aiFilterProgress: { done: parsed.n, total: parsed.total },
495
- aiDecisions: {
496
- ...prev.aiDecisions,
497
- [parsed.name]: { decision: parsed.decision, reason: parsed.reason },
498
- },
499
- }));
500
- }
501
- });
502
- child.on('close', (code, signal) => {
503
- autoFilterChildRef.current = null;
504
- if (signal === 'SIGTERM') {
505
- setState((prev) => ({ ...prev, aiFilterStatus: 'cancelled' }));
506
- }
507
- else if ((code ?? 0) !== 0) {
508
- const tail = buildAutoFilterErrorTail(stderr);
509
- setState((prev) => ({
510
- ...prev,
511
- aiFilterStatus: 'failed',
512
- aiFilterError: tail || `exit ${code}`,
513
- }));
514
- }
515
- else {
516
- setState((prev) => ({ ...prev, aiFilterStatus: 'complete' }));
517
- }
518
- res();
519
- });
520
- child.on('error', (err) => {
521
- autoFilterChildRef.current = null;
522
- setState((prev) => ({
523
- ...prev,
524
- aiFilterStatus: 'failed',
525
- aiFilterError: err.message,
526
- }));
527
- res();
528
- });
529
- });
530
- };
531
- const cancelAutoFilter = () => {
532
- const child = autoFilterChildRef.current;
533
- if (child && !child.killed) {
534
- try {
535
- child.kill('SIGTERM');
536
- }
537
- catch {
538
- // best-effort
539
- }
540
- }
541
- };
542
- const cancelAutoFilterAndWait = async () => {
543
- const child = autoFilterChildRef.current;
544
- const donePromise = autoFilterDonePromiseRef.current;
545
- if (!child || child.killed) {
546
- if (donePromise)
547
- await donePromise;
548
- return;
549
- }
550
- try {
551
- child.kill('SIGTERM');
552
- }
553
- catch {
554
- // best-effort
555
- }
556
- if (donePromise)
557
- await donePromise;
558
- };
559
- const cancelGeneratePrefetch = () => {
560
- const child = generateChildRef.current;
561
- if (child && !child.killed) {
562
- try {
563
- child.kill('SIGTERM');
564
- }
565
- catch {
566
- // best-effort
567
- }
568
- }
569
- generateChildRef.current = null;
570
- generatePromiseRef.current = null;
571
- setState((prev) => ({
572
- ...prev,
573
- generatePrefetchStatus: 'idle',
574
- generatePrefetchError: null,
575
- generateProgress: null,
576
- }));
577
- };
578
- const startGeneratePrefetch = (extractSessionId, tokensPath) => {
579
- const args = [
580
- findCliPath(),
581
- ...buildGenerateComponentsArgs({
582
- sessionId: extractSessionId,
583
- tokensPath,
584
- agent: state.agent,
585
- ...(state.agentModel ? { model: state.agentModel } : {}),
586
- noCache: effectiveNoCache,
587
- }),
588
- ];
589
- let progressCursor = null;
590
- const { child, donePromise } = spawnGenerateChild({
591
- command: 'node',
592
- args,
593
- onStderr: (chunk) => {
594
- const nextProgress = parseGenerateStderrChunk(chunk, progressCursor);
595
- if (nextProgress !== progressCursor) {
596
- progressCursor = nextProgress;
597
- setState((prev) => ({ ...prev, generateProgress: nextProgress }));
598
- }
599
- },
600
- });
601
- generateChildRef.current = child;
602
- generatePromiseRef.current = donePromise;
603
- setState((prev) => ({
604
- ...prev,
605
- generatePrefetchStatus: 'running',
606
- generatePrefetchError: null,
607
- }));
608
- donePromise
609
- .then((result) => {
610
- generateChildRef.current = null;
611
- if (result.signal === 'SIGTERM') {
612
- return;
613
- }
614
- if (result.exitCode !== 0) {
615
- const tail = result.stderr.trim().split('\n').slice(-3).join('\n') || `exit ${result.exitCode}`;
616
- setState((prev) => ({
617
- ...prev,
618
- generatePrefetchStatus: 'failed',
619
- generatePrefetchError: tail,
620
- generateProgress: null,
621
- }));
622
- return;
623
- }
624
- const sessionMatch = /^session=(.+)$/m.exec(result.stdout);
625
- const generateSessionId = sessionMatch ? sessionMatch[1].trim() : null;
626
- const countMatch = /(\d+) components?/.exec(result.stderr);
627
- const generatedCount = countMatch ? Number(countMatch[1]) : 0;
628
- const renamedMatch = /^renamed-slots:\s*(\d+)$/m.exec(result.stdout);
629
- const renamedSlotsCount = renamedMatch ? Number(renamedMatch[1]) : 0;
630
- setState((prev) => ({
631
- ...prev,
632
- generateSessionId,
633
- generatedCount,
634
- renamedSlotsCount,
635
- generateProgress: null,
636
- generatePrefetchStatus: 'complete',
637
- }));
638
- })
639
- .catch(() => {
640
- generateChildRef.current = null;
641
- setState((prev) => ({
642
- ...prev,
643
- generatePrefetchStatus: 'failed',
644
- generatePrefetchError: 'subprocess error',
645
- }));
646
- });
647
- return donePromise;
648
- };
649
- const runGenerate = async (extractSessionId, tokensPath, acceptedCount) => {
650
- const args = [
651
- findCliPath(),
652
- ...buildGenerateComponentsArgs({
653
- sessionId: extractSessionId,
654
- tokensPath,
655
- agent: state.agent,
656
- ...(state.agentModel ? { model: state.agentModel } : {}),
657
- noCache: effectiveNoCache,
658
- generatePromptPath,
659
- }),
660
- ];
661
- let progressCursor = state.generateProgress;
662
- const { donePromise } = spawnGenerateChild({
663
- command: 'node',
664
- args,
665
- onStderr: (chunk) => {
666
- const nextProgress = parseGenerateStderrChunk(chunk, progressCursor);
667
- if (nextProgress !== progressCursor) {
668
- progressCursor = nextProgress;
669
- update({ generateProgress: nextProgress });
670
- }
671
- },
672
- });
673
- const result = await donePromise;
674
- if (result.exitCode !== 0) {
675
- update({
676
- step: 'error',
677
- errorStep: 'generate components',
678
- errorMessage: result.stderr.trim() || 'Unknown error',
679
- });
680
- return;
681
- }
682
- const sessionMatch = /^session=(.+)$/m.exec(result.stdout);
683
- const generateSessionId = sessionMatch ? sessionMatch[1].trim() : null;
684
- const countMatch = /(\d+) components?/.exec(result.stderr);
685
- const generatedCount = countMatch ? Number(countMatch[1]) : acceptedCount;
686
- const renamedMatch = /^renamed-slots:\s*(\d+)$/m.exec(result.stdout);
687
- const renamedSlotsCount = renamedMatch ? Number(renamedMatch[1]) : 0;
688
- update({
689
- step: 'final-review',
690
- generateSessionId,
691
- generatedCount,
692
- renamedSlotsCount,
693
- generateProgress: null,
694
- });
695
- };
696
- const advanceToPushFlow = (generatedAcceptedCount) => {
697
- update({ generatedAcceptedCount, step: 'credentials' });
698
- };
699
- const runEditFromPreview = async () => {
700
- update({ step: 'final-review' });
701
- };
702
- const runSkipValidationErrorsAndRetry = async (errors) => {
703
- await applySkipValidationErrors(state.extractSessionId, errors);
704
- const { extractSessionId: sid, tokensPath: tp } = sessionRef.current;
705
- void runPreview(sid, tp, state.spaceId, state.environmentId, state.cmaToken, state.host);
706
- };
707
- const advanceWithCredentials = (spaceId, environmentId, cmaToken, host) => {
708
- const resolvedHost = resolveWizardHost(host);
709
- credentialsRef.current = { spaceId, environmentId, cmaToken };
710
- void validateCredentials(spaceId, environmentId, cmaToken, resolvedHost);
711
- };
712
- const confirmCredentials = async (spaceId, environmentId, cmaToken, host) => {
713
- const resolvedHost = resolveWizardHost(host);
714
- try {
715
- await writeExperiencesCredentials({
716
- spaceId,
717
- environmentId,
718
- cmaToken,
719
- host: resolvedHost,
720
- });
721
- advanceWithCredentials(spaceId, environmentId, cmaToken, resolvedHost);
722
- }
723
- catch (e) {
724
- const message = e instanceof Error ? e.message : 'Unable to save credentials';
725
- update({
726
- spaceId,
727
- environmentId,
728
- cmaToken,
729
- host: resolvedHost,
730
- credentialsError: `Failed to save credentials: ${message}`,
731
- step: 'credentials',
732
- });
733
- }
734
- };
735
- const validateCredentials = async (spaceId, environmentId, cmaToken, host) => {
736
- update({ step: 'credentials', credentialsValidating: true, credentialsError: '' });
737
- try {
738
- const resolvedHost = resolveWizardHost(host);
739
- const client = new ImportApiClient({
740
- cmaToken,
741
- spaceId,
742
- environmentId,
743
- host: resolvedHost,
744
- });
745
- await client.validateToken();
746
- update({ credentialsValidating: false });
747
- await advanceAfterCredentialsValidated();
748
- }
749
- catch (e) {
750
- if (e instanceof ApiError && (e.status === 401 || e.status === 403 || e.status === 404)) {
751
- cancelGeneratePrefetch();
752
- update({
753
- step: 'credentials',
754
- credentialsValidating: false,
755
- credentialsError: formatApiError(e, process.env['EDSI_VERBOSE_ERRORS'] === '1'),
756
- });
757
- return;
758
- }
759
- const msg = e instanceof Error ? e.message : 'Credential check failed';
760
- cancelGeneratePrefetch();
761
- update({
762
- step: 'error',
763
- errorStep: 'validating-credentials',
764
- errorMessage: msg,
765
- errorAllowCredentialRetry: false,
766
- credentialsValidating: false,
767
- });
768
- }
769
- };
770
- const advanceAfterCredentialsValidated = async () => {
771
- if (shouldSkipFinalReviewAfterCredentials(state)) {
772
- update({ step: 'push-decision-gate' });
773
- return;
774
- }
775
- const next = nextStepAfterCredentialsValidated({ acceptedCount: state.acceptedCount });
776
- if (next === 'generating') {
777
- const sid = sessionRef.current.extractSessionId;
778
- if (!sid) {
779
- update({
780
- step: 'error',
781
- errorStep: 'post-credentials',
782
- errorMessage: 'Internal error: extract session ID missing after credential validation.',
783
- });
784
- return;
785
- }
786
- const inflight = generatePromiseRef.current;
787
- if (inflight) {
788
- update({ step: 'generating' });
789
- const result = await inflight;
790
- generatePromiseRef.current = null;
791
- if (result.exitCode === 0 && result.signal !== 'SIGTERM') {
792
- setState((prev) => ({ ...prev, step: 'final-review' }));
793
- return;
794
- }
795
- }
796
- if (await runAgentAuthCheck('generating')) {
797
- void runGenerate(sid, state.tokensPath, state.acceptedCount);
798
- }
799
- return;
800
- }
801
- update({ step: 'push-decision-gate' });
802
- };
803
- const runPreview = async (extractSessionId, tokensPath, spaceId, environmentId, cmaToken, host) => {
804
- if (shouldBypassPreview(state)) {
805
- update(buildSkippedPreviewTransition());
806
- return;
807
- }
808
- update({ step: 'previewing' });
809
- const resolvedHost = resolveWizardHost(host);
810
- try {
811
- const client = new ImportApiClient({
812
- cmaToken,
813
- spaceId,
814
- environmentId,
815
- host: resolvedHost,
816
- });
817
- let components = [];
818
- if (extractSessionId) {
819
- const db = openPipelineDb();
820
- try {
821
- backfillUnclassifiedProps(db, extractSessionId);
822
- components = loadCDFComponents(db, extractSessionId);
823
- }
824
- finally {
825
- db.close();
826
- }
827
- }
828
- let tokens = [];
829
- if (tokensPath) {
830
- tokens = await readTokensFromPath('tokens', tokensPath);
831
- }
832
- let manifest = buildManifest(components, tokens, { deleteAllComponents: allowEmptyDeleteAllRef.current });
833
- let preview = await client.previewImport(manifest, allowDeletions);
834
- if (extractSessionId) {
835
- let needsRepreview = false;
836
- const db = openPipelineDb();
837
- try {
838
- if (preview.components.removed.length > 0) {
839
- const localNames = new Set(db.prepare(`SELECT name FROM raw_components WHERE session_id = ?`).all(extractSessionId).map((r) => r.name));
840
- const falseRemovals = preview.components.removed.filter((r) => localNames.has(r.name));
841
- if (falseRemovals.length > 0) {
842
- const seeded = seedCDFFromPreviewResponse(db, extractSessionId, falseRemovals);
843
- if (seeded > 0)
844
- needsRepreview = true;
845
- }
846
- }
847
- if (preview.components.changed.length > 0) {
848
- const seededDefaults = seedDefaultsFromChangedItems(db, extractSessionId, preview.components.changed);
849
- if (seededDefaults > 0)
850
- needsRepreview = true;
851
- }
852
- if (needsRepreview) {
853
- components = loadCDFComponents(db, extractSessionId);
854
- manifest = buildManifest(components, tokens, { deleteAllComponents: allowEmptyDeleteAllRef.current });
855
- preview = await client.previewImport(manifest, allowDeletions);
856
- }
857
- }
858
- finally {
859
- db.close();
860
- }
861
- }
862
- if (isEmptyPreview(preview)) {
863
- // No-op push: the accepted set already matches the target space, so
864
- // there is nothing to create/update/remove. Route to a terminal "done"
865
- // state rather than bouncing back to final-review — the review screen
866
- // resets every component to needs-review on remount, which otherwise
867
- // traps the operator in an accept → empty-preview → reset loop.
868
- update({
869
- step: 'done',
870
- serverPreview: preview,
871
- pushResult: {
872
- componentTypes: { created: 0, updated: 0, removed: 0, failed: 0 },
873
- designTokens: { created: 0, updated: 0, removed: 0, failed: 0 },
874
- },
875
- ...clearedValidationErrorState(),
876
- });
877
- return;
878
- }
879
- update({
880
- step: 'preview-gate',
881
- serverPreview: preview,
882
- manifest,
883
- finalizeErrorBanner: null,
884
- ...clearedValidationErrorState(),
885
- });
886
- }
887
- catch (e) {
888
- if (e instanceof ApiError) {
889
- if (e.status === 401 || e.status === 403) {
890
- let bodyMsg = '';
891
- try {
892
- bodyMsg = JSON.parse(e.body)['message'];
893
- }
894
- catch {
895
- /* non-JSON */
896
- }
897
- if (bodyMsg && /disabled/i.test(bodyMsg)) {
898
- update({
899
- step: 'error',
900
- errorStep: 'apply preview',
901
- errorMessage: `Preview failed: ${bodyMsg}`,
902
- errorAllowCredentialRetry: false,
903
- });
904
- return;
905
- }
906
- update({ step: 'credentials', credentialsError: e.message });
907
- return;
908
- }
909
- if (e.status === 404) {
910
- update({
911
- step: 'error',
912
- errorStep: 'apply preview',
913
- errorAllowCredentialRetry: true,
914
- errorMessage: `Not found (404). Check that the space ID, environment ID, and host are correct.\n\n` +
915
- ` Space: ${spaceId}\n` +
916
- ` Environment: ${environmentId}\n` +
917
- (resolvedHost ? ` Host: ${resolvedHost}\n` : '') +
918
- `\nIf using a custom --host, make sure the space exists on that host.`,
919
- });
920
- return;
921
- }
922
- const outcome = await handlePreview422(e, extractSessionId);
923
- if (outcome.kind === 'validation-error') {
924
- update({
925
- step: 'preview-validation-error',
926
- previewValidationErrors: outcome.errors,
927
- previewValidationMissingNames: outcome.missingNames,
928
- });
929
- return;
930
- }
931
- const formatted = formatApiError(e, process.env['EDSI_VERBOSE_ERRORS'] === '1');
932
- update({
933
- step: 'error',
934
- errorStep: 'apply preview',
935
- errorMessage: formatted,
936
- errorAllowCredentialRetry: true,
937
- });
938
- return;
939
- }
940
- const msg = e instanceof Error ? e.message : 'Preview failed';
941
- update({
942
- step: 'error',
943
- errorStep: 'apply preview',
944
- errorMessage: msg,
945
- errorAllowCredentialRetry: true,
946
- });
947
- }
948
- };
949
- const runPush = async (manifest, spaceId, environmentId, cmaToken, host, acknowledgeBreakingChanges, allowDeletions, preview) => {
950
- if (shouldRefusePush(state)) {
951
- update(buildSkippedPushTransition());
952
- return;
953
- }
954
- if (preview) {
955
- const hasComponentChanges = preview.components.new.length > 0 ||
956
- preview.components.changed.length > 0 ||
957
- preview.components.removed.length > 0;
958
- const hasTokenChanges = preview.tokens.new.length > 0 || preview.tokens.changed.length > 0 || preview.tokens.removed.length > 0;
959
- if (!hasComponentChanges && !hasTokenChanges) {
960
- update({
961
- step: 'done',
962
- pushResult: {
963
- componentTypes: { created: 0, updated: 0, removed: 0, failed: 0 },
964
- designTokens: { created: 0, updated: 0, removed: 0, failed: 0 },
965
- },
966
- });
967
- return;
968
- }
969
- }
970
- const cycles = detectSlotCycles(extractComponentsFromManifest(manifest));
971
- if (cycles.length > 0) {
972
- update({
973
- step: 'error',
974
- errorStep: 'apply push',
975
- errorMessage: formatSlotCycleReport(cycles).join('\n'),
976
- errorAllowCredentialRetry: false,
977
- });
978
- return;
979
- }
980
- update({ step: 'pushing', pushProgress: null });
981
- try {
982
- const resolvedHost = resolveWizardHost(host);
983
- const client = new ImportApiClient({
984
- cmaToken,
985
- spaceId,
986
- environmentId,
987
- host: resolvedHost,
988
- });
989
- let operation = await client.applyImport(manifest, { acknowledgeBreakingChanges, allowDeletions });
990
- try {
991
- logStep({
992
- applyResponse: {
993
- status: operation?.sys?.status,
994
- id: operation?.sys?.id,
995
- keys: Object.keys(operation ?? {}),
996
- },
997
- });
998
- }
999
- catch (err) {
1000
- process.stderr.write(`[eds] log write failed: ${err instanceof Error ? err.message : String(err)}\n`);
1001
- }
1002
- update({
1003
- pushProgress: { kind: 'queued', operationId: operation.sys.id },
1004
- });
1005
- let pollCount = 0;
1006
- operation = await client.pollOperation(operation.sys.id, {
1007
- onProgress: (op) => {
1008
- pollCount++;
1009
- const s = op.summary;
1010
- if (s) {
1011
- const done = s.total - s.pending;
1012
- const items = op.items ?? [];
1013
- const lastDone = items.length > 0 ? items[items.length - 1] : null;
1014
- const current = lastDone && lastDone.status === 'succeeded' ? lastDone.id : null;
1015
- update({
1016
- pushProgress: { kind: 'progress', processed: done, total: s.total, current },
1017
- });
1018
- }
1019
- try {
1020
- logStep({
1021
- pollTick: {
1022
- attempt: pollCount,
1023
- status: op.sys.status,
1024
- summary: op.summary,
1025
- },
1026
- });
1027
- }
1028
- catch (err) {
1029
- process.stderr.write(`[eds] log write failed: ${err instanceof Error ? err.message : String(err)}\n`);
1030
- }
1031
- },
1032
- });
1033
- try {
1034
- logStep({
1035
- pollResult: {
1036
- status: operation?.sys?.status,
1037
- keys: Object.keys(operation ?? {}),
1038
- itemCount: operation.items?.length,
1039
- summary: operation.summary,
1040
- sampleItems: operation.items?.slice(0, 3),
1041
- },
1042
- });
1043
- }
1044
- catch (logErr) {
1045
- logStep({ pollLogError: String(logErr) });
1046
- }
1047
- const items = operation.items ?? [];
1048
- let pushResult;
1049
- if (items.length > 0) {
1050
- pushResult = {
1051
- componentTypes: {
1052
- created: items.filter((i) => i.entityType === 'ComponentType' && i.action === 'create' && i.status === 'succeeded').length,
1053
- updated: items.filter((i) => i.entityType === 'ComponentType' && i.action === 'update' && i.status === 'succeeded').length,
1054
- removed: items.filter((i) => i.entityType === 'ComponentType' && i.action === 'delete' && i.status === 'succeeded').length,
1055
- failed: items.filter((i) => i.entityType === 'ComponentType' && i.status === 'failed').length,
1056
- },
1057
- designTokens: {
1058
- created: items.filter((i) => i.entityType === 'DesignToken' && i.action === 'create' && i.status === 'succeeded').length,
1059
- updated: items.filter((i) => i.entityType === 'DesignToken' && i.action === 'update' && i.status === 'succeeded').length,
1060
- removed: items.filter((i) => i.entityType === 'DesignToken' && i.action === 'delete' && i.status === 'succeeded').length,
1061
- failed: items.filter((i) => i.entityType === 'DesignToken' && i.status === 'failed').length,
1062
- },
1063
- summary: operation.summary,
1064
- failures: items
1065
- .filter((item) => item.status === 'failed')
1066
- .map((item) => ({
1067
- entityType: item.entityType,
1068
- entityId: item.id,
1069
- message: formatEdsiError(item.error),
1070
- })),
1071
- };
1072
- }
1073
- else {
1074
- const summary = operation.summary ?? { total: 0, pending: 0, succeeded: 0, failed: 0 };
1075
- const anyFailure = summary.failed > 0 || operation.sys.status === 'failed' || operation.sys.status === 'partial';
1076
- pushResult = {
1077
- componentTypes: {
1078
- created: anyFailure ? 0 : (preview?.components.new.length ?? 0),
1079
- updated: anyFailure ? 0 : (preview?.components.changed.length ?? 0),
1080
- removed: anyFailure ? 0 : (preview?.components.removed.length ?? 0),
1081
- failed: summary.failed,
1082
- },
1083
- designTokens: {
1084
- created: anyFailure ? 0 : (preview?.tokens.new.length ?? 0),
1085
- updated: anyFailure ? 0 : (preview?.tokens.changed.length ?? 0),
1086
- removed: anyFailure ? 0 : (preview?.tokens.removed.length ?? 0),
1087
- failed: 0,
1088
- },
1089
- summary,
1090
- };
1091
- }
1092
- update({ step: 'done', pushResult });
1093
- }
1094
- catch (e) {
1095
- let msg;
1096
- if (e instanceof ApiError) {
1097
- msg = formatApiError(e, process.env['EDSI_VERBOSE_ERRORS'] === '1');
1098
- }
1099
- else if (e instanceof Error) {
1100
- msg = e.message;
1101
- }
1102
- else {
1103
- msg = 'Push failed';
1104
- }
1105
- update({
1106
- step: 'error',
1107
- errorStep: 'apply push',
1108
- errorMessage: msg,
1109
- errorAllowCredentialRetry: true,
1110
- });
1111
- }
1112
- };
1113
- const runPrintFiles = async (extractSessionId, outDir, opts = {}) => {
1114
- update({ step: 'printing' });
1115
- const componentsPath = join(outDir, 'components.json');
1116
- const printArgs = ['print', 'components', '--out', componentsPath];
1117
- if (extractSessionId)
1118
- printArgs.push('--session', extractSessionId);
1119
- if (opts.allowEmpty)
1120
- printArgs.push('--allow-empty');
1121
- const r = await runCli(printArgs);
1122
- if (r.exitCode !== 0) {
1123
- update({
1124
- step: 'error',
1125
- errorStep: 'print components',
1126
- errorMessage: r.stderr.trim() || 'Unknown error',
1127
- });
1128
- return { ok: false };
1129
- }
1130
- let emittedTokensPath;
1131
- let emittedTokenCount;
1132
- if (opts.tokenSessionId) {
1133
- const tokensOut = join(outDir, 'tokens.json');
1134
- const tokenArgs = ['print', 'tokens', '--out', tokensOut, '--session', opts.tokenSessionId];
1135
- const tr = await runCli(tokenArgs);
1136
- if (tr.exitCode !== 0) {
1137
- update({
1138
- step: 'error',
1139
- errorStep: 'print tokens',
1140
- errorMessage: tr.stderr.trim() || 'Unknown error',
1141
- });
1142
- return { ok: false };
1143
- }
1144
- emittedTokensPath = tokensOut;
1145
- emittedTokenCount = parsePrintTokensCount(tr.stdout);
1146
- }
1147
- update(nextStateAfterPrint({ skipGate: opts.skipGate, componentsPath }));
1148
- return {
1149
- ok: true,
1150
- ...(emittedTokensPath ? { tokensPath: emittedTokensPath } : {}),
1151
- ...(typeof emittedTokenCount === 'number' ? { tokenCount: emittedTokenCount } : {}),
1152
- };
1153
- };
1154
- const runSaveAndPush = async () => {
1155
- await startSaveFlow({ skipGate: true, andPush: true });
1156
- };
1157
- const pendingSaveOptionsRef = useRef({});
1158
- const startSaveFlow = async (opts = {}) => {
1159
- pendingSaveOptionsRef.current = opts;
1160
- if (outDirOverride) {
1161
- await mkdir(outDirOverride, { recursive: true });
1162
- if (onConflictMode) {
1163
- const resolved = await resolveSavePath(outDirOverride, { onConflict: onConflictMode });
1164
- if (resolved.kind === 'fail') {
1165
- const files = resolved.conflict.files.join(', ');
1166
- process.stderr.write(`Error: --on-conflict fail — refusing to overwrite ${files} at ${resolved.conflict.path}.\n`);
1167
- process.exit(1);
1168
- return;
1169
- }
1170
- if (resolved.kind === 'write') {
1171
- await mkdir(resolved.path, { recursive: true });
1172
- await proceedToWrite(resolved.path);
1173
- return;
1174
- }
1175
- }
1176
- await proceedToWrite(outDirOverride);
1177
- return;
1178
- }
1179
- setState((prev) => ({ ...prev, step: 'path-prompt' }));
1180
- };
1181
- const proceedToWrite = async (path) => {
1182
- setState((prev) => ({ ...prev, outDir: path }));
1183
- const { extractSessionId, tokensPath } = sessionRef.current;
1184
- const { skipGate, andPush } = pendingSaveOptionsRef.current;
1185
- const result = await runPrintFiles(extractSessionId, path, {
1186
- ...(skipGate ? { skipGate: true } : {}),
1187
- ...(state.tokenSessionId ? { tokenSessionId: state.tokenSessionId } : {}),
1188
- ...(allowEmptyDeleteAllRef.current ? { allowEmpty: true } : {}),
1189
- });
1190
- if (!result.ok)
1191
- return;
1192
- const recordedTokensPath = result.tokensPath ?? (state.tokenSessionId ? tokensPath || null : null);
1193
- const recordedTokenCount = result.tokenCount ?? state.tokenCount;
1194
- if (result.ok) {
1195
- try {
1196
- let sourceFingerprint = null;
1197
- let savedFingerprint = null;
1198
- try {
1199
- if (state.extractSessionId) {
1200
- const db = openPipelineDb();
1201
- try {
1202
- sourceFingerprint = await buildSourceFingerprint({
1203
- db,
1204
- extractSessionId: state.extractSessionId,
1205
- rawTokensPath: state.rawTokensPath || null,
1206
- });
1207
- }
1208
- finally {
1209
- db.close();
1210
- }
1211
- }
1212
- }
1213
- catch (err) {
1214
- process.stderr.write(`Warning: failed to compute source fingerprint: ${err instanceof Error ? err.message : String(err)}\n`);
1215
- }
1216
- try {
1217
- const componentsBuf = await readFile(join(path, 'components.json')).catch(() => null);
1218
- const tokensBuf = recordedTokensPath ? await readFile(recordedTokensPath).catch(() => null) : null;
1219
- savedFingerprint = buildSavedFingerprint({
1220
- componentsJson: componentsBuf,
1221
- tokensJson: tokensBuf,
1222
- });
1223
- }
1224
- catch (err) {
1225
- process.stderr.write(`Warning: failed to compute saved fingerprint: ${err instanceof Error ? err.message : String(err)}\n`);
1226
- }
1227
- const record = await appendRun({
1228
- projectPath: state.projectPath,
1229
- savePath: path,
1230
- componentCount: state.generatedAcceptedCount || state.generatedCount,
1231
- tokenCount: recordedTokenCount,
1232
- tokensPath: recordedTokensPath || null,
1233
- tokenSessionId: state.tokenSessionId,
1234
- agent: state.agent,
1235
- pushedTo: null,
1236
- extractSessionId: state.extractSessionId ?? '',
1237
- generateSessionId: state.generateSessionId,
1238
- sourceFingerprint,
1239
- savedFingerprint,
1240
- compositionMode,
1241
- });
1242
- setState((prev) => ({ ...prev, lastRunId: record.id }));
1243
- }
1244
- catch (err) {
1245
- process.stderr.write(`Warning: failed to record run: ${err instanceof Error ? err.message : String(err)}\n`);
1246
- }
1247
- }
1248
- if (andPush) {
1249
- void runPreview(extractSessionId, tokensPath, state.spaceId, state.environmentId, state.cmaToken, state.host);
1250
- }
1251
- };
1252
- const tokenReuseChecked = useRef(false);
1253
- useEffect(() => {
1254
- if (state.step === 'generating-tokens') {
1255
- if (tokenReuseChecked.current)
1256
- return; // already checked or user chose regenerate
1257
- tokenReuseChecked.current = true;
1258
- const existingTokensPath = join(state.outDir, 'tokens.json');
1259
- (async () => {
1260
- try {
1261
- await access(existingTokensPath);
1262
- const [tokensStat, sourceStat] = await Promise.all([
1263
- stat(existingTokensPath),
1264
- stat(state.rawTokensPath).catch(() => null),
1265
- ]);
1266
- const sourceChanged = sourceStat ? sourceStat.mtimeMs > tokensStat.mtimeMs : false;
1267
- update({
1268
- step: 'token-reuse-gate',
1269
- tokensPath: existingTokensPath,
1270
- tokenSourceChanged: sourceChanged,
1271
- });
1272
- }
1273
- catch {
1274
- if (await runAgentAuthCheck('generating-tokens')) {
1275
- void runGenerateTokens(state.rawTokensPath, state.outDir);
1276
- }
1277
- }
1278
- })();
1279
- }
1280
- }, [state.step]);
1281
- const pushFromPickerDispatched = useRef(false);
1282
- useEffect(() => {
1283
- if (state.step !== 'push-from-picker')
1284
- return;
1285
- if (pushFromPickerDispatched.current)
1286
- return;
1287
- pushFromPickerDispatched.current = true;
1288
- void runPreview(state.extractSessionId, state.tokensPath, state.spaceId, state.environmentId, state.cmaToken, state.host);
1289
- }, [state.step]);
1290
- const noQuitSteps = [
1291
- 'run-picker',
1292
- 'checking-claude-auth',
1293
- 'validating-credentials',
1294
- 'generating-tokens',
1295
- 'extracting',
1296
- 'generating',
1297
- 'printing',
1298
- 'previewing',
1299
- 'push-from-picker',
1300
- 'pushing',
1301
- ];
1302
- const hints = noQuitSteps.includes(state.step) ? [] : [{ key: 'q', label: 'quit' }];
1303
- const hasTokens = !!state.tokensPath;
1304
- const hasComponents = !state.skipComponents;
1305
- const totalSteps = 3 + (hasTokens ? 1 : 0) + (hasComponents ? 2 : 0);
1306
- const stepContent = (() => {
1307
- switch (state.step) {
1308
- case 'run-picker':
1309
- return (_jsx(RunPicker, { runs: initialRuns ?? [], onSelect: (selection) => {
1310
- if (selection.action === 'new') {
1311
- update({ step: 'welcome' });
1312
- return;
1313
- }
1314
- onRunPicked?.(selection);
1315
- }, onCancel: () => process.exit(0) }));
1316
- case 'welcome':
1317
- return (_jsx(WelcomeStep, { onContinue: (path) => {
1318
- const projectPath = normalizePath(path);
1319
- const outDir = join(projectPath, '.contentful');
1320
- update({ step: 'token-input', projectPath, outDir });
1321
- }, onQuit: () => process.exit(0) }));
1322
- case 'token-input':
1323
- return (_jsx(TokenInputStep, { onConfirm: (rawTokensPath) => {
1324
- update({ rawTokensPath, step: 'generating-tokens' });
1325
- }, onSkip: () => update({ step: 'path-validation' }), onQuit: () => process.exit(0) }));
1326
- case 'token-reuse-gate':
1327
- return (_jsx(GateStep, { successMessage: "Existing tokens.json found", summary: state.tokenSourceChanged
1328
- ? `Source file has been modified since tokens were last generated.\n ${state.tokensPath}`
1329
- : `Source file has not changed since tokens were last generated.\n ${state.tokensPath}`, context: state.tokenSourceChanged
1330
- ? 'The source tokens file changed — regenerating is recommended.'
1331
- : 'No changes detected — reusing the existing tokens avoids nondeterministic AI drift.', continueLabel: "Reuse existing tokens", skipLabel: "Regenerate tokens", showSkip: true, onContinue: () => update({ step: 'path-validation', tokenSessionId: null }), onSkip: async () => {
1332
- update({ tokenSourceChanged: null });
1333
- if (await runAgentAuthCheck('generating-tokens')) {
1334
- void runGenerateTokens(state.rawTokensPath, state.outDir);
1335
- }
1336
- }, onQuit: () => process.exit(0) }));
1337
- case 'checking-claude-auth':
1338
- return (_jsx(RunningStep, { stepNumber: state.authCheckStepNumber, totalSteps: totalSteps, title: `Checking ${state.agent}`, description: `Verifying ${state.agent} is installed and authenticated...` }));
1339
- case 'generating-tokens':
1340
- return (_jsx(RunningStep, { stepNumber: 1, totalSteps: totalSteps, title: "Generating token definitions", description: `${state.agent} is mapping your design tokens to DTCG format and writing tokens.json. This may take a few minutes.` }));
1341
- case 'path-validation':
1342
- return (_jsx(PathValidationStep, { projectPath: state.projectPath, onConfirm: (path) => {
1343
- void runExtract(path);
1344
- }, onSkipComponents: () => {
1345
- if (noPush) {
1346
- update({ skipComponents: true, acceptedCount: 0 });
1347
- void startSaveFlow();
1348
- return;
1349
- }
1350
- update({ step: 'credentials', skipComponents: true, acceptedCount: 0 });
1351
- }, onChangePath: () => update({ step: 'welcome' }), onQuit: () => process.exit(0) }));
1352
- case 'extracting': {
1353
- const ep = state.extractProgress;
1354
- let extractDetail;
1355
- if (ep && ep.totalFiles > 0) {
1356
- extractDetail = `Analyzing ${ep.filesProcessed}/${ep.totalFiles} files · ${ep.componentsFound} component${ep.componentsFound === 1 ? '' : 's'} found`;
1357
- }
1358
- else if (ep && ep.scanned > 0) {
1359
- extractDetail = `Scanned ${ep.scanned} file${ep.scanned === 1 ? '' : 's'}...`;
1360
- }
1361
- else {
1362
- extractDetail = 'Scanning...';
1363
- }
1364
- const compositionDetail = (() => {
1365
- const phase = state.compositionPhase;
1366
- if (!phase)
1367
- return undefined;
1368
- if (phase === 'resolving')
1369
- return 'Resolving composition mapping...';
1370
- if (phase === 'cache-hit')
1371
- return 'Composition mapping (cached)...';
1372
- if (phase === 'authoring')
1373
- return 'Writing a composition parser...';
1374
- if (phase === 'parsing')
1375
- return 'Running the composition parser...';
1376
- if (phase.startsWith('agent:'))
1377
- return `Resolving composition via ${phase.slice('agent:'.length)} agent...`;
1378
- if (phase === 'done')
1379
- return 'Composition mapping resolved ✓';
1380
- return `Composition: ${phase}`;
1381
- })();
1382
- return (_jsx(RunningStep, { stepNumber: hasTokens ? 2 : 1, totalSteps: totalSteps, title: "Extracting components", description: "I'm scanning your files and figuring out what components exist, what props they have, and how they're structured. This is fully automatic \u2014 sit tight.", detail: extractDetail, secondaryDetail: compositionDetail }));
1383
- }
1384
- case 'scope-gate': {
1385
- if (!state.extractSessionId) {
1386
- return (_jsx(Box, { paddingX: 2, paddingY: 1, children: _jsx(Text, { color: PALETTE.error, children: "Error: extract session ID missing \u2014 please re-run." }) }));
1387
- }
1388
- const sessionId = state.extractSessionId;
1389
- const db = openPipelineDb();
1390
- let components;
1391
- try {
1392
- components = loadScopeComponents(db, sessionId);
1393
- }
1394
- finally {
1395
- db.close();
1396
- }
1397
- components = mergeAiDecisions(components, state.aiDecisions);
1398
- return (_jsx(ScopeGateHost, { components: components, autoAccept: autoAcceptScope, compositionMode: compositionMode, aiFilterStatus: state.aiFilterStatus, aiFilterProgress: state.aiFilterProgress, aiFilterError: state.aiFilterError, onCancelAutoFilter: cancelAutoFilter, onConfirm: (decisions) => {
1399
- void runScopeGate({
1400
- sessionId,
1401
- decisions,
1402
- cancelAutoFilter: state.aiFilterStatus === 'running' ? cancelAutoFilterAndWait : undefined,
1403
- onAdvanceToGenerate: async ({ sessionId: sid, acceptedCount }) => {
1404
- update({ acceptedCount, autoRejectedCount: 0 });
1405
- const next = nextStepAfterScopeGate({ acceptedCount, noPush });
1406
- if (next === 'generating') {
1407
- if (await runAgentAuthCheck('generating')) {
1408
- void runGenerate(sid, state.tokensPath, acceptedCount);
1409
- }
1410
- return;
1411
- }
1412
- if (acceptedCount > 0 && !noPush) {
1413
- if (await runAgentAuthCheck('credentials')) {
1414
- startGeneratePrefetch(sid, state.tokensPath);
1415
- }
1416
- }
1417
- update({ step: 'credentials' });
1418
- },
1419
- onAdvanceToPushFlow: (count) => {
1420
- update({ acceptedCount: count, autoRejectedCount: 0 });
1421
- const next = nextStepAfterScopeGate({ acceptedCount: count, noPush });
1422
- if (next === 'print-gate') {
1423
- void startSaveFlow();
1424
- return;
1425
- }
1426
- advanceToPushFlow(count);
1427
- },
1428
- });
1429
- }, onQuit: () => process.exit(0) }));
1430
- }
1431
- case 'generating': {
1432
- const p = state.generateProgress;
1433
- const stepNum = hasTokens ? 4 : 3;
1434
- const progressDetail = p
1435
- ? `[${p.done}/${p.total}] ${p.current} — this can take 10–30 minutes for large libraries`
1436
- : `Starting up ${state.agent}... (this can take 10–30 minutes for large libraries — grab a coffee)`;
1437
- 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 }));
1438
- }
1439
- case 'final-review': {
1440
- return (_jsx(FinalReviewHost, { extractSessionId: state.extractSessionId, generatedCount: state.generatedCount, autoAccept: autoAcceptScope, compositionMode: compositionMode, livePreview: livePreview, spaceId: state.spaceId, environmentId: state.environmentId, cmaToken: state.cmaToken, host: state.host, tokensPath: state.tokensPath, initialFinalizeError: state.finalizeErrorBanner, allowDeletions: allowDeletions, onFinalize: (accepted, rejected, unresolved) => {
1441
- process.stderr.write(`Accepted: ${accepted} Rejected: ${rejected} Unresolved: ${unresolved}\n`);
1442
- let acceptedCount = accepted;
1443
- const detectAcceptedCycles = () => {
1444
- if (!state.extractSessionId)
1445
- return [];
1446
- try {
1447
- const db = openPipelineDb();
1448
- try {
1449
- const acceptedComponents = loadCDFComponents(db, state.extractSessionId);
1450
- return findSlotCycles(buildComponentGraph(acceptedComponents));
1451
- }
1452
- finally {
1453
- db.close();
1454
- }
1455
- }
1456
- catch {
1457
- return [];
1458
- }
1459
- };
1460
- let acceptedCycles = detectAcceptedCycles();
1461
- const gateAction = resolveCycleGateAction({
1462
- hasCycles: acceptedCycles.length > 0,
1463
- autoRejectCycles,
1464
- });
1465
- const routeToCycleError = () => {
1466
- update({
1467
- step: 'error',
1468
- errorStep: 'final review',
1469
- errorMessage: formatSlotCycleReport(acceptedCycles).join('\n'),
1470
- errorAllowCredentialRetry: false,
1471
- });
1472
- };
1473
- if (gateAction === 'block') {
1474
- routeToCycleError();
1475
- return;
1476
- }
1477
- if (gateAction === 'auto-reject' && state.extractSessionId) {
1478
- const sessionId = state.extractSessionId;
1479
- let excluded = [];
1480
- try {
1481
- const db = openPipelineDb();
1482
- try {
1483
- const acceptedComponents = loadCDFComponents(db, sessionId);
1484
- const targets = computeCycleAutoRejectTargets(acceptedCycles, buildComponentGraph(acceptedComponents));
1485
- excluded = [...targets];
1486
- if (excluded.length > 0) {
1487
- const stmt = db.prepare(`UPDATE raw_components SET status = 'generate-rejected' WHERE session_id = ? AND name = ?`);
1488
- db.exec('BEGIN');
1489
- try {
1490
- for (const name of excluded) {
1491
- stmt.run(sessionId, name);
1492
- }
1493
- db.exec('COMMIT');
1494
- }
1495
- catch (e) {
1496
- db.exec('ROLLBACK');
1497
- throw e;
1498
- }
1499
- }
1500
- }
1501
- finally {
1502
- db.close();
1503
- }
1504
- }
1505
- catch {
1506
- routeToCycleError();
1507
- return;
1508
- }
1509
- if (excluded.length > 0) {
1510
- process.stderr.write(`Auto-rejected cycle participants: ${excluded.join(', ')}\n`);
1511
- logStep({ event: 'cycle-auto-reject', excluded });
1512
- }
1513
- acceptedCycles = detectAcceptedCycles();
1514
- if (acceptedCycles.length > 0) {
1515
- routeToCycleError();
1516
- return;
1517
- }
1518
- const remaining = (() => {
1519
- if (!state.extractSessionId)
1520
- return acceptedCount;
1521
- try {
1522
- const db = openPipelineDb();
1523
- try {
1524
- return loadCDFComponents(db, sessionId).length;
1525
- }
1526
- finally {
1527
- db.close();
1528
- }
1529
- }
1530
- catch {
1531
- return acceptedCount;
1532
- }
1533
- })();
1534
- acceptedCount = remaining;
1535
- }
1536
- const allowEmptyDeleteAll = acceptedCount === 0;
1537
- allowEmptyDeleteAllRef.current = allowEmptyDeleteAll;
1538
- update({ finalReviewPassed: true, allowEmptyDeleteAll });
1539
- if (noPush) {
1540
- update({ generatedAcceptedCount: acceptedCount });
1541
- void startSaveFlow();
1542
- return;
1543
- }
1544
- if (noSave) {
1545
- update({ generatedAcceptedCount: acceptedCount });
1546
- const { extractSessionId, tokensPath } = sessionRef.current;
1547
- void runPreview(extractSessionId, tokensPath, state.spaceId, state.environmentId, state.cmaToken, state.host);
1548
- return;
1549
- }
1550
- if (autoAcceptScope) {
1551
- update({ generatedAcceptedCount: acceptedCount });
1552
- void runSaveAndPush();
1553
- return;
1554
- }
1555
- update({ generatedAcceptedCount: acceptedCount, step: 'push-decision-gate' });
1556
- }, onQuit: () => process.exit(0) }));
1557
- }
1558
- case 'push-decision-gate': {
1559
- const tokenDesc = hasTokens ? 'tokens.json' : null;
1560
- const compDesc = hasComponents ? 'components.json' : null;
1561
- const files = [tokenDesc, compDesc].filter(Boolean).join(' and ');
1562
- const count = state.generatedAcceptedCount > 0 ? state.generatedAcceptedCount : state.generatedCount;
1563
- const summary = hasComponents
1564
- ? `${count} component definition${count !== 1 ? 's' : ''} ready${hasTokens ? ', design tokens ready' : ''}.`
1565
- : hasTokens
1566
- ? 'Design tokens ready.'
1567
- : 'Ready to continue.';
1568
- return (_jsx(PushDecisionGateStep, { summary: summary, context: `Save ${files || 'output files'} to disk, push to your Contentful space, or both.`, fileList: files || 'files', pushDisabled: state.credentialsSkipped, onChoice: (choice) => {
1569
- const action = chooseGateAction(choice);
1570
- if (action === 'save-and-push') {
1571
- void runSaveAndPush();
1572
- return;
1573
- }
1574
- if (action === 'push-only') {
1575
- const { extractSessionId, tokensPath } = sessionRef.current;
1576
- void runPreview(extractSessionId, tokensPath, state.spaceId, state.environmentId, state.cmaToken, state.host);
1577
- return;
1578
- }
1579
- void startSaveFlow();
1580
- }, onQuit: () => process.exit(0) }));
1581
- }
1582
- case 'credentials':
1583
- 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) => {
1584
- void confirmCredentials(spaceId, environmentId, cmaToken, host);
1585
- }, onContinue: (spaceId, environmentId, cmaToken, host) => {
1586
- void confirmCredentials(spaceId, environmentId, cmaToken, host);
1587
- }, onRetryPrefetch: state.generatePrefetchStatus === 'failed' && sessionRef.current.extractSessionId
1588
- ? () => {
1589
- const sid = sessionRef.current.extractSessionId;
1590
- void startGeneratePrefetch(sid, state.tokensPath);
1591
- }
1592
- : undefined, onSkip: () => {
1593
- update({ credentialsSkipped: true, credentialsError: '' });
1594
- void advanceAfterCredentialsValidated();
1595
- }, onQuit: () => {
1596
- cancelGeneratePrefetch();
1597
- process.exit(0);
1598
- } }));
1599
- case 'push-from-picker':
1600
- case 'previewing':
1601
- return (_jsx(RunningStep, { stepNumber: totalSteps, totalSteps: totalSteps, title: "Computing diff", description: "Computing diff against your Contentful space..." }));
1602
- case 'preview-gate': {
1603
- // Only offer "[e] Edit definitions" when the manifest actually has
1604
- // components to edit. In the delete-all / empty case the manifest is
1605
- // present-but-empty (only $schema), so editing would dead-end on the
1606
- // "No generated definitions found" screen.
1607
- const editableComponentCount = Object.keys(state.manifest?.componentsManifest ?? {}).filter((k) => k !== '$schema').length;
1608
- return (_jsx(WizardPreviewStep, { preview: state.serverPreview, spaceId: state.spaceId, environmentId: state.environmentId, stepNumber: totalSteps, totalSteps: totalSteps, allowDeletions: allowDeletions, onConfirm: (acknowledge, deleteMissing) => {
1609
- void runPush(state.manifest, state.spaceId, state.environmentId, state.cmaToken, state.host, acknowledge, deleteMissing, state.serverPreview);
1610
- }, ...(editableComponentCount > 0 ? { onEdit: () => void runEditFromPreview() } : {}), onSaveFiles: () => {
1611
- void startSaveFlow();
1612
- }, onQuit: () => process.exit(0) }));
1613
- }
1614
- case 'pushing':
1615
- return _jsx(PushingStep, { stepNumber: totalSteps, totalSteps: totalSteps, progress: state.pushProgress });
1616
- case 'path-prompt':
1617
- return (_jsx(PathPrompt, { defaultPath: state.outDir, onSubmit: (submitted) => {
1618
- void (async () => {
1619
- await mkdir(submitted, { recursive: true });
1620
- const hasConflict = await detectSaveConflict(submitted);
1621
- if (hasConflict) {
1622
- setState((prev) => ({ ...prev, step: 'save-conflict-gate', outDir: submitted }));
1623
- return;
1624
- }
1625
- await proceedToWrite(submitted);
1626
- })();
1627
- }, onCancel: () => process.exit(0) }));
1628
- case 'save-conflict-gate':
1629
- return (_jsx(SaveConflictGate, { path: state.outDir, onOverwrite: () => {
1630
- void proceedToWrite(state.outDir);
1631
- }, onNew: () => {
1632
- const subdir = buildTimestampedSubdir(state.outDir);
1633
- void (async () => {
1634
- await mkdir(subdir, { recursive: true });
1635
- await proceedToWrite(subdir);
1636
- })();
1637
- }, onCancel: () => setState((prev) => ({ ...prev, step: 'path-prompt' })) }));
1638
- case 'printing':
1639
- return (_jsx(RunningStep, { stepNumber: totalSteps, totalSteps: totalSteps, title: "Writing files", description: "Writing output files to disk..." }));
1640
- case 'print-gate': {
1641
- const teaser = buildRunTeaserLine(state.lastRunId);
1642
- return (_jsx(GateStep, { successMessage: "Files saved", summary: [
1643
- hasComponents && state.componentsPath ? `components.json → ${state.componentsPath}` : null,
1644
- hasTokens && state.tokensPath ? `tokens.json → ${state.tokensPath}` : null,
1645
- ]
1646
- .filter(Boolean)
1647
- .join('\n'), context: teaser
1648
- ? `Your files are saved to disk. ${teaser}`
1649
- : "Your files are saved to disk. Run `experiences import` again when you're ready to push to Contentful.", continueLabel: "Exit", showSkip: false, onContinue: () => process.exit(0), onQuit: () => process.exit(0) }));
1650
- }
1651
- case 'done': {
1652
- const totalFailed = state.pushResult.componentTypes.failed + state.pushResult.designTokens.failed;
1653
- const teaser = buildRunTeaserLine(state.lastRunId);
1654
- return (_jsx(DoneStep, { componentTypes: state.pushResult.componentTypes, designTokens: state.pushResult.designTokens, summary: state.pushResult.summary, failures: state.pushResult.failures, spaceId: state.spaceId, environmentId: state.environmentId, host: state.host, ...(teaser ? { runTeaser: teaser } : {}), onExit: () => process.exit(totalFailed > 0 ? 1 : 0) }));
1655
- }
1656
- case 'preview-validation-error': {
1657
- return (_jsx(PreviewValidationErrorStep, { errors: state.previewValidationErrors, missingNames: state.previewValidationMissingNames, onEdit: () => {
1658
- void runEditFromPreview();
1659
- }, onSkip: () => {
1660
- void runSkipValidationErrorsAndRetry(state.previewValidationErrors);
1661
- }, onQuit: () => process.exit(0) }));
1662
- }
1663
- case 'error':
1664
- return (_jsx(ErrorStep, { stepName: state.errorStep, message: state.errorMessage, onExit: () => process.exit(1), onRetryCredentials: state.errorAllowCredentialRetry ? () => update({ step: 'credentials', credentialsError: '' }) : undefined }));
1665
- default:
1666
- return null;
1667
- }
1668
- })();
1669
- return (_jsxs(Box, { flexDirection: "column", width: terminalWidth, children: [_jsx(TopBar, { subcommand: "import", hints: hints }), _jsx(CustomPromptBanner, { selectPromptPath: selectPromptPath, generatePromptPath: generatePromptPath }), stepContent] }));
1670
- }