i18ntk 1.4.1 → 1.4.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.
@@ -0,0 +1,476 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * i18nTK Debugger
5
+ * Main debugging script for identifying and fixing issues in the i18n toolkit
6
+ */
7
+
8
+ const fs = require('fs');
9
+ const path = require('path');
10
+ const { execSync } = require('child_process');
11
+ const SecurityUtils = require('../../utils/security');
12
+
13
+ class I18nDebugger {
14
+ constructor(projectRoot = null) {
15
+ // Validate and sanitize project root path
16
+ let defaultRoot;
17
+
18
+ if (projectRoot) {
19
+ defaultRoot = projectRoot;
20
+ } else {
21
+ // Find the actual project root by walking up from current directory
22
+ defaultRoot = this.findProjectRoot();
23
+ }
24
+
25
+ const validatedRoot = SecurityUtils.validatePath(defaultRoot, process.cwd());
26
+ if (!validatedRoot) {
27
+ throw new Error('Invalid project root path provided');
28
+ }
29
+
30
+ this.projectRoot = validatedRoot;
31
+ this.issues = [];
32
+ this.warnings = [];
33
+ this.logFile = path.join(__dirname, 'logs', `debug-${new Date().toISOString().replace(/[:.]/g, '-')}.log`);
34
+
35
+ // Ensure logs directory exists
36
+ const logsDir = path.dirname(this.logFile);
37
+ if (!fs.existsSync(logsDir)) {
38
+ fs.mkdirSync(logsDir, { recursive: true });
39
+ }
40
+ }
41
+
42
+ log(message, level = 'INFO') {
43
+ const timestamp = new Date().toISOString();
44
+ const logMessage = `[${timestamp}] [${level}] ${message}`;
45
+ console.log(logMessage);
46
+ fs.appendFileSync(this.logFile, logMessage + '\n');
47
+ }
48
+
49
+ findProjectRoot() {
50
+ // Start from the current working directory and walk up to find project root
51
+ let currentDir = process.cwd();
52
+
53
+ // First, check if we're running from within node_modules/i18ntk
54
+ const nodeModulesPattern = /[\/\\]node_modules[\/\\]i18ntk/;
55
+ if (nodeModulesPattern.test(currentDir)) {
56
+ // We're running from the i18ntk package, find the actual project root
57
+ let projectDir = currentDir;
58
+ while (projectDir !== path.dirname(projectDir)) {
59
+ if (fs.existsSync(path.join(projectDir, 'package.json')) &&
60
+ !projectDir.includes('node_modules')) {
61
+ return projectDir;
62
+ }
63
+ projectDir = path.dirname(projectDir);
64
+ }
65
+ }
66
+
67
+ // Walk up the directory tree to find package.json
68
+ while (currentDir !== path.dirname(currentDir)) {
69
+ const packageJsonPath = path.join(currentDir, 'package.json');
70
+
71
+ if (fs.existsSync(packageJsonPath)) {
72
+ try {
73
+ const packageContent = fs.readFileSync(packageJsonPath, 'utf8');
74
+ const packageData = JSON.parse(packageContent);
75
+
76
+ // Skip if this is the i18ntk package itself
77
+ if (packageData.name === 'i18ntk' && currentDir.includes('node_modules')) {
78
+ currentDir = path.dirname(currentDir);
79
+ continue;
80
+ }
81
+
82
+ // Found a valid project package.json
83
+ return currentDir;
84
+ } catch (error) {
85
+ // Invalid package.json, continue searching
86
+ }
87
+ }
88
+
89
+ currentDir = path.dirname(currentDir);
90
+ }
91
+
92
+ // Fallback to the original behavior if no project root found
93
+ return path.resolve(__dirname, '../..');
94
+ }
95
+
96
+ addIssue(issue) {
97
+ this.issues.push(issue);
98
+ this.log(`ISSUE: ${issue}`, 'ERROR');
99
+ }
100
+
101
+ addWarning(warning) {
102
+ this.warnings.push(warning);
103
+ this.log(`WARNING: ${warning}`, 'WARN');
104
+ }
105
+
106
+ checkFileExists(filePath, description) {
107
+ try {
108
+ // Validate path before checking existence
109
+ const validatedPath = SecurityUtils.validatePath(filePath, this.projectRoot);
110
+ if (!validatedPath) {
111
+ this.addIssue(`Invalid file path: ${filePath}`);
112
+ return false;
113
+ }
114
+
115
+ const fullPath = path.resolve(this.projectRoot, filePath);
116
+ if (!fs.existsSync(fullPath)) {
117
+ this.addIssue(`Missing file: ${filePath} (${description})`);
118
+ return false;
119
+ }
120
+ this.log(`āœ“ Found: ${filePath}`);
121
+ return true;
122
+ } catch (error) {
123
+ this.addIssue(`Error checking file existence: ${filePath} - ${error.message}`);
124
+ SecurityUtils.logSecurityEvent('File existence check failed', 'warn', { filePath, error: error.message });
125
+ return false;
126
+ }
127
+ }
128
+
129
+ checkOldNamingConventions() {
130
+ this.log('Checking for old naming conventions...');
131
+ const oldFiles = [
132
+ '00-manage-i18n.js',
133
+ '01-init-i18n.js',
134
+ '02-analyze-translations.js',
135
+ '03-validate-translations.js',
136
+ '04-check-usage.js',
137
+ '05-complete-translations.js',
138
+ '06-analyze-sizing.js',
139
+ '07-generate-summary.js'
140
+ ];
141
+
142
+ oldFiles.forEach(file => {
143
+ const fullPath = path.resolve(this.projectRoot, file);
144
+ if (fs.existsSync(fullPath)) {
145
+ this.addIssue(`Old naming convention file still exists: ${file}`);
146
+ }
147
+ });
148
+
149
+ // Check for references to old files in code
150
+ this.checkForOldReferences();
151
+ }
152
+
153
+ async checkForOldReferences() {
154
+ this.log('Checking for old file references in code...');
155
+ const filesToCheck = [
156
+ 'i18ntk-complete.js',
157
+ 'i18ntk-usage.js',
158
+ 'i18ntk-validate.js',
159
+ 'package.json'
160
+ ];
161
+
162
+ const oldReferences = [
163
+ '04-check-usage.js',
164
+ '00-manage-i18n.js',
165
+ '01-init-i18n.js',
166
+ '02-analyze-translations.js',
167
+ '03-validate-translations.js',
168
+ '05-complete-translations.js',
169
+ '06-analyze-sizing.js',
170
+ '07-generate-summary.js'
171
+ ];
172
+
173
+ for (const file of filesToCheck) {
174
+ const fullPath = path.resolve(this.projectRoot, file);
175
+ if (fs.existsSync(fullPath)) {
176
+ try {
177
+ const content = await SecurityUtils.safeReadFile(fullPath, this.projectRoot);
178
+ if (content) {
179
+ oldReferences.forEach(oldRef => {
180
+ if (content.includes(oldRef)) {
181
+ this.addIssue(`Old reference '${oldRef}' found in ${file}`);
182
+ }
183
+ });
184
+ }
185
+ } catch (error) {
186
+ this.addIssue(`Error reading file ${fullPath}: ${error.message}`);
187
+ SecurityUtils.logSecurityEvent('File read failed during reference check', 'error', { filePath: fullPath, error: error.message });
188
+ }
189
+ }
190
+ }
191
+ }
192
+
193
+ async checkTranslationKeys() {
194
+ this.log('Checking for missing translation keys...');
195
+ const uiLocalesDir = path.resolve(this.projectRoot, 'ui-locales');
196
+
197
+ if (!fs.existsSync(uiLocalesDir)) {
198
+ this.addIssue('ui-locales directory not found');
199
+ return;
200
+ }
201
+
202
+ const languages = ['en', 'de', 'es', 'fr', 'ja', 'ru', 'zh'];
203
+ const availableLanguages = [];
204
+
205
+ for (const lang of languages) {
206
+ const langDir = path.join(uiLocalesDir, lang);
207
+ const commonFile = path.join(langDir, 'common.json');
208
+ if (fs.existsSync(commonFile)) {
209
+ availableLanguages.push(lang);
210
+ }
211
+ }
212
+
213
+ if (availableLanguages.length === 0) {
214
+ this.addIssue('No locale files found in ui-locales directory');
215
+ return;
216
+ }
217
+
218
+ // Load English as reference
219
+ const enPath = path.join(uiLocalesDir, 'en', 'common.json');
220
+ if (!fs.existsSync(enPath)) {
221
+ this.addIssue('English locale file (en/common.json) not found');
222
+ return;
223
+ }
224
+
225
+ try {
226
+ const enContent = await SecurityUtils.safeReadFile(enPath, this.projectRoot);
227
+ if (!enContent) {
228
+ this.addIssue('Failed to read en.json file');
229
+ return;
230
+ }
231
+
232
+ const enLocale = SecurityUtils.safeParseJSON(enContent);
233
+ if (!enLocale) {
234
+ this.addIssue('Failed to parse en.json file');
235
+ return;
236
+ }
237
+
238
+ const requiredKeys = this.extractAllKeys(enLocale);
239
+
240
+ for (const language of availableLanguages) {
241
+ const filePath = path.join(uiLocalesDir, language, 'common.json');
242
+ try {
243
+ const content = await SecurityUtils.safeReadFile(filePath, this.projectRoot);
244
+ if (!content) {
245
+ this.addIssue(`Failed to read ${language}/common.json`);
246
+ continue;
247
+ }
248
+
249
+ const locale = SecurityUtils.safeParseJSON(content);
250
+ if (!locale) {
251
+ this.addIssue(`Failed to parse ${language}/common.json`);
252
+ continue;
253
+ }
254
+
255
+ const existingKeys = this.extractAllKeys(locale);
256
+
257
+ const missingKeys = requiredKeys.filter(key => !existingKeys.includes(key));
258
+ if (missingKeys.length > 0) {
259
+ this.addIssue(`Missing translation keys in ${language}/common.json: ${missingKeys.join(', ')}`);
260
+ }
261
+ } catch (error) {
262
+ this.addIssue(`Error processing ${language}/common.json: ${error.message}`);
263
+ SecurityUtils.logSecurityEvent('Translation file processing failed', 'error', { file: `${language}/common.json`, error: error.message });
264
+ }
265
+ }
266
+ } catch (error) {
267
+ this.addIssue(`Error processing en.json: ${error.message}`);
268
+ SecurityUtils.logSecurityEvent('English translation file processing failed', 'error', { error: error.message });
269
+ }
270
+ }
271
+
272
+ extractAllKeys(obj, prefix = '') {
273
+ let keys = [];
274
+ for (const key in obj) {
275
+ const fullKey = prefix ? `${prefix}.${key}` : key;
276
+ if (typeof obj[key] === 'object' && obj[key] !== null && !Array.isArray(obj[key])) {
277
+ keys = keys.concat(this.extractAllKeys(obj[key], fullKey));
278
+ } else {
279
+ keys.push(fullKey);
280
+ }
281
+ }
282
+ return keys;
283
+ }
284
+
285
+ async checkUserConfig() {
286
+ this.log('Checking user configuration...');
287
+
288
+ // Check i18ntk-config.json
289
+ const configPath = path.resolve(this.projectRoot, 'settings', 'i18ntk-config.json');
290
+ if (this.checkFileExists('settings/i18ntk-config.json', 'Main configuration file')) {
291
+ try {
292
+ const content = fs.readFileSync(configPath, 'utf8');
293
+ const config = JSON.parse(content);
294
+
295
+ this.log('Configuration file found and valid');
296
+
297
+ // Check directory paths
298
+ if (config.directories) {
299
+ const dirs = ['sourceDir', 'outputDir', 'uiLocalesDir'];
300
+ dirs.forEach(dir => {
301
+ if (config.directories[dir]) {
302
+ const dirPath = path.resolve(this.projectRoot, config.directories[dir]);
303
+ if (!fs.existsSync(dirPath)) {
304
+ this.addWarning(`Configured directory does not exist: ${config.directories[dir]}`);
305
+ }
306
+ }
307
+ });
308
+ }
309
+ } catch (error) {
310
+ this.addIssue(`Error processing i18ntk-config.json: ${error.message}`);
311
+ SecurityUtils.logSecurityEvent('User config processing failed', 'error', { error: error.message });
312
+ }
313
+ }
314
+ }
315
+
316
+ checkPackageJson() {
317
+ this.log('Checking package configuration...');
318
+ this.checkFileExists('package.json', 'Package configuration');
319
+ }
320
+
321
+ checkCoreFiles() {
322
+ this.log('Checking core i18nTK files...');
323
+ const coreFiles = [
324
+ 'main/i18ntk-manage.js',
325
+ 'main/i18ntk-init.js',
326
+ 'main/i18ntk-analyze.js',
327
+ 'main/i18ntk-validate.js',
328
+ 'main/i18ntk-usage.js',
329
+ 'main/i18ntk-complete.js',
330
+ 'main/i18ntk-sizing.js',
331
+ 'main/i18ntk-summary.js'
332
+ ];
333
+
334
+ coreFiles.forEach(file => {
335
+ this.checkFileExists(file, 'Core i18nTK script');
336
+ });
337
+ }
338
+
339
+ checkDependencies() {
340
+ this.log('Checking dependencies...');
341
+ try {
342
+ const packageJson = JSON.parse(fs.readFileSync(path.resolve(this.projectRoot, 'package.json'), 'utf8'));
343
+ const dependencies = { ...packageJson.dependencies, ...packageJson.devDependencies };
344
+
345
+ // Check if node_modules exists in project root
346
+ const nodeModulesPath = path.resolve(this.projectRoot, 'node_modules');
347
+ if (!fs.existsSync(nodeModulesPath)) {
348
+ // Also check parent directories for node_modules (monorepo support)
349
+ let currentDir = this.projectRoot;
350
+ let foundNodeModules = false;
351
+
352
+ while (currentDir !== path.dirname(currentDir)) {
353
+ const parentNodeModules = path.join(currentDir, 'node_modules');
354
+ if (fs.existsSync(parentNodeModules)) {
355
+ this.log(`Found node_modules in: ${parentNodeModules}`);
356
+ foundNodeModules = true;
357
+ break;
358
+ }
359
+ currentDir = path.dirname(currentDir);
360
+ }
361
+
362
+ if (!foundNodeModules) {
363
+ const depCount = Object.keys(dependencies).length;
364
+ if (depCount > 0) {
365
+ this.addWarning(`node_modules directory not found in project or parent directories. Run 'npm install' in ${this.projectRoot}`);
366
+ } else {
367
+ this.log('No dependencies found in package.json');
368
+ }
369
+ }
370
+ } else {
371
+ this.log(`Found node_modules in: ${nodeModulesPath}`);
372
+ }
373
+
374
+ this.log(`Found ${Object.keys(dependencies).length} dependencies`);
375
+ } catch (error) {
376
+ this.addIssue(`Could not check dependencies: ${error.message}`);
377
+ }
378
+ }
379
+
380
+ async generateReport() {
381
+ const timestamp = new Date().toLocaleString();
382
+ let summary = '';
383
+
384
+ summary += '\n' + '='.repeat(60) + '\n';
385
+ summary += ' i18nTK DEBUG REPORT\n';
386
+ summary += '='.repeat(60) + '\n';
387
+ summary += `Generated: ${timestamp}\n`;
388
+ summary += `Project Root: ${this.projectRoot}\n`;
389
+ summary += '-'.repeat(60) + '\n';
390
+ summary += `šŸ“Š Summary: ${this.issues.length} issue(s), ${this.warnings.length} warning(s)\n`;
391
+ summary += '-'.repeat(60) + '\n';
392
+
393
+ if (this.issues.length > 0) {
394
+ summary += '\n🚨 CRITICAL ISSUES:\n';
395
+ this.issues.forEach((issue, index) => {
396
+ summary += ` ${index + 1}. āŒ ${issue}\n`;
397
+ });
398
+ }
399
+
400
+ if (this.warnings.length > 0) {
401
+ summary += '\nāš ļø WARNINGS:\n';
402
+ this.warnings.forEach((warning, index) => {
403
+ summary += ` ${index + 1}. āš ļø ${warning}\n`;
404
+ });
405
+ }
406
+
407
+ if (this.issues.length === 0 && this.warnings.length === 0) {
408
+ summary += '\nāœ… EXCELLENT! No issues found.\n';
409
+ summary += ' The i18nTK project appears to be healthy.\n';
410
+ }
411
+
412
+ summary += '\n' + '='.repeat(60) + '\n';
413
+ summary += `šŸ“„ Full debug log: ${this.logFile}\n`;
414
+ summary += '='.repeat(60) + '\n';
415
+
416
+ console.log(summary);
417
+
418
+ // Save report to file securely
419
+ const reportPath = path.join(path.dirname(this.logFile), 'debug-report.txt');
420
+ const success = await SecurityUtils.safeWriteFile(reportPath, summary, this.projectRoot);
421
+ if (!success) {
422
+ console.warn('Warning: Could not save debug report due to security restrictions');
423
+ SecurityUtils.logSecurityEvent('Debug report save failed', 'warn', { reportPath });
424
+ }
425
+
426
+ return {
427
+ issues: this.issues,
428
+ warnings: this.warnings,
429
+ summary: summary,
430
+ reportPath: reportPath,
431
+ logFile: this.logFile
432
+ };
433
+ }
434
+
435
+ async run() {
436
+ this.log('Starting i18nTK Debug Analysis...');
437
+ this.log(`Project Root: ${this.projectRoot}`);
438
+ this.log(`Working Directory: ${process.cwd()}`);
439
+ this.log(`Debug Tool Directory: ${__dirname}`);
440
+
441
+ SecurityUtils.logSecurityEvent('Debug analysis started', 'info', { projectRoot: this.projectRoot });
442
+
443
+ try {
444
+ this.checkCoreFiles();
445
+ await this.checkUserConfig();
446
+ this.checkPackageJson();
447
+ this.checkOldNamingConventions();
448
+ await this.checkTranslationKeys();
449
+ this.checkDependencies();
450
+
451
+ SecurityUtils.logSecurityEvent('Debug analysis completed', 'info', {
452
+ issuesFound: this.issues.length,
453
+ warningsFound: this.warnings.length
454
+ });
455
+
456
+ return await this.generateReport();
457
+ } catch (error) {
458
+ this.addIssue(`Debug analysis failed: ${error.message}`);
459
+ SecurityUtils.logSecurityEvent('Debug analysis failed', 'error', { error: error.message });
460
+ return await this.generateReport();
461
+ }
462
+ }
463
+ }
464
+
465
+ // Run debugger if called directly
466
+ if (require.main === module) {
467
+ const debugTool = new I18nDebugger();
468
+ debugTool.run().then(result => {
469
+ process.exit(result.issues.length > 0 ? 1 : 0);
470
+ }).catch(error => {
471
+ console.error('Debugger failed:', error);
472
+ process.exit(1);
473
+ });
474
+ }
475
+
476
+ module.exports = I18nDebugger;