@contentful/experience-design-system-cli 2.26.1-dev-build-a852362.0 → 2.26.1-dev-build-f03f014.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (204) hide show
  1. package/dist/package.json +14 -10
  2. package/dist/src/index.js +34937 -9
  3. package/dist/src/lib/cli-path.d.ts +4 -0
  4. package/dist/src/runs/export-helpers.d.ts +1 -12
  5. package/dist/src/runs/push-helpers.d.ts +1 -6
  6. package/package.json +12 -7
  7. package/skills/generate-components.md +427 -0
  8. package/skills/generate-tokens.md +194 -0
  9. package/skills/select-components.md +224 -0
  10. package/dist/src/analytics/apply.js +0 -30
  11. package/dist/src/analytics/client.js +0 -81
  12. package/dist/src/analytics/constants.js +0 -4
  13. package/dist/src/analytics/env.js +0 -14
  14. package/dist/src/analytics/exit.js +0 -24
  15. package/dist/src/analytics/index.js +0 -9
  16. package/dist/src/analytics/normalize.js +0 -18
  17. package/dist/src/analytics/os.js +0 -13
  18. package/dist/src/analytics/session.js +0 -15
  19. package/dist/src/analytics/tracker.js +0 -126
  20. package/dist/src/analytics/types.js +0 -1
  21. package/dist/src/analyze/build-analyze-view-rows.js +0 -63
  22. package/dist/src/analyze/command.js +0 -599
  23. package/dist/src/analyze/composite-closure.js +0 -0
  24. package/dist/src/analyze/composition/agent-parser/author-prompt.js +0 -22
  25. package/dist/src/analyze/composition/agent-parser/extract-parser.js +0 -44
  26. package/dist/src/analyze/composition/agent-parser/load-prompt.js +0 -31
  27. package/dist/src/analyze/composition/agent-parser/resolve-via-parser.js +0 -88
  28. package/dist/src/analyze/composition/agent-parser/sandbox.js +0 -187
  29. package/dist/src/analyze/composition/apply-mapping.js +0 -67
  30. package/dist/src/analyze/composition/candidate-critic-agent.js +0 -39
  31. package/dist/src/analyze/composition/candidate-critic.js +0 -63
  32. package/dist/src/analyze/composition/candidate-files.js +0 -131
  33. package/dist/src/analyze/composition/composition-cache-key.js +0 -19
  34. package/dist/src/analyze/composition/interchange-schema.js +0 -61
  35. package/dist/src/analyze/composition/manifest-doc-evidence.js +0 -136
  36. package/dist/src/analyze/composition/merge-edges.js +0 -70
  37. package/dist/src/analyze/composition/parse-map-edges.js +0 -55
  38. package/dist/src/analyze/composition/resolve-mapping-cli.js +0 -29
  39. package/dist/src/analyze/composition/resolve-mapping.js +0 -111
  40. package/dist/src/analyze/cycle-detection.js +0 -234
  41. package/dist/src/analyze/cycle-view.js +0 -28
  42. package/dist/src/analyze/fuzzy-search.js +0 -55
  43. package/dist/src/analyze/issue-inheritance.js +0 -108
  44. package/dist/src/analyze/lineage.js +0 -0
  45. package/dist/src/analyze/scope-gate-cascade.js +0 -170
  46. package/dist/src/analyze/search-neighborhood.js +0 -56
  47. package/dist/src/analyze/select/command.js +0 -484
  48. package/dist/src/analyze/select/index.js +0 -5
  49. package/dist/src/analyze/select/parser.js +0 -53
  50. package/dist/src/analyze/select/persistence.js +0 -80
  51. package/dist/src/analyze/select/preview-annotations.js +0 -62
  52. package/dist/src/analyze/select/stdout.js +0 -3
  53. package/dist/src/analyze/select/tui/App.js +0 -483
  54. package/dist/src/analyze/select/tui/components/ComponentDetail.js +0 -48
  55. package/dist/src/analyze/select/tui/components/ComponentRationalePanel.js +0 -125
  56. package/dist/src/analyze/select/tui/components/FieldEditor.js +0 -1131
  57. package/dist/src/analyze/select/tui/components/FinalizeDialog.js +0 -36
  58. package/dist/src/analyze/select/tui/components/GotoBanner.js +0 -30
  59. package/dist/src/analyze/select/tui/components/GroupedSidebar.js +0 -517
  60. package/dist/src/analyze/select/tui/components/HelpOverlay.js +0 -17
  61. package/dist/src/analyze/select/tui/components/JsonEditor.js +0 -131
  62. package/dist/src/analyze/select/tui/components/JsonPanel.js +0 -66
  63. package/dist/src/analyze/select/tui/components/LineagePanel.js +0 -22
  64. package/dist/src/analyze/select/tui/components/PreviewSummaryBar.js +0 -29
  65. package/dist/src/analyze/select/tui/components/QuitDialog.js +0 -14
  66. package/dist/src/analyze/select/tui/components/RationalePanel.js +0 -80
  67. package/dist/src/analyze/select/tui/components/Sidebar.js +0 -92
  68. package/dist/src/analyze/select/tui/components/SourcePanel.js +0 -52
  69. package/dist/src/analyze/select/tui/components/StatusBar.js +0 -7
  70. package/dist/src/analyze/select/tui/components/TopBar.js +0 -10
  71. package/dist/src/analyze/select/tui/components/removed-components-text.js +0 -11
  72. package/dist/src/analyze/select/tui/hooks/scroll-offset.js +0 -26
  73. package/dist/src/analyze/select/tui/hooks/useImmediateInput.js +0 -66
  74. package/dist/src/analyze/select/tui/hooks/useKeymap.js +0 -67
  75. package/dist/src/analyze/select/tui/hooks/useSession.js +0 -52
  76. package/dist/src/analyze/select/tui/hooks/useUndo.js +0 -26
  77. package/dist/src/analyze/select/tui/theme.js +0 -28
  78. package/dist/src/analyze/select/types.js +0 -39
  79. package/dist/src/analyze/select-agent/command.js +0 -558
  80. package/dist/src/analyze/select-agent/context-builder.js +0 -197
  81. package/dist/src/analyze/select-agent/show-rationale.js +0 -92
  82. package/dist/src/analyze/selection-cascade.js +0 -19
  83. package/dist/src/analyze/slot-graph.js +0 -17
  84. package/dist/src/analyze/tui/AnalyzeView.js +0 -64
  85. package/dist/src/apply/api-client.js +0 -440
  86. package/dist/src/apply/command.js +0 -845
  87. package/dist/src/apply/manifest.js +0 -1
  88. package/dist/src/apply/preview-utils.js +0 -26
  89. package/dist/src/apply/tui/SelectView.js +0 -34
  90. package/dist/src/apply/tui/ServerApplyView.js +0 -49
  91. package/dist/src/apply/tui/ServerPreviewView.js +0 -23
  92. package/dist/src/credentials-store.js +0 -74
  93. package/dist/src/generate/command.js +0 -597
  94. package/dist/src/generate/edit/command.js +0 -127
  95. package/dist/src/generate/tui/GenerateView.js +0 -10
  96. package/dist/src/host-utils.js +0 -26
  97. package/dist/src/import/agent-model-resolve.js +0 -35
  98. package/dist/src/import/auto-filter-resolve.js +0 -16
  99. package/dist/src/import/command.js +0 -380
  100. package/dist/src/import/cycle-auto-reject.js +0 -14
  101. package/dist/src/import/orchestrator.js +0 -603
  102. package/dist/src/import/path-utils.js +0 -30
  103. package/dist/src/import/picker-dispatch.js +0 -44
  104. package/dist/src/import/print-prompt.js +0 -31
  105. package/dist/src/import/strip-allowed-components.js +0 -22
  106. package/dist/src/import/tui/CustomPromptBanner.js +0 -8
  107. package/dist/src/import/tui/WizardApp.js +0 -1670
  108. package/dist/src/import/tui/ai-flag.js +0 -6
  109. package/dist/src/import/tui/auto-filter-error.js +0 -29
  110. package/dist/src/import/tui/autocomplete.js +0 -32
  111. package/dist/src/import/tui/components/AutoFilterBanner.js +0 -19
  112. package/dist/src/import/tui/components/CounterStrip.js +0 -12
  113. package/dist/src/import/tui/components/LegendEntry.js +0 -6
  114. package/dist/src/import/tui/cycle-panel-scroll.js +0 -33
  115. package/dist/src/import/tui/final-review-host.js +0 -25
  116. package/dist/src/import/tui/group-collapse.js +0 -11
  117. package/dist/src/import/tui/history.js +0 -53
  118. package/dist/src/import/tui/hooks/useLineage.js +0 -50
  119. package/dist/src/import/tui/hooks/useOverlayPanel.js +0 -27
  120. package/dist/src/import/tui/lineage-layout.js +0 -82
  121. package/dist/src/import/tui/merge-ai-decisions.js +0 -22
  122. package/dist/src/import/tui/push-decision-gate-helpers.js +0 -8
  123. package/dist/src/import/tui/push-progress.js +0 -1
  124. package/dist/src/import/tui/run-print-files-helpers.js +0 -8
  125. package/dist/src/import/tui/run-teaser.js +0 -9
  126. package/dist/src/import/tui/runLivePreview.js +0 -82
  127. package/dist/src/import/tui/runScopeGate.js +0 -28
  128. package/dist/src/import/tui/scope-gate-columns.js +0 -94
  129. package/dist/src/import/tui/scope-gate-host.js +0 -28
  130. package/dist/src/import/tui/sidebar-width.js +0 -3
  131. package/dist/src/import/tui/spawn-generate.js +0 -53
  132. package/dist/src/import/tui/step-filters.js +0 -42
  133. package/dist/src/import/tui/steps/AtomicGenerateReviewStep.js +0 -726
  134. package/dist/src/import/tui/steps/AtomicScopeGateStep.js +0 -210
  135. package/dist/src/import/tui/steps/CredentialsStep.js +0 -108
  136. package/dist/src/import/tui/steps/DoneStep.js +0 -24
  137. package/dist/src/import/tui/steps/ErrorStep.js +0 -16
  138. package/dist/src/import/tui/steps/GateStep.js +0 -22
  139. package/dist/src/import/tui/steps/GenerateReviewStep.js +0 -1618
  140. package/dist/src/import/tui/steps/PathValidationStep.js +0 -152
  141. package/dist/src/import/tui/steps/PreviewStep.js +0 -37
  142. package/dist/src/import/tui/steps/PreviewValidationErrorStep.js +0 -23
  143. package/dist/src/import/tui/steps/PushDecisionGateStep.js +0 -73
  144. package/dist/src/import/tui/steps/PushingStep.js +0 -24
  145. package/dist/src/import/tui/steps/RunningStep.js +0 -21
  146. package/dist/src/import/tui/steps/ScopeGateStep.js +0 -907
  147. package/dist/src/import/tui/steps/TokenInputStep.js +0 -88
  148. package/dist/src/import/tui/steps/WelcomeStep.js +0 -34
  149. package/dist/src/import/tui/steps/WizardPreviewStep.js +0 -169
  150. package/dist/src/import/tui/steps/auto-reject-decision.js +0 -9
  151. package/dist/src/import/tui/steps/breaking-change-format.js +0 -24
  152. package/dist/src/import/tui/steps/enumerate-cycle-breaks.js +0 -0
  153. package/dist/src/import/tui/steps/preview-diff.js +0 -175
  154. package/dist/src/import/tui/useFinalizePreview.js +0 -0
  155. package/dist/src/import/tui/useLivePreview.js +0 -122
  156. package/dist/src/import/tui/wizard-422-helpers.js +0 -60
  157. package/dist/src/import/tui/wizard-generate-progress.js +0 -25
  158. package/dist/src/import/tui/wizard-save-flow.js +0 -20
  159. package/dist/src/import/tui/wizard-state-transitions.js +0 -32
  160. package/dist/src/lib/agent-model-options.js +0 -15
  161. package/dist/src/lib/command-options.js +0 -30
  162. package/dist/src/lib/composition-mode.js +0 -39
  163. package/dist/src/lib/contentful-urls.js +0 -27
  164. package/dist/src/lib/debug-logger.js +0 -229
  165. package/dist/src/lib/debug-preamble.js +0 -33
  166. package/dist/src/lib/error-parser.js +0 -269
  167. package/dist/src/lib/prompt-overrides.js +0 -69
  168. package/dist/src/lib/terminal-capabilities.js +0 -51
  169. package/dist/src/lib/user-agent.js +0 -29
  170. package/dist/src/output/format.js +0 -110
  171. package/dist/src/print/command.js +0 -221
  172. package/dist/src/print/validate/tui/ValidateView.js +0 -37
  173. package/dist/src/print/validate/validators/cdf-validator.js +0 -104
  174. package/dist/src/print/validate/validators/dtcg-validator.js +0 -110
  175. package/dist/src/print/validate/validators/format-errors.js +0 -18
  176. package/dist/src/program.js +0 -105
  177. package/dist/src/runs/export-helpers.js +0 -59
  178. package/dist/src/runs/fingerprint.js +0 -75
  179. package/dist/src/runs/ls-command.js +0 -137
  180. package/dist/src/runs/modify-launcher.js +0 -42
  181. package/dist/src/runs/path-prompt.js +0 -72
  182. package/dist/src/runs/push-creds-prompt.js +0 -39
  183. package/dist/src/runs/push-helpers.js +0 -72
  184. package/dist/src/runs/push-launcher.js +0 -77
  185. package/dist/src/runs/replay-helpers.js +0 -164
  186. package/dist/src/runs/resolve-run-target.js +0 -45
  187. package/dist/src/runs/run-picker-mount.js +0 -72
  188. package/dist/src/runs/run-picker.js +0 -129
  189. package/dist/src/runs/save-conflict.js +0 -56
  190. package/dist/src/runs/save-path-resolver.js +0 -75
  191. package/dist/src/runs/staleness.js +0 -140
  192. package/dist/src/runs/store.js +0 -136
  193. package/dist/src/session/cache-keys.js +0 -44
  194. package/dist/src/session/command.js +0 -261
  195. package/dist/src/session/db.js +0 -1538
  196. package/dist/src/session/migration.js +0 -117
  197. package/dist/src/session/session-id.js +0 -212
  198. package/dist/src/session/stats.js +0 -89
  199. package/dist/src/setup/analytics-prompt.js +0 -24
  200. package/dist/src/setup/auto-filter-prompt.js +0 -24
  201. package/dist/src/setup/command.js +0 -966
  202. package/dist/src/setup/composition-mode-prompt.js +0 -37
  203. package/dist/src/setup/debug-mode-prompt.js +0 -22
  204. package/dist/src/types.js +0 -1
