@contentful/experience-design-system-extraction 2.34.5-dev-build-12a524fb.0 → 2.34.5-dev-build-5cd8bcd6.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 +1 -1
- package/dist/src/extract/pipeline.js +29 -0
- package/dist/src/extract/react.js +23 -31
- package/dist/src/extract/tsx-shared.d.ts +11 -1
- package/dist/src/extract/tsx-shared.js +26 -0
- package/dist/src/extract/vue-tsx.js +17 -26
- package/dist/src/index.d.ts +1 -1
- package/dist/src/types.d.ts +9 -0
- package/package.json +1 -1
package/dist/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contentful/experience-design-system-extraction",
|
|
3
|
-
"version": "2.34.5-dev-build-
|
|
3
|
+
"version": "2.34.5-dev-build-5cd8bcd6.0",
|
|
4
4
|
"description": "Component extraction engine for the Contentful Experience Design System SDK",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -226,6 +226,7 @@ function choosePreferredComponent(existing, candidate) {
|
|
|
226
226
|
}
|
|
227
227
|
export async function extractComponents(filePaths, onProgress, opts) {
|
|
228
228
|
const filesByExtractor = new Map();
|
|
229
|
+
const exclusions = [];
|
|
229
230
|
for (const extractor of extractors) {
|
|
230
231
|
filesByExtractor.set(extractor, []);
|
|
231
232
|
}
|
|
@@ -234,6 +235,18 @@ export async function extractComponents(filePaths, onProgress, opts) {
|
|
|
234
235
|
if (extractor.fileFilter(filePath)) {
|
|
235
236
|
filesByExtractor.get(extractor).push(filePath);
|
|
236
237
|
}
|
|
238
|
+
else if (extractor.name === 'svelte' && filePath.endsWith('.svelte')) {
|
|
239
|
+
const filename = filePath.replace(/\\/g, '/').split('/').pop() ?? '';
|
|
240
|
+
if (/^\+(page|layout|error)\.svelte$/.test(filename)) {
|
|
241
|
+
exclusions.push({
|
|
242
|
+
itemType: 'file',
|
|
243
|
+
name: filename,
|
|
244
|
+
source: filePath,
|
|
245
|
+
reason: 'SvelteKit route entrypoints are framework-managed, not authorable components',
|
|
246
|
+
stage: 'file-filter',
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
}
|
|
237
250
|
}
|
|
238
251
|
}
|
|
239
252
|
const perExtractorFiles = new Map();
|
|
@@ -276,6 +289,7 @@ export async function extractComponents(filePaths, onProgress, opts) {
|
|
|
276
289
|
}
|
|
277
290
|
for (const result of results) {
|
|
278
291
|
allWarnings.push(...result.warnings);
|
|
292
|
+
exclusions.push(...(result.exclusions ?? []));
|
|
279
293
|
for (const component of result.components) {
|
|
280
294
|
const scopeKey = getFamilyScopeKey(component.source, component.name, topLevelFamiliesByRoot);
|
|
281
295
|
const identityKey = `${component.name}::${scopeKey}`;
|
|
@@ -283,6 +297,13 @@ export async function extractComponents(filePaths, onProgress, opts) {
|
|
|
283
297
|
if (existing) {
|
|
284
298
|
const selected = choosePreferredComponent(existing, component);
|
|
285
299
|
allWarnings.push(`Duplicate component "${component.name}" found in ${component.source} (already seen in ${existing.source}); ${selected.reason}`);
|
|
300
|
+
exclusions.push({
|
|
301
|
+
itemType: 'component',
|
|
302
|
+
name: selected.loser.name,
|
|
303
|
+
source: selected.loser.source,
|
|
304
|
+
reason: `duplicate identity; ${selected.reason}`,
|
|
305
|
+
stage: 'duplicate-filter',
|
|
306
|
+
});
|
|
286
307
|
componentsByKey.set(identityKey, selected.winner);
|
|
287
308
|
continue;
|
|
288
309
|
}
|
|
@@ -303,6 +324,13 @@ export async function extractComponents(filePaths, onProgress, opts) {
|
|
|
303
324
|
for (const component of allComponents) {
|
|
304
325
|
if (/^use[A-Z]/.test(component.name)) {
|
|
305
326
|
allWarnings.push(`Skipped hook: ${component.name} (hooks are not renderable components)`);
|
|
327
|
+
exclusions.push({
|
|
328
|
+
itemType: 'component',
|
|
329
|
+
name: component.name,
|
|
330
|
+
source: component.source,
|
|
331
|
+
reason: 'Hook names are not renderable components',
|
|
332
|
+
stage: 'component-filter',
|
|
333
|
+
});
|
|
306
334
|
continue;
|
|
307
335
|
}
|
|
308
336
|
filteredComponents.push(component);
|
|
@@ -310,5 +338,6 @@ export async function extractComponents(filePaths, onProgress, opts) {
|
|
|
310
338
|
return {
|
|
311
339
|
components: filteredComponents.sort((a, b) => a.name.localeCompare(b.name)),
|
|
312
340
|
warnings: allWarnings,
|
|
341
|
+
exclusions,
|
|
313
342
|
};
|
|
314
343
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Node, SyntaxKind, } from 'ts-morph';
|
|
2
|
-
import { extractAllowedValues, getNodeDefinitions, getTypeReferenceName, getTypeTargetDeclarations,
|
|
2
|
+
import { extractAllowedValues, getNodeDefinitions, getTypeReferenceName, getTypeTargetDeclarations, extractTsxComponents, getValueTargetDeclarations, getJsxTagNameNode, isIntrinsicJsxElement, isCompositionHookName, resolveComponentExportName, } from './tsx-shared.js';
|
|
3
3
|
import { shouldBeSlot } from './slot-detection.js';
|
|
4
4
|
import { extractAllowedComponentsFromTypeText, extractAllowedComponentsFromJsdoc } from './slot-allowed-components.js';
|
|
5
5
|
import { getSourceLineMetadata } from './source-line-metadata.js';
|
|
@@ -1626,29 +1626,18 @@ function hasImplementationChildrenHint(funcNode, param) {
|
|
|
1626
1626
|
.some((attr) => isComponentSpreadAttribute(attr, new Set([paramName])));
|
|
1627
1627
|
}
|
|
1628
1628
|
export async function extractReactComponents(filePaths) {
|
|
1629
|
-
const
|
|
1629
|
+
const exclusions = [];
|
|
1630
|
+
const extractionContext = extractTsxComponents(filePaths, /\.[jt]sx$/, (sourceFile) => {
|
|
1631
|
+
if (isStencilFile(sourceFile))
|
|
1632
|
+
return [];
|
|
1633
|
+
const fileExports = [...sourceFile.getExportedDeclarations().keys()];
|
|
1634
|
+
const isNext = isNextJsComponent(sourceFile.getFilePath(), fileExports);
|
|
1635
|
+
return extractFromSourceFile(sourceFile, isNext, exclusions);
|
|
1636
|
+
});
|
|
1630
1637
|
if (!extractionContext) {
|
|
1631
1638
|
return { components: [], warnings: [] };
|
|
1632
1639
|
}
|
|
1633
|
-
const {
|
|
1634
|
-
const warnings = [];
|
|
1635
|
-
const components = [];
|
|
1636
|
-
for (const filePath of componentFiles) {
|
|
1637
|
-
try {
|
|
1638
|
-
const sourceFile = project.getSourceFile(filePath);
|
|
1639
|
-
if (!sourceFile)
|
|
1640
|
-
continue;
|
|
1641
|
-
if (isStencilFile(sourceFile))
|
|
1642
|
-
continue;
|
|
1643
|
-
const fileExports = [...sourceFile.getExportedDeclarations().keys()];
|
|
1644
|
-
const isNext = isNextJsComponent(sourceFile.getFilePath(), fileExports);
|
|
1645
|
-
const extracted = extractFromSourceFile(sourceFile, isNext);
|
|
1646
|
-
components.push(...extracted);
|
|
1647
|
-
}
|
|
1648
|
-
catch (e) {
|
|
1649
|
-
warnings.push(`Failed to extract from ${filePath}: ${e instanceof Error ? e.message : String(e)}`);
|
|
1650
|
-
}
|
|
1651
|
-
}
|
|
1640
|
+
const { project, components, warnings } = extractionContext;
|
|
1652
1641
|
const propsToComponent = new Map();
|
|
1653
1642
|
const componentNames = new Set();
|
|
1654
1643
|
for (const c of components) {
|
|
@@ -1764,24 +1753,27 @@ export async function extractReactComponents(filePaths) {
|
|
|
1764
1753
|
return {
|
|
1765
1754
|
components: components.sort((a, b) => a.name.localeCompare(b.name)),
|
|
1766
1755
|
warnings,
|
|
1756
|
+
exclusions,
|
|
1767
1757
|
};
|
|
1768
1758
|
}
|
|
1769
|
-
function extractFromSourceFile(sourceFile, isNext) {
|
|
1759
|
+
function extractFromSourceFile(sourceFile, isNext, exclusions) {
|
|
1770
1760
|
const components = [];
|
|
1771
1761
|
const exported = sourceFile.getExportedDeclarations();
|
|
1772
1762
|
const usesCreateContext = sourceFileUsesCreateContext(sourceFile);
|
|
1773
1763
|
for (const [exportKey, declarations] of exported) {
|
|
1774
|
-
|
|
1775
|
-
if (
|
|
1776
|
-
const defaultExportName = resolveDefaultExportName(declarations, exported, true);
|
|
1777
|
-
if (!defaultExportName)
|
|
1778
|
-
continue;
|
|
1779
|
-
name = defaultExportName;
|
|
1780
|
-
}
|
|
1781
|
-
if (!/^[A-Z]/.test(name))
|
|
1764
|
+
const name = resolveComponentExportName(exportKey, declarations, exported, true);
|
|
1765
|
+
if (!name)
|
|
1782
1766
|
continue;
|
|
1783
|
-
if (name
|
|
1767
|
+
if (isCompositionHookName(name)) {
|
|
1768
|
+
exclusions.push({
|
|
1769
|
+
itemType: 'component',
|
|
1770
|
+
name,
|
|
1771
|
+
source: sourceFile.getFilePath(),
|
|
1772
|
+
reason: 'React hook names are not renderable components',
|
|
1773
|
+
stage: 'component-filter',
|
|
1774
|
+
});
|
|
1784
1775
|
continue;
|
|
1776
|
+
}
|
|
1785
1777
|
const funcNode = resolveBestFunctionNode(declarations);
|
|
1786
1778
|
if (!funcNode)
|
|
1787
1779
|
continue;
|
|
@@ -1,8 +1,18 @@
|
|
|
1
|
-
import { Node, Project, type Type } from 'ts-morph';
|
|
1
|
+
import { Node, Project, type SourceFile, type Type } from 'ts-morph';
|
|
2
2
|
export declare function getTsxExtractionContext(filePaths: string[], componentFilePattern: RegExp): {
|
|
3
3
|
componentFiles: string[];
|
|
4
4
|
project: Project;
|
|
5
5
|
} | undefined;
|
|
6
|
+
export declare function extractTsxComponents<T>(filePaths: string[], componentFilePattern: RegExp, extractSourceFile: (sourceFile: SourceFile) => T[]): {
|
|
7
|
+
componentFiles: string[];
|
|
8
|
+
project: Project;
|
|
9
|
+
components: T[];
|
|
10
|
+
warnings: string[];
|
|
11
|
+
} | undefined;
|
|
12
|
+
export declare function resolveComponentExportName(exportKey: string, declarations: Node[], exported: {
|
|
13
|
+
has(name: string): boolean;
|
|
14
|
+
}, allowVariableDeclaration?: boolean): string | undefined;
|
|
15
|
+
export declare function isCompositionHookName(name: string): boolean;
|
|
6
16
|
export declare function resolveDefaultExportName(declarations: Node[], exported: {
|
|
7
17
|
has(name: string): boolean;
|
|
8
18
|
}, allowVariableDeclaration?: boolean): string | undefined;
|
|
@@ -35,6 +35,32 @@ export function getTsxExtractionContext(filePaths, componentFilePattern) {
|
|
|
35
35
|
project: createTsxProject(getTsxProjectFiles(filePaths)),
|
|
36
36
|
};
|
|
37
37
|
}
|
|
38
|
+
export function extractTsxComponents(filePaths, componentFilePattern, extractSourceFile) {
|
|
39
|
+
const extractionContext = getTsxExtractionContext(filePaths, componentFilePattern);
|
|
40
|
+
if (!extractionContext)
|
|
41
|
+
return undefined;
|
|
42
|
+
const warnings = [];
|
|
43
|
+
const components = [];
|
|
44
|
+
for (const filePath of extractionContext.componentFiles) {
|
|
45
|
+
try {
|
|
46
|
+
const sourceFile = extractionContext.project.getSourceFile(filePath);
|
|
47
|
+
if (!sourceFile)
|
|
48
|
+
continue;
|
|
49
|
+
components.push(...extractSourceFile(sourceFile));
|
|
50
|
+
}
|
|
51
|
+
catch (e) {
|
|
52
|
+
warnings.push(`Failed to extract from ${filePath}: ${e instanceof Error ? e.message : String(e)}`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return { ...extractionContext, components, warnings };
|
|
56
|
+
}
|
|
57
|
+
export function resolveComponentExportName(exportKey, declarations, exported, allowVariableDeclaration = false) {
|
|
58
|
+
const name = exportKey === 'default' ? resolveDefaultExportName(declarations, exported, allowVariableDeclaration) : exportKey;
|
|
59
|
+
return name && /^[A-Z]/.test(name) ? name : undefined;
|
|
60
|
+
}
|
|
61
|
+
export function isCompositionHookName(name) {
|
|
62
|
+
return name.startsWith('use');
|
|
63
|
+
}
|
|
38
64
|
export function resolveDefaultExportName(declarations, exported, allowVariableDeclaration = false) {
|
|
39
65
|
const declaration = declarations[0];
|
|
40
66
|
const name = Node.isFunctionDeclaration(declaration)
|
|
@@ -1,44 +1,35 @@
|
|
|
1
1
|
import { Node, SyntaxKind } from 'ts-morph';
|
|
2
|
-
import { extractAllowedValues, getTypeReferenceName, getTypeTargetDeclarations,
|
|
2
|
+
import { extractAllowedValues, extractTsxComponents, getTypeReferenceName, getTypeTargetDeclarations, getValueTargetDeclarations, isCompositionHookName, resolveComponentExportName, } from './tsx-shared.js';
|
|
3
3
|
export async function extractVueTsxComponents(filePaths) {
|
|
4
|
-
const
|
|
4
|
+
const exclusions = [];
|
|
5
|
+
const extractionContext = extractTsxComponents(filePaths, /\.tsx$/, (sourceFile) => extractFromSourceFile(sourceFile, exclusions));
|
|
5
6
|
if (!extractionContext) {
|
|
6
7
|
return { components: [], warnings: [] };
|
|
7
8
|
}
|
|
8
|
-
const {
|
|
9
|
-
const warnings = [];
|
|
10
|
-
const components = [];
|
|
11
|
-
for (const filePath of componentFiles) {
|
|
12
|
-
try {
|
|
13
|
-
const sourceFile = project.getSourceFile(filePath);
|
|
14
|
-
if (!sourceFile)
|
|
15
|
-
continue;
|
|
16
|
-
components.push(...extractFromSourceFile(sourceFile));
|
|
17
|
-
}
|
|
18
|
-
catch (e) {
|
|
19
|
-
warnings.push(`Failed to extract from ${filePath}: ${e instanceof Error ? e.message : String(e)}`);
|
|
20
|
-
}
|
|
21
|
-
}
|
|
9
|
+
const { components, warnings } = extractionContext;
|
|
22
10
|
return {
|
|
23
11
|
components: components.sort((a, b) => a.name.localeCompare(b.name)),
|
|
24
12
|
warnings,
|
|
13
|
+
exclusions,
|
|
25
14
|
};
|
|
26
15
|
}
|
|
27
|
-
function extractFromSourceFile(sourceFile) {
|
|
16
|
+
function extractFromSourceFile(sourceFile, exclusions) {
|
|
28
17
|
const components = [];
|
|
29
18
|
const exported = sourceFile.getExportedDeclarations();
|
|
30
19
|
for (const [exportKey, declarations] of exported) {
|
|
31
|
-
|
|
32
|
-
if (
|
|
33
|
-
const defaultExportName = resolveDefaultExportName(declarations, exported);
|
|
34
|
-
if (!defaultExportName)
|
|
35
|
-
continue;
|
|
36
|
-
name = defaultExportName;
|
|
37
|
-
}
|
|
38
|
-
if (!/^[A-Z]/.test(name))
|
|
20
|
+
const name = resolveComponentExportName(exportKey, declarations, exported);
|
|
21
|
+
if (!name)
|
|
39
22
|
continue;
|
|
40
|
-
if (name
|
|
23
|
+
if (isCompositionHookName(name)) {
|
|
24
|
+
exclusions.push({
|
|
25
|
+
itemType: 'component',
|
|
26
|
+
name,
|
|
27
|
+
source: sourceFile.getFilePath(),
|
|
28
|
+
reason: 'Vue composition hook names are not renderable components',
|
|
29
|
+
stage: 'component-filter',
|
|
30
|
+
});
|
|
41
31
|
continue;
|
|
32
|
+
}
|
|
42
33
|
const component = extractVueTsxComponent(declarations, name, sourceFile);
|
|
43
34
|
if (component) {
|
|
44
35
|
components.push(component);
|
package/dist/src/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type { RawComponentDefinition, RawPropDefinition, RawSlotDefinition, ComponentExtractionResult, ExtractorProgress, ExtractorOptions, ComponentExtractor, ExtractionValidationIssue, ExtractionValidationIssueCode, } from './types.js';
|
|
1
|
+
export type { RawComponentDefinition, RawPropDefinition, RawSlotDefinition, ComponentExtractionResult, ExtractionExclusion, ExtractorProgress, ExtractorOptions, ComponentExtractor, ExtractionValidationIssue, ExtractionValidationIssueCode, } from './types.js';
|
|
2
2
|
export { stripScoringFields } from './types.js';
|
|
3
3
|
export { extractComponents } from './extract/pipeline.js';
|
|
4
4
|
export { extractReactComponents } from './extract/react.js';
|
package/dist/src/types.d.ts
CHANGED
|
@@ -62,6 +62,15 @@ export interface RawComponentDefinition {
|
|
|
62
62
|
export interface ComponentExtractionResult {
|
|
63
63
|
components: RawComponentDefinition[];
|
|
64
64
|
warnings: string[];
|
|
65
|
+
exclusions?: ExtractionExclusion[];
|
|
66
|
+
}
|
|
67
|
+
/** A deterministic item removed before the interactive scope gate. */
|
|
68
|
+
export interface ExtractionExclusion {
|
|
69
|
+
itemType: 'component' | 'file' | 'prop';
|
|
70
|
+
name: string;
|
|
71
|
+
source?: string;
|
|
72
|
+
reason: string;
|
|
73
|
+
stage: string;
|
|
65
74
|
}
|
|
66
75
|
export type ExtractorProgress = {
|
|
67
76
|
filesProcessed: number;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contentful/experience-design-system-extraction",
|
|
3
|
-
"version": "2.34.5-dev-build-
|
|
3
|
+
"version": "2.34.5-dev-build-5cd8bcd6.0",
|
|
4
4
|
"description": "Component extraction engine for the Contentful Experience Design System SDK",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|