@contentful/experience-design-system-cli 2.23.2-dev-build-8b2cfc9.0 → 2.23.2-dev-build-5146e8a.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 (36) 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/apply/api-client.d.ts +4 -4
  5. package/dist/src/apply/api-client.js +5 -16
  6. package/dist/src/apply/command.js +24 -27
  7. package/dist/src/import/command.js +6 -1
  8. package/dist/src/import/orchestrator.d.ts +1 -0
  9. package/dist/src/import/orchestrator.js +3 -6
  10. package/dist/src/import/tui/WizardApp.d.ts +2 -1
  11. package/dist/src/import/tui/WizardApp.js +2 -2
  12. package/dist/src/index.js +1 -5
  13. package/dist/src/lib/command-options.d.ts +1 -0
  14. package/dist/src/lib/command-options.js +3 -0
  15. package/dist/src/program.js +1 -8
  16. package/dist/src/runs/modify-launcher.d.ts +2 -0
  17. package/dist/src/runs/modify-launcher.js +2 -0
  18. package/dist/src/runs/push-helpers.d.ts +1 -0
  19. package/dist/src/runs/push-helpers.js +3 -0
  20. package/dist/src/runs/replay-helpers.d.ts +4 -0
  21. package/dist/src/runs/replay-helpers.js +3 -0
  22. package/package.json +5 -6
  23. package/dist/src/analytics/apply.d.ts +0 -6
  24. package/dist/src/analytics/apply.js +0 -30
  25. package/dist/src/analytics/client.d.ts +0 -3
  26. package/dist/src/analytics/client.js +0 -39
  27. package/dist/src/analytics/index.d.ts +0 -6
  28. package/dist/src/analytics/index.js +0 -5
  29. package/dist/src/analytics/normalize.d.ts +0 -3
  30. package/dist/src/analytics/normalize.js +0 -17
  31. package/dist/src/analytics/os.d.ts +0 -2
  32. package/dist/src/analytics/os.js +0 -13
  33. package/dist/src/analytics/tracker.d.ts +0 -19
  34. package/dist/src/analytics/tracker.js +0 -157
  35. package/dist/src/analytics/types.d.ts +0 -29
  36. package/dist/src/analytics/types.js +0 -1
package/README.md CHANGED
@@ -214,6 +214,7 @@ Pass `--select-prompt-path <path>` and/or `--generate-prompt-path <path>` to swa
214
214
  | `--host <url>` | `https://api.contentful.com` | Override API base URL |
215
215
  | `--on-conflict <mode>` | _(prompt via `<SaveConflictGate>`)_ | Headless conflict resolution when a file already exists at the save path: `overwrite`, `skip`, or `fail`. Bypasses the wizard's interactive save-conflict gate. Mutex with `--no-save`. |
216
216
  | `--print-prompt` | — | Print the generate prompt to stdout and exit. Replaces the prompt-print semantics of `--dry-run`. |
217
+ | `--allow-deletions` | off (non-destructive) | Allow the push to delete remote ComponentTypes/DesignTokens missing from the manifest. Default skips them instead of deleting. Forwarded to headless subprocess pushes and `--push-from-run`. |
217
218
  | `--dry-run` | _(deprecated)_ | Deprecated alias for `--print-prompt`. Emits a stderr deprecation notice; prompt-print semantics will be removed in a future release. |
218
219
 
219
220
  ### Run-picker at wizard start
@@ -418,7 +419,7 @@ experiences apply select --space-id <id> --environment-id <env> --session <id>
418
419
  experiences apply push --space-id <id> --environment-id <env> --session <id> [--yes]
