@contentful/experience-design-system-cli 2.2.1

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 (165) hide show
  1. package/README.md +532 -0
  2. package/bin/cli.js +58 -0
  3. package/dist/package.json +56 -0
  4. package/dist/src/analyze/command.d.ts +3 -0
  5. package/dist/src/analyze/command.js +175 -0
  6. package/dist/src/analyze/extract/astro.d.ts +5 -0
  7. package/dist/src/analyze/extract/astro.js +280 -0
  8. package/dist/src/analyze/extract/pipeline.d.ts +6 -0
  9. package/dist/src/analyze/extract/pipeline.js +298 -0
  10. package/dist/src/analyze/extract/react.d.ts +2 -0
  11. package/dist/src/analyze/extract/react.js +1949 -0
  12. package/dist/src/analyze/extract/slot-detection.d.ts +35 -0
  13. package/dist/src/analyze/extract/slot-detection.js +101 -0
  14. package/dist/src/analyze/extract/stencil.d.ts +2 -0
  15. package/dist/src/analyze/extract/stencil.js +293 -0
  16. package/dist/src/analyze/extract/tsx-shared.d.ts +8 -0
  17. package/dist/src/analyze/extract/tsx-shared.js +263 -0
  18. package/dist/src/analyze/extract/vue-tsx.d.ts +2 -0
  19. package/dist/src/analyze/extract/vue-tsx.js +498 -0
  20. package/dist/src/analyze/extract/vue.d.ts +5 -0
  21. package/dist/src/analyze/extract/vue.js +647 -0
  22. package/dist/src/analyze/extract/web-components.d.ts +2 -0
  23. package/dist/src/analyze/extract/web-components.js +866 -0
  24. package/dist/src/analyze/pre-classify.d.ts +17 -0
  25. package/dist/src/analyze/pre-classify.js +144 -0
  26. package/dist/src/analyze/select/command.d.ts +2 -0
  27. package/dist/src/analyze/select/command.js +256 -0
  28. package/dist/src/analyze/select/index.d.ts +6 -0
  29. package/dist/src/analyze/select/index.js +5 -0
  30. package/dist/src/analyze/select/parser.d.ts +6 -0
  31. package/dist/src/analyze/select/parser.js +53 -0
  32. package/dist/src/analyze/select/persistence.d.ts +9 -0
  33. package/dist/src/analyze/select/persistence.js +42 -0
  34. package/dist/src/analyze/select/stdout.d.ts +7 -0
  35. package/dist/src/analyze/select/stdout.js +3 -0
  36. package/dist/src/analyze/select/tui/App.d.ts +8 -0
  37. package/dist/src/analyze/select/tui/App.js +491 -0
  38. package/dist/src/analyze/select/tui/components/ComponentDetail.d.ts +20 -0
  39. package/dist/src/analyze/select/tui/components/ComponentDetail.js +43 -0
  40. package/dist/src/analyze/select/tui/components/FieldEditor.d.ts +11 -0
  41. package/dist/src/analyze/select/tui/components/FieldEditor.js +531 -0
  42. package/dist/src/analyze/select/tui/components/FinalizeDialog.d.ts +10 -0
  43. package/dist/src/analyze/select/tui/components/FinalizeDialog.js +15 -0
  44. package/dist/src/analyze/select/tui/components/HelpOverlay.d.ts +7 -0
  45. package/dist/src/analyze/select/tui/components/HelpOverlay.js +11 -0
  46. package/dist/src/analyze/select/tui/components/JsonEditor.d.ts +11 -0
  47. package/dist/src/analyze/select/tui/components/JsonEditor.js +154 -0
  48. package/dist/src/analyze/select/tui/components/JsonPanel.d.ts +11 -0
  49. package/dist/src/analyze/select/tui/components/JsonPanel.js +62 -0
  50. package/dist/src/analyze/select/tui/components/PreviewSummaryBar.d.ts +8 -0
  51. package/dist/src/analyze/select/tui/components/PreviewSummaryBar.js +29 -0
  52. package/dist/src/analyze/select/tui/components/QuitDialog.d.ts +8 -0
  53. package/dist/src/analyze/select/tui/components/QuitDialog.js +14 -0
  54. package/dist/src/analyze/select/tui/components/Sidebar.d.ts +15 -0
  55. package/dist/src/analyze/select/tui/components/Sidebar.js +48 -0
  56. package/dist/src/analyze/select/tui/components/SourcePanel.d.ts +11 -0
  57. package/dist/src/analyze/select/tui/components/SourcePanel.js +52 -0
  58. package/dist/src/analyze/select/tui/components/StatusBar.d.ts +11 -0
  59. package/dist/src/analyze/select/tui/components/StatusBar.js +6 -0
  60. package/dist/src/analyze/select/tui/components/TopBar.d.ts +10 -0
  61. package/dist/src/analyze/select/tui/components/TopBar.js +5 -0
  62. package/dist/src/analyze/select/tui/hooks/useImmediateInput.d.ts +24 -0
  63. package/dist/src/analyze/select/tui/hooks/useImmediateInput.js +68 -0
  64. package/dist/src/analyze/select/tui/hooks/useKeymap.d.ts +24 -0
  65. package/dist/src/analyze/select/tui/hooks/useKeymap.js +67 -0
  66. package/dist/src/analyze/select/tui/hooks/useSession.d.ts +19 -0
  67. package/dist/src/analyze/select/tui/hooks/useSession.js +52 -0
  68. package/dist/src/analyze/select/tui/hooks/useUndo.d.ts +8 -0
  69. package/dist/src/analyze/select/tui/hooks/useUndo.js +26 -0
  70. package/dist/src/analyze/select/types.d.ts +46 -0
  71. package/dist/src/analyze/select/types.js +20 -0
  72. package/dist/src/analyze/select-agent/command.d.ts +2 -0
  73. package/dist/src/analyze/select-agent/command.js +208 -0
  74. package/dist/src/analyze/tui/AnalyzeView.d.ts +24 -0
  75. package/dist/src/analyze/tui/AnalyzeView.js +38 -0
  76. package/dist/src/apply/api-client.d.ts +35 -0
  77. package/dist/src/apply/api-client.js +143 -0
  78. package/dist/src/apply/command.d.ts +6 -0
  79. package/dist/src/apply/command.js +787 -0
  80. package/dist/src/apply/manifest.d.ts +1 -0
  81. package/dist/src/apply/manifest.js +1 -0
  82. package/dist/src/apply/tui/SelectView.d.ts +18 -0
  83. package/dist/src/apply/tui/SelectView.js +34 -0
  84. package/dist/src/apply/tui/ServerApplyView.d.ts +32 -0
  85. package/dist/src/apply/tui/ServerApplyView.js +42 -0
  86. package/dist/src/apply/tui/ServerPreviewView.d.ts +9 -0
  87. package/dist/src/apply/tui/ServerPreviewView.js +21 -0
  88. package/dist/src/credentials-store.d.ts +8 -0
  89. package/dist/src/credentials-store.js +30 -0
  90. package/dist/src/generate/agent-runner.d.ts +86 -0
  91. package/dist/src/generate/agent-runner.js +314 -0
  92. package/dist/src/generate/command.d.ts +2 -0
  93. package/dist/src/generate/command.js +545 -0
  94. package/dist/src/generate/edit/command.d.ts +2 -0
  95. package/dist/src/generate/edit/command.js +126 -0
  96. package/dist/src/generate/prompt-builder.d.ts +18 -0
  97. package/dist/src/generate/prompt-builder.js +202 -0
  98. package/dist/src/generate/tui/GenerateView.d.ts +12 -0
  99. package/dist/src/generate/tui/GenerateView.js +10 -0
  100. package/dist/src/import/command.d.ts +2 -0
  101. package/dist/src/import/command.js +96 -0
  102. package/dist/src/import/orchestrator.d.ts +37 -0
  103. package/dist/src/import/orchestrator.js +374 -0
  104. package/dist/src/import/path-utils.d.ts +15 -0
  105. package/dist/src/import/path-utils.js +30 -0
  106. package/dist/src/import/tui/WizardApp.d.ts +10 -0
  107. package/dist/src/import/tui/WizardApp.js +906 -0
  108. package/dist/src/import/tui/steps/CredentialsStep.d.ts +15 -0
  109. package/dist/src/import/tui/steps/CredentialsStep.js +79 -0
  110. package/dist/src/import/tui/steps/DoneStep.d.ts +20 -0
  111. package/dist/src/import/tui/steps/DoneStep.js +17 -0
  112. package/dist/src/import/tui/steps/ErrorStep.d.ts +8 -0
  113. package/dist/src/import/tui/steps/ErrorStep.js +11 -0
  114. package/dist/src/import/tui/steps/GateStep.d.ts +14 -0
  115. package/dist/src/import/tui/steps/GateStep.js +20 -0
  116. package/dist/src/import/tui/steps/GenerateReviewStep.d.ts +8 -0
  117. package/dist/src/import/tui/steps/GenerateReviewStep.js +208 -0
  118. package/dist/src/import/tui/steps/PathValidationStep.d.ts +10 -0
  119. package/dist/src/import/tui/steps/PathValidationStep.js +151 -0
  120. package/dist/src/import/tui/steps/PreviewStep.d.ts +21 -0
  121. package/dist/src/import/tui/steps/PreviewStep.js +36 -0
  122. package/dist/src/import/tui/steps/RunningStep.d.ts +10 -0
  123. package/dist/src/import/tui/steps/RunningStep.js +20 -0
  124. package/dist/src/import/tui/steps/TokenInputStep.d.ts +8 -0
  125. package/dist/src/import/tui/steps/TokenInputStep.js +70 -0
  126. package/dist/src/import/tui/steps/WelcomeStep.d.ts +7 -0
  127. package/dist/src/import/tui/steps/WelcomeStep.js +33 -0
  128. package/dist/src/import/tui/steps/WizardPreviewStep.d.ts +15 -0
  129. package/dist/src/import/tui/steps/WizardPreviewStep.js +121 -0
  130. package/dist/src/import/tui/steps/preview-diff.d.ts +10 -0
  131. package/dist/src/import/tui/steps/preview-diff.js +132 -0
  132. package/dist/src/index.d.ts +1 -0
  133. package/dist/src/index.js +2 -0
  134. package/dist/src/output/format.d.ts +23 -0
  135. package/dist/src/output/format.js +110 -0
  136. package/dist/src/print/command.d.ts +2 -0
  137. package/dist/src/print/command.js +199 -0
  138. package/dist/src/print/validate/tui/ValidateView.d.ts +15 -0
  139. package/dist/src/print/validate/tui/ValidateView.js +37 -0
  140. package/dist/src/print/validate/validators/cdf-validator.d.ts +2 -0
  141. package/dist/src/print/validate/validators/cdf-validator.js +104 -0
  142. package/dist/src/print/validate/validators/dtcg-validator.d.ts +2 -0
  143. package/dist/src/print/validate/validators/dtcg-validator.js +110 -0
  144. package/dist/src/print/validate/validators/format-errors.d.ts +12 -0
  145. package/dist/src/print/validate/validators/format-errors.js +18 -0
  146. package/dist/src/program.d.ts +2 -0
  147. package/dist/src/program.js +25 -0
  148. package/dist/src/session/command.d.ts +2 -0
  149. package/dist/src/session/command.js +261 -0
  150. package/dist/src/session/db.d.ts +111 -0
  151. package/dist/src/session/db.js +1114 -0
  152. package/dist/src/session/migration.d.ts +4 -0
  153. package/dist/src/session/migration.js +117 -0
  154. package/dist/src/session/session-id.d.ts +1 -0
  155. package/dist/src/session/session-id.js +212 -0
  156. package/dist/src/session/stats.d.ts +27 -0
  157. package/dist/src/session/stats.js +89 -0
  158. package/dist/src/setup/command.d.ts +2 -0
  159. package/dist/src/setup/command.js +765 -0
  160. package/dist/src/types.d.ts +48 -0
  161. package/dist/src/types.js +1 -0
  162. package/package.json +55 -0
  163. package/skills/generate-components.md +361 -0
  164. package/skills/generate-tokens.md +194 -0
  165. package/skills/select-components.md +180 -0
