@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.
- package/dist/package.json +14 -10
- package/dist/src/index.js +34937 -9
- package/dist/src/lib/cli-path.d.ts +4 -0
- package/dist/src/runs/export-helpers.d.ts +1 -12
- package/dist/src/runs/push-helpers.d.ts +1 -6
- package/package.json +12 -7
- package/skills/generate-components.md +427 -0
- package/skills/generate-tokens.md +194 -0
- package/skills/select-components.md +224 -0
- package/dist/src/analytics/apply.js +0 -30
- package/dist/src/analytics/client.js +0 -81
- package/dist/src/analytics/constants.js +0 -4
- package/dist/src/analytics/env.js +0 -14
- package/dist/src/analytics/exit.js +0 -24
- package/dist/src/analytics/index.js +0 -9
- package/dist/src/analytics/normalize.js +0 -18
- package/dist/src/analytics/os.js +0 -13
- package/dist/src/analytics/session.js +0 -15
- package/dist/src/analytics/tracker.js +0 -126
- package/dist/src/analytics/types.js +0 -1
- package/dist/src/analyze/build-analyze-view-rows.js +0 -63
- package/dist/src/analyze/command.js +0 -599
- package/dist/src/analyze/composite-closure.js +0 -0
- package/dist/src/analyze/composition/agent-parser/author-prompt.js +0 -22
- package/dist/src/analyze/composition/agent-parser/extract-parser.js +0 -44
- package/dist/src/analyze/composition/agent-parser/load-prompt.js +0 -31
- package/dist/src/analyze/composition/agent-parser/resolve-via-parser.js +0 -88
- package/dist/src/analyze/composition/agent-parser/sandbox.js +0 -187
- package/dist/src/analyze/composition/apply-mapping.js +0 -67
- package/dist/src/analyze/composition/candidate-critic-agent.js +0 -39
- package/dist/src/analyze/composition/candidate-critic.js +0 -63
- package/dist/src/analyze/composition/candidate-files.js +0 -131
- package/dist/src/analyze/composition/composition-cache-key.js +0 -19
- package/dist/src/analyze/composition/interchange-schema.js +0 -61
- package/dist/src/analyze/composition/manifest-doc-evidence.js +0 -136
- package/dist/src/analyze/composition/merge-edges.js +0 -70
- package/dist/src/analyze/composition/parse-map-edges.js +0 -55
- package/dist/src/analyze/composition/resolve-mapping-cli.js +0 -29
- package/dist/src/analyze/composition/resolve-mapping.js +0 -111
- package/dist/src/analyze/cycle-detection.js +0 -234
- package/dist/src/analyze/cycle-view.js +0 -28
- package/dist/src/analyze/fuzzy-search.js +0 -55
- package/dist/src/analyze/issue-inheritance.js +0 -108
- package/dist/src/analyze/lineage.js +0 -0
- package/dist/src/analyze/scope-gate-cascade.js +0 -170
- package/dist/src/analyze/search-neighborhood.js +0 -56
- package/dist/src/analyze/select/command.js +0 -484
- package/dist/src/analyze/select/index.js +0 -5
- package/dist/src/analyze/select/parser.js +0 -53
- package/dist/src/analyze/select/persistence.js +0 -80
- package/dist/src/analyze/select/preview-annotations.js +0 -62
- package/dist/src/analyze/select/stdout.js +0 -3
- package/dist/src/analyze/select/tui/App.js +0 -483
- package/dist/src/analyze/select/tui/components/ComponentDetail.js +0 -48
- package/dist/src/analyze/select/tui/components/ComponentRationalePanel.js +0 -125
- package/dist/src/analyze/select/tui/components/FieldEditor.js +0 -1131
- package/dist/src/analyze/select/tui/components/FinalizeDialog.js +0 -36
- package/dist/src/analyze/select/tui/components/GotoBanner.js +0 -30
- package/dist/src/analyze/select/tui/components/GroupedSidebar.js +0 -517
- package/dist/src/analyze/select/tui/components/HelpOverlay.js +0 -17
- package/dist/src/analyze/select/tui/components/JsonEditor.js +0 -131
- package/dist/src/analyze/select/tui/components/JsonPanel.js +0 -66
- package/dist/src/analyze/select/tui/components/LineagePanel.js +0 -22
- package/dist/src/analyze/select/tui/components/PreviewSummaryBar.js +0 -29
- package/dist/src/analyze/select/tui/components/QuitDialog.js +0 -14
- package/dist/src/analyze/select/tui/components/RationalePanel.js +0 -80
- package/dist/src/analyze/select/tui/components/Sidebar.js +0 -92
- package/dist/src/analyze/select/tui/components/SourcePanel.js +0 -52
- package/dist/src/analyze/select/tui/components/StatusBar.js +0 -7
- package/dist/src/analyze/select/tui/components/TopBar.js +0 -10
- package/dist/src/analyze/select/tui/components/removed-components-text.js +0 -11
- package/dist/src/analyze/select/tui/hooks/scroll-offset.js +0 -26
- package/dist/src/analyze/select/tui/hooks/useImmediateInput.js +0 -66
- package/dist/src/analyze/select/tui/hooks/useKeymap.js +0 -67
- package/dist/src/analyze/select/tui/hooks/useSession.js +0 -52
- package/dist/src/analyze/select/tui/hooks/useUndo.js +0 -26
- package/dist/src/analyze/select/tui/theme.js +0 -28
- package/dist/src/analyze/select/types.js +0 -39
- package/dist/src/analyze/select-agent/command.js +0 -558
- package/dist/src/analyze/select-agent/context-builder.js +0 -197
- package/dist/src/analyze/select-agent/show-rationale.js +0 -92
- package/dist/src/analyze/selection-cascade.js +0 -19
- package/dist/src/analyze/slot-graph.js +0 -17
- package/dist/src/analyze/tui/AnalyzeView.js +0 -64
- package/dist/src/apply/api-client.js +0 -440
- package/dist/src/apply/command.js +0 -845
- package/dist/src/apply/manifest.js +0 -1
- package/dist/src/apply/preview-utils.js +0 -26
- package/dist/src/apply/tui/SelectView.js +0 -34
- package/dist/src/apply/tui/ServerApplyView.js +0 -49
- package/dist/src/apply/tui/ServerPreviewView.js +0 -23
- package/dist/src/credentials-store.js +0 -74
- package/dist/src/generate/command.js +0 -597
- package/dist/src/generate/edit/command.js +0 -127
- package/dist/src/generate/tui/GenerateView.js +0 -10
- package/dist/src/host-utils.js +0 -26
- package/dist/src/import/agent-model-resolve.js +0 -35
- package/dist/src/import/auto-filter-resolve.js +0 -16
- package/dist/src/import/command.js +0 -380
- package/dist/src/import/cycle-auto-reject.js +0 -14
- package/dist/src/import/orchestrator.js +0 -603
- package/dist/src/import/path-utils.js +0 -30
- package/dist/src/import/picker-dispatch.js +0 -44
- package/dist/src/import/print-prompt.js +0 -31
- package/dist/src/import/strip-allowed-components.js +0 -22
- package/dist/src/import/tui/CustomPromptBanner.js +0 -8
- package/dist/src/import/tui/WizardApp.js +0 -1670
- package/dist/src/import/tui/ai-flag.js +0 -6
- package/dist/src/import/tui/auto-filter-error.js +0 -29
- package/dist/src/import/tui/autocomplete.js +0 -32
- package/dist/src/import/tui/components/AutoFilterBanner.js +0 -19
- package/dist/src/import/tui/components/CounterStrip.js +0 -12
- package/dist/src/import/tui/components/LegendEntry.js +0 -6
- package/dist/src/import/tui/cycle-panel-scroll.js +0 -33
- package/dist/src/import/tui/final-review-host.js +0 -25
- package/dist/src/import/tui/group-collapse.js +0 -11
- package/dist/src/import/tui/history.js +0 -53
- package/dist/src/import/tui/hooks/useLineage.js +0 -50
- package/dist/src/import/tui/hooks/useOverlayPanel.js +0 -27
- package/dist/src/import/tui/lineage-layout.js +0 -82
- package/dist/src/import/tui/merge-ai-decisions.js +0 -22
- package/dist/src/import/tui/push-decision-gate-helpers.js +0 -8
- package/dist/src/import/tui/push-progress.js +0 -1
- package/dist/src/import/tui/run-print-files-helpers.js +0 -8
- package/dist/src/import/tui/run-teaser.js +0 -9
- package/dist/src/import/tui/runLivePreview.js +0 -82
- package/dist/src/import/tui/runScopeGate.js +0 -28
- package/dist/src/import/tui/scope-gate-columns.js +0 -94
- package/dist/src/import/tui/scope-gate-host.js +0 -28
- package/dist/src/import/tui/sidebar-width.js +0 -3
- package/dist/src/import/tui/spawn-generate.js +0 -53
- package/dist/src/import/tui/step-filters.js +0 -42
- package/dist/src/import/tui/steps/AtomicGenerateReviewStep.js +0 -726
- package/dist/src/import/tui/steps/AtomicScopeGateStep.js +0 -210
- package/dist/src/import/tui/steps/CredentialsStep.js +0 -108
- package/dist/src/import/tui/steps/DoneStep.js +0 -24
- package/dist/src/import/tui/steps/ErrorStep.js +0 -16
- package/dist/src/import/tui/steps/GateStep.js +0 -22
- package/dist/src/import/tui/steps/GenerateReviewStep.js +0 -1618
- package/dist/src/import/tui/steps/PathValidationStep.js +0 -152
- package/dist/src/import/tui/steps/PreviewStep.js +0 -37
- package/dist/src/import/tui/steps/PreviewValidationErrorStep.js +0 -23
- package/dist/src/import/tui/steps/PushDecisionGateStep.js +0 -73
- package/dist/src/import/tui/steps/PushingStep.js +0 -24
- package/dist/src/import/tui/steps/RunningStep.js +0 -21
- package/dist/src/import/tui/steps/ScopeGateStep.js +0 -907
- package/dist/src/import/tui/steps/TokenInputStep.js +0 -88
- package/dist/src/import/tui/steps/WelcomeStep.js +0 -34
- package/dist/src/import/tui/steps/WizardPreviewStep.js +0 -169
- package/dist/src/import/tui/steps/auto-reject-decision.js +0 -9
- package/dist/src/import/tui/steps/breaking-change-format.js +0 -24
- package/dist/src/import/tui/steps/enumerate-cycle-breaks.js +0 -0
- package/dist/src/import/tui/steps/preview-diff.js +0 -175
- package/dist/src/import/tui/useFinalizePreview.js +0 -0
- package/dist/src/import/tui/useLivePreview.js +0 -122
- package/dist/src/import/tui/wizard-422-helpers.js +0 -60
- package/dist/src/import/tui/wizard-generate-progress.js +0 -25
- package/dist/src/import/tui/wizard-save-flow.js +0 -20
- package/dist/src/import/tui/wizard-state-transitions.js +0 -32
- package/dist/src/lib/agent-model-options.js +0 -15
- package/dist/src/lib/command-options.js +0 -30
- package/dist/src/lib/composition-mode.js +0 -39
- package/dist/src/lib/contentful-urls.js +0 -27
- package/dist/src/lib/debug-logger.js +0 -229
- package/dist/src/lib/debug-preamble.js +0 -33
- package/dist/src/lib/error-parser.js +0 -269
- package/dist/src/lib/prompt-overrides.js +0 -69
- package/dist/src/lib/terminal-capabilities.js +0 -51
- package/dist/src/lib/user-agent.js +0 -29
- package/dist/src/output/format.js +0 -110
- package/dist/src/print/command.js +0 -221
- package/dist/src/print/validate/tui/ValidateView.js +0 -37
- package/dist/src/print/validate/validators/cdf-validator.js +0 -104
- package/dist/src/print/validate/validators/dtcg-validator.js +0 -110
- package/dist/src/print/validate/validators/format-errors.js +0 -18
- package/dist/src/program.js +0 -105
- package/dist/src/runs/export-helpers.js +0 -59
- package/dist/src/runs/fingerprint.js +0 -75
- package/dist/src/runs/ls-command.js +0 -137
- package/dist/src/runs/modify-launcher.js +0 -42
- package/dist/src/runs/path-prompt.js +0 -72
- package/dist/src/runs/push-creds-prompt.js +0 -39
- package/dist/src/runs/push-helpers.js +0 -72
- package/dist/src/runs/push-launcher.js +0 -77
- package/dist/src/runs/replay-helpers.js +0 -164
- package/dist/src/runs/resolve-run-target.js +0 -45
- package/dist/src/runs/run-picker-mount.js +0 -72
- package/dist/src/runs/run-picker.js +0 -129
- package/dist/src/runs/save-conflict.js +0 -56
- package/dist/src/runs/save-path-resolver.js +0 -75
- package/dist/src/runs/staleness.js +0 -140
- package/dist/src/runs/store.js +0 -136
- package/dist/src/session/cache-keys.js +0 -44
- package/dist/src/session/command.js +0 -261
- package/dist/src/session/db.js +0 -1538
- package/dist/src/session/migration.js +0 -117
- package/dist/src/session/session-id.js +0 -212
- package/dist/src/session/stats.js +0 -89
- package/dist/src/setup/analytics-prompt.js +0 -24
- package/dist/src/setup/auto-filter-prompt.js +0 -24
- package/dist/src/setup/command.js +0 -966
- package/dist/src/setup/composition-mode-prompt.js +0 -37
- package/dist/src/setup/debug-mode-prompt.js +0 -22
- 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
|
-
}
|