419
420
  ```
420
421
 
421
- Shared flags: `--components`, `--tokens`, `--session`, `--space-id`, `--environment-id`, `--cma-token`, `--host`, `--viewports`. `apply preview` adds `--include-unchanged`. `apply select` adds `--select-all`, `--select`, `--deselect`, `--force`. `apply push` adds `--yes`, `--verbose`, `--force`, `--dry-run`.
422
+ Shared flags: `--components`, `--tokens`, `--session`, `--space-id`, `--environment-id`, `--cma-token`, `--host`, `--viewports`. `apply preview` adds `--include-unchanged`. `apply select` adds `--select-all`, `--select`, `--deselect`, `--force`, `--allow-deletions`. `apply push` adds `--yes`, `--verbose`, `--force`, `--dry-run`, `--allow-deletions`. By default, remote ComponentTypes and DesignTokens missing from the pushed manifest are skipped, not deleted; pass `--allow-deletions` to restore the prior delete behavior.
422
423
 
423
424
  Design tokens are written first (component types may reference token kinds). Each entity write is recorded in the session database atomically — interrupted pushes resume from where they left off.
424
425
 
@@ -450,29 +451,6 @@ Wizard run history is separate: `~/.config/experiences/runs.json`.
450
451
 
451
452
  ---
452
453
 
453
- ## Usage data
454
-
455
- The CLI collects **anonymous usage data** to help us understand which commands are used and where the import workflow succeeds or fails. This data does **not** include your source code, file paths, credentials, prompts, or any content you author.
456
-
457
- What may be included:
458
-
459
- - Command name and duration
460
- - CLI, Node.js, and operating-system version
461
- - Anonymous session identifiers that link steps within a single import run
462
- - Structural counts (for example, how many components were extracted or accepted)
463
- - Space and environment IDs you pass on the command line
464
- - Contentful request IDs from API responses (to correlate failures with server logs)
465
-
466
- You can turn this off at any time:
467
-
468
- ```bash
469
- DISABLE_ANALYTICS=1 experiences import --project ./my-app
470
- ```
471
-
472
- Setting `DISABLE_ANALYTICS` to any value disables collection for that invocation.
473
-
474
- ---
475
-
476
454
  ## Development
477
455
 
478
456
  ```bash
package/dist/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-5146e8a.0",
4
4
  "description": "Contentful Experiences design system import CLI",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -41,7 +41,6 @@
41
41
  "@contentful/experience-design-system-extraction": "workspace:*",
42
42
  "@contentful/experience-design-system-generation": "workspace:*",
43
43
  "@contentful/experience-design-system-types": "workspace:*",
44
- "@segment/analytics-node": "^3.0.0",
45
44
  "commander": "^13.1.0",
46
45
  "ink": "^4.4.1",
47
46
  "react": "^18.3.1",
@@ -24,7 +24,6 @@ import { DEFAULT_AGENT_NAME, isAgentName, runAgent, } from '@contentful/experien
24
24
  import { readExperiencesCredentials } from '../credentials-store.js';
25
25
  import { buildAnalyzeViewRows, partitionGlobalWarnings } from './build-analyze-view-rows.js';
26
26
  import { getInteractiveTerminalSupport } from '../lib/terminal-capabilities.js';
27
- import { bindAnalyticsSession, emitSessionStarted, enrichCommandResult } from '../analytics/index.js';
28
27
  import { getDebugLogger } from '../lib/debug-logger.js';
29
28
  const SCANNED_FILE_EXTENSIONS = new Set(['.astro', '.js', '.jsx', '.svelte', '.ts', '.tsx', '.vue']);
