i18ntk 1.10.2 → 2.0.2

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 (108) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +141 -1191
  3. package/main/i18ntk-analyze.js +65 -84
  4. package/main/i18ntk-backup-class.js +420 -0
  5. package/main/i18ntk-backup.js +3 -3
  6. package/main/i18ntk-complete.js +90 -65
  7. package/main/i18ntk-doctor.js +123 -103
  8. package/main/i18ntk-fixer.js +61 -725
  9. package/main/i18ntk-go.js +14 -15
  10. package/main/i18ntk-init.js +77 -26
  11. package/main/i18ntk-java.js +27 -32
  12. package/main/i18ntk-js.js +70 -68
  13. package/main/i18ntk-manage.js +129 -30
  14. package/main/i18ntk-php.js +75 -75
  15. package/main/i18ntk-py.js +55 -56
  16. package/main/i18ntk-scanner.js +59 -57
  17. package/main/i18ntk-setup.js +9 -404
  18. package/main/i18ntk-sizing.js +6 -6
  19. package/main/i18ntk-summary.js +21 -18
  20. package/main/i18ntk-ui.js +11 -10
  21. package/main/i18ntk-usage.js +54 -18
  22. package/main/i18ntk-validate.js +13 -13
  23. package/main/manage/commands/AnalyzeCommand.js +1124 -0
  24. package/main/manage/commands/BackupCommand.js +62 -0
  25. package/main/manage/commands/CommandRouter.js +295 -0
  26. package/main/manage/commands/CompleteCommand.js +61 -0
  27. package/main/manage/commands/DoctorCommand.js +60 -0
  28. package/main/manage/commands/FixerCommand.js +624 -0
  29. package/main/manage/commands/InitCommand.js +62 -0
  30. package/main/manage/commands/ScannerCommand.js +654 -0
  31. package/main/manage/commands/SizingCommand.js +60 -0
  32. package/main/manage/commands/SummaryCommand.js +61 -0
  33. package/main/manage/commands/UsageCommand.js +60 -0
  34. package/main/manage/commands/ValidateCommand.js +978 -0
  35. package/main/manage/index-fixed.js +1447 -0
  36. package/main/manage/index.js +1462 -0
  37. package/main/manage/managers/DebugMenu.js +140 -0
  38. package/main/manage/managers/InteractiveMenu.js +177 -0
  39. package/main/manage/managers/LanguageMenu.js +62 -0
  40. package/main/manage/managers/SettingsMenu.js +53 -0
  41. package/main/manage/services/AuthenticationService.js +263 -0
  42. package/main/manage/services/ConfigurationService-fixed.js +449 -0
  43. package/main/manage/services/ConfigurationService.js +449 -0
  44. package/main/manage/services/FileManagementService.js +368 -0
  45. package/main/manage/services/FrameworkDetectionService.js +458 -0
  46. package/main/manage/services/InitService.js +1051 -0
  47. package/main/manage/services/SetupService.js +462 -0
  48. package/main/manage/services/SummaryService.js +450 -0
  49. package/main/manage/services/UsageService.js +1502 -0
  50. package/package.json +32 -29
  51. package/runtime/enhanced.d.ts +221 -221
  52. package/runtime/index.d.ts +29 -29
  53. package/runtime/index.full.d.ts +331 -331
  54. package/runtime/index.js +7 -6
  55. package/scripts/build-lite.js +17 -17
  56. package/scripts/deprecate-versions.js +23 -6
  57. package/scripts/export-translations.js +5 -5
  58. package/scripts/fix-all-i18n.js +3 -3
  59. package/scripts/fix-and-purify-i18n.js +3 -2
  60. package/scripts/fix-locale-control-chars.js +110 -0
  61. package/scripts/lint-locales.js +80 -0
  62. package/scripts/locale-optimizer.js +8 -8
  63. package/scripts/prepublish.js +21 -21
  64. package/scripts/security-check.js +117 -117
  65. package/scripts/sync-translations.js +4 -4
  66. package/scripts/sync-ui-locales.js +9 -8
  67. package/scripts/validate-all-translations.js +8 -7
  68. package/scripts/verify-deprecations.js +157 -161
  69. package/scripts/verify-translations.js +6 -5
  70. package/settings/i18ntk-config.json +282 -282
  71. package/settings/language-config.json +5 -5
  72. package/settings/settings-cli.js +9 -9
  73. package/settings/settings-manager.js +18 -18
  74. package/ui-locales/de.json +2417 -2348
  75. package/ui-locales/en.json +2415 -2352
  76. package/ui-locales/es.json +2425 -2353
  77. package/ui-locales/fr.json +2418 -2348
  78. package/ui-locales/ja.json +2463 -2361
  79. package/ui-locales/ru.json +2463 -2359
  80. package/ui-locales/zh.json +2418 -2351
  81. package/utils/admin-auth.js +2 -2
  82. package/utils/admin-cli.js +297 -297
  83. package/utils/admin-pin.js +9 -9
  84. package/utils/cli-helper.js +9 -9
  85. package/utils/config-helper.js +73 -104
  86. package/utils/config-manager.js +204 -171
  87. package/utils/config.js +5 -4
  88. package/utils/env-manager.js +249 -263
  89. package/utils/framework-detector.js +27 -24
  90. package/utils/i18n-helper.js +85 -41
  91. package/utils/init-helper.js +152 -94
  92. package/utils/json-output.js +98 -98
  93. package/utils/mini-commander.js +179 -0
  94. package/utils/missing-key-validator.js +5 -5
  95. package/utils/plugin-loader.js +40 -29
  96. package/utils/prompt.js +14 -44
  97. package/utils/safe-json.js +40 -0
  98. package/utils/secure-errors.js +3 -3
  99. package/utils/security-check-improved.js +390 -0
  100. package/utils/security-config.js +5 -5
  101. package/utils/security-fixed.js +607 -0
  102. package/utils/security.js +652 -602
  103. package/utils/setup-enforcer.js +136 -44
  104. package/utils/setup-validator.js +33 -32
  105. package/utils/ultra-performance-optimizer.js +11 -9
  106. package/utils/watch-locales.js +2 -1
  107. package/utils/prompt-fixed.js +0 -55
  108. package/utils/security-check.js +0 -454
