@contentful/experience-design-system-cli 2.23.2-dev-build-753ceef.0 → 2.23.2-dev-build-e996ffb.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 (69) hide show
  1. package/README.md +2 -24
  2. package/dist/package.json +1 -2
  3. package/dist/src/analyze/command.js +2 -8
  4. package/dist/src/analyze/select/command.js +9 -13
  5. package/dist/src/analyze/select/tui/App.js +4 -1
  6. package/dist/src/analyze/select-agent/command.js +11 -14
  7. package/dist/src/apply/api-client.d.ts +5 -5
  8. package/dist/src/apply/api-client.js +8 -18
  9. package/dist/src/apply/command.d.ts +1 -1
  10. package/dist/src/apply/command.js +77 -81
  11. package/dist/src/apply/preview-utils.d.ts +5 -1
  12. package/dist/src/apply/preview-utils.js +5 -1
  13. package/dist/src/apply/tui/ServerApplyView.d.ts +6 -3
  14. package/dist/src/apply/tui/ServerApplyView.js +15 -6
  15. package/dist/src/apply/tui/ServerPreviewView.d.ts +2 -1
  16. package/dist/src/apply/tui/ServerPreviewView.js +3 -3
  17. package/dist/src/generate/command.js +9 -15
  18. package/dist/src/generate/edit/command.js +0 -1
  19. package/dist/src/import/command.js +6 -1
  20. package/dist/src/import/orchestrator.d.ts +1 -0
  21. package/dist/src/import/orchestrator.js +14 -18
  22. package/dist/src/import/tui/WizardApp.d.ts +2 -1
  23. package/dist/src/import/tui/WizardApp.js +8 -8
  24. package/dist/src/import/tui/final-review-host.d.ts +2 -1
  25. package/dist/src/import/tui/final-review-host.js +2 -2
  26. package/dist/src/import/tui/runLivePreview.d.ts +3 -0
  27. package/dist/src/import/tui/runLivePreview.js +4 -1
  28. package/dist/src/import/tui/steps/GenerateReviewStep.d.ts +2 -1
  29. package/dist/src/import/tui/steps/GenerateReviewStep.js +3 -3
  30. package/dist/src/import/tui/steps/WizardPreviewStep.d.ts +6 -2
  31. package/dist/src/import/tui/steps/WizardPreviewStep.js +12 -3
  32. package/dist/src/import/tui/useFinalizePreview.d.ts +2 -0
  33. package/dist/src/import/tui/useFinalizePreview.js +0 -0
  34. package/dist/src/import/tui/useLivePreview.d.ts +2 -0
  35. package/dist/src/import/tui/useLivePreview.js +1 -0
  36. package/dist/src/index.js +1 -6
  37. package/dist/src/lib/command-options.d.ts +1 -0
  38. package/dist/src/lib/command-options.js +3 -0
  39. package/dist/src/print/command.js +13 -16
  40. package/dist/src/program.js +1 -8
  41. package/dist/src/runs/modify-launcher.d.ts +2 -0
  42. package/dist/src/runs/modify-launcher.js +2 -0
  43. package/dist/src/runs/push-helpers.d.ts +1 -0
  44. package/dist/src/runs/push-helpers.js +3 -0
  45. package/dist/src/runs/replay-helpers.d.ts +4 -0
  46. package/dist/src/runs/replay-helpers.js +3 -0
  47. package/package.json +5 -6
  48. package/dist/src/analytics/apply.d.ts +0 -6
  49. package/dist/src/analytics/apply.js +0 -30
  50. package/dist/src/analytics/client.d.ts +0 -7
  51. package/dist/src/analytics/client.js +0 -68
  52. package/dist/src/analytics/constants.d.ts +0 -4
  53. package/dist/src/analytics/constants.js +0 -4
  54. package/dist/src/analytics/env.d.ts +0 -4
  55. package/dist/src/analytics/env.js +0 -14
  56. package/dist/src/analytics/exit.d.ts +0 -5
  57. package/dist/src/analytics/exit.js +0 -24
  58. package/dist/src/analytics/index.d.ts +0 -10
  59. package/dist/src/analytics/index.js +0 -9
  60. package/dist/src/analytics/normalize.d.ts +0 -3
  61. package/dist/src/analytics/normalize.js +0 -18
  62. package/dist/src/analytics/os.d.ts +0 -2
  63. package/dist/src/analytics/os.js +0 -13
  64. package/dist/src/analytics/session.d.ts +0 -3
  65. package/dist/src/analytics/session.js +0 -15
  66. package/dist/src/analytics/tracker.d.ts +0 -17
  67. package/dist/src/analytics/tracker.js +0 -126
  68. package/dist/src/analytics/types.d.ts +0 -28
  69. package/dist/src/analytics/types.js +0 -1