30
29
  /**
@@ -265,8 +264,6 @@ export function registerAnalyzeCommand(program) {
265
264
  inputPath: projectRoot,
266
265
  outDir,
267
266
  });
268
- await bindAnalyticsSession(sessionId);
269
- await emitSessionStarted('analyze_extract');
270
267
  const stepId = createStep(db, sessionId, 'analyze extract', {
271
268
  project: projectRoot,
272
269
  });
@@ -540,7 +537,6 @@ export function registerAnalyzeCommand(program) {
540
537
  storeSlotCycles(db, sessionId, withBreaks);
541
538
  storeScannedFiles(db, sessionId, sourceFiles.map((f) => relative(projectRoot, f)));
542
539
  updateStep(db, stepId, 'complete', { sessionId });
543
- enrichCommandResult({ extracted_component_count: validatedComponents.length });
544
540
  db.close();
545
541
  const allWarnings = [...extraction.warnings, ...filterWarnings];
546
542
  const { rows: componentRows, totalErrors } = buildAnalyzeViewRows(filteredComponents, validatedComponents, allWarnings);
@@ -45,9 +45,6 @@ export declare class ImportApiClient {
45
45
  private spaceId;
46
46
  private environmentId;
47
47
  private retry;
48
- private lastRequestId?;
49
- getLastRequestId(): string | undefined;
50
- private noteRequestId;
51
48
  constructor(opts: ApiClientOptions);
52
49
  private base;
53
50
  private headers;
@@ -55,7 +52,10 @@ export declare class ImportApiClient {
55
52
  validateToken(): Promise<void>;
56
53
  checkPreflight(): Promise<void>;
57
54
  previewImport(manifest: ManifestPayload): Promise<ServerPreviewResponse>;
58
- applyImport(manifest: ManifestPayload, acknowledgeBreakingChanges: boolean): Promise<ApplyOperationResponse>;
55
+ applyImport(manifest: ManifestPayload, options: {
56
+ acknowledgeBreakingChanges: boolean;
57
+ allowDeletions?: boolean;
58
+ }): Promise<ApplyOperationResponse>;
59
59
  pollOperation(operationId: string, opts?: {
60
60
  intervalMs?: number;
61
61
  maxIntervalMs?: number;
@@ -180,15 +180,6 @@ export class ImportApiClient {
180
180
  spaceId;
181
181
  environmentId;
182
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
183
  constructor(opts) {
193
184
  this.host = toApiHost(opts.host);
194
185
  this.token = opts.cmaToken;
@@ -246,10 +237,8 @@ export class ImportApiClient {
246
237
  await this.retry.sleep(delayMs);
247
238
  continue;
248
239
  }
249
- if (!isTransientStatus(result.response.status)) {
250
- this.noteRequestId(result.response);
240
+ if (!isTransientStatus(result.response.status))
251
241
  return result;
252
- }
253
242
  if (attempt === this.retry.maxAttempts) {
254
243
  const body = stringifyError(result.error);
255
244
  const guidance = `The ${phase} request failed after ${attempt} attempts because the service remained unavailable. ` +
@@ -284,7 +273,6 @@ export class ImportApiClient {
284
273
  // false positives that don't apply to the design-systems API authorization path.
285
274
  const url = `${this.host}/users/me`;
286
275
  const res = await request(url, { token: this.token });
287
- this.noteRequestId(res);
288
276
  if (res.status === 401) {
289
277
  throw new ApiError('CMA token is invalid or revoked', res.status, await res.text());
290
278
  }
@@ -310,7 +298,6 @@ export class ImportApiClient {
310
298
  catch {
311
299
  return; // network error — don't block; the real call will surface it
312
300
  }
313
- this.noteRequestId(res);
314
301
  if (res.ok)
315
302
  return;
316
303
  if (res.status >= 500)
@@ -348,12 +335,14 @@ export class ImportApiClient {
348
335
  debug.event('apply', 'preview.ok', { status: result.response.status, durationMs: Date.now() - startedAt });
349
336
  return sanitizePreviewResponse(parsed);
350
337
  }
351
- async applyImport(manifest, acknowledgeBreakingChanges) {
338
+ async applyImport(manifest, options) {
339
+ const { acknowledgeBreakingChanges, allowDeletions = false } = options;
352
340
  const debug = getDebugLogger();
353
341
  const startedAt = Date.now();
354
342
  debug.event('apply', 'apply.request', {
355
343
  url: `${this.base()}/design_systems/imports/apply`,
356
344
  acknowledgeBreakingChanges,
345
+ allowDeletions,
357
346
  });
358
347
  let result;
359
348
  try {
@@ -361,7 +350,7 @@ export class ImportApiClient {
361
350
  baseUrl: this.host,
362
351
  headers: this.headers(),
363
352
  path: { spaceId: this.spaceId, environmentId: this.environmentId },
364
- body: { ...manifest, acknowledgeBreakingChanges },
353
+ body: { ...manifest, acknowledgeBreakingChanges, allowDeletions },
365
354
  parseAs: 'json',
366
355
  });
367
356
  }
@@ -12,13 +12,11 @@ import { ServerPreviewApp, ServerPreviewConfirm, ServerApplyProgress, ServerAppl
12
12
  import { SelectView, makeSelectKey } from './tui/SelectView.js';
13
13
  import { buildPostPushUrl } from '../lib/contentful-urls.js';
14
14
  import { resolveCompositionMode } from '../lib/composition-mode.js';
15
- import { addArtifactInputOptions, addCompositionOptions, addContentfulTargetOptions, addSelectionOptions, } from '../lib/command-options.js';
15
+ import { addAllowDeletionsOption, addArtifactInputOptions, addCompositionOptions, addContentfulTargetOptions, addSelectionOptions, } from '../lib/command-options.js';
16
16
  import { stripAllowedComponents } from '../import/strip-allowed-components.js';
17
17
  import { readExperiencesCredentials } from '../credentials-store.js';
18
18
  import { getInteractiveTerminalSupport, requireInteractiveTerminal } from '../lib/terminal-capabilities.js';
19
- import { bindAnalyticsSessionId, completeActiveCommand, failActiveCommand, recordApplyOutcome, recordContentfulContext, } from '../analytics/index.js';
20
19
  function die(message) {
21
- void failActiveCommand({ exit_code: 1 });
22
20
  process.stderr.write(`${message}\n`);
23
21
  process.exit(1);
24
22
  }
@@ -433,12 +431,6 @@ export function registerApplyCommand(program) {
433
431
  throw e;
434
432
  }
435
433
  const { components, tokens, client } = inputs;
436
- const spaceId = opts.spaceId;
437
- const environmentId = opts.environmentId;
438
- await bindAnalyticsSessionId(opts.session, {
439
- space_key: spaceId,
440
- environment_key: environmentId,
441
- });
442
434
  try {
443
435
  await client.validateToken();
444
436
  }
@@ -458,7 +450,8 @@ export function registerApplyCommand(program) {
458
450
  die(`Error: ${formatApiError(e)}`);
459
451
  throw e;
460
452
  }
461
- recordContentfulContext(client, spaceId, environmentId);
453
+ const spaceId = opts.spaceId;
454
+ const environmentId = opts.environmentId;
462
455
  if (getInteractiveTerminalSupport().supported) {
463
456
  const { waitUntilExit } = render(createElement(ServerPreviewApp, {
464
457
  preview,
@@ -476,6 +469,7 @@ export function registerApplyCommand(program) {
476
469
  addArtifactInputOptions(pushCmd);
477
470
  addContentfulTargetOptions(pushCmd);
478
471
  addCompositionOptions(pushCmd);
472
+ addAllowDeletionsOption(pushCmd);
479
473
  pushCmd
480
474
  .option('--yes', 'Skip interactive confirmation')
481
475
  .option('--verbose', 'Show all entity progress including skipped/unchanged')
@@ -497,12 +491,6 @@ export function registerApplyCommand(program) {
497
491
  throw e;
498
492
  }
499
493
  const { components, tokens, client } = inputs;
500
- const spaceId = opts.spaceId;
501
- const environmentId = opts.environmentId;
502
- await bindAnalyticsSessionId(opts.session, {
503
- space_key: spaceId,
504
- environment_key: environmentId,
505
- });
506
494
  assertNoSlotCycles(components);
507
495
  try {
508
496
  await client.validateToken();
@@ -522,6 +510,8 @@ export function registerApplyCommand(program) {
522
510
  die(`Error: ${formatApiError(e, opts.verbose)}`);
523
511
  throw e;
524
512
  }
513
+ const spaceId = opts.spaceId;
514
+ const environmentId = opts.environmentId;
525
515
  if (opts.dryRun) {
526
516
  if (isTTY) {
527
517
  const { waitUntilExit } = render(createElement(ServerPreviewApp, {
@@ -558,7 +548,10 @@ export function registerApplyCommand(program) {
558
548
  }
559
549
  let operation;
560
550
  try {
561
- operation = await client.applyImport(manifest, breakingWithImpact || opts.force === true);
551
+ operation = await client.applyImport(manifest, {
552
+ acknowledgeBreakingChanges: breakingWithImpact || opts.force === true,
553
+ allowDeletions: opts.allowDeletions === true,
554
+ });
562
555
  }
563
556
  catch (e) {
564
557
  if (e instanceof ApiError)
@@ -575,14 +568,8 @@ export function registerApplyCommand(program) {
575
568
  throw e;
576
569
  }
577
570
  const summary = buildApplyOutput(operation, spaceId, environmentId, opts.host);
578
- recordApplyOutcome(client, spaceId, environmentId, operation);
579
571
  process.stdout.write(JSON.stringify(summary, null, 2) + '\n');
580
- const exitCode = operation.sys.status === 'succeeded' ? 0 : 1;
581
- if (exitCode === 0)
582
- await completeActiveCommand();
583
- else
584
- await failActiveCommand({ exit_code: exitCode });
585
- process.exit(exitCode);
572
+ process.exit(operation.sys.status === 'succeeded' ? 0 : 1);
586
573
  return;
587
574
  }
588
575
  await new Promise((resolvePromise) => {
@@ -594,7 +581,10 @@ export function registerApplyCommand(program) {
594
581
  }));
595
582
  let operation;
596
583
  try {
597
- operation = await client.applyImport(manifest, acknowledge);
584
+ operation = await client.applyImport(manifest, {
585
+ acknowledgeBreakingChanges: acknowledge,
586
+ allowDeletions: opts.allowDeletions === true,
587
+ });
598
588
  }
599
589
  catch (e) {
600
590
  if (e instanceof ApiError) {
@@ -657,6 +647,7 @@ export function registerApplyCommand(program) {
657
647
  addContentfulTargetOptions(selectCmd);
658
648
  addCompositionOptions(selectCmd);
659
649
  addSelectionOptions(selectCmd);
650
+ addAllowDeletionsOption(selectCmd);
660
651
  selectCmd.option('--force', 'Skip confirmation for breaking changes').action(async (opts) => {
661
652
  const nonInteractive = opts.selectAll || (opts.select ?? []).length > 0 || (opts.deselect ?? []).length > 0;
662
653
  if (!nonInteractive) {
@@ -724,7 +715,10 @@ export function registerApplyCommand(program) {
724
715
  }
725
716
  let operation;
726
717
  try {
727
- operation = await client.applyImport(filteredManifest, hasBreaking || opts.force === true);
718
+ operation = await client.applyImport(filteredManifest, {
719
+ acknowledgeBreakingChanges: hasBreaking || opts.force === true,
720
+ allowDeletions: opts.allowDeletions === true,
721
+ });
728
722
  }
729
723
  catch (e) {
730
724
  if (e instanceof ApiError)
@@ -766,7 +760,10 @@ export function registerApplyCommand(program) {
766
760
  }));
767
761
  let operation;
768
762
  try {
769
- operation = await client.applyImport(filteredManifest, hasBreaking);
763
+ operation = await client.applyImport(filteredManifest, {
764
+ acknowledgeBreakingChanges: hasBreaking,
765
+ allowDeletions: opts.allowDeletions === true,
766
+ });
770
767
  }
771
768
  catch (e) {
772
769
  if (e instanceof ApiError) {
@@ -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;
@@ -7,7 +7,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
9
  import { getDebugLogger, debugEnvForSubprocess } from '../lib/debug-logger.js';
10
- import { bindAnalyticsSession, emitSessionStarted } from '../analytics/index.js';
11
10
  function findCliPath() {
12
11
  return join(fileURLToPath(import.meta.url), '..', '..', '..', '..', 'bin', 'cli.js');
13
12
  }
@@ -17,7 +16,7 @@ async function runStep(args, cliPath, env = {}, streamStderr = false) {
17
16
  debug.event('import', 'subprocess.spawn', { cliPath, args });
18
17
  return new Promise((res) => {
19
18
  const child = execFile('node', [cliPath, ...args], {
20
- env: debugEnvForSubprocess({ ...process.env, ...env, EDS_IMPORT_PIPELINE: '1' }),
19
+ env: debugEnvForSubprocess({ ...process.env, ...env }),
21
20
  });
22
21
  let stdout = '';
23
22
  let stderr = '';
@@ -109,10 +108,6 @@ export async function runPipeline(opts, progressWriter, cliPathOverride) {
109
108
  inputPath: projectRoot,
110
109
  outDir,
111
110
  });
112
- await bindAnalyticsSession(sessionId, {
113
- ...(opts.spaceId ? { space_key: opts.spaceId, environment_key: opts.environmentId } : {}),
114
- });
115
- await emitSessionStarted('import');
116
111
  progressWriter(`Experience Design System CLI — Pipeline Import`);
117
112
  progressWriter(`Project: ${projectRoot}`);
118
113
  progressWriter(`Output: ${outDir}`);
@@ -423,6 +418,8 @@ export async function runPipeline(opts, progressWriter, cliPathOverride) {
423
418
  pushArgs.push('--host', opts.host);
424
419
  if (opts.verbose)
425
420
  pushArgs.push('--verbose');
421
+ if (opts.allowDeletions)
422
+ pushArgs.push('--allow-deletions');
426
423
  pushArgs.push('--yes');
427
424
  const pushStepId = createStep(db, sessionId, 'apply push', {
428
425
  components: componentsPath,
@@ -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();
@@ -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: {
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-5146e8a.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-5146e8a.0",
37
+ "@contentful/experience-design-system-types": "2.23.2-dev-build-5146e8a.0",
38
+ "@contentful/experience-design-system-generation": "2.23.2-dev-build-5146e8a.0",
39
+ "@contentful/experience-design-system-extraction": "2.23.2-dev-build-5146e8a.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;
@@ -1,157 +0,0 @@
1
- import { cliVersion, trackEvent } from './client.js';
2
- import { generateSessionId } from '../session/session-id.js';
3
- import { normalizeCommand } from './normalize.js';
4
- import { getOsName } from './os.js';
5
- let pending = null;
6
- let sessionId;
7
- let sessionStartedEmitted = false;
8
- function isPipelineStep() {
9
- return process.env.EDS_IMPORT_PIPELINE === '1';
10
- }
11
- function buildBaseProps(command) {
12
- return {
13
- dsi_session_id: sessionId,
14
- command,
15
- ...(isPipelineStep() ? { is_pipeline_step: true } : {}),
16
- };
17
- }
18
- function mergeContext(target, source) {
19
- if (source.space_key !== undefined)
20
- target.space_key = source.space_key;
21
- if (source.environment_key !== undefined)
22
- target.environment_key = source.environment_key;
23
- if (source.x_contentful_request_id !== undefined) {
24
- target.x_contentful_request_id = source.x_contentful_request_id;
25
- }
26
- }
27
- /** Record the start of a tracked command. Invoked is deferred until a session is bound. */
28
- export function noteCommandStart(commandChain) {
29
- const command = normalizeCommand(commandChain);
30
- if (!command)
31
- return;
32
- pending = {
33
- command,
34
- startedAt: Date.now(),
35
- invoked: false,
36
- terminalEmitted: false,
37
- context: {},
38
- completion: {},
39
- };
40
- }
41
- /** Bind a session id, minting one when the command has no pipeline session. */
42
- export async function bindAnalyticsSessionId(sessionId, context) {
43
- const id = sessionId ?? generateSessionId();
44
- await bindAnalyticsSession(id, context);
45
- return id;
46
- }
47
- /** Attach Contentful target context for the active command. */
48
- export function setCommandContext(context) {
49
- if (!pending)
50
- return;
51
- mergeContext(pending.context, context);
52
- mergeContext(pending.completion, context);
53
- }
54
- /** Merge optional completion fields before the terminal event fires. */
55
- export function enrichCommandResult(fields) {
56
- if (!pending)
57
- return;
58
- Object.assign(pending.completion, fields);
59
- mergeContext(pending.context, fields);
60
- }
61
- export function getBoundSessionId() {
62
- return sessionId;
63
- }
64
- /** Bind the pipeline session and emit invoked once both command and session are known. */
65
- export async function bindAnalyticsSession(id, context) {
66
- sessionId = id;
67
- if (context)
68
- setCommandContext(context);
69
- await emitInvokedIfReady();
70
- }
71
- /** Emit session_started once per new pipeline head. */
72
- export async function emitSessionStarted(entryCommand) {
73
- if (!sessionId || sessionStartedEmitted)
74
- return;
75
- sessionStartedEmitted = true;
76
- await trackEvent('dsi_cli_session_started', {
77
- dsi_session_id: sessionId,
78
- entry_command: entryCommand,
79
- cli_version: cliVersion(),
80
- node_version: process.version,
81
- os_name: getOsName(),
82
- }, sessionId);
83
- await emitInvokedIfReady();
84
- }
85
- async function emitInvokedIfReady() {
86
- if (!pending || pending.invoked || !sessionId)
87
- return;
88
- pending.invoked = true;
89
- await trackEvent('dsi_cli_command_invoked', {
90
- ...buildBaseProps(pending.command),
91
- ...pending.context,
92
- }, sessionId);
93
- }
94
- export async function completeActiveCommand() {
95
- if (!pending || pending.terminalEmitted || !sessionId || !pending.invoked)
96
- return;
97
- pending.terminalEmitted = true;
98
- const durationMs = Date.now() - pending.startedAt;
99
- await trackEvent('dsi_cli_command_completed', {
100
- ...buildBaseProps(pending.command),
101
- ...pending.context,
102
- ...pending.completion,
103
- outcome: 'ok',
104
- duration_ms: durationMs,
105
- }, sessionId);
106
- }
107
- export async function failActiveCommand(fields = {}) {
108
- if (!pending || pending.terminalEmitted || !sessionId || !pending.invoked)
109
- return;
110
- pending.terminalEmitted = true;
111
- const durationMs = Date.now() - pending.startedAt;
112
- const outcome = fields.exit_code === 130 ? 'interrupted' : 'error';
113
- await trackEvent('dsi_cli_command_failed', {
114
- ...buildBaseProps(pending.command),
115
- ...pending.context,
116
- ...fields,
117
- outcome,
118
- duration_ms: durationMs,
119
- }, sessionId);
120
- }
121
- let exitHookRegistered = false;
122
- /** Best-effort terminal event when the process exits without a clean postAction hook. */
123
- export function registerAnalyticsExitHook() {
124
- if (exitHookRegistered)
125
- return;
126
- exitHookRegistered = true;
127
- process.on('exit', (code) => {
128
- if (!pending || pending.terminalEmitted || !sessionId || !pending.invoked)
129
- return;
130
- const durationMs = Date.now() - pending.startedAt;
131
- const outcome = code === 0 ? 'ok' : code === 130 ? 'interrupted' : 'error';
132
- const event = code === 0 ? 'dsi_cli_command_completed' : 'dsi_cli_command_failed';
133
- const props = code === 0
134
- ? {
135
- ...buildBaseProps(pending.command),
136
- ...pending.context,
137
- ...pending.completion,
138
- outcome: 'ok',
139
- duration_ms: durationMs,
140
- }
141
- : {
142
- ...buildBaseProps(pending.command),
143
- ...pending.context,
144
- outcome: outcome === 'interrupted' ? 'interrupted' : 'error',
145
- duration_ms: durationMs,
146
- exit_code: code,
147
- };
148
- // Synchronous best-effort — async flush cannot run on 'exit'.
149
- void trackEvent(event, props, sessionId);
150
- });
151
- }
152
- export function resetAnalyticsStateForTests() {
153
- pending = null;
154
- sessionId = undefined;
155
- sessionStartedEmitted = false;
156
- exitHookRegistered = false;
157
- }
@@ -1,29 +0,0 @@
1
- /** Tracked CLI commands — closed set aligned with the published event schema. */
2
- export type DsiCliCommand = 'analyze_extract' | 'analyze_select' | 'generate_components' | 'generate_tokens' | 'generate_edit' | 'apply_preview' | 'apply_select' | 'apply_push' | 'print_components' | 'print_tokens' | 'import';
3
- export type EntryCommand = 'analyze_extract' | 'import';
4
- export type OsName = 'macOS' | 'Linux' | 'Windows' | 'Android' | 'other';
5
- export type WriteResult = {
6
- created_count: number;
7
- updated_count: number;
8
- failed_count: number;
9
- };
10
- export type CommandContext = {
11
- space_key?: string;
12
- environment_key?: string;
13
- x_contentful_request_id?: string;
14
- };
15
- export type CommandCompletion = CommandContext & {
16
- dsi_operation_id?: string;
17
- extracted_component_count?: number;
18
- accepted_component_count?: number;
19
- component_type_result?: WriteResult;
20
- design_token_result?: WriteResult;
21
- };
22
- export type CommandFailure = CommandContext & {
23
- dsi_operation_id?: string;
24
- error_name?: string;
25
- error_code?: string;
26
- http_status_code?: number;
27
- x_contentful_request_id?: string;
28
- exit_code?: number;
29
- };
@@ -1 +0,0 @@
1
- export {};