@contentful/experience-design-system-extraction 2.35.0-alpha.0 → 2.35.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 +15 -39
- package/dist/src/extract/tsx-shared.d.ts +15 -1
- package/dist/src/extract/tsx-shared.js +45 -0
- package/dist/src/extract/vue-tsx.js +7 -33
- 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
|
@@ -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, getValueTargetDeclarations, getJsxTagNameNode, extractTsxComponents, getRenderableExports, isIntrinsicJsxElement, } 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,13 @@ function hasImplementationChildrenHint(funcNode, param) {
|
|
|
1626
1626
|
.some((attr) => isComponentSpreadAttribute(attr, new Set([paramName])));
|
|
1627
1627
|
}
|
|
1628
1628
|
export async function extractReactComponents(filePaths) {
|
|
1629
|
-
const
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
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
|
-
}
|
|
1629
|
+
const { components, warnings, exclusions, project } = extractTsxComponents(filePaths, /\.[jt]sx$/, (sourceFile, exclusions) => {
|
|
1630
|
+
if (isStencilFile(sourceFile))
|
|
1631
|
+
return [];
|
|
1632
|
+
const fileExports = [...sourceFile.getExportedDeclarations().keys()];
|
|
1633
|
+
const isNext = isNextJsComponent(sourceFile.getFilePath(), fileExports);
|
|
1634
|
+
return extractFromSourceFile(sourceFile, isNext, exclusions);
|
|
1635
|
+
});
|
|
1652
1636
|
const propsToComponent = new Map();
|
|
1653
1637
|
const componentNames = new Set();
|
|
1654
1638
|
for (const c of components) {
|
|
@@ -1690,7 +1674,7 @@ export async function extractReactComponents(filePaths) {
|
|
|
1690
1674
|
const cached = structuralByFile.get(filePath);
|
|
1691
1675
|
if (cached)
|
|
1692
1676
|
return cached;
|
|
1693
|
-
const sourceFile = project
|
|
1677
|
+
const sourceFile = project?.getSourceFile(filePath);
|
|
1694
1678
|
if (!sourceFile)
|
|
1695
1679
|
return [];
|
|
1696
1680
|
const ctx = { propsToComponent, componentNames };
|
|
@@ -1764,24 +1748,16 @@ export async function extractReactComponents(filePaths) {
|
|
|
1764
1748
|
return {
|
|
1765
1749
|
components: components.sort((a, b) => a.name.localeCompare(b.name)),
|
|
1766
1750
|
warnings,
|
|
1751
|
+
exclusions,
|
|
1767
1752
|
};
|
|
1768
1753
|
}
|
|
1769
|
-
function extractFromSourceFile(sourceFile, isNext) {
|
|
1754
|
+
function extractFromSourceFile(sourceFile, isNext, exclusions) {
|
|
1770
1755
|
const components = [];
|
|
1771
|
-
const exported = sourceFile.getExportedDeclarations();
|
|
1772
1756
|
const usesCreateContext = sourceFileUsesCreateContext(sourceFile);
|
|
1773
|
-
for (const
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
if (!defaultExportName)
|
|
1778
|
-
continue;
|
|
1779
|
-
name = defaultExportName;
|
|
1780
|
-
}
|
|
1781
|
-
if (!/^[A-Z]/.test(name))
|
|
1782
|
-
continue;
|
|
1783
|
-
if (name.startsWith('use'))
|
|
1784
|
-
continue;
|
|
1757
|
+
for (const { name, declarations } of getRenderableExports(sourceFile, exclusions, {
|
|
1758
|
+
allowVariableDeclaration: true,
|
|
1759
|
+
hookReason: 'React hook names are not renderable components',
|
|
1760
|
+
})) {
|
|
1785
1761
|
const funcNode = resolveBestFunctionNode(declarations);
|
|
1786
1762
|
if (!funcNode)
|
|
1787
1763
|
continue;
|
|
@@ -1,11 +1,25 @@
|
|
|
1
|
-
import { Node, Project, type Type } from 'ts-morph';
|
|
1
|
+
import { Node, Project, type SourceFile, type Type } from 'ts-morph';
|
|
2
|
+
import type { ExtractionExclusion } from '../types.js';
|
|
2
3
|
export declare function getTsxExtractionContext(filePaths: string[], componentFilePattern: RegExp): {
|
|
3
4
|
componentFiles: string[];
|
|
4
5
|
project: Project;
|
|
5
6
|
} | undefined;
|
|
7
|
+
export declare function extractTsxComponents<T>(filePaths: string[], componentFilePattern: RegExp, extract: (sourceFile: SourceFile, exclusions: ExtractionExclusion[]) => T[]): {
|
|
8
|
+
components: T[];
|
|
9
|
+
warnings: string[];
|
|
10
|
+
exclusions: ExtractionExclusion[];
|
|
11
|
+
project?: Project;
|
|
12
|
+
};
|
|
6
13
|
export declare function resolveDefaultExportName(declarations: Node[], exported: {
|
|
7
14
|
has(name: string): boolean;
|
|
8
15
|
}, allowVariableDeclaration?: boolean): string | undefined;
|
|
16
|
+
export declare function getRenderableExports(sourceFile: SourceFile, exclusions: ExtractionExclusion[], options: {
|
|
17
|
+
allowVariableDeclaration?: boolean;
|
|
18
|
+
hookReason: string;
|
|
19
|
+
}): Array<{
|
|
20
|
+
name: string;
|
|
21
|
+
declarations: Node[];
|
|
22
|
+
}>;
|
|
9
23
|
export declare function kebabToPascal(input: string): string;
|
|
10
24
|
export declare function extractAllowedValues(type: Type): string[] | undefined;
|
|
11
25
|
/**
|
|
@@ -35,6 +35,28 @@ export function getTsxExtractionContext(filePaths, componentFilePattern) {
|
|
|
35
35
|
project: createTsxProject(getTsxProjectFiles(filePaths)),
|
|
36
36
|
};
|
|
37
37
|
}
|
|
38
|
+
export function extractTsxComponents(filePaths, componentFilePattern, extract) {
|
|
39
|
+
const extractionContext = getTsxExtractionContext(filePaths, componentFilePattern);
|
|
40
|
+
if (!extractionContext) {
|
|
41
|
+
return { components: [], warnings: [], exclusions: [] };
|
|
42
|
+
}
|
|
43
|
+
const { componentFiles, project } = extractionContext;
|
|
44
|
+
const components = [];
|
|
45
|
+
const warnings = [];
|
|
46
|
+
const exclusions = [];
|
|
47
|
+
for (const filePath of componentFiles) {
|
|
48
|
+
try {
|
|
49
|
+
const sourceFile = project.getSourceFile(filePath);
|
|
50
|
+
if (!sourceFile)
|
|
51
|
+
continue;
|
|
52
|
+
components.push(...extract(sourceFile, exclusions));
|
|
53
|
+
}
|
|
54
|
+
catch (e) {
|
|
55
|
+
warnings.push(`Failed to extract from ${filePath}: ${e instanceof Error ? e.message : String(e)}`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return { components, warnings, exclusions, project };
|
|
59
|
+
}
|
|
38
60
|
export function resolveDefaultExportName(declarations, exported, allowVariableDeclaration = false) {
|
|
39
61
|
const declaration = declarations[0];
|
|
40
62
|
const name = Node.isFunctionDeclaration(declaration)
|
|
@@ -46,6 +68,29 @@ export function resolveDefaultExportName(declarations, exported, allowVariableDe
|
|
|
46
68
|
return undefined;
|
|
47
69
|
return name;
|
|
48
70
|
}
|
|
71
|
+
export function getRenderableExports(sourceFile, exclusions, options) {
|
|
72
|
+
const renderable = [];
|
|
73
|
+
const exported = sourceFile.getExportedDeclarations();
|
|
74
|
+
for (const [exportKey, declarations] of exported) {
|
|
75
|
+
const name = exportKey === 'default'
|
|
76
|
+
? resolveDefaultExportName(declarations, exported, options.allowVariableDeclaration)
|
|
77
|
+
: exportKey;
|
|
78
|
+
if (!name || !/^[A-Z]/.test(name))
|
|
79
|
+
continue;
|
|
80
|
+
if (name.startsWith('use')) {
|
|
81
|
+
exclusions.push({
|
|
82
|
+
itemType: 'component',
|
|
83
|
+
name,
|
|
84
|
+
source: sourceFile.getFilePath(),
|
|
85
|
+
reason: options.hookReason,
|
|
86
|
+
stage: 'component-filter',
|
|
87
|
+
});
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
renderable.push({ name, declarations });
|
|
91
|
+
}
|
|
92
|
+
return renderable;
|
|
93
|
+
}
|
|
49
94
|
export function kebabToPascal(input) {
|
|
50
95
|
return input
|
|
51
96
|
.split('-')
|
|
@@ -1,44 +1,18 @@
|
|
|
1
1
|
import { Node, SyntaxKind } from 'ts-morph';
|
|
2
|
-
import { extractAllowedValues, getTypeReferenceName, getTypeTargetDeclarations,
|
|
2
|
+
import { extractAllowedValues, extractTsxComponents, getTypeReferenceName, getTypeTargetDeclarations, getValueTargetDeclarations, getRenderableExports, } from './tsx-shared.js';
|
|
3
3
|
export async function extractVueTsxComponents(filePaths) {
|
|
4
|
-
const
|
|
5
|
-
if (!extractionContext) {
|
|
6
|
-
return { components: [], warnings: [] };
|
|
7
|
-
}
|
|
8
|
-
const { componentFiles, project } = extractionContext;
|
|
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
|
-
}
|
|
4
|
+
const { components, warnings, exclusions } = extractTsxComponents(filePaths, /\.tsx$/, extractFromSourceFile);
|
|
22
5
|
return {
|
|
23
6
|
components: components.sort((a, b) => a.name.localeCompare(b.name)),
|
|
24
7
|
warnings,
|
|
8
|
+
exclusions,
|
|
25
9
|
};
|
|
26
10
|
}
|
|
27
|
-
function extractFromSourceFile(sourceFile) {
|
|
11
|
+
function extractFromSourceFile(sourceFile, exclusions) {
|
|
28
12
|
const components = [];
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
if (exportKey === 'default') {
|
|
33
|
-
const defaultExportName = resolveDefaultExportName(declarations, exported);
|
|
34
|
-
if (!defaultExportName)
|
|
35
|
-
continue;
|
|
36
|
-
name = defaultExportName;
|
|
37
|
-
}
|
|
38
|
-
if (!/^[A-Z]/.test(name))
|
|
39
|
-
continue;
|
|
40
|
-
if (name.startsWith('use'))
|
|
41
|
-
continue;
|
|
13
|
+
for (const { name, declarations } of getRenderableExports(sourceFile, exclusions, {
|
|
14
|
+
hookReason: 'Vue composition hook names are not renderable components',
|
|
15
|
+
})) {
|
|
42
16
|
const component = extractVueTsxComponent(declarations, name, sourceFile);
|
|
43
17
|
if (component) {
|
|
44
18
|
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