@@ -0,0 +1,15 @@
1
+ import React from 'react';
2
+ type CredentialsStepProps = {
3
+ summary?: string;
4
+ error?: string;
5
+ initialSpaceId?: string;
6
+ initialEnvironmentId?: string;
7
+ initialCmaToken?: string;
8
+ /** Called when the user submits with all fields changed from their initial values */
9
+ onConfirm: (spaceId: string, environmentId: string, cmaToken: string) => void;
10
+ /** Called when the user submits without changing any field (use existing creds as-is) */
11
+ onContinue?: (spaceId: string, environmentId: string, cmaToken: string) => void;
12
+ onQuit: () => void;
13
+ };
14
+ export declare function CredentialsStep({ summary, error: externalError, initialSpaceId, initialEnvironmentId, initialCmaToken, onConfirm, onContinue, onQuit, }: CredentialsStepProps): React.ReactElement;
15
+ export {};
@@ -0,0 +1,79 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState, useEffect } from 'react';
3
+ import { Box, Text } from 'ink';
4
+ import { useImmediateInput } from '../../../analyze/select/tui/hooks/useImmediateInput.js';
5
+ export function CredentialsStep({ summary, error: externalError, initialSpaceId = '', initialEnvironmentId = 'master', initialCmaToken = '', onConfirm, onContinue, onQuit, }) {
6
+ const [spaceId, setSpaceId] = useState(initialSpaceId);
7
+ const [environmentId, setEnvironmentId] = useState(initialEnvironmentId);
8
+ const [cmaToken, setCmaToken] = useState(initialCmaToken);
9
+ const [activeField, setActiveField] = useState('spaceId');
10
+ const [inlineError, setInlineError] = useState(null);
11
+ const [cursorVisible, setCursorVisible] = useState(true);
12
+ useEffect(() => {
13
+ const interval = setInterval(() => setCursorVisible((v) => !v), 500);
14
+ return () => clearInterval(interval);
15
+ }, []);
16
+ useImmediateInput((input, key) => {
17
+ if (key.return) {
18
+ if (activeField === 'spaceId') {
19
+ setActiveField('environmentId');
20
+ return;
21
+ }
22
+ if (activeField === 'environmentId') {
23
+ setActiveField('cmaToken');
24
+ return;
25
+ }
26
+ // Submit
27
+ if (!spaceId.trim() || !environmentId.trim() || !cmaToken.trim()) {
28
+ setInlineError('All fields are required.');
29
+ return;
30
+ }
31
+ setInlineError(null);
32
+ const unchanged = spaceId.trim() === initialSpaceId &&
33
+ environmentId.trim() === initialEnvironmentId &&
34
+ cmaToken.trim() === initialCmaToken;
35
+ if (unchanged && onContinue) {
36
+ onContinue(spaceId.trim(), environmentId.trim(), cmaToken.trim());
37
+ }
38
+ else {
39
+ onConfirm(spaceId.trim(), environmentId.trim(), cmaToken.trim());
40
+ }
41
+ return;
42
+ }
43
+ if (key.tab) {
44
+ setActiveField((f) => (f === 'spaceId' ? 'environmentId' : f === 'environmentId' ? 'cmaToken' : 'spaceId'));
45
+ return;
46
+ }
47
+ if (key.escape || input === 'q') {
48
+ onQuit();
49
+ return;
50
+ }
51
+ if (key.backspace || key.delete) {
52
+ if (activeField === 'spaceId')
53
+ setSpaceId((v) => v.slice(0, -1));
54
+ else if (activeField === 'environmentId')
55
+ setEnvironmentId((v) => v.slice(0, -1));
56
+ else
57
+ setCmaToken((v) => v.slice(0, -1));
58
+ return;
59
+ }
60
+ if (input && !key.ctrl && !key.meta) {
61
+ if (activeField === 'spaceId')
62
+ setSpaceId((v) => v + input);
63
+ else if (activeField === 'environmentId')
64
+ setEnvironmentId((v) => v + input);
65
+ else
66
+ setCmaToken((v) => v + input);
67
+ }
68
+ });
69
+ const cursor = cursorVisible ? '█' : ' ';
70
+ function renderField(label, value, field, masked = false) {
71
+ const isActive = activeField === field;
72
+ const display = masked ? '•'.repeat(value.length) : value;
73
+ return (_jsxs(Box, { gap: 1, children: [_jsx(Text, { color: isActive ? 'cyan' : undefined, children: '?' }), _jsxs(Text, { bold: isActive, children: [label, ":"] }), _jsx(Text, { children: isActive ? display + cursor : display || _jsx(Text, { dimColor: true, children: "(empty)" }) })] }));
74
+ }
75
+ const displayError = inlineError ?? externalError ?? null;
76
+ return (_jsxs(Box, { flexDirection: "column", gap: 1, paddingX: 2, paddingY: 1, children: [summary && _jsxs(Text, { color: "green", children: ["\u2713 ", summary] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { children: initialSpaceId && initialCmaToken
77
+ ? 'Credentials pre-filled from exo setup. Press Enter to continue or edit any field to update.'
78
+ : 'Enter your Contentful credentials to continue.' }) }), !(initialSpaceId && initialCmaToken) && (_jsx(Text, { dimColor: true, children: "Tip: run exo setup to save these to ~/.config/exo/credentials.json so they pre-fill here automatically." })), _jsxs(Box, { flexDirection: "column", gap: 0, marginTop: 1, children: [renderField('Space ID', spaceId, 'spaceId'), renderField('Environment', environmentId, 'environmentId'), renderField('CMA Token', cmaToken, 'cmaToken', true)] }), displayError && _jsxs(Text, { color: "red", children: ["\u2717 ", displayError] }), _jsxs(Box, { gap: 3, marginTop: 1, children: [_jsx(Text, { dimColor: true, children: "[Enter] Next field / Submit" }), _jsx(Text, { dimColor: true, children: "[Tab] Switch field" }), _jsx(Text, { dimColor: true, children: "[q] Quit" })] })] }));
79
+ }
@@ -0,0 +1,20 @@
1
+ import React from 'react';
2
+ type EntityResult = {
3
+ created: number;
4
+ updated: number;
5
+ failed: number;
6
+ };
7
+ type DoneStepProps = {
8
+ componentTypes: EntityResult;
9
+ designTokens: EntityResult;
10
+ summary?: {
11
+ total: number;
12
+ succeeded: number;
13
+ failed: number;
14
+ };
15
+ spaceId: string;
16
+ environmentId: string;
17
+ onExit: () => void;
18
+ };
19
+ export declare function DoneStep({ componentTypes, designTokens, summary, spaceId, environmentId, onExit, }: DoneStepProps): React.ReactElement;
20
+ export {};
@@ -0,0 +1,17 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { Box, Text } from 'ink';
3
+ import { useImmediateInput } from '../../../analyze/select/tui/hooks/useImmediateInput.js';
4
+ export function DoneStep({ componentTypes, designTokens, summary, spaceId, environmentId, onExit, }) {
5
+ useImmediateInput((input, key) => {
6
+ if (key.return || input === 'q' || key.escape) {
7
+ onExit();
8
+ }
9
+ });
10
+ const totalFailed = componentTypes.failed + designTokens.failed;
11
+ const totalPushed = componentTypes.created + componentTypes.updated + designTokens.created + designTokens.updated;
12
+ const success = totalFailed === 0;
13
+ function EntityRows({ entity, label }) {
14
+ return (_jsxs(_Fragment, { children: [entity.created > 0 && (_jsxs(Box, { gap: 1, children: [_jsx(Text, { color: "green", children: "\u2713" }), _jsxs(Text, { children: [entity.created, " ", label, entity.created !== 1 ? 's' : '', " created"] })] })), entity.updated > 0 && (_jsxs(Box, { gap: 1, children: [_jsx(Text, { color: "green", children: "\u2713" }), _jsxs(Text, { children: [entity.updated, " ", label, entity.updated !== 1 ? 's' : '', " updated"] })] })), entity.failed > 0 && (_jsxs(Box, { gap: 1, children: [_jsx(Text, { color: "red", children: "\u2717" }), _jsxs(Text, { color: "red", children: [entity.failed, " ", label, entity.failed !== 1 ? 's' : '', " failed \u2014 check logs above"] })] }))] }));
15
+ }
16
+ return (_jsxs(Box, { flexDirection: "column", gap: 1, paddingX: 2, paddingY: 1, children: [success ? (_jsx(Text, { bold: true, color: "green", children: "Done!" })) : (_jsx(Text, { bold: true, color: "yellow", children: "\u26A0 Finished with errors" })), totalPushed === 0 && totalFailed === 0 && !summary ? (_jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Nothing was pushed \u2014 everything was already up to date." }) })) : (_jsxs(Box, { flexDirection: "column", gap: 0, marginTop: 1, children: [_jsx(EntityRows, { entity: componentTypes, label: "Component Type" }), _jsx(EntityRows, { entity: designTokens, label: "Design Token" }), summary && (_jsxs(Box, { gap: 1, marginTop: 1, children: [_jsxs(Text, { dimColor: true, children: ["Server: ", summary.succeeded, "/", summary.total, " succeeded"] }), summary.failed > 0 && _jsxs(Text, { color: "red", children: [", ", summary.failed, " failed"] })] }))] })), _jsxs(Box, { gap: 1, marginTop: 1, children: [_jsx(Text, { dimColor: true, children: "Space:" }), _jsx(Text, { children: spaceId }), _jsx(Text, { dimColor: true, children: "/" }), _jsx(Text, { dimColor: true, children: "Environment:" }), _jsx(Text, { children: environmentId })] }), success && totalPushed > 0 && (_jsxs(Box, { flexDirection: "column", gap: 1, marginTop: 1, children: [_jsx(Text, { dimColor: true, children: "Your design system is now in Contentful ExO." }), _jsxs(Box, { flexDirection: "column", gap: 0, children: [_jsx(Text, { dimColor: true, children: "View it here:" }), _jsx(Text, { color: "cyan", children: `https://app.contentful.com/spaces/${spaceId}/environments/${environmentId}/exo/components` })] })] })), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "[Enter / q] Exit" }) })] }));
17
+ }
@@ -0,0 +1,8 @@
1
+ import React from 'react';
2
+ type ErrorStepProps = {
3
+ stepName: string;
4
+ message: string;
5
+ onExit: () => void;
6
+ };
7
+ export declare function ErrorStep({ stepName, message, onExit }: ErrorStepProps): React.ReactElement;
8
+ export {};
@@ -0,0 +1,11 @@
1
+ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
+ import { Box, Text } from 'ink';
3
+ import { useImmediateInput } from '../../../analyze/select/tui/hooks/useImmediateInput.js';
4
+ export function ErrorStep({ stepName, message, onExit }) {
5
+ useImmediateInput((input, key) => {
6
+ if (key.return || input === 'q' || key.escape) {
7
+ onExit();
8
+ }
9
+ });
10
+ return (_jsxs(Box, { flexDirection: "column", gap: 1, paddingX: 2, paddingY: 1, children: [_jsxs(Text, { bold: true, color: "red", children: ["\u2717 ", stepName, " failed"] }), _jsx(Text, { color: "red", children: message }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "[Enter / q] Exit" }) })] }));
11
+ }
@@ -0,0 +1,14 @@
1
+ import React from 'react';
2
+ type GateStepProps = {
3
+ successMessage: string;
4
+ summary?: string;
5
+ context: string;
6
+ continueLabel?: string;
7
+ skipLabel?: string;
8
+ onContinue: () => void;
9
+ onSkip?: () => void;
10
+ onQuit: () => void;
11
+ showSkip?: boolean;
12
+ };
13
+ export declare function GateStep({ successMessage, summary, context, continueLabel, skipLabel, onContinue, onSkip, onQuit, showSkip, }: GateStepProps): React.ReactElement;
14
+ export {};
@@ -0,0 +1,20 @@
1
+ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
+ import { Box, Text } from 'ink';
3
+ import { useImmediateInput } from '../../../analyze/select/tui/hooks/useImmediateInput.js';
4
+ export function GateStep({ successMessage, summary, context, continueLabel = 'Continue', skipLabel = 'Approve all and skip', onContinue, onSkip, onQuit, showSkip = true, }) {
5
+ useImmediateInput((input, key) => {
6
+ if (key.return) {
7
+ onContinue();
8
+ return;
9
+ }
10
+ if (input === 'a' && showSkip && onSkip) {
11
+ onSkip();
12
+ return;
13
+ }
14
+ if (input === 'q' || key.escape) {
15
+ onQuit();
16
+ return;
17
+ }
18
+ });
19
+ return (_jsxs(Box, { flexDirection: "column", gap: 1, paddingX: 2, paddingY: 1, children: [_jsxs(Text, { color: "green", children: ["\u2713 ", successMessage] }), summary && _jsx(Text, { dimColor: true, children: summary }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { children: context }) }), _jsxs(Box, { gap: 3, marginTop: 1, children: [_jsxs(Text, { dimColor: true, children: ["[Enter] ", continueLabel] }), showSkip && onSkip && _jsxs(Text, { dimColor: true, children: ["[a] ", skipLabel] }), _jsx(Text, { dimColor: true, children: "[q] Quit" })] })] }));
20
+ }
@@ -0,0 +1,8 @@
1
+ import React from 'react';
2
+ type GenerateReviewStepProps = {
3
+ extractSessionId: string;
4
+ onFinalize: (accepted: number, rejected: number) => void;
5
+ onQuit: () => void;
6
+ };
7
+ export declare function GenerateReviewStep({ extractSessionId, onFinalize, onQuit, }: GenerateReviewStepProps): React.ReactElement;
8
+ export {};
@@ -0,0 +1,208 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useEffect, useState } from 'react';
3
+ import { Box, Text, useStdout } from 'ink';
4
+ import { Sidebar } from '../../../analyze/select/tui/components/Sidebar.js';
5
+ import { JsonPanel } from '../../../analyze/select/tui/components/JsonPanel.js';
6
+ import { FieldEditor } from '../../../analyze/select/tui/components/FieldEditor.js';
7
+ import { StatusBar } from '../../../analyze/select/tui/components/StatusBar.js';
8
+ import { FinalizeDialog } from '../../../analyze/select/tui/components/FinalizeDialog.js';
9
+ import { QuitDialog } from '../../../analyze/select/tui/components/QuitDialog.js';
10
+ import { useImmediateInput } from '../../../analyze/select/tui/hooks/useImmediateInput.js';
11
+ import { openPipelineDb, loadCDFComponents, storeCDFComponents } from '../../../session/db.js';
12
+ const VISIBLE_COUNT = 20;
13
+ const PANEL_HEIGHT = 22;
14
+ export function GenerateReviewStep({ extractSessionId, onFinalize, onQuit, }) {
15
+ const { stdout } = useStdout();
16
+ const terminalWidth = stdout?.columns ?? 80;
17
+ const [components, setComponents] = useState([]);
18
+ const [loading, setLoading] = useState(true);
19
+ const [loadError, setLoadError] = useState(null);
20
+ const [selectedIdx, setSelectedIdx] = useState(0);
21
+ const [sidebarScrollOffset, setSidebarScrollOffset] = useState(0);
22
+ const [jsonScrollOffset, setJsonScrollOffset] = useState(0);
23
+ const [sidebarFocused, setSidebarFocused] = useState(true);
24
+ const [showFinalize, setShowFinalize] = useState(false);
25
+ const [showQuit, setShowQuit] = useState(false);
26
+ const [editMode, setEditMode] = useState(false);
27
+ const [draftValue, setDraftValue] = useState('');
28
+ const [saveError, setSaveError] = useState(null);
29
+ useEffect(() => {
30
+ async function load() {
31
+ const db = openPipelineDb();
32
+ let cdfComponents;
33
+ try {
34
+ cdfComponents = loadCDFComponents(db, extractSessionId);
35
+ }
36
+ finally {
37
+ db.close();
38
+ }
39
+ if (cdfComponents.length === 0) {
40
+ setLoadError('No generated definitions found for this session. Try re-running generate.');
41
+ setLoading(false);
42
+ return;
43
+ }
44
+ setComponents(cdfComponents.map(({ key, entry }) => ({ key, entry, status: 'needs-review' })));
45
+ setLoading(false);
46
+ }
47
+ load().catch((e) => {
48
+ setLoadError(String(e));
49
+ setLoading(false);
50
+ });
51
+ }, []);
52
+ const updateStatus = (idx, status) => {
53
+ setComponents((prev) => prev.map((c, i) => (i === idx ? { ...c, status } : c)));
54
+ };
55
+ const handleFinalizeConfirm = () => {
56
+ const rejected = components.filter((c) => c.status === 'rejected').map((c) => c.key);
57
+ if (rejected.length > 0) {
58
+ const db = openPipelineDb();
59
+ try {
60
+ const stmt = db.prepare(`UPDATE raw_components SET status = 'generate-rejected' WHERE session_id = ? AND name = ?`);
61
+ db.exec('BEGIN');
62
+ try {
63
+ for (const name of rejected) {
64
+ stmt.run(extractSessionId, name);
65
+ }
66
+ db.exec('COMMIT');
67
+ }
68
+ catch (e) {
69
+ db.exec('ROLLBACK');
70
+ throw e;
71
+ }
72
+ }
73
+ finally {
74
+ db.close();
75
+ }
76
+ }
77
+ const accepted = components.filter((c) => c.status !== 'rejected').length;
78
+ onFinalize(accepted, rejected.length);
79
+ };
80
+ const handleEditSave = () => {
81
+ const current = components[selectedIdx];
82
+ if (!current)
83
+ return;
84
+ try {
85
+ const parsed = JSON.parse(draftValue);
86
+ // Accept both bare entry and wrapped { [key]: entry } forms
87
+ const keys = Object.keys(parsed);
88
+ const entry = keys.length === 1 && typeof parsed[keys[0]] === 'object' && parsed[keys[0]] !== null
89
+ ? parsed[keys[0]]
90
+ : parsed;
91
+ if (entry.$type !== 'component' || typeof entry.$properties !== 'object' || entry.$properties === null) {
92
+ setSaveError('Invalid CDF entry: must have $type: "component" and $properties object');
93
+ return;
94
+ }
95
+ setComponents((prev) => prev.map((c, i) => i === selectedIdx ? { ...c, entry, status: c.status === 'needs-review' ? 'accepted' : c.status } : c));
96
+ setEditMode(false);
97
+ setDraftValue('');
98
+ setSaveError(null);
99
+ const db = openPipelineDb();
100
+ try {
101
+ storeCDFComponents(db, extractSessionId, [{ key: current.key, entry }]);
102
+ }
103
+ finally {
104
+ db.close();
105
+ }
106
+ }
107
+ catch (e) {
108
+ setSaveError(String(e));
109
+ }
110
+ };
111
+ const handleEditDiscard = () => {
112
+ setEditMode(false);
113
+ setDraftValue('');
114
+ setSaveError(null);
115
+ };
116
+ const dialogOpen = showFinalize || showQuit;
117
+ useImmediateInput((input, key) => {
118
+ if (loading || loadError)
119
+ return;
120
+ if (dialogOpen)
121
+ return;
122
+ if (editMode)
123
+ return;
124
+ if (input === 'q') {
125
+ setShowQuit(true);
126
+ return;
127
+ }
128
+ if (input === 'F') {
129
+ setShowFinalize(true);
130
+ return;
131
+ }
132
+ if (key.tab) {
133
+ setSidebarFocused((prev) => !prev);
134
+ return;
135
+ }
136
+ if (input === 'a') {
137
+ updateStatus(selectedIdx, 'accepted');
138
+ return;
139
+ }
140
+ if (input === 'r') {
141
+ updateStatus(selectedIdx, 'rejected');
142
+ return;
143
+ }
144
+ if (input === 'A') {
145
+ setComponents((prev) => prev.map((c) => (c.status === 'needs-review' ? { ...c, status: 'accepted' } : c)));
146
+ return;
147
+ }
148
+ if (input === 'e' && selected) {
149
+ setDraftValue(JSON.stringify({ [selected.key]: selected.entry }, null, 2));
150
+ setEditMode(true);
151
+ return;
152
+ }
153
+ if (sidebarFocused) {
154
+ if (key.upArrow || input === 'k') {
155
+ const newIdx = Math.max(0, selectedIdx - 1);
156
+ setSelectedIdx(newIdx);
157
+ setJsonScrollOffset(0);
158
+ setSidebarScrollOffset((prev) => Math.min(prev, newIdx));
159
+ }
160
+ else if (key.downArrow || input === 'j') {
161
+ const newIdx = Math.min(components.length - 1, selectedIdx + 1);
162
+ setSelectedIdx(newIdx);
163
+ setJsonScrollOffset(0);
164
+ setSidebarScrollOffset((prev) => (newIdx >= prev + VISIBLE_COUNT ? newIdx - VISIBLE_COUNT + 1 : prev));
165
+ }
166
+ }
167
+ else {
168
+ if (key.upArrow || input === 'k') {
169
+ setJsonScrollOffset((prev) => Math.max(0, prev - 1));
170
+ }
171
+ else if (key.downArrow || input === 'j') {
172
+ setJsonScrollOffset((prev) => prev + 1);
173
+ }
174
+ }
175
+ });
176
+ if (loading) {
177
+ return (_jsx(Box, { paddingX: 2, paddingY: 1, children: _jsx(Text, { dimColor: true, children: "Loading generated definitions..." }) }));
178
+ }
179
+ if (loadError) {
180
+ return (_jsx(Box, { paddingX: 2, paddingY: 1, children: _jsx(Text, { color: "red", children: loadError }) }));
181
+ }
182
+ const selected = components[selectedIdx] ?? null;
183
+ const selectedJson = selected ? JSON.stringify({ [selected.key]: selected.entry }, null, 2) : '';
184
+ const sidebarItems = components.map((c) => ({
185
+ id: c.key,
186
+ name: c.key,
187
+ status: c.status,
188
+ }));
189
+ const longestName = components.reduce((m, c) => Math.max(m, c.key.length), 0);
190
+ const sidebarWidth = Math.min(Math.max(longestName + 4, 14), 22);
191
+ const panelWidth = Math.max(10, terminalWidth - sidebarWidth - 4);
192
+ const accepted = components.filter((c) => c.status === 'accepted').length;
193
+ const rejected = components.filter((c) => c.status === 'rejected').length;
194
+ const needsReview = components.filter((c) => c.status === 'needs-review').length;
195
+ const propCount = selected ? Object.keys(selected.entry.$properties).length : 0;
196
+ const slotCount = selected?.entry.$slots ? Object.keys(selected.entry.$slots).length : 0;
197
+ return (_jsxs(Box, { flexDirection: "column", children: [showFinalize && (_jsx(FinalizeDialog, { accepted: accepted, rejected: rejected, needsReview: needsReview, onConfirm: handleFinalizeConfirm, onCancel: () => setShowFinalize(false) })), showQuit && _jsx(QuitDialog, { hasUnsavedDrafts: false, onConfirm: onQuit, onCancel: () => setShowQuit(false) }), !dialogOpen && (_jsxs(Box, { children: [_jsx(Sidebar, { components: sidebarItems, selectedId: selected?.key ?? null, focused: sidebarFocused, scrollOffset: sidebarScrollOffset, visibleCount: VISIBLE_COUNT, onSelect: (id) => {
198
+ const idx = components.findIndex((c) => c.key === id);
199
+ if (idx >= 0) {
200
+ setSelectedIdx(idx);
201
+ setJsonScrollOffset(0);
202
+ }
203
+ }, onScrollChange: setSidebarScrollOffset, width: sidebarWidth }), _jsx(Box, { flexGrow: 1, paddingLeft: 1, flexDirection: "column", children: selected ? (_jsxs(_Fragment, { children: [_jsxs(Box, { children: [_jsx(Text, { bold: true, children: selected.key }), _jsx(Box, { flexGrow: 1 }), _jsxs(Text, { dimColor: true, children: [propCount, " prop", propCount !== 1 ? 's' : '', slotCount > 0 ? ` · ${slotCount} slot${slotCount !== 1 ? 's' : ''}` : '', ' ', sidebarFocused ? '[Tab] focus panel' : '[Tab] focus list'] })] }), editMode ? (_jsx(FieldEditor, { value: draftValue || selectedJson, width: panelWidth, height: PANEL_HEIGHT, onChange: setDraftValue, onSave: handleEditSave, onDiscard: handleEditDiscard })) : (_jsx(JsonPanel, { label: "GENERATED DEFINITION", value: selectedJson, scrollOffset: jsonScrollOffset, width: panelWidth, height: PANEL_HEIGHT, active: !sidebarFocused })), saveError && _jsx(Text, { color: "red", children: '✗ ' + saveError }), _jsx(Text, { dimColor: true, children: editMode
204
+ ? ' [Ctrl+S] save [Esc] discard'
205
+ : ' [a] accept [r] reject [e] edit [A] accept all [F] finalize [Tab] toggle focus [q] quit' })] })) : (_jsx(Text, { dimColor: true, children: "No component selected" })) })] })), !dialogOpen && (_jsx(StatusBar, { accepted: accepted, rejected: rejected, reviewed: 0, needsReview: needsReview, onApproveAll: () => {
206
+ setComponents((prev) => prev.map((c) => (c.status === 'needs-review' ? { ...c, status: 'accepted' } : c)));
207
+ }, onFinalize: () => setShowFinalize(true) }))] }));
208
+ }
@@ -0,0 +1,10 @@
1
+ import React from 'react';
2
+ type PathValidationStepProps = {
3
+ projectPath: string;
4
+ onConfirm: (projectPath: string) => void;
5
+ onSkipComponents: () => void;
6
+ onChangePath: () => void;
7
+ onQuit: () => void;
8
+ };
9
+ export declare function PathValidationStep({ projectPath, onConfirm, onSkipComponents, onChangePath, onQuit, }: PathValidationStepProps): React.ReactElement;
10
+ export {};
@@ -0,0 +1,151 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState, useEffect } from 'react';
3
+ import { Box, Text } from 'ink';
4
+ import { promises as fs } from 'node:fs';
5
+ import { join } from 'node:path';
6
+ import { useImmediateInput } from '../../../analyze/select/tui/hooks/useImmediateInput.js';
7
+ const IGNORE_DIRS = new Set([
8
+ 'node_modules',
9
+ 'dist',
10
+ 'build',
11
+ '.next',
12
+ '.nuxt',
13
+ 'coverage',
14
+ 'storybook-static',
15
+ 'out',
16
+ '.git',
17
+ ]);
18
+ async function countFiles(dir) {
19
+ const counts = {
20
+ tsx: 0,
21
+ ts: 0,
22
+ vue: 0,
23
+ astro: 0,
24
+ jsx: 0,
25
+ js: 0,
26
+ json: 0,
27
+ other: 0,
28
+ total: 0,
29
+ };
30
+ async function walk(current) {
31
+ let entries;
32
+ try {
33
+ entries = await fs.readdir(current);
34
+ }
35
+ catch {
36
+ return;
37
+ }
38
+ await Promise.all(entries.map(async (entry) => {
39
+ if (IGNORE_DIRS.has(entry))
40
+ return;
41
+ const full = join(current, entry);
42
+ let stat;
43
+ try {
44
+ stat = await fs.stat(full);
45
+ }
46
+ catch {
47
+ return;
48
+ }
49
+ if (stat.isDirectory()) {
50
+ await walk(full);
51
+ }
52
+ else {
53
+ counts.total++;
54
+ const ext = entry.slice(entry.lastIndexOf('.'));
55
+ if (ext === '.tsx')
56
+ counts.tsx++;
57
+ else if (ext === '.ts' && !entry.endsWith('.d.ts'))
58
+ counts.ts++;
59
+ else if (ext === '.vue')
60
+ counts.vue++;
61
+ else if (ext === '.astro')
62
+ counts.astro++;
63
+ else if (ext === '.jsx')
64
+ counts.jsx++;
65
+ else if (ext === '.js')
66
+ counts.js++;
67
+ else if (ext === '.json')
68
+ counts.json++;
69
+ else
70
+ counts.other++;
71
+ }
72
+ }));
73
+ }
74
+ await walk(dir);
75
+ return counts;
76
+ }
77
+ export function PathValidationStep({ projectPath, onConfirm, onSkipComponents, onChangePath, onQuit, }) {
78
+ const [counts, setCounts] = useState(null);
79
+ const [error, setError] = useState(null);
80
+ useEffect(() => {
81
+ void (async () => {
82
+ try {
83
+ const stat = await fs.stat(projectPath);
84
+ if (stat.isFile()) {
85
+ setError(`That's a file, not a directory.\n\nProvide the path to your component library's root folder, not a specific file.\n\nExample: ~/projects/my-design-system`);
86
+ return;
87
+ }
88
+ if (!stat.isDirectory()) {
89
+ setError(`Not a directory: ${projectPath}`);
90
+ return;
91
+ }
92
+ const result = await countFiles(projectPath);
93
+ setCounts(result);
94
+ }
95
+ catch (e) {
96
+ const code = e.code;
97
+ if (code === 'ENOENT') {
98
+ setError(`Directory not found: ${projectPath}\n\nDouble-check the path and try again. Tip: you can use ~, relative, or absolute paths.`);
99
+ }
100
+ else if (code === 'EACCES') {
101
+ setError(`Permission denied: ${projectPath}\n\nYou don't have read access to this directory.`);
102
+ }
103
+ else {
104
+ setError(`Cannot access: ${projectPath}`);
105
+ }
106
+ }
107
+ })();
108
+ }, [projectPath]);
109
+ useImmediateInput((input, key) => {
110
+ if (error) {
111
+ if (input === 'e' || key.return) {
112
+ onChangePath();
113
+ return;
114
+ }
115
+ if (input === 'q' || key.escape) {
116
+ onQuit();
117
+ return;
118
+ }
119
+ return;
120
+ }
121
+ if (!counts)
122
+ return;
123
+ if (key.return) {
124
+ onConfirm(projectPath);
125
+ return;
126
+ }
127
+ if (input === 's') {
128
+ onSkipComponents();
129
+ return;
130
+ }
131
+ if (input === 'e') {
132
+ onChangePath();
133
+ return;
134
+ }
135
+ if (input === 'q' || key.escape) {
136
+ onQuit();
137
+ return;
138
+ }
139
+ });
140
+ if (!counts && !error) {
141
+ return (_jsx(Box, { flexDirection: "column", paddingX: 2, paddingY: 1, children: _jsxs(Text, { dimColor: true, children: ["Scanning ", _jsx(Text, { bold: true, children: projectPath }), "..."] }) }));
142
+ }
143
+ if (error) {
144
+ const lines = error.split('\n');
145
+ return (_jsxs(Box, { flexDirection: "column", gap: 1, paddingX: 2, paddingY: 1, children: [_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { color: "red", children: ["\u2717 ", lines[0]] }), lines.slice(1).map((line, i) => (_jsx(Text, { dimColor: true, children: line }, i)))] }), _jsxs(Box, { gap: 3, marginTop: 1, children: [_jsx(Text, { dimColor: true, children: "[e / Enter] Try a different path" }), _jsx(Text, { dimColor: true, children: "[q] Quit" })] })] }));
146
+ }
147
+ const c = counts;
148
+ const componentFiles = c.tsx + c.ts + c.vue + c.astro + c.jsx + c.js;
149
+ const tokenFiles = c.json;
150
+ return (_jsxs(Box, { flexDirection: "column", gap: 1, paddingX: 2, paddingY: 1, children: [_jsxs(Text, { children: ["Scanning ", _jsx(Text, { bold: true, children: projectPath }), "..."] }), _jsxs(Box, { flexDirection: "column", gap: 0, marginTop: 1, children: [_jsxs(Text, { color: "green", children: ["\u2713 Found ", c.total, " files:"] }), c.tsx > 0 && (_jsxs(Text, { children: [' • ', String(c.tsx).padStart(3), " .tsx files"] })), c.ts > 0 && (_jsxs(Text, { children: [' • ', String(c.ts).padStart(3), " .ts files"] })), c.vue > 0 && (_jsxs(Text, { children: [' • ', String(c.vue).padStart(3), " .vue files"] })), c.astro > 0 && (_jsxs(Text, { children: [' • ', String(c.astro).padStart(3), " .astro files"] })), c.jsx > 0 && (_jsxs(Text, { children: [' • ', String(c.jsx).padStart(3), " .jsx files"] })), c.js > 0 && (_jsxs(Text, { children: [' • ', String(c.js).padStart(3), " .js files"] })), c.json > 0 && (_jsxs(Text, { color: "cyan", children: [' • ', String(c.json).padStart(3), " .json files (design tokens)"] })), c.other > 0 && (_jsxs(Text, { dimColor: true, children: [' • ', String(c.other).padStart(3), " other (ignored)"] }))] }), componentFiles === 0 && tokenFiles === 0 && (_jsx(Text, { color: "yellow", children: "\u26A0 No component or token files found. Try a different path." })), componentFiles === 0 && tokenFiles > 0 && (_jsx(Text, { color: "yellow", children: "\u26A0 No component files found \u2014 only token files detected." })), _jsx(Box, { marginTop: 1, children: _jsx(Text, { children: "Does this look right?" }) }), _jsxs(Box, { gap: 3, marginTop: 1, children: [_jsx(Text, { dimColor: true, children: "[Enter] Yes, start extracting" }), _jsx(Text, { dimColor: true, children: "[s] Skip components" }), _jsx(Text, { dimColor: true, children: "[e] Change path" }), _jsx(Text, { dimColor: true, children: "[q] Quit" })] })] }));
151
+ }
@@ -0,0 +1,21 @@
1
+ import React from 'react';
2
+ type EntitySummary = {
3
+ created: number;
4
+ updated: number;
5
+ unchanged: number;
6
+ conflicts: number;
7
+ failed: number;
8
+ };
9
+ type PreviewStepProps = {
10
+ componentTypes: EntitySummary;
11
+ designTokens: EntitySummary;
12
+ spaceId: string;
13
+ environmentId: string;
14
+ stepNumber: number;
15
+ totalSteps: number;
16
+ onConfirm: () => void;
17
+ onSaveFiles?: () => void;
18
+ onQuit: () => void;
19
+ };
20
+ export declare function PreviewStep({ componentTypes, designTokens, spaceId, environmentId, stepNumber, totalSteps, onConfirm, onSaveFiles, onQuit, }: PreviewStepProps): React.ReactElement;
21
+ export {};
@@ -0,0 +1,36 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { Box, Text } from 'ink';
3
+ import { useImmediateInput } from '../../../analyze/select/tui/hooks/useImmediateInput.js';
4
+ export function PreviewStep({ componentTypes, designTokens, spaceId, environmentId, stepNumber, totalSteps, onConfirm, onSaveFiles, onQuit, }) {
5
+ useImmediateInput((input, key) => {
6
+ if (key.return) {
7
+ onConfirm();
8
+ return;
9
+ }
10
+ if (input === 's' && onSaveFiles) {
11
+ onSaveFiles();
12
+ return;
13
+ }
14
+ if (input === 'q' || key.escape) {
15
+ onQuit();
16
+ return;
17
+ }
18
+ });
19
+ const hasComponents = componentTypes.created > 0 ||
20
+ componentTypes.updated > 0 ||
21
+ componentTypes.unchanged > 0 ||
22
+ componentTypes.conflicts > 0 ||
23
+ componentTypes.failed > 0;
24
+ const hasTokens = designTokens.created > 0 ||
25
+ designTokens.updated > 0 ||
26
+ designTokens.unchanged > 0 ||
27
+ designTokens.conflicts > 0 ||
28
+ designTokens.failed > 0;
29
+ const hasAnything = hasComponents || hasTokens;
30
+ function EntityRows({ label, summary }) {
31
+ if (!summary.created && !summary.updated && !summary.unchanged && !summary.conflicts && !summary.failed)
32
+ return null;
33
+ return (_jsxs(_Fragment, { children: [_jsx(Box, { gap: 1, marginTop: 1, children: _jsx(Text, { bold: true, dimColor: true, children: label }) }), summary.created > 0 && (_jsxs(Box, { gap: 1, children: [_jsx(Text, { color: "green", children: " \uFF0B" }), _jsxs(Text, { children: [summary.created, " will be created"] })] })), summary.updated > 0 && (_jsxs(Box, { gap: 1, children: [_jsx(Text, { color: "yellow", children: " \uFF5E" }), _jsxs(Text, { children: [summary.updated, " will be updated"] })] })), summary.unchanged > 0 && (_jsxs(Box, { gap: 1, children: [_jsx(Text, { dimColor: true, children: " \u00B7" }), _jsxs(Text, { dimColor: true, children: [summary.unchanged, " unchanged"] })] })), summary.conflicts > 0 && (_jsxs(Box, { gap: 1, children: [_jsx(Text, { color: "red", children: " \u2717" }), _jsxs(Text, { color: "red", children: [summary.conflicts, " conflict", summary.conflicts !== 1 ? 's' : '', " \u2014 resolve before pushing"] })] })), summary.failed > 0 && (_jsxs(Box, { gap: 1, children: [_jsx(Text, { color: "red", children: " \u2717" }), _jsxs(Text, { color: "red", children: [summary.failed, " failed \u2014 check logs"] })] }))] }));
34
+ }
35
+ return (_jsxs(Box, { flexDirection: "column", gap: 1, paddingX: 2, paddingY: 1, children: [_jsxs(Box, { flexDirection: "column", gap: 0, children: [_jsx(Text, { dimColor: true, children: '─'.repeat(40) }), _jsxs(Box, { gap: 1, children: [_jsxs(Text, { bold: true, children: ["Step ", stepNumber, " of ", totalSteps] }), _jsx(Text, { bold: true, children: "\u2014" }), _jsx(Text, { bold: true, children: "Push to Contentful" })] }), _jsx(Text, { dimColor: true, children: '─'.repeat(40) })] }), hasAnything ? (_jsxs(_Fragment, { children: [_jsx(Text, { children: "Here's what will happen in your space:" }), _jsxs(Box, { flexDirection: "column", gap: 0, children: [hasComponents && _jsx(EntityRows, { label: "ComponentTypes", summary: componentTypes }), hasTokens && _jsx(EntityRows, { label: "Design Tokens", summary: designTokens })] })] })) : (_jsx(Text, { dimColor: true, children: "Nothing to push \u2014 everything is already up to date." })), _jsxs(Box, { gap: 1, marginTop: 1, children: [_jsx(Text, { dimColor: true, children: "Space:" }), _jsx(Text, { children: spaceId }), _jsx(Text, { dimColor: true, children: "/" }), _jsx(Text, { dimColor: true, children: "Environment:" }), _jsx(Text, { children: environmentId })] }), _jsxs(Box, { gap: 3, marginTop: 1, children: [_jsx(Text, { dimColor: true, children: "[Enter] Push to Contentful" }), onSaveFiles && _jsx(Text, { dimColor: true, children: "[s] Save files instead" }), _jsx(Text, { dimColor: true, children: "[q] Cancel" })] })] }));
36
+ }