@@ -14,7 +14,6 @@ import { hashPromptForSkill } from '../session/cache-keys.js';
14
14
  import { getRefineArtifactsRoot, getRefineSessionPaths } from '../analyze/select/persistence.js';
15
15
  import { readExperiencesCredentials } from '../credentials-store.js';
16
16
  import { addAgentModelOptions } from '../lib/agent-model-options.js';
17
- import { bindAnalyticsSessionId, exitWithAnalytics } from '../analytics/index.js';
18
17
  const execFileAsync = promisify(execFile);
19
18
  const DEFAULT_TIMEOUT_MS = Number(process.env.EDS_AGENT_TIMEOUT_MS ?? 3 * 60 * 1000);
20
19
  const DEFAULT_COMPONENT_CONCURRENCY = 10;
@@ -24,8 +23,7 @@ const invoker = createLocalCliAgentInvoker({
24
23
  });
25
24
  function die(message) {
26
25
  process.stderr.write(`${message}\n`);
27
- void exitWithAnalytics(1);
28
- throw new Error('exit');
26
+ process.exit(1);
29
27
  }
30
28
  async function pathExists(p) {
31
29
  return access(p)
@@ -278,8 +276,7 @@ function resolveSessionId(sessionFlag) {
278
276
  .get();
279
277
  if (!row) {
280
278
  process.stderr.write('Error: no completed analyze extract session found. Run analyze extract first, or pass --session <id>.\n');
281
- void exitWithAnalytics(1);
282
- throw new Error('exit');
279
+ process.exit(1);
283
280
  }
284
281
  return row.id;
285
282
  }
@@ -343,7 +340,6 @@ async function runGenerateSkill(skill, opts, verbose = false) {
343
340
  let allComponents;
344
341
  if (skill === 'components') {
345
342
  sessionId = resolveSessionId(opts.session);
346
- await bindAnalyticsSessionId(sessionId);
347
343
  const acceptedNames = await loadAcceptedNames(sessionId);
348
344
  const db = openPipelineDb();
349
345
  try {
@@ -400,7 +396,7 @@ async function runGenerateSkill(skill, opts, verbose = false) {
400
396
  skillPathOverride: generatePromptPath,
401
397
  });
402
398
  process.stdout.write(prompt + '\n');
403
- await exitWithAnalytics(0);
399
+ process.exit(0);
404
400
  }
405
401
  const binary = resolveBinary(agent);
406
402
  if (!(await assertBinaryInPath(binary))) {
@@ -409,7 +405,7 @@ async function runGenerateSkill(skill, opts, verbose = false) {
409
405
  skill,
410
406
  sessionId: sessionId ?? '',
411
407
  });
412
- await exitWithAnalytics(1);
408
+ process.exit(1);
413
409
  }
414
410
  if (skill === 'components' && allComponents && sessionId) {
415
411
  const db = openPipelineDb();
@@ -475,8 +471,6 @@ async function runGenerateSkill(skill, opts, verbose = false) {
475
471
  resolvedSessionId = newId;
476
472
  }
477
473
  }
478
- sessionId = resolvedSessionId;
479
- await bindAnalyticsSessionId(resolvedSessionId);
480
474
  const tokenPromptHash = await hashPromptForSkill('tokens');
481
475
  // Check cache before invoking agent
482
476
  if (!noCache) {
@@ -489,12 +483,12 @@ async function runGenerateSkill(skill, opts, verbose = false) {
489
483
  // Skip agent invocation — jump to view
490
484
  const viewResult = { skill, agent, sessionId: sessionId ?? '' };
491
485
  if (process.stdout.isTTY) {
492
- const { waitUntilExit } = render(createElement(GenerateView, { result: viewResult, onExit: () => void exitWithAnalytics(0) }));
486
+ const { waitUntilExit } = render(createElement(GenerateView, { result: viewResult, onExit: () => process.exit(0) }));
493
487
  await waitUntilExit();
494
488
  }
495
489
  else {
496
490
  process.stdout.write(`generate complete\nskill: ${skill}\nagent: ${agent}\nsession=${sessionId ?? ''}\n`);
497
- await exitWithAnalytics(0);
491
+ process.exit(0);
498
492
  }
499
493
  return;
500
494
  }
@@ -529,7 +523,7 @@ async function runGenerateSkill(skill, opts, verbose = false) {
529
523
  process.stderr.write(`Error: agent produced no set_token calls.\n` +
530
524
  `Run with --dry-run to inspect the prompt.\n\n` +
531
525
  `Agent output:\n${result.stdout}\n`);
532
- await exitWithAnalytics(1);
526
+ process.exit(1);
533
527
  }
534
528
  if (tokenWarnings.length > 0) {
535
529
  process.stderr.write(`Warnings:\n${tokenWarnings.map((w) => ` ${w}`).join('\n')}\n`);
@@ -554,13 +548,13 @@ async function runGenerateSkill(skill, opts, verbose = false) {
554
548
  if (process.stdout.isTTY) {
555
549
  const { waitUntilExit } = render(createElement(GenerateView, {
556
550
  result: viewResult,
557
- onExit: () => void exitWithAnalytics(0),
551
+ onExit: () => process.exit(0),
558
552
  }));
559
553
  await waitUntilExit();
560
554
  }
561
555
  else {
562
556
  process.stdout.write(`generate complete\nskill: ${skill}\nagent: ${agent}\nsession=${sessionId ?? ''}\n`);
563
- await exitWithAnalytics(0);
557
+ process.exit(0);
564
558
  }
565
559
  }
566
560
  function addAgentFlags(cmd) {
@@ -99,7 +99,6 @@ async function runNonInteractive(opts, skill) {
99
99
  process.stderr.write(`Accepted: ${accepted.length} Rejected: ${rejected.length}\n`);
100
100
  }
101
101
  export function registerGenerateEditCommand(parent, skill) {
102
- // TODO(analytics): bindAnalyticsSessionId when generate edit ships — tracked in schema as generate_edit.
103
102
  parent
104
103
  .command('edit')
105
104
  .description(`Review and correct generate ${skill} output before pushing`)
@@ -4,7 +4,7 @@ import { resolveAutoFilter } from './auto-filter-resolve.js';
4
4
  import { resolveAgent, resolveModel } from './agent-model-resolve.js';
5
5
  import { addAgentModelOptions } from '../lib/agent-model-options.js';
6
6
  import { resolveCompositionMode } from '../lib/composition-mode.js';
7
- import { addCompositionOptions } from '../lib/command-options.js';
7
+ import { addAllowDeletionsOption, addCompositionOptions } from '../lib/command-options.js';
8
8
  import { isConflictMode } from '../runs/save-path-resolver.js';
9
9
  import { readExperiencesCredentials } from '../credentials-store.js';
10
10
  import { DEFAULT_CONFIGURED_HOST, toConfiguredHost } from '../host-utils.js';
@@ -46,6 +46,7 @@ export function registerImportCommand(program) {
46
46
  .option('--print-prompt', 'Print the generate components prompt without invoking the agent. Replaces the legacy --dry-run prompt-print behaviour on this command.')
47
47
  .option('--auto-accept-scope', 'Accept all extracted components without prompting (for scripted/non-TTY callers)');
48
48
  addCompositionOptions(cmd);
49
+ addAllowDeletionsOption(cmd);
49
50
  cmd
50
51
  .option('--composition-map <path>', 'Consume a hand-authored parent→children interchange map (implies --composite)')
51
52
  .option('--composition-agent', 'Opt into agentic mapping resolution when deterministic sources find no groups (implies --composite)')
@@ -136,6 +137,7 @@ export function registerImportCommand(program) {
136
137
  ...(opts.host ? { host: opts.host } : {}),
137
138
  interactive: interactiveTerminalSupported,
138
139
  ...(opts.force ? { force: true } : {}),
140
+ ...(opts.allowDeletions ? { allowDeletions: true } : {}),
139
141
  });
140
142
  return;
141
143
  }
@@ -166,6 +168,7 @@ export function registerImportCommand(program) {
166
168
  ...(opts.saveAsNew ? { saveAsNew: true } : {}),
167
169
  ...(opts.outDir ? { outDir: opts.outDir } : {}),
168
170
  ...(opts.force ? { force: true } : {}),
171
+ ...(opts.allowDeletions ? { allowDeletions: true } : {}),
169
172
  });
170
173
  return;
171
174
  }
@@ -297,6 +300,7 @@ export function registerImportCommand(program) {
297
300
  selectPromptPath: opts.selectPromptPath ?? creds.selectPromptPath,
298
301
  generatePromptPath: opts.generatePromptPath ?? creds.generatePromptPath,
299
302
  ...(opts.rawTokens ? { initialRawTokensPath: resolve(opts.rawTokens) } : {}),
303
+ allowDeletions: opts.allowDeletions === true,
300
304
  ...pickerProps,
301
305
  }));
