@auto-engineer/design-system-importer 0.4.8 → 0.5.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/.turbo/turbo-build.log +1 -1
- package/.turbo/turbo-format.log +10 -8
- package/.turbo/turbo-lint.log +1 -1
- package/.turbo/turbo-test.log +14 -12
- package/.turbo/turbo-type-check.log +5 -4
- package/CHANGELOG.md +11 -0
- package/DEBUG.md +195 -0
- package/README.md +45 -18
- package/dist/FigmaComponentsBuilder.d.ts +1 -0
- package/dist/FigmaComponentsBuilder.d.ts.map +1 -1
- package/dist/FigmaComponentsBuilder.js +138 -26
- package/dist/FigmaComponentsBuilder.js.map +1 -1
- package/dist/cli-manifest.d.ts +3 -0
- package/dist/cli-manifest.d.ts.map +1 -0
- package/dist/cli-manifest.js +17 -0
- package/dist/cli-manifest.js.map +1 -0
- package/dist/commands/import-design-system.d.ts +8 -1
- package/dist/commands/import-design-system.d.ts.map +1 -1
- package/dist/commands/import-design-system.js +88 -5
- package/dist/commands/import-design-system.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +104 -17
- package/dist/index.js.map +1 -1
- package/dist/utils/FilterLoader.d.ts.map +1 -1
- package/dist/utils/FilterLoader.js +84 -4
- package/dist/utils/FilterLoader.js.map +1 -1
- package/package.json +5 -6
- package/src/FigmaComponentsBuilder.ts +162 -29
- package/src/cli-manifest.ts +18 -0
- package/src/commands/import-design-system.ts +101 -5
- package/src/index.ts +125 -16
- package/src/utils/FilterLoader.ts +85 -4
- package/tsconfig.tsbuildinfo +1 -1
package/src/index.ts
CHANGED
|
@@ -4,120 +4,195 @@ import * as dotenv from 'dotenv';
|
|
|
4
4
|
import { fileURLToPath } from 'url';
|
|
5
5
|
import * as Figma from 'figma-api';
|
|
6
6
|
import { FigmaComponentsBuilder, type FilterFunctionType } from './FigmaComponentsBuilder';
|
|
7
|
+
import createDebug from 'debug';
|
|
7
8
|
// import { AIProvider, generateTextWithAI } from '@auto-engineer/ai-gateway';
|
|
8
9
|
|
|
10
|
+
const debug = createDebug('design-system-importer');
|
|
11
|
+
const debugFiles = createDebug('design-system-importer:files');
|
|
12
|
+
const debugFigma = createDebug('design-system-importer:figma');
|
|
13
|
+
const debugMarkdown = createDebug('design-system-importer:markdown');
|
|
14
|
+
const debugComponents = createDebug('design-system-importer:components');
|
|
15
|
+
const debugCopy = createDebug('design-system-importer:copy');
|
|
16
|
+
|
|
9
17
|
dotenv.config();
|
|
18
|
+
debug('Design system importer initialized');
|
|
10
19
|
|
|
11
20
|
const __filename = fileURLToPath(import.meta.url);
|
|
12
21
|
|
|
22
|
+
debugFigma('Initializing Figma API with personal access token');
|
|
13
23
|
const api = new Figma.Api({
|
|
14
24
|
personalAccessToken: process.env.FIGMA_PERSONAL_TOKEN as string,
|
|
15
25
|
});
|
|
26
|
+
debugFigma('Figma API initialized');
|
|
16
27
|
|
|
17
28
|
async function getAllTsxFiles(dir: string): Promise<string[]> {
|
|
29
|
+
debugFiles('Scanning directory for TSX files: %s', dir);
|
|
18
30
|
let results: string[] = [];
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
34
|
+
debugFiles('Found %d entries in %s', entries.length, dir);
|
|
35
|
+
|
|
36
|
+
for (const entry of entries) {
|
|
37
|
+
const fullPath = path.join(dir, entry.name);
|
|
38
|
+
if (entry.isDirectory()) {
|
|
39
|
+
debugFiles('Entering subdirectory: %s', entry.name);
|
|
40
|
+
const subResults = await getAllTsxFiles(fullPath);
|
|
41
|
+
results = results.concat(subResults);
|
|
42
|
+
debugFiles('Found %d TSX files in %s', subResults.length, entry.name);
|
|
43
|
+
} else if (entry.isFile() && entry.name.endsWith('.tsx')) {
|
|
44
|
+
debugFiles('Found TSX file: %s', entry.name);
|
|
45
|
+
results.push(fullPath);
|
|
46
|
+
}
|
|
26
47
|
}
|
|
48
|
+
} catch (error) {
|
|
49
|
+
debugFiles('Error reading directory %s: %O', dir, error);
|
|
27
50
|
}
|
|
51
|
+
|
|
52
|
+
debugFiles('Total TSX files found in %s: %d', dir, results.length);
|
|
28
53
|
return results;
|
|
29
54
|
}
|
|
30
55
|
|
|
31
56
|
function getComponentNameFromFile(filePath: string): string {
|
|
57
|
+
debugComponents('Extracting component name from: %s', filePath);
|
|
32
58
|
const file = path.basename(filePath, '.tsx');
|
|
33
59
|
// Capitalize first letter
|
|
34
|
-
|
|
60
|
+
const componentName = file.charAt(0).toUpperCase() + file.slice(1);
|
|
61
|
+
debugComponents('Component name: %s', componentName);
|
|
62
|
+
return componentName;
|
|
35
63
|
}
|
|
36
64
|
|
|
37
65
|
export async function generateDesignSystemMarkdown(inputDir: string, outputDir: string): Promise<void> {
|
|
66
|
+
debugMarkdown('Generating design system markdown from: %s to: %s', inputDir, outputDir);
|
|
67
|
+
|
|
38
68
|
const files = await getAllTsxFiles(inputDir);
|
|
39
69
|
if (files.length === 0) {
|
|
70
|
+
debugMarkdown('WARNING: No .tsx files found in input directory');
|
|
40
71
|
console.warn('No .tsx files found in input directory.');
|
|
41
72
|
return;
|
|
42
73
|
}
|
|
43
74
|
|
|
75
|
+
debugMarkdown('Processing %d TSX files', files.length);
|
|
44
76
|
const componentNames = files.map(getComponentNameFromFile).sort();
|
|
77
|
+
debugMarkdown('Found %d unique components', componentNames.length);
|
|
45
78
|
|
|
46
79
|
let md = '# Design System\n\n## Components\n\n';
|
|
47
80
|
for (const name of componentNames) {
|
|
48
81
|
md += `- ${name}\n`;
|
|
82
|
+
debugMarkdown('Added component to markdown: %s', name);
|
|
49
83
|
}
|
|
50
84
|
|
|
85
|
+
debugMarkdown('Creating output directory: %s', outputDir);
|
|
51
86
|
await fs.mkdir(outputDir, { recursive: true });
|
|
87
|
+
|
|
52
88
|
const outPath = path.join(outputDir, 'design-system.md');
|
|
89
|
+
debugMarkdown('Writing markdown to: %s', outPath);
|
|
53
90
|
await fs.writeFile(outPath, md);
|
|
91
|
+
debugMarkdown('Markdown file written successfully, size: %d bytes', md.length);
|
|
54
92
|
}
|
|
55
93
|
|
|
56
94
|
export * from './commands/import-design-system';
|
|
57
95
|
export type { FilterFunctionType } from './FigmaComponentsBuilder';
|
|
96
|
+
export { CLI_MANIFEST } from './cli-manifest';
|
|
58
97
|
|
|
59
98
|
async function copyFile(inputDir: string, outputDir: string, file: string): Promise<void> {
|
|
60
99
|
const srcPath = path.join(inputDir, file);
|
|
61
100
|
const destPath = path.join(outputDir, file);
|
|
101
|
+
debugCopy('Attempting to copy file: %s from %s to %s', file, inputDir, outputDir);
|
|
62
102
|
|
|
63
103
|
// Check if source file exists
|
|
64
104
|
try {
|
|
65
105
|
await fs.access(srcPath);
|
|
106
|
+
debugCopy('Source file exists: %s', srcPath);
|
|
107
|
+
|
|
108
|
+
debugCopy('Creating output directory: %s', outputDir);
|
|
66
109
|
await fs.mkdir(outputDir, { recursive: true });
|
|
110
|
+
|
|
67
111
|
await fs.copyFile(srcPath, destPath);
|
|
112
|
+
debugCopy('Successfully copied %s to %s', file, destPath);
|
|
68
113
|
} catch (error) {
|
|
69
114
|
// File doesn't exist, skip copying
|
|
115
|
+
debugCopy('File %s not found in %s, error: %O', file, inputDir, error);
|
|
70
116
|
console.log(`File ${file} not found in ${inputDir}, skipping...`, error);
|
|
71
117
|
}
|
|
72
118
|
}
|
|
73
119
|
|
|
74
120
|
export async function copyDesignSystemDocsAndUserPreferences(inputDir: string, outputDir: string): Promise<void> {
|
|
121
|
+
debugCopy('Copying design system docs from %s to %s', inputDir, outputDir);
|
|
122
|
+
|
|
75
123
|
// Ensure output directory exists
|
|
124
|
+
debugCopy('Creating output directory: %s', outputDir);
|
|
76
125
|
await fs.mkdir(outputDir, { recursive: true });
|
|
77
126
|
|
|
78
127
|
// Try to copy existing files
|
|
128
|
+
debugCopy('Copying design-system.md...');
|
|
79
129
|
await copyFile(inputDir, outputDir, 'design-system.md');
|
|
130
|
+
|
|
131
|
+
debugCopy('Copying design-system-principles.md...');
|
|
80
132
|
await copyFile(inputDir, outputDir, 'design-system-principles.md');
|
|
81
133
|
|
|
82
134
|
// If design-system.md doesn't exist in output, try to generate it from TSX files
|
|
83
135
|
const designSystemPath = path.join(outputDir, 'design-system.md');
|
|
136
|
+
debugCopy('Checking if design-system.md exists at: %s', designSystemPath);
|
|
137
|
+
|
|
84
138
|
try {
|
|
85
139
|
await fs.access(designSystemPath);
|
|
140
|
+
debugCopy('design-system.md already exists');
|
|
86
141
|
} catch {
|
|
142
|
+
debugCopy('design-system.md does not exist, attempting to generate from TSX files');
|
|
87
143
|
// File doesn't exist, try to generate from TSX files if inputDir exists
|
|
88
144
|
try {
|
|
89
145
|
await fs.access(inputDir);
|
|
146
|
+
debugCopy('Input directory is accessible: %s', inputDir);
|
|
147
|
+
|
|
90
148
|
const files = await getAllTsxFiles(inputDir);
|
|
91
149
|
if (files.length > 0) {
|
|
150
|
+
debugCopy('Found %d TSX files, generating design-system.md', files.length);
|
|
92
151
|
await generateDesignSystemMarkdown(inputDir, outputDir);
|
|
93
152
|
console.log(`Generated design-system.md from ${files.length} component files`);
|
|
94
153
|
} else {
|
|
154
|
+
debugCopy('No TSX files found in %s', inputDir);
|
|
95
155
|
console.log(`No .tsx files found in ${inputDir} to generate design-system.md`);
|
|
96
156
|
}
|
|
97
|
-
} catch {
|
|
157
|
+
} catch (error) {
|
|
158
|
+
debugCopy('Input directory %s not accessible: %O', inputDir, error);
|
|
98
159
|
console.log(`Input directory ${inputDir} not accessible`);
|
|
99
160
|
}
|
|
100
161
|
}
|
|
162
|
+
|
|
163
|
+
debugCopy('Design system docs copy/generation complete');
|
|
101
164
|
}
|
|
102
165
|
|
|
103
166
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
104
167
|
async function getFigmaComponents(): Promise<{ name: string; description: string; thumbnail: string }[]> {
|
|
168
|
+
debugFigma('Fetching Figma components from file: %s', process.env.FIGMA_FILE_ID);
|
|
105
169
|
let components: { name: string; description: string; thumbnail: string }[] = [];
|
|
170
|
+
|
|
106
171
|
try {
|
|
172
|
+
debugFigma('Making API call to Figma...');
|
|
107
173
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
108
174
|
const response = await api.getFileComponentSets({ file_key: process.env.FIGMA_FILE_ID });
|
|
175
|
+
debugFigma('Figma API response received');
|
|
176
|
+
|
|
109
177
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access
|
|
110
178
|
components = response.meta.component_sets.map(
|
|
111
|
-
(component: { name: string; description: string; thumbnail_url: string }) =>
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
179
|
+
(component: { name: string; description: string; thumbnail_url: string }) => {
|
|
180
|
+
debugFigma('Processing component: %s', component.name);
|
|
181
|
+
return {
|
|
182
|
+
name: component.name,
|
|
183
|
+
description: component.description,
|
|
184
|
+
thumbnail: component.thumbnail_url,
|
|
185
|
+
};
|
|
186
|
+
},
|
|
116
187
|
);
|
|
188
|
+
|
|
189
|
+
debugFigma('Successfully fetched %d components from Figma', components.length);
|
|
117
190
|
console.log('figma response: ', response);
|
|
118
191
|
} catch (e) {
|
|
192
|
+
debugFigma('ERROR: Failed to fetch Figma components: %O', e);
|
|
119
193
|
console.error(e);
|
|
120
194
|
}
|
|
195
|
+
|
|
121
196
|
console.log(components.length);
|
|
122
197
|
return components;
|
|
123
198
|
}
|
|
@@ -125,16 +200,21 @@ async function getFigmaComponents(): Promise<{ name: string; description: string
|
|
|
125
200
|
export function generateMarkdownFromComponents(
|
|
126
201
|
components: { name: string; description: string; thumbnail: string }[],
|
|
127
202
|
): string {
|
|
203
|
+
debugMarkdown('Generating markdown from %d components', components.length);
|
|
128
204
|
let md = '# Design System\n\n## Components\n\n';
|
|
129
205
|
|
|
130
206
|
if (components.length === 0) {
|
|
207
|
+
debugMarkdown('WARNING: No components found to generate markdown');
|
|
131
208
|
console.warn('No components found');
|
|
132
209
|
}
|
|
133
210
|
|
|
134
|
-
for (const component of components) {
|
|
211
|
+
for (const [index, component] of components.entries()) {
|
|
212
|
+
debugMarkdown('Processing component %d/%d: %s', index + 1, components.length, component.name);
|
|
135
213
|
md += `### ${component.name}\nDescription: ${component.description}\nImage: \n\n`;
|
|
214
|
+
debugMarkdown('Added component %s with description length: %d', component.name, component.description.length);
|
|
136
215
|
}
|
|
137
216
|
|
|
217
|
+
debugMarkdown('Generated markdown document, total size: %d bytes', md.length);
|
|
138
218
|
return md;
|
|
139
219
|
}
|
|
140
220
|
|
|
@@ -149,28 +229,44 @@ export async function importDesignSystemComponentsFromFigma(
|
|
|
149
229
|
strategy: ImportStrategy = ImportStrategy.WITH_COMPONENT_SETS,
|
|
150
230
|
filterFn?: FilterFunctionType,
|
|
151
231
|
): Promise<void> {
|
|
232
|
+
debug('Starting Figma design system import');
|
|
233
|
+
debug('Output directory: %s', outputDir);
|
|
234
|
+
debug('Import strategy: %s', strategy);
|
|
235
|
+
debug('Filter function provided: %s', filterFn ? 'yes' : 'no');
|
|
236
|
+
|
|
152
237
|
const figmaComponentsBuilder = new FigmaComponentsBuilder();
|
|
238
|
+
debugComponents('FigmaComponentsBuilder instance created');
|
|
153
239
|
|
|
154
240
|
if (strategy === ImportStrategy.WITH_COMPONENTS) {
|
|
241
|
+
debugComponents('Using strategy: WITH_COMPONENTS');
|
|
155
242
|
await figmaComponentsBuilder.withFigmaComponents();
|
|
156
243
|
} else if (strategy === ImportStrategy.WITH_COMPONENT_SETS) {
|
|
244
|
+
debugComponents('Using strategy: WITH_COMPONENT_SETS');
|
|
157
245
|
await figmaComponentsBuilder.withFigmaComponentSets();
|
|
158
246
|
} else if (strategy === ImportStrategy.WITH_ALL_FIGMA_INSTANCES) {
|
|
247
|
+
debugComponents('Using strategy: WITH_ALL_FIGMA_INSTANCES');
|
|
159
248
|
await figmaComponentsBuilder.withAllFigmaInstanceNames();
|
|
160
249
|
}
|
|
250
|
+
debugComponents('Strategy applied successfully');
|
|
161
251
|
|
|
162
252
|
// figmaComponentsBuilder.withFilteredNamesForMui();
|
|
163
253
|
// figmaComponentsBuilder.withFilteredNamesForShadcn();
|
|
164
254
|
|
|
165
255
|
if (filterFn) {
|
|
256
|
+
debugComponents('Applying custom filter function');
|
|
166
257
|
figmaComponentsBuilder.withFilter(filterFn);
|
|
167
258
|
}
|
|
168
259
|
|
|
260
|
+
debugComponents('Building Figma components...');
|
|
169
261
|
const figmaComponents = figmaComponentsBuilder.build();
|
|
262
|
+
debugComponents('Built %d Figma components', figmaComponents.length);
|
|
170
263
|
|
|
171
264
|
console.log(figmaComponents.length);
|
|
172
265
|
|
|
266
|
+
debugMarkdown('Generating markdown from Figma components');
|
|
173
267
|
const generatedComponentsMDFile = generateMarkdownFromComponents(figmaComponents);
|
|
268
|
+
debugMarkdown('Markdown generated, size: %d bytes', generatedComponentsMDFile.length);
|
|
269
|
+
|
|
174
270
|
// const mdWithImageAnalysis = await generateTextWithAI(
|
|
175
271
|
// `
|
|
176
272
|
// Given this markdown file content:
|
|
@@ -186,8 +282,21 @@ export async function importDesignSystemComponentsFromFigma(
|
|
|
186
282
|
// { temperature: 0.2, maxTokens: 8000 },
|
|
187
283
|
// );
|
|
188
284
|
// await fs.mkdir(outputDir, { recursive: true });
|
|
189
|
-
|
|
285
|
+
|
|
286
|
+
// Parse the outputDir to determine if it's a file path or directory
|
|
287
|
+
const isFilePath = outputDir.endsWith('.md');
|
|
288
|
+
const actualOutputDir = isFilePath ? path.dirname(outputDir) : outputDir;
|
|
289
|
+
const fileName = isFilePath ? path.basename(outputDir) : 'design-system.md';
|
|
290
|
+
|
|
291
|
+
debugFiles('Creating output directory: %s', actualOutputDir);
|
|
292
|
+
await fs.mkdir(actualOutputDir, { recursive: true });
|
|
293
|
+
|
|
294
|
+
const outPath = path.join(actualOutputDir, fileName);
|
|
295
|
+
debugFiles('Writing markdown to: %s', outPath);
|
|
190
296
|
await fs.writeFile(outPath, generatedComponentsMDFile);
|
|
297
|
+
debugFiles('Design system markdown written successfully');
|
|
298
|
+
|
|
299
|
+
debug('Figma design system import complete');
|
|
191
300
|
}
|
|
192
301
|
|
|
193
302
|
// Check if this file is being run directly
|
|
@@ -4,57 +4,83 @@ import { existsSync, readFileSync, writeFileSync, mkdtempSync, rmSync } from 'fs
|
|
|
4
4
|
import { spawnSync } from 'child_process';
|
|
5
5
|
import { tmpdir } from 'os';
|
|
6
6
|
import type { FilterFunctionType } from '../FigmaComponentsBuilder';
|
|
7
|
+
import createDebug from 'debug';
|
|
8
|
+
|
|
9
|
+
const debug = createDebug('design-system-importer:filter-loader');
|
|
10
|
+
const debugLoad = createDebug('design-system-importer:filter-loader:load');
|
|
11
|
+
const debugTS = createDebug('design-system-importer:filter-loader:typescript');
|
|
12
|
+
const debugStrategy = createDebug('design-system-importer:filter-loader:strategy');
|
|
13
|
+
const debugTemplate = createDebug('design-system-importer:filter-loader:template');
|
|
7
14
|
|
|
8
15
|
export class FilterLoader {
|
|
9
16
|
private templatesDir: string;
|
|
10
17
|
|
|
11
18
|
constructor() {
|
|
19
|
+
debug('FilterLoader instance created');
|
|
12
20
|
// Get the directory where template files are stored
|
|
13
21
|
const currentFile = fileURLToPath(import.meta.url);
|
|
14
22
|
this.templatesDir = join(dirname(currentFile), 'templates');
|
|
23
|
+
debug('Templates directory: %s', this.templatesDir);
|
|
15
24
|
}
|
|
16
25
|
|
|
17
26
|
async loadFilter(filePath: string): Promise<FilterFunctionType> {
|
|
27
|
+
debugLoad('Loading filter from: %s', filePath);
|
|
28
|
+
|
|
18
29
|
if (typeof filePath !== 'string' || filePath.trim().length === 0) {
|
|
30
|
+
debugLoad('ERROR: Invalid filter path provided');
|
|
19
31
|
throw new Error('Filter file path is required');
|
|
20
32
|
}
|
|
21
33
|
|
|
22
34
|
const absolutePath = resolvePath(process.cwd(), filePath);
|
|
35
|
+
debugLoad('Resolved absolute path: %s', absolutePath);
|
|
23
36
|
|
|
24
37
|
if (!existsSync(absolutePath)) {
|
|
38
|
+
debugLoad('ERROR: Filter file not found at %s', absolutePath);
|
|
25
39
|
throw new Error(`Filter file not found: ${absolutePath}`);
|
|
26
40
|
}
|
|
27
41
|
|
|
28
42
|
const ext = extname(absolutePath).toLowerCase();
|
|
43
|
+
debugLoad('File extension: %s', ext);
|
|
29
44
|
|
|
30
45
|
// For TypeScript files, use tsx to load them
|
|
31
46
|
if (ext === '.ts' || ext === '.tsx') {
|
|
47
|
+
debugLoad('Detected TypeScript file, using TypeScript loader');
|
|
32
48
|
return this.loadTypeScriptFilter(absolutePath);
|
|
33
49
|
}
|
|
34
50
|
|
|
35
51
|
// For JavaScript files, load directly
|
|
52
|
+
debugLoad('Loading JavaScript file directly');
|
|
36
53
|
const fileUrl = pathToFileURL(absolutePath).href;
|
|
54
|
+
debugLoad('File URL: %s', fileUrl);
|
|
55
|
+
|
|
37
56
|
const loadedUnknown: unknown = await import(fileUrl);
|
|
38
57
|
const loadedModule = loadedUnknown as { filter?: unknown; default?: unknown };
|
|
58
|
+
debugLoad('Module loaded, checking for filter function');
|
|
39
59
|
|
|
40
60
|
if (typeof loadedModule.filter === 'function') {
|
|
61
|
+
debugLoad('Found named export "filter"');
|
|
41
62
|
return loadedModule.filter as FilterFunctionType;
|
|
42
63
|
}
|
|
43
64
|
|
|
44
65
|
if (typeof loadedModule.default === 'function') {
|
|
66
|
+
debugLoad('Found default export, using as filter');
|
|
45
67
|
console.warn('Using default export from filter module. Prefer a named export "filter".');
|
|
46
68
|
return loadedModule.default as FilterFunctionType;
|
|
47
69
|
}
|
|
48
70
|
|
|
71
|
+
debugLoad('ERROR: No filter function found in module');
|
|
49
72
|
throw new Error('No filter function found. Export a function named "filter" or as a default export from the file.');
|
|
50
73
|
}
|
|
51
74
|
|
|
52
75
|
private async loadTypeScriptFilter(filePath: string): Promise<FilterFunctionType> {
|
|
76
|
+
debugTS('Loading TypeScript filter from: %s', filePath);
|
|
53
77
|
// Create a temporary directory for our scripts
|
|
54
78
|
const tempDir = mkdtempSync(join(tmpdir(), 'tsx-filter-'));
|
|
79
|
+
debugTS('Created temp directory: %s', tempDir);
|
|
55
80
|
|
|
56
81
|
try {
|
|
57
82
|
const filterUrl = pathToFileURL(filePath).href;
|
|
83
|
+
debugTS('Filter URL: %s', filterUrl);
|
|
58
84
|
|
|
59
85
|
// Try different loading strategies in order
|
|
60
86
|
const strategies = [
|
|
@@ -62,14 +88,19 @@ export class FilterLoader {
|
|
|
62
88
|
() => this.tryNpxTsxExport(tempDir, filterUrl),
|
|
63
89
|
() => this.tryNodeLoaderOptions(tempDir, filterUrl),
|
|
64
90
|
];
|
|
91
|
+
debugTS('Will try %d loading strategies', strategies.length);
|
|
65
92
|
|
|
66
|
-
for (
|
|
67
|
-
|
|
93
|
+
for (let i = 0; i < strategies.length; i++) {
|
|
94
|
+
debugTS('Trying strategy %d/%d', i + 1, strategies.length);
|
|
95
|
+
const result = await strategies[i]();
|
|
68
96
|
if (result) {
|
|
97
|
+
debugTS('Strategy %d succeeded!', i + 1);
|
|
69
98
|
return result;
|
|
70
99
|
}
|
|
100
|
+
debugTS('Strategy %d failed, trying next...', i + 1);
|
|
71
101
|
}
|
|
72
102
|
|
|
103
|
+
debugTS('ERROR: All strategies failed to load TypeScript filter');
|
|
73
104
|
throw new Error(
|
|
74
105
|
`TypeScript filter cannot be loaded. ` +
|
|
75
106
|
`Please ensure tsx is installed in your project:\n` +
|
|
@@ -77,25 +108,32 @@ export class FilterLoader {
|
|
|
77
108
|
`Or provide a JavaScript version of your filter.`,
|
|
78
109
|
);
|
|
79
110
|
} catch (error) {
|
|
111
|
+
debugTS('ERROR: Failed to load TypeScript filter: %O', error);
|
|
80
112
|
throw new Error(
|
|
81
113
|
`Failed to load TypeScript filter.\n` +
|
|
82
114
|
`Error: ${error instanceof Error ? error.message : String(error)}\n` +
|
|
83
115
|
`Please ensure tsx is installed locally: npm install -D tsx`,
|
|
84
116
|
);
|
|
85
117
|
} finally {
|
|
118
|
+
debugTS('Cleaning up temp directory: %s', tempDir);
|
|
86
119
|
// Clean up temp files
|
|
87
120
|
try {
|
|
88
121
|
rmSync(tempDir, { recursive: true, force: true });
|
|
89
|
-
|
|
122
|
+
debugTS('Temp directory cleaned up successfully');
|
|
123
|
+
} catch (cleanupError) {
|
|
124
|
+
debugTS('Warning: Failed to clean up temp directory: %O', cleanupError);
|
|
90
125
|
// Ignore cleanup errors
|
|
91
126
|
}
|
|
92
127
|
}
|
|
93
128
|
}
|
|
94
129
|
|
|
95
130
|
private async tryNodeDirectLoad(tempDir: string, filterUrl: string): Promise<FilterFunctionType | null> {
|
|
131
|
+
debugStrategy('Strategy: Node direct load');
|
|
96
132
|
// First attempt: Try to run the loader script with Node.js directly
|
|
97
133
|
const loaderFile = this.prepareTemplate(tempDir, 'tsx-loader.mjs', filterUrl);
|
|
134
|
+
debugStrategy('Prepared loader file: %s', loaderFile);
|
|
98
135
|
|
|
136
|
+
debugStrategy('Spawning node with experimental loader...');
|
|
99
137
|
const result = spawnSync('node', [loaderFile], {
|
|
100
138
|
cwd: process.cwd(),
|
|
101
139
|
encoding: 'utf-8',
|
|
@@ -104,46 +142,69 @@ export class FilterLoader {
|
|
|
104
142
|
NODE_OPTIONS: '--experimental-loader tsx',
|
|
105
143
|
},
|
|
106
144
|
});
|
|
145
|
+
debugStrategy('Node process exit code: %d', result.status);
|
|
107
146
|
|
|
108
147
|
if (result.status === 0) {
|
|
148
|
+
debugStrategy('Node direct load succeeded, importing module');
|
|
109
149
|
// Success! Now import the loader module to get the filter
|
|
110
150
|
const loaderModule = (await import(pathToFileURL(loaderFile).href)) as { filter: FilterFunctionType };
|
|
151
|
+
debugStrategy('Filter function retrieved from module');
|
|
111
152
|
return loaderModule.filter;
|
|
112
153
|
}
|
|
113
154
|
|
|
155
|
+
debugStrategy('Node direct load failed');
|
|
156
|
+
if (result.stderr) {
|
|
157
|
+
debugStrategy('Error output: %s', result.stderr);
|
|
158
|
+
}
|
|
114
159
|
return null;
|
|
115
160
|
}
|
|
116
161
|
|
|
117
162
|
private async tryNpxTsxExport(tempDir: string, filterUrl: string): Promise<FilterFunctionType | null> {
|
|
163
|
+
debugStrategy('Strategy: npx tsx export');
|
|
118
164
|
// Second attempt: Use npx tsx to run a script that exports the filter
|
|
119
165
|
const exportFile = this.prepareTemplate(tempDir, 'tsx-export.mjs', filterUrl);
|
|
166
|
+
debugStrategy('Prepared export file: %s', exportFile);
|
|
120
167
|
|
|
168
|
+
debugStrategy('Spawning npx tsx...');
|
|
121
169
|
const npxResult = spawnSync('npx', ['tsx', exportFile], {
|
|
122
170
|
cwd: process.cwd(),
|
|
123
171
|
encoding: 'utf-8',
|
|
124
172
|
shell: true,
|
|
125
173
|
});
|
|
174
|
+
debugStrategy('npx process exit code: %d', npxResult.status);
|
|
126
175
|
|
|
127
176
|
if (npxResult.status === 0 && npxResult.stdout) {
|
|
177
|
+
debugStrategy('npx tsx succeeded, parsing output');
|
|
128
178
|
try {
|
|
129
179
|
const output = JSON.parse(npxResult.stdout) as { success: boolean; filterCode?: string };
|
|
130
180
|
if (output.success === true && typeof output.filterCode === 'string' && output.filterCode.length > 0) {
|
|
181
|
+
debugStrategy('Successfully parsed filter code, creating function');
|
|
131
182
|
// Create a function from the filter code
|
|
132
183
|
// eslint-disable-next-line @typescript-eslint/no-implied-eval, @typescript-eslint/no-unsafe-call
|
|
133
184
|
const filter = new Function('return ' + output.filterCode)() as FilterFunctionType;
|
|
185
|
+
debugStrategy('Filter function created successfully');
|
|
134
186
|
return filter;
|
|
135
187
|
}
|
|
136
|
-
|
|
188
|
+
debugStrategy('Output missing success flag or filter code');
|
|
189
|
+
} catch (parseError) {
|
|
190
|
+
debugStrategy('Failed to parse npx output: %O', parseError);
|
|
137
191
|
// JSON parse failed, try another approach
|
|
138
192
|
}
|
|
193
|
+
} else {
|
|
194
|
+
debugStrategy('npx tsx failed or no output');
|
|
195
|
+
if (npxResult.stderr) {
|
|
196
|
+
debugStrategy('Error output: %s', npxResult.stderr);
|
|
197
|
+
}
|
|
139
198
|
}
|
|
140
199
|
|
|
141
200
|
return null;
|
|
142
201
|
}
|
|
143
202
|
|
|
144
203
|
private async tryNodeLoaderOptions(tempDir: string, filterUrl: string): Promise<FilterFunctionType | null> {
|
|
204
|
+
debugStrategy('Strategy: Node loader options');
|
|
145
205
|
// Third attempt: Create a wrapper that uses dynamic import with tsx
|
|
146
206
|
const wrapperFile = this.prepareTemplate(tempDir, 'tsx-wrapper.mjs', filterUrl);
|
|
207
|
+
debugStrategy('Prepared wrapper file: %s', wrapperFile);
|
|
147
208
|
|
|
148
209
|
// Try with various Node.js loader configurations
|
|
149
210
|
const loaderOptions = [
|
|
@@ -152,53 +213,73 @@ export class FilterLoader {
|
|
|
152
213
|
['--require', 'tsx'],
|
|
153
214
|
['--import', 'tsx'],
|
|
154
215
|
];
|
|
216
|
+
debugStrategy('Will try %d loader configurations', loaderOptions.length);
|
|
155
217
|
|
|
156
218
|
for (const options of loaderOptions) {
|
|
219
|
+
debugStrategy('Trying loader options: %s', options.join(' '));
|
|
157
220
|
const testResult = spawnSync('node', [...options, wrapperFile], {
|
|
158
221
|
cwd: process.cwd(),
|
|
159
222
|
encoding: 'utf-8',
|
|
160
223
|
env: { ...process.env },
|
|
161
224
|
});
|
|
225
|
+
debugStrategy('Exit code: %d', testResult.status);
|
|
162
226
|
|
|
163
227
|
if (testResult.status === 0) {
|
|
228
|
+
debugStrategy('Configuration works! Loading final filter');
|
|
164
229
|
// This configuration works! Use it to load the actual filter
|
|
165
230
|
const finalFile = this.prepareTemplate(tempDir, 'tsx-final-loader.mjs', filterUrl);
|
|
166
231
|
const finalModule = (await import(pathToFileURL(finalFile).href)) as { filter: FilterFunctionType };
|
|
232
|
+
debugStrategy('Filter loaded successfully with options: %s', options.join(' '));
|
|
167
233
|
return finalModule.filter;
|
|
234
|
+
} else if (testResult.stderr) {
|
|
235
|
+
debugStrategy('Error with %s: %s', options.join(' '), testResult.stderr);
|
|
168
236
|
}
|
|
169
237
|
}
|
|
170
238
|
|
|
239
|
+
debugStrategy('All loader options failed');
|
|
171
240
|
return null;
|
|
172
241
|
}
|
|
173
242
|
|
|
174
243
|
private prepareTemplate(tempDir: string, templateName: string, filterPath: string): string {
|
|
244
|
+
debugTemplate('Preparing template: %s', templateName);
|
|
245
|
+
debugTemplate('Filter path: %s', filterPath);
|
|
175
246
|
const templatePath = join(this.templatesDir, templateName);
|
|
176
247
|
const outputPath = join(tempDir, templateName);
|
|
248
|
+
debugTemplate('Template path: %s', templatePath);
|
|
249
|
+
debugTemplate('Output path: %s', outputPath);
|
|
177
250
|
|
|
178
251
|
// Read the template
|
|
179
252
|
let content: string;
|
|
180
253
|
if (existsSync(templatePath)) {
|
|
254
|
+
debugTemplate('Reading template from source');
|
|
181
255
|
content = readFileSync(templatePath, 'utf-8');
|
|
182
256
|
} else {
|
|
257
|
+
debugTemplate('Template not in source, checking dist');
|
|
183
258
|
// Fallback to dist directory if running from compiled code
|
|
184
259
|
const distTemplatePath = templatePath.replace('/src/', '/dist/');
|
|
260
|
+
debugTemplate('Checking dist path: %s', distTemplatePath);
|
|
185
261
|
if (existsSync(distTemplatePath)) {
|
|
262
|
+
debugTemplate('Reading template from dist');
|
|
186
263
|
content = readFileSync(distTemplatePath, 'utf-8');
|
|
187
264
|
} else {
|
|
265
|
+
debugTemplate('ERROR: Template not found in source or dist');
|
|
188
266
|
throw new Error(`Template not found: ${templateName}`);
|
|
189
267
|
}
|
|
190
268
|
}
|
|
191
269
|
|
|
192
270
|
// Replace the placeholder with the actual filter path
|
|
193
271
|
content = content.replace(/__FILTER_PATH__/g, filterPath);
|
|
272
|
+
debugTemplate('Replaced filter path placeholder');
|
|
194
273
|
|
|
195
274
|
// Write the prepared script
|
|
196
275
|
writeFileSync(outputPath, content);
|
|
276
|
+
debugTemplate('Written prepared script to: %s', outputPath);
|
|
197
277
|
|
|
198
278
|
return outputPath;
|
|
199
279
|
}
|
|
200
280
|
|
|
201
281
|
cleanup(): void {
|
|
282
|
+
debug('FilterLoader cleanup called');
|
|
202
283
|
// No cleanup needed
|
|
203
284
|
}
|
|
204
285
|
}
|