@contentful/experience-design-system-cli 2.23.2-dev-build-8b2cfc9.0 → 2.23.2-dev-build-09d87bb.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 (55) hide show
  1. package/README.md +2 -24
  2. package/dist/package.json +1 -2
  3. package/dist/src/analyze/command.js +0 -4
  4. package/dist/src/analyze/select/tui/App.js +4 -1
  5. package/dist/src/apply/api-client.d.ts +5 -5
  6. package/dist/src/apply/api-client.js +8 -18
  7. package/dist/src/apply/command.js +32 -32
  8. package/dist/src/apply/preview-utils.d.ts +5 -1
  9. package/dist/src/apply/preview-utils.js +5 -1
  10. package/dist/src/apply/tui/ServerApplyView.d.ts +6 -3
  11. package/dist/src/apply/tui/ServerApplyView.js +15 -6
  12. package/dist/src/apply/tui/ServerPreviewView.d.ts +2 -1
  13. package/dist/src/apply/tui/ServerPreviewView.js +3 -3
  14. package/dist/src/import/command.js +6 -1
  15. package/dist/src/import/orchestrator.d.ts +1 -0
  16. package/dist/src/import/orchestrator.js +3 -6
  17. package/dist/src/import/tui/WizardApp.d.ts +2 -1
  18. package/dist/src/import/tui/WizardApp.js +8 -8
  19. package/dist/src/import/tui/final-review-host.d.ts +2 -1
  20. package/dist/src/import/tui/final-review-host.js +2 -2
  21. package/dist/src/import/tui/runLivePreview.d.ts +3 -0
  22. package/dist/src/import/tui/runLivePreview.js +4 -1
  23. package/dist/src/import/tui/steps/GenerateReviewStep.d.ts +2 -1
  24. package/dist/src/import/tui/steps/GenerateReviewStep.js +3 -3
  25. package/dist/src/import/tui/steps/WizardPreviewStep.d.ts +6 -2
  26. package/dist/src/import/tui/steps/WizardPreviewStep.js +12 -3
  27. package/dist/src/import/tui/useFinalizePreview.d.ts +2 -0
  28. package/dist/src/import/tui/useFinalizePreview.js +0 -0
  29. package/dist/src/import/tui/useLivePreview.d.ts +2 -0
  30. package/dist/src/import/tui/useLivePreview.js +1 -0
  31. package/dist/src/index.js +1 -5
  32. package/dist/src/lib/command-options.d.ts +1 -0
  33. package/dist/src/lib/command-options.js +3 -0
  34. package/dist/src/program.js +1 -8
  35. package/dist/src/runs/modify-launcher.d.ts +2 -0
  36. package/dist/src/runs/modify-launcher.js +2 -0
  37. package/dist/src/runs/push-helpers.d.ts +1 -0
  38. package/dist/src/runs/push-helpers.js +3 -0
  39. package/dist/src/runs/replay-helpers.d.ts +4 -0
  40. package/dist/src/runs/replay-helpers.js +3 -0
  41. package/package.json +5 -6
  42. package/dist/src/analytics/apply.d.ts +0 -6
  43. package/dist/src/analytics/apply.js +0 -30
  44. package/dist/src/analytics/client.d.ts +0 -3
  45. package/dist/src/analytics/client.js +0 -39
  46. package/dist/src/analytics/index.d.ts +0 -6
  47. package/dist/src/analytics/index.js +0 -5
  48. package/dist/src/analytics/normalize.d.ts +0 -3
  49. package/dist/src/analytics/normalize.js +0 -17
  50. package/dist/src/analytics/os.d.ts +0 -2
  51. package/dist/src/analytics/os.js +0 -13
  52. package/dist/src/analytics/tracker.d.ts +0 -19
  53. package/dist/src/analytics/tracker.js +0 -157
  54. package/dist/src/analytics/types.d.ts +0 -29
  55. package/dist/src/analytics/types.js +0 -1
@@ -138,7 +138,7 @@ function logStep(entry) {
138
138
  appendFileSync(WIZARD_LOG, line);
139
139
  getDebugLogger().event('wizard', 'step', entry);
140
140
  }
