transcripto-cli 1.0.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.
Files changed (58) hide show
  1. package/README.md +576 -0
  2. package/dist/cli/generate.d.ts +2 -0
  3. package/dist/cli/generate.d.ts.map +1 -0
  4. package/dist/cli/generate.js +416 -0
  5. package/dist/cli/generate.js.map +1 -0
  6. package/dist/cli/index.d.ts +3 -0
  7. package/dist/cli/index.d.ts.map +1 -0
  8. package/dist/cli/index.js +43 -0
  9. package/dist/cli/index.js.map +1 -0
  10. package/dist/cli/init.d.ts +2 -0
  11. package/dist/cli/init.d.ts.map +1 -0
  12. package/dist/cli/init.js +81 -0
  13. package/dist/cli/init.js.map +1 -0
  14. package/dist/cli/report.d.ts +2 -0
  15. package/dist/cli/report.d.ts.map +1 -0
  16. package/dist/cli/report.js +137 -0
  17. package/dist/cli/report.js.map +1 -0
  18. package/dist/cli/scan.d.ts +2 -0
  19. package/dist/cli/scan.d.ts.map +1 -0
  20. package/dist/cli/scan.js +62 -0
  21. package/dist/cli/scan.js.map +1 -0
  22. package/dist/cli/watch-i18n.d.ts +2 -0
  23. package/dist/cli/watch-i18n.d.ts.map +1 -0
  24. package/dist/cli/watch-i18n.js +73 -0
  25. package/dist/cli/watch-i18n.js.map +1 -0
  26. package/dist/cli/watch.d.ts +2 -0
  27. package/dist/cli/watch.d.ts.map +1 -0
  28. package/dist/cli/watch.js +147 -0
  29. package/dist/cli/watch.js.map +1 -0
  30. package/dist/core/i18nGenerator.d.ts +16 -0
  31. package/dist/core/i18nGenerator.d.ts.map +1 -0
  32. package/dist/core/i18nGenerator.js +139 -0
  33. package/dist/core/i18nGenerator.js.map +1 -0
  34. package/dist/core/projectScanner.d.ts +12 -0
  35. package/dist/core/projectScanner.d.ts.map +1 -0
  36. package/dist/core/projectScanner.js +53 -0
  37. package/dist/core/projectScanner.js.map +1 -0
  38. package/dist/core/stringExtractor.d.ts +21 -0
  39. package/dist/core/stringExtractor.d.ts.map +1 -0
  40. package/dist/core/stringExtractor.js +268 -0
  41. package/dist/core/stringExtractor.js.map +1 -0
  42. package/dist/index.d.ts +7 -0
  43. package/dist/index.d.ts.map +1 -0
  44. package/dist/index.js +10 -0
  45. package/dist/index.js.map +1 -0
  46. package/package.json +44 -0
  47. package/src/cli/generate.ts +422 -0
  48. package/src/cli/index.ts +50 -0
  49. package/src/cli/init.ts +96 -0
  50. package/src/cli/report.ts +160 -0
  51. package/src/cli/scan.ts +69 -0
  52. package/src/cli/watch-i18n.ts +77 -0
  53. package/src/cli/watch.ts +129 -0
  54. package/src/core/i18nGenerator.ts +127 -0
  55. package/src/core/projectScanner.ts +62 -0
  56. package/src/core/stringExtractor.ts +276 -0
  57. package/src/index.ts +7 -0
  58. package/tsconfig.json +20 -0
