@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,440 +0,0 @@
1
- import { designSystemImportSourcelessPreview, designSystemImportApply, designSystemImportGetOperation, } from '@contentful/experience-design-system-client';
2
- import { DEFAULT_API_HOST, toApiHost } from '../host-utils.js';
3
- import { getDebugLogger } from '../lib/debug-logger.js';
4
- import { buildUserAgent } from '../lib/user-agent.js';
5
- export const DEFAULT_HOST = DEFAULT_API_HOST;
6
- export const PREVIEW_ERROR_PREFIX = 'preview failed:';
7
- export const APPLY_ERROR_PREFIX = 'apply failed:';
8
- // Substring match the orchestrator uses to distinguish a parseable
9
- // component-level validation failure from generic 422s. Quoted because the
10
- // match runs against the raw JSON body (which contains `"code":"ValidationFailed"`).
11
- // If the server ever changes the casing or naming, isPreviewValidationError
12
- // silently returns false and the retry loop never fires — so this lives next
13
- // to the prefixes as a deliberate, named contract rather than an inline
14
- // magic string in the orchestrator.
15
- export const VALIDATION_FAILED_CODE = '"ValidationFailed"';
16
- const MAX_RETRY_AFTER_MS = 60_000;
17
- function defaultSleep(delayMs) {
18
- return new Promise((resolve) => setTimeout(resolve, delayMs));
19
- }
20
- function isTransientStatus(status) {
21
- return status >= 500 && status <= 599;
22
- }
23
- function retryAfterMs(response) {
24
- const value = response.headers?.get('retry-after')?.trim();
25
- if (!value)
26
- return undefined;
27
- const seconds = Number(value);
28
- const delayMs = Number.isFinite(seconds) ? seconds * 1000 : Date.parse(value) - Date.now();
29
- if (!Number.isFinite(delayMs) || delayMs < 0 || delayMs > MAX_RETRY_AFTER_MS)
30
- return undefined;
31
- return Math.round(delayMs);
32
- }
33
- function errorMessage(error) {
34
- return error instanceof Error && error.message ? error.message : String(error);
35
- }
36
- // The generated client parses non-2xx bodies to JSON internally (unlike a raw
37
- // fetch(), which hands back the raw body string). Re-serializing here keeps
38
- // ApiError's `body: string` contract and parsePreviewValidationErrors'
39
- // `JSON.parse` unchanged for callers, at the cost of a harmless double-parse
40
- // on error paths only.
41
- function stringifyError(error) {
42
- return typeof error === 'string' ? error : JSON.stringify(error ?? {});
43
- }
44
- // Cap on the body slice appended to ApiError.message. Bumped from 1000 →
45
- // 16384 so realistic 422 ValidationFailed reports (which list every
46
- // offending component, ~100 chars per error, easily exceeds 1KB once you
47
- // cross ~10 components) survive intact through subprocess stderr. The
48
- // orchestrator's parseOffendingComponentNames does JSON.parse on this slice
49
- // and silently fails to recover any offenders if the JSON is mid-truncated.
50
- // The cap stays in place to keep a runaway server response from blowing up
51
- // log output.
52
- const ERROR_BODY_LOG_CAP = 16384;
53
- export class ApiError extends Error {
54
- status;
55
- body;
56
- guidance;
57
- constructor(message, status, body, guidance) {
58
- super(message);
59
- this.status = status;
60
- this.body = body;
61
- this.guidance = guidance;
62
- if (body) {
63
- const trimmed = body.length > ERROR_BODY_LOG_CAP ? body.slice(0, ERROR_BODY_LOG_CAP) + '…' : body;
64
- this.message = `${message}\n${trimmed}`;
65
- }
66
- }
67
- }
68
- const COMPONENT_PATH_PREFIX = 'manifest:components/';
69
- /**
70
- * Parse the JSON body of a 422 from `previewImport()` into structured
71
- * per-component validation errors. Returns [] for any malformed input
72
- * so callers can fall back to the generic error path without try/catch.
73
- *
74
- * Path shape: `manifest:components/<Name>/$slots/<key>` or
75
- * `manifest:components/<Name>/$properties/<key>`. Only the component
76
- * name is extracted today; `path` and `message` are kept verbatim so
77
- * future surfaces (debug logging, headless retry in SP-4) can render
78
- * the field-level detail.
79
- */
80
- export function parsePreviewValidationErrors(body) {
81
- if (!body)
82
- return [];
83
- let parsed;
84
- try {
85
- parsed = JSON.parse(body);
86
- }
87
- catch {
88
- return [];
89
- }
90
- const details = parsed?.details;
91
- const errors = details?.errors;
92
- if (!Array.isArray(errors))
93
- return [];
94
- const out = [];
95
- for (const raw of errors) {
96
- if (typeof raw !== 'object' || raw === null)
97
- continue;
98
- const entry = raw;
99
- if (typeof entry.path !== 'string' || typeof entry.message !== 'string')
100
- continue;
101
- if (!entry.path.startsWith(COMPONENT_PATH_PREFIX))
102
- continue;
103
- const tail = entry.path.slice(COMPONENT_PATH_PREFIX.length);
104
- const slash = tail.indexOf('/');
105
- const componentName = slash === -1 ? tail : tail.slice(0, slash);
106
- if (!componentName)
107
- continue;
108
- out.push({ componentName, path: entry.path, message: entry.message });
109
- }
110
- return out;
111
- }
112
- const PROPERTY_BREAKING_REASONS = new Set([
113
- 'removed',
114
- 'added_required_no_default',
115
- 'type_changed',
116
- 'validation_narrowed',
117
- ]);
118
- const SLOT_BREAKING_REASONS = new Set(['slot_removed', 'slot_allowed_components_narrowed']);
119
- export function sanitizeBreakingChanges(raw) {
120
- if (!Array.isArray(raw))
121
- return [];
122
- const out = [];
123
- for (const bc of raw) {
124
- if (typeof bc !== 'object' || bc === null)
125
- continue;
126
- const reason = bc.reason;
127
- if (typeof reason !== 'string')
128
- continue;
129
- if ('propertyId' in bc && typeof bc.propertyId === 'string') {
130
- if (PROPERTY_BREAKING_REASONS.has(reason))
131
- out.push(bc);
132
- continue;
133
- }
134
- if ('slotId' in bc && typeof bc.slotId === 'string') {
135
- if (SLOT_BREAKING_REASONS.has(reason))
136
- out.push(bc);
137
- continue;
138
- }
139
- }
140
- return out;
141
- }
142
- function sanitizePreviewResponse(res) {
143
- for (const item of res.components?.changed ?? []) {
144
- const cc = item.changeClassification;
145
- if (cc && Array.isArray(cc.breakingChanges)) {
146
- cc.breakingChanges = sanitizeBreakingChanges(cc.breakingChanges);
147
- }
148
- }
149
- return res;
150
- }
151
- function isApsDenialBody(body) {
152
- if (!body)
153
- return false;
154
- try {
155
- const parsed = JSON.parse(body);
156
- const id = parsed?.sys?.id;
157
- return id === 'NotFound' || id === 'AccessDenied';
158
- }
159
- catch {
160
- return false;
161
- }
162
- }
163
- async function request(url, options) {
164
- const headers = {
165
- Authorization: `Bearer ${options.token}`,
166
- 'Content-Type': 'application/json',
167
- 'X-Contentful-User-Agent': buildUserAgent(),
168
- };
169
- const init = {
170
- method: options.method ?? 'GET',
171
- headers,
172
- };
173
- if (options.body)
174
- init.body = options.body;
175
- return fetch(url, init);
176
- }
177
- export class ImportApiClient {
178
- host;
179
- token;
180
- spaceId;
181
- environmentId;
182
- retry;
183
- lastRequestId;
184
- getLastRequestId() {
185
- return this.lastRequestId;
186
- }
187
- noteRequestId(response) {
188
- const requestId = response.headers.get('x-contentful-request-id');
189
- if (requestId)
190
- this.lastRequestId = requestId;
191
- }
192
- constructor(opts) {
193
- this.host = toApiHost(opts.host);
194
- this.token = opts.cmaToken;
195
- this.spaceId = opts.spaceId;
196
- this.environmentId = opts.environmentId;
197
- const initialDelayMs = Math.max(0, opts.retry?.initialDelayMs ?? 250);
198
- this.retry = {
199
- maxAttempts: Math.max(1, Math.floor(opts.retry?.maxAttempts ?? 3)),
200
- initialDelayMs,
201
- maxDelayMs: Math.max(initialDelayMs, opts.retry?.maxDelayMs ?? 2000),
202
- sleep: opts.retry?.sleep ?? defaultSleep,
203
- };
204
- }
205
- base() {
206
- return `${this.host}/spaces/${this.spaceId}/environments/${this.environmentId}`;
207
- }
208
- headers() {
209
- return {
210
- Authorization: `Bearer ${this.token}`,
211
- 'Content-Type': 'application/json',
212
- 'X-Contentful-User-Agent': buildUserAgent(),
213
- };
214
- }
215
- async requestWithRetry(phase, errorPrefix, makeCall, context = {}) {
216
- const debug = getDebugLogger();
217
- const startedAt = Date.now();
218
- for (let attempt = 1; attempt <= this.retry.maxAttempts; attempt++) {
219
- let result;
220
- try {
221
- result = await makeCall();
222
- }
223
- catch (error) {
224
- if (attempt === this.retry.maxAttempts) {
225
- const body = `The request failed after ${attempt} attempts because of a network error: ${errorMessage(error)}. ` +
226
- 'Check your connection and try again.';
227
- debug.event('apply', `${phase}.error`, {
228
- ...context,
229
- attempt,
230
- maxAttempts: this.retry.maxAttempts,
231
- status: 0,
232
- reason: 'transport_retry_exhausted',
233
- durationMs: Date.now() - startedAt,
234
- });
235
- throw new ApiError(`${errorPrefix} 0`, 0, body);
236
- }
237
- const delayMs = Math.min(this.retry.initialDelayMs * 2 ** (attempt - 1), this.retry.maxDelayMs);
238
- debug.event('apply', `${phase}.retry`, {
239
- ...context,
240
- attempt,
241
- maxAttempts: this.retry.maxAttempts,
242
- status: 0,
243
- reason: 'transport_error',
244
- delayMs,
245
- });
246
- await this.retry.sleep(delayMs);
247
- continue;
248
- }
249
- if (!isTransientStatus(result.response.status)) {
250
- this.noteRequestId(result.response);
251
- return result;
252
- }
253
- if (attempt === this.retry.maxAttempts) {
254
- const body = stringifyError(result.error);
255
- const guidance = `The ${phase} request failed after ${attempt} attempts because the service remained unavailable. ` +
256
- 'Wait a moment and try again.';
257
- debug.event('apply', `${phase}.error`, {
258
- ...context,
259
- attempt,
260
- maxAttempts: this.retry.maxAttempts,
261
- status: result.response.status,
262
- reason: 'retry_exhausted',
263
- durationMs: Date.now() - startedAt,
264
- bodyHead: body.slice(0, 2000),
265
- });
266
- throw new ApiError(`${errorPrefix} ${result.response.status}`, result.response.status, body, guidance);
267
- }
268
- const backoffMs = Math.min(this.retry.initialDelayMs * 2 ** (attempt - 1), this.retry.maxDelayMs);
269
- const delayMs = retryAfterMs(result.response) ?? backoffMs;
270
- debug.event('apply', `${phase}.retry`, {
271
- ...context,
272
- attempt,
273
- maxAttempts: this.retry.maxAttempts,
274
- status: result.response.status,
275
- reason: 'http_5xx',
276
- delayMs,
277
- });
278
- await this.retry.sleep(delayMs);
279
- }
280
- throw new Error('Retry attempts exhausted');
281
- }
282
- async validateToken() {
283
- // /users/me is the canonical token-validity endpoint — avoids space-membership
284
- // false positives that don't apply to the design-systems API authorization path.
285
- const url = `${this.host}/users/me`;
286
- const res = await request(url, { token: this.token });
287
- this.noteRequestId(res);
288
- if (res.status === 401) {
289
- throw new ApiError('CMA token is invalid or revoked', res.status, await res.text());
290
- }
291
- if (!res.ok) {
292
- throw new ApiError(`unexpected error validating token: ${res.status}`, res.status, await res.text());
293
- }
294
- await this.checkPreflight();
295
- }
296
- // Probe the sources-api preflight endpoint to catch APS-denied users up-front,
297
- // rather than letting them run a full apply flow that only fails at the write.
298
- // The endpoint gates Component + DesignToken permissions and returns:
299
- // 200 → user has apply permissions, proceed
300
- // 403 { sys.id: AccessDenied } → user lacks write; surface via error-parser
301
- // 404 { sys.id: NotFound } → user lacks read (APS hides existence)
302
- // 404 without a NotFound envelope → older backend deployment; treat as pass
303
- // 5xx / network → backend flake; treat as pass, do not block
304
- async checkPreflight() {
305
- const url = `${this.host}/spaces/${this.spaceId}/environments/${this.environmentId}/design_systems/preflight`;
306
- let res;
307
- try {
308
- res = await request(url, { token: this.token });
309
- }
310
- catch {
311
- return; // network error — don't block; the real call will surface it
312
- }
313
- this.noteRequestId(res);
314
- if (res.ok)
315
- return;
316
- if (res.status >= 500)
317
- return; // backend flake — don't block
318
- const body = await res.text();
319
- if (res.status === 404 && !isApsDenialBody(body))
320
- return; // legacy backend without the endpoint
321
- throw new ApiError(`preflight failed: ${res.status}`, res.status, body);
322
- }
323
- async previewImport(manifest, allowDeletions = false) {
324
- const debug = getDebugLogger();
325
- const startedAt = Date.now();
326
- debug.event('apply', 'preview.request', {
327
- url: `${this.base()}/design_systems/imports/preview`,
328
- componentCount: manifest.components?.length ?? 0,
329
- tokenCount: manifest.designTokens?.length ?? 0,
330
- allowDeletions,
331
- });
332
- const result = await this.requestWithRetry('preview', PREVIEW_ERROR_PREFIX, () => designSystemImportSourcelessPreview({
333
- baseUrl: this.host,
334
- headers: this.headers(),
335
- path: { spaceId: this.spaceId, environmentId: this.environmentId },
336
- body: { ...manifest, allowDeletions },
337
- parseAs: 'json',
338
- }));
339
- if (!result.response.ok) {
340
- const body = stringifyError(result.error);
341
- debug.event('apply', 'preview.error', {
342
- status: result.response.status,
343
- durationMs: Date.now() - startedAt,
344
- bodyHead: body.slice(0, 2000),
345
- });
346
- throw new ApiError(`${PREVIEW_ERROR_PREFIX} ${result.response.status}`, result.response.status, body);
347
- }
348
- const parsed = result.data;
349
- debug.event('apply', 'preview.ok', { status: result.response.status, durationMs: Date.now() - startedAt });
350
- return sanitizePreviewResponse(parsed);
351
- }
352
- async applyImport(manifest, options) {
353
- const { acknowledgeBreakingChanges, allowDeletions = false } = options;
354
- const debug = getDebugLogger();
355
- const startedAt = Date.now();
356
- debug.event('apply', 'apply.request', {
357
- url: `${this.base()}/design_systems/imports/apply`,
358
- acknowledgeBreakingChanges,
359
- allowDeletions,
360
- });
361
- let result;
362
- try {
363
- result = await designSystemImportApply({
364
- baseUrl: this.host,
365
- headers: this.headers(),
366
- path: { spaceId: this.spaceId, environmentId: this.environmentId },
367
- body: { ...manifest, acknowledgeBreakingChanges, allowDeletions },
368
- parseAs: 'json',
369
- });
370
- }
371
- catch (error) {
372
- const body = `The apply request was not retried because its outcome is unknown and retrying could start a duplicate operation. ` +
373
- `Check for an existing operation before trying again. Cause: ${errorMessage(error)}`;
374
- debug.event('apply', 'apply.error', {
375
- status: 0,
376
- durationMs: Date.now() - startedAt,
377
- reason: 'transport_error_not_retried',
378
- });
379
- throw new ApiError(`${APPLY_ERROR_PREFIX} 0`, 0, body);
380
- }
381
- if (!result.response.ok) {
382
- const body = stringifyError(result.error);
383
- const guidance = isTransientStatus(result.response.status)
384
- ? 'The apply request was not retried because the server may already have started an operation and retrying could create a duplicate. Check for an existing operation before trying again.'
385
- : undefined;
386
- debug.event('apply', 'apply.error', {
387
- status: result.response.status,
388
- durationMs: Date.now() - startedAt,
389
- bodyHead: body.slice(0, 2000),
390
- });
391
- throw new ApiError(`${APPLY_ERROR_PREFIX} ${result.response.status}`, result.response.status, body, guidance);
392
- }
393
- const parsed = result.data;
394
- debug.event('apply', 'apply.accepted', {
395
- status: result.response.status,
396
- operationId: parsed.sys?.id,
397
- durationMs: Date.now() - startedAt,
398
- });
399
- return parsed;
400
- }
401
- async pollOperation(operationId, opts = {}) {
402
- const intervalMs = opts.intervalMs ?? 2000;
403
- const maxIntervalMs = opts.maxIntervalMs ?? Math.round(intervalMs * 2.5);
404
- const maxAttempts = opts.maxAttempts ?? 150;
405
- const terminalStatuses = new Set(['succeeded', 'partial', 'failed']);
406
- for (let attempt = 0; attempt < maxAttempts; attempt++) {
407
- const result = await this.requestWithRetry('poll', 'poll failed:', () => designSystemImportGetOperation({
408
- baseUrl: this.host,
409
- headers: this.headers(),
410
- path: { spaceId: this.spaceId, environmentId: this.environmentId, operationId },
411
- parseAs: 'json',
412
- }), { operationId });
413
- if (!result.response.ok) {
414
- throw new ApiError(`poll failed: ${result.response.status}`, result.response.status, stringifyError(result.error));
415
- }
416
- const op = result.data;
417
- opts.onProgress?.(op);
418
- getDebugLogger().event('apply', 'poll.tick', {
419
- operationId,
420
- attempt,
421
- status: op.sys.status,
422
- });
423
- if (terminalStatuses.has(op.sys.status)) {
424
- getDebugLogger().event('apply', 'poll.terminal', {
425
- operationId,
426
- attempt,
427
- status: op.sys.status,
428
- });
429
- return op;
430
- }
431
- if (attempt < maxAttempts - 1) {
432
- const progress = maxAttempts > 1 ? attempt / (maxAttempts - 1) : 0;
433
- const baseDelay = intervalMs + (maxIntervalMs - intervalMs) * progress;
434
- const jitter = Math.random() * baseDelay * 0.15;
435
- await new Promise((resolve) => setTimeout(resolve, baseDelay + jitter));
436
- }
437
- }
438
- throw new Error(`Operation ${operationId} timed out after ${maxAttempts} attempts`);
439
- }
440
- }