302
306
  unmountInk = unmount;
@@ -357,6 +361,7 @@ export function registerImportCommand(program) {
357
361
  dryRun: dryRunForward,
358
362
  selectPromptPath: opts.selectPromptPath,
359
363
  autoRejectCycles: opts.autoRejectCycles ?? false,
364
+ allowDeletions: opts.allowDeletions ?? false,
360
365
  compositionMode: headlessCompositionMode,
361
366
  ...(opts.compositionMap ? { compositionMap: opts.compositionMap } : {}),
362
367
  ...(opts.compositionAgent ? { compositionAgent: true } : {}),
@@ -26,6 +26,7 @@ export interface PipelineOptions {
26
26
  selectPromptPath?: string;
27
27
  /** When true, auto-reject cycle participants and retry push instead of surfacing an error. */
28
28
  autoRejectCycles?: boolean;
29
+ allowDeletions?: boolean;
29
30
  compositionMode?: CompositionMode;
30
31
  compositionMap?: string;
31
32
  compositionAgent?: boolean;
@@ -6,19 +6,17 @@ import { openPipelineDb, getOrCreateSession, createStep, updateStep, findLatestS
6
6
  import { detectSlotCycles, formatSlotCycleReport } from '../apply/command.js';
7
7
  import { PREVIEW_ERROR_PREFIX, VALIDATION_FAILED_CODE, parsePreviewValidationErrors } from '../apply/api-client.js';
8
8
  import { buildPostPushUrl } from '../lib/contentful-urls.js';
9
- import { getDebugLogger } from '../lib/debug-logger.js';
10
- import { bindAnalyticsSession, emitSessionStarted } from '../analytics/index.js';
11
- import { pipelineSubprocessEnv } from '../analytics/env.js';
9
+ import { getDebugLogger, debugEnvForSubprocess } from '../lib/debug-logger.js';
12
10
  function findCliPath() {
13
11
  return join(fileURLToPath(import.meta.url), '..', '..', '..', '..', 'bin', 'cli.js');
14
12
  }
15
- async function runStep(args, cliPath, analyticsSessionId, env = {}, streamStderr = false) {
13
+ async function runStep(args, cliPath, env = {}, streamStderr = false) {
16
14
  const debug = getDebugLogger();
17
15
  const startedAt = Date.now();
18
16
  debug.event('import', 'subprocess.spawn', { cliPath, args });
19
17
  return new Promise((res) => {
20
18
  const child = execFile('node', [cliPath, ...args], {
21
- env: pipelineSubprocessEnv({ ...process.env, ...env }, analyticsSessionId),
19
+ env: debugEnvForSubprocess({ ...process.env, ...env }),
22
20
  });
23
21
  let stdout = '';
24
22
  let stderr = '';
@@ -110,10 +108,6 @@ export async function runPipeline(opts, progressWriter, cliPathOverride) {
110
108
  inputPath: projectRoot,
111
109
  outDir,
112
110
  });
113
- await bindAnalyticsSession(sessionId, {
114
- ...(opts.spaceId ? { space_key: opts.spaceId, environment_key: opts.environmentId } : {}),
115
- });
116
- await emitSessionStarted('import');
117
111
  progressWriter(`Experience Design System CLI — Pipeline Import`);
118
112
  progressWriter(`Project: ${projectRoot}`);
119
113
  progressWriter(`Output: ${outDir}`);
@@ -165,7 +159,7 @@ export async function runPipeline(opts, progressWriter, cliPathOverride) {
165
159
  if (opts.agent)
166
160
  analyzeArgs.push('--agent', opts.agent);
167
161
  }
168
- const r = await runStep(analyzeArgs, cliPath, sessionId);
162
+ const r = await runStep(analyzeArgs, cliPath);
169
163
  const durationMs = Date.now() - t0;
170
164
  if (r.exitCode !== 0) {
171
165
  if (r.stderr)
@@ -237,7 +231,7 @@ export async function runPipeline(opts, progressWriter, cliPathOverride) {
237
231
  editArgs.push('--select-all');
238
232
  }
239
233
  }
240
- const rEdit = await runStep(editArgs, cliPath, sessionId, { FORCE_COLOR: '1' }, useAgentSelect);
234
+ const rEdit = await runStep(editArgs, cliPath, { FORCE_COLOR: '1' }, useAgentSelect);
241
235
  const editDurationMs = Date.now() - t0Edit;
242
236
  if (rEdit.exitCode !== 0) {
243
237
  if (rEdit.stderr && !useAgentSelect)
@@ -298,7 +292,7 @@ export async function runPipeline(opts, progressWriter, cliPathOverride) {
298
292
  extractSession: extractSessionId ?? '',
299
293
  });
300
294
  const t0 = Date.now();
301
- const r = await runStep(generateArgs, cliPath, sessionId, { FORCE_COLOR: '1' }, true);
295
+ const r = await runStep(generateArgs, cliPath, { FORCE_COLOR: '1' }, true);
302
296
  const durationMs = Date.now() - t0;
303
297
  if (r.exitCode !== 0) {
304
298
  updateStep(db, stepId, 'failed', {}, r.stderr);
@@ -361,7 +355,7 @@ export async function runPipeline(opts, progressWriter, cliPathOverride) {
361
355
  out: componentsPath,
362
356
  });
363
357
  const t0 = Date.now();
364
- const r = await runStep(printArgs, cliPath, sessionId);
358
+ const r = await runStep(printArgs, cliPath);
365
359
  const durationMs = Date.now() - t0;
366
360
  if (r.exitCode !== 0) {
367
361
  if (r.stderr)
@@ -424,12 +418,14 @@ export async function runPipeline(opts, progressWriter, cliPathOverride) {
424
418
  pushArgs.push('--host', opts.host);
425
419
  if (opts.verbose)
426
420
  pushArgs.push('--verbose');
421
+ if (opts.allowDeletions)
422
+ pushArgs.push('--allow-deletions');
427
423
  pushArgs.push('--yes');
428
424
  const pushStepId = createStep(db, sessionId, 'apply push', {
429
425
  components: componentsPath,
430
426
  });
431
427
  const t0 = Date.now();
432
- let r = await runStep(pushArgs, cliPath, sessionId, { FORCE_COLOR: '1' }, true);
428
+ let r = await runStep(pushArgs, cliPath, { FORCE_COLOR: '1' }, true);
433
429
  const excludedByRetry = [];
434
430
  let validationRetryCount = 0;
435
431
  while (validationRetryCount < MAX_VALIDATION_RETRIES && isPreviewValidationError(r) && extractSessionId) {
@@ -446,10 +442,10 @@ export async function runPipeline(opts, progressWriter, cliPathOverride) {
446
442
  '--exclude-components',
447
443
  offenders.join(','),
448
444
  ];
449
- const rejectResult = await runStep(rejectArgs, cliPath, sessionId);
445
+ const rejectResult = await runStep(rejectArgs, cliPath);
450
446
  if (rejectResult.exitCode !== 0)
451
447
  break;
452
- r = await runStep(pushArgs, cliPath, sessionId, { FORCE_COLOR: '1' }, true);
448
+ r = await runStep(pushArgs, cliPath, { FORCE_COLOR: '1' }, true);
453
449
  validationRetryCount++;
454
450
  }
455
451
  const durationMs = Date.now() - t0;
@@ -484,10 +480,10 @@ export async function runPipeline(opts, progressWriter, cliPathOverride) {
484
480
  '--exclude-components',
485
481
  cycleNames.join(','),
486
482
  ];
487
- const rejectResult = await runStep(rejectArgs, cliPath, sessionId);
483
+ const rejectResult = await runStep(rejectArgs, cliPath);
488
484
  if (rejectResult.exitCode === 0) {
489
485
  const retryT0 = Date.now();
490
- const retryR = await runStep(pushArgs, cliPath, sessionId, { FORCE_COLOR: '1' }, true);
486
+ const retryR = await runStep(pushArgs, cliPath, { FORCE_COLOR: '1' }, true);
491
487
  const retryDurationMs = Date.now() - t0 + (Date.now() - retryT0);
492
488
  if (isSlotCycleError(retryR)) {
493
489
  const retryReport = extractCycleReport(retryR.stderr);
@@ -66,5 +66,6 @@ export type WizardAppProps = {
66
66
  initialRawTokensPath?: string;
67
67
  initialRuns?: RunRecord[];
68
68
  onRunPicked?: (selection: RunPickerSelection) => void;
69
+ allowDeletions?: boolean;
69
70
  };
70
- export declare function WizardApp({ initialSpaceId, initialEnvironmentId, initialCmaToken, initialHost, initialAgent, initialModel, initialProjectPath, host, autoAcceptScope, autoRejectCycles, compositionMode, compositionMap, compositionAgent, compositionAgentMode, compositionRefresh, generateMap, promptOverrides, noCache, autoFilter, livePreview, noPush, noSave, outDirOverride, onConflictMode, selectPromptPath, generatePromptPath, seedExtractSessionId, seedGenerateSessionId, seedTokenSessionId, seedTokensPath, initialStep, initialRawTokensPath, initialRuns, onRunPicked, }?: WizardAppProps): React.ReactElement;
71
+ export declare function WizardApp({ initialSpaceId, initialEnvironmentId, initialCmaToken, initialHost, initialAgent, initialModel, initialProjectPath, host, autoAcceptScope, autoRejectCycles, compositionMode, compositionMap, compositionAgent, compositionAgentMode, compositionRefresh, generateMap, promptOverrides, noCache, autoFilter, livePreview, noPush, noSave, outDirOverride, onConflictMode, selectPromptPath, generatePromptPath, seedExtractSessionId, seedGenerateSessionId, seedTokenSessionId, seedTokensPath, initialStep, initialRawTokensPath, initialRuns, onRunPicked, allowDeletions, }?: WizardAppProps): React.ReactElement;
@@ -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,12 +1,7 @@
1
1
  import { createProgram } from './program.js';
2
- import { failActiveCommand, flushAnalytics } 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
- });
9
- await flushAnalytics();
4
+ .catch((err) => {
10
5
  process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}\n`);
11
6
  process.exit(1);
12
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
+ }