@contentful/experience-design-system-cli 2.11.4-dev-build-11add11.0 → 2.11.4-dev-build-f3b5300.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.11.4-dev-build-11add11.0",
3
+ "version": "2.11.4-dev-build-f3b5300.0",
4
4
  "description": "Contentful Experiences design system import CLI",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -202,28 +202,41 @@ async function retryComponentWithProject(component, ctx, project, warnings) {
202
202
  }
203
203
  if (!members || members.length === 0)
204
204
  return false;
205
- // If the only members are Snippet-typed (the partial-heritage signal that
206
- // tripped the original warning), we haven't actually recovered useful props.
207
- if (members.every((m) => m.isSnippet))
208
- return false;
205
+ // Snippet-only resolution is still useful it confirms the slot surface
206
+ // and lets us replace the original (which may have had fewer slot entries
207
+ // because the cold AST-only pass missed members declared in heritage).
208
+ // Apply the recovered slots, but DON'T drop `props-type-unresolved` from
209
+ // reasons (the prop side genuinely has no resolvable members).
210
+ const onlySnippets = members.every((m) => m.isSnippet);
209
211
  const { props, snippetSlots } = extractFromTypeMembersOnly(members);
210
212
  // Merge any template <slot> entries that survived the original pass — we
211
213
  // can't easily re-derive those without re-parsing the fragment, but the
212
214
  // existing component already has them.
213
215
  const templateSlots = component.slots.filter((s) => !snippetSlots.some((ss) => ss.name === s.name));
214
- component.props = props;
215
- component.slots = mergeSlots(snippetSlots, templateSlots).slots;
216
- // Drop props-type-unresolved from reasons; recompute confidence.
217
- const remainingReasons = (component.reviewReasons ?? []).filter((r) => r !== 'props-type-unresolved');
216
+ const finalSlots = mergeSlots(snippetSlots, templateSlots).slots;
217
+ // Defensive dedupe: if a name somehow ended up in both buckets (e.g. when ts-morph
218
+ // returned `any` for a Snippet member and the Snippet detector failed downstream),
219
+ // the slot wins slot semantics aren't recoverable from a duplicated prop.
220
+ const slotNames = new Set(finalSlots.map((s) => s.name));
221
+ component.props = props.filter((p) => !slotNames.has(p.name));
222
+ component.slots = finalSlots;
223
+ // Recompute confidence. When only Snippets came back, keep the
224
+ // `props-type-unresolved` reason — slot surface is real but the prop side
225
+ // is still legitimately unresolved (the extends-from-node_modules failed).
226
+ // When real props came back, drop the reason: full recovery.
227
+ const remainingReasons = onlySnippets
228
+ ? (component.reviewReasons ?? [])
229
+ : (component.reviewReasons ?? []).filter((r) => r !== 'props-type-unresolved');
218
230
  const score = computeExtractionScore(component, {
219
231
  additionalIssueCount: remainingReasons.length,
220
232
  additionalReasons: remainingReasons,
221
233
  });
222
234
  component.extractionConfidence = score.confidence;
223
235
  component.reviewReasons = score.reasons;
224
- component.needsReview = deriveNeedsReview(score.confidence);
225
- // Remove the per-component unresolved-type warning so it doesn't muddy the
226
- // summary line / TUI grouping after recovery.
236
+ component.needsReview = deriveNeedsReview(score.confidence) || remainingReasons.includes('props-type-unresolved');
237
+ // Remove the per-component unresolved-type warning. The `props-type-unresolved`
238
+ // reason on the component itself still carries the signal for TUI drill-down;
239
+ // the warnings array is replaced by the collapsed summary upstream.
227
240
  const componentName = component.name;
228
241
  const idx = warnings.findIndex((w) => w.startsWith(`${componentName}: declared Props type `) && /resolved to /.test(w));
229
242
  if (idx >= 0)
