@contentful/experience-design-system-cli 2.34.5-dev-build-f6f44159.0 → 2.34.5-dev-build-a95aeeba.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.
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contentful/experience-design-system-cli",
3
- "version": "2.34.5-dev-build-f6f44159.0",
3
+ "version": "2.34.5-dev-build-a95aeeba.0",
4
4
  "description": "Contentful Experiences design system import CLI",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -11,6 +11,8 @@ export declare function buildGenerateComponentsArgs(opts: {
11
11
  generatePromptPath?: string;
12
12
  promptOverrides?: string[];
13
13
  existingEntitiesPath?: string;
14
+ restoreCache?: boolean;
15
+ cachedComponents?: string[];
14
16
  }): string[];
15
17
  export declare function buildGenerateTokensArgs(opts: {
16
18
  rawTokensPath: string;
package/dist/src/index.js CHANGED
@@ -110,41 +110,58 @@ var init_credentials_store = __esm({
110
110
  var render_with_goodbye_exports = {};
111
111
  __export(render_with_goodbye_exports, {
112
112
  GoodbyeBoundary: () => GoodbyeBoundary,
113
- renderWithGoodbye: () => renderWithGoodbye
113
+ renderWithGoodbye: () => renderWithGoodbye,
114
+ useScreenTransitionClear: () => useScreenTransitionClear
114
115
  });
115
- import { useEffect, useRef, useState } from "react";
116
- import { render, Text } from "ink";
116
+ import { createContext, useCallback, useContext, useEffect, useRef, useState } from "react";
117
+ import { render, Text, useInput } from "ink";
117
118
  import { Fragment, jsx } from "react/jsx-runtime";
119
+ function useScreenTransitionClear() {
120
+ return useContext(ScreenTransitionClearContext);
121
+ }
118
122
  function GoodbyeBoundary({ children }) {
119
123
  const [isExiting, setIsExiting] = useState(false);
120
124
  const isExitingRef = useRef(false);
125
+ const exitTimerRef = useRef(void 0);
126
+ const requestExit = useCallback(() => {
127
+ if (isExitingRef.current) return;
128
+ isExitingRef.current = true;
129
+ setIsExiting(true);
130
+ exitTimerRef.current = setTimeout(() => process.exit(0), GOODBYE_RENDER_DELAY_MS);
131
+ }, []);
132
+ useInput((input, key) => {
133
+ if (key.ctrl && input === "c") requestExit();
134
+ });
121
135
  useEffect(() => {
122
- let exitTimer;
123
- const handleSigint = () => {
124
- if (isExitingRef.current) return;
125
- isExitingRef.current = true;
126
- setIsExiting(true);
127
- exitTimer = setTimeout(() => process.exit(0), GOODBYE_RENDER_DELAY_MS);
128
- };
129
- process.on("SIGINT", handleSigint);
136
+ process.on("SIGINT", requestExit);
130
137
  return () => {
131
- process.off("SIGINT", handleSigint);
132
- if (exitTimer) clearTimeout(exitTimer);
138
+ process.off("SIGINT", requestExit);
139
+ if (exitTimerRef.current) clearTimeout(exitTimerRef.current);
133
140
  };
134
- }, []);
141
+ }, [requestExit]);
135
142
  return isExiting ? /* @__PURE__ */ jsx(Text, { children: "Goodbye!" }) : /* @__PURE__ */ jsx(Fragment, { children });
136
143
  }
137
144
  function renderWithGoodbye(element, options) {
138
- return render(/* @__PURE__ */ jsx(GoodbyeBoundary, { children: element }), {
139
- ...options,
140
- exitOnCtrlC: false
141
- });
145
+ const instanceRef = {};
146
+ const clearScreen = () => {
147
+ instanceRef.current?.clear();
148
+ };
149
+ const instance = render(
150
+ /* @__PURE__ */ jsx(ScreenTransitionClearContext.Provider, { value: clearScreen, children: /* @__PURE__ */ jsx(GoodbyeBoundary, { children: element }) }),
151
+ {
152
+ ...options,
153
+ exitOnCtrlC: false
154
+ }
155
+ );
156
+ instanceRef.current = instance;
157
+ return instance;
142
158
  }
143
- var GOODBYE_RENDER_DELAY_MS;
159
+ var GOODBYE_RENDER_DELAY_MS, ScreenTransitionClearContext;
144
160
  var init_render_with_goodbye = __esm({
145
161
  "packages/experience-design-system-cli/src/tui/render-with-goodbye.tsx"() {
146
162
  "use strict";
147
163
  GOODBYE_RENDER_DELAY_MS = 50;
164
+ ScreenTransitionClearContext = createContext(() => void 0);
148
165
  }
149
166
  });
150
167
 
@@ -1952,8 +1969,7 @@ function hashComponentShape(component) {
1952
1969
  })),
1953
1970
  slots: component.slots.map((s) => ({
1954
1971
  name: s.name,
1955
- isDefault: s.isDefault,
1956
- allowedComponents: [...s.allowedComponents ?? []].sort()
1972
+ isDefault: s.isDefault
1957
1973
  }))
1958
1974
  };
1959
1975
  return createHash2("sha256").update(JSON.stringify(payload)).digest("hex");
@@ -3543,15 +3559,38 @@ function lookupCacheByEntity(db, entityType, entityId) {
3543
3559
  };
3544
3560
  }
3545
3561
  function storeCache(db, inputHash, entityType, entityId, sourceSessionId, humanEdited, promptHash = "") {
3562
+ storeCaches(db, [{ inputHash, entityType, entityId, sourceSessionId, humanEdited, promptHash }]);
3563
+ }
3564
+ function storeCaches(db, entries) {
3565
+ if (entries.length === 0) return;
3546
3566
  const now = (/* @__PURE__ */ new Date()).toISOString();
3547
- db.prepare(
3567
+ const insert = db.prepare(
3548
3568
  `INSERT INTO generation_cache (input_hash, entity_type, entity_id, source_session_id, human_edited, created_at, updated_at, prompt_hash)
3549
3569
  VALUES (?, ?, ?, ?, ?, ?, ?, ?)
3550
3570
  ON CONFLICT(input_hash, prompt_hash, entity_type, entity_id) DO UPDATE SET
3551
3571
  source_session_id = excluded.source_session_id,
3552
3572
  human_edited = CASE WHEN generation_cache.human_edited = 1 THEN 1 ELSE excluded.human_edited END,
3553
3573
  updated_at = excluded.updated_at`
3554
- ).run(inputHash, entityType, entityId, sourceSessionId, humanEdited ? 1 : 0, now, now, promptHash);
3574
+ );
3575
+ db.exec("BEGIN");
3576
+ try {
3577
+ for (const entry of entries) {
3578
+ insert.run(
3579
+ entry.inputHash,
3580
+ entry.entityType,
3581
+ entry.entityId,
3582
+ entry.sourceSessionId,
3583
+ entry.humanEdited ? 1 : 0,
3584
+ now,
3585
+ now,
3586
+ entry.promptHash ?? ""
3587
+ );
3588
+ }
3589
+ db.exec("COMMIT");
3590
+ } catch (e) {
3591
+ db.exec("ROLLBACK");
3592
+ throw e;
3593
+ }
3555
3594
  }
3556
3595
  function storeScannedFiles(db, sessionId2, filePaths) {
3557
3596
  db.exec("BEGIN");
@@ -3601,9 +3640,9 @@ function loadSlotCycles(db, sessionId2) {
3601
3640
  suggestedBreak: r.suggested_break_json ? JSON.parse(r.suggested_break_json) : null
3602
3641
  }));
3603
3642
  }
