@contentful/experience-design-system-cli 2.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (165) hide show
  1. package/README.md +532 -0
  2. package/bin/cli.js +58 -0
  3. package/dist/package.json +56 -0
  4. package/dist/src/analyze/command.d.ts +3 -0
  5. package/dist/src/analyze/command.js +175 -0
  6. package/dist/src/analyze/extract/astro.d.ts +5 -0
  7. package/dist/src/analyze/extract/astro.js +280 -0
  8. package/dist/src/analyze/extract/pipeline.d.ts +6 -0
  9. package/dist/src/analyze/extract/pipeline.js +298 -0
  10. package/dist/src/analyze/extract/react.d.ts +2 -0
  11. package/dist/src/analyze/extract/react.js +1949 -0
  12. package/dist/src/analyze/extract/slot-detection.d.ts +35 -0
  13. package/dist/src/analyze/extract/slot-detection.js +101 -0
  14. package/dist/src/analyze/extract/stencil.d.ts +2 -0
  15. package/dist/src/analyze/extract/stencil.js +293 -0
  16. package/dist/src/analyze/extract/tsx-shared.d.ts +8 -0
  17. package/dist/src/analyze/extract/tsx-shared.js +263 -0
  18. package/dist/src/analyze/extract/vue-tsx.d.ts +2 -0
  19. package/dist/src/analyze/extract/vue-tsx.js +498 -0
  20. package/dist/src/analyze/extract/vue.d.ts +5 -0
  21. package/dist/src/analyze/extract/vue.js +647 -0
  22. package/dist/src/analyze/extract/web-components.d.ts +2 -0
  23. package/dist/src/analyze/extract/web-components.js +866 -0
  24. package/dist/src/analyze/pre-classify.d.ts +17 -0
  25. package/dist/src/analyze/pre-classify.js +144 -0
  26. package/dist/src/analyze/select/command.d.ts +2 -0
  27. package/dist/src/analyze/select/command.js +256 -0
  28. package/dist/src/analyze/select/index.d.ts +6 -0
  29. package/dist/src/analyze/select/index.js +5 -0
  30. package/dist/src/analyze/select/parser.d.ts +6 -0
  31. package/dist/src/analyze/select/parser.js +53 -0
  32. package/dist/src/analyze/select/persistence.d.ts +9 -0
  33. package/dist/src/analyze/select/persistence.js +42 -0
  34. package/dist/src/analyze/select/stdout.d.ts +7 -0
  35. package/dist/src/analyze/select/stdout.js +3 -0
  36. package/dist/src/analyze/select/tui/App.d.ts +8 -0
  37. package/dist/src/analyze/select/tui/App.js +491 -0
  38. package/dist/src/analyze/select/tui/components/ComponentDetail.d.ts +20 -0
  39. package/dist/src/analyze/select/tui/components/ComponentDetail.js +43 -0
  40. package/dist/src/analyze/select/tui/components/FieldEditor.d.ts +11 -0
  41. package/dist/src/analyze/select/tui/components/FieldEditor.js +531 -0
  42. package/dist/src/analyze/select/tui/components/FinalizeDialog.d.ts +10 -0
  43. package/dist/src/analyze/select/tui/components/FinalizeDialog.js +15 -0
  44. package/dist/src/analyze/select/tui/components/HelpOverlay.d.ts +7 -0
  45. package/dist/src/analyze/select/tui/components/HelpOverlay.js +11 -0
  46. package/dist/src/analyze/select/tui/components/JsonEditor.d.ts +11 -0
  47. package/dist/src/analyze/select/tui/components/JsonEditor.js +154 -0
  48. package/dist/src/analyze/select/tui/components/JsonPanel.d.ts +11 -0
  49. package/dist/src/analyze/select/tui/components/JsonPanel.js +62 -0
  50. package/dist/src/analyze/select/tui/components/PreviewSummaryBar.d.ts +8 -0
  51. package/dist/src/analyze/select/tui/components/PreviewSummaryBar.js +29 -0
  52. package/dist/src/analyze/select/tui/components/QuitDialog.d.ts +8 -0
  53. package/dist/src/analyze/select/tui/components/QuitDialog.js +14 -0
  54. package/dist/src/analyze/select/tui/components/Sidebar.d.ts +15 -0
  55. package/dist/src/analyze/select/tui/components/Sidebar.js +48 -0
  56. package/dist/src/analyze/select/tui/components/SourcePanel.d.ts +11 -0
  57. package/dist/src/analyze/select/tui/components/SourcePanel.js +52 -0
  58. package/dist/src/analyze/select/tui/components/StatusBar.d.ts +11 -0
  59. package/dist/src/analyze/select/tui/components/StatusBar.js +6 -0
  60. package/dist/src/analyze/select/tui/components/TopBar.d.ts +10 -0
  61. package/dist/src/analyze/select/tui/components/TopBar.js +5 -0
  62. package/dist/src/analyze/select/tui/hooks/useImmediateInput.d.ts +24 -0
  63. package/dist/src/analyze/select/tui/hooks/useImmediateInput.js +68 -0
  64. package/dist/src/analyze/select/tui/hooks/useKeymap.d.ts +24 -0
  65. package/dist/src/analyze/select/tui/hooks/useKeymap.js +67 -0
  66. package/dist/src/analyze/select/tui/hooks/useSession.d.ts +19 -0
  67. package/dist/src/analyze/select/tui/hooks/useSession.js +52 -0
  68. package/dist/src/analyze/select/tui/hooks/useUndo.d.ts +8 -0
  69. package/dist/src/analyze/select/tui/hooks/useUndo.js +26 -0
  70. package/dist/src/analyze/select/types.d.ts +46 -0
  71. package/dist/src/analyze/select/types.js +20 -0
  72. package/dist/src/analyze/select-agent/command.d.ts +2 -0
  73. package/dist/src/analyze/select-agent/command.js +208 -0
  74. package/dist/src/analyze/tui/AnalyzeView.d.ts +24 -0
  75. package/dist/src/analyze/tui/AnalyzeView.js +38 -0
  76. package/dist/src/apply/api-client.d.ts +35 -0
  77. package/dist/src/apply/api-client.js +143 -0
  78. package/dist/src/apply/command.d.ts +6 -0
  79. package/dist/src/apply/command.js +787 -0
  80. package/dist/src/apply/manifest.d.ts +1 -0
  81. package/dist/src/apply/manifest.js +1 -0
  82. package/dist/src/apply/tui/SelectView.d.ts +18 -0
  83. package/dist/src/apply/tui/SelectView.js +34 -0
  84. package/dist/src/apply/tui/ServerApplyView.d.ts +32 -0
  85. package/dist/src/apply/tui/ServerApplyView.js +42 -0
  86. package/dist/src/apply/tui/ServerPreviewView.d.ts +9 -0
  87. package/dist/src/apply/tui/ServerPreviewView.js +21 -0
  88. package/dist/src/credentials-store.d.ts +8 -0
  89. package/dist/src/credentials-store.js +30 -0
  90. package/dist/src/generate/agent-runner.d.ts +86 -0
  91. package/dist/src/generate/agent-runner.js +314 -0
  92. package/dist/src/generate/command.d.ts +2 -0
  93. package/dist/src/generate/command.js +545 -0
  94. package/dist/src/generate/edit/command.d.ts +2 -0
  95. package/dist/src/generate/edit/command.js +126 -0
  96. package/dist/src/generate/prompt-builder.d.ts +18 -0
  97. package/dist/src/generate/prompt-builder.js +202 -0
  98. package/dist/src/generate/tui/GenerateView.d.ts +12 -0
  99. package/dist/src/generate/tui/GenerateView.js +10 -0
  100. package/dist/src/import/command.d.ts +2 -0
  101. package/dist/src/import/command.js +96 -0
  102. package/dist/src/import/orchestrator.d.ts +37 -0
  103. package/dist/src/import/orchestrator.js +374 -0
  104. package/dist/src/import/path-utils.d.ts +15 -0
  105. package/dist/src/import/path-utils.js +30 -0
  106. package/dist/src/import/tui/WizardApp.d.ts +10 -0
  107. package/dist/src/import/tui/WizardApp.js +906 -0
  108. package/dist/src/import/tui/steps/CredentialsStep.d.ts +15 -0
  109. package/dist/src/import/tui/steps/CredentialsStep.js +79 -0
  110. package/dist/src/import/tui/steps/DoneStep.d.ts +20 -0
  111. package/dist/src/import/tui/steps/DoneStep.js +17 -0
  112. package/dist/src/import/tui/steps/ErrorStep.d.ts +8 -0
  113. package/dist/src/import/tui/steps/ErrorStep.js +11 -0
  114. package/dist/src/import/tui/steps/GateStep.d.ts +14 -0
  115. package/dist/src/import/tui/steps/GateStep.js +20 -0
  116. package/dist/src/import/tui/steps/GenerateReviewStep.d.ts +8 -0
  117. package/dist/src/import/tui/steps/GenerateReviewStep.js +208 -0
  118. package/dist/src/import/tui/steps/PathValidationStep.d.ts +10 -0
  119. package/dist/src/import/tui/steps/PathValidationStep.js +151 -0
  120. package/dist/src/import/tui/steps/PreviewStep.d.ts +21 -0
  121. package/dist/src/import/tui/steps/PreviewStep.js +36 -0
  122. package/dist/src/import/tui/steps/RunningStep.d.ts +10 -0
  123. package/dist/src/import/tui/steps/RunningStep.js +20 -0
  124. package/dist/src/import/tui/steps/TokenInputStep.d.ts +8 -0
  125. package/dist/src/import/tui/steps/TokenInputStep.js +70 -0
  126. package/dist/src/import/tui/steps/WelcomeStep.d.ts +7 -0
  127. package/dist/src/import/tui/steps/WelcomeStep.js +33 -0
  128. package/dist/src/import/tui/steps/WizardPreviewStep.d.ts +15 -0
  129. package/dist/src/import/tui/steps/WizardPreviewStep.js +121 -0
  130. package/dist/src/import/tui/steps/preview-diff.d.ts +10 -0
  131. package/dist/src/import/tui/steps/preview-diff.js +132 -0
  132. package/dist/src/index.d.ts +1 -0
  133. package/dist/src/index.js +2 -0
  134. package/dist/src/output/format.d.ts +23 -0
  135. package/dist/src/output/format.js +110 -0
  136. package/dist/src/print/command.d.ts +2 -0
  137. package/dist/src/print/command.js +199 -0
  138. package/dist/src/print/validate/tui/ValidateView.d.ts +15 -0
  139. package/dist/src/print/validate/tui/ValidateView.js +37 -0
  140. package/dist/src/print/validate/validators/cdf-validator.d.ts +2 -0
  141. package/dist/src/print/validate/validators/cdf-validator.js +104 -0
  142. package/dist/src/print/validate/validators/dtcg-validator.d.ts +2 -0
  143. package/dist/src/print/validate/validators/dtcg-validator.js +110 -0
  144. package/dist/src/print/validate/validators/format-errors.d.ts +12 -0
  145. package/dist/src/print/validate/validators/format-errors.js +18 -0
  146. package/dist/src/program.d.ts +2 -0
  147. package/dist/src/program.js +25 -0
  148. package/dist/src/session/command.d.ts +2 -0
  149. package/dist/src/session/command.js +261 -0
  150. package/dist/src/session/db.d.ts +111 -0
  151. package/dist/src/session/db.js +1114 -0
  152. package/dist/src/session/migration.d.ts +4 -0
  153. package/dist/src/session/migration.js +117 -0
  154. package/dist/src/session/session-id.d.ts +1 -0
  155. package/dist/src/session/session-id.js +212 -0
  156. package/dist/src/session/stats.d.ts +27 -0
  157. package/dist/src/session/stats.js +89 -0
  158. package/dist/src/setup/command.d.ts +2 -0
  159. package/dist/src/setup/command.js +765 -0
  160. package/dist/src/types.d.ts +48 -0
  161. package/dist/src/types.js +1 -0
  162. package/package.json +55 -0
  163. package/skills/generate-components.md +361 -0
  164. package/skills/generate-tokens.md +194 -0
  165. package/skills/select-components.md +180 -0
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Slot detection utilities for ReactNode-typed props.
3
+ *
4
+ * Props whose type resolves to ReactNode/ReactElement/JSX.Element are treated
5
+ * as slots rather than plain props — unless their name is in the content-name
6
+ * exception list (label, title, description, etc.).
7
+ *
8
+ * Array ReactNode types (e.g. ReactNode[]) ALWAYS become slots, even if the
9
+ * prop name is in the exception list.
10
+ */
11
+ /**
12
+ * Prop names that should remain as props even when typed as ReactNode.
13
+ * These are typically text-content names, not composable slots.
14
+ */
15
+ export declare const CONTENT_NAME_EXCEPTIONS: Set<string>;
16
+ /**
17
+ * Checks if a type string represents a ReactNode/ReactElement/JSX.Element type,
18
+ * including unions with null/undefined.
19
+ */
20
+ export declare function isReactNodeType(typeText: string): boolean;
21
+ /**
22
+ * Checks if a type string represents an array of ReactNode.
23
+ * Examples: ReactNode[], React.ReactNode[], Array<ReactNode>
24
+ */
25
+ export declare function isArrayReactNodeType(typeText: string): boolean;
26
+ /**
27
+ * Determines whether a prop should be converted to a slot based on its name and type.
28
+ *
29
+ * Rules:
30
+ * 1. If the type is not a ReactNode type → false
31
+ * 2. If the type is an array ReactNode → true (overrides exception list)
32
+ * 3. If the name is in the content-name exception list → false
33
+ * 4. Otherwise → true
34
+ */
35
+ export declare function shouldBeSlot(propName: string, typeText: string): boolean;
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Slot detection utilities for ReactNode-typed props.
3
+ *
4
+ * Props whose type resolves to ReactNode/ReactElement/JSX.Element are treated
5
+ * as slots rather than plain props — unless their name is in the content-name
6
+ * exception list (label, title, description, etc.).
7
+ *
8
+ * Array ReactNode types (e.g. ReactNode[]) ALWAYS become slots, even if the
9
+ * prop name is in the exception list.
10
+ */
11
+ /**
12
+ * Prop names that should remain as props even when typed as ReactNode.
13
+ * These are typically text-content names, not composable slots.
14
+ */
15
+ export const CONTENT_NAME_EXCEPTIONS = new Set([
16
+ 'label',
17
+ 'title',
18
+ 'description',
19
+ 'text',
20
+ 'caption',
21
+ 'message',
22
+ 'placeholder',
23
+ 'tooltip',
24
+ 'heading',
25
+ 'subheading',
26
+ 'body',
27
+ 'summary',
28
+ 'excerpt',
29
+ ]);
30
+ const REACT_NODE_EXACT_PATTERNS = ['ReactNode', 'React.ReactNode', 'ReactElement', 'React.ReactElement', 'JSX.Element'];
31
+ /**
32
+ * Checks if a type string represents a ReactNode/ReactElement/JSX.Element type,
33
+ * including unions with null/undefined.
34
+ */
35
+ export function isReactNodeType(typeText) {
36
+ const normalized = typeText.replace(/\s+/g, ' ').trim();
37
+ // Strip optional markers and union with null/undefined
38
+ const stripped = normalized
39
+ .split('|')
40
+ .map((part) => part.trim())
41
+ .filter((part) => part !== 'null' && part !== 'undefined')
42
+ .join(' | ');
43
+ // Check exact match (after stripping null/undefined union members)
44
+ if (REACT_NODE_EXACT_PATTERNS.includes(stripped)) {
45
+ return true;
46
+ }
47
+ // Check array patterns
48
+ if (isArrayReactNodeType(typeText)) {
49
+ return true;
50
+ }
51
+ return false;
52
+ }
53
+ /**
54
+ * Checks if a type string represents an array of ReactNode.
55
+ * Examples: ReactNode[], React.ReactNode[], Array<ReactNode>
56
+ */
57
+ export function isArrayReactNodeType(typeText) {
58
+ const normalized = typeText.replace(/\s+/g, ' ').trim();
59
+ // Strip optional markers and union with null/undefined
60
+ const stripped = normalized
61
+ .split('|')
62
+ .map((part) => part.trim())
63
+ .filter((part) => part !== 'null' && part !== 'undefined')
64
+ .join(' | ');
65
+ // Pattern: ReactNode[] or React.ReactNode[] etc.
66
+ for (const pattern of REACT_NODE_EXACT_PATTERNS) {
67
+ if (stripped === `${pattern}[]`) {
68
+ return true;
69
+ }
70
+ }
71
+ // Pattern: Array<ReactNode> or Array<React.ReactNode> etc.
72
+ for (const pattern of REACT_NODE_EXACT_PATTERNS) {
73
+ if (stripped === `Array<${pattern}>`) {
74
+ return true;
75
+ }
76
+ }
77
+ return false;
78
+ }
79
+ /**
80
+ * Determines whether a prop should be converted to a slot based on its name and type.
81
+ *
82
+ * Rules:
83
+ * 1. If the type is not a ReactNode type → false
84
+ * 2. If the type is an array ReactNode → true (overrides exception list)
85
+ * 3. If the name is in the content-name exception list → false
86
+ * 4. Otherwise → true
87
+ */
88
+ export function shouldBeSlot(propName, typeText) {
89
+ if (!isReactNodeType(typeText)) {
90
+ return false;
91
+ }
92
+ // Array ReactNode always becomes a slot, even for exception names
93
+ if (isArrayReactNodeType(typeText)) {
94
+ return true;
95
+ }
96
+ // Check exception list
97
+ if (CONTENT_NAME_EXCEPTIONS.has(propName)) {
98
+ return false;
99
+ }
100
+ return true;
101
+ }
@@ -0,0 +1,2 @@
1
+ import type { ComponentExtractionResult } from '../../types.js';
2
+ export declare function extractStencilComponents(filePaths: string[]): Promise<ComponentExtractionResult>;
@@ -0,0 +1,293 @@
1
+ import { Project, Node } from 'ts-morph';
2
+ function isStencilFile(sourceFile) {
3
+ return sourceFile.getImportDeclarations().some((imp) => imp.getModuleSpecifierValue() === '@stencil/core');
4
+ }
5
+ function kebabToPascal(input) {
6
+ return input
7
+ .split('-')
8
+ .filter(Boolean)
9
+ .map((s) => s.charAt(0).toUpperCase() + s.slice(1))
10
+ .join('');
11
+ }
12
+ function getComponentTag(classDecl) {
13
+ for (const decorator of classDecl.getDecorators()) {
14
+ if (decorator.getName() !== 'Component')
15
+ continue;
16
+ const args = decorator.getArguments();
17
+ if (args.length === 0)
18
+ continue;
19
+ const arg = args[0];
20
+ if (!Node.isObjectLiteralExpression(arg))
21
+ continue;
22
+ const tagProp = arg.getProperty('tag');
23
+ if (!tagProp || !Node.isPropertyAssignment(tagProp))
24
+ continue;
25
+ const initializer = tagProp.getInitializer();
26
+ if (!initializer || !Node.isStringLiteral(initializer))
27
+ continue;
28
+ return initializer.getLiteralValue();
29
+ }
30
+ return undefined;
31
+ }
32
+ function hasDecorator(node, decoratorName) {
33
+ if (!Node.isPropertyDeclaration(node))
34
+ return false;
35
+ return node.getDecorators().some((d) => d.getName() === decoratorName);
36
+ }
37
+ function extractAllowedValues(typeText) {
38
+ // Match inline string literal unions: 'a' | 'b' | 'c'
39
+ const literalPattern = /^'[^']*'(?:\s*\|\s*'[^']*')+$/;
40
+ if (!literalPattern.test(typeText.trim()))
41
+ return undefined;
42
+ const values = typeText
43
+ .split('|')
44
+ .map((v) => v.trim().replace(/^'|'$/g, ''))
45
+ .filter(Boolean)
46
+ .sort();
47
+ return values.length >= 2 ? values : undefined;
48
+ }
49
+ function extractProps(classDecl) {
50
+ const props = [];
51
+ for (const property of classDecl.getProperties()) {
52
+ if (!hasDecorator(property, 'Prop'))
53
+ continue;
54
+ const name = property.getName();
55
+ const typeNode = property.getTypeNode();
56
+ const typeText = typeNode ? typeNode.getText() : 'unknown';
57
+ const hasQuestionToken = property.hasQuestionToken();
58
+ const hasExclamation = property.hasExclamationToken();
59
+ const initializer = property.getInitializer();
60
+ let defaultValue;
61
+ if (initializer) {
62
+ if (Node.isStringLiteral(initializer)) {
63
+ defaultValue = initializer.getLiteralValue();
64
+ }
65
+ else {
66
+ defaultValue = initializer.getText();
67
+ }
68
+ }
69
+ const isRequired = hasExclamation || (!hasQuestionToken && !initializer);
70
+ // JSDoc description
71
+ const jsDocs = property.getJsDocs();
72
+ let description;
73
+ let isDeprecated = false;
74
+ if (jsDocs.length > 0) {
75
+ const jsDoc = jsDocs[0];
76
+ description = jsDoc.getDescription().trim() || undefined;
77
+ for (const tag of jsDoc.getTags()) {
78
+ if (tag.getTagName() === 'deprecated') {
79
+ isDeprecated = true;
80
+ // The deprecated tag may carry a message (e.g. "@deprecated Use size instead.")
81
+ const tagComment = tag.getCommentText()?.trim();
82
+ if (tagComment && !description) {
83
+ description = tagComment;
84
+ }
85
+ }
86
+ }
87
+ }
88
+ if (isDeprecated && description) {
89
+ description = `[DEPRECATED] ${description}`;
90
+ }
91
+ else if (isDeprecated) {
92
+ description = '[DEPRECATED]';
93
+ }
94
+ const allowedValues = extractAllowedValues(typeText);
95
+ props.push({
96
+ name,
97
+ type: typeText,
98
+ required: isRequired,
99
+ ...(defaultValue !== undefined && { defaultValue }),
100
+ ...(description && { description }),
101
+ ...(allowedValues && { allowedValues }),
102
+ });
103
+ }
104
+ return props.sort((a, b) => a.name.localeCompare(b.name));
105
+ }
106
+ function normalizeSlotName(name) {
107
+ const normalizedName = name && name.length > 0 ? name : 'default';
108
+ return {
109
+ name: normalizedName,
110
+ isDefault: normalizedName === 'default',
111
+ };
112
+ }
113
+ function extractSlots(classDecl, warnings, componentName) {
114
+ const slots = new Map();
115
+ const upsertSlot = (slot) => {
116
+ const existing = slots.get(slot.name);
117
+ if (!existing) {
118
+ slots.set(slot.name, slot);
119
+ return;
120
+ }
121
+ slots.set(slot.name, {
122
+ ...existing,
123
+ ...slot,
124
+ description: existing.description ?? slot.description,
125
+ });
126
+ };
127
+ const jsDocs = classDecl.getJsDocs();
128
+ for (const jsDoc of jsDocs) {
129
+ for (const tag of jsDoc.getTags()) {
130
+ if (tag.getTagName() !== 'slot')
131
+ continue;
132
+ const comment = tag.getCommentText()?.trim();
133
+ if (!comment)
134
+ continue;
135
+ if (comment.startsWith('{')) {
136
+ try {
137
+ const parsed = JSON.parse(comment);
138
+ const slot = normalizeSlotName(parsed.name);
139
+ let description = parsed.description || undefined;
140
+ if (parsed.isDeprecated && description) {
141
+ description = `[DEPRECATED] ${description}`;
142
+ }
143
+ else if (parsed.isDeprecated) {
144
+ description = '[DEPRECATED]';
145
+ }
146
+ upsertSlot({
147
+ ...slot,
148
+ ...(description && { description }),
149
+ });
150
+ continue;
151
+ }
152
+ catch {
153
+ warnings.push(`Failed to parse @slot JSDoc in ${componentName}: invalid JSON "${comment}"`);
154
+ continue;
155
+ }
156
+ }
157
+ const match = comment.match(/^(?:(\S+)\s*-\s*)?(.*)$/s);
158
+ if (!match)
159
+ continue;
160
+ const [, rawName, rawDescription] = match;
161
+ const slot = normalizeSlotName(rawName);
162
+ const description = rawDescription.trim() || undefined;
163
+ upsertSlot({
164
+ ...slot,
165
+ ...(description && { description }),
166
+ });
167
+ }
168
+ }
169
+ for (const method of classDecl.getMethods()) {
170
+ method.forEachDescendant((node) => {
171
+ if (!Node.isJsxSelfClosingElement(node) && !Node.isJsxElement(node))
172
+ return;
173
+ const openingElement = Node.isJsxElement(node) ? node.getOpeningElement() : node;
174
+ const tagName = openingElement.getTagNameNode().getText();
175
+ const slotAttribute = openingElement
176
+ .getAttributes()
177
+ .find((attribute) => Node.isJsxAttribute(attribute) && attribute.getNameNode().getText() === 'slot');
178
+ if (tagName === 'slot') {
179
+ const nameAttribute = openingElement
180
+ .getAttributes()
181
+ .find((attribute) => Node.isJsxAttribute(attribute) && attribute.getNameNode().getText() === 'name');
182
+ const initializer = Node.isJsxAttribute(nameAttribute) ? nameAttribute.getInitializer() : undefined;
183
+ const name = initializer && Node.isStringLiteral(initializer)
184
+ ? initializer.getLiteralValue()
185
+ : initializer && Node.isJsxExpression(initializer)
186
+ ? (initializer.getExpression()?.getText() ?? '')
187
+ : '';
188
+ upsertSlot({
189
+ ...normalizeSlotName(name),
190
+ });
191
+ return;
192
+ }
193
+ if (!Node.isJsxAttribute(slotAttribute))
194
+ return;
195
+ const initializer = slotAttribute.getInitializer();
196
+ if (!initializer || !Node.isStringLiteral(initializer))
197
+ return;
198
+ const name = initializer.getLiteralValue();
199
+ if (!name)
200
+ return;
201
+ upsertSlot({
202
+ name,
203
+ isDefault: false,
204
+ });
205
+ });
206
+ }
207
+ return [...slots.values()].sort((a, b) => a.name.localeCompare(b.name));
208
+ }
209
+ function detectEvents(classDecl) {
210
+ const eventNames = [];
211
+ for (const property of classDecl.getProperties()) {
212
+ if (!hasDecorator(property, 'Event'))
213
+ continue;
214
+ eventNames.push(property.getName());
215
+ }
216
+ return eventNames.sort();
217
+ }
218
+ function extractFromSourceFile(sourceFile, warnings) {
219
+ const components = [];
220
+ for (const classDecl of sourceFile.getClasses()) {
221
+ const tag = getComponentTag(classDecl);
222
+ if (!tag)
223
+ continue;
224
+ const name = kebabToPascal(tag);
225
+ const props = extractProps(classDecl);
226
+ const slots = extractSlots(classDecl, warnings, name);
227
+ const eventNames = detectEvents(classDecl);
228
+ if (eventNames.length > 0) {
229
+ warnings.push(`Component ${name} has ${eventNames.length} events not captured: ${eventNames.join(', ')}`);
230
+ }
231
+ components.push({
232
+ name,
233
+ source: sourceFile.getFilePath(),
234
+ framework: 'stencil',
235
+ props,
236
+ slots,
237
+ });
238
+ }
239
+ return components;
240
+ }
241
+ function detectFunctionalComponents(sourceFile, warnings) {
242
+ for (const [exportName, declarations] of sourceFile.getExportedDeclarations()) {
243
+ for (const decl of declarations) {
244
+ if (!Node.isVariableDeclaration(decl))
245
+ continue;
246
+ const typeNode = decl.getTypeNode();
247
+ if (!typeNode)
248
+ continue;
249
+ const typeText = typeNode.getText();
250
+ if (!typeText.startsWith('FunctionalComponent'))
251
+ continue;
252
+ warnings.push(`Stencil FunctionalComponent detected but not extracted: ${exportName} in ${sourceFile.getFilePath()}`);
253
+ }
254
+ }
255
+ }
256
+ export async function extractStencilComponents(filePaths) {
257
+ const tsxFiles = filePaths.filter((f) => /\.[jt]sx$/.test(f));
258
+ if (tsxFiles.length === 0) {
259
+ return { components: [], warnings: [] };
260
+ }
261
+ const project = new Project({
262
+ compilerOptions: {
263
+ jsx: 1, // JsxEmit.Preserve
264
+ target: 99, // ScriptTarget.ESNext
265
+ module: 99, // ModuleKind.ESNext
266
+ moduleResolution: 100, // ModuleResolutionKind.Bundler
267
+ skipLibCheck: true,
268
+ allowJs: true,
269
+ },
270
+ skipAddingFilesFromTsConfig: true,
271
+ });
272
+ for (const filePath of tsxFiles) {
273
+ project.addSourceFileAtPath(filePath);
274
+ }
275
+ const warnings = [];
276
+ const components = [];
277
+ for (const sourceFile of project.getSourceFiles()) {
278
+ try {
279
+ if (!isStencilFile(sourceFile))
280
+ continue;
281
+ const extracted = extractFromSourceFile(sourceFile, warnings);
282
+ components.push(...extracted);
283
+ detectFunctionalComponents(sourceFile, warnings);
284
+ }
285
+ catch (e) {
286
+ warnings.push(`Failed to extract from ${sourceFile.getFilePath()}: ${e instanceof Error ? e.message : String(e)}`);
287
+ }
288
+ }
289
+ return {
290
+ components: components.sort((a, b) => a.name.localeCompare(b.name)),
291
+ warnings,
292
+ };
293
+ }
@@ -0,0 +1,8 @@
1
+ import { Node, type Type } from 'ts-morph';
2
+ export declare function extractAllowedValues(type: Type): string[] | undefined;
3
+ export declare function getNodeDefinitions(node: Node): {
4
+ getDeclarationNode(): Node | undefined;
5
+ }[];
6
+ export declare function getTypeTargetDeclarations(targetNode: Node, allowWorkspaceImportFallback?: boolean): Node[];
7
+ export declare function getValueTargetDeclarations(targetNode: Node): Node[];
8
+ export declare function getTypeReferenceName(typeNode: Node): string | undefined;