141
- export function WizardApp({ initialSpaceId = '', initialEnvironmentId = 'master', initialCmaToken = '', initialHost, initialAgent, initialModel, initialProjectPath, host, autoAcceptScope = false, autoRejectCycles = false, compositionMode = 'atomic', compositionMap, compositionAgent = false, compositionAgentMode, compositionRefresh = false, generateMap, promptOverrides, noCache = false, autoFilter = true, livePreview = true, noPush = false, noSave = false, outDirOverride, onConflictMode, selectPromptPath, generatePromptPath, seedExtractSessionId, seedGenerateSessionId, seedTokenSessionId, seedTokensPath, initialStep, initialRawTokensPath, initialRuns, onRunPicked, } = {}) {
141
+ export function WizardApp({ initialSpaceId = '', initialEnvironmentId = 'master', initialCmaToken = '', initialHost, initialAgent, initialModel, initialProjectPath, host, autoAcceptScope = false, autoRejectCycles = false, compositionMode = 'atomic', compositionMap, compositionAgent = false, compositionAgentMode, compositionRefresh = false, generateMap, promptOverrides, noCache = false, autoFilter = true, livePreview = true, noPush = false, noSave = false, outDirOverride, onConflictMode, selectPromptPath, generatePromptPath, seedExtractSessionId, seedGenerateSessionId, seedTokenSessionId, seedTokensPath, initialStep, initialRawTokensPath, initialRuns, onRunPicked, allowDeletions = false, } = {}) {
142
142
  const defaultConfiguredHost = toConfiguredHost(host || process.env['EDS_HOST']) ?? DEFAULT_CONFIGURED_HOST;
143
143
  const resolveWizardHost = (hostValue) => hostValue || defaultConfiguredHost;
144
144
  const { stdout } = useStdout();
@@ -830,7 +830,7 @@ export function WizardApp({ initialSpaceId = '', initialEnvironmentId = 'master'
830
830
  tokens = await readTokensFromPath('tokens', tokensPath);
831
831
  }
832
832
  let manifest = buildManifest(components, tokens, { deleteAllComponents: allowEmptyDeleteAllRef.current });
833
- let preview = await client.previewImport(manifest);
833
+ let preview = await client.previewImport(manifest, allowDeletions);
834
834
  if (extractSessionId) {
835
835
  let needsRepreview = false;
836
836
  const db = openPipelineDb();
@@ -852,7 +852,7 @@ export function WizardApp({ initialSpaceId = '', initialEnvironmentId = 'master'
852
852
  if (needsRepreview) {
853
853
  components = loadCDFComponents(db, extractSessionId);
854
854
  manifest = buildManifest(components, tokens, { deleteAllComponents: allowEmptyDeleteAllRef.current });
855
- preview = await client.previewImport(manifest);
855
+ preview = await client.previewImport(manifest, allowDeletions);
856
856
  }
857
857
  }
858
858
  finally {
@@ -946,7 +946,7 @@ export function WizardApp({ initialSpaceId = '', initialEnvironmentId = 'master'
946
946
  });
947
947
  }
948
948
  };
949
- const runPush = async (manifest, spaceId, environmentId, cmaToken, host, acknowledgeBreakingChanges, preview) => {
949
+ const runPush = async (manifest, spaceId, environmentId, cmaToken, host, acknowledgeBreakingChanges, allowDeletions, preview) => {
950
950
  if (shouldRefusePush(state)) {
951
951
  update(buildSkippedPushTransition());
952
952
  return;
@@ -986,7 +986,7 @@ export function WizardApp({ initialSpaceId = '', initialEnvironmentId = 'master'
986
986
  environmentId,
987
987
  host: resolvedHost,
988
988
  });
989
- let operation = await client.applyImport(manifest, acknowledgeBreakingChanges);
989
+ let operation = await client.applyImport(manifest, { acknowledgeBreakingChanges, allowDeletions });
990
990
  try {
991
991
  logStep({
992
992
  applyResponse: {
@@ -1437,7 +1437,7 @@ export function WizardApp({ initialSpaceId = '', initialEnvironmentId = 'master'
1437
1437
  return (_jsx(RunningStep, { stepNumber: stepNum, totalSteps: totalSteps, title: "Generating definitions", description: `${formatAcceptanceSummary({ accepted: state.acceptedCount, autoRejected: state.autoRejectedCount })} ${state.agent} is mapping your TypeScript types to Contentful's CDF format.${hasTokens ? ' Using your design tokens for prop resolution.' : ''}`, detail: progressDetail }));
1438
1438
  }
1439
1439
  case 'final-review': {
1440
- return (_jsx(FinalReviewHost, { extractSessionId: state.extractSessionId, generatedCount: state.generatedCount, autoAccept: autoAcceptScope, compositionMode: compositionMode, livePreview: livePreview, spaceId: state.spaceId, environmentId: state.environmentId, cmaToken: state.cmaToken, host: state.host, tokensPath: state.tokensPath, initialFinalizeError: state.finalizeErrorBanner, onFinalize: (accepted, rejected, unresolved) => {
1440
+ return (_jsx(FinalReviewHost, { extractSessionId: state.extractSessionId, generatedCount: state.generatedCount, autoAccept: autoAcceptScope, compositionMode: compositionMode, livePreview: livePreview, spaceId: state.spaceId, environmentId: state.environmentId, cmaToken: state.cmaToken, host: state.host, tokensPath: state.tokensPath, initialFinalizeError: state.finalizeErrorBanner, allowDeletions: allowDeletions, onFinalize: (accepted, rejected, unresolved) => {
1441
1441
  process.stderr.write(`Accepted: ${accepted} Rejected: ${rejected} Unresolved: ${unresolved}\n`);
1442
1442
  let acceptedCount = accepted;
1443
1443
  const detectAcceptedCycles = () => {
@@ -1605,8 +1605,8 @@ export function WizardApp({ initialSpaceId = '', initialEnvironmentId = 'master'
1605
1605
  // present-but-empty (only $schema), so editing would dead-end on the
1606
1606
  // "No generated definitions found" screen.
1607
1607
  const editableComponentCount = Object.keys(state.manifest?.componentsManifest ?? {}).filter((k) => k !== '$schema').length;
1608
- return (_jsx(WizardPreviewStep, { preview: state.serverPreview, spaceId: state.spaceId, environmentId: state.environmentId, stepNumber: totalSteps, totalSteps: totalSteps, onConfirm: (acknowledge) => {
1609
- void runPush(state.manifest, state.spaceId, state.environmentId, state.cmaToken, state.host, acknowledge, state.serverPreview);
1608
+ return (_jsx(WizardPreviewStep, { preview: state.serverPreview, spaceId: state.spaceId, environmentId: state.environmentId, stepNumber: totalSteps, totalSteps: totalSteps, allowDeletions: allowDeletions, onConfirm: (acknowledge, deleteMissing) => {
1609
+ void runPush(state.manifest, state.spaceId, state.environmentId, state.cmaToken, state.host, acknowledge, deleteMissing, state.serverPreview);
1610
1610
  }, ...(editableComponentCount > 0 ? { onEdit: () => void runEditFromPreview() } : {}), onSaveFiles: () => {
1611
1611
  void startSaveFlow();
1612
1612
  }, onQuit: () => process.exit(0) }));
@@ -14,5 +14,6 @@ export type FinalReviewHostProps = {
14
14
  host?: string;
15
15
  tokensPath?: string;
16
16
  initialFinalizeError?: string | null;
17
+ allowDeletions?: boolean;
17
18
  };
18
- export declare function FinalReviewHost({ extractSessionId, generatedCount, autoAccept, compositionMode, onFinalize, onQuit, livePreview, spaceId, environmentId, cmaToken, host, tokensPath, initialFinalizeError, }: FinalReviewHostProps): React.ReactElement;
19
+ export declare function FinalReviewHost({ extractSessionId, generatedCount, autoAccept, compositionMode, onFinalize, onQuit, livePreview, spaceId, environmentId, cmaToken, host, tokensPath, initialFinalizeError, allowDeletions, }: FinalReviewHostProps): React.ReactElement;
@@ -4,7 +4,7 @@ import { PALETTE } from '../../analyze/select/tui/theme.js';
4
4
  import React from 'react';
5
5
  import { GenerateReviewStep } from './steps/GenerateReviewStep.js';
6
6
  import { AtomicGenerateReviewStep } from './steps/AtomicGenerateReviewStep.js';
7
- export function FinalReviewHost({ extractSessionId, generatedCount, autoAccept, compositionMode = 'atomic', onFinalize, onQuit, livePreview, spaceId, environmentId, cmaToken, host, tokensPath, initialFinalizeError, }) {
7
+ export function FinalReviewHost({ extractSessionId, generatedCount, autoAccept, compositionMode = 'atomic', onFinalize, onQuit, livePreview, spaceId, environmentId, cmaToken, host, tokensPath, initialFinalizeError, allowDeletions, }) {
8
8
  if (!extractSessionId) {
9
9
  return (_jsx(Box, { paddingX: 2, paddingY: 1, children: _jsx(Text, { color: PALETTE.error, children: "Error: no session ID \u2014 cannot load generated definitions." }) }));
10
10
  }
@@ -15,7 +15,7 @@ export function FinalReviewHost({ extractSessionId, generatedCount, autoAccept,
15
15
  // passes projectSlotGraph to FieldEditor and never walks closures/cycles, so
16
16
  // slot-composition editing and every hierarchy affordance stay absent.
17
17
  const StepComponent = compositionMode === 'atomic' ? AtomicGenerateReviewStep : GenerateReviewStep;
18
- return (_jsx(StepComponent, { extractSessionId: extractSessionId, onFinalize: onFinalize, onQuit: onQuit, livePreview: livePreview, spaceId: spaceId, environmentId: environmentId, cmaToken: cmaToken, host: host, tokensPath: tokensPath, initialFinalizeError: initialFinalizeError }));
18
+ return (_jsx(StepComponent, { extractSessionId: extractSessionId, onFinalize: onFinalize, onQuit: onQuit, livePreview: livePreview, spaceId: spaceId, environmentId: environmentId, cmaToken: cmaToken, host: host, tokensPath: tokensPath, initialFinalizeError: initialFinalizeError, ...(compositionMode !== 'atomic' ? { allowDeletions } : {}) }));
19
19
  }
20
20
  function FinalReviewAutoAccept({ generatedCount, onFinalize, }) {
21
21
  React.useEffect(() => {
@@ -26,6 +26,9 @@ export type RunLivePreviewOptions = {
26
26
  * full delete. Lets the Finalize dialog show exactly what the accepted push
27
27
  * would delete, independent of the session's on-disk generated rows. */
28
28
  acceptedKeys?: ReadonlySet<string>;
29
+ /** Forwarded verbatim to `previewImport`. Governs whether the response
30
+ * includes removed entities or a suppressed-count summary instead. */
31
+ allowDeletions?: boolean;
29
32
  };
30
33
  /**
31
34
  * Pure async helper used by `useLivePreview` to re-fire `previewImport` after a
@@ -60,7 +60,10 @@ export async function runLivePreview(opts) {
60
60
  timeoutHandle = setTimeout(() => reject(new TimeoutError()), timeoutMs);
61
61
  });
62
62
  try {
63
- const response = (await Promise.race([client.previewImport(manifest), timeoutPromise]));
63
+ const response = (await Promise.race([
64
+ client.previewImport(manifest, opts.allowDeletions === true),
65
+ timeoutPromise,
66
+ ]));
64
67
  if (process.env['EDS_VERBOSE']) {
65
68
  const durationMs = Date.now() - startedAt;
66
69
  try {
@@ -11,6 +11,7 @@ type GenerateReviewStepProps = {
11
11
  host?: string;
12
12
  tokensPath?: string;
13
13
  initialFinalizeError?: string | null;
14
+ allowDeletions?: boolean;
14
15
  };
15
16
  export declare function sortComponentsForSidebar<T extends {
16
17
  key: string;
@@ -33,4 +34,4 @@ export interface BreakingRow {
33
34
  }
34
35
  export declare function buildBreakingRows(breakingChanges: BreakingComponent[]): BreakingRow[];
35
36
  export declare function deriveBreakingChanges(response: ServerPreviewResponse): BreakingComponent[];
36
- export declare function GenerateReviewStep({ extractSessionId, onFinalize, onQuit, livePreview, spaceId, environmentId, cmaToken, host, tokensPath, initialFinalizeError, }: GenerateReviewStepProps): React.ReactElement;
37
+ export declare function GenerateReviewStep({ extractSessionId, onFinalize, onQuit, livePreview, spaceId, environmentId, cmaToken, host, tokensPath, initialFinalizeError, allowDeletions, }: GenerateReviewStepProps): React.ReactElement;
@@ -174,7 +174,7 @@ export function deriveBreakingChanges(response) {
174
174
  }
175
175
  return out;
176
176
  }
177
- export function GenerateReviewStep({ extractSessionId, onFinalize, onQuit, livePreview = true, spaceId = '', environmentId = '', cmaToken = '', host = '', tokensPath = '', initialFinalizeError = null, }) {
177
+ export function GenerateReviewStep({ extractSessionId, onFinalize, onQuit, livePreview = true, spaceId = '', environmentId = '', cmaToken = '', host = '', tokensPath = '', initialFinalizeError = null, allowDeletions = false, }) {
178
178
  const { stdout } = useStdout();
179
179
  const terminalWidth = stdout?.columns ?? 80;
180
180
  const [components, setComponents] = useState([]);
@@ -271,9 +271,8 @@ export function GenerateReviewStep({ extractSessionId, onFinalize, onQuit, liveP
271
271
  cmaToken,
272
272
  host,
273
273
  onResult: handleLivePreviewResult,
274
- // With nothing accepted, preview the delete-all diff so the review UI shows
275
- // which existing components a push would remove (instead of an empty preview).
276
274
  deleteAllComponents: acceptedCountForPreview === 0,
275
+ allowDeletions,
277
276
  });
278
277
  const SPINNER_FRAMES = '⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏';
279
278
  const [spinnerTick, setSpinnerTick] = useState(0);
@@ -352,6 +351,7 @@ export function GenerateReviewStep({ extractSessionId, onFinalize, onQuit, liveP
352
351
  cmaToken,
353
352
  host,
354
353
  acceptedKeys: new Set(components.filter((c) => c.status === 'accepted').map((c) => c.key)),
354
+ allowDeletions,
355
355
  });
356
356
  const handleFinalizeConfirm = () => {
357
357
  const acceptedCount = components.filter((c) => c.status === 'accepted').length;
@@ -12,10 +12,14 @@ type WizardPreviewStepProps = {
12
12
  environmentId: string;
13
13
  stepNumber: number;
14
14
  totalSteps: number;
15
- onConfirm: (acknowledge: boolean) => void;
15
+ /** The value the preview was actually fetched with — not a hint. When
16
+ * `false`, the server never returned removed entities, so there is
17
+ * nothing to render item-by-item or toggle over. */
18
+ allowDeletions?: boolean;
19
+ onConfirm: (acknowledge: boolean, allowDeletions: boolean) => void;
16
20
  onEdit?: () => void;
17
21
  onSaveFiles?: () => void;
18
22
  onQuit: () => void;
19
23
  };
20
- export declare function WizardPreviewStep({ preview, spaceId, environmentId, stepNumber, totalSteps, onConfirm, onEdit, onSaveFiles, onQuit, }: WizardPreviewStepProps): React.ReactElement;
24
+ export declare function WizardPreviewStep({ preview, spaceId, environmentId, stepNumber, totalSteps, allowDeletions: fetchedAllowDeletions, onConfirm, onEdit, onSaveFiles, onQuit, }: WizardPreviewStepProps): React.ReactElement;
21
25
  export {};
@@ -89,10 +89,14 @@ export function buildPreviewDiffLines(preview) {
89
89
  }
90
90
  return lines;
91
91
  }
92
- export function WizardPreviewStep({ preview, spaceId, environmentId, stepNumber, totalSteps, onConfirm, onEdit, onSaveFiles, onQuit, }) {
92
+ export function WizardPreviewStep({ preview, spaceId, environmentId, stepNumber, totalSteps, allowDeletions: fetchedAllowDeletions = false, onConfirm, onEdit, onSaveFiles, onQuit, }) {
93
93
  const breakingWithImpact = hasBreakingChangesWithImpact(preview);
94
94
  const [diffExpanded, setDiffExpanded] = useState(false);
95
95
  const [scrollOffset, setScrollOffset] = useState(0);
96
+ // Local state exists only to let the user opt OUT of a deletion the fetch
97
+ // already surfaced — it can never turn true when the fetch used false,
98
+ // because there's nothing in `preview` to reveal in that case.
99
+ const [allowDeletions, setAllowDeletions] = useState(fetchedAllowDeletions);
96
100
  const { stdout } = useStdout();
97
101
  const terminalRows = stdout?.rows ?? 40;
98
102
  const viewportHeight = Math.max(terminalRows - 14, 10);
@@ -105,9 +109,14 @@ export function WizardPreviewStep({ preview, spaceId, environmentId, stepNumber,
105
109
  }));
106
110
  }, [diffExpanded, preview]);
107
111
  const maxScroll = Math.max(0, allDiffLines.length - viewportHeight);
112
+ const removedCount = preview.components.removed.length + preview.tokens.removed.length;
108
113
  useImmediateInput((input, key) => {
109
114
  if (key.return) {
110
- onConfirm(breakingWithImpact);
115
+ onConfirm(breakingWithImpact, allowDeletions);
116
+ return;
117
+ }
118
+ if ((input === 'x' || input === 'X') && fetchedAllowDeletions && removedCount > 0) {
119
+ setAllowDeletions((prev) => !prev);
111
120
  return;
112
121
  }
113
122
  if (input === 'd' || input === 'D') {
@@ -156,5 +165,5 @@ export function WizardPreviewStep({ preview, spaceId, environmentId, stepNumber,
156
165
  })] })), components.changed.length > 0 && (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { gap: 1, children: [_jsx(Text, { color: PALETTE.warning, children: " \uFF5E" }), _jsxs(Text, { children: [components.changed.length, " will be updated"] })] }), components.changed.map((item, i) => {
157
166
  const isBreaking = item.changeClassification?.classification === 'breaking';
158
167
  return (_jsxs(Text, { color: isBreaking ? PALETTE.error : PALETTE.warning, children: [' ', isBreaking ? '⚠' : '~', " ", item.current.name] }, `chg-${i}`));
159
- })] })), components.removed.length > 0 && (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { gap: 1, children: [_jsx(Text, { color: PALETTE.error, children: " \u2717" }), _jsxs(Text, { children: [components.removed.length, " will be removed"] })] }), components.removed.map((item, i) => (_jsxs(Text, { color: PALETTE.error, children: [' ', "\u2717 ", item.name] }, `rm-${i}`)))] })), components.unchanged.length > 0 && (_jsxs(Box, { gap: 1, children: [_jsx(Text, { dimColor: true, children: " \u00B7" }), _jsxs(Text, { dimColor: true, children: [components.unchanged.length, " unchanged"] })] }))] })), hasTokens && (_jsxs(Box, { flexDirection: "column", gap: 0, children: [_jsx(Box, { gap: 1, marginTop: 1, children: _jsx(Text, { bold: true, dimColor: true, children: "Design Tokens" }) }), tokens.new.length > 0 && (_jsxs(Box, { gap: 1, children: [_jsx(Text, { color: PALETTE.success, children: " \uFF0B" }), _jsxs(Text, { children: [tokens.new.length, " will be created"] })] })), tokens.changed.length > 0 && (_jsxs(Box, { gap: 1, children: [_jsx(Text, { color: PALETTE.warning, children: " \uFF5E" }), _jsxs(Text, { children: [tokens.changed.length, " will be updated"] })] })), tokens.removed.length > 0 && (_jsxs(Box, { gap: 1, children: [_jsx(Text, { color: PALETTE.error, children: " \u2717" }), _jsxs(Text, { children: [tokens.removed.length, " will be removed"] })] })), tokens.unchanged.length > 0 && (_jsxs(Box, { gap: 1, children: [_jsx(Text, { dimColor: true, children: " \u00B7" }), _jsxs(Text, { dimColor: true, children: [tokens.unchanged.length, " unchanged"] })] }))] })), diffExpanded && allDiffLines.length > 0 && (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { dimColor: true, children: '─'.repeat(40) }), _jsxs(Text, { dimColor: true, children: [' ', "Diff (", allDiffLines.length, " lines) \u2014 line ", scrollOffset + 1, "\u2013", Math.min(scrollOffset + viewportHeight, allDiffLines.length), " of ", allDiffLines.length] }), _jsx(Box, { flexDirection: "column", children: allDiffLines.slice(scrollOffset, scrollOffset + viewportHeight).map((line) => (_jsx(Box, { children: line.element }, line.key))) }), maxScroll > 0 && _jsx(Text, { dimColor: true, children: " \u2195 j/k to scroll, f/b to page" })] }))] })) : (_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 })] }), breakingWithImpact && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: PALETTE.error, bold: true, children: "\u26A0 Breaking changes will affect downstream entities. Press Enter to acknowledge and apply." }) })), _jsxs(Box, { gap: 3, marginTop: 1, children: [_jsx(Text, { dimColor: true, children: "[Enter] Push to Contentful" }), _jsxs(Text, { dimColor: true, children: ["[d] ", diffExpanded ? 'Hide' : 'Show', " diff"] }), diffExpanded && _jsx(Text, { dimColor: true, children: "[j/k] Scroll [f/b] Page" }), onEdit && _jsx(Text, { dimColor: true, children: "[e] Edit definitions" }), onSaveFiles && _jsx(Text, { dimColor: true, children: "[s] Save files instead" }), _jsx(Text, { dimColor: true, children: "[q] Cancel" })] })] }));
168
+ })] })), components.removed.length > 0 && (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { gap: 1, children: [_jsx(Text, { color: allowDeletions ? PALETTE.error : PALETTE.warning, children: allowDeletions ? ' ✗' : ' ⊘' }), _jsxs(Text, { children: [components.removed.length, " will be ", allowDeletions ? 'deleted' : 'skipped'] })] }), components.removed.map((item, i) => (_jsxs(Text, { color: allowDeletions ? PALETTE.error : PALETTE.warning, dimColor: !allowDeletions, children: [' ', allowDeletions ? '✗' : '⊘', " ", item.name] }, `rm-${i}`)))] })), components.unchanged.length > 0 && (_jsxs(Box, { gap: 1, children: [_jsx(Text, { dimColor: true, children: " \u00B7" }), _jsxs(Text, { dimColor: true, children: [components.unchanged.length, " unchanged"] })] }))] })), hasTokens && (_jsxs(Box, { flexDirection: "column", gap: 0, children: [_jsx(Box, { gap: 1, marginTop: 1, children: _jsx(Text, { bold: true, dimColor: true, children: "Design Tokens" }) }), tokens.new.length > 0 && (_jsxs(Box, { gap: 1, children: [_jsx(Text, { color: PALETTE.success, children: " \uFF0B" }), _jsxs(Text, { children: [tokens.new.length, " will be created"] })] })), tokens.changed.length > 0 && (_jsxs(Box, { gap: 1, children: [_jsx(Text, { color: PALETTE.warning, children: " \uFF5E" }), _jsxs(Text, { children: [tokens.changed.length, " will be updated"] })] })), tokens.removed.length > 0 && (_jsxs(Box, { gap: 1, children: [_jsx(Text, { color: allowDeletions ? PALETTE.error : PALETTE.warning, children: allowDeletions ? ' ✗' : ' ⊘' }), _jsxs(Text, { children: [tokens.removed.length, " will be ", allowDeletions ? 'deleted' : 'skipped'] })] })), tokens.unchanged.length > 0 && (_jsxs(Box, { gap: 1, children: [_jsx(Text, { dimColor: true, children: " \u00B7" }), _jsxs(Text, { dimColor: true, children: [tokens.unchanged.length, " unchanged"] })] }))] })), diffExpanded && allDiffLines.length > 0 && (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { dimColor: true, children: '─'.repeat(40) }), _jsxs(Text, { dimColor: true, children: [' ', "Diff (", allDiffLines.length, " lines) \u2014 line ", scrollOffset + 1, "\u2013", Math.min(scrollOffset + viewportHeight, allDiffLines.length), " of ", allDiffLines.length] }), _jsx(Box, { flexDirection: "column", children: allDiffLines.slice(scrollOffset, scrollOffset + viewportHeight).map((line) => (_jsx(Box, { children: line.element }, line.key))) }), maxScroll > 0 && _jsx(Text, { dimColor: true, children: " \u2195 j/k to scroll, f/b to page" })] }))] })) : (_jsx(Text, { dimColor: true, children: "Nothing to push \u2014 everything is already up to date." })), !fetchedAllowDeletions && (_jsxs(Box, { gap: 1, children: [_jsx(Text, { color: PALETTE.warning, children: " \u2298" }), _jsx(Text, { children: "Deletions are hidden by default \u2014 rerun with --allow-deletions to see them." })] })), _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 })] }), breakingWithImpact && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: PALETTE.error, bold: true, children: "\u26A0 Breaking changes will affect downstream entities. Press Enter to acknowledge and apply." }) })), allowDeletions && removedCount > 0 && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: PALETTE.error, bold: true, children: ["\u26A0 ", removedCount, " missing ", removedCount === 1 ? 'entity' : 'entities', " will be permanently deleted. Press Enter to confirm."] }) })), _jsxs(Box, { gap: 3, marginTop: 1, children: [_jsx(Text, { dimColor: true, children: "[Enter] Push to Contentful" }), _jsxs(Text, { dimColor: true, children: ["[d] ", diffExpanded ? 'Hide' : 'Show', " diff"] }), diffExpanded && _jsx(Text, { dimColor: true, children: "[j/k] Scroll [f/b] Page" }), onEdit && _jsx(Text, { dimColor: true, children: "[e] Edit definitions" }), onSaveFiles && _jsx(Text, { dimColor: true, children: "[s] Save files instead" }), fetchedAllowDeletions && removedCount > 0 && (_jsxs(Text, { dimColor: true, children: ["[x] ", allowDeletions ? '[✓]' : '[ ]', " Allow deletions"] })), _jsx(Text, { dimColor: true, children: "[q] Cancel" })] })] }));
160
169
  }