@@ -456,22 +469,26 @@ function extractTypesFromExports(exportsField) {
456
469
  * When a large fraction of components emit the same `declared Props type ...
457
470
  * resolved to ... properties` warning (typical of headless libraries like
458
471
  * skeleton-svelte that extend types from external packages we can't reach),
459
- * collapse them into a single summary line at the top + the per-component
460
- * details below. Per-component reviewReasons and needsReview stay intact;
461
- * this only affects the warnings array's readability.
472
+ * replace ALL of them with a single summary line. Per-component
473
+ * reviewReasons + needsReview flags stay intact, so the user can still
474
+ * drill into any individual component to see the issue — we just don't
475
+ * flood the top-level warnings panel with N near-identical lines.
462
476
  *
463
477
  * Threshold: 3 or more identical-shape warnings. Below that, the literal
464
- * per-component lines are clearer than a summary.
478
+ * per-component lines are short enough to keep as-is.
465
479
  */
466
480
  function collapseUnresolvedTypeWarnings(warnings) {
467
481
  const isUnresolvedWarning = (w) => /declared Props type .* resolved to/.test(w);
468
- const unresolved = warnings.filter(isUnresolvedWarning);
469
- if (unresolved.length < 3)
482
+ const unresolvedCount = warnings.filter(isUnresolvedWarning).length;
483
+ if (unresolvedCount < 3)
470
484
  return warnings;
471
- const summary = `Unresolved component types: ${unresolved.length} components have a declared Props type the parser couldn't fully resolve — ` +
485
+ const others = warnings.filter((w) => !isUnresolvedWarning(w));
486
+ const summary = `Unresolved component types: ${unresolvedCount} components have a declared Props type the parser couldn't fully resolve — ` +
472
487
  `most often a cross-package extends pattern (e.g. an interface that extends a type from a node_modules package). ` +
488
+ `Each affected component is flagged with reviewReasons: ['props-type-unresolved'] and needsReview = true; ` +
489
+ `select one in the TUI to drill in. ` +
473
490
  `See https://github.com/contentful/experience-design-system-sdk-public/pull/44 for context and partner workarounds.`;
474
- return [summary, ...warnings];
491
+ return [summary, ...others];
475
492
  }
476
493
  async function extractFromSvelteFile(filePath, source) {
477
494
  const warnings = [];
@@ -872,10 +889,18 @@ async function resolveViaTypeChecker(annotation, instance, moduleScript, filePat
872
889
  }
873
890
  const allowed = extractAllowedValuesFromType(propType);
874
891
  const description = readJsDocFromDeclaration(declaration);
875
- // Snippet detection is alias-based first (works through generic instantiation
876
- // and full type expansion) and falls back to text-matching for the simple
877
- // case where alias info isn't available.
878
- const isSnippet = typeRefersToSnippet(propType) || isSnippetTypeText(typeText, snippetLocals);
892
+ // Snippet detection try three signals in order of reliability:
893
+ // 1. The author's declared type-node text on the property's source declaration.
894
+ // This survives even when ts-morph fails to fully resolve a generic
895
+ // instantiation and falls back to `any`/`unknown` (intermittent under
896
+ // partial type-checker state).
897
+ // 2. The resolved type's alias-symbol chain — works when ts-morph DID fully
898
+ // resolve the type (`Snippet<[T]>` expanded to its call signature).
899
+ // 3. Text-match on the rendered type. Last-resort fallback.
900
+ const declaredTypeText = readDeclaredTypeNodeText(declaration);
901
+ const isSnippet = (declaredTypeText !== null && isSnippetTypeText(declaredTypeText, snippetLocals)) ||
902
+ typeRefersToSnippet(propType) ||
903
+ isSnippetTypeText(typeText, snippetLocals);
879
904
  members.push({
880
905
  name,
881
906
  optional,
@@ -924,6 +949,20 @@ function readJsDocFromDeclaration(decl) {
924
949
  }
925
950
  return undefined;
926
951
  }
952
+ /**
953
+ * Read the literal text of the property's declared type annotation as the
954
+ * AUTHOR wrote it (e.g. `Snippet<[Attrs]>`), independent of what the TS
955
+ * checker resolved it to. This is critical because ts-morph's checker can
956
+ * intermittently fall back to `any`/`unknown` for cross-package generic
957
+ * instantiations, in which case the symbol-/alias-based detector has nothing
958
+ * to follow. The author's syntactic annotation is stable in either case.
959
+ */
960
+ function readDeclaredTypeNodeText(decl) {
961
+ if (Node.isPropertySignature(decl)) {
962
+ return decl.getTypeNode()?.getText() ?? null;
963
+ }
964
+ return null;
965
+ }
927
966
  function isSnippetTypeText(typeText, snippetLocals) {
928
967
  // Direct match against the local Snippet name (handles aliasing).
929
968
  for (const local of snippetLocals) {
@@ -60,5 +60,5 @@ export function AnalyzeView({ result, onExit }) {
60
60
  .map((e, i) => (_jsx(Text, { color: "red", children: ' ✗ ' + e.component + ': ' + e.error }, i)))] })), result.totalWarnings > 0 && (_jsxs(_Fragment, { children: [_jsx(Text, { children: " " }), _jsx(Text, { dimColor: true, children: '─'.repeat(70) }), _jsx(Text, { bold: true, color: "yellow", children: 'Warnings (' + result.totalWarnings + ')' }), _jsx(Text, { dimColor: true, children: '─'.repeat(70) }), _jsx(Text, { children: " " }), result.components
61
61
  .filter((c) => c.warnings.length > 0)
62
62
  .flatMap((c) => c.warnings.map((w) => ({ component: c.name, warning: w })))
63
- .map((w, i) => (_jsx(Text, { color: "yellow", children: ' ⚠ ' + w.component + ': ' + w.warning }, i)))] })), _jsx(Text, { children: " " }), _jsx(Text, { dimColor: true, children: 'Run: analyze select --session ' + result.sessionId }), _jsx(Text, { children: " " })] }), _jsx(Box, { borderStyle: "single", paddingX: 1, children: _jsx(Text, { dimColor: true, children: "Press Enter or q to exit" }) })] }));
63
+ .map((w, i) => (_jsx(Text, { color: "yellow", children: ' ⚠ ' + (w.warning.startsWith(w.component + ': ') ? w.warning : w.component + ': ' + w.warning) }, i)))] })), _jsx(Text, { children: " " }), _jsx(Text, { dimColor: true, children: 'Run: analyze select --session ' + result.sessionId }), _jsx(Text, { children: " " })] }), _jsx(Box, { borderStyle: "single", paddingX: 1, children: _jsx(Text, { dimColor: true, children: "Press Enter or q to exit" }) })] }));
64
64
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contentful/experience-design-system-cli",
3
- "version": "2.11.4-dev-build-11add11.0",
3
+ "version": "2.11.4-dev-build-f3b5300.0",
4
4
  "description": "Contentful Experiences design system import CLI",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -37,7 +37,7 @@
37
37
  "svelte": "^5.56.4",
38
38
  "ts-morph": "^27.0.2",
39
39
  "typescript": "^5.9.3",
40
- "@contentful/experience-design-system-types": "2.11.4-dev-build-11add11.0"
40
+ "@contentful/experience-design-system-types": "2.11.4-dev-build-f3b5300.0"
41
41
  },
42
42
  "devDependencies": {
43
43
  "@tsconfig/node24": "^24.0.3",