@contentful/experience-design-system-cli 2.42.1-dev-build-15c351c1.0 → 2.42.1-dev-build-774b3e20.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/app.js CHANGED
@@ -313,7 +313,7 @@ async function finishDebugRun({
313
313
  if (!await isDebugModeEnabled()) return;
314
314
  const durationMs = Date.now() - run.startedAt;
315
315
  const log = {
316
- flow: logFolder(run.flow, run.step),
316
+ folder: logFolder(run.flow, run.step),
317
317
  menuOption: run.menuOption,
318
318
  content: renderMarkdown(run, { outputs: redact(outputs), status, exitMethod, durationMs })
319
319
  };
@@ -328,7 +328,7 @@ function logFolder(flow, step) {
328
328
  return flow.startsWith("import/") && /^\d+-/.test(step) ? `import/${step}` : flow;
329
329
  }
330
330
  async function writeLog(root, log) {
331
- const dir = join5(root, ...log.flow.split("/"));
331
+ const dir = join5(root, ...log.folder.split("/"));
332
332
  await mkdir2(dir, { recursive: true });
333
333
  const filename = nextAttemptFilename(dir, log.menuOption);
334
334
  await writeFile2(join5(dir, filename), log.content, { mode: 384 });
@@ -3054,6 +3054,19 @@ import { Box as Box8, Text as Text8 } from "ink";
3054
3054
  import { ProgressBar, Spinner, ThemeProvider } from "@inkjs/ui";
3055
3055
 
3056
3056
  // packages/experience-design-system-cli/src/tui/import/shared/running-step/logic.ts
3057
+ function lineState(line) {
3058
+ if (line.state) return line.state;
3059
+ return line.complete ? "done" : "running";
3060
+ }
3061
+ function formatDuration(ms) {
3062
+ return ms < 1e3 ? "<1s" : formatElapsed(Math.round(ms / 1e3));
3063
+ }
3064
+ function trackFinished(previous, rows, elapsedMs) {
3065
+ return rows.map((row, index) => previous[index] ?? (lineState(row) === "running" ? void 0 : elapsedMs));
3066
+ }
3067
+ function withTimes(rows, rowMs) {
3068
+ return rows.map((row, index) => rowMs[index] === void 0 ? row : { ...row, ms: rowMs[index] });
3069
+ }
3057
3070
  function progressPercent(progress) {
3058
3071
  if (progress.total <= 0) return 0;
3059
3072
  return Math.min(100, Math.max(0, Math.round(progress.done / progress.total * 100)));
@@ -3089,6 +3102,9 @@ function useElapsed() {
3089
3102
 
3090
3103
  // packages/experience-design-system-cli/src/tui/import/shared/running-step/running-step.tsx
3091
3104
  import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
3105
+ var STATE_ICON = { done: "\u2713", warning: "!", failed: "\u2717" };
3106
+ var STATE_COLOR = { done: PALETTE.success, warning: PALETTE.warning, failed: PALETTE.error };
3107
+ var textColor = (state2) => state2 === "warning" || state2 === "failed" ? STATE_COLOR[state2] : PALETTE.muted;
3092
3108
  function Line({ line }) {
3093
3109
  if (line.progress) {
3094
3110
  return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", gap: 1, children: [
@@ -3100,12 +3116,14 @@ function Line({ line }) {
3100
3116
  line.progress.total
3101
3117
  ] })
3102
3118
  ] }),
3103
- /* @__PURE__ */ jsx8(Text8, { color: PALETTE.muted, children: line.text })
3119
+ /* @__PURE__ */ jsx8(Text8, { color: textColor(lineState(line)), children: line.text })
3104
3120
  ] });
3105
3121
  }
3122
+ const state2 = lineState(line);
3106
3123
  return /* @__PURE__ */ jsxs8(Box8, { gap: 1, children: [
3107
- line.complete ? /* @__PURE__ */ jsx8(Text8, { color: PALETTE.success, children: "\u2713" }) : /* @__PURE__ */ jsx8(Spinner, {}),
3108
- /* @__PURE__ */ jsx8(Text8, { color: PALETTE.muted, children: line.text })
3124
+ state2 === "running" ? /* @__PURE__ */ jsx8(Spinner, {}) : /* @__PURE__ */ jsx8(Text8, { color: STATE_COLOR[state2], children: STATE_ICON[state2] }),
3125
+ /* @__PURE__ */ jsx8(Text8, { color: textColor(state2), children: line.text }),
3126
+ line.ms !== void 0 && /* @__PURE__ */ jsx8(Text8, { color: PALETTE.muted, children: formatDuration(line.ms) })
3109
3127
  ] });
3110
3128
  }