@@ -11,6 +11,8 @@ export type UseFinalizePreviewOptions = {
11
11
  host: string;
12
12
  /** Component keys the operator has accepted; drives the scoped preview. */
13
13
  acceptedKeys: ReadonlySet<string>;
14
+ /** Forwarded verbatim to `runLivePreview`/`previewImport`. */
15
+ allowDeletions?: boolean;
14
16
  };
15
17
  export type UseFinalizePreviewReturn = {
16
18
  status: FinalizePreviewStatus;
@@ -12,6 +12,8 @@ export type UseLivePreviewOptions = {
12
12
  /** Preview an empty-but-present manifest (delete-all diff) when nothing is
13
13
  * accepted, so the final-review UI can show what a push would delete. */
14
14
  deleteAllComponents?: boolean;
15
+ /** Forwarded verbatim to `runLivePreview`/`previewImport`. */
16
+ allowDeletions?: boolean;
15
17
  };
16
18
  export type LivePreviewStatus = 'idle' | 'running';
17
19
  export type UseLivePreviewReturn = {
@@ -82,6 +82,7 @@ export function useLivePreview(opts) {
82
82
  host: current.host,
83
83
  generation,
84
84
  deleteAllComponents: current.deleteAllComponents === true,
85
+ allowDeletions: current.allowDeletions === true,
85
86
  });
86
87
  // Discard stale responses (generation tag).
87
88
  if (result.generation !== latestRef.current)
package/dist/src/index.js CHANGED
@@ -1,11 +1,7 @@
1
1
  import { createProgram } from './program.js';
2
- import { failActiveCommand } from './analytics/index.js';
3
2
  createProgram()
4
3
  .parseAsync()
5
- .catch(async (err) => {
6
- await failActiveCommand({
7
- error_name: err instanceof Error ? err.name : 'Error',
8
- });
4
+ .catch((err) => {
9
5
  process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}\n`);
10
6
  process.exit(1);
11
7
  });
@@ -3,3 +3,4 @@ export declare function addArtifactInputOptions(cmd: Command): Command;
3
3
  export declare function addContentfulTargetOptions(cmd: Command): Command;
4
4
  export declare function addCompositionOptions(cmd: Command): Command;
5
5
  export declare function addSelectionOptions(cmd: Command): Command;
6
+ export declare function addAllowDeletionsOption(cmd: Command): Command;
@@ -25,3 +25,6 @@ export function addSelectionOptions(cmd) {
25
25
  .option('--select <pattern>', 'Select entities by ID pattern (repeatable)', collectOptionValue, [])
26
26
  .option('--deselect <pattern>', 'Deselect entities by ID pattern (repeatable)', collectOptionValue, []);
27
27
  }
28
+ export function addAllowDeletionsOption(cmd) {
29
+ return cmd.option('--allow-deletions', 'Allow the push to delete remote ComponentTypes/DesignTokens missing from the manifest (default: skip them)');
30
+ }
@@ -12,7 +12,6 @@ import { registerImportCommand } from './import/command.js';
12
12
  import { registerSetupCommand } from './setup/command.js';
13
13
  import { registerRunsCommand } from './runs/ls-command.js';
14
14
  import { beginCommand } from './lib/debug-preamble.js';
15
- import { completeActiveCommand, noteCommandStart, registerAnalyticsExitHook } from './analytics/index.js';
16
15
  const require = createRequire(import.meta.url);
17
16
  const pkg = require('../package.json');
18
17
  /**
@@ -74,7 +73,6 @@ export function createProgram() {
74
73
  // and a bright-green "debug logs at <path>" banner is printed to stderr.
75
74
  program.option('--debug', 'Write a JSONL trace of every decision to ~/.contentful/experience-design-system-cli/debug/');
76
75
  program.option('--no-debug', 'Force debug logging off (overrides EDSI_DEBUG and persisted setup preference)');
77
- registerAnalyticsExitHook();
78
76
  program.hook('preAction', async (_thisCommand, actionCommand) => {
79
77
  // Merge opts from actionCommand and all ancestors — root-level --debug
80
78
  // set alongside a subcommand ends up on the root command's opts, not the
@@ -91,12 +89,7 @@ export function createProgram() {
91
89
  const chain = [];
92
90
  for (let c = actionCommand; c && c.parent; c = c.parent)
93
91
  chain.unshift(c.name());
94
- const commandChain = chain.join(' ') || actionCommand.name();
95
- noteCommandStart(commandChain);
96
- await beginCommand(commandChain, { ...(debug !== undefined ? { debug } : {}) });
97
- });
98
- program.hook('postAction', async () => {
99
- await completeActiveCommand();
92
+ await beginCommand(chain.join(' ') || actionCommand.name(), { ...(debug !== undefined ? { debug } : {}) });
100
93
  });
101
94
  return program;
102
95
  }
@@ -26,5 +26,7 @@ export type ModifyLauncherInput = {
26
26
  initialHost?: string;
27
27
  /** Pre-fill CMA token (from credentials.json / env). */
28
28
  initialCmaToken?: string;
29
+ /** From `--allow-deletions` flag. Forwarded to wizard's push step. */
30
+ allowDeletions?: boolean;
29
31
  };
30
32
  export declare function launchModifyWizard(input: ModifyLauncherInput): Promise<void>;
@@ -35,6 +35,8 @@ export async function launchModifyWizard(input) {
35
35
  props.initialHost = input.initialHost;
36
36
  if (input.initialCmaToken)
37
37
  props.initialCmaToken = input.initialCmaToken;
38
+ if (input.allowDeletions !== undefined)
39
+ props.allowDeletions = input.allowDeletions;
38
40
  const { waitUntilExit } = render(createElement(WizardApp, props));
39
41
  await waitUntilExit();
40
42
  }
@@ -15,6 +15,7 @@ export type PushSessionOptions = {
15
15
  environmentId: string;
16
16
  cmaToken: string;
17
17
  host?: string;
18
+ allowDeletions?: boolean;
18
19
  };
19
20
  /**
20
21
  * Push a recorded pipeline.db session's components + tokens to Contentful by
@@ -60,6 +60,9 @@ export async function pushRunSession(opts) {
60
60
  if (opts.host) {
61
61
  args.push('--host', opts.host);
62
62
  }
63
+ if (opts.allowDeletions) {
64
+ args.push('--allow-deletions');
65
+ }
63
66
  const r = await runCli(args);
64
67
  if (r.exitCode === 0)
65
68
  return { ok: true };
@@ -16,6 +16,8 @@ export type ReplayRunOptions = {
16
16
  interactive?: boolean;
17
17
  /** When true, bypass the source/saved-file staleness check. */
18
18
  force?: boolean;
19
+ /** From `--allow-deletions` flag. Forwarded verbatim to the pushed session's apply request. */
20
+ allowDeletions?: boolean;
19
21
  /**
20
22
  * Test seam: replace the interactive prompt with a deterministic resolver.
21
23
  * The CLI surface never sets this; only used by tests.
@@ -57,6 +59,8 @@ export type ModifyRunOptions = {
57
59
  outDir?: string;
58
60
  /** When true, bypass the source/saved-file staleness check. */
59
61
  force?: boolean;
62
+ /** From `--allow-deletions` flag. Forwarded through the modify wizard's push step. */
63
+ allowDeletions?: boolean;
60
64
  };
61
65
  /**
62
66
  * Re-open the wizard with a prior run's extract/generate session pre-populated
@@ -83,6 +83,7 @@ export async function replayRun(opts) {
83
83
  environmentId,
84
84
  cmaToken,
85
85
  ...(host ? { host } : {}),
86
+ ...(opts.allowDeletions ? { allowDeletions: true } : {}),
86
87
  });
87
88
  if (!result.ok) {
88
89
  throw new Error(result.error);
@@ -98,6 +99,7 @@ export async function replayRun(opts) {
98
99
  environmentId,
99
100
  cmaToken,
100
101
  ...(host ? { host } : {}),
102
+ ...(opts.allowDeletions ? { allowDeletions: true } : {}),
101
103
  });
102
104
  if (!tokenResult.ok) {
103
105
  throw new Error(tokenResult.error);
@@ -157,5 +159,6 @@ export async function modifyRun(opts) {
157
159
  ...(mergedEnvironmentId ? { initialEnvironmentId: mergedEnvironmentId } : {}),
158
160
  ...(mergedHost ? { initialHost: mergedHost } : {}),
159
161
  ...(mergedCmaToken ? { initialCmaToken: mergedCmaToken } : {}),
162
+ ...(opts.allowDeletions !== undefined ? { allowDeletions: opts.allowDeletions } : {}),
160
163
  });
161
164
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contentful/experience-design-system-cli",
3
- "version": "2.23.2-dev-build-8b2cfc9.0",
3
+ "version": "2.23.2-dev-build-09d87bb.0",
4
4
  "description": "Contentful Experiences design system import CLI",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -28,16 +28,15 @@
28
28
  "prompts/"
29
29
  ],
30
30
  "dependencies": {
31
- "@segment/analytics-node": "^3.0.0",
32
31
  "commander": "^13.1.0",
33
32
  "ink": "^4.4.1",
34
33
  "react": "^18.3.1",
35
34
  "react-devtools-core": "^4.19.1",
36
35
  "react-dom": "^18.3.1",
37
- "@contentful/experience-design-system-client": "2.23.2-dev-build-8b2cfc9.0",
38
- "@contentful/experience-design-system-extraction": "2.23.2-dev-build-8b2cfc9.0",
39
- "@contentful/experience-design-system-generation": "2.23.2-dev-build-8b2cfc9.0",
40
- "@contentful/experience-design-system-types": "2.23.2-dev-build-8b2cfc9.0"
36
+ "@contentful/experience-design-system-client": "2.23.2-dev-build-09d87bb.0",
37
+ "@contentful/experience-design-system-generation": "2.23.2-dev-build-09d87bb.0",
38
+ "@contentful/experience-design-system-types": "2.23.2-dev-build-09d87bb.0",
39
+ "@contentful/experience-design-system-extraction": "2.23.2-dev-build-09d87bb.0"
41
40
  },
42
41
  "devDependencies": {
43
42
  "@tsconfig/node24": "^24.0.4",
@@ -1,6 +0,0 @@
1
- import type { ApplyOperationResponse } from '@contentful/experience-design-system-types';
2
- import type { ImportApiClient } from '../apply/api-client.js';
3
- /** Record apply/preview API outcomes on the active command. */
4
- export declare function recordApplyOutcome(client: ImportApiClient, spaceId: string, environmentId: string, operation: ApplyOperationResponse): void;
5
- /** Record Contentful target context without an apply operation (e.g. preview-only). */
6
- export declare function recordContentfulContext(client: ImportApiClient, spaceId: string, environmentId: string): void;
@@ -1,30 +0,0 @@
1
- import { enrichCommandResult, setCommandContext } from './tracker.js';
2
- function countWriteResult(items, entityType) {
3
- const subset = items.filter((item) => item.entityType === entityType);
4
- return {
5
- created_count: subset.filter((item) => item.action === 'create' && item.status === 'succeeded').length,
6
- updated_count: subset.filter((item) => item.action === 'update' && item.status === 'succeeded').length,
7
- failed_count: subset.filter((item) => item.status === 'failed').length,
8
- };
9
- }
10
- /** Record apply/preview API outcomes on the active command. */
11
- export function recordApplyOutcome(client, spaceId, environmentId, operation) {
12
- setCommandContext({
13
- space_key: spaceId,
14
- environment_key: environmentId,
15
- x_contentful_request_id: client.getLastRequestId(),
16
- });
17
- enrichCommandResult({
18
- dsi_operation_id: operation.sys.id,
19
- component_type_result: countWriteResult(operation.items ?? [], 'ComponentType'),
20
- design_token_result: countWriteResult(operation.items ?? [], 'DesignToken'),
21
- });
22
- }
23
- /** Record Contentful target context without an apply operation (e.g. preview-only). */
24
- export function recordContentfulContext(client, spaceId, environmentId) {
25
- setCommandContext({
26
- space_key: spaceId,
27
- environment_key: environmentId,
28
- x_contentful_request_id: client.getLastRequestId(),
29
- });
30
- }
@@ -1,3 +0,0 @@
1
- export declare function cliVersion(): string;
2
- export declare function analyticsEnabled(): boolean;
3
- export declare function trackEvent(event: string, properties: Record<string, unknown>, anonymousId: string): Promise<void>;
@@ -1,39 +0,0 @@
1
- import { createRequire } from 'node:module';
2
- import { Analytics } from '@segment/analytics-node';
3
- const require = createRequire(import.meta.url);
4
- const pkg = require('../../package.json');
5
- // Anonymous usage telemetry for the CLI. Disabled when DISABLE_ANALYTICS is set
6
- // or when no write key is configured. Never blocks command execution.
7
- export function cliVersion() {
8
- return pkg.version;
9
- }
10
- export function analyticsEnabled() {
11
- return !process.env.DISABLE_ANALYTICS && Boolean(resolveWriteKey());
12
- }
13
- function resolveWriteKey() {
14
- const key = (process.env.SEGMENT_WRITE_KEY ?? '').trim();
15
- return key.length > 0 ? key : undefined;
16
- }
17
- export async function trackEvent(event, properties, anonymousId) {
18
- if (!analyticsEnabled())
19
- return;
20
- const writeKey = resolveWriteKey();
21
- if (!writeKey)
22
- return;
23
- try {
24
- const client = new Analytics({ writeKey });
25
- client.on('error', () => {
26
- /* never block the CLI on telemetry errors */
27
- });
28
- client.track({
29
- event,
30
- properties,
31
- anonymousId,
32
- timestamp: new Date(),
33
- });
34
- await client.closeAndFlush();
35
- }
36
- catch {
37
- // Telemetry must never affect CLI exit codes or output.
38
- }
39
- }
@@ -1,6 +0,0 @@
1
- export { analyticsEnabled, cliVersion, trackEvent } from './client.js';
2
- export { normalizeCommand } from './normalize.js';
3
- export { getOsName } from './os.js';
4
- export { bindAnalyticsSession, bindAnalyticsSessionId, completeActiveCommand, emitSessionStarted, enrichCommandResult, failActiveCommand, getBoundSessionId, noteCommandStart, registerAnalyticsExitHook, resetAnalyticsStateForTests, setCommandContext, } from './tracker.js';
5
- export { recordApplyOutcome, recordContentfulContext } from './apply.js';
6
- export type { CommandCompletion, CommandContext, CommandFailure, DsiCliCommand, EntryCommand, OsName, WriteResult, } from './types.js';
@@ -1,5 +0,0 @@
1
- export { analyticsEnabled, cliVersion, trackEvent } from './client.js';
2
- export { normalizeCommand } from './normalize.js';
3
- export { getOsName } from './os.js';
4
- export { bindAnalyticsSession, bindAnalyticsSessionId, completeActiveCommand, emitSessionStarted, enrichCommandResult, failActiveCommand, getBoundSessionId, noteCommandStart, registerAnalyticsExitHook, resetAnalyticsStateForTests, setCommandContext, } from './tracker.js';
5
- export { recordApplyOutcome, recordContentfulContext } from './apply.js';
@@ -1,3 +0,0 @@
1
- import type { DsiCliCommand } from './types.js';
2
- /** Map a Commander command chain (e.g. "apply push") to a tracked command id, if any. */
3
- export declare function normalizeCommand(commandChain: string): DsiCliCommand | undefined;
@@ -1,17 +0,0 @@
1
- const COMMAND_MAP = {
2
- 'analyze extract': 'analyze_extract',
3
- 'analyze select': 'analyze_select',
4
- 'generate components': 'generate_components',
5
- 'generate tokens': 'generate_tokens',
6
- 'generate edit': 'generate_edit',
7
- 'apply preview': 'apply_preview',
8
- 'apply select': 'apply_select',
9
- 'apply push': 'apply_push',
10
- 'print components': 'print_components',
11
- 'print tokens': 'print_tokens',
12
- import: 'import',
13
- };
14
- /** Map a Commander command chain (e.g. "apply push") to a tracked command id, if any. */
15
- export function normalizeCommand(commandChain) {
16
- return COMMAND_MAP[commandChain];
17
- }
@@ -1,2 +0,0 @@
1
- import type { OsName } from './types.js';
2
- export declare function getOsName(): OsName;
@@ -1,13 +0,0 @@
1
- const OS_NAMES = {
2
- android: 'Android',
3
- aix: 'Linux',
4
- darwin: 'macOS',
5
- freebsd: 'Linux',
6
- linux: 'Linux',
7
- openbsd: 'Linux',
8
- sunos: 'Linux',
9
- win32: 'Windows',
10
- };
11
- export function getOsName() {
12
- return OS_NAMES[process.platform] ?? 'other';
13
- }
@@ -1,19 +0,0 @@
1
- import type { CommandCompletion, CommandContext, CommandFailure, EntryCommand } from './types.js';
2
- /** Record the start of a tracked command. Invoked is deferred until a session is bound. */
3
- export declare function noteCommandStart(commandChain: string): void;
4
- /** Bind a session id, minting one when the command has no pipeline session. */
5
- export declare function bindAnalyticsSessionId(sessionId: string | undefined, context?: CommandContext): Promise<string>;
6
- /** Attach Contentful target context for the active command. */
7
- export declare function setCommandContext(context: CommandContext): void;
8
- /** Merge optional completion fields before the terminal event fires. */
9
- export declare function enrichCommandResult(fields: CommandCompletion): void;
10
- export declare function getBoundSessionId(): string | undefined;
11
- /** Bind the pipeline session and emit invoked once both command and session are known. */
12
- export declare function bindAnalyticsSession(id: string, context?: CommandContext): Promise<void>;
13
- /** Emit session_started once per new pipeline head. */
14
- export declare function emitSessionStarted(entryCommand: EntryCommand): Promise<void>;
15
- export declare function completeActiveCommand(): Promise<void>;
16
- export declare function failActiveCommand(fields?: CommandFailure): Promise<void>;
17
- /** Best-effort terminal event when the process exits without a clean postAction hook. */
18
- export declare function registerAnalyticsExitHook(): void;
19
- export declare function resetAnalyticsStateForTests(): void;