@@ -0,0 +1,160 @@
1
+ import { promises as fs } from 'fs';
2
+ import chalk from 'chalk';
3
+ import { ExtractedString } from '../core/stringExtractor';
4
+
5
+ interface ReportData {
6
+ totalStrings: number;
7
+ filesScanned: number;
8
+ languages: string[];
9
+ coverage: number;
10
+ missingTranslations: string[];
11
+ stringsByFile: Record<string, number>;
12
+ }
13
+
14
+ export async function reportCommand(): Promise<void> {
15
+ console.log(chalk.blue('📊 Generating localization report...'));
16
+
17
+ try {
18
+ const report = await generateReport();
19
+ displayReport(report);
20
+
21
+ } catch (error) {
22
+ console.error(chalk.red('❌ Report generation failed:'), error);
23
+ process.exit(1);
24
+ }
25
+ }
26
+
27
+ async function generateReport(): Promise<ReportData> {
28
+ // Load extracted strings
29
+ const strings = await loadExtractedStrings();
30
+
31
+ // Load config
32
+ const config = await loadConfig();
33
+
34
+ // Load existing translations
35
+ const translations = await loadTranslations(config.i18n.languages);
36
+
37
+ // Calculate metrics
38
+ const filesScanned = new Set(strings.map(s => s.filePath)).size;
39
+ const stringsByFile: Record<string, number> = {};
40
+
41
+ strings.forEach(str => {
42
+ const fileName = str.filePath.split('/').pop() || str.filePath;
43
+ stringsByFile[fileName] = (stringsByFile[fileName] || 0) + 1;
44
+ });
45
+
46
+ // Check missing translations
47
+ const missingTranslations: string[] = [];
48
+ for (const lang of config.i18n.languages) {
49
+ if (lang !== 'en' && (!translations[lang] || Object.keys(translations[lang]).length < strings.length)) {
50
+ missingTranslations.push(lang);
51
+ }
52
+ }
53
+
54
+ const coverage = strings.length > 0
55
+ ? Math.round((translations.en ? Object.keys(translations.en).length : 0) / strings.length * 100)
56
+ : 0;
57
+
58
+ return {
59
+ totalStrings: strings.length,
60
+ filesScanned,
61
+ languages: config.i18n.languages,
62
+ coverage,
63
+ missingTranslations,
64
+ stringsByFile
65
+ };
66
+ }
67
+
68
+ function displayReport(report: ReportData): void {
69
+ console.log(chalk.green('\n📈 Transcripto Localization Report'));
70
+ console.log(chalk.gray('='.repeat(40)));
71
+
72
+ console.log(chalk.white(`\n📝 Total Strings: ${report.totalStrings}`));
73
+ console.log(chalk.white(`📁 Files Scanned: ${report.filesScanned}`));
74
+ console.log(chalk.white(`🌐 Languages: ${report.languages.join(', ')}`));
75
+
76
+ // Coverage with color coding
77
+ let coverageColor = chalk.red;
78
+ if (report.coverage >= 80) coverageColor = chalk.green;
79
+ else if (report.coverage >= 50) coverageColor = chalk.yellow;
80
+
81
+ console.log(coverageColor(`📊 Coverage: ${report.coverage}%`));
82
+
83
+ // Missing translations
84
+ if (report.missingTranslations.length > 0) {
85
+ console.log(chalk.red(`\n⚠️ Missing Translations: ${report.missingTranslations.join(', ')}`));
86
+ } else {
87
+ console.log(chalk.green('\n✅ All languages have complete translations'));
88
+ }
89
+
90
+ // Strings by file
91
+ if (Object.keys(report.stringsByFile).length > 0) {
92
+ console.log(chalk.white('\n📋 Strings by File:'));
93
+ const sortedFiles = Object.entries(report.stringsByFile)
94
+ .sort(([,a], [,b]) => b - a)
95
+ .slice(0, 10);
96
+
97
+ sortedFiles.forEach(([file, count]) => {
98
+ console.log(chalk.gray(` ${file}: ${count} strings`));
99
+ });
100
+
101
+ if (Object.keys(report.stringsByFile).length > 10) {
102
+ console.log(chalk.gray(` ... and ${Object.keys(report.stringsByFile).length - 10} more files`));
103
+ }
104
+ }
105
+
106
+ // Recommendations
107
+ console.log(chalk.white('\n💡 Recommendations:'));
108
+
109
+ if (report.totalStrings === 0) {
110
+ console.log(chalk.yellow(' • Run "transcripto scan" to extract UI strings'));
111
+ } else if (report.coverage < 100) {
112
+ console.log(chalk.yellow(' • Run "transcripto generate" to update translation files'));
113
+ }
114
+
115
+ if (report.missingTranslations.length > 0) {
116
+ console.log(chalk.yellow(' • Run "npx lingo.dev@latest run" to generate missing translations'));
117
+ }
118
+
119
+ if (report.totalStrings > 0 && report.coverage === 100) {
120
+ console.log(chalk.green(' • Localization is complete! 🎉'));
121
+ }
122
+ }
123
+
124
+ async function loadExtractedStrings(): Promise<ExtractedString[]> {
125
+ try {
126
+ const content = await fs.readFile('.transcripto/extracted-strings.json', 'utf-8');
127
+ return JSON.parse(content);
128
+ } catch (error) {
129
+ return [];
130
+ }
131
+ }
132
+
133
+ async function loadConfig() {
134
+ try {
135
+ const configContent = await fs.readFile('.transcripto/config.json', 'utf-8');
136
+ return JSON.parse(configContent);
137
+ } catch (error) {
138
+ return {
139
+ i18n: {
140
+ languages: ['en', 'hi', 'kn'],
141
+ outputDir: './src/i18n'
142
+ }
143
+ };
144
+ }
145
+ }
146
+
147
+ async function loadTranslations(languages: string[]): Promise<Record<string, Record<string, string>>> {
148
+ const translations: Record<string, Record<string, string>> = {};
149
+
150
+ for (const lang of languages) {
151
+ try {
152
+ const content = await fs.readFile(`./src/i18n/${lang}.json`, 'utf-8');
153
+ translations[lang] = JSON.parse(content);
154
+ } catch (error) {
155
+ translations[lang] = {};
156
+ }
157
+ }
158
+
159
+ return translations;
160
+ }
@@ -0,0 +1,69 @@
1
+ import { promises as fs } from 'fs';
2
+ import chalk from 'chalk';
3
+ import { ProjectScanner } from '../core/projectScanner';
4
+ import { StringExtractor } from '../core/stringExtractor';
5
+
6
+ export async function scanCommand(): Promise<void> {
7
+ console.log(chalk.blue('🔍 Scanning project for UI text strings...'));
8
+
9
+ try {
10
+ // Load config
11
+ const config = await loadConfig();
12
+
13
+ // Initialize scanner and extractor
14
+ const scanner = new ProjectScanner();
15
+ const extractor = new StringExtractor();
16
+
17
+ // Scan project
18
+ const files = await scanner.scanProject();
19
+ console.log(chalk.gray(`📁 Found ${files.length} files to scan`));
20
+
21
+ // Extract strings
22
+ const strings = await extractor.extractStrings(files);
23
+
24
+ // Save results
25
+ await fs.writeFile(
26
+ '.transcripto/extracted-strings.json',
27
+ JSON.stringify(strings, null, 2),
28
+ 'utf-8'
29
+ );
30
+
31
+ console.log(chalk.green(`✅ Extracted ${strings.length} UI text strings`));
32
+
33
+ if (strings.length > 0) {
34
+ console.log(chalk.yellow('\n📝 Sample extracted strings:'));
35
+ strings.slice(0, 5).forEach((str, index) => {
36
+ console.log(chalk.white(` ${index + 1}. "${str.text}" → ${str.key}`));
37
+ console.log(chalk.gray(` from ${str.filePath}:${str.line}`));
38
+ });
39
+
40
+ if (strings.length > 5) {
41
+ console.log(chalk.gray(` ... and ${strings.length - 5} more`));
42
+ }
43
+ }
44
+
45
+ console.log(chalk.yellow('\n🎯 Next steps:'));
46
+ console.log(chalk.white(' transcripto generate - Generate i18n files from extracted strings'));
47
+ console.log(chalk.white(' transcripto replace - Replace inline text with constants'));
48
+
49
+ } catch (error) {
50
+ console.error(chalk.red('❌ Scan failed:'), error);
51
+ process.exit(1);
52
+ }
53
+ }
54
+
55
+ async function loadConfig() {
56
+ try {
57
+ const configContent = await fs.readFile('.transcripto/config.json', 'utf-8');
58
+ return JSON.parse(configContent);
59
+ } catch (error) {
60
+ console.warn(chalk.yellow('⚠️ No .transcripto/config.json found, using defaults'));
61
+ return {
62
+ scan: {
63
+ extensions: ['.ts', '.tsx', '.js', '.jsx', '.html'],
64
+ exclude: ['node_modules', 'dist', 'build', '.git', 'coverage'],
65
+ minStringLength: 2
66
+ }
67
+ };
68
+ }
69
+ }
@@ -0,0 +1,77 @@
1
+ import chalk from 'chalk';
2
+ import { promises as fs } from 'fs';
3
+ import chokidar from 'chokidar';
4
+ import { ProjectScanner } from '../core/projectScanner';
5
+ import { StringExtractor } from '../core/stringExtractor';
6
+ import { I18nGenerator } from '../core/i18nGenerator';
7
+
8
+ export async function watchI18nCommand(): Promise<void> {
9
+ console.log(chalk.cyan('👁️ Starting i18n file watcher...'));
10
+ console.log(chalk.gray('📁 Monitoring: ./i18n/en.json'));
11
+ console.log(chalk.gray('🔄 Auto-translating on changes...\n'));
12
+
13
+ // Ensure i18n directory exists
14
+ try {
15
+ await fs.mkdir('./i18n', { recursive: true });
16
+ } catch {
17
+ // Directory already exists
18
+ }
19
+
20
+ // Initial translation if en.json exists
21
+ try {
22
+ const enContent = await fs.readFile('./i18n/en.json', 'utf-8');
23
+ const translations = JSON.parse(enContent);
24
+ const keys = Object.keys(translations);
25
+
26
+ if (keys.length > 0) {
27
+ console.log(chalk.yellow('📝 Found existing translations, running initial translation...'));
28
+ await runTranslation();
29
+ }
30
+ } catch {
31
+ console.log(chalk.yellow('📝 No existing en.json found, waiting for file creation...'));
32
+ }
33
+
34
+ // Watch for changes in en.json
35
+ const watcher = chokidar.watch('./i18n/en.json', {
36
+ persistent: true,
37
+ ignoreInitial: true
38
+ });
39
+
40
+ watcher.on('change', async () => {
41
+ console.log(chalk.cyan('\n🔄 File changed: ./i18n/en.json'));
42
+ console.log(chalk.yellow('🌐 Running auto-translation...'));
43
+
44
+ try {
45
+ await runTranslation();
46
+ console.log(chalk.green('✅ Auto-translation completed!'));
47
+ } catch (error) {
48
+ console.error(chalk.red('❌ Auto-translation failed:'), error);
49
+ }
50
+ });
51
+
52
+ watcher.on('add', async () => {
53
+ console.log(chalk.cyan('\n📁 File created: ./i18n/en.json'));
54
+ console.log(chalk.yellow('🌐 Running initial translation...'));
55
+
56
+ try {
57
+ await runTranslation();
58
+ console.log(chalk.green('✅ Initial translation completed!'));
59
+ } catch (error) {
60
+ console.error(chalk.red('❌ Initial translation failed:'), error);
61
+ }
62
+ });
63
+
64
+ console.log(chalk.green('🎉 Watcher started successfully!'));
65
+ console.log(chalk.gray('💡 Add new keys to ./i18n/en.json and they will be auto-translated'));
66
+ console.log(chalk.gray('⏹️ Press Ctrl+C to stop watching\n'));
67
+ }
68
+
69
+ async function runTranslation() {
70
+ const generator = new I18nGenerator();
71
+
72
+ // Generate lingo.dev config for root/i18n
73
+ await generator.generateLingoDevConfig();
74
+
75
+ // Run lingo.dev translation
76
+ await generator.runLingoDev();
77
+ }
@@ -0,0 +1,129 @@
1
+ import { promises as fs } from 'fs';
2
+ import path from 'path';
3
+ import chalk from 'chalk';
4
+ import * as chokidar from 'chokidar';
5
+ import { I18nGenerator } from '../core/i18nGenerator';
6
+ import { ProjectScanner } from '../core/projectScanner';
7
+ import { StringExtractor } from '../core/stringExtractor';
8
+ import { ExtractedString } from '../core/stringExtractor';
9
+
10
+ export async function watchCommand(options: any): Promise<void> {
11
+ console.log(chalk.blue('👁️ Starting Transcripto file watcher...'));
12
+
13
+ const yes = options.yes || false;
14
+
15
+ if (yes) {
16
+ console.log(chalk.yellow('🤖 Running in non-interactive mode (--yes)'));
17
+ }
18
+
19
+ // Initial setup
20
+ await setupProject(yes);
21
+
22
+ // Watch for file changes
23
+ const watcher = chokidar.watch([
24
+ 'src/**/*.{ts,tsx,js,jsx}',
25
+ '!src/i18n/**/*',
26
+ '!dist/**/*',
27
+ '!node_modules/**/*'
28
+ ], {
29
+ ignored: /node_modules/,
30
+ persistent: true,
31
+ ignoreInitial: true
32
+ });
33
+
34
+ console.log(chalk.green('📁 Watching for changes in src/ directory...'));
35
+ console.log(chalk.gray('💡 Press Ctrl+C to stop watching'));
36
+
37
+ watcher.on('change', async (filePath: string) => {
38
+ console.log(chalk.yellow(`\n📝 File changed: ${filePath}`));
39
+ await handleFileChange(filePath, yes);
40
+ });
41
+
42
+ watcher.on('add', async (filePath: string) => {
43
+ console.log(chalk.yellow(`\n➕ File added: ${filePath}`));
44
+ await handleFileChange(filePath, yes);
45
+ });
46
+
47
+ // Handle process termination
48
+ process.on('SIGINT', () => {
49
+ console.log(chalk.cyan('\n👋 Stopping Transcripto watcher...'));
50
+ watcher.close();
51
+ process.exit(0);
52
+ });
53
+ }
54
+
55
+ async function setupProject(yes: boolean): Promise<void> {
56
+ try {
57
+ // Check if project is initialized
58
+ try {
59
+ await fs.access('.transcripto');
60
+ console.log(chalk.green('✅ Transcripto project already initialized'));
61
+ } catch {
62
+ console.log(chalk.yellow('🔧 Initializing Transcripto project...'));
63
+ const { initCommand } = await import('./init');
64
+ await initCommand();
65
+ }
66
+
67
+ // Initial scan and generation
68
+ console.log(chalk.blue('🔍 Performing initial scan...'));
69
+ await performFullWorkflow(yes);
70
+
71
+ } catch (error) {
72
+ console.error(chalk.red('❌ Setup failed:'), error);
73
+ process.exit(1);
74
+ }
75
+ }
76
+
77
+ async function handleFileChange(filePath: string, yes: boolean): Promise<void> {
78
+ try {
79
+ console.log(chalk.cyan('🔄 Updating translations...'));
80
+
81
+ // Perform full workflow
82
+ await performFullWorkflow(yes);
83
+
84
+ console.log(chalk.green('✅ Translations updated successfully!'));
85
+
86
+ } catch (error) {
87
+ console.error(chalk.red('❌ Update failed:'), error);
88
+ }
89
+ }
90
+
91
+ async function performFullWorkflow(yes: boolean): Promise<void> {
92
+ // Scan for strings
93
+ const scanner = new ProjectScanner();
94
+ const files = await scanner.scanProject('./src');
95
+
96
+ const extractor = new StringExtractor();
97
+ const strings = await extractor.extractStrings(files);
98
+
99
+ if (strings.length === 0) {
100
+ console.log(chalk.yellow('⚠️ No strings found'));
101
+ return;
102
+ }
103
+
104
+ // Generate i18n files using same config as generate command
105
+ const generator = new I18nGenerator();
106
+ const config = {
107
+ outputDir: './i18n', // Changed to root folder
108
+ languages: ['en'], // Start with just English, lingo.dev will add more
109
+ constantsFile: '', // No constants file needed
110
+ keyPrefix: ''
111
+ };
112
+
113
+ await generator.generateI18nFiles(strings, config);
114
+ await generator.generateLingoDevConfig(); // Let lingo.dev decide target languages
115
+
116
+ // Run lingo.dev automatically
117
+ try {
118
+ await generator.runLingoDev();
119
+ console.log(chalk.green('🌐 lingo.dev translations completed!'));
120
+ } catch (error) {
121
+ console.log(chalk.yellow('⚠️ lingo.dev failed, continuing...'));
122
+ }
123
+
124
+ // Replace strings with constants
125
+ // const replacer = new TextReplacer();
126
+ // await replacer.replaceInFiles(strings);
127
+
128
+ console.log(chalk.green(`✅ Processed ${strings.length} strings`));
129
+ }
@@ -0,0 +1,127 @@
1
+ import { promises as fs } from 'fs';
2
+ import path from 'path';
3
+ import { ExtractedString } from './stringExtractor';
4
+
5
+ export interface I18nConfig {
6
+ outputDir: string;
7
+ languages: string[];
8
+ constantsFile: string;
9
+ keyPrefix: string;
10
+ }
11
+
12
+ export class I18nGenerator {
13
+ private readonly defaultConfig: I18nConfig = {
14
+ outputDir: './i18n', // Changed to root folder
15
+ languages: ['en', 'hi', 'kn'],
16
+ constantsFile: './src/i18n/constants.ts',
17
+ keyPrefix: ''
18
+ };
19
+
20
+ async generateI18nFiles(
21
+ strings: ExtractedString[],
22
+ config: Partial<I18nConfig> = {}
23
+ ): Promise<void> {
24
+ const finalConfig = { ...this.defaultConfig, ...config };
25
+
26
+ // Create output directory
27
+ await fs.mkdir(finalConfig.outputDir, { recursive: true });
28
+
29
+ // Generate translation files only (no constants file)
30
+ for (const language of finalConfig.languages) {
31
+ await this.generateTranslationFile(strings, language, finalConfig);
32
+ }
33
+ }
34
+
35
+ private async generateTranslationFile(
36
+ strings: ExtractedString[],
37
+ language: string,
38
+ config: I18nConfig
39
+ ): Promise<void> {
40
+ const translations: Record<string, string> = {};
41
+
42
+ for (const str of strings) {
43
+ translations[str.key] = str.text;
44
+ }
45
+
46
+ const filePath = path.join(config.outputDir, `${language}.json`);
47
+ const content = JSON.stringify(translations, null, 2);
48
+
49
+ await fs.writeFile(filePath, content, 'utf-8');
50
+ }
51
+
52
+ async generateLingoDevConfig(): Promise<void> {
53
+ // First, delete any existing lingo.dev config to ensure clean setup
54
+ try {
55
+ await fs.unlink('.lingodev.json');
56
+ console.log('🗑️ Removed existing lingo.dev config');
57
+ } catch {
58
+ // Config doesn't exist, that's fine
59
+ }
60
+
61
+ // Let lingo.dev handle target languages automatically - don't specify any
62
+ const config = {
63
+ source: './i18n/en.json', // Changed to root folder
64
+ // target: [] - Let lingo.dev decide automatically
65
+ output: './i18n', // Changed to root folder
66
+ format: 'json'
67
+ };
68
+
69
+ const content = JSON.stringify(config, null, 2);
70
+ await fs.writeFile('.lingodev.json', content, 'utf-8');
71
+ console.log('📁 Created lingo.dev config with automatic language selection');
72
+ }
73
+
74
+ async runLingoDev(): Promise<void> {
75
+ const { spawn } = await import('child_process');
76
+
77
+ // First, initialize lingo.dev if not already done
78
+ try {
79
+ await fs.access('i18n.json');
80
+ console.log('📁 lingo.dev already initialized');
81
+ } catch {
82
+ console.log('🔧 Initializing lingo.dev...');
83
+ await this.initializeLingoDev();
84
+ }
85
+
86
+ // Then run lingo.dev
87
+ return new Promise((resolve, reject) => {
88
+ const process = spawn('npx', ['lingo.dev@latest', 'run'], {
89
+ stdio: 'inherit',
90
+ shell: true
91
+ });
92
+
93
+ process.on('close', (code) => {
94
+ if (code === 0) {
95
+ console.log('✅ lingo.dev translations completed successfully!');
96
+ resolve();
97
+ } else {
98
+ reject(new Error(`lingo.dev process exited with code ${code}`));
99
+ }
100
+ });
101
+
102
+ process.on('error', reject);
103
+ });
104
+ }
105
+
106
+ private async initializeLingoDev(): Promise<void> {
107
+ const { spawn } = await import('child_process');
108
+
109
+ return new Promise((resolve, reject) => {
110
+ const process = spawn('npx', ['lingo.dev@latest', 'init'], {
111
+ stdio: 'inherit',
112
+ shell: true
113
+ });
114
+
115
+ process.on('close', (code) => {
116
+ if (code === 0) {
117
+ console.log('✅ lingo.dev initialized successfully');
118
+ resolve();
119
+ } else {
120
+ reject(new Error(`lingo.dev init failed with code ${code}`));
121
+ }
122
+ });
123
+
124
+ process.on('error', reject);
125
+ });
126
+ }
127
+ }
@@ -0,0 +1,62 @@
1
+ import { glob } from 'glob';
2
+ import { promises as fs } from 'fs';
3
+ import path from 'path';
4
+
5
+ export interface FileInfo {
6
+ path: string;
7
+ content: string;
8
+ size: number;
9
+ }
10
+
11
+ export class ProjectScanner {
12
+ private readonly extensions = ['.ts', '.tsx', '.js', '.jsx', '.html'];
13
+ private readonly excludePatterns = [
14
+ '**/node_modules/**',
15
+ '**/dist/**',
16
+ '**/build/**',
17
+ '**/.git/**',
18
+ '**/coverage/**'
19
+ ];
20
+
21
+ async scanProject(rootPath: string = process.cwd()): Promise<FileInfo[]> {
22
+ const pattern = `**/*.{${this.extensions.map(ext => ext.slice(1)).join(',')}}`;
23
+
24
+ const files = await glob(pattern, {
25
+ cwd: rootPath,
26
+ ignore: this.excludePatterns,
27
+ absolute: true
28
+ });
29
+
30
+ const fileInfos: FileInfo[] = [];
31
+
32
+ for (const filePath of files) {
33
+ try {
34
+ const content = await fs.readFile(filePath, 'utf-8');
35
+ const stats = await fs.stat(filePath);
36
+
37
+ fileInfos.push({
38
+ path: filePath,
39
+ content,
40
+ size: stats.size
41
+ });
42
+ } catch (error) {
43
+ console.warn(`Warning: Could not read file ${filePath}:`, error);
44
+ }
45
+ }
46
+
47
+ return fileInfos;
48
+ }
49
+
50
+ async getProjectStructure(rootPath: string = process.cwd()): Promise<string[]> {
51
+ const pattern = '**/*';
52
+
53
+ const files = await glob(pattern, {
54
+ cwd: rootPath,
55
+ ignore: this.excludePatterns,
56
+ absolute: false,
57
+ nodir: true
58
+ });
59
+
60
+ return files.sort();
61
+ }
62
+ }