3111
3129
  function RunningStep({
@@ -18751,7 +18769,7 @@ function plural(count, noun) {
18751
18769
  function compositionDone(progress) {
18752
18770
  return progress.composition === "done" || progress.composition === "cache-hit";
18753
18771
  }
18754
- function progressRows(progress, agent) {
18772
+ function progressRows(progress, agent, rowMs = []) {
18755
18773
  const { scan, composition, selection, existingEntities, tokens } = progress;
18756
18774
  const scanned = scan?.scanned ?? 0;
18757
18775
  const scanComplete = (scan?.total ?? 0) > 0;
@@ -18772,16 +18790,20 @@ function progressRows(progress, agent) {
18772
18790
  if (existingEntities !== "idle") {
18773
18791
  rows.push({
18774
18792
  text: existingEntities === "running" ? "Fetching existing components and tokens..." : existingEntities === "complete" ? "Existing components and tokens ready" : "Existing entities unavailable; continuing without them",
18775
- complete: existingEntities !== "running"
18793
+ complete: existingEntities !== "running",
18794
+ // The import carries on without them, so this is not shown as a success.
18795
+ ...existingEntities === "failed" ? { state: "warning" } : {}
18776
18796
  });
18777
18797
  }
18778
18798
  if (tokens !== "idle") {
18779
18799
  rows.push({
18780
18800
  text: tokens === "complete" ? "Token definitions ready" : tokens === "failed" ? "Token definition generation failed" : `Generating token definitions via ${agent}...`,
18781
- complete: tokens === "complete"
18801
+ complete: tokens === "complete",
18802
+ // A failed job is not still running.
18803
+ ...tokens === "failed" ? { state: "failed" } : {}
18782
18804
  });
18783
18805
  }
18784
- return rows;
18806
+ return withTimes(rows, rowMs);
18785
18807
  }
18786
18808
  function noComponentsMessage(projectPath) {
18787
18809
  return `No components found in ${projectPath}.
@@ -18810,13 +18832,13 @@ function resolveAgentChoice(flag, stored) {
18810
18832
 
18811
18833
  // packages/experience-design-system-cli/src/tui/import/steps/06-extraction/screen.tsx
18812
18834
  import { jsx as jsx16 } from "react/jsx-runtime";
18813
- function ExtractionScreen({ progress, agent }) {
18835
+ function ExtractionScreen({ progress, agent, rowMs }) {
18814
18836
  return /* @__PURE__ */ jsx16(
18815
18837
  RunningStep,
18816
18838
  {
18817
18839
  step: "extraction",
18818
18840
  description: "Extracting context from your source files.",
18819
- lines: progressRows(progress, agent)
18841
+ lines: progressRows(progress, agent, rowMs)
18820
18842
  }
18821
18843
  );
18822
18844
  }
@@ -19245,9 +19267,12 @@ function useExtraction(input) {
19245
19267
  progress: initialProgress({
19246
19268
  hasCredentials: input.credentials !== null,
19247
19269
  hasRawTokens: input.rawTokensPath !== ""
19248
- })
19270
+ }),
19271
+ rowMs: []
19249
19272
  });
19250
19273
  let lastProgress;
19274
+ const startedAt = Date.now();
19275
+ let rowMs = [];
19251
19276
  startDebugRun({
19252
19277
  flow: "import/extraction",
19253
19278
  step: "06-extraction",
@@ -19256,7 +19281,8 @@ function useExtraction(input) {
19256
19281
  });
19257
19282
  void runExtraction(input, (progress) => {
19258
19283
  lastProgress = progress;
19259
- if (current.current === input) setState({ status: "running", progress });
19284
+ rowMs = trackFinished(rowMs, progressRows(progress, input.agent), Date.now() - startedAt);
19285
+ if (current.current === input) setState({ status: "running", progress, rowMs });
19260
19286
  }).then((result) => {
19261
19287
  if (current.current !== input) return;
19262
19288
  void finishDebugRun({
@@ -21921,27 +21947,12 @@ var HELP_SECTIONS = [
21921
21947
  ]
21922
21948
  }
21923
21949
  ];
21924
- function ScopeGateScreen(props) {
21925
- if (props.components.length === 0) return /* @__PURE__ */ jsx29(NoComponents, {});
21926
- return /* @__PURE__ */ jsx29(ScopeGateView, { ...props });
21927
- }
21928
- function NoComponents() {
21929
- useEffect14(() => {
21930
- startDebugRun({
21931
- flow: "import/scope-gate",
21932
- step: "07-scope-gate",
21933
- menuOption: "Scope gate",
21934
- inputs: scopeGateLogInputs([], 0)
21935
- });
21936
- void finishDebugRun({
21937
- outputs: { outcome: "nothing to decide", error: "No components were found for this session." },
21938
- status: "error",
21939
- exitMethod: "discarded"
21940
- });
21941
- }, []);
21942
- return /* @__PURE__ */ jsx29(Box25, { paddingX: 2, paddingY: 1, children: /* @__PURE__ */ jsx29(Text26, { color: PALETTE.error, children: "Error: no components found for this session \u2014 please re-run analyze extract." }) });
21943
- }
21944
- function ScopeGateView({ components, tokensPath, onConfirm, onQuit }) {
21950
+ function ScopeGateScreen({
21951
+ components,
21952
+ tokensPath,
21953
+ onConfirm,
21954
+ onQuit
21955
+ }) {
21945
21956
  const tokens = useMemo4(() => {
21946
21957
  const loaded = tokensPath ? loadTokenRows(tokensPath) : void 0;
21947
21958
  return loaded?.ok ? loaded.rows : [];
@@ -22196,23 +22207,34 @@ function describeGeneration(input) {
22196
22207
  return `${formatAcceptanceSummary({ accepted: input.acceptedCount, autoRejected: 0 })}
22197
22208
  ${agent} is reading each selected component and writing a Contentful component definition for it.${input.hasTokens ? "\nUsing your design tokens for prop resolution." : ""}`;
22198
22209
  }
22199
- function progressRows2(progress, agent) {
22210
+ function componentsRow(components, name) {
22211
+ if (!components) return { text: `Starting up ${name}...`, complete: false };
22212
+ const { done, total, current, failed } = components;
22213
+ if (total > 0 && done >= total) {
22214
+ return failed > 0 ? {
22215
+ text: `Generated ${total - failed} of ${total} components, ${failed} failed`,
22216
+ complete: false,
22217
+ state: "failed"
22218
+ } : { text: `Generated ${total} component${total === 1 ? "" : "s"}`, complete: true };
22219
+ }
22220
+ return {
22221
+ text: failed > 0 ? `${current} (${failed} failed so far)` : current,
22222
+ complete: false,
22223
+ progress: { done, total },
22224
+ ...failed > 0 ? { state: "warning" } : {}
22225
+ };
22226
+ }
22227
+ function progressRows2(progress, agent, rowMs = []) {
22200
22228
  const name = capitalize(agent);
22201
22229
  const { components, mapTokens } = progress;
22202
- const rows = [
22203
- {
22204
- text: components ? components.current : `Starting up ${name}...`,
22205
- complete: false,
22206
- ...components ? { progress: { done: components.done, total: components.total } } : {}
22207
- }
22208
- ];
22230
+ const rows = [componentsRow(components, name)];
22209
22231
  if (mapTokens !== "idle") {
22210
22232
  rows.push({
22211
22233
  text: mapTokens === "running" ? `Mapping token restrictions via ${name}...` : "Token restrictions ready",
22212
22234
  complete: mapTokens === "complete"
22213
22235
  });
22214
22236
  }
22215
- return rows;
22237
+ return withTimes(rows, rowMs);
22216
22238
  }
22217
22239
 
22218
22240
  // packages/experience-design-system-cli/src/tui/import/steps/08-generation/screen.tsx
@@ -22221,14 +22243,15 @@ function GenerationScreen({
22221
22243
  progress,
22222
22244
  agent,
22223
22245
  acceptedCount,
22224
- hasTokens
22246
+ hasTokens,
22247
+ rowMs
22225
22248
  }) {
22226
22249
  return /* @__PURE__ */ jsx30(
22227
22250
  RunningStep,
22228
22251
  {
22229
22252
  step: "generation",
22230
22253
  description: describeGeneration({ acceptedCount, agent, hasTokens }),
22231
- lines: progressRows2(progress, agent)
22254
+ lines: progressRows2(progress, agent, rowMs)
22232
22255
  }
22233
22256
  );
22234
22257
  }
@@ -22388,11 +22411,17 @@ function appendGeneratePromptOverride(input) {
22388
22411
  return [...input.promptOverrides, `generate=${input.generatePromptPath}`];
22389
22412
  }
22390
22413
  function applyProgressEvent2(event, previous, total) {
22414
+ const failed = previous?.failed ?? 0;
22391
22415
  if (event.kind === "component:started") {
22392
- return { done: previous?.done ?? 0, total: event.total || total, current: event.name };
22416
+ return { done: previous?.done ?? 0, total: event.total || total, current: event.name, failed };
22393
22417
  }
22394
22418
  if (event.kind === "component:completed" || event.kind === "component:failed" || event.kind === "component:cached") {
22395
- return { done: (previous?.done ?? 0) + 1, total: event.total || total, current: event.name };
22419
+ return {
22420
+ done: (previous?.done ?? 0) + 1,
22421
+ total: event.total || total,
22422
+ current: event.name,
22423
+ failed: failed + (event.kind === "component:failed" ? 1 : 0)
22424
+ };
22396
22425
  }
22397
22426
  return previous;
22398
22427
  }
@@ -22407,8 +22436,10 @@ function useGeneration(input) {
22407
22436
  setState(void 0);
22408
22437
  return;
22409
22438
  }
22410
- setState({ status: "running", progress: initialProgress2() });
22439
+ setState({ status: "running", progress: initialProgress2(), rowMs: [] });
22411
22440
  let lastProgress;
22441
+ const startedAt = Date.now();
22442
+ let rowMs = [];
22412
22443
  startDebugRun({
22413
22444
  flow: "import/generation",
22414
22445
  step: "08-generation",
@@ -22417,7 +22448,8 @@ function useGeneration(input) {
22417
22448
  });
22418
22449
  void runGeneration(input, (progress) => {
22419
22450
  lastProgress = progress;
22420
- if (current.current === input) setState({ status: "running", progress });
22451
+ rowMs = trackFinished(rowMs, progressRows2(progress, input.agent), Date.now() - startedAt);
22452
+ if (current.current === input) setState({ status: "running", progress, rowMs });
22421
22453
  }).then((result) => {
22422
22454
  if (current.current !== input) return;
22423
22455
  void finishDebugRun({
@@ -22627,6 +22659,14 @@ function entryErrors(node, prefix) {
22627
22659
  const type = value["$type"];
22628
22660
  const check = type === "component" ? validateComponentEntry : TOKEN_TYPE_SET3.has(type) ? validateTokenEntry : void 0;
22629
22661
  if (!check) {
22662
+ if (type !== void 0) {
22663
+ errors.push({
22664
+ path: `${path5}/$type`,
22665
+ message: `Unknown $type "${String(type)}"`,
22666
+ expected: "component or a design token type",
22667
+ actual: String(type)
22668
+ });
22669
+ }
22630
22670
  errors.push(...entryErrors(value, path5));
22631
22671
  } else if (!check(value)) {
22632
22672
  errors.push(...toValidationErrors(check.errors, path5));
@@ -26958,7 +26998,7 @@ function openReviewTrail(components, now) {
26958
26998
  };
26959
26999
  }
26960
27000
  function recordReviewAction(trail, label) {
26961
- const kind = label.split(":")[0] ?? label;
27001
+ const kind = label.split(":")[0];
26962
27002
  trail.kinds[kind] = (trail.kinds[kind] ?? 0) + 1;
26963
27003
  if (trail.actions.length < MAX_ACTIONS) trail.actions.push(label);
26964
27004
  }
@@ -28723,6 +28763,10 @@ function PushingStep({ progress, produced, counts }) {
28723
28763
  "/",
28724
28764
  progress.total,
28725
28765
  " entities"
28766
+ ] }),
28767
+ progress.failed > 0 && /* @__PURE__ */ jsxs43(Text45, { color: PALETTE.error, children: [
28768
+ progress.failed,
28769
+ " failed"
28726
28770
  ] })
28727
28771
  ] }) }),
28728
28772
  showCurrent && /* @__PURE__ */ jsxs43(Box43, { gap: 1, children: [
@@ -29293,6 +29337,8 @@ function flowContext(state2) {
29293
29337
  }
29294
29338
  function arrive(trail, state2) {
29295
29339
  const next = { ...trail, at: state2.step };
29340
+ delete next.failure;
29341
+ delete next.validation;
29296
29342
  if (state2.step === "error") {
29297
29343
  next.failure = { step: state2.errorStep, message: scrub(state2.errorMessage, state2) };
29298
29344
  }
@@ -29787,7 +29833,9 @@ async function runPush(input, deps, onPatch) {
29787
29833
  const items = op.items ?? [];
29788
29834
  const lastDone = items.length > 0 ? items[items.length - 1] : null;
29789
29835
  const current = lastDone && lastDone.status === "succeeded" ? lastDone.id : null;
29790
- onPatch({ pushProgress: { kind: "progress", processed: done, total: summary.total, current } });
29836
+ onPatch({
29837
+ pushProgress: { kind: "progress", processed: done, total: summary.total, failed: summary.failed, current }
29838
+ });
29791
29839
  }
29792
29840
  safeLog(deps, { pollTick: { attempt: pollCount, status: op.sys.status, summary: op.summary } });
29793
29841
  }
@@ -31833,7 +31881,14 @@ function App({ importOptions, inkClear }) {
31833
31881
  return /* @__PURE__ */ jsx66(ErrorStep, { stepName: extraction.result.step, message: extraction.result.message, onExit: leaveImport });
31834
31882
  }
31835
31883
  if (extraction?.status === "running") {
31836
- return /* @__PURE__ */ jsx66(ExtractionScreen, { progress: extraction.progress, agent: extractionRun.input.agent });
31884
+ return /* @__PURE__ */ jsx66(
31885
+ ExtractionScreen,
31886
+ {
31887
+ progress: extraction.progress,
31888
+ agent: extractionRun.input.agent,
31889
+ rowMs: extraction.rowMs
31890
+ }
31891
+ );
31837
31892
  }
31838
31893
  return /* @__PURE__ */ jsx66(Fragment22, {});
31839
31894
  }
@@ -31862,7 +31917,8 @@ function App({ importOptions, inkClear }) {
31862
31917
  progress: generation.progress,
31863
31918
  agent: scopeRun.input.agent,
31864
31919
  acceptedCount: scopeFlow.decisions.accepted.length,
31865
- hasTokens: scopeRun.extraction.tokensPath !== ""
31920
+ hasTokens: scopeRun.extraction.tokensPath !== "",
31921
+ rowMs: generation.rowMs
31866
31922
  }
31867
31923
  );
31868
31924
  }
package/dist/index.js CHANGED
@@ -19616,7 +19616,7 @@ async function finishDebugRun({
19616
19616
  if (!await isDebugModeEnabled()) return;
19617
19617
  const durationMs = Date.now() - run.startedAt;
19618
19618
  const log = {
19619
- flow: logFolder(run.flow, run.step),
19619
+ folder: logFolder(run.flow, run.step),
19620
19620
  menuOption: run.menuOption,
19621
19621
  content: renderMarkdown(run, { outputs: redact(outputs), status, exitMethod, durationMs })
19622
19622
  };
@@ -19631,7 +19631,7 @@ function logFolder(flow, step) {
19631
19631
  return flow.startsWith("import/") && /^\d+-/.test(step) ? `import/${step}` : flow;
19632
19632
  }
19633
19633
  async function writeLog(root, log) {
19634
- const dir = join19(root, ...log.flow.split("/"));
19634
+ const dir = join19(root, ...log.folder.split("/"));
19635
19635
  await mkdir7(dir, { recursive: true });
19636
19636
  const filename = nextAttemptFilename(dir, log.menuOption);
19637
19637
  await writeFile7(join19(dir, filename), log.content, { mode: 384 });
@@ -20138,6 +20138,19 @@ var init_overview_box = __esm({
20138
20138
  });
20139
20139
 
20140
20140
  // packages/experience-design-system-cli/src/tui/import/shared/running-step/logic.ts
20141
+ function lineState(line) {
20142
+ if (line.state) return line.state;
20143
+ return line.complete ? "done" : "running";
20144
+ }
20145
+ function formatDuration(ms) {
20146
+ return ms < 1e3 ? "<1s" : formatElapsed(Math.round(ms / 1e3));
20147
+ }
20148
+ function trackFinished(previous, rows, elapsedMs) {
20149
+ return rows.map((row, index) => previous[index] ?? (lineState(row) === "running" ? void 0 : elapsedMs));
20150
+ }
20151
+ function withTimes(rows, rowMs) {
20152
+ return rows.map((row, index) => rowMs[index] === void 0 ? row : { ...row, ms: rowMs[index] });
20153
+ }
20141
20154
  function progressPercent(progress) {
20142
20155
  if (progress.total <= 0) return 0;
20143
20156
  return Math.min(100, Math.max(0, Math.round(progress.done / progress.total * 100)));
@@ -20204,12 +20217,14 @@ function Line({ line }) {
20204
20217
  line.progress.total
20205
20218
  ] })
20206
20219
  ] }),
20207
- /* @__PURE__ */ jsx8(Text8, { color: PALETTE.muted, children: line.text })
20220
+ /* @__PURE__ */ jsx8(Text8, { color: textColor(lineState(line)), children: line.text })
20208
20221
  ] });
20209
20222
  }
20223
+ const state2 = lineState(line);
20210
20224
  return /* @__PURE__ */ jsxs8(Box8, { gap: 1, children: [
20211
- line.complete ? /* @__PURE__ */ jsx8(Text8, { color: PALETTE.success, children: "\u2713" }) : /* @__PURE__ */ jsx8(Spinner, {}),
20212
- /* @__PURE__ */ jsx8(Text8, { color: PALETTE.muted, children: line.text })
20225
+ state2 === "running" ? /* @__PURE__ */ jsx8(Spinner, {}) : /* @__PURE__ */ jsx8(Text8, { color: STATE_COLOR[state2], children: STATE_ICON[state2] }),
20226
+ /* @__PURE__ */ jsx8(Text8, { color: textColor(state2), children: line.text }),
20227
+ line.ms !== void 0 && /* @__PURE__ */ jsx8(Text8, { color: PALETTE.muted, children: formatDuration(line.ms) })
20213
20228
  ] });
20214
20229
  }
20215
20230
  function RunningStep({
@@ -20230,6 +20245,7 @@ function RunningStep({
20230
20245
  ] })
20231
20246
  ] });
20232
20247
  }
20248
+ var STATE_ICON, STATE_COLOR, textColor;
20233
20249
  var init_running_step = __esm({
20234
20250
  "packages/experience-design-system-cli/src/tui/import/shared/running-step/running-step.tsx"() {
20235
20251
  "use strict";
@@ -20238,6 +20254,9 @@ var init_running_step = __esm({
20238
20254
  init_theme2();
20239
20255
  init_StepHeader();
20240
20256
  init_use_elapsed();
20257
+ STATE_ICON = { done: "\u2713", warning: "!", failed: "\u2717" };
20258
+ STATE_COLOR = { done: PALETTE.success, warning: PALETTE.warning, failed: PALETTE.error };
20259
+ textColor = (state2) => state2 === "warning" || state2 === "failed" ? STATE_COLOR[state2] : PALETTE.muted;
20241
20260
  }
20242
20261
  });
20243
20262
 
@@ -21535,7 +21554,7 @@ function plural(count, noun) {
21535
21554
  function compositionDone(progress) {
21536
21555
  return progress.composition === "done" || progress.composition === "cache-hit";
21537
21556
  }
21538
- function progressRows(progress, agent) {
21557
+ function progressRows(progress, agent, rowMs = []) {
21539
21558
  const { scan, composition, selection, existingEntities, tokens } = progress;
21540
21559
  const scanned = scan?.scanned ?? 0;
21541
21560
  const scanComplete = (scan?.total ?? 0) > 0;
@@ -21556,16 +21575,20 @@ function progressRows(progress, agent) {
21556
21575
  if (existingEntities !== "idle") {
21557
21576
  rows.push({
21558
21577
  text: existingEntities === "running" ? "Fetching existing components and tokens..." : existingEntities === "complete" ? "Existing components and tokens ready" : "Existing entities unavailable; continuing without them",
21559
- complete: existingEntities !== "running"
21578
+ complete: existingEntities !== "running",
21579
+ // The import carries on without them, so this is not shown as a success.
21580
+ ...existingEntities === "failed" ? { state: "warning" } : {}
21560
21581
  });
21561
21582
  }
21562
21583
  if (tokens !== "idle") {
21563
21584
  rows.push({
21564
21585
  text: tokens === "complete" ? "Token definitions ready" : tokens === "failed" ? "Token definition generation failed" : `Generating token definitions via ${agent}...`,
21565
- complete: tokens === "complete"
21586
+ complete: tokens === "complete",
21587
+ // A failed job is not still running.
21588
+ ...tokens === "failed" ? { state: "failed" } : {}
21566
21589
  });
21567
21590
  }
21568
- return rows;
21591
+ return withTimes(rows, rowMs);
21569
21592
  }
21570
21593
  function noComponentsMessage(projectPath) {
21571
21594
  return `No components found in ${projectPath}.
@@ -21596,18 +21619,19 @@ var init_logic9 = __esm({
21596
21619
  "use strict";
21597
21620
  init_src3();
21598
21621
  init_debug_trail();
21622
+ init_logic4();
21599
21623
  }
21600
21624
  });
21601
21625
 
21602
21626
  // packages/experience-design-system-cli/src/tui/import/steps/06-extraction/screen.tsx
21603
21627
  import { jsx as jsx16 } from "react/jsx-runtime";
21604
- function ExtractionScreen({ progress, agent }) {
21628
+ function ExtractionScreen({ progress, agent, rowMs }) {
21605
21629
  return /* @__PURE__ */ jsx16(
21606
21630
  RunningStep,
21607
21631
  {
21608
21632
  step: "extraction",
21609
21633
  description: "Extracting context from your source files.",
21610
- lines: progressRows(progress, agent)
21634
+ lines: progressRows(progress, agent, rowMs)
21611
21635
  }
21612
21636
  );
21613
21637
  }
@@ -22092,9 +22116,12 @@ function useExtraction(input) {
22092
22116
  progress: initialProgress({
22093
22117
  hasCredentials: input.credentials !== null,
22094
22118
  hasRawTokens: input.rawTokensPath !== ""
22095
- })
22119
+ }),
22120
+ rowMs: []
22096
22121
  });
22097
22122
  let lastProgress;
22123
+ const startedAt = Date.now();
22124
+ let rowMs = [];
22098
22125
  startDebugRun({
22099
22126
  flow: "import/extraction",
22100
22127
  step: "06-extraction",
@@ -22103,7 +22130,8 @@ function useExtraction(input) {
22103
22130
  });
22104
22131
  void runExtraction(input, (progress) => {
22105
22132
  lastProgress = progress;
22106
- if (current.current === input) setState({ status: "running", progress });
22133
+ rowMs = trackFinished(rowMs, progressRows(progress, input.agent), Date.now() - startedAt);
22134
+ if (current.current === input) setState({ status: "running", progress, rowMs });
22107
22135
  }).then((result) => {
22108
22136
  if (current.current !== input) return;
22109
22137
  void finishDebugRun({
@@ -22122,6 +22150,7 @@ var init_use_extraction = __esm({
22122
22150
  "packages/experience-design-system-cli/src/tui/import/steps/06-extraction/use-extraction.ts"() {
22123
22151
  "use strict";
22124
22152
  init_debug_store();
22153
+ init_logic4();
22125
22154
  init_logic9();
22126
22155
  init_run();
22127
22156
  }
@@ -24978,27 +25007,12 @@ var init_use_scope_gate = __esm({
24978
25007
  import { useEffect as useEffect14, useMemo as useMemo4 } from "react";
24979
25008
  import { Box as Box25, Text as Text26 } from "ink";
24980
25009
  import { Fragment as Fragment5, jsx as jsx29, jsxs as jsxs25 } from "react/jsx-runtime";
24981
- function ScopeGateScreen(props) {
24982
- if (props.components.length === 0) return /* @__PURE__ */ jsx29(NoComponents, {});
24983
- return /* @__PURE__ */ jsx29(ScopeGateView, { ...props });
24984
- }
24985
- function NoComponents() {
24986
- useEffect14(() => {
24987
- startDebugRun({
24988
- flow: "import/scope-gate",
24989
- step: "07-scope-gate",
24990
- menuOption: "Scope gate",
24991
- inputs: scopeGateLogInputs([], 0)
24992
- });
24993
- void finishDebugRun({
24994
- outputs: { outcome: "nothing to decide", error: "No components were found for this session." },
24995
- status: "error",
24996
- exitMethod: "discarded"
24997
- });
24998
- }, []);
24999
- return /* @__PURE__ */ jsx29(Box25, { paddingX: 2, paddingY: 1, children: /* @__PURE__ */ jsx29(Text26, { color: PALETTE.error, children: "Error: no components found for this session \u2014 please re-run analyze extract." }) });
25000
- }
25001
- function ScopeGateView({ components, tokensPath, onConfirm, onQuit }) {
25010
+ function ScopeGateScreen({
25011
+ components,
25012
+ tokensPath,
25013
+ onConfirm,
25014
+ onQuit
25015
+ }) {
25002
25016
  const tokens = useMemo4(() => {
25003
25017
  const loaded = tokensPath ? loadTokenRows(tokensPath) : void 0;
25004
25018
  return loaded?.ok ? loaded.rows : [];
@@ -25330,28 +25344,40 @@ function describeGeneration(input) {
25330
25344
  return `${formatAcceptanceSummary({ accepted: input.acceptedCount, autoRejected: 0 })}
25331
25345
  ${agent} is reading each selected component and writing a Contentful component definition for it.${input.hasTokens ? "\nUsing your design tokens for prop resolution." : ""}`;
25332
25346
  }
25333
- function progressRows2(progress, agent) {
25347
+ function componentsRow(components, name) {
25348
+ if (!components) return { text: `Starting up ${name}...`, complete: false };
25349
+ const { done, total, current, failed } = components;
25350
+ if (total > 0 && done >= total) {
25351
+ return failed > 0 ? {
25352
+ text: `Generated ${total - failed} of ${total} components, ${failed} failed`,
25353
+ complete: false,
25354
+ state: "failed"
25355
+ } : { text: `Generated ${total} component${total === 1 ? "" : "s"}`, complete: true };
25356
+ }
25357
+ return {
25358
+ text: failed > 0 ? `${current} (${failed} failed so far)` : current,
25359
+ complete: false,
25360
+ progress: { done, total },
25361
+ ...failed > 0 ? { state: "warning" } : {}
25362
+ };
25363
+ }
25364
+ function progressRows2(progress, agent, rowMs = []) {
25334
25365
  const name = capitalize(agent);
25335
25366
  const { components, mapTokens } = progress;
25336
- const rows = [
25337
- {
25338
- text: components ? components.current : `Starting up ${name}...`,
25339
- complete: false,
25340
- ...components ? { progress: { done: components.done, total: components.total } } : {}
25341
- }
25342
- ];
25367
+ const rows = [componentsRow(components, name)];
25343
25368
  if (mapTokens !== "idle") {
25344
25369
  rows.push({
25345
25370
  text: mapTokens === "running" ? `Mapping token restrictions via ${name}...` : "Token restrictions ready",
25346
25371
  complete: mapTokens === "complete"
25347
25372
  });
25348
25373
  }
25349
- return rows;
25374
+ return withTimes(rows, rowMs);
25350
25375
  }
25351
25376
  var capitalize;
25352
25377
  var init_logic11 = __esm({
25353
25378
  "packages/experience-design-system-cli/src/tui/import/steps/08-generation/logic.ts"() {
25354
25379
  "use strict";
25380
+ init_logic4();
25355
25381
  capitalize = (name) => name.charAt(0).toUpperCase() + name.slice(1);
25356
25382
  }
25357
25383
  });
@@ -25362,14 +25388,15 @@ function GenerationScreen({
25362
25388
  progress,
25363
25389
  agent,
25364
25390
  acceptedCount,
25365
- hasTokens
25391
+ hasTokens,
25392
+ rowMs
25366
25393
  }) {
25367
25394
  return /* @__PURE__ */ jsx30(
25368
25395
  RunningStep,
25369
25396
  {
25370
25397
  step: "generation",
25371
25398
  description: describeGeneration({ acceptedCount, agent, hasTokens }),
25372
- lines: progressRows2(progress, agent)
25399
+ lines: progressRows2(progress, agent, rowMs)
25373
25400
  }
25374
25401
  );
25375
25402
  }
@@ -25532,11 +25559,17 @@ function appendGeneratePromptOverride(input) {
25532
25559
  return [...input.promptOverrides, `generate=${input.generatePromptPath}`];
25533
25560
  }
25534
25561
  function applyProgressEvent2(event, previous, total) {
25562
+ const failed = previous?.failed ?? 0;
25535
25563
  if (event.kind === "component:started") {
25536
- return { done: previous?.done ?? 0, total: event.total || total, current: event.name };
25564
+ return { done: previous?.done ?? 0, total: event.total || total, current: event.name, failed };
25537
25565
  }
25538
25566
  if (event.kind === "component:completed" || event.kind === "component:failed" || event.kind === "component:cached") {
25539
- return { done: (previous?.done ?? 0) + 1, total: event.total || total, current: event.name };
25567
+ return {
25568
+ done: (previous?.done ?? 0) + 1,
25569
+ total: event.total || total,
25570
+ current: event.name,
25571
+ failed: failed + (event.kind === "component:failed" ? 1 : 0)
25572
+ };
25540
25573
  }
25541
25574
  return previous;
25542
25575
  }
@@ -25574,8 +25607,10 @@ function useGeneration(input) {
25574
25607
  setState(void 0);
25575
25608
  return;
25576
25609
  }
25577
- setState({ status: "running", progress: initialProgress2() });
25610
+ setState({ status: "running", progress: initialProgress2(), rowMs: [] });
25578
25611
  let lastProgress;
25612
+ const startedAt = Date.now();
25613
+ let rowMs = [];
25579
25614
  startDebugRun({
25580
25615
  flow: "import/generation",
25581
25616
  step: "08-generation",
@@ -25584,7 +25619,8 @@ function useGeneration(input) {
25584
25619
  });
25585
25620
  void runGeneration(input, (progress) => {
25586
25621
  lastProgress = progress;
25587
- if (current.current === input) setState({ status: "running", progress });
25622
+ rowMs = trackFinished(rowMs, progressRows2(progress, input.agent), Date.now() - startedAt);
25623
+ if (current.current === input) setState({ status: "running", progress, rowMs });
25588
25624
  }).then((result) => {
25589
25625
  if (current.current !== input) return;
25590
25626
  void finishDebugRun({
@@ -25603,6 +25639,7 @@ var init_use_generation = __esm({
25603
25639
  "packages/experience-design-system-cli/src/tui/import/steps/08-generation/use-generation.ts"() {
25604
25640
  "use strict";
25605
25641
  init_debug_store();
25642
+ init_logic4();
25606
25643
  init_logic11();
25607
25644
  init_run2();
25608
25645
  }
@@ -25818,6 +25855,14 @@ function entryErrors(node, prefix) {
25818
25855
  const type = value["$type"];
25819
25856
  const check = type === "component" ? validateComponentEntry : TOKEN_TYPE_SET3.has(type) ? validateTokenEntry : void 0;
25820
25857
  if (!check) {
25858
+ if (type !== void 0) {
25859
+ errors.push({
25860
+ path: `${path5}/$type`,
25861
+ message: `Unknown $type "${String(type)}"`,
25862
+ expected: "component or a design token type",
25863
+ actual: String(type)
25864
+ });
25865
+ }
25821
25866
  errors.push(...entryErrors(value, path5));
25822
25867
  } else if (!check(value)) {
25823
25868
  errors.push(...toValidationErrors(check.errors, path5));
@@ -30527,7 +30572,7 @@ function openReviewTrail(components, now) {
30527
30572
  };
30528
30573
  }
30529
30574
  function recordReviewAction(trail, label) {
30530
- const kind = label.split(":")[0] ?? label;
30575
+ const kind = label.split(":")[0];
30531
30576
  trail.kinds[kind] = (trail.kinds[kind] ?? 0) + 1;
30532
30577
  if (trail.actions.length < MAX_ACTIONS) trail.actions.push(label);
30533
30578
  }
@@ -32433,6 +32478,10 @@ function PushingStep({ progress, produced, counts }) {
32433
32478
  "/",
32434
32479
  progress.total,
32435
32480
  " entities"
32481
+ ] }),
32482
+ progress.failed > 0 && /* @__PURE__ */ jsxs43(Text45, { color: PALETTE.error, children: [
32483
+ progress.failed,
32484
+ " failed"
32436
32485
  ] })
32437
32486
  ] }) }),
32438
32487
  showCurrent && /* @__PURE__ */ jsxs43(Box43, { gap: 1, children: [
@@ -33018,6 +33067,8 @@ function flowContext(state2) {
33018
33067
  }
33019
33068
  function arrive(trail, state2) {
33020
33069
  const next = { ...trail, at: state2.step };
33070
+ delete next.failure;
33071
+ delete next.validation;
33021
33072
  if (state2.step === "error") {
33022
33073
  next.failure = { step: state2.errorStep, message: scrub(state2.errorMessage, state2) };
33023
33074
  }
@@ -33559,7 +33610,9 @@ async function runPush(input, deps, onPatch) {
33559
33610
  const items = op.items ?? [];
33560
33611
  const lastDone = items.length > 0 ? items[items.length - 1] : null;
33561
33612
  const current = lastDone && lastDone.status === "succeeded" ? lastDone.id : null;
33562
- onPatch({ pushProgress: { kind: "progress", processed: done, total: summary.total, current } });
33613
+ onPatch({
33614
+ pushProgress: { kind: "progress", processed: done, total: summary.total, failed: summary.failed, current }
33615
+ });
33563
33616
  }
33564
33617
  safeLog(deps, { pollTick: { attempt: pollCount, status: op.sys.status, summary: op.summary } });
33565
33618
  }
@@ -35690,7 +35743,14 @@ function App({ importOptions, inkClear }) {
35690
35743
  return /* @__PURE__ */ jsx66(ErrorStep, { stepName: extraction.result.step, message: extraction.result.message, onExit: leaveImport });
35691
35744
  }
35692
35745
  if (extraction?.status === "running") {
35693
- return /* @__PURE__ */ jsx66(ExtractionScreen, { progress: extraction.progress, agent: extractionRun.input.agent });
35746
+ return /* @__PURE__ */ jsx66(
35747
+ ExtractionScreen,
35748
+ {
35749
+ progress: extraction.progress,
35750
+ agent: extractionRun.input.agent,
35751
+ rowMs: extraction.rowMs
35752
+ }
35753
+ );
35694
35754
  }
35695
35755
  return /* @__PURE__ */ jsx66(Fragment22, {});
35696
35756
  }
@@ -35719,7 +35779,8 @@ function App({ importOptions, inkClear }) {
35719
35779
  progress: generation.progress,
35720
35780
  agent: scopeRun.input.agent,
35721
35781
  acceptedCount: scopeFlow.decisions.accepted.length,
35722
- hasTokens: scopeRun.extraction.tokensPath !== ""
35782
+ hasTokens: scopeRun.extraction.tokensPath !== "",
35783
+ rowMs: generation.rowMs
35723
35784
  }
35724
35785
  );
35725
35786
  }
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contentful/experience-design-system-cli",
3
- "version": "2.42.1-dev-build-15c351c1.0",
3
+ "version": "2.42.1-dev-build-774b3e20.0",
4
4
  "description": "Contentful Experiences design system import CLI",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -5,7 +5,23 @@ export interface Progress {
5
5
  export interface RunningLine {
6
6
  text: string;
7
7
  complete?: boolean;
8
+ /** Set for a row that ended badly. A warning carries on without it; a failure does not. Otherwise `complete` decides. */
9
+ state?: 'warning' | 'failed';
8
10
  progress?: Progress;
11
+ /** How long the row took, once it has finished. */
12
+ ms?: number;
9
13
  }
14
+ export type LineState = 'running' | 'done' | 'warning' | 'failed';
15
+ /** What a row looks like. A row that ended badly is never shown as running or as done. */
16
+ export declare function lineState(line: RunningLine): LineState;
17
+ export declare function formatDuration(ms: number): string;
18
+ /**
19
+ * How long each row took, by position, for the rows that have finished. A row's time is how long the run had been going
20
+ * when it was first seen finished, and it does not change after that. Rows keep their position for the whole run, so a row
21
+ * that appears later is added at the end.
22
+ */
23
+ export declare function trackFinished(previous: ReadonlyArray<number | undefined>, rows: readonly RunningLine[], elapsedMs: number): Array<number | undefined>;
24
+ /** The rows with the time each finished row took, by position. */
25
+ export declare function withTimes(rows: RunningLine[], rowMs: ReadonlyArray<number | undefined>): RunningLine[];
10
26
  export declare function progressPercent(progress: Progress): number;
11
27
  export declare function formatElapsed(totalSeconds: number): string;
@@ -1,4 +1,5 @@
1
1
  import type { ExtractAndComposeProgress } from '@contentful/experience-design-system-backend-pipeline';
2
+ import { type RunningLine } from '../../shared/running-step/logic.js';
2
3
  import type { ExtractionInput, ExtractionProgress, ExtractionResult } from './types.js';
3
4
  /** Structured progress events emitted by the orchestrator (extraction + composition). */
4
5
  export type ProgressEvent = ExtractAndComposeProgress;
@@ -24,11 +25,11 @@ export declare function initialProgress(input: {
24
25
  hasRawTokens: boolean;
25
26
  }): ExtractionProgress;
26
27
  export declare function applyProgressEvent(progress: ExtractionProgress, event: ProgressEvent): ExtractionProgress;
27
- /** The rows the screen shows, in the order the wizard showed them. Rows that do not apply are left out. */
28
- export declare function progressRows(progress: ExtractionProgress, agent: string): Array<{
29
- text: string;
30
- complete: boolean;
31
- }>;
28
+ /**
29
+ * The rows the screen shows, in the order the wizard showed them. Rows that do not apply are left out. `rowMs` is how long
30
+ * each finished row took, by position (see `trackFinished`).
31
+ */
32
+ export declare function progressRows(progress: ExtractionProgress, agent: string, rowMs?: ReadonlyArray<number | undefined>): RunningLine[];
32
33
  /** True when every job is already done, so the screen would only flash. The wizard shows nothing in this case. */
33
34
  export declare function isFullCacheHit(progress: ExtractionProgress): boolean;
34
35
  export declare function noComponentsMessage(projectPath: string): string;
@@ -3,6 +3,8 @@ import type { ExtractionProgress } from './types.js';
3
3
  interface ExtractionScreenProps {
4
4
  progress: ExtractionProgress;
5
5
  agent: string;
6
+ /** How long each finished row took, by position. */
7
+ rowMs?: ReadonlyArray<number | undefined>;
6
8
  }
7
- export declare function ExtractionScreen({ progress, agent }: ExtractionScreenProps): React.ReactElement;
9
+ export declare function ExtractionScreen({ progress, agent, rowMs }: ExtractionScreenProps): React.ReactElement;
8
10
  export {};
@@ -2,6 +2,7 @@ import type { ExtractionInput, ExtractionProgress, ExtractionResult } from './ty
2
2
  export type ExtractionState = {
3
3
  status: 'running';
4
4
  progress: ExtractionProgress;
5
+ rowMs: Array<number | undefined>;
5
6
  } | {
6
7
  status: 'done';
7
8
  result: ExtractionResult;
@@ -6,5 +6,5 @@ interface ScopeGateScreenProps {
6
6
  onConfirm: (decisions: ScopeDecisions) => void;
7
7
  onQuit: () => void;
8
8
  }
9
- export declare function ScopeGateScreen(props: ScopeGateScreenProps): React.ReactElement;
9
+ export declare function ScopeGateScreen({ components, tokensPath, onConfirm, onQuit, }: ScopeGateScreenProps): React.ReactElement;
10
10
  export {};
@@ -1,3 +1,4 @@
1
+ import { type RunningLine } from '../../shared/running-step/logic.js';
1
2
  import type { GenerationInput, GenerationProgress, GenerationResult } from './types.js';
2
3
  /** What the debug log says the stage started with. The counts are logged, not the lists of names. */
3
4
  export declare function generationLogInputs(input: GenerationInput): Record<string, unknown>;
@@ -20,11 +21,5 @@ export declare function describeGeneration(input: {
20
21
  agent: string;
21
22
  hasTokens: boolean;
22
23
  }): string;
23
- export declare function progressRows(progress: GenerationProgress, agent: string): Array<{
24
- text: string;
25
- complete: boolean;
26
- progress?: {
27
- done: number;
28
- total: number;
29
- };
30
- }>;
24
+ /** `rowMs` is how long each finished row took, by position (see `trackFinished`). */
25
+ export declare function progressRows(progress: GenerationProgress, agent: string, rowMs?: ReadonlyArray<number | undefined>): RunningLine[];
@@ -5,6 +5,8 @@ interface GenerationScreenProps {
5
5
  agent: string;
6
6
  acceptedCount: number;
7
7
  hasTokens: boolean;
8
+ /** How long each finished row took, by position. */
9
+ rowMs?: ReadonlyArray<number | undefined>;
8
10
  }
9
- export declare function GenerationScreen({ progress, agent, acceptedCount, hasTokens, }: GenerationScreenProps): React.ReactElement;
11
+ export declare function GenerationScreen({ progress, agent, acceptedCount, hasTokens, rowMs, }: GenerationScreenProps): React.ReactElement;
10
12
  export {};
@@ -26,11 +26,15 @@ export interface GenerationInput {
26
26
  }
27
27
  /** One line per thing the screen shows, in the order it shows them. */
28
28
  export interface GenerationProgress {
29
- /** Components finished, out of the total, and the one being worked on. Null until the first progress line. */
29
+ /**
30
+ * Components finished, out of the total, and the one being worked on. `failed` counts the finished ones that failed: the
31
+ * run carries on through the rest and only fails at the end. Null until the first progress line.
32
+ */
30
33
  components: {
31
34
  done: number;
32
35
  total: number;
33
36
  current: string;
37
+ failed: number;
34
38
  } | null;
35
39
  mapTokens: 'idle' | 'running' | 'complete';
36
40
  /** Whether token mapping will run at all; unknown until the generated components have been looked at. */
@@ -2,6 +2,7 @@ import type { GenerationInput, GenerationProgress, GenerationResult } from './ty
2
2
  export type GenerationState = {
3
3
  status: 'running';
4
4
  progress: GenerationProgress;
5
+ rowMs: Array<number | undefined>;
5
6
  } | {
6
7
  status: 'done';
7
8
  result: GenerationResult;
@@ -5,5 +5,6 @@ export type PushProgress = {
5
5
  kind: 'progress';
6
6
  processed: number;
7
7
  total: number;
8
+ failed: number;
8
9
  current: string | null;
9
10
  } | null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contentful/experience-design-system-cli",
3
- "version": "2.42.1-dev-build-15c351c1.0",
3
+ "version": "2.42.1-dev-build-774b3e20.0",
4
4
  "description": "Contentful Experiences design system import CLI",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -45,7 +45,7 @@
45
45
  "ts-morph": "^27.0.2",
46
46
  "typescript": "^5.9.3",
47
47
  "zod": "^4.6.5",
48
- "@contentful/experience-design-system-types": "2.42.1-dev-build-15c351c1.0"
48
+ "@contentful/experience-design-system-types": "2.42.1-dev-build-774b3e20.0"
49
49
  },
50
50
  "devDependencies": {
51
51
  "@tsconfig/node24": "^24.0.4",
@@ -59,7 +59,7 @@
59
59
  "ink-testing-library": "^4.0.0",
60
60
  "typescript-eslint": "^8.67.0",
61
61
  "vitest": "^4.0.16",
62
- "@contentful/experience-design-system-backend-pipeline": "2.42.1-dev-build-15c351c1.0"
62
+ "@contentful/experience-design-system-backend-pipeline": "2.42.1-dev-build-774b3e20.0"
63
63
  },
64
64
  "repository": {
65
65
  "type": "git",