i18nsmith 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/audit.d.ts +3 -0
- package/dist/commands/audit.d.ts.map +1 -0
- package/dist/commands/audit.js +180 -0
- package/dist/commands/audit.js.map +1 -0
- package/dist/commands/backup.d.ts +6 -0
- package/dist/commands/backup.d.ts.map +1 -0
- package/dist/commands/backup.js +85 -0
- package/dist/commands/backup.js.map +1 -0
- package/dist/commands/check.d.ts +3 -0
- package/dist/commands/check.d.ts.map +1 -0
- package/dist/commands/check.js +151 -0
- package/dist/commands/check.js.map +1 -0
- package/dist/commands/config.d.ts +3 -0
- package/dist/commands/config.d.ts.map +1 -0
- package/dist/commands/config.js +235 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/debug-patterns.d.ts +3 -0
- package/dist/commands/debug-patterns.d.ts.map +1 -0
- package/dist/commands/debug-patterns.js +192 -0
- package/dist/commands/debug-patterns.js.map +1 -0
- package/dist/commands/debug-patterns.test.d.ts +2 -0
- package/dist/commands/debug-patterns.test.d.ts.map +1 -0
- package/dist/commands/debug-patterns.test.js +109 -0
- package/dist/commands/debug-patterns.test.js.map +1 -0
- package/dist/commands/diagnose.d.ts +3 -0
- package/dist/commands/diagnose.d.ts.map +1 -0
- package/dist/commands/diagnose.js +117 -0
- package/dist/commands/diagnose.js.map +1 -0
- package/dist/commands/init.d.ts +8 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +450 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/init.test.d.ts +2 -0
- package/dist/commands/init.test.d.ts.map +1 -0
- package/dist/commands/init.test.js +74 -0
- package/dist/commands/init.test.js.map +1 -0
- package/dist/commands/install-hooks.d.ts +3 -0
- package/dist/commands/install-hooks.d.ts.map +1 -0
- package/dist/commands/install-hooks.js +52 -0
- package/dist/commands/install-hooks.js.map +1 -0
- package/dist/commands/preflight.d.ts +7 -0
- package/dist/commands/preflight.d.ts.map +1 -0
- package/dist/commands/preflight.js +417 -0
- package/dist/commands/preflight.js.map +1 -0
- package/dist/commands/preflight.test.d.ts +5 -0
- package/dist/commands/preflight.test.d.ts.map +1 -0
- package/dist/commands/preflight.test.js +108 -0
- package/dist/commands/preflight.test.js.map +1 -0
- package/dist/commands/rename.d.ts +6 -0
- package/dist/commands/rename.d.ts.map +1 -0
- package/dist/commands/rename.js +204 -0
- package/dist/commands/rename.js.map +1 -0
- package/dist/commands/scaffold-adapter.d.ts +3 -0
- package/dist/commands/scaffold-adapter.d.ts.map +1 -0
- package/dist/commands/scaffold-adapter.js +204 -0
- package/dist/commands/scaffold-adapter.js.map +1 -0
- package/dist/commands/scaffold-adapter.test.d.ts +2 -0
- package/dist/commands/scaffold-adapter.test.d.ts.map +1 -0
- package/dist/commands/scaffold-adapter.test.js +102 -0
- package/dist/commands/scaffold-adapter.test.js.map +1 -0
- package/dist/commands/scan.d.ts +3 -0
- package/dist/commands/scan.d.ts.map +1 -0
- package/dist/commands/scan.js +93 -0
- package/dist/commands/scan.js.map +1 -0
- package/dist/commands/sync-seed.test.d.ts +2 -0
- package/dist/commands/sync-seed.test.d.ts.map +1 -0
- package/dist/commands/sync-seed.test.js +86 -0
- package/dist/commands/sync-seed.test.js.map +1 -0
- package/dist/commands/sync.d.ts +3 -0
- package/dist/commands/sync.d.ts.map +1 -0
- package/dist/commands/sync.js +590 -0
- package/dist/commands/sync.js.map +1 -0
- package/dist/commands/transform.d.ts +3 -0
- package/dist/commands/transform.d.ts.map +1 -0
- package/dist/commands/transform.js +114 -0
- package/dist/commands/transform.js.map +1 -0
- package/dist/commands/translate/csv-handler.d.ts +21 -0
- package/dist/commands/translate/csv-handler.d.ts.map +1 -0
- package/dist/commands/translate/csv-handler.js +270 -0
- package/dist/commands/translate/csv-handler.js.map +1 -0
- package/dist/commands/translate/executor.d.ts +31 -0
- package/dist/commands/translate/executor.d.ts.map +1 -0
- package/dist/commands/translate/executor.js +117 -0
- package/dist/commands/translate/executor.js.map +1 -0
- package/dist/commands/translate/index.d.ts +10 -0
- package/dist/commands/translate/index.d.ts.map +1 -0
- package/dist/commands/translate/index.js +170 -0
- package/dist/commands/translate/index.js.map +1 -0
- package/dist/commands/translate/reporter.d.ts +29 -0
- package/dist/commands/translate/reporter.d.ts.map +1 -0
- package/dist/commands/translate/reporter.js +103 -0
- package/dist/commands/translate/reporter.js.map +1 -0
- package/dist/commands/translate/types.d.ts +50 -0
- package/dist/commands/translate/types.d.ts.map +1 -0
- package/dist/commands/translate/types.js +5 -0
- package/dist/commands/translate/types.js.map +1 -0
- package/dist/commands/translate.d.ts +7 -0
- package/dist/commands/translate.d.ts.map +1 -0
- package/dist/commands/translate.js +7 -0
- package/dist/commands/translate.js.map +1 -0
- package/dist/commands/translate.test.d.ts +2 -0
- package/dist/commands/translate.test.d.ts.map +1 -0
- package/dist/commands/translate.test.js +118 -0
- package/dist/commands/translate.test.js.map +1 -0
- package/dist/e2e.test.d.ts +6 -0
- package/dist/e2e.test.d.ts.map +1 -0
- package/dist/e2e.test.js +376 -0
- package/dist/e2e.test.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +39 -0
- package/dist/index.js.map +1 -0
- package/dist/integration.test.d.ts +6 -0
- package/dist/integration.test.d.ts.map +1 -0
- package/dist/integration.test.js +320 -0
- package/dist/integration.test.js.map +1 -0
- package/dist/utils/diagnostics-exit.d.ts +12 -0
- package/dist/utils/diagnostics-exit.d.ts.map +1 -0
- package/dist/utils/diagnostics-exit.js +49 -0
- package/dist/utils/diagnostics-exit.js.map +1 -0
- package/dist/utils/diagnostics-exit.test.d.ts +2 -0
- package/dist/utils/diagnostics-exit.test.d.ts.map +1 -0
- package/dist/utils/diagnostics-exit.test.js +40 -0
- package/dist/utils/diagnostics-exit.test.js.map +1 -0
- package/dist/utils/diff-utils.d.ts +4 -0
- package/dist/utils/diff-utils.d.ts.map +1 -0
- package/dist/utils/diff-utils.js +30 -0
- package/dist/utils/diff-utils.js.map +1 -0
- package/dist/utils/diff-utils.test.d.ts +2 -0
- package/dist/utils/diff-utils.test.d.ts.map +1 -0
- package/dist/utils/diff-utils.test.js +30 -0
- package/dist/utils/diff-utils.test.js.map +1 -0
- package/dist/utils/exit-codes.d.ts +142 -0
- package/dist/utils/exit-codes.d.ts.map +1 -0
- package/dist/utils/exit-codes.js +168 -0
- package/dist/utils/exit-codes.js.map +1 -0
- package/dist/utils/package-manager.d.ts +4 -0
- package/dist/utils/package-manager.d.ts.map +1 -0
- package/dist/utils/package-manager.js +40 -0
- package/dist/utils/package-manager.js.map +1 -0
- package/dist/utils/pkg.d.ts +3 -0
- package/dist/utils/pkg.d.ts.map +1 -0
- package/dist/utils/pkg.js +24 -0
- package/dist/utils/pkg.js.map +1 -0
- package/dist/utils/provider-injector.d.ts +36 -0
- package/dist/utils/provider-injector.d.ts.map +1 -0
- package/dist/utils/provider-injector.js +223 -0
- package/dist/utils/provider-injector.js.map +1 -0
- package/dist/utils/provider-injector.test.d.ts +2 -0
- package/dist/utils/provider-injector.test.d.ts.map +1 -0
- package/dist/utils/provider-injector.test.js +67 -0
- package/dist/utils/provider-injector.test.js.map +1 -0
- package/dist/utils/scaffold.d.ts +20 -0
- package/dist/utils/scaffold.d.ts.map +1 -0
- package/dist/utils/scaffold.js +197 -0
- package/dist/utils/scaffold.js.map +1 -0
- package/package.json +35 -0
- package/src/commands/audit.ts +234 -0
- package/src/commands/backup.ts +96 -0
- package/src/commands/check.ts +191 -0
- package/src/commands/config.ts +263 -0
- package/src/commands/debug-patterns.test.ts +134 -0
- package/src/commands/debug-patterns.ts +257 -0
- package/src/commands/diagnose.ts +136 -0
- package/src/commands/init.test.ts +82 -0
- package/src/commands/init.ts +536 -0
- package/src/commands/install-hooks.ts +66 -0
- package/src/commands/preflight.test.ts +139 -0
- package/src/commands/preflight.ts +488 -0
- package/src/commands/rename.ts +264 -0
- package/src/commands/scaffold-adapter.test.ts +110 -0
- package/src/commands/scaffold-adapter.ts +250 -0
- package/src/commands/scan.ts +125 -0
- package/src/commands/sync-seed.test.ts +116 -0
- package/src/commands/sync.ts +736 -0
- package/src/commands/transform.ts +151 -0
- package/src/commands/translate/README.md +75 -0
- package/src/commands/translate/csv-handler.ts +301 -0
- package/src/commands/translate/executor.ts +188 -0
- package/src/commands/translate/index.ts +220 -0
- package/src/commands/translate/reporter.ts +138 -0
- package/src/commands/translate/types.ts +56 -0
- package/src/commands/translate.test.ts +173 -0
- package/src/commands/translate.ts +6 -0
- package/src/e2e.test.ts +479 -0
- package/src/fixtures/README.md +61 -0
- package/src/fixtures/basic-react/i18n.config.json +15 -0
- package/src/fixtures/basic-react/locales/de.json +8 -0
- package/src/fixtures/basic-react/locales/en.json +8 -0
- package/src/fixtures/basic-react/locales/fr.json +8 -0
- package/src/fixtures/basic-react/src/App.tsx +15 -0
- package/src/fixtures/basic-react/src/Messages.tsx +12 -0
- package/src/fixtures/nested-locales/i18n.config.json +9 -0
- package/src/fixtures/nested-locales/locales/en.json +23 -0
- package/src/fixtures/nested-locales/locales/fr.json +23 -0
- package/src/fixtures/nested-locales/src/HomePage.tsx +13 -0
- package/src/fixtures/suspicious-keys/i18n.config.json +9 -0
- package/src/fixtures/suspicious-keys/locales/en.json +11 -0
- package/src/fixtures/suspicious-keys/locales/fr.json +11 -0
- package/src/fixtures/suspicious-keys/src/BadKeys.tsx +19 -0
- package/src/index.ts +43 -0
- package/src/integration.test.ts +438 -0
- package/src/utils/diagnostics-exit.test.ts +47 -0
- package/src/utils/diagnostics-exit.ts +63 -0
- package/src/utils/diff-utils.test.ts +36 -0
- package/src/utils/diff-utils.ts +42 -0
- package/src/utils/exit-codes.ts +201 -0
- package/src/utils/package-manager.ts +44 -0
- package/src/utils/pkg.ts +23 -0
- package/src/utils/provider-injector.test.ts +79 -0
- package/src/utils/provider-injector.ts +315 -0
- package/src/utils/scaffold.ts +240 -0
- package/tsconfig.json +17 -0
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { Command } from 'commander';
|
|
4
|
+
import fg from 'fast-glob';
|
|
5
|
+
import { loadConfigWithMeta } from '@i18nsmith/core';
|
|
6
|
+
|
|
7
|
+
interface DebugPatternsOptions {
|
|
8
|
+
config?: string;
|
|
9
|
+
json?: boolean;
|
|
10
|
+
verbose?: boolean;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface PatternMatch {
|
|
14
|
+
pattern: string;
|
|
15
|
+
type: 'include' | 'exclude';
|
|
16
|
+
matchedFiles: string[];
|
|
17
|
+
matchCount: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface DebugPatternsSummary {
|
|
21
|
+
projectRoot: string;
|
|
22
|
+
includePatterns: PatternMatch[];
|
|
23
|
+
excludePatterns: PatternMatch[];
|
|
24
|
+
totalIncluded: number;
|
|
25
|
+
totalExcluded: number;
|
|
26
|
+
effectiveFiles: string[];
|
|
27
|
+
unmatchedSuggestions: string[];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function registerDebugPatterns(program: Command) {
|
|
31
|
+
program
|
|
32
|
+
.command('debug-patterns')
|
|
33
|
+
.description('Debug include/exclude glob patterns to understand file matching')
|
|
34
|
+
.option('-c, --config <path>', 'Path to i18nsmith config file', 'i18n.config.json')
|
|
35
|
+
.option('--json', 'Print raw JSON results', false)
|
|
36
|
+
.option('--verbose', 'Show all matched files for each pattern', false)
|
|
37
|
+
.action(async (options: DebugPatternsOptions) => {
|
|
38
|
+
try {
|
|
39
|
+
const { config, projectRoot, configPath } = await loadConfigWithMeta(options.config);
|
|
40
|
+
|
|
41
|
+
console.log(chalk.blue('Debugging glob patterns...'));
|
|
42
|
+
console.log(chalk.gray(`Config: ${path.relative(process.cwd(), configPath)}`));
|
|
43
|
+
console.log(chalk.gray(`Project root: ${projectRoot}\n`));
|
|
44
|
+
|
|
45
|
+
const includePatterns = config.include ?? ['**/*.tsx', '**/*.ts', '**/*.jsx', '**/*.js'];
|
|
46
|
+
const excludePatterns = config.exclude ?? ['**/node_modules/**', '**/dist/**', '**/*.test.*', '**/*.spec.*'];
|
|
47
|
+
|
|
48
|
+
const summary = await analyzePatterns(projectRoot, includePatterns, excludePatterns, options.verbose);
|
|
49
|
+
|
|
50
|
+
if (options.json) {
|
|
51
|
+
console.log(JSON.stringify(summary, null, 2));
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
printPatternAnalysis(summary, options.verbose);
|
|
56
|
+
} catch (error) {
|
|
57
|
+
console.error(chalk.red('Pattern debug failed:'), (error as Error).message);
|
|
58
|
+
process.exitCode = 1;
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async function analyzePatterns(
|
|
64
|
+
projectRoot: string,
|
|
65
|
+
includePatterns: string[],
|
|
66
|
+
excludePatterns: string[],
|
|
67
|
+
verbose?: boolean
|
|
68
|
+
): Promise<DebugPatternsSummary> {
|
|
69
|
+
const includeMatches: PatternMatch[] = [];
|
|
70
|
+
const excludeMatches: PatternMatch[] = [];
|
|
71
|
+
|
|
72
|
+
// Analyze each include pattern individually
|
|
73
|
+
for (const pattern of includePatterns) {
|
|
74
|
+
const files = await fg(pattern, {
|
|
75
|
+
cwd: projectRoot,
|
|
76
|
+
ignore: ['**/node_modules/**'],
|
|
77
|
+
onlyFiles: true,
|
|
78
|
+
absolute: false,
|
|
79
|
+
});
|
|
80
|
+
includeMatches.push({
|
|
81
|
+
pattern,
|
|
82
|
+
type: 'include',
|
|
83
|
+
matchedFiles: files.sort(),
|
|
84
|
+
matchCount: files.length,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Analyze each exclude pattern individually
|
|
89
|
+
for (const pattern of excludePatterns) {
|
|
90
|
+
const files = await fg(pattern, {
|
|
91
|
+
cwd: projectRoot,
|
|
92
|
+
onlyFiles: true,
|
|
93
|
+
absolute: false,
|
|
94
|
+
});
|
|
95
|
+
excludeMatches.push({
|
|
96
|
+
pattern,
|
|
97
|
+
type: 'exclude',
|
|
98
|
+
matchedFiles: files.sort(),
|
|
99
|
+
matchCount: files.length,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Calculate effective files (include - exclude)
|
|
104
|
+
const allIncluded = new Set<string>();
|
|
105
|
+
for (const match of includeMatches) {
|
|
106
|
+
for (const file of match.matchedFiles) {
|
|
107
|
+
allIncluded.add(file);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const allExcluded = new Set<string>();
|
|
112
|
+
for (const match of excludeMatches) {
|
|
113
|
+
for (const file of match.matchedFiles) {
|
|
114
|
+
allExcluded.add(file);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const effectiveFiles = Array.from(allIncluded)
|
|
119
|
+
.filter(file => !allExcluded.has(file))
|
|
120
|
+
.sort();
|
|
121
|
+
|
|
122
|
+
// Generate suggestions for unmatched patterns
|
|
123
|
+
const suggestions = generateSuggestions(includeMatches, excludeMatches, projectRoot);
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
projectRoot,
|
|
127
|
+
includePatterns: includeMatches,
|
|
128
|
+
excludePatterns: excludeMatches,
|
|
129
|
+
totalIncluded: allIncluded.size,
|
|
130
|
+
totalExcluded: allExcluded.size,
|
|
131
|
+
effectiveFiles,
|
|
132
|
+
unmatchedSuggestions: suggestions,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function generateSuggestions(
|
|
137
|
+
includeMatches: PatternMatch[],
|
|
138
|
+
excludeMatches: PatternMatch[],
|
|
139
|
+
projectRoot: string
|
|
140
|
+
): string[] {
|
|
141
|
+
const suggestions: string[] = [];
|
|
142
|
+
|
|
143
|
+
// Check for patterns with no matches
|
|
144
|
+
for (const match of includeMatches) {
|
|
145
|
+
if (match.matchCount === 0) {
|
|
146
|
+
const suggestion = suggestPatternFix(match.pattern, projectRoot);
|
|
147
|
+
if (suggestion) {
|
|
148
|
+
suggestions.push(`Include pattern "${match.pattern}" matched 0 files. ${suggestion}`);
|
|
149
|
+
} else {
|
|
150
|
+
suggestions.push(`Include pattern "${match.pattern}" matched 0 files. Check if files exist or adjust the pattern.`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Check if excludes are too broad
|
|
156
|
+
const totalIncluded = includeMatches.reduce((sum, m) => sum + m.matchCount, 0);
|
|
157
|
+
const totalExcluded = excludeMatches.reduce((sum, m) => sum + m.matchCount, 0);
|
|
158
|
+
|
|
159
|
+
if (totalExcluded > totalIncluded * 0.9 && totalIncluded > 0) {
|
|
160
|
+
suggestions.push('Warning: Exclude patterns are filtering out most files. Consider narrowing exclusions.');
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Check for common pattern mistakes
|
|
164
|
+
for (const match of includeMatches) {
|
|
165
|
+
if (match.pattern.startsWith('/')) {
|
|
166
|
+
suggestions.push(`Pattern "${match.pattern}" starts with "/" which may not match relative paths. Try "${match.pattern.slice(1)}".`);
|
|
167
|
+
}
|
|
168
|
+
if (match.pattern.includes('\\')) {
|
|
169
|
+
suggestions.push(`Pattern "${match.pattern}" contains backslashes. Use forward slashes "/" for glob patterns.`);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return suggestions;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function suggestPatternFix(pattern: string, projectRoot: string): string | null {
|
|
177
|
+
// Common fixes for patterns that don't match
|
|
178
|
+
|
|
179
|
+
// If pattern is like "src/**/*.tsx" but files are in "app/**/*.tsx"
|
|
180
|
+
if (pattern.startsWith('src/')) {
|
|
181
|
+
return 'Try "app/**/*" or check your source directory structure.';
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// If pattern uses .tsx but project has .jsx
|
|
185
|
+
if (pattern.includes('.tsx')) {
|
|
186
|
+
return 'Try replacing ".tsx" with ".jsx" if using JavaScript.';
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// If pattern uses double asterisk incorrectly
|
|
190
|
+
if (pattern.includes('/**') && !pattern.includes('/**/')) {
|
|
191
|
+
return 'Use "**/" for recursive matching (e.g., "src/**/*.tsx").';
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return null;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function printPatternAnalysis(summary: DebugPatternsSummary, verbose?: boolean) {
|
|
198
|
+
console.log(chalk.blue('📁 Include Patterns\n'));
|
|
199
|
+
|
|
200
|
+
for (const match of summary.includePatterns) {
|
|
201
|
+
const status = match.matchCount > 0 ? chalk.green('✓') : chalk.red('✗');
|
|
202
|
+
console.log(` ${status} ${chalk.cyan(match.pattern)} → ${match.matchCount} file(s)`);
|
|
203
|
+
|
|
204
|
+
if (verbose && match.matchCount > 0) {
|
|
205
|
+
const preview = match.matchedFiles.slice(0, 10);
|
|
206
|
+
preview.forEach(file => console.log(chalk.gray(` • ${file}`)));
|
|
207
|
+
if (match.matchedFiles.length > 10) {
|
|
208
|
+
console.log(chalk.gray(` ... and ${match.matchedFiles.length - 10} more`));
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
console.log(chalk.blue('\n🚫 Exclude Patterns\n'));
|
|
214
|
+
|
|
215
|
+
for (const match of summary.excludePatterns) {
|
|
216
|
+
const status = match.matchCount > 0 ? chalk.yellow('⚠') : chalk.gray('○');
|
|
217
|
+
console.log(` ${status} ${chalk.cyan(match.pattern)} → ${match.matchCount} file(s)`);
|
|
218
|
+
|
|
219
|
+
if (verbose && match.matchCount > 0) {
|
|
220
|
+
const preview = match.matchedFiles.slice(0, 5);
|
|
221
|
+
preview.forEach(file => console.log(chalk.gray(` • ${file}`)));
|
|
222
|
+
if (match.matchedFiles.length > 5) {
|
|
223
|
+
console.log(chalk.gray(` ... and ${match.matchedFiles.length - 5} more`));
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
console.log(chalk.blue('\n📊 Summary\n'));
|
|
229
|
+
console.log(` Total matched by include patterns: ${summary.totalIncluded}`);
|
|
230
|
+
console.log(` Total matched by exclude patterns: ${summary.totalExcluded}`);
|
|
231
|
+
console.log(chalk.green(` Effective files to scan: ${summary.effectiveFiles.length}`));
|
|
232
|
+
|
|
233
|
+
if (verbose && summary.effectiveFiles.length > 0) {
|
|
234
|
+
console.log(chalk.blue('\n📄 Effective Files (first 20)\n'));
|
|
235
|
+
summary.effectiveFiles.slice(0, 20).forEach(file => {
|
|
236
|
+
console.log(chalk.gray(` • ${file}`));
|
|
237
|
+
});
|
|
238
|
+
if (summary.effectiveFiles.length > 20) {
|
|
239
|
+
console.log(chalk.gray(` ... and ${summary.effectiveFiles.length - 20} more`));
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (summary.unmatchedSuggestions.length > 0) {
|
|
244
|
+
console.log(chalk.yellow('\n💡 Suggestions\n'));
|
|
245
|
+
summary.unmatchedSuggestions.forEach(suggestion => {
|
|
246
|
+
console.log(chalk.yellow(` • ${suggestion}`));
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if (summary.effectiveFiles.length === 0) {
|
|
251
|
+
console.log(chalk.red('\n⚠️ No files will be scanned! Check your patterns.'));
|
|
252
|
+
console.log(chalk.gray(' Common issues:'));
|
|
253
|
+
console.log(chalk.gray(' • Include patterns don\'t match your source directory'));
|
|
254
|
+
console.log(chalk.gray(' • Exclude patterns are too broad'));
|
|
255
|
+
console.log(chalk.gray(' • File extensions don\'t match (.tsx vs .jsx, .ts vs .js)'));
|
|
256
|
+
}
|
|
257
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import type { Command } from 'commander';
|
|
5
|
+
import { loadConfig, diagnoseWorkspace } from '@i18nsmith/core';
|
|
6
|
+
import type { DiagnosisReport } from '@i18nsmith/core';
|
|
7
|
+
import { getDiagnosisExitSignal } from '../utils/diagnostics-exit.js';
|
|
8
|
+
|
|
9
|
+
interface DiagnoseCommandOptions {
|
|
10
|
+
config?: string;
|
|
11
|
+
json?: boolean;
|
|
12
|
+
report?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function printDiagnosisReport(report: DiagnosisReport) {
|
|
16
|
+
console.log(chalk.green(`Locales directory: ${report.localesDir}`));
|
|
17
|
+
|
|
18
|
+
console.log(chalk.blue('\nLocales'));
|
|
19
|
+
if (report.localeFiles.length === 0) {
|
|
20
|
+
console.log(chalk.yellow(' • No locale files detected.'));
|
|
21
|
+
} else {
|
|
22
|
+
for (const entry of report.localeFiles) {
|
|
23
|
+
const relPath = path.relative(process.cwd(), entry.path);
|
|
24
|
+
const status = entry.missing
|
|
25
|
+
? chalk.red('missing')
|
|
26
|
+
: entry.parseError
|
|
27
|
+
? chalk.red('invalid JSON')
|
|
28
|
+
: `${entry.keyCount} keys`;
|
|
29
|
+
console.log(` • ${entry.locale} — ${status}${entry.missing ? '' : ` (${relPath})`}`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
console.log(chalk.blue('\nRuntime packages'));
|
|
34
|
+
if (report.runtimePackages.length === 0) {
|
|
35
|
+
console.log(chalk.yellow(' • None detected in package.json.'));
|
|
36
|
+
} else {
|
|
37
|
+
for (const pkg of report.runtimePackages) {
|
|
38
|
+
console.log(` • ${pkg.name}@${pkg.version ?? 'latest'} (${pkg.source})`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
console.log(chalk.blue('\nProvider candidates'));
|
|
43
|
+
if (report.providerFiles.length === 0) {
|
|
44
|
+
console.log(chalk.gray(' • No provider files discovered.'));
|
|
45
|
+
} else {
|
|
46
|
+
for (const provider of report.providerFiles) {
|
|
47
|
+
const flags: string[] = [];
|
|
48
|
+
if (provider.frameworkHint !== 'unknown') {
|
|
49
|
+
flags.push(provider.frameworkHint);
|
|
50
|
+
}
|
|
51
|
+
if (provider.hasI18nProvider) {
|
|
52
|
+
flags.push('wraps <I18nProvider>');
|
|
53
|
+
}
|
|
54
|
+
if (provider.usesTranslationHook) {
|
|
55
|
+
flags.push('imports translation hook');
|
|
56
|
+
}
|
|
57
|
+
const flagLabel = flags.length ? ` (${flags.join(', ')})` : '';
|
|
58
|
+
console.log(` • ${provider.relativePath}${flagLabel}`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
console.log(chalk.blue('\nTranslation usage'));
|
|
63
|
+
console.log(
|
|
64
|
+
` • Files scanned: ${report.translationUsage.filesExamined} — ` +
|
|
65
|
+
`${report.translationUsage.hookOccurrences} ${report.translationUsage.hookName} hooks, ` +
|
|
66
|
+
`${report.translationUsage.identifierOccurrences} ${report.translationUsage.translationIdentifier}() calls`
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
if (report.translationUsage.hookExampleFiles.length) {
|
|
70
|
+
console.log(
|
|
71
|
+
chalk.gray(` Examples: ${report.translationUsage.hookExampleFiles.concat(report.translationUsage.identifierExampleFiles).slice(0, 5).join(', ')}`)
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (report.actionableItems.length) {
|
|
76
|
+
console.log(chalk.blue('\nActionable items'));
|
|
77
|
+
for (const item of report.actionableItems) {
|
|
78
|
+
const label = item.severity === 'error' ? chalk.red('ERROR') : item.severity === 'warn' ? chalk.yellow('WARN') : chalk.cyan('INFO');
|
|
79
|
+
console.log(` • [${label}] ${item.message}`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (report.recommendations.length) {
|
|
84
|
+
console.log(chalk.blue('\nRecommendations'));
|
|
85
|
+
for (const rec of report.recommendations) {
|
|
86
|
+
console.log(` • ${rec}`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (report.conflicts.length) {
|
|
91
|
+
console.log(chalk.red('\nConflicts'));
|
|
92
|
+
for (const conflict of report.conflicts) {
|
|
93
|
+
const files = conflict.files?.length ? ` (${conflict.files.join(', ')})` : '';
|
|
94
|
+
console.log(` • ${conflict.message}${files}`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function registerDiagnose(program: Command) {
|
|
100
|
+
program
|
|
101
|
+
.command('diagnose')
|
|
102
|
+
.description('Detect existing i18n assets and potential merge conflicts')
|
|
103
|
+
.option('-c, --config <path>', 'Path to i18nsmith config file', 'i18n.config.json')
|
|
104
|
+
.option('--json', 'Print raw JSON results', false)
|
|
105
|
+
.option('--report <path>', 'Write JSON report to a file (for CI or editors)')
|
|
106
|
+
.action(async (options: DiagnoseCommandOptions) => {
|
|
107
|
+
console.log(chalk.blue('Running repository diagnostics...'));
|
|
108
|
+
try {
|
|
109
|
+
const config = await loadConfig(options.config);
|
|
110
|
+
const report = await diagnoseWorkspace(config);
|
|
111
|
+
|
|
112
|
+
if (options.report) {
|
|
113
|
+
const outputPath = path.resolve(process.cwd(), options.report);
|
|
114
|
+
await fs.mkdir(path.dirname(outputPath), { recursive: true });
|
|
115
|
+
await fs.writeFile(outputPath, JSON.stringify(report, null, 2));
|
|
116
|
+
console.log(chalk.green(`Diagnosis report written to ${outputPath}`));
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (options.json) {
|
|
120
|
+
console.log(JSON.stringify(report, null, 2));
|
|
121
|
+
} else {
|
|
122
|
+
printDiagnosisReport(report);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const exitSignal = getDiagnosisExitSignal(report);
|
|
126
|
+
if (exitSignal) {
|
|
127
|
+
console.error(chalk.red(`\nBlocking conflicts detected (${report.conflicts.length}).`));
|
|
128
|
+
console.error(chalk.red(`Exit code ${exitSignal.code}: ${exitSignal.reason}`));
|
|
129
|
+
process.exitCode = exitSignal.code;
|
|
130
|
+
}
|
|
131
|
+
} catch (error) {
|
|
132
|
+
console.error(chalk.red('Diagnose failed:'), (error as Error).message);
|
|
133
|
+
process.exitCode = 1;
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import { registerInit, parseGlobList } from './init.js';
|
|
4
|
+
|
|
5
|
+
vi.mock('@i18nsmith/core', () => ({
|
|
6
|
+
diagnoseWorkspace: vi.fn().mockResolvedValue({
|
|
7
|
+
localesDir: 'locales',
|
|
8
|
+
localeFiles: [],
|
|
9
|
+
detectedLocales: [],
|
|
10
|
+
runtimePackages: [],
|
|
11
|
+
providerFiles: [],
|
|
12
|
+
adapterFiles: [],
|
|
13
|
+
translationUsage: {
|
|
14
|
+
hookName: 'useTranslation',
|
|
15
|
+
translationIdentifier: 't',
|
|
16
|
+
filesExamined: 0,
|
|
17
|
+
hookOccurrences: 0,
|
|
18
|
+
identifierOccurrences: 0,
|
|
19
|
+
hookExampleFiles: [],
|
|
20
|
+
identifierExampleFiles: [],
|
|
21
|
+
},
|
|
22
|
+
actionableItems: [],
|
|
23
|
+
conflicts: [],
|
|
24
|
+
recommendations: [],
|
|
25
|
+
}),
|
|
26
|
+
}));
|
|
27
|
+
|
|
28
|
+
vi.mock('inquirer', () => ({
|
|
29
|
+
default: {
|
|
30
|
+
prompt: vi.fn().mockResolvedValue({
|
|
31
|
+
sourceLanguage: 'en',
|
|
32
|
+
adapter: 'custom',
|
|
33
|
+
localesDir: 'locales',
|
|
34
|
+
}),
|
|
35
|
+
},
|
|
36
|
+
}));
|
|
37
|
+
|
|
38
|
+
vi.mock('fs/promises', () => ({
|
|
39
|
+
default: {
|
|
40
|
+
writeFile: vi.fn().mockResolvedValue(undefined),
|
|
41
|
+
mkdir: vi.fn().mockResolvedValue(undefined),
|
|
42
|
+
},
|
|
43
|
+
}));
|
|
44
|
+
|
|
45
|
+
describe('init command', () => {
|
|
46
|
+
it('should register the init command', () => {
|
|
47
|
+
const program = new Command();
|
|
48
|
+
registerInit(program);
|
|
49
|
+
const command = program.commands.find((cmd) => cmd.name() === 'init');
|
|
50
|
+
expect(command).toBeDefined();
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
describe('parseGlobList', () => {
|
|
55
|
+
it('treats brace-expanded globs as atomic tokens', () => {
|
|
56
|
+
const input = 'src/**/*.{ts,tsx,js,jsx}, app/**/*.{ts,tsx}';
|
|
57
|
+
expect(parseGlobList(input)).toEqual([
|
|
58
|
+
'src/**/*.{ts,tsx,js,jsx}',
|
|
59
|
+
'app/**/*.{ts,tsx}',
|
|
60
|
+
]);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('handles nested braces', () => {
|
|
64
|
+
const input = 'src/**/*.{ts,tsx,{spec,test}.ts}';
|
|
65
|
+
expect(parseGlobList(input)).toEqual(['src/**/*.{ts,tsx,{spec,test}.ts}']);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('splits simple comma-separated values', () => {
|
|
69
|
+
const input = 'en, fr, es';
|
|
70
|
+
expect(parseGlobList(input)).toEqual(['en', 'fr', 'es']);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('handles empty input', () => {
|
|
74
|
+
expect(parseGlobList('')).toEqual([]);
|
|
75
|
+
expect(parseGlobList(' ')).toEqual([]);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('trims whitespace around entries', () => {
|
|
79
|
+
const input = ' src/**/* , app/**/* ';
|
|
80
|
+
expect(parseGlobList(input)).toEqual(['src/**/*', 'app/**/*']);
|
|
81
|
+
});
|
|
82
|
+
});
|