3604
- function copyComponentFromCache(db, sourceSessionId, targetSessionId, componentId) {
3643
+ function copyComponentFromCache(db, sourceSessionId, targetSessionId, componentId, manageTransaction = true) {
3605
3644
  const now = (/* @__PURE__ */ new Date()).toISOString();
3606
- db.exec("BEGIN");
3645
+ if (manageTransaction) db.exec("BEGIN");
3607
3646
  try {
3608
3647
  const srcComp = db.prepare(
3609
3648
  `SELECT description, status, component_description_rationale, props_rationale, slots_rationale
@@ -3679,6 +3718,19 @@ function copyComponentFromCache(db, sourceSessionId, targetSessionId, componentI
3679
3718
  insertSAC.run(targetSessionId, componentId, sac.slot_name, sac.allowed_component, sac.position);
3680
3719
  }
3681
3720
  db.prepare("UPDATE sessions SET updated_at = ? WHERE id = ?").run(now, targetSessionId);
3721
+ if (manageTransaction) db.exec("COMMIT");
3722
+ } catch (e) {
3723
+ if (manageTransaction) db.exec("ROLLBACK");
3724
+ throw e;
3725
+ }
3726
+ }
3727
+ function copyComponentsFromCache(db, entries) {
3728
+ if (entries.length === 0) return;
3729
+ db.exec("BEGIN");
3730
+ try {
3731
+ for (const entry of entries) {
3732
+ copyComponentFromCache(db, entry.sourceSessionId, entry.targetSessionId, entry.componentId, false);
3733
+ }
3682
3734
  db.exec("COMMIT");
3683
3735
  } catch (e) {
3684
3736
  db.exec("ROLLBACK");
@@ -4821,43 +4873,41 @@ function printFallbackInstructions(options) {
4821
4873
  lines.push(` Re-run the generate command with the agent available, or use --dry-run to inspect the prompt.`);
4822
4874
  process.stderr.write(lines.join("\n") + "\n");
4823
4875
  }
4824
- async function runOneComponent(agent, model, db, sessionId2, component, tokensInline, tokenMapInline, index, total, verbose, noCache, skillPathOverride, skillContentOverride, promptHash, existingContentfulEntities, existingTokensInline) {
4876
+ function cachedComponentResult(componentName, warnings = []) {
4877
+ return {
4878
+ componentName,
4879
+ classified: 0,
4880
+ excluded: 0,
4881
+ slots: 0,
4882
+ warnings,
4883
+ failed: false,
4884
+ cached: true,
4885
+ renamedSlotsCount: 0
4886
+ };
4887
+ }
4888
+ function normalizeComponentForCache(component) {
4889
+ const slots = component.slots.map((slot, index, allSlots) => ({
4890
+ ...slot,
4891
+ name: slot.name.trim() || (allSlots.length === 1 ? "children" : `slot_${index}`)
4892
+ }));
4893
+ return { ...component, slots };
4894
+ }
4895
+ function lookupComponentCache(db, component, promptHash) {
4896
+ const normalized = normalizeComponentForCache(component);
4897
+ const inputHash = hashComponentShape(normalized);
4898
+ const cached = lookupCache(db, inputHash, "component", component.component_id, promptHash);
4899
+ if (cached) return cached;
4900
+ const legacyInputHash = hashComponentShape(component);
4901
+ return legacyInputHash === inputHash ? null : lookupCache(db, legacyInputHash, "component", component.component_id, promptHash);
4902
+ }
4903
+ function resolveComponentCache(db, component, promptHash) {
4904
+ const cached = lookupComponentCache(db, component, promptHash);
4905
+ if (cached) return { entry: cached, humanEdited: cached.humanEdited };
4906
+ const pinned = lookupCacheByEntity(db, "component", component.component_id);
4907
+ return pinned?.humanEdited ? { entry: pinned, humanEdited: true } : null;
4908
+ }
4909
+ async function runOneComponent(agent, model, db, sessionId2, component, tokensInline, tokenMapInline, index, total, verbose, noCache, skillPathOverride, skillContentOverride, promptHash, existingContentfulEntities, existingTokensInline, precomputedCachedNames) {
4825
4910
  const pos = c.dim(`[${index + 1}/${total}]`);
4826
- if (!noCache) {
4827
- const inputHash = hashComponentShape(component);
4828
- const cached = lookupCache(db, inputHash, "component", component.component_id, promptHash);
4829
- if (cached) {
4830
- copyComponentFromCache(db, cached.sourceSessionId, sessionId2, component.component_id);
4831
- process.stderr.write(` ${pos} ${c.bold(component.name)} ${c.green("cached")}
4832
- `);
4833
- return {
4834
- componentName: component.name,
4835
- classified: 0,
4836
- excluded: 0,
4837
- slots: 0,
4838
- warnings: [],
4839
- failed: false,
4840
- cached: true,
4841
- renamedSlotsCount: 0
4842
- };
4843
- }
4844
- const pinned = lookupCacheByEntity(db, "component", component.component_id);
4845
- if (pinned?.humanEdited) {
4846
- copyComponentFromCache(db, pinned.sourceSessionId, sessionId2, component.component_id);
4847
- process.stderr.write(` ${pos} ${c.bold(component.name)} ${c.cyan("pinned (human-edited)")}
4848
- `);
4849
- return {
4850
- componentName: component.name,
4851
- classified: 0,
4852
- excluded: 0,
4853
- slots: 0,
4854
- warnings: [`${component.name}: source changed but human edits preserved`],
4855
- failed: false,
4856
- cached: true,
4857
- renamedSlotsCount: 0
4858
- };
4859
- }
4860
- }
4861
4911
  const { renames, warnings: renameWarnings } = renameEmptySlots(
4862
4912
  db,
4863
4913
  sessionId2,
@@ -4872,6 +4922,37 @@ async function runOneComponent(agent, model, db, sessionId2, component, tokensIn
4872
4922
  for (const w of renameWarnings) process.stderr.write(` ${c.yellow("\u26A0")} ${w}
4873
4923
  `);
4874
4924
  }
4925
+ const cacheComponent = normalizeComponentForCache(component);
4926
+ if (!noCache && precomputedCachedNames.has(component.name)) {
4927
+ process.stderr.write(` ${pos} ${c.bold(component.name)} ${c.green("cached")}
4928
+ `);
4929
+ return cachedComponentResult(component.name);
4930
+ }
4931
+ if (!noCache) {
4932
+ const inputHash = hashComponentShape(cacheComponent);
4933
+ const resolution = resolveComponentCache(db, component, promptHash);
4934
+ if (resolution && !resolution.humanEdited) {
4935
+ copyComponentFromCache(db, resolution.entry.sourceSessionId, sessionId2, component.component_id);
4936
+ storeCache(
4937
+ db,
4938
+ inputHash,
4939
+ "component",
4940
+ component.component_id,
4941
+ resolution.entry.sourceSessionId,
4942
+ resolution.entry.humanEdited,
4943
+ promptHash
4944
+ );
4945
+ process.stderr.write(` ${pos} ${c.bold(component.name)} ${c.green("cached")}
4946
+ `);
4947
+ return cachedComponentResult(component.name);
4948
+ }
4949
+ if (resolution?.humanEdited) {
4950
+ copyComponentFromCache(db, resolution.entry.sourceSessionId, sessionId2, component.component_id);
4951
+ process.stderr.write(` ${pos} ${c.bold(component.name)} ${c.cyan("pinned (human-edited)")}
4952
+ `);
4953
+ return cachedComponentResult(component.name, [`${component.name}: source changed but human edits preserved`]);
4954
+ }
4955
+ }
4875
4956
  const rawComponentsInline = JSON.stringify(
4876
4957
  [
4877
4958
  {
@@ -4941,7 +5022,7 @@ ${outputBuf}`);
4941
5022
  }
4942
5023
  const applied = applyToolCalls(db, sessionId2, component.component_id, component.name, calls, warnings);
4943
5024
  if (!noCache) {
4944
- const inputHash = hashComponentShape(component);
5025
+ const inputHash = hashComponentShape(cacheComponent);
4945
5026
  storeCache(db, inputHash, "component", component.component_id, sessionId2, false, promptHash);
4946
5027
  }
4947
5028
  return {
@@ -4965,7 +5046,7 @@ ${outputBuf}`);
4965
5046
  renamedSlotsCount: renames.length
4966
5047
  };
4967
5048
  }
4968
- async function runAllComponents(agent, model, db, sessionId2, components, tokensInline, tokenMapInline, verbose, noCache, skillPathOverride, skillContentOverride, promptHash, existingContentfulEntities, existingTokensInline) {
5049
+ async function runAllComponents(agent, model, db, sessionId2, components, tokensInline, tokenMapInline, verbose, noCache, skillPathOverride, skillContentOverride, promptHash, existingContentfulEntities, existingTokensInline, precomputedCachedNames) {
4969
5050
  const concurrency = Number(process.env.EDS_GENERATE_CONCURRENCY ?? DEFAULT_COMPONENT_CONCURRENCY);
4970
5051
  process.stderr.write(
4971
5052
  `Categorizing ${c.bold(String(components.length))} component${components.length === 1 ? "" : "s"}` + c.dim(` (concurrency: ${concurrency})`) + "\n"
@@ -4992,7 +5073,8 @@ async function runAllComponents(agent, model, db, sessionId2, components, tokens
4992
5073
  skillContentOverride,
4993
5074
  promptHash,
4994
5075
  existingContentfulEntities,
4995
- existingTokensInline
5076
+ existingTokensInline,
5077
+ precomputedCachedNames
4996
5078
  );
4997
5079
  completed += 1;
4998
5080
  process.stderr.write(`${formatGenerateProgressLine(completed, components.length, results[i].componentName)}
@@ -5021,6 +5103,15 @@ async function loadAcceptedNames(sessionId2) {
5021
5103
  return null;
5022
5104
  }
5023
5105
  }
5106
+ function parsePrecomputedCachedNames(value) {
5107
+ if (!value) return /* @__PURE__ */ new Set();
5108
+ try {
5109
+ const parsed = JSON.parse(value);
5110
+ return new Set(Array.isArray(parsed) ? parsed.filter((name) => typeof name === "string") : []);
5111
+ } catch {
5112
+ return /* @__PURE__ */ new Set();
5113
+ }
5114
+ }
5024
5115
  async function runGenerateSkill(skill, opts, verbose = false) {
5025
5116
  const savedCreds = await readExperiencesCredentials();
5026
5117
  const agentName = opts.agent ?? savedCreds.agent;
@@ -5175,6 +5266,7 @@ async function runGenerateSkill(skill, opts, verbose = false) {
5175
5266
  if (skill === "components" && allComponents && sessionId2) {
5176
5267
  const db = openPipelineDb();
5177
5268
  let componentResults;
5269
+ const precomputedCachedNames = parsePrecomputedCachedNames(opts.cachedComponents);
5178
5270
  try {
5179
5271
  const existingContentfulEntitiesHashInputs = [];
5180
5272
  if (existingTokensInline) existingContentfulEntitiesHashInputs.push(hashContent(existingTokensInline));
@@ -5186,6 +5278,52 @@ async function runGenerateSkill(skill, opts, verbose = false) {
5186
5278
  existingContentfulEntitiesHashInputs,
5187
5279
  generatePrompt
5188
5280
  );
5281
+ if (opts.cacheStatus) {
5282
+ const cacheEnabled = opts.cache !== false && process.env.EDS_NO_CACHE !== "1";
5283
+ const cachedComponents = cacheEnabled ? allComponents.flatMap((component) => {
5284
+ const resolution = resolveComponentCache(db, component, promptHash);
5285
+ return resolution ? [{ component, resolution }] : [];
5286
+ }) : [];
5287
+ if (cacheEnabled && opts.restoreCache) {
5288
+ const restorations = [];
5289
+ const rekeyed = [];
5290
+ const componentStatus = db.prepare(
5291
+ "SELECT status FROM raw_components WHERE session_id = ? AND component_id = ?"
5292
+ );
5293
+ for (const { component, resolution } of cachedComponents) {
5294
+ const current = componentStatus.get(sessionId2, component.component_id);
5295
+ if (current?.status === "generated") continue;
5296
+ renameEmptySlots(db, sessionId2, component.component_id, component.name, component.slots.length);
5297
+ restorations.push({
5298
+ sourceSessionId: resolution.entry.sourceSessionId,
5299
+ targetSessionId: sessionId2,
5300
+ componentId: component.component_id
5301
+ });
5302
+ rekeyed.push({ component, cached: resolution.entry });
5303
+ }
5304
+ copyComponentsFromCache(db, restorations);
5305
+ storeCaches(
5306
+ db,
5307
+ rekeyed.map(({ component, cached }) => ({
5308
+ inputHash: hashComponentShape(normalizeComponentForCache(component)),
5309
+ entityType: "component",
5310
+ entityId: component.component_id,
5311
+ sourceSessionId: cached.sourceSessionId,
5312
+ humanEdited: cached.humanEdited,
5313
+ promptHash
5314
+ }))
5315
+ );
5316
+ }
5317
+ const fullyCached = cacheEnabled && cachedComponents.length === allComponents.length;
5318
+ process.stdout.write(`cache-status=${fullyCached ? "hit" : "miss"}
5319
+ `);
5320
+ process.stdout.write(
5321
+ `cache-components=${JSON.stringify(cachedComponents.map(({ component }) => component.name))}
5322
+ `
5323
+ );
5324
+ await exitWithAnalytics(0);
5325
+ return;
5326
+ }
5189
5327
  componentResults = await runAllComponents(
5190
5328
  agent,
5191
5329
  model,
@@ -5200,7 +5338,8 @@ async function runGenerateSkill(skill, opts, verbose = false) {
5200
5338
  generatePrompt,
5201
5339
  promptHash,
5202
5340
  existingContentfulEntities,
5203
- existingTokensInline
5341
+ existingTokensInline,
5342
+ precomputedCachedNames
5204
5343
  );
5205
5344
  } finally {
5206
5345
  db.close();
@@ -5386,6 +5525,12 @@ function registerInternalGenerateCommand(program) {
5386
5525
  "--existing-entities-path <path>",
5387
5526
  "Path to the .existing-entities.json file written after CMA credentials are supplied. When present, per-component prompts include a summary of the target space so classifications can align to existing prop names/tokens. Missing/malformed files are treated as no-op."
5388
5527
  );
5528
+ componentsCmd.option("--cache-status", "Check whether all selected component definitions are cached");
5529
+ componentsCmd.option("--restore-cache", "Restore cached component definitions into the current session");
5530
+ componentsCmd.option(
5531
+ "--cached-components <json>",
5532
+ "Component names already restored by a prior cache-status lookup; skip per-component cache lookup"
5533
+ );
5389
5534
  addAgentFlags(componentsCmd).action(async (opts) => {
5390
5535
  await runGenerateSkill("components", opts, opts.verbose ?? false);
5391
5536
  });
@@ -7145,7 +7290,7 @@ var init_contentful_urls = __esm({
7145
7290
  });
7146
7291
 
7147
7292
  // packages/experience-design-system-cli/src/apply/tui/ServerApplyView.tsx
7148
- import { Box as Box3, Text as Text4, useInput } from "ink";
7293
+ import { Box as Box3, Text as Text4, useInput as useInput2 } from "ink";
7149
7294
  import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
7150
7295
  function ServerPreviewConfirm({
7151
7296
  preview,
@@ -7155,7 +7300,7 @@ function ServerPreviewConfirm({
7155
7300
  onConfirm,
7156
7301
  onCancel
7157
7302
  }) {
7158
- useInput((input, key) => {
7303
+ useInput2((input, key) => {
7159
7304
  if (key.return) onConfirm(breakingWithImpact);
7160
7305
  else if (key.escape || input === "q") onCancel();
7161
7306
  });
@@ -7184,7 +7329,7 @@ function ServerApplyProgress({
7184
7329
  operationId,
7185
7330
  error
7186
7331
  }) {
7187
- useInput((_input, key) => {
7332
+ useInput2((_input, key) => {
7188
7333
  if (status === "error" && (key.escape || _input === "q")) process.exit(1);
7189
7334
  });
7190
7335
  return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", paddingX: 2, paddingY: 1, children: [
@@ -7212,7 +7357,7 @@ function ServerApplyProgress({
7212
7357
  ] });
7213
7358
  }
7214
7359
  function ServerApplyDone({ operation, spaceId, environmentId, host }) {
7215
- useInput((input, key) => {
7360
+ useInput2((input, key) => {
7216
7361
  if (key.escape || input === "q") {
7217
7362
  process.exit(operation.sys.status === "succeeded" ? 0 : 1);
7218
7363
  }
@@ -7326,7 +7471,6 @@ var init_terminal_capabilities = __esm({
7326
7471
 
7327
7472
  // packages/experience-design-system-cli/src/apply/command.ts
7328
7473
  import { createElement as createElement2 } from "react";
7329
- import { render as render2 } from "ink";
7330
7474
  import { readFile as readFile9, readdir as readdir2, stat as stat2 } from "node:fs/promises";
7331
7475
  import { join as join8 } from "node:path";
7332
7476
  import {
@@ -7730,11 +7874,11 @@ function registerApplyCommand(program) {
7730
7874
  environmentId,
7731
7875
  host,
7732
7876
  acknowledgeBreakingChanges: acknowledge,
7733
- rerender: (element) => instance.rerender(element),
7877
+ rerender: (element) => instance.rerender(createElement2(GoodbyeBoundary, null, element)),
7734
7878
  onDone: resolvePromise
7735
7879
  });
7736
7880
  };
7737
- const instance = render2(
7881
+ const instance = renderWithGoodbye(
7738
7882
  createElement2(ServerPreviewConfirm, {
7739
7883
  preview,
7740
7884
  spaceId,
@@ -7756,6 +7900,7 @@ var IGNORE_TOKEN_DIRS;
7756
7900
  var init_command2 = __esm({
7757
7901
  "packages/experience-design-system-cli/src/apply/command.ts"() {
7758
7902
  "use strict";
7903
+ init_render_with_goodbye();
7759
7904
  init_api_client();
7760
7905
  init_error_parser();
7761
7906
  init_cycle_detection();
@@ -10176,7 +10321,7 @@ var init_SpaceEnvironment = __esm({
10176
10321
  });
10177
10322
 
10178
10323
  // packages/experience-design-system-cli/src/import/tui/preview-confirmation-input.ts
10179
- import { useCallback } from "react";
10324
+ import { useCallback as useCallback2 } from "react";
10180
10325
  function handlePreviewConfirmationInput(_input, key, options) {
10181
10326
  if (key.return) {
10182
10327
  options.onConfirm(options.breakingWithImpact);
@@ -10185,7 +10330,7 @@ function handlePreviewConfirmationInput(_input, key, options) {
10185
10330
  return false;
10186
10331
  }
10187
10332
  function usePreviewConfirmationInput(breakingWithImpact, onConfirm) {
10188
- return useCallback(
10333
+ return useCallback2(
10189
10334
  (input, key) => handlePreviewConfirmationInput(input, key, {
10190
10335
  breakingWithImpact,
10191
10336
  onConfirm
@@ -19517,20 +19662,20 @@ var init_SearchMatchSummary = __esm({
19517
19662
  });
19518
19663
 
19519
19664
  // packages/experience-design-system-cli/src/import/tui/hooks/useOverlayPanel.ts
19520
- import { useCallback as useCallback2, useState as useState13 } from "react";
19665
+ import { useCallback as useCallback3, useState as useState13 } from "react";
19521
19666
  function useOverlayPanel(opts) {
19522
19667
  const { toggleKey, onClose } = opts;
19523
19668
  const [isOpen, setIsOpen] = useState13(false);
19524
- const open = useCallback2(() => {
19669
+ const open = useCallback3(() => {
19525
19670
  setIsOpen(true);
19526
19671
  }, []);
19527
- const close = useCallback2(() => {
19672
+ const close = useCallback3(() => {
19528
19673
  setIsOpen((prev) => {
19529
19674
  if (prev && onClose) onClose();
19530
19675
  return false;
19531
19676
  });
19532
19677
  }, [onClose]);
19533
- const handleInput = useCallback2(
19678
+ const handleInput = useCallback3(
19534
19679
  (input, key) => {
19535
19680
  if (!isOpen) return false;
19536
19681
  if (input === toggleKey || key.escape === true) {
@@ -24254,7 +24399,7 @@ var init_useFinalizePreview = __esm({
24254
24399
  });
24255
24400
 
24256
24401
  // packages/experience-design-system-cli/src/import/tui/hooks/useReviewSession.ts
24257
- import { useCallback as useCallback3, useEffect as useEffect10, useRef as useRef6, useState as useState17 } from "react";
24402
+ import { useCallback as useCallback4, useEffect as useEffect10, useRef as useRef6, useState as useState17 } from "react";
24258
24403
  function loadReviewSessionState({
24259
24404
  extractSessionId,
24260
24405
  tokenSessionId,
@@ -24304,7 +24449,7 @@ function useReviewSession({
24304
24449
  const [loading, setLoading] = useState17(true);
24305
24450
  const [loadError, setLoadError] = useState17(null);
24306
24451
  const [availableTokens, setAvailableTokens] = useState17([]);
24307
- const loadInitialState = useCallback3(async () => {
24452
+ const loadInitialState = useCallback4(async () => {
24308
24453
  const result = loadSession();
24309
24454
  if (result.error || !tokensPath) return { result, catalog: result.tokens };
24310
24455
  const catalog = (await readTokensFromPath("tokens", tokensPath)).map((token) => ({
@@ -25005,7 +25150,7 @@ var init_preview_annotations = __esm({
25005
25150
  });
25006
25151
 
25007
25152
  // packages/experience-design-system-cli/src/import/tui/useLivePreview.ts
25008
- import { useCallback as useCallback4, useEffect as useEffect12, useRef as useRef8, useState as useState20 } from "react";
25153
+ import { useCallback as useCallback5, useEffect as useEffect12, useRef as useRef8, useState as useState20 } from "react";
25009
25154
  function useLivePreview(opts) {
25010
25155
  const debounceMs = opts.debounceMs ?? DEFAULT_DEBOUNCE_MS;
25011
25156
  const [status, setStatus] = useState20("idle");
@@ -25030,7 +25175,7 @@ function useLivePreview(opts) {
25030
25175
  }
25031
25176
  };
25032
25177
  }, []);
25033
- const trigger = useCallback4(() => {
25178
+ const trigger = useCallback5(() => {
25034
25179
  const current = optsRef.current;
25035
25180
  if (!current.enabled) return;
25036
25181
  if (disabledRef.current) return;
@@ -25103,7 +25248,7 @@ var init_useLivePreview = __esm({
25103
25248
  });
25104
25249
 
25105
25250
  // packages/experience-design-system-cli/src/import/tui/hooks/useReviewPreview.ts
25106
- import { useCallback as useCallback5, useEffect as useEffect13, useState as useState21 } from "react";
25251
+ import { useCallback as useCallback6, useEffect as useEffect13, useState as useState21 } from "react";
25107
25252
  function useReviewPreview({
25108
25253
  components,
25109
25254
  loading,
@@ -25119,7 +25264,7 @@ function useReviewPreview({
25119
25264
  }) {
25120
25265
  const [previewAnnotations, setPreviewAnnotations] = useState21(/* @__PURE__ */ new Map());
25121
25266
  const [removedComponents, setRemovedComponents] = useState21([]);
25122
- const handleResult = useCallback5(
25267
+ const handleResult = useCallback6(
25123
25268
  (response) => {
25124
25269
  if (!response) return;
25125
25270
  setPreviewAnnotations(
@@ -25208,7 +25353,7 @@ var init_LivePreviewSummary = __esm({
25208
25353
  });
25209
25354
 
25210
25355
  // packages/experience-design-system-cli/src/import/tui/steps/GenerateReviewStep.tsx
25211
- import { useCallback as useCallback6, useEffect as useEffect14, useMemo as useMemo3, useRef as useRef9, useState as useState22 } from "react";
25356
+ import { useCallback as useCallback7, useEffect as useEffect14, useMemo as useMemo3, useRef as useRef9, useState as useState22 } from "react";
25212
25357
  import figures6 from "figures";
25213
25358
  import { Box as Box41, Text as Text45 } from "ink";
25214
25359
  import { Fragment as Fragment18, jsx as jsx49, jsxs as jsxs42 } from "react/jsx-runtime";
@@ -25374,10 +25519,10 @@ function GenerateReviewStepView({
25374
25519
  }) {
25375
25520
  const { columns: terminalWidth, rows: terminalRows } = useTerminalSize();
25376
25521
  const [slotCycles, setSlotCycles] = useState22([]);
25377
- const setLoadedSlotCycles = useCallback6((cycles) => {
25522
+ const setLoadedSlotCycles = useCallback7((cycles) => {
25378
25523
  setSlotCycles(cycles ?? []);
25379
25524
  }, []);
25380
- const loadSessionState = useCallback6(
25525
+ const loadSessionState = useCallback7(
25381
25526
  () => loadReviewSessionState({
25382
25527
  extractSessionId,
25383
25528
  tokenSessionId,
@@ -25999,7 +26144,7 @@ function GenerateReviewStepView({
25999
26144
  }
26000
26145
  return;
26001
26146
  }
26002
- if (input === "c" && cycleRows.length > 0) {
26147
+ if (input === "c" && !key.ctrl && !key.meta && cycleRows.length > 0) {
26003
26148
  cyclePanel.open();
26004
26149
  setCyclePanelScroll(0);
26005
26150
  setCyclesCursor(0);
@@ -26587,10 +26732,31 @@ var init_GenerateReviewStep = __esm({
26587
26732
 
26588
26733
  // packages/experience-design-system-cli/src/import/tui/runScopeGate.ts
26589
26734
  async function runScopeGate(opts) {
26735
+ const startedAt = Date.now();
26736
+ getDebugLogger().event("wizard", "scope-decision-persistence.start", {
26737
+ sessionId: opts.sessionId,
26738
+ acceptedCount: opts.decisions.accepted.length,
26739
+ rejectedCount: opts.decisions.rejected.length
26740
+ });
26590
26741
  const db = openPipelineDb();
26591
26742
  try {
26592
26743
  applyScopeDecisions(db, opts.sessionId, opts.decisions);
26593
26744
  await writeScopeDecisionsSnapshot(db, opts.sessionId, opts.decisions);
26745
+ getDebugLogger().event("wizard", "scope-decision-persistence.complete", {
26746
+ sessionId: opts.sessionId,
26747
+ acceptedCount: opts.decisions.accepted.length,
26748
+ rejectedCount: opts.decisions.rejected.length,
26749
+ durationMs: Date.now() - startedAt
26750
+ });
26751
+ } catch (error) {
26752
+ getDebugLogger().event("wizard", "scope-decision-persistence.error", {
26753
+ sessionId: opts.sessionId,
26754
+ acceptedCount: opts.decisions.accepted.length,
26755
+ rejectedCount: opts.decisions.rejected.length,
26756
+ durationMs: Date.now() - startedAt,
26757
+ error: error instanceof Error ? error.message : String(error)
26758
+ });
26759
+ throw error;
26594
26760
  } finally {
26595
26761
  db.close();
26596
26762
  }
@@ -26604,6 +26770,7 @@ var init_runScopeGate = __esm({
26604
26770
  "packages/experience-design-system-cli/src/import/tui/runScopeGate.ts"() {
26605
26771
  "use strict";
26606
26772
  init_persistence();
26773
+ init_debug_logger();
26607
26774
  init_db();
26608
26775
  }
26609
26776
  });
@@ -26857,6 +27024,10 @@ function buildGenerateComponentsArgs(opts) {
26857
27024
  if (opts.generatePromptPath) args.push("--generate-prompt-path", opts.generatePromptPath);
26858
27025
  for (const prompt of opts.promptOverrides ?? []) args.push("--prompt", prompt);
26859
27026
  if (opts.existingEntitiesPath) args.push("--existing-entities-path", opts.existingEntitiesPath);
27027
+ if (opts.restoreCache) args.push("--restore-cache");
27028
+ if (opts.cachedComponents && opts.cachedComponents.length > 0) {
27029
+ args.push("--cached-components", JSON.stringify(opts.cachedComponents));
27030
+ }
26860
27031
  return args;
26861
27032
  }
26862
27033
  function buildGenerateTokensArgs(opts) {
@@ -26940,6 +27111,17 @@ function logStep(entry) {
26940
27111
  appendFileSync2(WIZARD_LOG, line);
26941
27112
  getDebugLogger().event("wizard", "step", entry);
26942
27113
  }
27114
+ function startWizardTimer(name, payload = {}) {
27115
+ const startedAt = Date.now();
27116
+ getDebugLogger().event("wizard", `${name}.start`, payload);
27117
+ return startedAt;
27118
+ }
27119
+ function finishWizardTimer(name, startedAt, payload = {}) {
27120
+ getDebugLogger().event("wizard", `${name}.complete`, {
27121
+ ...payload,
27122
+ durationMs: Date.now() - startedAt
27123
+ });
27124
+ }
26943
27125
  function WizardApp({
26944
27126
  initialSpaceId = "",
26945
27127
  initialEnvironmentId = "master",
@@ -26962,6 +27144,7 @@ function WizardApp({
26962
27144
  const resolveWizardHost = (hostValue) => hostValue || defaultConfiguredHost;
26963
27145
  const credentialKey = (spaceId, environmentId, cmaToken, hostValue) => [spaceId.trim(), environmentId.trim(), cmaToken.trim(), resolveWizardHost(hostValue)].join("\0");
26964
27146
  const { columns: terminalWidth } = useTerminalSize();
27147
+ const clearScreenOnTransition = useScreenTransitionClear();
26965
27148
  const logInit = useRef10(false);
26966
27149
  const initialAgentAuthRef = useRef10(initialAgentAuth ?? null);
26967
27150
  if (!logInit.current) {
@@ -26975,6 +27158,8 @@ function WizardApp({
26975
27158
  });
26976
27159
  const allowEmptyDeleteAllRef = useRef10(false);
26977
27160
  const generateChildRef = useRef10(null);
27161
+ const generationCachePreflightRef = useRef10(null);
27162
+ const selectionToReviewStartedAtRef = useRef10(null);
26978
27163
  const credentialsValidationIdRef = useRef10(0);
26979
27164
  const pendingCredentialValidationRef = useRef10(null);
26980
27165
  const validatedCredentialsKeyRef = useRef10(null);
@@ -26982,6 +27167,7 @@ function WizardApp({
26982
27167
  const extractPromiseRef = useRef10(null);
26983
27168
  const extractStartedRef = useRef10(false);
26984
27169
  const credentialsReadyRef = useRef10(false);
27170
+ const agentAuthVerifiedRef = useRef10(false);
26985
27171
  const scopeGateCompletedRef = useRef10(false);
26986
27172
  const existingEntitiesPromiseRef = useRef10(null);
26987
27173
  const generatePromiseRef = useRef10(null);
@@ -27052,7 +27238,10 @@ function WizardApp({
27052
27238
  tokensPath: state.tokensPath
27053
27239
  };
27054
27240
  }, [state.extractSessionId, state.tokensPath]);
27055
- const update = (partial) => {
27241
+ const update = (partial, options = {}) => {
27242
+ if (partial.step !== void 0 && partial.step !== state.step && options.clearScreen !== false) {
27243
+ clearScreenOnTransition();
27244
+ }
27056
27245
  const sanitized = { ...partial };
27057
27246
  if (sanitized.serverPreview) {
27058
27247
  const p = sanitized.serverPreview;
@@ -27099,12 +27288,41 @@ function WizardApp({
27099
27288
  logStep({ update: sanitized });
27100
27289
  setState((prev) => ({ ...prev, ...partial }));
27101
27290
  };
27102
- const runAgentAuthCheck = async (nextStep) => {
27291
+ const finishSelectionToReviewTimer = (mode) => {
27292
+ const startedAt = selectionToReviewStartedAtRef.current;
27293
+ if (startedAt === null) return;
27294
+ selectionToReviewStartedAtRef.current = null;
27295
+ finishWizardTimer("selection-to-review", startedAt, { mode });
27296
+ };
27297
+ const runAgentAuthCheck = async (nextStep, deferStep = false) => {
27298
+ const timer = startWizardTimer("agent-auth-validation", {
27299
+ agent: state.agent,
27300
+ deferred: deferStep,
27301
+ nextStep
27302
+ });
27303
+ if (agentAuthVerifiedRef.current) {
27304
+ finishWizardTimer("agent-auth-validation", timer, { status: "already-verified" });
27305
+ if (!deferStep) update({ step: nextStep });
27306
+ return true;
27307
+ }
27103
27308
  const authCheckStepNumber = state.tokensPath ? 4 : 3;
27104
27309
  const prefetchedAuth = initialAgentAuthRef.current;
27105
27310
  initialAgentAuthRef.current = null;
27106
- if (!prefetchedAuth) update({ step: "checking-claude-auth", authCheckStepNumber });
27107
- const status = await (prefetchedAuth ?? checkAgentAuth(state.agent));
27311
+ if (!prefetchedAuth && !deferStep) update({ step: "checking-claude-auth", authCheckStepNumber });
27312
+ let status;
27313
+ try {
27314
+ status = await (prefetchedAuth ?? checkAgentAuth(state.agent));
27315
+ } catch (error) {
27316
+ finishWizardTimer("agent-auth-validation", timer, {
27317
+ status: "error",
27318
+ error: error instanceof Error ? error.message : String(error)
27319
+ });
27320
+ throw error;
27321
+ }
27322
+ finishWizardTimer("agent-auth-validation", timer, {
27323
+ status,
27324
+ prefetched: Boolean(prefetchedAuth)
27325
+ });
27108
27326
  if (status === "not-found") {
27109
27327
  update({
27110
27328
  step: "error",
@@ -27128,7 +27346,8 @@ If you are using AWS Bedrock, run:
27128
27346
  });
27129
27347
  return false;
27130
27348
  }
27131
- update({ step: nextStep });
27349
+ agentAuthVerifiedRef.current = true;
27350
+ if (!deferStep) update({ step: nextStep });
27132
27351
  return true;
27133
27352
  };
27134
27353
  const runGenerateTokens = async (rawTokensPath, outDir, forceRegenerate = false) => {
@@ -27183,7 +27402,13 @@ If you are using AWS Bedrock, run:
27183
27402
  update({ tokensPath, tokenSessionId, tokenCount, tokenGenerationStatus: "complete" });
27184
27403
  return true;
27185
27404
  };
27186
- const runMapTokens2 = async (sessionId2) => {
27405
+ const runMapTokens2 = async (sessionId2, silent = false) => {
27406
+ const timer = startWizardTimer("token-mapping", {
27407
+ sessionId: sessionId2,
27408
+ silent,
27409
+ noCache: effectiveNoCache,
27410
+ skipAgent: skipMapTokens
27411
+ });
27187
27412
  let mappablePropCount = 0;
27188
27413
  let rawTokenCount = 0;
27189
27414
  try {
@@ -27210,13 +27435,22 @@ If you are using AWS Bedrock, run:
27210
27435
  errorStep: "map tokens",
27211
27436
  errorMessage: error instanceof Error ? error.message : String(error)
27212
27437
  });
27438
+ finishWizardTimer("token-mapping", timer, {
27439
+ status: "error",
27440
+ error: error instanceof Error ? error.message : String(error)
27441
+ });
27213
27442
  return false;
27214
27443
  }
27215
27444
  if (!shouldRunMapTokens({ mappablePropCount, rawTokenCount })) {
27216
- update({ mapTokensEligible: false, mapTokensStatus: "complete" });
27445
+ if (!silent) update({ mapTokensEligible: false, mapTokensStatus: "complete" });
27446
+ finishWizardTimer("token-mapping", timer, {
27447
+ status: "skipped",
27448
+ mappablePropCount,
27449
+ rawTokenCount
27450
+ });
27217
27451
  return true;
27218
27452
  }
27219
- update({ mapTokensEligible: true, mapTokensStatus: "running" });
27453
+ if (!silent) update({ mapTokensEligible: true, mapTokensStatus: "running", step: "generating" });
27220
27454
  const args = buildMapTokensArgs({
27221
27455
  sessionId: sessionId2,
27222
27456
  agent: state.agent,
@@ -27234,9 +27468,21 @@ If you are using AWS Bedrock, run:
27234
27468
  errorStep: "map tokens",
27235
27469
  errorMessage: result.stderr.trim() || "Unknown error"
27236
27470
  });
27471
+ finishWizardTimer("token-mapping", timer, {
27472
+ status: "error",
27473
+ exitCode: result.exitCode,
27474
+ mappablePropCount,
27475
+ rawTokenCount
27476
+ });
27237
27477
  return false;
27238
27478
  }
27239
- update({ mapTokensStatus: "complete" });
27479
+ if (!silent) update({ mapTokensStatus: "complete" });
27480
+ finishWizardTimer("token-mapping", timer, {
27481
+ status: "complete",
27482
+ exitCode: result.exitCode,
27483
+ mappablePropCount,
27484
+ rawTokenCount
27485
+ });
27240
27486
  return true;
27241
27487
  };
27242
27488
  const startExistingEntitiesFetch = (projectPath, outDir) => {
@@ -27486,7 +27732,7 @@ Make sure this path contains TypeScript/React/Vue component files (.tsx, .ts, .v
27486
27732
  generateProgress: null
27487
27733
  }));
27488
27734
  };
27489
- const buildGenerateArgs = (extractSessionId, tokensPath, generatePromptPath2) => buildGenerateProcessArgs({
27735
+ const buildGenerateOptions = (extractSessionId, tokensPath, generatePromptPath2, cachedComponents) => ({
27490
27736
  sessionId: extractSessionId,
27491
27737
  tokensPath,
27492
27738
  agent: state.agent,
@@ -27495,8 +27741,127 @@ Make sure this path contains TypeScript/React/Vue component files (.tsx, .ts, .v
27495
27741
  noCache: effectiveNoCache,
27496
27742
  ...state.existingEntitiesPath ? { existingEntitiesPath: state.existingEntitiesPath } : {},
27497
27743
  ...generatePromptPath2 ? { generatePromptPath: generatePromptPath2 } : {},
27498
- promptOverrides
27744
+ promptOverrides,
27745
+ ...cachedComponents && cachedComponents.length > 0 ? { cachedComponents } : {}
27499
27746
  });
27747
+ const buildGenerateArgs = (extractSessionId, tokensPath, generatePromptPath2, cachedComponents) => buildGenerateProcessArgs(buildGenerateOptions(extractSessionId, tokensPath, generatePromptPath2, cachedComponents));
27748
+ const buildGenerateCliArgs = (extractSessionId, tokensPath, generatePromptPath2, cachedComponents) => buildGenerateComponentsArgs(
27749
+ buildGenerateOptions(extractSessionId, tokensPath, generatePromptPath2, cachedComponents)
27750
+ );
27751
+ const checkGenerateCache = async (extractSessionId, tokensPath) => {
27752
+ const timer = startWizardTimer("generation-cache-status", {
27753
+ sessionId: extractSessionId,
27754
+ noCache: effectiveNoCache
27755
+ });
27756
+ if (effectiveNoCache) {
27757
+ finishWizardTimer("generation-cache-status", timer, { status: "skipped" });
27758
+ return false;
27759
+ }
27760
+ const result = await runCli([...buildGenerateCliArgs(extractSessionId, tokensPath), "--cache-status"]);
27761
+ const hit = result.exitCode === 0 && /^cache-status=hit$/m.test(result.stdout);
27762
+ finishWizardTimer("generation-cache-status", timer, {
27763
+ status: hit ? "hit" : "miss",
27764
+ exitCode: result.exitCode,
27765
+ ...result.exitCode !== 0 ? { stderrTail: result.stderr.trim().slice(-500) } : {}
27766
+ });
27767
+ return hit;
27768
+ };
27769
+ const checkGenerateCacheComponents = async (extractSessionId, tokensPath, restoreCache = false) => {
27770
+ const timer = startWizardTimer("generation-cache-components", {
27771
+ sessionId: extractSessionId,
27772
+ restoreCache,
27773
+ noCache: effectiveNoCache
27774
+ });
27775
+ if (effectiveNoCache) {
27776
+ finishWizardTimer("generation-cache-components", timer, { status: "skipped" });
27777
+ return /* @__PURE__ */ new Set();
27778
+ }
27779
+ const args = [...buildGenerateCliArgs(extractSessionId, tokensPath), "--cache-status"];
27780
+ if (restoreCache) args.push("--restore-cache");
27781
+ const result = await runCli(args);
27782
+ if (result.exitCode !== 0) {
27783
+ finishWizardTimer("generation-cache-components", timer, {
27784
+ status: "error",
27785
+ exitCode: result.exitCode,
27786
+ stderrTail: result.stderr.trim().slice(-500)
27787
+ });
27788
+ return /* @__PURE__ */ new Set();
27789
+ }
27790
+ const line = /^cache-components=(.+)$/m.exec(result.stdout)?.[1];
27791
+ if (!line) {
27792
+ finishWizardTimer("generation-cache-components", timer, {
27793
+ status: "missing-output",
27794
+ exitCode: result.exitCode,
27795
+ cachedCount: 0
27796
+ });
27797
+ return /* @__PURE__ */ new Set();
27798
+ }
27799
+ try {
27800
+ const names = JSON.parse(line);
27801
+ const cachedNames = new Set(
27802
+ Array.isArray(names) ? names.filter((name) => typeof name === "string") : []
27803
+ );
27804
+ finishWizardTimer("generation-cache-components", timer, {
27805
+ status: "complete",
27806
+ exitCode: result.exitCode,
27807
+ cachedCount: cachedNames.size
27808
+ });
27809
+ return cachedNames;
27810
+ } catch {
27811
+ finishWizardTimer("generation-cache-components", timer, {
27812
+ status: "invalid-output",
27813
+ exitCode: result.exitCode,
27814
+ cachedCount: 0
27815
+ });
27816
+ return /* @__PURE__ */ new Set();
27817
+ }
27818
+ };
27819
+ useEffect15(() => {
27820
+ if (state.step !== "extracting" && state.step !== "scope-gate" || !state.extractSessionId || state.existingEntitiesStatus === "running" || state.existingEntitiesStatus === "idle" || state.selectionAgentStatus !== "complete" || state.compositionPhase !== "done" && state.compositionPhase !== "cache-hit" || state.rawTokensPath && state.tokenGenerationStatus !== "complete" || effectiveNoCache) {
27821
+ return;
27822
+ }
27823
+ const sessionId2 = state.extractSessionId;
27824
+ if (generationCachePreflightRef.current?.sessionId === sessionId2) return;
27825
+ const timer = startWizardTimer("generation-cache-preflight", { sessionId: sessionId2 });
27826
+ const db = openPipelineDb();
27827
+ let generatedNames = null;
27828
+ try {
27829
+ const rows = db.prepare("SELECT name, status FROM raw_components WHERE session_id = ?").all(sessionId2);
27830
+ if (rows.length > 0 && rows.every((row) => row.status === "generated")) {
27831
+ generatedNames = new Set(rows.map((row) => row.name));
27832
+ }
27833
+ } finally {
27834
+ db.close();
27835
+ }
27836
+ if (generatedNames) {
27837
+ generationCachePreflightRef.current = { sessionId: sessionId2, promise: Promise.resolve(generatedNames) };
27838
+ finishWizardTimer("generation-cache-preflight", timer, {
27839
+ status: "complete",
27840
+ source: "session-db",
27841
+ cachedCount: generatedNames.size
27842
+ });
27843
+ return;
27844
+ }
27845
+ const promise = checkGenerateCacheComponents(sessionId2, state.tokensPath, true);
27846
+ generationCachePreflightRef.current = { sessionId: sessionId2, promise };
27847
+ void promise.then((cachedNames) => {
27848
+ finishWizardTimer("generation-cache-preflight", timer, {
27849
+ status: "complete",
27850
+ source: "cache-status",
27851
+ cachedCount: cachedNames.size
27852
+ });
27853
+ });
27854
+ }, [
27855
+ state.step,
27856
+ state.extractSessionId,
27857
+ state.tokensPath,
27858
+ state.rawTokensPath,
27859
+ state.tokenGenerationStatus,
27860
+ state.selectionAgentStatus,
27861
+ state.compositionPhase,
27862
+ state.existingEntitiesStatus,
27863
+ state.existingEntitiesPath
27864
+ ]);
27500
27865
  const startGeneratePrefetch = (extractSessionId, tokensPath) => {
27501
27866
  const args = buildGenerateArgs(extractSessionId, tokensPath);
27502
27867
  let progressCursor = null;
@@ -27551,8 +27916,14 @@ Make sure this path contains TypeScript/React/Vue component files (.tsx, .ts, .v
27551
27916
  });
27552
27917
  return donePromise;
27553
27918
  };
27554
- const runGenerate = async (extractSessionId, tokensPath, acceptedCount) => {
27555
- const args = buildGenerateArgs(extractSessionId, tokensPath, generatePromptPath);
27919
+ const runGenerate = async (extractSessionId, tokensPath, acceptedCount, suppressScreen = false, cachedComponents = []) => {
27920
+ const timer = startWizardTimer("component-generation", {
27921
+ sessionId: extractSessionId,
27922
+ acceptedCount,
27923
+ suppressScreen,
27924
+ noCache: effectiveNoCache
27925
+ });
27926
+ const args = buildGenerateArgs(extractSessionId, tokensPath, generatePromptPath, cachedComponents);
27556
27927
  let progressCursor = state.generateProgress;
27557
27928
  const { donePromise } = spawnGenerateChild({
27558
27929
  command: "node",
@@ -27561,12 +27932,17 @@ Make sure this path contains TypeScript/React/Vue component files (.tsx, .ts, .v
27561
27932
  const nextProgress = parseGenerateStderrChunk(chunk, progressCursor);
27562
27933
  if (nextProgress !== progressCursor) {
27563
27934
  progressCursor = nextProgress;
27564
- update({ generateProgress: nextProgress });
27935
+ if (!suppressScreen) update({ generateProgress: nextProgress });
27565
27936
  }
27566
27937
  }
27567
27938
  });
27568
27939
  const result = await donePromise;
27569
27940
  if (result.exitCode !== 0) {
27941
+ finishWizardTimer("component-generation", timer, {
27942
+ status: "error",
27943
+ exitCode: result.exitCode
27944
+ });
27945
+ finishSelectionToReviewTimer("error");
27570
27946
  update({
27571
27947
  step: "error",
27572
27948
  errorStep: "generate components",
@@ -27574,14 +27950,67 @@ Make sure this path contains TypeScript/React/Vue component files (.tsx, .ts, .v
27574
27950
  });
27575
27951
  return;
27576
27952
  }
27577
- const { generateSessionId: generateSessionId2, generatedCount } = parseGenerateResult(result, acceptedCount);
27953
+ const { generateSessionId: generateSessionId2, generatedCount, renamedSlotsCount } = parseGenerateResult(result, acceptedCount);
27578
27954
  const mappedSessionId = generateSessionId2 ?? extractSessionId;
27579
- update({
27580
- generateSessionId: mappedSessionId,
27955
+ finishWizardTimer("component-generation", timer, {
27956
+ status: "complete",
27957
+ exitCode: result.exitCode,
27581
27958
  generatedCount,
27582
- generateProgress: null
27959
+ renamedSlotsCount
27583
27960
  });
27584
- if (await runMapTokens2(mappedSessionId)) update({ step: "final-review" });
27961
+ if (!suppressScreen) {
27962
+ update({
27963
+ acceptedCount,
27964
+ autoRejectedCount: 0,
27965
+ generateSessionId: mappedSessionId,
27966
+ generatedCount,
27967
+ generateProgress: null
27968
+ });
27969
+ if (await runMapTokens2(mappedSessionId)) {
27970
+ finishSelectionToReviewTimer("generated");
27971
+ update({ step: "final-review" });
27972
+ } else {
27973
+ finishSelectionToReviewTimer("error");
27974
+ }
27975
+ return;
27976
+ }
27977
+ const mapped = await runMapTokens2(mappedSessionId, true);
27978
+ if (mapped) {
27979
+ finishSelectionToReviewTimer("generated");
27980
+ update(
27981
+ {
27982
+ acceptedCount,
27983
+ autoRejectedCount: 0,
27984
+ generateSessionId: mappedSessionId,
27985
+ generatedCount,
27986
+ generateProgress: null,
27987
+ mapTokensStatus: "complete",
27988
+ step: "final-review"
27989
+ },
27990
+ { clearScreen: false }
27991
+ );
27992
+ } else {
27993
+ finishSelectionToReviewTimer("error");
27994
+ }
27995
+ };
27996
+ const finishCachedGeneration = async (sessionId2, acceptedCount) => {
27997
+ if (await runMapTokens2(sessionId2, true)) {
27998
+ finishSelectionToReviewTimer("cached");
27999
+ update(
28000
+ {
28001
+ acceptedCount,
28002
+ autoRejectedCount: 0,
28003
+ generateSessionId: sessionId2,
28004
+ generatedCount: acceptedCount,
28005
+ generateProgress: null,
28006
+ mapTokensStatus: "complete",
28007
+ step: "final-review"
28008
+ },
28009
+ { clearScreen: false }
28010
+ );
28011
+ } else {
28012
+ finishSelectionToReviewTimer("error");
28013
+ }
27585
28014
  };
27586
28015
  const advanceToPushFlow = (generatedAcceptedCount) => {
27587
28016
  update({ generatedAcceptedCount });
@@ -27766,8 +28195,15 @@ Make sure this path contains TypeScript/React/Vue component files (.tsx, .ts, .v
27766
28195
  return;
27767
28196
  }
27768
28197
  }
27769
- if (await runAgentAuthCheck("generating")) {
27770
- void runGenerate(sid, state.tokensPath, state.acceptedCount);
28198
+ if (await runAgentAuthCheck("generating", true)) {
28199
+ const cacheHit = await checkGenerateCache(sid, state.tokensPath);
28200
+ if (cacheHit) {
28201
+ update({ step: "generating" });
28202
+ void finishCachedGeneration(sid, state.acceptedCount);
28203
+ } else {
28204
+ update({ step: "generating" });
28205
+ void runGenerate(sid, state.tokensPath, state.acceptedCount);
28206
+ }
27771
28207
  }
27772
28208
  return;
27773
28209
  }
@@ -28135,13 +28571,13 @@ If using a custom host configuration, make sure the space exists on that host.`
28135
28571
  update(nextStateAfterPrint({ skipGate: opts.skipGate, componentsPath }));
28136
28572
  return { ok: true };
28137
28573
  };
28138
- const runSaveAndPush = async () => {
28139
- await startSaveFlow({ skipGate: true, andPush: true });
28574
+ const runSaveAndPush = async (statePatch = {}) => {
28575
+ await startSaveFlow({ skipGate: true, andPush: true }, statePatch);
28140
28576
  };
28141
28577
  const pendingSaveOptionsRef = useRef10({});
28142
- const startSaveFlow = async (opts = {}) => {
28578
+ const startSaveFlow = async (opts = {}, statePatch = {}) => {
28143
28579
  pendingSaveOptionsRef.current = opts;
28144
- setState((prev) => ({ ...prev, step: "path-prompt" }));
28580
+ update({ ...statePatch, step: "path-prompt" });
28145
28581
  };
28146
28582
  const proceedToWrite = async (path) => {
28147
28583
  setState((prev) => ({ ...prev, outDir: path }));
@@ -28384,31 +28820,86 @@ If using a custom host configuration, make sure the space exists on that host.`
28384
28820
  {
28385
28821
  components,
28386
28822
  onConfirm: (decisions) => {
28387
- void runScopeGate({
28388
- sessionId: sessionId2,
28389
- decisions,
28390
- onAdvanceToGenerate: async ({ sessionId: sid, acceptedCount }) => {
28391
- scopeGateCompletedRef.current = true;
28392
- if (shouldGenerateAfterScopeGate({ acceptedCount })) {
28393
- update({
28394
- acceptedCount,
28395
- autoRejectedCount: 0,
28396
- step: "checking-claude-auth",
28397
- authCheckStepNumber: state.tokensPath ? 4 : 3
28398
- });
28399
- if (await runAgentAuthCheck("generating")) {
28400
- void runGenerate(sid, state.tokensPath, acceptedCount);
28823
+ void (async () => {
28824
+ selectionToReviewStartedAtRef.current = decisions.accepted.length > 0 ? startWizardTimer("selection-to-review", {
28825
+ sessionId: sessionId2,
28826
+ acceptedCount: decisions.accepted.length,
28827
+ rejectedCount: decisions.rejected.length
28828
+ }) : null;
28829
+ const preflight = generationCachePreflightRef.current;
28830
+ const cachePromise = preflight?.sessionId === sessionId2 ? preflight.promise : checkGenerateCacheComponents(sessionId2, state.tokensPath, true);
28831
+ if (!preflight || preflight.sessionId !== sessionId2) {
28832
+ generationCachePreflightRef.current = { sessionId: sessionId2, promise: cachePromise };
28833
+ }
28834
+ const cacheWaitStartedAt = startWizardTimer("selection-cache-restoration-wait", {
28835
+ sessionId: sessionId2,
28836
+ preflight: Boolean(preflight?.sessionId === sessionId2)
28837
+ });
28838
+ try {
28839
+ const cachedNames = await cachePromise;
28840
+ finishWizardTimer("selection-cache-restoration-wait", cacheWaitStartedAt, {
28841
+ status: "complete",
28842
+ cachedCount: cachedNames.size
28843
+ });
28844
+ } catch (error) {
28845
+ finishWizardTimer("selection-cache-restoration-wait", cacheWaitStartedAt, {
28846
+ status: "error",
28847
+ error: error instanceof Error ? error.message : String(error)
28848
+ });
28849
+ finishSelectionToReviewTimer("error");
28850
+ throw error;
28851
+ }
28852
+ await runScopeGate({
28853
+ sessionId: sessionId2,
28854
+ decisions,
28855
+ onAdvanceToGenerate: async ({ sessionId: sid, acceptedCount }) => {
28856
+ scopeGateCompletedRef.current = true;
28857
+ if (shouldGenerateAfterScopeGate({ acceptedCount })) {
28858
+ const authPromise = runAgentAuthCheck("generating", true);
28859
+ if (await authPromise) {
28860
+ const comparisonStartedAt = startWizardTimer("generation-cache-set-comparison", {
28861
+ sessionId: sid,
28862
+ acceptedCount: decisions?.accepted.length ?? 0
28863
+ });
28864
+ let cachedNames = await cachePromise;
28865
+ const acceptedNames = decisions?.accepted ?? [];
28866
+ let cacheHit = !effectiveNoCache && acceptedNames.length > 0 && acceptedNames.every((name) => cachedNames.has(name));
28867
+ if (!cacheHit && !effectiveNoCache && acceptedNames.length > 0) {
28868
+ cachedNames = await checkGenerateCacheComponents(sid, state.tokensPath, true);
28869
+ cacheHit = acceptedNames.every((name) => cachedNames.has(name));
28870
+ }
28871
+ finishWizardTimer("generation-cache-set-comparison", comparisonStartedAt, {
28872
+ status: "complete",
28873
+ cacheHit,
28874
+ cachedCount: cachedNames.size,
28875
+ acceptedCount: acceptedNames.length
28876
+ });
28877
+ if (cacheHit) {
28878
+ void finishCachedGeneration(sid, acceptedCount);
28879
+ } else {
28880
+ const acceptedNameSet = new Set(acceptedNames);
28881
+ const restoredCachedNames = [...cachedNames].filter((name) => acceptedNameSet.has(name));
28882
+ update({
28883
+ step: "generating",
28884
+ generateProgress: null,
28885
+ acceptedCount
28886
+ });
28887
+ void runGenerate(sid, state.tokensPath, acceptedCount, false, restoredCachedNames);
28888
+ }
28889
+ } else {
28890
+ finishSelectionToReviewTimer("error");
28891
+ }
28892
+ return;
28401
28893
  }
28402
- return;
28894
+ void runSaveAndPush();
28895
+ },
28896
+ onAdvanceToPushFlow: (count) => {
28897
+ scopeGateCompletedRef.current = true;
28898
+ update({ acceptedCount: count, autoRejectedCount: 0 });
28899
+ advanceToPushFlow(count);
28403
28900
  }
28404
- void runSaveAndPush();
28405
- },
28406
- onAdvanceToPushFlow: (count) => {
28407
- scopeGateCompletedRef.current = true;
28408
- update({ acceptedCount: count, autoRejectedCount: 0 });
28409
- advanceToPushFlow(count);
28410
- }
28411
- });
28901
+ });
28902
+ })();
28412
28903
  },
28413
28904
  onQuit: () => process.exit(0)
28414
28905
  }
@@ -28495,8 +28986,10 @@ If using a custom host configuration, make sure the space exists on that host.`
28495
28986
  }
28496
28987
  const allowEmptyDeleteAll = acceptedCount === 0;
28497
28988
  allowEmptyDeleteAllRef.current = allowEmptyDeleteAll;
28498
- update({ finalReviewPassed: true, generatedAcceptedCount: acceptedCount });
28499
- void runSaveAndPush();
28989
+ void runSaveAndPush({
28990
+ finalReviewPassed: true,
28991
+ generatedAcceptedCount: acceptedCount
28992
+ });
28500
28993
  },
28501
28994
  onQuit: () => process.exit(0)
28502
28995
  }
@@ -28735,6 +29228,7 @@ var init_WizardApp = __esm({
28735
29228
  init_prompt_overrides();
28736
29229
  init_run_selection_agent();
28737
29230
  init_use_terminal_size();
29231
+ init_render_with_goodbye();
28738
29232
  SAVE_CONFIRMATION_DELAY_MS = 1e3;
28739
29233
  WIZARD_LOG = join20(tmpdir(), "experiences-import-wizard.log");
28740
29234
  }
@@ -122,6 +122,14 @@ export declare function computeMapTokensInputHash(db: DatabaseSync, sessionId: s
122
122
  export declare function lookupCache(db: DatabaseSync, inputHash: string, entityType: CacheEntityType, entityId: string, promptHash?: string): CacheEntry | null;
123
123
  export declare function lookupCacheByEntity(db: DatabaseSync, entityType: CacheEntityType, entityId: string): CacheEntry | null;
124
124
  export declare function storeCache(db: DatabaseSync, inputHash: string, entityType: CacheEntityType, entityId: string, sourceSessionId: string, humanEdited: boolean, promptHash?: string): void;
125
+ export declare function storeCaches(db: DatabaseSync, entries: Array<{
126
+ inputHash: string;
127
+ entityType: CacheEntityType;
128
+ entityId: string;
129
+ sourceSessionId: string;
130
+ humanEdited: boolean;
131
+ promptHash?: string;
132
+ }>): void;
125
133
  export declare function storeScannedFiles(db: DatabaseSync, sessionId: string, filePaths: string[]): void;
126
134
  export declare function loadScannedFiles(db: DatabaseSync, sessionId: string): string[];
127
135
  export declare function storeSlotCycles(db: DatabaseSync, sessionId: string, cycles: Array<SlotCycle & {
@@ -133,7 +141,12 @@ export interface StoredSlotCycle extends SlotCycle {
133
141
  export declare function loadSlotCycles(db: DatabaseSync, sessionId: string): StoredSlotCycle[];
134
142
  export declare function clearSlotCycles(db: DatabaseSync, sessionId: string): void;
135
143
  export declare function markCacheHumanEdited(db: DatabaseSync, entityType: 'component' | 'token_set', entityId: string): void;
136
- export declare function copyComponentFromCache(db: DatabaseSync, sourceSessionId: string, targetSessionId: string, componentId: string): void;
144
+ export declare function copyComponentFromCache(db: DatabaseSync, sourceSessionId: string, targetSessionId: string, componentId: string, manageTransaction?: boolean): void;
145
+ export declare function copyComponentsFromCache(db: DatabaseSync, entries: Array<{
146
+ sourceSessionId: string;
147
+ targetSessionId: string;
148
+ componentId: string;
149
+ }>): void;
137
150
  export declare function copyTokensFromCache(db: DatabaseSync, sourceSessionId: string, targetSessionId: string): void;
138
151
  export declare function copyMapTokensFromCache(db: DatabaseSync, sourceSessionId: string, targetSessionId: string): number;
139
152
  export declare function getCliCacheVersion(): Promise<string>;
@@ -1,5 +1,6 @@
1
1
  import React from 'react';
2
2
  import { type Instance, type RenderOptions } from 'ink';
3
+ export declare function useScreenTransitionClear(): () => void;
3
4
  export declare function GoodbyeBoundary({ children }: {
4
5
  children: React.ReactNode;
5
6
  }): React.ReactElement;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contentful/experience-design-system-cli",
3
- "version": "2.34.5-dev-build-f6f44159.0",
3
+ "version": "2.34.5-dev-build-a95aeeba.0",
4
4
  "description": "Contentful Experiences design system import CLI",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -41,8 +41,8 @@
41
41
  "svelte": "^5.56.4",
42
42
  "ts-morph": "^27.0.2",
43
43
  "typescript": "^5.9.3",
44
- "@contentful/experience-design-system-cli-v2": "2.34.5-dev-build-f6f44159.0",
45
- "@contentful/experience-design-system-types": "2.34.5-dev-build-f6f44159.0"
44
+ "@contentful/experience-design-system-types": "2.34.5-dev-build-a95aeeba.0",
45
+ "@contentful/experience-design-system-cli-v2": "2.34.5-dev-build-a95aeeba.0"
46
46
  },
47
47
  "devDependencies": {
48
48
  "@tsconfig/node24": "^24.0.4",
@@ -54,9 +54,9 @@
54
54
  "ink-testing-library": "^4.0.0",
55
55
  "typescript-eslint": "^8.67.0",
56
56
  "vitest": "^4.0.16",
57
- "@contentful/experience-design-system-client": "2.34.5-dev-build-f6f44159.0",
58
- "@contentful/experience-design-system-generation": "2.34.5-dev-build-f6f44159.0",
59
- "@contentful/experience-design-system-extraction": "2.34.5-dev-build-f6f44159.0"
57
+ "@contentful/experience-design-system-extraction": "2.34.5-dev-build-a95aeeba.0",
58
+ "@contentful/experience-design-system-client": "2.34.5-dev-build-a95aeeba.0",
59
+ "@contentful/experience-design-system-generation": "2.34.5-dev-build-a95aeeba.0"
60
60
  },
61
61
  "repository": {
62
62
  "type": "git",