@@ -1,484 +0,0 @@
1
- import { access, readFile } from 'node:fs/promises';
2
- import { dirname, resolve } from 'node:path';
3
- import { createElement } from 'react';
4
- import { render } from 'ink';
5
- import { requireInteractiveTerminal } from '../../lib/terminal-capabilities.js';
6
- import { getRefineArtifactsRoot, ensureRefineSession, getRefineSessionPaths, saveReviewState } from './persistence.js';
7
- import { loadReviewInput } from './parser.js';
8
- import { App } from './tui/App.js';
9
- import { openPipelineDb, loadRawComponents, storeRawComponents, createStep, updateStep } from '../../session/db.js';
10
- import { validateExtractedComponents, shouldExcludeDueToValidation, formatExclusionWarning, } from '@contentful/experience-design-system-extraction';
11
- import { bindAnalyticsSessionId, enrichCommandResult, exitWithAnalytics } from '../../analytics/index.js';
12
- const SAFE_PATH_RE = /^[a-zA-Z0-9_.$[\]=]+$/;
13
- const PROTO_KEYS = new Set(['__proto__', 'constructor', 'prototype']);
14
- function applyDotPath(obj, path, value) {
15
- if (!SAFE_PATH_RE.test(path)) {
16
- process.stderr.write(`Warning: --patch path contains invalid characters: '${path}', skipping\n`);
17
- return;
18
- }
19
- const parts = path.split('.');
20
- if (parts.some((p) => PROTO_KEYS.has(p))) {
21
- process.stderr.write(`Warning: --patch path contains forbidden key: '${path}', skipping\n`);
22
- return;
23
- }
24
- let current = obj;
25
- for (let i = 0; i < parts.length - 1; i++) {
26
- const part = parts[i];
27
- // Handle array predicate syntax: field[name=value]
28
- const arrayMatch = /^(.+)\[name=(.+)\]$/.exec(part);
29
- if (arrayMatch) {
30
- const [, fieldName, matchValue] = arrayMatch;
31
- const arr = current[fieldName];
32
- if (Array.isArray(arr)) {
33
- const item = arr.find((el) => el['name'] === matchValue);
34
- if (item) {
35
- current = item;
36
- }
37
- else {
38
- process.stderr.write(`Warning: --patch array item [name=${matchValue}] not found in '${fieldName}', skipping\n`);
39
- return;
40
- }
41
- }
42
- }
43
- else {
44
- if (typeof current[part] !== 'object' || current[part] === null) {
45
- process.stderr.write(`Warning: --patch path '${path}' — '${part}' is not an object, skipping\n`);
46
- return;
47
- }
48
- current = current[part];
49
- }
50
- }
51
- const lastPart = parts[parts.length - 1];
52
- current[lastPart] = value;
53
- }
54
- function applyPatch(snapshot, ops) {
55
- const components = snapshot.components.map((c) => {
56
- const op = ops.find((o) => o.component === c.name);
57
- if (!op)
58
- return c;
59
- let updated = { ...c };
60
- if (op.status) {
61
- updated = { ...updated, status: op.status };
62
- }
63
- if (op.set) {
64
- const editedProposal = structuredClone(updated.editedProposal);
65
- for (const [path, value] of Object.entries(op.set)) {
66
- applyDotPath(editedProposal, path, value);
67
- }
68
- updated = {
69
- ...updated,
70
- editedProposal: editedProposal,
71
- };
72
- }
73
- return updated;
74
- });
75
- return { ...snapshot, components };
76
- }
77
- async function runNonInteractive(snapshot, opts, paths, sessionId) {
78
- // --exclude-components on its own (no --select-all / --select / --deselect /
79
- // --reject / --patch) is the orchestrator's 422 retry-loop invocation: only
80
- // the named components should change. The rebuild path below calls
81
- // storeRawComponents which DELETEs all rows and re-inserts with default
82
- // status='extracted', wiping the post-generate state required by
83
- // loadCDFComponents (status='generated' + raw_props.cdf_type populated).
84
- // Short-circuit through rejectComponentsByName, which is a pure UPDATE.
85
- const onlyExcludeComponents = !!opts.excludeComponents &&
86
- !opts.acceptAll &&
87
- !opts.selectAll &&
88
- !(opts.select ?? []).length &&
89
- !(opts.deselect ?? []).length &&
90
- !(opts.reject ?? []).length &&
91
- !opts.patch;
92
- if (onlyExcludeComponents) {
93
- const names = opts
94
- .excludeComponents.split(',')
95
- .map((n) => n.trim())
96
- .filter(Boolean);
97
- if (names.length > 0) {
98
- // paths.sessionDir = resolve(artifactsRoot, sessionId), so dirname gives
99
- // back the artifactsRoot — keeps rejectComponentsByName from re-resolving
100
- // it via env var when the caller already knows it.
101
- const artifactsRoot = dirname(paths.sessionDir);
102
- await rejectComponentsByName(sessionId, names, { artifactsRoot });
103
- }
104
- const accepted = snapshot.components.filter((c) => c.status === 'accepted').length;
105
- const rejected = snapshot.components.filter((c) => c.status === 'rejected').length + (names.length || 0);
106
- enrichCommandResult({ accepted_component_count: accepted });
107
- process.stderr.write(`Accepted: ${accepted} Rejected: ${rejected}\n`);
108
- return;
109
- }
110
- let result = { ...snapshot };
111
- const rejectPatterns = [...(opts.reject ?? []), ...(opts.deselect ?? [])].map((p) => p.toLowerCase());
112
- const selectPatterns = (opts.select ?? []).map((p) => p.toLowerCase());
113
- const selectAll = opts.acceptAll || opts.selectAll;
114
- // Fail-loud gate: --select-all stops with a non-zero exit when ANY component
115
- // has error-severity validation issues. The user must opt in to auto-rejection
116
- // with --exclude-invalid. Silent exclusion in CI / orchestrator contexts is
117
- // dangerous — the caller should see and acknowledge that components are
118
- // being dropped from the import.
119
- if (selectAll && !opts.excludeInvalid) {
120
- const invalid = result.components.filter((c) => shouldExcludeDueToValidation(c.originalProposal));
121
- if (invalid.length > 0) {
122
- const lines = [
123
- `Error: ${invalid.length} component(s) failed validation; refusing --select-all without --exclude-invalid:`,
124
- ];
125
- for (const c of invalid) {
126
- const codes = (c.originalProposal.validationIssues ?? [])
127
- .filter((i) => i.severity === 'error')
128
- .map((i) => i.code)
129
- .join(', ');
130
- lines.push(` ✗ ${c.name} ${codes}`);
131
- }
132
- lines.push('');
133
- lines.push('Re-run with --exclude-invalid to auto-reject these components, or run analyze select interactively to fix them.');
134
- process.stderr.write(lines.join('\n') + '\n');
135
- await exitWithAnalytics(1);
136
- return;
137
- }
138
- }
139
- if (selectAll || rejectPatterns.length > 0 || selectPatterns.length > 0) {
140
- result = {
141
- ...result,
142
- components: result.components.map((c) => {
143
- const nameLower = c.name.toLowerCase();
144
- if (rejectPatterns.some((p) => nameLower.includes(p))) {
145
- return { ...c, status: 'rejected' };
146
- }
147
- if (selectAll && opts.excludeInvalid && shouldExcludeDueToValidation(c.originalProposal)) {
148
- return { ...c, status: 'rejected' };
149
- }
150
- if (selectAll || selectPatterns.some((p) => nameLower.includes(p))) {
151
- return { ...c, status: 'accepted' };
152
- }
153
- return c;
154
- }),
155
- };
156
- }
157
- // Apply --exclude-components: force-reject the named components regardless of
158
- // how --select-all / --select / --deselect classified them. Used by the
159
- // orchestrator's 422 retry loop to exclude server-validation offenders.
160
- if (opts.excludeComponents) {
161
- const excludeNames = new Set(opts.excludeComponents
162
- .split(',')
163
- .map((n) => n.trim())
164
- .filter(Boolean));
165
- if (excludeNames.size > 0) {
166
- result = {
167
- ...result,
168
- components: result.components.map((c) => excludeNames.has(c.name) ? { ...c, status: 'rejected' } : c),
169
- };
170
- }
171
- }
172
- // Apply --patch
173
- if (opts.patch) {
174
- let patchOps;
175
- try {
176
- const raw = await readFile(resolve(opts.patch), 'utf8');
177
- const parsed = JSON.parse(raw);
178
- if (!Array.isArray(parsed)) {
179
- process.stderr.write(`Error: --patch file must be a JSON array of patch operations: ${opts.patch}\n`);
180
- await exitWithAnalytics(1);
181
- return;
182
- }
183
- patchOps = parsed;
184
- }
185
- catch {
186
- process.stderr.write(`Error: cannot read or parse --patch file: ${opts.patch}\n`);
187
- await exitWithAnalytics(1);
188
- return;
189
- }
190
- // Warn on unknown component names
191
- const knownNames = new Set(result.components.map((c) => c.name));
192
- for (const op of patchOps) {
193
- if (!knownNames.has(op.component)) {
194
- process.stderr.write(`Warning: --patch targets unknown component '${op.component}', skipping\n`);
195
- }
196
- }
197
- result = applyPatch(result, patchOps);
198
- }
199
- const accepted = result.components.filter((c) => c.status === 'accepted');
200
- const rejected = result.components.filter((c) => c.status === 'rejected');
201
- // Surface what was excluded by --select-all --exclude-invalid so the
202
- // non-interactive caller (CI, orchestrator, scripted pipeline) doesn't
203
- // have to guess what failed validation. The warning is printed BEFORE
204
- // saveReviewState so the message lands ahead of the bare counts.
205
- if (selectAll && opts.excludeInvalid) {
206
- const autoRejected = rejected
207
- .filter((c) => shouldExcludeDueToValidation(c.originalProposal))
208
- .map((c) => ({ name: c.name, validationIssues: c.originalProposal.validationIssues }));
209
- process.stderr.write(formatExclusionWarning(autoRejected));
210
- }
211
- // Persist decisions to session state so pipeline orchestrator can read them
212
- await saveReviewState(paths.statePath, result);
213
- // Sync edited proposals back to the DB so generation uses the user's edits.
214
- //
215
- // Only --patch can mutate editedProposal — the other non-interactive flags
216
- // (--select-all / --select / --deselect / --reject / --exclude-invalid)
217
- // change status only. Calling storeRawComponents in the no-edit case is
218
- // not just wasteful: it's destructive. storeRawComponents starts with
219
- // `DELETE FROM raw_components WHERE session_id = ?` and re-inserts only
220
- // the components passed in — so on a --select-all run, every rejected
221
- // component (including its raw_slots / raw_props children) is physically
222
- // removed. A second invocation of the same command would then see a
223
- // smaller snapshot, lose the rejected component(s), and report different
224
- // counts.
225
- if (opts.patch && accepted.length > 0) {
226
- const db = openPipelineDb();
227
- try {
228
- storeRawComponents(db, sessionId, accepted.map((c) => c.editedProposal));
229
- }
230
- finally {
231
- db.close();
232
- }
233
- }
234
- enrichCommandResult({ accepted_component_count: accepted.length });
235
- process.stderr.write(`Accepted: ${accepted.length} Rejected: ${rejected.length}\n`);
236
- }
237
- /**
238
- * Load components from the pipeline DB and re-run extraction validation.
239
- *
240
- * `validationIssues` is intentionally not persisted (the validator is pure
241
- * and cheap to re-run), so any cold-start of `analyze select` from a prior
242
- * `analyze extract` session needs to recompute it before building the
243
- * review snapshot — otherwise the TUI sees no validation errors at all.
244
- */
245
- export async function loadAndValidateForReview(sessionId, projectRoot) {
246
- const db = openPipelineDb();
247
- let rawComponents;
248
- try {
249
- rawComponents = loadRawComponents(db, sessionId);
250
- }
251
- finally {
252
- db.close();
253
- }
254
- const validatedComponents = validateExtractedComponents(rawComponents);
255
- return loadReviewInput(validatedComponents, { reviewRoot: projectRoot });
256
- }
257
- /**
258
- * Synthesize SERVER_VALIDATION_FAILED issues onto the review state file's
259
- * matching components. Used by the wizard's 422 recovery path: after a
260
- * preview-API validation error, this runs before re-launching the
261
- * analyze-select TUI so the Sidebar's existing red-warning rendering
262
- * pins the offenders to the top.
263
- *
264
- * Idempotent on identical input is NOT a contract: each call appends.
265
- * The wizard's 422 path is a one-shot per preview attempt, so duplicate
266
- * issues only matter if a caller invokes this twice without the user
267
- * round-tripping through the TUI in between.
268
- */
269
- export async function patchReviewStateWithValidationErrors(sessionId, errors, opts = {}) {
270
- const artifactsRoot = opts.artifactsRoot ?? getRefineArtifactsRoot();
271
- const paths = await getRefineSessionPaths(sessionId, artifactsRoot);
272
- let snapshot;
273
- try {
274
- await access(paths.statePath);
275
- snapshot = JSON.parse(await readFile(paths.statePath, 'utf8'));
276
- }
277
- catch {
278
- snapshot = await loadAndValidateForReview(sessionId, undefined);
279
- snapshot = await ensureRefineSession(sessionId, artifactsRoot, snapshot);
280
- }
281
- const errorsByName = new Map();
282
- for (const err of errors) {
283
- const list = errorsByName.get(err.componentName) ?? [];
284
- list.push(err);
285
- errorsByName.set(err.componentName, list);
286
- }
287
- const patchedNames = [];
288
- const knownNames = new Set(snapshot.components.map((c) => c.name));
289
- const components = snapshot.components.map((c) => {
290
- const matched = errorsByName.get(c.name);
291
- if (!matched || matched.length === 0)
292
- return c;
293
- patchedNames.push(c.name);
294
- const newIssues = matched.map((err) => ({
295
- severity: 'error',
296
- code: 'SERVER_VALIDATION_FAILED',
297
- message: err.message,
298
- }));
299
- const existing = c.originalProposal.validationIssues ?? [];
300
- return {
301
- ...c,
302
- originalProposal: {
303
- ...c.originalProposal,
304
- validationIssues: [...existing, ...newIssues],
305
- },
306
- };
307
- });
308
- const missingNames = [...errorsByName.keys()].filter((n) => !knownNames.has(n));
309
- await saveReviewState(paths.statePath, { ...snapshot, components });
310
- return { patchedNames, missingNames };
311
- }
312
- /**
313
- * Exclude components whose names appear in `names` from the next preview/push.
314
- * Used by the wizard's "skip and retry" path after a 422.
315
- *
316
- * Two stores are updated:
317
- * - Pipeline DB (`raw_components.status` → `'generate-rejected'`) so that
318
- * `loadCDFComponents()` excludes them from the next manifest. This is the
319
- * load-bearing change — preview reads from the DB, not the JSON file.
320
- * - Review state JSON (`status` → `'rejected'`) so that if the user re-enters
321
- * the analyze-select TUI later, the rejected status is reflected.
322
- *
323
- * `'generate-rejected'` matches the existing convention for "component was
324
- * generated successfully but later excluded" (see GenerateReviewStep) — the
325
- * component generated fine locally but failed server-side validation.
326
- */
327
- export async function rejectComponentsByName(sessionId, names, opts = {}) {
328
- if (names.length === 0)
329
- return;
330
- const db = openPipelineDb();
331
- try {
332
- const placeholders = names.map(() => '?').join(',');
333
- db.prepare(`UPDATE raw_components SET status = 'generate-rejected' WHERE session_id = ? AND name IN (${placeholders})`).run(sessionId, ...names);
334
- }
335
- finally {
336
- db.close();
337
- }
338
- const artifactsRoot = opts.artifactsRoot ?? getRefineArtifactsRoot();
339
- const paths = await getRefineSessionPaths(sessionId, artifactsRoot);
340
- const nameSet = new Set(names);
341
- let snapshot;
342
- try {
343
- snapshot = JSON.parse(await readFile(paths.statePath, 'utf8'));
344
- }
345
- catch {
346
- return;
347
- }
348
- const components = snapshot.components.map((c) => (nameSet.has(c.name) ? { ...c, status: 'rejected' } : c));
349
- await saveReviewState(paths.statePath, { ...snapshot, components });
350
- }
351
- async function resolveSessionId(sessionFlag) {
352
- if (sessionFlag)
353
- return sessionFlag;
354
- const db = openPipelineDb();
355
- try {
356
- const row = db
357
- .prepare(`SELECT s.id FROM sessions s
358
- JOIN steps st ON st.session_id = s.id
359
- WHERE st.command = 'analyze extract'
360
- AND st.status = 'complete'
361
- ORDER BY st.started_at DESC
362
- LIMIT 1`)
363
- .get();
364
- if (!row) {
365
- process.stderr.write('Error: no completed analyze extract session found. Run analyze extract first, or pass --session <id>.\n');
366
- return await exitWithAnalytics(1);
367
- }
368
- return row.id;
369
- }
370
- finally {
371
- db.close();
372
- }
373
- }
374
- export function registerAnalyzeEditCommand(program) {
375
- program
376
- .command('select')
377
- .alias('edit')
378
- .description('Select components for generation and optionally patch their definitions')
379
- .option('--session <id>', 'Session ID from analyze extract (defaults to most recent)')
380
- .option('--project-root <path>', 'Project root for resolving component source files')
381
- .option('--select-all', 'Select all components without launching the TUI')
382
- .option('--select <pattern>', 'Select components whose name contains pattern (repeatable)', collect, [])
383
- .option('--deselect <pattern>', 'Deselect components whose name contains pattern (repeatable)', collect, [])
384
- .option('--accept-all', 'Alias for --select-all', false)
385
- .option('--reject <pattern>', 'Alias for --deselect <pattern> (repeatable)', collect, [])
386
- .option('--patch <path>', 'Path to a JSON patch file for structured component overrides')
387
- .option('--exclude-invalid', 'With --select-all: auto-reject components with validation errors instead of failing loud (opt-in bypass for the gate)')
388
- .option('--exclude-components <names>', 'Comma-separated component names to force-reject, regardless of other selection flags')
389
- .action(async ({ session: sessionFlag, projectRoot, acceptAll, selectAll, reject, deselect, select, patch, excludeInvalid, excludeComponents, }) => {
390
- const sessionId = await resolveSessionId(sessionFlag);
391
- await bindAnalyticsSessionId(sessionId);
392
- const db = openPipelineDb();
393
- let rawComponentCount = 0;
394
- try {
395
- rawComponentCount = loadRawComponents(db, sessionId).length;
396
- }
397
- finally {
398
- db.close();
399
- }
400
- if (rawComponentCount === 0) {
401
- process.stderr.write(`Error: session '${sessionId}' has no raw components. Run analyze extract first.\n`);
402
- await exitWithAnalytics(1);
403
- return;
404
- }
405
- const artifactsRoot = getRefineArtifactsRoot();
406
- const nonInteractive = acceptAll ||
407
- selectAll ||
408
- (reject ?? []).length > 0 ||
409
- (deselect ?? []).length > 0 ||
410
- (select ?? []).length > 0 ||
411
- !!patch ||
412
- !!excludeComponents;
413
- let paths;
414
- let snapshot;
415
- try {
416
- snapshot = await loadAndValidateForReview(sessionId, projectRoot);
417
- paths = await getRefineSessionPaths(sessionId, artifactsRoot);
418
- if (!nonInteractive) {
419
- snapshot = await ensureRefineSession(sessionId, artifactsRoot, snapshot);
420
- }
421
- else {
422
- await ensureRefineSession(sessionId, artifactsRoot, snapshot);
423
- }
424
- }
425
- catch (error) {
426
- process.stderr.write(`Error: unable to initialize refine session.\n${error instanceof Error ? error.message : String(error)}\n`);
427
- await exitWithAnalytics(1);
428
- return;
429
- }
430
- // Non-interactive path
431
- if (nonInteractive) {
432
- const stepDb = openPipelineDb();
433
- const stepId = createStep(stepDb, sessionId, 'analyze select', {
434
- sessionId,
435
- });
436
- try {
437
- await runNonInteractive(snapshot, {
438
- session: sessionFlag,
439
- projectRoot,
440
- acceptAll,
441
- selectAll,
442
- reject,
443
- deselect,
444
- select,
445
- patch,
446
- excludeInvalid,
447
- excludeComponents,
448
- }, paths, sessionId);
449
- updateStep(stepDb, stepId, 'complete', { sessionId });
450
- }
451
- catch (err) {
452
- updateStep(stepDb, stepId, 'failed', {}, err instanceof Error ? err.message : String(err));
453
- stepDb.close();
454
- throw err;
455
- }
456
- stepDb.close();
457
- return;
458
- }
459
- // Test mode: print launch contract and return without launching TUI
460
- if (process.env.EDS_REVIEW_TEST_MODE === '1') {
461
- process.stdout.write(`session=${sessionId}\n` +
462
- `session_dir=${paths.sessionDir}\n` +
463
- `events.jsonl=${paths.eventsPath}\n` +
464
- `current-review-state.json=${paths.statePath}\n`);
465
- return;
466
- }
467
- requireInteractiveTerminal({
468
- alternative: 'pass `--accept-all`, `--select-all`, `--reject`, `--deselect`, `--select`, or `--patch`',
469
- });
470
- if (process.stdout.columns !== undefined && process.stdout.columns < 60) {
471
- process.stderr.write(`Error: terminal too narrow (${process.stdout.columns} cols). Resize to 60+ columns.\n`);
472
- await exitWithAnalytics(1);
473
- }
474
- const { waitUntilExit } = render(createElement(App, {
475
- sessionId,
476
- artifactsRoot,
477
- reviewRoot: projectRoot,
478
- }));
479
- await waitUntilExit();
480
- });
481
- }
482
- function collect(val, prev) {
483
- return [...prev, val];
484
- }
@@ -1,5 +0,0 @@
1
- export { registerAnalyzeEditCommand } from './command.js';
2
- export { loadReviewInput } from './parser.js';
3
- export { appendReviewEvent, ensureRefineSession, getRefineArtifactsRoot, getRefineSessionPaths, saveReviewState, } from './persistence.js';
4
- export { formatFinalizeContract } from './stdout.js';
5
- export { createReviewSessionSummary } from './types.js';
@@ -1,53 +0,0 @@
1
- import { createHash } from 'node:crypto';
2
- import { access } from 'node:fs/promises';
3
- import { isAbsolute, relative, resolve } from 'node:path';
4
- function createComponentId(name, resolvedSourcePath) {
5
- const sourceHash = createHash('sha256').update(`${name}:${resolvedSourcePath}`).digest('hex').slice(0, 12);
6
- return `${name}-${sourceHash}`;
7
- }
8
- async function resolveComponentSourcePath(source, reviewRoot) {
9
- // Absolute paths stored in the DB are used directly — no reviewRoot boundary check needed.
10
- if (isAbsolute(source)) {
11
- try {
12
- await access(source);
13
- return source;
14
- }
15
- catch {
16
- throw new Error(`Unable to access component source at ${source}`);
17
- }
18
- }
19
- const candidate = resolve(reviewRoot, source);
20
- const relativeToRoot = relative(reviewRoot, candidate);
21
- if (relativeToRoot.startsWith('..') || relativeToRoot === '..' || isAbsolute(relativeToRoot)) {
22
- throw new Error(`Resolved component source is outside the review root: ${source}. Pass --project-root <path> to set the correct base.`);
23
- }
24
- try {
25
- await access(candidate);
26
- return candidate;
27
- }
28
- catch {
29
- throw new Error(`Unable to access component source at ${candidate}`);
30
- }
31
- }
32
- export async function loadReviewInput(components, options = {}) {
33
- const reviewRoot = resolve(options.reviewRoot ?? process.cwd());
34
- const records = await Promise.all(components.map(async (component) => {
35
- let resolvedSourcePath;
36
- try {
37
- resolvedSourcePath = await resolveComponentSourcePath(component.source, reviewRoot);
38
- }
39
- catch (error) {
40
- throw new Error(`Unable to access component source for ${component.name}: ${error instanceof Error ? error.message : String(error)}`);
41
- }
42
- return {
43
- id: createComponentId(component.name, resolvedSourcePath),
44
- name: component.name,
45
- resolvedSourcePath,
46
- sourceCode: null,
47
- originalProposal: component,
48
- editedProposal: structuredClone(component),
49
- status: 'needs-review',
50
- };
51
- }));
52
- return { components: records };
53
- }
@@ -1,80 +0,0 @@
1
- import { access, appendFile, mkdir, readFile, writeFile } from 'node:fs/promises';
2
- import { homedir } from 'node:os';
3
- import { resolve } from 'node:path';
4
- import { loadRawComponents } from '../../session/db.js';
5
- export function getRefineArtifactsRoot() {
6
- if (process.env.EDS_REVIEW_ARTIFACTS_DIR) {
7
- return resolve(process.env.EDS_REVIEW_ARTIFACTS_DIR);
8
- }
9
- return resolve(homedir(), '.contentful', 'experience-design-system-cli', 'reviews');
10
- }
11
- export async function getRefineSessionPaths(sessionId, artifactsRoot) {
12
- const sessionDir = resolve(artifactsRoot, sessionId);
13
- return {
14
- sessionDir,
15
- eventsPath: resolve(sessionDir, 'events.jsonl'),
16
- statePath: resolve(sessionDir, 'current-review-state.json'),
17
- };
18
- }
19
- export async function saveReviewState(statePath, session) {
20
- await writeFile(statePath, JSON.stringify(session, null, 2), 'utf8');
21
- }
22
- export async function appendReviewEvent(eventsPath, event) {
23
- const record = {
24
- type: event.type,
25
- timestamp: new Date().toISOString(),
26
- payload: event.payload,
27
- };
28
- await appendFile(eventsPath, JSON.stringify(record) + '\n', 'utf8');
29
- }
30
- export async function ensureRefineSession(sessionId, artifactsRoot, initialSnapshot) {
31
- const paths = await getRefineSessionPaths(sessionId, artifactsRoot);
32
- await mkdir(paths.sessionDir, { recursive: true });
33
- try {
34
- await access(paths.statePath);
35
- const savedState = await readFile(paths.statePath, 'utf8');
36
- return JSON.parse(savedState);
37
- }
38
- catch {
39
- await writeFile(paths.statePath, JSON.stringify(initialSnapshot, null, 2), 'utf8');
40
- await writeFile(paths.eventsPath, '', 'utf8');
41
- return initialSnapshot;
42
- }
43
- }
44
- /**
45
- * Persist scope-gate decisions to `current-review-state.json` so downstream
46
- * consumers (notably `loadAcceptedNames` in `generate components`) can filter
47
- * out rejected components. The wizard's scope-gate doesn't drive the rich
48
- * `analyze select` TUI, so fields that file consumes (resolvedSourcePath,
49
- * sourceCode) are populated with safe placeholders. Only `name` and `status`
50
- * matter for `loadAcceptedNames`.
51
- */
52
- export async function writeScopeDecisionsSnapshot(db, sessionId, decisions) {
53
- const acceptedSet = new Set(decisions.accepted);
54
- const rawComponents = loadRawComponents(db, sessionId);
55
- const records = rawComponents.map((c) => {
56
- const status = acceptedSet.has(c.name) ? 'accepted' : 'rejected';
57
- const proposal = {
58
- name: c.name,
59
- source: c.source,
60
- framework: c.framework,
61
- props: c.props,
62
- slots: c.slots,
63
- ...(c.extractionConfidence !== undefined ? { extractionConfidence: c.extractionConfidence } : {}),
64
- ...(c.reviewReasons !== undefined ? { reviewReasons: c.reviewReasons } : {}),
65
- ...(c.needsReview !== undefined ? { needsReview: c.needsReview } : {}),
66
- };
67
- return {
68
- id: c.component_id,
69
- name: c.name,
70
- resolvedSourcePath: '',
71
- sourceCode: null,
72
- originalProposal: proposal,
73
- editedProposal: proposal,
74
- status,
75
- };
76
- });
77
- const paths = await getRefineSessionPaths(sessionId, getRefineArtifactsRoot());
78
- await mkdir(paths.sessionDir, { recursive: true });
79
- await saveReviewState(paths.statePath, { components: records });
80
- }
@@ -1,62 +0,0 @@
1
- /**
2
- * Pure mapper from a `ServerPreviewResponse` to a per-component-name
3
- * `PreviewAnnotation` map. Consumed by the wizard's final-review sidebar
4
- * (and any other surface that wants to render diff badges next to a
5
- * component row).
6
- *
7
- * The `new` bucket of `ServerPreviewResponse.components` is `CDFComponentEntry[]`
8
- * — those entries do NOT carry a `name` field on the wire (the name is the
9
- * KEY in the parent manifest). So instead of trying to read names off entries,
10
- * we derive the "new" set as:
11
- *
12
- * new = localNames \ (unchangedNames ∪ changedNames ∪ removedNames)
13
- *
14
- * Callers must pass the local manifest's component keys as `localNames` so
15
- * we can perform the set difference. Removed components from the server that
16
- * aren't in the local manifest are still annotated as `'removed'` (so the
17
- * detail panel can list them).
18
- *
19
- * Precedence: `breaking` > `changed` > `removed` > `new`.
20
- *
21
- * Unchanged entries are omitted from the map — callers should treat
22
- * `map.get(name) === undefined` as "no annotation".
23
- */
24
- export function applyPreviewAnnotations(preview, localNames) {
25
- const out = new Map();
26
- const unchanged = new Set(preview.components.unchanged);
27
- // Annotate changed first; collect their names so we can exclude from "new".
28
- const changedNames = new Set();
29
- for (const item of preview.components.changed) {
30
- const name = item.current?.name;
31
- if (typeof name !== 'string')
32
- continue;
33
- changedNames.add(name);
34
- if (item.changeClassification?.classification === 'breaking') {
35
- out.set(name, 'breaking');
36
- }
37
- else {
38
- out.set(name, 'changed');
39
- }
40
- }
41
- // Annotate removed.
42
- const removedNames = new Set();
43
- for (const entity of preview.components.removed) {
44
- if (entity.name) {
45
- removedNames.add(entity.name);
46
- out.set(entity.name, 'removed');
47
- }
48
- }
49
- // Derive `new` = localNames \ (unchanged ∪ changed ∪ removed).
50
- for (const name of localNames) {
51
- if (unchanged.has(name))
52
- continue;
53
- if (changedNames.has(name))
54
- continue;
55
- if (removedNames.has(name))
56
- continue;
57
- if (out.has(name))
58
- continue;
59
- out.set(name, 'new');
60
- }
61
- return out;
62
- }