@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,647 @@
1
+ import { basename, dirname, resolve, join } from 'node:path';
2
+ import { readFile } from 'node:fs/promises';
3
+ import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
4
+ import os from 'node:os';
5
+ import { parse as parseSFC } from '@vue/compiler-sfc';
6
+ import { Project, Node } from 'ts-morph';
7
+ // @vue/compiler-core NodeTypes enum values (stable since Vue 3.0)
8
+ const ELEMENT_TYPE = 1;
9
+ const ATTRIBUTE_TYPE = 6;
10
+ const VUE_TYPE_MAP = {
11
+ String: 'string',
12
+ Number: 'number',
13
+ Boolean: 'boolean',
14
+ Array: 'any[]',
15
+ Object: 'object',
16
+ Function: 'function',
17
+ Date: 'Date',
18
+ Symbol: 'symbol',
19
+ };
20
+ const VUE_EXTRACT_CONCURRENCY = Number(process.env['EDS_EXTRACT_CONCURRENCY'] ?? 0) || os.cpus().length;
21
+ export async function extractVueComponents(filePaths, onProgress) {
22
+ const vueFiles = filePaths.filter((f) => f.endsWith('.vue'));
23
+ if (vueFiles.length === 0) {
24
+ return { components: [], warnings: [] };
25
+ }
26
+ const warnings = [];
27
+ const components = [];
28
+ let filesProcessed = 0;
29
+ let componentsFound = 0;
30
+ const queue = [...vueFiles];
31
+ async function worker() {
32
+ while (queue.length > 0) {
33
+ const filePath = queue.shift();
34
+ if (!filePath)
35
+ break;
36
+ try {
37
+ const source = await readFile(filePath, 'utf-8');
38
+ const { component, warnings: fileWarnings } = await extractFromVueSFC(filePath, source);
39
+ warnings.push(...fileWarnings);
40
+ if (component) {
41
+ components.push(component);
42
+ componentsFound++;
43
+ }
44
+ }
45
+ catch (e) {
46
+ warnings.push(`Failed to extract from ${filePath}: ${e instanceof Error ? e.message : String(e)}`);
47
+ }
48
+ filesProcessed++;
49
+ onProgress?.({ filesProcessed, componentsFound });
50
+ }
51
+ }
52
+ await Promise.all(Array.from({ length: Math.min(VUE_EXTRACT_CONCURRENCY, vueFiles.length) }, worker));
53
+ return {
54
+ components: components.sort((a, b) => a.name.localeCompare(b.name)),
55
+ warnings,
56
+ };
57
+ }
58
+ async function extractFromVueSFC(filePath, source) {
59
+ const { descriptor, errors } = parseSFC(source);
60
+ const fileWarnings = errors.map((e) => `Parse error in ${filePath}: ${e instanceof Error ? e.message : String(e)}`);
61
+ const name = getVueComponentName(filePath);
62
+ // Props: <script setup> wins over Options API <script>
63
+ let props = [];
64
+ const setupProps = descriptor.scriptSetup ? await extractSetupProps(filePath, descriptor.scriptSetup.content) : null;
65
+ if (setupProps !== null) {
66
+ props = setupProps;
67
+ }
68
+ else if (descriptor.script) {
69
+ return extractOptionsComponent(filePath, descriptor.script.content, fileWarnings, name, descriptor.template?.ast, source);
70
+ }
71
+ // Slots: from template AST + runtime $slots access
72
+ let slots = descriptor.template?.ast
73
+ ? extractSlotsFromTemplate(descriptor.template.ast)
74
+ : [];
75
+ const runtimeSlots = extractSlotsFromRuntimeAccess(source);
76
+ slots = mergeSlotsDedup(slots, runtimeSlots);
77
+ return {
78
+ component: {
79
+ name,
80
+ source: filePath,
81
+ framework: 'vue',
82
+ props,
83
+ slots,
84
+ },
85
+ warnings: fileWarnings,
86
+ };
87
+ }
88
+ function getVueComponentName(filePath) {
89
+ const fileName = basename(filePath, '.vue');
90
+ if (fileName !== 'index') {
91
+ return fileName;
92
+ }
93
+ const parentDirName = basename(dirname(filePath));
94
+ return parentDirName || fileName;
95
+ }
96
+ async function extractOptionsComponent(filePath, scriptContent, fileWarnings, name, templateAst, sfcSource) {
97
+ const props = await extractOptionsProps(filePath, scriptContent);
98
+ let slots = templateAst ? extractSlotsFromTemplate(templateAst) : [];
99
+ if (sfcSource) {
100
+ const runtimeSlots = extractSlotsFromRuntimeAccess(sfcSource);
101
+ slots = mergeSlotsDedup(slots, runtimeSlots);
102
+ }
103
+ return {
104
+ component: {
105
+ name,
106
+ source: filePath,
107
+ framework: 'vue',
108
+ props,
109
+ slots,
110
+ },
111
+ warnings: fileWarnings,
112
+ };
113
+ }
114
+ async function extractSetupProps(filePath, scriptSetupContent) {
115
+ const project = new Project({
116
+ compilerOptions: {
117
+ strict: false,
118
+ target: 99, // ESNext
119
+ module: 99, // ESNext
120
+ allowJs: true,
121
+ },
122
+ useInMemoryFileSystem: true,
123
+ skipAddingFilesFromTsConfig: true,
124
+ });
125
+ const sf = project.createSourceFile('__setup__.ts', scriptSetupContent);
126
+ // Try generic syntax: defineProps<{...}>()
127
+ let genericTypeText = null;
128
+ sf.forEachDescendant((node) => {
129
+ if (genericTypeText !== null)
130
+ return;
131
+ if (Node.isCallExpression(node) &&
132
+ node.getExpression().getText() === 'defineProps' &&
133
+ node.getTypeArguments().length > 0) {
134
+ genericTypeText = node.getTypeArguments()[0].getText();
135
+ }
136
+ });
137
+ if (genericTypeText !== null) {
138
+ return extractGenericProps(genericTypeText);
139
+ }
140
+ const importedObjectRefs = collectSetupImportedObjectRefs(sf, filePath);
141
+ // Try object syntax: defineProps({...})
142
+ let objectProps = null;
143
+ const visitedImports = new Set();
144
+ for (const node of sf.getDescendants()) {
145
+ if (objectProps !== null)
146
+ break;
147
+ if (Node.isCallExpression(node) &&
148
+ node.getExpression().getText() === 'defineProps' &&
149
+ node.getTypeArguments().length === 0) {
150
+ const args = node.getArguments();
151
+ if (args.length > 0 && Node.isObjectLiteralExpression(args[0])) {
152
+ objectProps = await parseSetupObjectProps(args[0], importedObjectRefs, visitedImports);
153
+ }
154
+ }
155
+ }
156
+ if (objectProps !== null) {
157
+ return objectProps;
158
+ }
159
+ // No defineProps found in setup script
160
+ return null;
161
+ }
162
+ function extractGenericProps(typeText) {
163
+ const project = new Project({
164
+ compilerOptions: {
165
+ strict: true,
166
+ target: 99,
167
+ module: 99,
168
+ },
169
+ useInMemoryFileSystem: true,
170
+ skipAddingFilesFromTsConfig: true,
171
+ });
172
+ // Wrap in a type alias so we can resolve it
173
+ const sf = project.createSourceFile('__props__.ts', `type __Props__ = ${typeText};`);
174
+ const typeAlias = sf.getTypeAlias('__Props__');
175
+ if (!typeAlias)
176
+ return [];
177
+ const type = typeAlias.getType();
178
+ const props = [];
179
+ for (const property of type.getProperties()) {
180
+ const name = property.getName();
181
+ const decl = property.getValueDeclaration() ?? property.getDeclarations()[0];
182
+ if (!decl)
183
+ continue;
184
+ const propType = property.getTypeAtLocation(decl);
185
+ // Strip `| undefined` from optional types for clean output
186
+ let resolvedTypeText = propType.getText(decl);
187
+ if (property.isOptional()) {
188
+ resolvedTypeText = resolvedTypeText.replace(/\s*\|\s*undefined$/, '').replace(/^undefined\s*\|\s*/, '');
189
+ }
190
+ props.push({
191
+ name,
192
+ type: resolvedTypeText,
193
+ required: !property.isOptional(),
194
+ });
195
+ }
196
+ return props.sort((a, b) => a.name.localeCompare(b.name));
197
+ }
198
+ function collectSetupImportedObjectRefs(sf, filePath) {
199
+ const refs = new Map();
200
+ for (const importDecl of sf.getImportDeclarations()) {
201
+ const specifier = importDecl.getModuleSpecifierValue();
202
+ if (!specifier.startsWith('.'))
203
+ continue;
204
+ const resolvedImportPath = resolveLocalScriptModule(filePath, specifier);
205
+ if (!resolvedImportPath)
206
+ continue;
207
+ for (const namedImport of importDecl.getNamedImports()) {
208
+ refs.set(namedImport.getAliasNode()?.getText() ?? namedImport.getName(), {
209
+ filePath: resolvedImportPath,
210
+ exportName: namedImport.getName(),
211
+ });
212
+ }
213
+ }
214
+ return refs;
215
+ }
216
+ function resolveLocalScriptModule(importingFilePath, specifier) {
217
+ const basePath = resolve(dirname(importingFilePath), specifier);
218
+ const candidates = [
219
+ basePath,
220
+ `${basePath}.js`,
221
+ `${basePath}.ts`,
222
+ `${basePath}.mjs`,
223
+ `${basePath}.cjs`,
224
+ join(basePath, 'index.js'),
225
+ join(basePath, 'index.ts'),
226
+ join(basePath, 'index.mjs'),
227
+ join(basePath, 'index.cjs'),
228
+ ];
229
+ for (const candidate of candidates) {
230
+ if (existsSync(candidate) && statSync(candidate).isFile()) {
231
+ return candidate;
232
+ }
233
+ }
234
+ return null;
235
+ }
236
+ async function parseSetupObjectProps(obj, importedObjectRefs, visitedImports) {
237
+ const mergedProps = new Map();
238
+ for (const prop of parseObjectProps(obj)) {
239
+ mergedProps.set(prop.name, prop);
240
+ }
241
+ for (const prop of obj.getProperties()) {
242
+ if (!Node.isSpreadAssignment(prop))
243
+ continue;
244
+ const expression = prop.getExpression();
245
+ if (!Node.isIdentifier(expression))
246
+ continue;
247
+ const importedRef = importedObjectRefs.get(expression.getText());
248
+ if (!importedRef)
249
+ continue;
250
+ const importedProps = await extractNamedObjectExportProps(importedRef, visitedImports);
251
+ for (const importedProp of importedProps) {
252
+ if (!mergedProps.has(importedProp.name)) {
253
+ mergedProps.set(importedProp.name, importedProp);
254
+ }
255
+ }
256
+ }
257
+ return [...mergedProps.values()].sort((a, b) => a.name.localeCompare(b.name));
258
+ }
259
+ async function extractNamedObjectExportProps(importedRef, visitedImports) {
260
+ const visitKey = `${importedRef.filePath}:${importedRef.exportName}`;
261
+ if (visitedImports.has(visitKey))
262
+ return [];
263
+ visitedImports.add(visitKey);
264
+ const source = await readFile(importedRef.filePath, 'utf-8');
265
+ const project = new Project({
266
+ compilerOptions: {
267
+ strict: false,
268
+ target: 99,
269
+ module: 99,
270
+ allowJs: true,
271
+ },
272
+ useInMemoryFileSystem: true,
273
+ skipAddingFilesFromTsConfig: true,
274
+ });
275
+ const sf = project.createSourceFile('__imported_object__.ts', source);
276
+ for (const declaration of sf.getVariableDeclarations()) {
277
+ if (declaration.getName() !== importedRef.exportName)
278
+ continue;
279
+ const initializer = declaration.getInitializer();
280
+ if (!initializer || !Node.isObjectLiteralExpression(initializer))
281
+ continue;
282
+ return parseObjectProps(initializer);
283
+ }
284
+ return [];
285
+ }
286
+ /**
287
+ * Resolve a bare module specifier (e.g. "primevue/select", "@primevue/core/baseeditableholder")
288
+ * to a .vue source file within the same monorepo workspace.
289
+ *
290
+ * Strategy:
291
+ * 1. Walk up from the importing file to find the monorepo root (directory with a
292
+ * root package.json that contains "workspaces" or a pnpm-workspace.yaml).
293
+ * 2. Scan workspace package directories for a package.json whose "name" matches
294
+ * the package portion of the specifier.
295
+ * 3. Use the package.json "exports" field (or "main") to resolve the subpath to
296
+ * an absolute .vue file path.
297
+ */
298
+ const workspaceRootCache = new Map();
299
+ function findWorkspaceRoot(startDir) {
300
+ if (workspaceRootCache.has(startDir))
301
+ return workspaceRootCache.get(startDir);
302
+ let dir = startDir;
303
+ while (true) {
304
+ const pkgJsonPath = join(dir, 'package.json');
305
+ if (existsSync(pkgJsonPath)) {
306
+ try {
307
+ const pkg = JSON.parse(readFileSync(pkgJsonPath, 'utf8'));
308
+ if (pkg.workspaces || existsSync(join(dir, 'pnpm-workspace.yaml'))) {
309
+ workspaceRootCache.set(startDir, dir);
310
+ return dir;
311
+ }
312
+ }
313
+ catch {
314
+ /* skip malformed */
315
+ }
316
+ }
317
+ const parent = dirname(dir);
318
+ if (parent === dir) {
319
+ workspaceRootCache.set(startDir, null);
320
+ return null;
321
+ }
322
+ dir = parent;
323
+ }
324
+ }
325
+ const workspacePackageDirsCache = new Map();
326
+ function getWorkspacePackageDirs(workspaceRoot) {
327
+ if (workspacePackageDirsCache.has(workspaceRoot)) {
328
+ return workspacePackageDirsCache.get(workspaceRoot);
329
+ }
330
+ const packageMap = new Map();
331
+ const packagesDir = join(workspaceRoot, 'packages');
332
+ if (!existsSync(packagesDir)) {
333
+ workspacePackageDirsCache.set(workspaceRoot, packageMap);
334
+ return packageMap;
335
+ }
336
+ function scanDir(dir, depth) {
337
+ if (depth > 3)
338
+ return;
339
+ let entries;
340
+ try {
341
+ entries = readdirSync(dir);
342
+ }
343
+ catch {
344
+ return;
345
+ }
346
+ const pkgJsonPath = join(dir, 'package.json');
347
+ if (existsSync(pkgJsonPath)) {
348
+ try {
349
+ const pkg = JSON.parse(readFileSync(pkgJsonPath, 'utf8'));
350
+ if (typeof pkg.name === 'string') {
351
+ packageMap.set(pkg.name, dir);
352
+ }
353
+ }
354
+ catch {
355
+ /* skip */
356
+ }
357
+ }
358
+ for (const entry of entries) {
359
+ if (entry === 'node_modules' || entry === '.git' || entry.startsWith('.'))
360
+ continue;
361
+ const entryPath = join(dir, entry);
362
+ try {
363
+ const stat = statSync(entryPath);
364
+ if (stat.isDirectory()) {
365
+ scanDir(entryPath, depth + 1);
366
+ }
367
+ }
368
+ catch {
369
+ /* skip */
370
+ }
371
+ }
372
+ }
373
+ scanDir(packagesDir, 0);
374
+ workspacePackageDirsCache.set(workspaceRoot, packageMap);
375
+ return packageMap;
376
+ }
377
+ function resolveWorkspaceVueImport(specifier, importingFilePath) {
378
+ const workspaceRoot = findWorkspaceRoot(dirname(importingFilePath));
379
+ if (!workspaceRoot)
380
+ return null;
381
+ const packageDirs = getWorkspacePackageDirs(workspaceRoot);
382
+ // Parse the specifier into package name and subpath
383
+ // e.g. "primevue/select" → packageName="primevue", subpath="./select"
384
+ // e.g. "@primevue/core/baseeditableholder" → packageName="@primevue/core", subpath="./baseeditableholder"
385
+ let packageName;
386
+ let subpath;
387
+ if (specifier.startsWith('@')) {
388
+ // Scoped package: @scope/name or @scope/name/subpath
389
+ const parts = specifier.split('/');
390
+ if (parts.length < 2)
391
+ return null;
392
+ packageName = `${parts[0]}/${parts[1]}`;
393
+ subpath = parts.length > 2 ? './' + parts.slice(2).join('/') : '.';
394
+ }
395
+ else {
396
+ const slashIndex = specifier.indexOf('/');
397
+ if (slashIndex === -1) {
398
+ packageName = specifier;
399
+ subpath = '.';
400
+ }
401
+ else {
402
+ packageName = specifier.substring(0, slashIndex);
403
+ subpath = './' + specifier.substring(slashIndex + 1);
404
+ }
405
+ }
406
+ const packageDir = packageDirs.get(packageName);
407
+ if (!packageDir)
408
+ return null;
409
+ // Try package.json exports field first
410
+ const pkgJsonPath = join(packageDir, 'package.json');
411
+ try {
412
+ const pkg = JSON.parse(readFileSync(pkgJsonPath, 'utf8'));
413
+ if (pkg.exports && subpath !== '.') {
414
+ const exportValue = pkg.exports[subpath];
415
+ if (typeof exportValue === 'string' && exportValue.endsWith('.vue')) {
416
+ const resolved = resolve(packageDir, exportValue);
417
+ if (existsSync(resolved))
418
+ return resolved;
419
+ }
420
+ }
421
+ // Try subpath as a directory with its own package.json
422
+ if (subpath !== '.') {
423
+ const subDir = resolve(packageDir, subpath.replace(/^\.\//, ''));
424
+ // Check in src/ first (common pattern)
425
+ for (const base of [join(packageDir, 'src', subpath.replace(/^\.\//, '')), subDir]) {
426
+ const subPkgPath = join(base, 'package.json');
427
+ if (existsSync(subPkgPath)) {
428
+ try {
429
+ const subPkg = JSON.parse(readFileSync(subPkgPath, 'utf8'));
430
+ const main = subPkg.main || subPkg.module;
431
+ if (typeof main === 'string' && main.endsWith('.vue')) {
432
+ const resolved = resolve(base, main);
433
+ if (existsSync(resolved))
434
+ return resolved;
435
+ }
436
+ }
437
+ catch {
438
+ /* skip */
439
+ }
440
+ }
441
+ }
442
+ }
443
+ // Fallback: try main/module field for root imports
444
+ if (subpath === '.') {
445
+ const main = pkg.main || pkg.module;
446
+ if (typeof main === 'string' && main.endsWith('.vue')) {
447
+ const resolved = resolve(packageDir, main);
448
+ if (existsSync(resolved))
449
+ return resolved;
450
+ }
451
+ }
452
+ }
453
+ catch {
454
+ /* skip malformed */
455
+ }
456
+ return null;
457
+ }
458
+ async function extractOptionsProps(filePath, scriptContent, visited = new Set()) {
459
+ const resolvedFilePath = resolve(filePath);
460
+ if (visited.has(resolvedFilePath)) {
461
+ return [];
462
+ }
463
+ visited.add(resolvedFilePath);
464
+ const project = new Project({
465
+ compilerOptions: {
466
+ strict: false,
467
+ target: 99,
468
+ module: 99,
469
+ allowJs: true,
470
+ },
471
+ useInMemoryFileSystem: true,
472
+ skipAddingFilesFromTsConfig: true,
473
+ });
474
+ const sf = project.createSourceFile('__options__.ts', scriptContent);
475
+ const localImports = new Map();
476
+ for (const importDecl of sf.getImportDeclarations()) {
477
+ const specifier = importDecl.getModuleSpecifierValue();
478
+ const defaultImport = importDecl.getDefaultImport();
479
+ if (!defaultImport)
480
+ continue;
481
+ if (specifier.startsWith('.')) {
482
+ if (!specifier.endsWith('.vue'))
483
+ continue;
484
+ localImports.set(defaultImport.getText(), resolve(dirname(resolvedFilePath), specifier));
485
+ }
486
+ else {
487
+ // Try resolving bare module specifier to a .vue file in the workspace
488
+ const resolvedVuePath = resolveWorkspaceVueImport(specifier, resolvedFilePath);
489
+ if (resolvedVuePath) {
490
+ localImports.set(defaultImport.getText(), resolvedVuePath);
491
+ }
492
+ }
493
+ }
494
+ for (const node of sf.getDescendants()) {
495
+ if (!Node.isExportAssignment(node))
496
+ continue;
497
+ const expr = node.getExpression();
498
+ if (!Node.isObjectLiteralExpression(expr))
499
+ continue;
500
+ const mergedProps = new Map();
501
+ const extendsProp = expr.getProperty('extends');
502
+ if (extendsProp && Node.isPropertyAssignment(extendsProp)) {
503
+ const extendsInit = extendsProp.getInitializer();
504
+ if (extendsInit && Node.isIdentifier(extendsInit)) {
505
+ const extendsPath = localImports.get(extendsInit.getText());
506
+ if (extendsPath) {
507
+ const inheritedProps = await extractInheritedVueProps(extendsPath, visited);
508
+ for (const prop of inheritedProps) {
509
+ mergedProps.set(prop.name, prop);
510
+ }
511
+ }
512
+ }
513
+ }
514
+ const propsProp = expr.getProperty('props');
515
+ if (!propsProp || !Node.isPropertyAssignment(propsProp)) {
516
+ return [...mergedProps.values()].sort((a, b) => a.name.localeCompare(b.name));
517
+ }
518
+ const propsInit = propsProp.getInitializer();
519
+ if (!propsInit || !Node.isObjectLiteralExpression(propsInit)) {
520
+ return [...mergedProps.values()].sort((a, b) => a.name.localeCompare(b.name));
521
+ }
522
+ for (const prop of parseObjectProps(propsInit)) {
523
+ mergedProps.set(prop.name, prop);
524
+ }
525
+ return [...mergedProps.values()].sort((a, b) => a.name.localeCompare(b.name));
526
+ }
527
+ return [];
528
+ }
529
+ async function extractInheritedVueProps(filePath, visited) {
530
+ const source = await readFile(filePath, 'utf-8');
531
+ const { descriptor } = parseSFC(source);
532
+ if (!descriptor.script) {
533
+ return [];
534
+ }
535
+ return extractOptionsProps(filePath, descriptor.script.content, visited);
536
+ }
537
+ function parseObjectProps(obj) {
538
+ if (!Node.isObjectLiteralExpression(obj))
539
+ return [];
540
+ const result = [];
541
+ for (const prop of obj.getProperties()) {
542
+ if (!Node.isPropertyAssignment(prop))
543
+ continue;
544
+ const name = prop.getName();
545
+ if (!isPublicVuePropName(name))
546
+ continue;
547
+ const init = prop.getInitializer();
548
+ if (!init || !Node.isObjectLiteralExpression(init)) {
549
+ // Bare constructor reference like `propName: String`
550
+ const initText = init?.getText() ?? 'any';
551
+ result.push({
552
+ name,
553
+ type: VUE_TYPE_MAP[initText] ?? 'any',
554
+ required: false,
555
+ });
556
+ continue;
557
+ }
558
+ // { type: X, required: bool, default: val }
559
+ const typeProp = init.getProperty('type');
560
+ const requiredProp = init.getProperty('required');
561
+ const defaultProp = init.getProperty('default');
562
+ let type = 'any';
563
+ if (typeProp && Node.isPropertyAssignment(typeProp)) {
564
+ const typeInit = typeProp.getInitializer();
565
+ if (typeInit) {
566
+ type = VUE_TYPE_MAP[typeInit.getText()] ?? 'any';
567
+ }
568
+ }
569
+ let required = false;
570
+ if (requiredProp && Node.isPropertyAssignment(requiredProp)) {
571
+ const reqInit = requiredProp.getInitializer();
572
+ if (reqInit) {
573
+ required = reqInit.getText() === 'true';
574
+ }
575
+ }
576
+ let defaultValue;
577
+ if (defaultProp && Node.isPropertyAssignment(defaultProp)) {
578
+ const defInit = defaultProp.getInitializer();
579
+ if (defInit) {
580
+ defaultValue = defInit.getText().replace(/^['"]|['"]$/g, '');
581
+ }
582
+ }
583
+ result.push({
584
+ name,
585
+ type,
586
+ required,
587
+ ...(defaultValue !== undefined && { defaultValue }),
588
+ });
589
+ }
590
+ return result;
591
+ }
592
+ function isPublicVuePropName(name) {
593
+ return !/^[_$]/.test(name);
594
+ }
595
+ function extractSlotsFromTemplate(ast) {
596
+ const slots = [];
597
+ function walk(node) {
598
+ if (node.type === ELEMENT_TYPE && node.tag === 'slot') {
599
+ const nameProp = node.props?.find((p) => p.type === ATTRIBUTE_TYPE && p.name === 'name');
600
+ const slotName = nameProp?.value?.content ?? 'default';
601
+ const isDefault = slotName === 'default';
602
+ if (!slots.some((s) => s.name === slotName)) {
603
+ slots.push({ name: slotName, isDefault });
604
+ }
605
+ }
606
+ if (node.children) {
607
+ for (const child of node.children) {
608
+ if (typeof child === 'object' && child !== null && 'type' in child) {
609
+ walk(child);
610
+ }
611
+ }
612
+ }
613
+ }
614
+ walk(ast);
615
+ return slots;
616
+ }
617
+ /**
618
+ * Detect slots referenced via runtime `$slots.name` or `$slots['name']` access
619
+ * in template expressions or script content that aren't declared as `<slot>` tags.
620
+ */
621
+ function extractSlotsFromRuntimeAccess(source) {
622
+ const slots = [];
623
+ const seen = new Set();
624
+ // Match $slots.name (dot access) and $slots['name'] / $slots["name"] (bracket access)
625
+ const dotPattern = /\$slots\.([a-zA-Z_]\w*)/g;
626
+ const bracketPattern = /\$slots\[['"]([a-zA-Z_][\w-]*)['"]\]/g;
627
+ for (const pattern of [dotPattern, bracketPattern]) {
628
+ let match;
629
+ while ((match = pattern.exec(source)) !== null) {
630
+ const name = match[1];
631
+ if (!seen.has(name)) {
632
+ seen.add(name);
633
+ slots.push({ name, isDefault: name === 'default' });
634
+ }
635
+ }
636
+ }
637
+ return slots;
638
+ }
639
+ function mergeSlotsDedup(base, extra) {
640
+ const merged = [...base];
641
+ for (const slot of extra) {
642
+ if (!merged.some((s) => s.name === slot.name)) {
643
+ merged.push(slot);
644
+ }
645
+ }
646
+ return merged;
647
+ }
@@ -0,0 +1,2 @@
1
+ import type { ComponentExtractionResult } from '../../types.js';
2
+ export declare function extractWebComponentDefinitions(filePaths: string[]): Promise<ComponentExtractionResult>;