@contentful/experience-design-system-extraction 2.33.1-dev-build-d7fc540.0 → 2.33.1-dev-build-1b6f259.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-extraction",
3
- "version": "2.33.1-dev-build-d7fc540.0",
3
+ "version": "2.33.1-dev-build-1b6f259.0",
4
4
  "description": "Component extraction engine for the Contentful Experience Design System SDK",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -1,7 +1,9 @@
1
1
  import { basename } from 'node:path';
2
- import { readFile } from 'node:fs/promises';
3
2
  import os from 'node:os';
4
3
  import { Project, Node } from 'ts-morph';
4
+ import { createSortedExtractionResult, runFileExtractionWorkers } from './file-extraction-workers.js';
5
+ import { resolveTypeProperty } from './resolve-type-property.js';
6
+ import { getSourceLineMetadata } from './source-line-metadata.js';
5
7
  function extractAllowedValues(typeText) {
6
8
  // Check if the type is a union of string literals like 'a' | 'b' | 'c'
7
9
  const parts = typeText.split('|').map((p) => p.trim());
@@ -30,8 +32,8 @@ function extractBindingPropName(element) {
30
32
  return null;
31
33
  return element.getPropertyNameNode()?.getText() ?? element.getNameNode().getText();
32
34
  }
33
- function extractFallbackPropsFromFrontmatter(frontmatter) {
34
- const project = new Project({
35
+ function createAstroFrontmatterProject() {
36
+ return new Project({
35
37
  compilerOptions: {
36
38
  strict: false,
37
39
  target: 99,
@@ -41,27 +43,33 @@ function extractFallbackPropsFromFrontmatter(frontmatter) {
41
43
  useInMemoryFileSystem: true,
42
44
  skipAddingFilesFromTsConfig: true,
43
45
  });
44
- const sf = project.createSourceFile('__frontmatter__.ts', frontmatter);
45
- const props = new Map();
46
+ }
47
+ function forEachAstroPropsBinding(frontmatter, visit) {
48
+ const sf = createAstroFrontmatterProject().createSourceFile('__frontmatter__.ts', frontmatter);
46
49
  sf.forEachDescendant((node) => {
47
50
  if (!Node.isVariableDeclaration(node))
48
51
  return;
49
52
  const initializer = node.getInitializer();
50
- if (!usesAstroProps(initializer))
53
+ if (!initializer || !usesAstroProps(initializer))
51
54
  return;
52
55
  const nameNode = node.getNameNode();
53
56
  if (!Node.isObjectBindingPattern(nameNode))
54
57
  return;
55
- for (const element of nameNode.getElements()) {
56
- const propName = extractBindingPropName(element);
57
- if (!propName)
58
- continue;
59
- props.set(propName, {
60
- name: propName,
61
- type: 'any',
62
- required: !element.getInitializer(),
63
- });
64
- }
58
+ for (const element of nameNode.getElements())
59
+ visit(element);
60
+ });
61
+ }
62
+ function extractFallbackPropsFromFrontmatter(frontmatter) {
63
+ const props = new Map();
64
+ forEachAstroPropsBinding(frontmatter, (element) => {
65
+ const propName = extractBindingPropName(element);
66
+ if (!propName)
67
+ return;
68
+ props.set(propName, {
69
+ name: propName,
70
+ type: 'any',
71
+ required: !element.getInitializer(),
72
+ });
65
73
  });
66
74
  return [...props.values()].sort((a, b) => a.name.localeCompare(b.name));
67
75
  }
@@ -82,17 +90,7 @@ function mergeProps(...propGroups) {
82
90
  return [...merged.values()].sort((a, b) => a.name.localeCompare(b.name));
83
91
  }
84
92
  function extractPropsFromFrontmatter(frontmatter) {
85
- const project = new Project({
86
- compilerOptions: {
87
- strict: false,
88
- target: 99, // ESNext
89
- module: 99, // ESNext
90
- allowJs: true,
91
- },
92
- useInMemoryFileSystem: true,
93
- skipAddingFilesFromTsConfig: true,
94
- });
95
- const sf = project.createSourceFile('__frontmatter__.ts', frontmatter);
93
+ const sf = createAstroFrontmatterProject().createSourceFile('__frontmatter__.ts', frontmatter);
96
94
  const props = [];
97
95
  // Find `interface Props` or `type Props = ...`
98
96
  const propsInterface = sf.getInterface('Props');
@@ -116,25 +114,17 @@ function extractPropsFromFrontmatter(frontmatter) {
116
114
  else if (propsTypeAlias) {
117
115
  const type = propsTypeAlias.getType();
118
116
  for (const property of type.getProperties()) {
119
- const name = property.getName();
120
- const decl = property.getValueDeclaration() ?? property.getDeclarations()[0];
121
- if (!decl)
117
+ const resolved = resolveTypeProperty(property);
118
+ if (!resolved)
122
119
  continue;
123
- const propType = property.getTypeAtLocation(decl);
124
- const typeText = propType.getText(decl);
125
- const required = !property.isOptional();
120
+ const { name, declaration: decl, typeText, required } = resolved;
126
121
  const allowedValues = extractAllowedValues(typeText);
127
122
  props.push({
128
123
  name,
129
124
  type: typeText,
130
125
  required,
131
126
  ...(allowedValues && { allowedValues }),
132
- ...(typeof decl.getStartLineNumber === 'function'
133
- ? {
134
- sourceStartLine: decl.getStartLineNumber(),
135
- sourceEndLine: decl.getEndLineNumber(),
136
- }
137
- : {}),
127
+ ...getSourceLineMetadata(decl),
138
128
  });
139
129
  }
140
130
  }
@@ -142,38 +132,15 @@ function extractPropsFromFrontmatter(frontmatter) {
142
132
  }
143
133
  function extractDefaultsFromFrontmatter(frontmatter) {
144
134
  const defaults = new Map();
145
- const project = new Project({
146
- compilerOptions: {
147
- strict: false,
148
- target: 99,
149
- module: 99,
150
- allowJs: true,
151
- },
152
- useInMemoryFileSystem: true,
153
- skipAddingFilesFromTsConfig: true,
154
- });
155
- const sf = project.createSourceFile('__frontmatter__.ts', frontmatter);
156
- sf.forEachDescendant((node) => {
157
- if (!Node.isVariableDeclaration(node))
158
- return;
159
- const initializer = node.getInitializer();
160
- if (!initializer)
135
+ forEachAstroPropsBinding(frontmatter, (element) => {
136
+ const propName = extractBindingPropName(element);
137
+ if (!propName)
161
138
  return;
162
- if (!usesAstroProps(initializer))
139
+ const elementInitializer = element.getInitializer();
140
+ if (!elementInitializer)
163
141
  return;
164
- const nameNode = node.getNameNode();
165
- if (!Node.isObjectBindingPattern(nameNode))
166
- return;
167
- for (const element of nameNode.getElements()) {
168
- const propName = extractBindingPropName(element);
169
- if (!propName)
170
- continue;
171
- const elementInitializer = element.getInitializer();
172
- if (!elementInitializer)
173
- continue;
174
- const value = elementInitializer.getText().replace(/^['"]|['"]$/g, '');
175
- defaults.set(propName, value);
176
- }
142
+ const value = elementInitializer.getText().replace(/^['"]|['"]$/g, '');
143
+ defaults.set(propName, value);
177
144
  });
178
145
  return defaults;
179
146
  }
@@ -253,37 +220,8 @@ function extractFromAstroFile(filePath, source) {
253
220
  const ASTRO_EXTRACT_CONCURRENCY = Number(process.env['EDS_EXTRACT_CONCURRENCY'] ?? 0) || os.cpus().length;
254
221
  export async function extractAstroComponents(filePaths, onProgress) {
255
222
  const astroFiles = filePaths.filter((f) => f.endsWith('.astro'));
256
- if (astroFiles.length === 0) {
257
- return { components: [], warnings: [] };
258
- }
259
- const warnings = [];
260
- const components = [];
261
- let filesProcessed = 0;
262
- let componentsFound = 0;
263
- const queue = [...astroFiles];
264
- async function worker() {
265
- while (queue.length > 0) {
266
- const filePath = queue.shift();
267
- if (!filePath)
268
- break;
269
- try {
270
- const source = await readFile(filePath, 'utf-8');
271
- const component = extractFromAstroFile(filePath, source);
272
- if (component) {
273
- components.push(component);
274
- componentsFound++;
275
- }
276
- }
277
- catch (e) {
278
- warnings.push(`Failed to extract from ${filePath}: ${e instanceof Error ? e.message : String(e)}`);
279
- }
280
- filesProcessed++;
281
- onProgress?.({ filesProcessed, componentsFound });
282
- }
283
- }
284
- await Promise.all(Array.from({ length: Math.min(ASTRO_EXTRACT_CONCURRENCY, astroFiles.length) }, worker));
285
- return {
286
- components: components.sort((a, b) => a.name.localeCompare(b.name)),
287
- warnings,
288
- };
223
+ const { items: components, warnings } = await runFileExtractionWorkers(astroFiles, ASTRO_EXTRACT_CONCURRENCY, async (filePath, source) => ({
224
+ item: extractFromAstroFile(filePath, source),
225
+ }), (filePath, error) => `Failed to extract from ${filePath}: ${error instanceof Error ? error.message : String(error)}`, onProgress);
226
+ return createSortedExtractionResult(components, warnings);
289
227
  }
@@ -0,0 +1,25 @@
1
+ import type { Project, SourceFile } from 'ts-morph';
2
+ export type FileExtractionOutcome<T, M = undefined> = {
3
+ item: T | null;
4
+ warnings?: string[];
5
+ metadata?: M;
6
+ };
7
+ type FileExtractionProgress = {
8
+ filesProcessed: number;
9
+ componentsFound: number;
10
+ };
11
+ export declare function runFileExtractionWorkers<T, M = undefined>(filePaths: string[], concurrency: number, extractFile: (filePath: string, source: string) => Promise<FileExtractionOutcome<T, M>>, formatError: (filePath: string, error: unknown) => string, onProgress?: (progress: FileExtractionProgress) => void, onResult?: (filePath: string, outcome: FileExtractionOutcome<T, M>) => void): Promise<{
12
+ items: T[];
13
+ warnings: string[];
14
+ }>;
15
+ export declare function createSortedExtractionResult<T extends {
16
+ name: string;
17
+ }>(components: T[], warnings: string[]): {
18
+ components: T[];
19
+ warnings: string[];
20
+ };
21
+ export declare function extractProjectSourceFiles<T>(project: Project, extractFile: (sourceFile: SourceFile, warnings: string[]) => T[]): {
22
+ items: T[];
23
+ warnings: string[];
24
+ };
25
+ export {};
@@ -0,0 +1,48 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ export async function runFileExtractionWorkers(filePaths, concurrency, extractFile, formatError, onProgress, onResult) {
3
+ const warnings = [];
4
+ const items = [];
5
+ let filesProcessed = 0;
6
+ const queue = [...filePaths];
7
+ async function worker() {
8
+ while (queue.length > 0) {
9
+ const filePath = queue.shift();
10
+ if (!filePath)
11
+ break;
12
+ try {
13
+ const source = await readFile(filePath, 'utf-8');
14
+ const outcome = await extractFile(filePath, source);
15
+ warnings.push(...(outcome.warnings ?? []));
16
+ if (outcome.item)
17
+ items.push(outcome.item);
18
+ onResult?.(filePath, outcome);
19
+ }
20
+ catch (error) {
21
+ warnings.push(formatError(filePath, error));
22
+ }
23
+ filesProcessed++;
24
+ onProgress?.({ filesProcessed, componentsFound: items.length });
25
+ }
26
+ }
27
+ await Promise.all(Array.from({ length: Math.min(concurrency, filePaths.length) }, worker));
28
+ return { items, warnings };
29
+ }
30
+ export function createSortedExtractionResult(components, warnings) {
31
+ return {
32
+ components: components.sort((a, b) => a.name.localeCompare(b.name)),
33
+ warnings,
34
+ };
35
+ }
36
+ export function extractProjectSourceFiles(project, extractFile) {
37
+ const warnings = [];
38
+ const items = [];
39
+ for (const sourceFile of project.getSourceFiles()) {
40
+ try {
41
+ items.push(...extractFile(sourceFile, warnings));
42
+ }
43
+ catch (error) {
44
+ warnings.push(`Failed to extract from ${sourceFile.getFilePath()}: ${error instanceof Error ? error.message : String(error)}`);
45
+ }
46
+ }
47
+ return { items, warnings };
48
+ }
@@ -1,5 +1,5 @@
1
1
  import type { RawComponentDefinition } from '../types.js';
2
- export interface NonAuthorableResult {
2
+ interface NonAuthorableResult {
3
3
  skip: boolean;
4
4
  reason?: string;
5
5
  }
@@ -14,3 +14,4 @@ export interface NonAuthorableResult {
14
14
  * other repos.
15
15
  */
16
16
  export declare function isNonAuthorableComponent(component: RawComponentDefinition): NonAuthorableResult;
17
+ export {};
@@ -0,0 +1 @@
1
+ export declare function parseImportedNames(importClause: string): string[];
@@ -0,0 +1,27 @@
1
+ export function parseImportedNames(importClause) {
2
+ const trimmed = importClause.trim();
3
+ if (!trimmed)
4
+ return [];
5
+ const names = [];
6
+ const defaultAndNamed = trimmed.split(',').map((part) => part.trim());
7
+ for (const part of defaultAndNamed) {
8
+ if (!part)
9
+ continue;
10
+ if (part.startsWith('{') && part.endsWith('}')) {
11
+ for (const named of part
12
+ .slice(1, -1)
13
+ .split(',')
14
+ .map((item) => item.trim())
15
+ .filter(Boolean)) {
16
+ const [local] = named.split(/\s+as\s+/i);
17
+ if (local)
18
+ names.push(local.trim());
19
+ }
20
+ continue;
21
+ }
22
+ const [local] = part.split(/\s+as\s+/i);
23
+ if (local)
24
+ names.push(local.trim());
25
+ }
26
+ return names;
27
+ }
@@ -1,6 +1,7 @@
1
1
  import type { ComponentExtractionResult, ExtractorOptions } from '../types.js';
2
- export type ExtractProgress = {
2
+ type ExtractProgress = {
3
3
  filesProcessed: number;
4
4
  componentsFound: number;
5
5
  };
6
6
  export declare function extractComponents(filePaths: string[], onProgress?: (progress: ExtractProgress) => void, opts?: ExtractorOptions): Promise<ComponentExtractionResult>;
7
+ export {};