@@ -0,0 +1,458 @@
1
+ /**
2
+ * Framework Detection Service
3
+ * Handles framework detection, i18n directory detection, and framework suggestions
4
+ * @module services/FrameworkDetectionService
5
+ */
6
+
7
+ const path = require('path');
8
+ const fs = require('fs');
9
+ const SecurityUtils = require('../../../utils/security');
10
+
11
+ module.exports = class FrameworkDetectionService {
12
+ constructor(config = {}) {
13
+ this.config = config;
14
+ this.settings = null;
15
+ this.configManager = null;
16
+ }
17
+
18
+ /**
19
+ * Initialize the service with required dependencies
20
+ * @param {Object} configManager - Configuration manager instance
21
+ */
22
+ initialize(configManager) {
23
+ this.configManager = configManager;
24
+ this.settings = configManager.loadSettings ? configManager.loadSettings() : (configManager.getConfig ? configManager.getConfig() : {});
25
+ }
26
+
27
+ /**
28
+ * Detect environment and framework from project structure
29
+ * @returns {Promise<Object>} Object with detectedLanguage and detectedFramework
30
+ */
31
+ async detectEnvironmentAndFramework() {
32
+ // Defensive check to ensure SecurityUtils is available
33
+ if (!SecurityUtils) {
34
+ throw new Error('SecurityUtils is not available. This may indicate a module loading issue.');
35
+ }
36
+
37
+ const packageJsonPath = path.join(process.cwd(), 'package.json');
38
+ const pyprojectPath = path.join(process.cwd(), 'pyproject.toml');
39
+ const requirementsPath = path.join(process.cwd(), 'requirements.txt');
40
+ const goModPath = path.join(process.cwd(), 'go.mod');
41
+ const pomPath = path.join(process.cwd(), 'pom.xml');
42
+ const composerPath = path.join(process.cwd(), 'composer.json');
43
+
44
+ let detectedLanguage = 'generic';
45
+ let detectedFramework = 'generic';
46
+
47
+ if (SecurityUtils.safeExistsSync(packageJsonPath)) {
48
+ detectedLanguage = 'javascript';
49
+ try {
50
+ const packageJson = JSON.parse(SecurityUtils.safeReadFileSync(packageJsonPath, path.dirname(packageJsonPath), 'utf8'));
51
+ const deps = {
52
+ ...(packageJson.dependencies || {}),
53
+ ...(packageJson.devDependencies || {}),
54
+ ...(packageJson.peerDependencies || {})
55
+ };
56
+
57
+ // Check for i18ntk-runtime first (check both package names)
58
+ const hasI18nTkRuntime = deps['i18ntk-runtime'] || deps['i18ntk/runtime'];
59
+
60
+ // Check for common i18n patterns in source code if not found in package.json
61
+ if (!hasI18nTkRuntime) {
62
+ const i18nPatterns = [
63
+ /i18n\.t\(['\"`]/,
64
+ /useI18n\(/,
65
+ /from ['\"]i18ntk[\/\\]runtime['\"]/,
66
+ /require\(['\"]i18ntk[\/\\]runtime['\"]\)/
67
+ ];
68
+
69
+ const sourceFiles = await this.customGlob(['src/**/*.{js,jsx,ts,tsx}'], {
70
+ cwd: process.cwd(),
71
+ ignore: ['**/node_modules/**', '**/dist/**', '**/build/**']
72
+ });
73
+
74
+ for (const file of sourceFiles) {
75
+ try {
76
+ const content = await fs.promises.readFile(path.join(process.cwd(), file), 'utf8');
77
+ if (i18nPatterns.some(pattern => pattern.test(content))) {
78
+ detectedFramework = 'i18ntk-runtime';
79
+ break;
80
+ }
81
+ } catch (e) {
82
+ // Skip files we can't read
83
+ continue;
84
+ }
85
+ }
86
+ } else {
87
+ detectedFramework = 'i18ntk-runtime';
88
+ }
89
+
90
+ // Only check other frameworks if i18ntk-runtime wasn't detected
91
+ if (detectedFramework !== 'i18ntk-runtime') {
92
+ if (deps.react || deps['react-dom']) detectedFramework = 'react';
93
+ else if (deps.vue || deps['vue-router']) detectedFramework = 'vue';
94
+ else if (deps['@angular/core']) detectedFramework = 'angular';
95
+ else if (deps.next) detectedFramework = 'nextjs';
96
+ else if (deps.nuxt) detectedFramework = 'nuxt';
97
+ else if (deps.svelte) detectedFramework = 'svelte';
98
+ else detectedFramework = 'generic';
99
+ }
100
+ } catch (error) {
101
+ detectedFramework = 'generic';
102
+ }
103
+ } else if (SecurityUtils.safeExistsSync(pyprojectPath) || SecurityUtils.safeExistsSync(requirementsPath)) {
104
+ detectedLanguage = 'python';
105
+ try {
106
+ if (SecurityUtils.safeExistsSync(requirementsPath)) {
107
+ const requirements = SecurityUtils.safeReadFileSync(requirementsPath, path.dirname(requirementsPath), 'utf8');
108
+ if (requirements.includes('django')) detectedFramework = 'django';
109
+ else if (requirements.includes('flask')) detectedFramework = 'flask';
110
+ else if (requirements.includes('fastapi')) detectedFramework = 'fastapi';
111
+ else detectedFramework = 'generic';
112
+ }
113
+ } catch (error) {
114
+ detectedFramework = 'generic';
115
+ }
116
+ } else if (SecurityUtils.safeExistsSync(goModPath)) {
117
+ detectedLanguage = 'go';
118
+ detectedFramework = 'generic';
119
+ } else if (SecurityUtils.safeExistsSync(pomPath)) {
120
+ detectedLanguage = 'java';
121
+ try {
122
+ const pomContent = SecurityUtils.safeReadFileSync(pomPath, path.dirname(pomPath), 'utf8');
123
+ if (pomContent.includes('spring-boot')) detectedFramework = 'spring-boot';
124
+ else if (pomContent.includes('spring')) detectedFramework = 'spring';
125
+ else if (pomContent.includes('quarkus')) detectedFramework = 'quarkus';
126
+ else detectedFramework = 'generic';
127
+ } catch (error) {
128
+ detectedFramework = 'generic';
129
+ }
130
+ } else if (SecurityUtils.safeExistsSync(composerPath)) {
131
+ detectedLanguage = 'php';
132
+ try {
133
+ const composer = JSON.parse(SecurityUtils.safeReadFileSync(composerPath, path.dirname(composerPath), 'utf8'));
134
+ const deps = composer.require || {};
135
+
136
+ if (deps['laravel/framework']) detectedFramework = 'laravel';
137
+ else if (deps['symfony/framework-bundle']) detectedFramework = 'symfony';
138
+ else if (deps['wordpress']) detectedFramework = 'wordpress';
139
+ else detectedFramework = 'generic';
140
+ } catch (error) {
141
+ detectedFramework = 'generic';
142
+ }
143
+ }
144
+
145
+ return { detectedLanguage, detectedFramework };
146
+ }
147
+
148
+ /**
149
+ * Get framework suggestions for a given language
150
+ * @param {string} language - The programming language
151
+ * @returns {Array} Array of framework suggestions with name and description
152
+ */
153
+ getFrameworkSuggestions(language) {
154
+ const suggestions = {
155
+ javascript: [
156
+ { name: 'i18next', description: 'Feature-rich i18n framework for JavaScript' },
157
+ { name: 'react-i18next', description: 'React integration for i18next' },
158
+ { name: 'vue-i18n', description: 'Vue.js i18n plugin' },
159
+ { name: 'Angular i18n', description: 'Built-in Angular i18n' }
160
+ ],
161
+ typescript: [
162
+ { name: 'i18next', description: 'TypeScript-first i18n framework' },
163
+ { name: 'react-i18next', description: 'React + TypeScript integration' },
164
+ { name: 'vue-i18n', description: 'Vue.js i18n with TypeScript support' }
165
+ ],
166
+ python: [
167
+ { name: 'Django i18n', description: 'Built-in Django internationalization' },
168
+ { name: 'Flask-Babel', description: 'Babel integration for Flask' },
169
+ { name: 'FastAPI i18n', description: 'i18n middleware for FastAPI' }
170
+ ],
171
+ java: [
172
+ { name: 'Spring i18n', description: 'Spring Framework internationalization' },
173
+ { name: 'Spring Boot i18n', description: 'Spring Boot auto-configuration' },
174
+ { name: 'Quarkus i18n', description: 'Quarkus internationalization support' }
175
+ ],
176
+ go: [
177
+ { name: 'go-i18n', description: 'Go i18n library with pluralization' },
178
+ { name: 'nicksnyder/go-i18n', description: 'Feature-rich Go i18n' }
179
+ ],
180
+ php: [
181
+ { name: 'Laravel i18n', description: 'Built-in Laravel localization' },
182
+ { name: 'Symfony Translation', description: 'Symfony translation component' },
183
+ { name: 'WordPress i18n', description: 'WordPress localization functions' }
184
+ ]
185
+ };
186
+
187
+ return suggestions[language] || suggestions.javascript;
188
+ }
189
+
190
+ /**
191
+ * Handle framework detection and prompting logic
192
+ * @param {Object} prompt - Prompt interface for user interaction
193
+ * @param {Object} cfg - Configuration object
194
+ * @param {string} currentVersion - Current version of the tool
195
+ * @returns {Promise<Object>} Updated configuration
196
+ */
197
+ async maybePromptFramework(prompt, cfg, currentVersion) {
198
+ // Load current settings to check framework configuration
199
+ let settings = this.settings || (this.configManager.loadSettings ? this.configManager.loadSettings() : (this.configManager.getConfig ? this.configManager.getConfig() : {}));
200
+
201
+ // Ensure framework configuration exists with all required fields
202
+ if (!settings.framework) {
203
+ settings.framework = {
204
+ detected: false,
205
+ preference: null,
206
+ prompt: 'always',
207
+ lastPromptedVersion: null,
208
+ installed: [],
209
+ version: '1.0' // Schema version for future compatibility
210
+ };
211
+
212
+ // Save the updated settings
213
+ if (this.configManager.saveSettings) {
214
+ await this.configManager.saveSettings(settings);
215
+ } else if (this.configManager.saveConfig) {
216
+ await this.configManager.saveConfig(settings);
217
+ }
218
+ }
219
+
220
+ // Reload settings to ensure we have the latest framework detection results
221
+ const freshSettings = this.configManager.loadSettings ? this.configManager.loadSettings() : (this.configManager.getConfig ? this.configManager.getConfig() : {});
222
+ if (freshSettings.framework) {
223
+ settings.framework = { ...settings.framework, ...freshSettings.framework };
224
+ }
225
+
226
+ // Check if framework is already detected or preference is explicitly set to none
227
+ if (settings.framework.detected || settings.framework.preference === 'none') {
228
+ return cfg;
229
+ }
230
+
231
+ // Check if DNR (Do Not Remind) is active for this version
232
+ if (settings.framework.prompt === 'suppress' && settings.framework.lastPromptedVersion === currentVersion) {
233
+ return cfg;
234
+ }
235
+
236
+ // Reset DNR if version changed
237
+ if (settings.framework.prompt === 'suppress' && settings.framework.lastPromptedVersion !== currentVersion) {
238
+ settings.framework.prompt = 'always';
239
+ settings.framework.lastPromptedVersion = null;
240
+
241
+ // Save the updated settings
242
+ if (this.configManager.saveSettings) {
243
+ await this.configManager.saveSettings(settings);
244
+ } else if (this.configManager.saveConfig) {
245
+ await this.configManager.saveConfig(settings);
246
+ }
247
+ }
248
+
249
+ // This function is now handled by ensureInitializedOrExit for better flow control
250
+ return cfg;
251
+ }
252
+
253
+ /**
254
+ * Auto-detect i18n directory from common locations only if not configured in settings
255
+ * @returns {Object} Updated configuration with detected i18n directory
256
+ */
257
+ detectI18nDirectory() {
258
+ const settings = this.settings || (this.configManager.loadSettings ? this.configManager.loadSettings() : (this.configManager.getConfig ? this.configManager.getConfig() : {}));
259
+ const projectRoot = path.resolve(settings.projectRoot || this.config.projectRoot || '.');
260
+ const fs = require('fs');
261
+
262
+ // Use per-script directory configuration if available, fallback to global sourceDir
263
+ const sourceDir = settings.scriptDirectories?.manage || settings.sourceDir;
264
+
265
+ if (sourceDir) {
266
+ this.config.sourceDir = path.resolve(projectRoot, sourceDir);
267
+ return;
268
+ }
269
+
270
+ // Define possible i18n paths for auto-detection
271
+ const possibleI18nPaths = [
272
+ './locales',
273
+ './src/locales',
274
+ './src/i18n',
275
+ './src/i18n/locales',
276
+ './app/locales',
277
+ './app/i18n',
278
+ './public/locales',
279
+ './assets/locales',
280
+ './translations',
281
+ './lang'
282
+ ];
283
+
284
+ // Only auto-detect if no settings are configured
285
+ for (const possiblePath of possibleI18nPaths) {
286
+ const resolvedPath = path.resolve(projectRoot, possiblePath);
287
+ if (SecurityUtils.safeExistsSync(resolvedPath)) {
288
+ // Check if it contains language directories
289
+ try {
290
+ const items = fs.readdirSync(resolvedPath);
291
+ const hasLanguageDirs = items.some(item => {
292
+ const itemPath = path.join(resolvedPath, item);
293
+ return fs.statSync(itemPath).isDirectory() &&
294
+ ['en', 'de', 'es', 'fr', 'ru', 'ja', 'zh'].includes(item);
295
+ });
296
+
297
+ if (hasLanguageDirs) {
298
+ this.config.sourceDir = possiblePath;
299
+ // Note: Translation function would need to be injected
300
+ // t('init.autoDetectedI18nDirectory', { path: possiblePath });
301
+ break;
302
+ }
303
+ } catch (error) {
304
+ // Continue checking other paths
305
+ }
306
+ }
307
+ }
308
+ }
309
+
310
+ /**
311
+ * Check if i18n framework is installed - configuration-based check without prompts
312
+ * @param {Object} ui - UI instance for translations (optional)
313
+ * @returns {Promise<boolean>} True if frameworks detected, false otherwise
314
+ */
315
+ async checkI18nDependencies(ui = null) {
316
+ const packageJsonPath = path.resolve('./package.json');
317
+
318
+ if (!SecurityUtils.safeExistsSync(packageJsonPath)) {
319
+ if (ui && ui.t) {
320
+ console.log(ui.t('errors.noPackageJson'));
321
+ } else {
322
+ console.log('No package.json found');
323
+ }
324
+ return false; // Treat as no framework detected
325
+ }
326
+
327
+ try {
328
+ const packageJson = JSON.parse(SecurityUtils.safeReadFileSync(packageJsonPath, path.dirname(packageJsonPath), 'utf8'));
329
+ // Include peerDependencies in the check
330
+ const dependencies = {
331
+ ...packageJson.dependencies,
332
+ ...packageJson.devDependencies,
333
+ ...packageJson.peerDependencies
334
+ };
335
+
336
+ const i18nFrameworks = [
337
+ 'react-i18next',
338
+ 'vue-i18n',
339
+ 'angular-i18n',
340
+ 'i18next',
341
+ 'next-i18next',
342
+ 'svelte-i18n',
343
+ '@nuxtjs/i18n',
344
+ 'i18ntk-runtime'
345
+ ];
346
+
347
+ const installedFrameworks = i18nFrameworks.filter(framework => dependencies[framework]);
348
+
349
+ if (installedFrameworks.length > 0) {
350
+ if (ui && ui.t) {
351
+ console.log(ui.t('init.detectedFrameworks', { frameworks: installedFrameworks.join(', ') }));
352
+ } else {
353
+ console.log(`Detected frameworks: ${installedFrameworks.join(', ')}`);
354
+ }
355
+ const cfg = this.settings || (this.configManager.loadSettings ? this.configManager.loadSettings() : (this.configManager.getConfig ? this.configManager.getConfig() : {}));
356
+ cfg.framework = cfg.framework || {};
357
+ cfg.framework.detected = true;
358
+ cfg.framework.installed = installedFrameworks;
359
+ if (this.configManager.saveSettings) {
360
+ this.configManager.saveSettings(cfg);
361
+ } else if (this.configManager.saveConfig) {
362
+ this.configManager.saveConfig(cfg);
363
+ }
364
+ return true;
365
+ } else {
366
+ const cfg = this.settings || (this.configManager.loadSettings ? this.configManager.loadSettings() : (this.configManager.getConfig ? this.configManager.getConfig() : {}));
367
+ if (cfg.framework) {
368
+ cfg.framework.detected = false;
369
+ if (this.configManager.saveSettings) {
370
+ this.configManager.saveSettings(cfg);
371
+ } else if (this.configManager.saveConfig) {
372
+ this.configManager.saveConfig(cfg);
373
+ }
374
+ }
375
+ // Always signal that frameworks were not detected
376
+ return false;
377
+ }
378
+ } catch (error) {
379
+ // Note: Translation function would need to be injected
380
+ // console.log(t('init.errors.packageJsonRead'));
381
+ console.log('Error reading package.json');
382
+ return false; // Treat as no framework detected on error
383
+ }
384
+ }
385
+
386
+ /**
387
+ * Custom glob implementation using Node.js built-in modules (zero dependencies)
388
+ * @param {string[]} patterns - Array of glob patterns
389
+ * @param {Object} options - Options object with cwd and ignore properties
390
+ * @returns {Promise<string[]>} Array of matching file paths
391
+ */
392
+ async customGlob(patterns, options = {}) {
393
+ const cwd = options.cwd || process.cwd();
394
+ const ignorePatterns = options.ignore || [];
395
+
396
+ function matchesPattern(filename, pattern) {
397
+ // Simple pattern matching for **/*.{js,jsx,ts,tsx} style patterns
398
+ if (pattern.includes('**/*')) {
399
+ const extensionPart = pattern.split('*.')[1];
400
+ if (extensionPart) {
401
+ const extensions = extensionPart.replace('{', '').replace('}', '').split(',');
402
+ return extensions.some(ext => filename.endsWith('.' + ext.trim()));
403
+ }
404
+ }
405
+ return filename.includes(pattern.replace('**/', ''));
406
+ }
407
+
408
+ function shouldIgnore(filePath) {
409
+ return ignorePatterns.some(pattern => {
410
+ if (pattern.includes('**/')) {
411
+ const patternEnd = pattern.replace('**/', '');
412
+ return filePath.includes('/' + patternEnd) || filePath.includes('\\' + patternEnd);
413
+ }
414
+ return filePath.includes(pattern);
415
+ });
416
+ }
417
+
418
+ function findFiles(dir, results = []) {
419
+ try {
420
+ const items = fs.readdirSync(dir);
421
+
422
+ for (const item of items) {
423
+ const fullPath = path.join(dir, item);
424
+ const relativePath = path.relative(cwd, fullPath);
425
+
426
+ if (shouldIgnore(relativePath)) {
427
+ continue;
428
+ }
429
+
430
+ try {
431
+ const stat = fs.statSync(fullPath);
432
+
433
+ if (stat.isDirectory()) {
434
+ findFiles(fullPath, results);
435
+ } else if (stat.isFile()) {
436
+ // Check if file matches any of our patterns
437
+ for (const pattern of patterns) {
438
+ if (matchesPattern(item, pattern)) {
439
+ results.push(relativePath);
440
+ break;
441
+ }
442
+ }
443
+ }
444
+ } catch (error) {
445
+ // Skip files we can't access
446
+ continue;
447
+ }
448
+ }
449
+ } catch (error) {
450
+ // Skip directories we can't access
451
+ }
452
+
453
+ return results;
454
+ }
455
+
456
+ return findFiles(cwd);
457
+ }
458
+ };