i18nsmith 0.2.0 → 0.3.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.map +1 -1
- package/dist/commands/backup.d.ts.map +1 -1
- package/dist/commands/check.d.ts +25 -0
- package/dist/commands/check.d.ts.map +1 -1
- package/dist/commands/config.d.ts.map +1 -1
- package/dist/commands/debug-patterns.d.ts.map +1 -1
- package/dist/commands/diagnose.d.ts.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/install-hooks.d.ts.map +1 -1
- package/dist/commands/preflight.d.ts.map +1 -1
- package/dist/commands/rename.d.ts.map +1 -1
- package/dist/commands/review.d.ts.map +1 -1
- package/dist/commands/scaffold-adapter.d.ts.map +1 -1
- package/dist/commands/scan.d.ts.map +1 -1
- package/dist/commands/sync.d.ts +1 -1
- package/dist/commands/sync.d.ts.map +1 -1
- package/dist/commands/transform.d.ts.map +1 -1
- package/dist/commands/translate/index.d.ts.map +1 -1
- package/dist/index.js +2574 -107704
- package/dist/rename-suspicious.test.d.ts +2 -0
- package/dist/rename-suspicious.test.d.ts.map +1 -0
- package/dist/utils/diff-utils.d.ts +5 -0
- package/dist/utils/diff-utils.d.ts.map +1 -1
- package/dist/utils/errors.d.ts +8 -0
- package/dist/utils/errors.d.ts.map +1 -0
- package/dist/utils/locale-audit.d.ts +39 -0
- package/dist/utils/locale-audit.d.ts.map +1 -0
- package/dist/utils/preview.d.ts.map +1 -1
- package/dist/utils/preview.test.d.ts +2 -0
- package/dist/utils/preview.test.d.ts.map +1 -0
- package/package.json +5 -5
- package/src/commands/audit.ts +18 -209
- package/src/commands/backup.ts +67 -63
- package/src/commands/check.ts +119 -68
- package/src/commands/config.ts +117 -95
- package/src/commands/debug-patterns.ts +25 -22
- package/src/commands/diagnose.ts +29 -26
- package/src/commands/init.ts +84 -79
- package/src/commands/install-hooks.ts +18 -15
- package/src/commands/preflight.ts +21 -13
- package/src/commands/rename.ts +86 -81
- package/src/commands/review.ts +81 -78
- package/src/commands/scaffold-adapter.ts +8 -4
- package/src/commands/scan.ts +61 -58
- package/src/commands/sync.ts +640 -203
- package/src/commands/transform.ts +117 -8
- package/src/commands/translate/index.ts +7 -4
- package/src/e2e.test.ts +78 -14
- package/src/integration.test.ts +86 -0
- package/src/rename-suspicious.test.ts +124 -0
- package/src/utils/diff-utils.ts +6 -0
- package/src/utils/errors.ts +34 -0
- package/src/utils/locale-audit.ts +219 -0
- package/src/utils/preview.test.ts +137 -0
- package/src/utils/preview.ts +2 -8
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rename-suspicious.test.d.ts","sourceRoot":"","sources":["../src/rename-suspicious.test.ts"],"names":[],"mappings":""}
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI presentation layer for diff utilities.
|
|
3
|
+
* This module handles printing and writing locale diffs for CLI commands.
|
|
4
|
+
* Core diff building logic is in packages/core/src/diff-utils.ts.
|
|
5
|
+
*/
|
|
1
6
|
import type { SyncSummary } from '@i18nsmith/core';
|
|
2
7
|
export declare function printLocaleDiffs(diffs: SyncSummary['diffs']): void;
|
|
3
8
|
export declare function writeLocaleDiffPatches(diffs: SyncSummary['diffs'], directory: string): Promise<void>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"diff-utils.d.ts","sourceRoot":"","sources":["../../src/utils/diff-utils.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"diff-utils.d.ts","sourceRoot":"","sources":["../../src/utils/diff-utils.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAEnD,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,WAAW,CAAC,OAAO,CAAC,QAW3D;AAED,wBAAsB,sBAAsB,CAAC,KAAK,EAAE,WAAW,CAAC,OAAO,CAAC,EAAE,SAAS,EAAE,MAAM,iBAuB1F"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare class CliError extends Error {
|
|
2
|
+
exitCode: number;
|
|
3
|
+
constructor(message: string, exitCode?: number);
|
|
4
|
+
}
|
|
5
|
+
type MaybePromise<T> = T | Promise<T>;
|
|
6
|
+
export declare function withErrorHandling<A extends unknown[], R>(action: (...args: A) => MaybePromise<R>): (...args: A) => Promise<R | undefined>;
|
|
7
|
+
export {};
|
|
8
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/utils/errors.ts"],"names":[],"mappings":"AAEA,qBAAa,QAAS,SAAQ,KAAK;IACG,QAAQ;gBAAhC,OAAO,EAAE,MAAM,EAAS,QAAQ,SAAI;CAIjD;AAED,KAAK,YAAY,CAAC,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;AAEtC,wBAAgB,iBAAiB,CAAC,CAAC,SAAS,OAAO,EAAE,EAAE,CAAC,EACtD,MAAM,EAAE,CAAC,GAAG,IAAI,EAAE,CAAC,KAAK,YAAY,CAAC,CAAC,CAAC,GACtC,CAAC,GAAG,IAAI,EAAE,CAAC,KAAK,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC,CAoBxC"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { type I18nConfig } from '@i18nsmith/core';
|
|
2
|
+
export interface AuditIssue {
|
|
3
|
+
key: string;
|
|
4
|
+
value: string;
|
|
5
|
+
reason: string;
|
|
6
|
+
description: string;
|
|
7
|
+
suggestion?: string;
|
|
8
|
+
}
|
|
9
|
+
export interface QualityIssue {
|
|
10
|
+
type: 'duplicate-value' | 'inconsistent-key' | 'orphaned-namespace';
|
|
11
|
+
description: string;
|
|
12
|
+
keys?: string[];
|
|
13
|
+
suggestion?: string;
|
|
14
|
+
}
|
|
15
|
+
export interface LocaleAuditResult {
|
|
16
|
+
locale: string;
|
|
17
|
+
totalKeys: number;
|
|
18
|
+
issues: AuditIssue[];
|
|
19
|
+
qualityIssues: QualityIssue[];
|
|
20
|
+
}
|
|
21
|
+
export interface LocaleAuditSummary {
|
|
22
|
+
results: LocaleAuditResult[];
|
|
23
|
+
totalIssues: number;
|
|
24
|
+
totalQualityIssues: number;
|
|
25
|
+
}
|
|
26
|
+
export interface LocaleAuditContext {
|
|
27
|
+
config: I18nConfig;
|
|
28
|
+
projectRoot: string;
|
|
29
|
+
}
|
|
30
|
+
export interface LocaleAuditOptions {
|
|
31
|
+
locales?: string[];
|
|
32
|
+
checkDuplicates?: boolean;
|
|
33
|
+
checkInconsistent?: boolean;
|
|
34
|
+
checkOrphaned?: boolean;
|
|
35
|
+
}
|
|
36
|
+
export declare function runLocaleAudit(context: LocaleAuditContext, options?: LocaleAuditOptions): Promise<LocaleAuditSummary>;
|
|
37
|
+
export declare function printLocaleAuditResults(summary: LocaleAuditSummary): void;
|
|
38
|
+
export declare function hasAuditFindings(summary: LocaleAuditSummary): boolean;
|
|
39
|
+
//# sourceMappingURL=locale-audit.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"locale-audit.d.ts","sourceRoot":"","sources":["../../src/utils/locale-audit.ts"],"names":[],"mappings":"AAEA,OAAO,EAKL,KAAK,UAAU,EAChB,MAAM,iBAAiB,CAAC;AAEzB,MAAM,WAAW,UAAU;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,iBAAiB,GAAG,kBAAkB,GAAG,oBAAoB,CAAC;IACpE,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,UAAU,EAAE,CAAC;IACrB,aAAa,EAAE,YAAY,EAAE,CAAC;CAC/B;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,iBAAiB,EAAE,CAAC;IAC7B,WAAW,EAAE,MAAM,CAAC;IACpB,kBAAkB,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,UAAU,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,wBAAsB,cAAc,CAClC,OAAO,EAAE,kBAAkB,EAC3B,OAAO,GAAE,kBAAuB,GAC/B,OAAO,CAAC,kBAAkB,CAAC,CAkH7B;AAED,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,kBAAkB,QA6ClE;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAErE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"preview.d.ts","sourceRoot":"","sources":["../../src/utils/preview.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,cAAc,CAAC,QAAQ;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,QAAQ,CAAC;CACnB;AAED,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,WAAW,GAAG,YAAY,GAAG,WAAW,CAAC;AAE5E,wBAAsB,gBAAgB,CAAC,QAAQ,EAC7C,IAAI,EAAE,WAAW,EACjB,OAAO,EAAE,QAAQ,EACjB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,MAAM,CAAC,CAcjB;AAED,wBAAsB,eAAe,CAAC,QAAQ,EAC5C,YAAY,EAAE,WAAW,EACzB,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,cAAc,CAAC,QAAQ,CAAC,CAAC,CAWnC;AAED,wBAAsB,gBAAgB,CACpC,YAAY,EAAE,WAAW,EACzB,WAAW,EAAE,MAAM,EACnB,SAAS,GAAE,MAAM,EAAO,GACvB,OAAO,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"preview.d.ts","sourceRoot":"","sources":["../../src/utils/preview.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,cAAc,CAAC,QAAQ;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,QAAQ,CAAC;CACnB;AAED,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,WAAW,GAAG,YAAY,GAAG,WAAW,CAAC;AAE5E,wBAAsB,gBAAgB,CAAC,QAAQ,EAC7C,IAAI,EAAE,WAAW,EACjB,OAAO,EAAE,QAAQ,EACjB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,MAAM,CAAC,CAcjB;AAED,wBAAsB,eAAe,CAAC,QAAQ,EAC5C,YAAY,EAAE,WAAW,EACzB,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,cAAc,CAAC,QAAQ,CAAC,CAAC,CAWnC;AAED,wBAAsB,gBAAgB,CACpC,YAAY,EAAE,WAAW,EACzB,WAAW,EAAE,MAAM,EACnB,SAAS,GAAE,MAAM,EAAO,GACvB,OAAO,CAAC,IAAI,CAAC,CAiBf"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"preview.test.d.ts","sourceRoot":"","sources":["../../src/utils/preview.test.ts"],"names":[],"mappings":""}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "i18nsmith",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "CLI for i18nsmith",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -21,6 +21,10 @@
|
|
|
21
21
|
"test": "vitest run"
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
|
+
"@i18nsmith/core": "workspace:*",
|
|
25
|
+
"@i18nsmith/transformer": "workspace:*",
|
|
26
|
+
"@i18nsmith/translation": "workspace:*",
|
|
27
|
+
"@i18nsmith/translator-mock": "workspace:*",
|
|
24
28
|
"chalk": "^5.0.0",
|
|
25
29
|
"commander": "^11.0.0",
|
|
26
30
|
"diff": "^8.0.2",
|
|
@@ -31,10 +35,6 @@
|
|
|
31
35
|
"ts-morph": "^21.0.0"
|
|
32
36
|
},
|
|
33
37
|
"devDependencies": {
|
|
34
|
-
"@i18nsmith/core": "workspace:*",
|
|
35
|
-
"@i18nsmith/transformer": "workspace:*",
|
|
36
|
-
"@i18nsmith/translation": "workspace:*",
|
|
37
|
-
"@i18nsmith/translator-mock": "workspace:*",
|
|
38
38
|
"esbuild": "^0.25.0"
|
|
39
39
|
}
|
|
40
40
|
}
|
package/src/commands/audit.ts
CHANGED
|
@@ -1,14 +1,7 @@
|
|
|
1
|
-
import fs from 'fs/promises';
|
|
2
|
-
import path from 'path';
|
|
3
1
|
import chalk from 'chalk';
|
|
4
2
|
import type { Command } from 'commander';
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
LocaleStore,
|
|
8
|
-
KeyValidator,
|
|
9
|
-
LocaleValidator,
|
|
10
|
-
SUSPICIOUS_KEY_REASON_DESCRIPTIONS,
|
|
11
|
-
} from '@i18nsmith/core';
|
|
3
|
+
import { runCheck } from './check.js';
|
|
4
|
+
import { withErrorHandling } from '../utils/errors.js';
|
|
12
5
|
|
|
13
6
|
interface AuditCommandOptions {
|
|
14
7
|
config?: string;
|
|
@@ -21,28 +14,6 @@ interface AuditCommandOptions {
|
|
|
21
14
|
orphaned?: boolean;
|
|
22
15
|
}
|
|
23
16
|
|
|
24
|
-
interface AuditIssue {
|
|
25
|
-
key: string;
|
|
26
|
-
value: string;
|
|
27
|
-
reason: string;
|
|
28
|
-
description: string;
|
|
29
|
-
suggestion?: string;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
interface QualityIssue {
|
|
33
|
-
type: 'duplicate-value' | 'inconsistent-key' | 'orphaned-namespace';
|
|
34
|
-
description: string;
|
|
35
|
-
keys?: string[];
|
|
36
|
-
suggestion?: string;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
interface LocaleAuditResult {
|
|
40
|
-
locale: string;
|
|
41
|
-
totalKeys: number;
|
|
42
|
-
issues: AuditIssue[];
|
|
43
|
-
qualityIssues: QualityIssue[];
|
|
44
|
-
}
|
|
45
|
-
|
|
46
17
|
export function registerAudit(program: Command) {
|
|
47
18
|
program
|
|
48
19
|
.command('audit')
|
|
@@ -53,182 +24,20 @@ export function registerAudit(program: Command) {
|
|
|
53
24
|
.option('--report <path>', 'Write JSON report to a file')
|
|
54
25
|
.option('--strict', 'Exit with error code if any issues found', false)
|
|
55
26
|
.option('--duplicates', 'Check for duplicate values (consolidation opportunities)', false)
|
|
56
|
-
.option('--inconsistent', '
|
|
57
|
-
.option('--orphaned', '
|
|
58
|
-
.action(async (options: AuditCommandOptions) => {
|
|
59
|
-
console.log(chalk.
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
let localesToAudit = options.locale ?? [];
|
|
73
|
-
if (localesToAudit.length === 0) {
|
|
74
|
-
localesToAudit = [config.sourceLanguage ?? 'en', ...(config.targetLanguages ?? [])];
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// Enable all quality checks if none specified
|
|
78
|
-
const runQualityChecks = options.duplicates || options.inconsistent || options.orphaned;
|
|
79
|
-
const checkDuplicates = options.duplicates || !runQualityChecks;
|
|
80
|
-
const checkInconsistent = options.inconsistent;
|
|
81
|
-
const checkOrphaned = options.orphaned;
|
|
82
|
-
|
|
83
|
-
const results: LocaleAuditResult[] = [];
|
|
84
|
-
const allKeys = new Set<string>();
|
|
85
|
-
|
|
86
|
-
// First pass: collect all keys
|
|
87
|
-
for (const locale of localesToAudit) {
|
|
88
|
-
const data = await localeStore.get(locale);
|
|
89
|
-
for (const key of Object.keys(data)) {
|
|
90
|
-
allKeys.add(key);
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// Second pass: run audits
|
|
95
|
-
for (const locale of localesToAudit) {
|
|
96
|
-
const data = await localeStore.get(locale);
|
|
97
|
-
const keys = Object.keys(data);
|
|
98
|
-
const issues: AuditIssue[] = [];
|
|
99
|
-
const qualityIssues: QualityIssue[] = [];
|
|
100
|
-
|
|
101
|
-
// Suspicious key detection
|
|
102
|
-
for (const key of keys) {
|
|
103
|
-
const value = data[key];
|
|
104
|
-
const analysis = keyValidator.analyzeWithValue(key, value);
|
|
105
|
-
|
|
106
|
-
if (analysis.suspicious && analysis.reason) {
|
|
107
|
-
issues.push({
|
|
108
|
-
key,
|
|
109
|
-
value: value.length > 50 ? `${value.slice(0, 47)}...` : value,
|
|
110
|
-
reason: analysis.reason,
|
|
111
|
-
description: SUSPICIOUS_KEY_REASON_DESCRIPTIONS[analysis.reason] ?? 'Unknown issue',
|
|
112
|
-
suggestion: keyValidator.suggestFix(key, analysis.reason),
|
|
113
|
-
});
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// Quality checks
|
|
118
|
-
if (checkDuplicates) {
|
|
119
|
-
const duplicates = localeValidator.detectDuplicateValues(locale, data);
|
|
120
|
-
for (const dup of duplicates) {
|
|
121
|
-
qualityIssues.push({
|
|
122
|
-
type: 'duplicate-value',
|
|
123
|
-
description: `Value "${dup.value.slice(0, 40)}${dup.value.length > 40 ? '...' : ''}" used by ${dup.keys.length} keys`,
|
|
124
|
-
keys: dup.keys,
|
|
125
|
-
suggestion: 'Consider consolidating to a single key',
|
|
126
|
-
});
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
if (checkInconsistent && locale === localesToAudit[0]) {
|
|
131
|
-
// Only check inconsistent keys once (using all keys)
|
|
132
|
-
const inconsistent = localeValidator.detectInconsistentKeys(Array.from(allKeys));
|
|
133
|
-
for (const inc of inconsistent) {
|
|
134
|
-
qualityIssues.push({
|
|
135
|
-
type: 'inconsistent-key',
|
|
136
|
-
description: inc.pattern,
|
|
137
|
-
keys: inc.variants,
|
|
138
|
-
suggestion: inc.suggestion,
|
|
139
|
-
});
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
if (checkOrphaned && locale === localesToAudit[0]) {
|
|
144
|
-
// Only check orphaned namespaces once (using all keys)
|
|
145
|
-
const orphaned = localeValidator.detectOrphanedNamespaces(Array.from(allKeys));
|
|
146
|
-
for (const orph of orphaned) {
|
|
147
|
-
qualityIssues.push({
|
|
148
|
-
type: 'orphaned-namespace',
|
|
149
|
-
description: `Namespace "${orph.namespace}" has only ${orph.keyCount} key(s)`,
|
|
150
|
-
keys: orph.keys,
|
|
151
|
-
suggestion: 'Consider merging into a related namespace',
|
|
152
|
-
});
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
results.push({
|
|
157
|
-
locale,
|
|
158
|
-
totalKeys: keys.length,
|
|
159
|
-
issues,
|
|
160
|
-
qualityIssues,
|
|
161
|
-
});
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
const totalIssues = results.reduce((sum, r) => sum + r.issues.length, 0);
|
|
165
|
-
const totalQualityIssues = results.reduce((sum, r) => sum + r.qualityIssues.length, 0);
|
|
166
|
-
|
|
167
|
-
if (options.report) {
|
|
168
|
-
const outputPath = path.resolve(process.cwd(), options.report);
|
|
169
|
-
await fs.mkdir(path.dirname(outputPath), { recursive: true });
|
|
170
|
-
await fs.writeFile(outputPath, JSON.stringify({ results, totalIssues, totalQualityIssues }, null, 2));
|
|
171
|
-
console.log(chalk.green(`Audit report written to ${outputPath}`));
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
if (options.json) {
|
|
175
|
-
console.log(JSON.stringify({ results, totalIssues, totalQualityIssues }, null, 2));
|
|
176
|
-
} else {
|
|
177
|
-
// Pretty print results
|
|
178
|
-
for (const result of results) {
|
|
179
|
-
const hasIssues = result.issues.length > 0 || result.qualityIssues.length > 0;
|
|
180
|
-
if (!hasIssues) {
|
|
181
|
-
console.log(chalk.green(`✓ ${result.locale}.json: ${result.totalKeys} keys, no issues`));
|
|
182
|
-
} else {
|
|
183
|
-
console.log(chalk.yellow(`⚠ ${result.locale}.json: ${result.totalKeys} keys`));
|
|
184
|
-
|
|
185
|
-
// Suspicious keys
|
|
186
|
-
if (result.issues.length > 0) {
|
|
187
|
-
console.log(chalk.yellow(` Suspicious keys: ${result.issues.length}`));
|
|
188
|
-
for (const issue of result.issues) {
|
|
189
|
-
console.log(chalk.dim(` - "${issue.key}"`));
|
|
190
|
-
console.log(chalk.dim(` ${issue.description}`));
|
|
191
|
-
if (issue.suggestion) {
|
|
192
|
-
console.log(chalk.dim(` Suggestion: ${issue.suggestion}`));
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
// Quality issues
|
|
198
|
-
if (result.qualityIssues.length > 0) {
|
|
199
|
-
console.log(chalk.cyan(` Quality checks: ${result.qualityIssues.length}`));
|
|
200
|
-
for (const issue of result.qualityIssues) {
|
|
201
|
-
const typeLabel = issue.type === 'duplicate-value' ? '📋' :
|
|
202
|
-
issue.type === 'inconsistent-key' ? '🔀' : '📦';
|
|
203
|
-
console.log(chalk.dim(` ${typeLabel} ${issue.description}`));
|
|
204
|
-
if (issue.suggestion) {
|
|
205
|
-
console.log(chalk.dim(` ${issue.suggestion}`));
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
console.log();
|
|
213
|
-
if (totalIssues === 0 && totalQualityIssues === 0) {
|
|
214
|
-
console.log(chalk.green('✓ No issues found in locale files'));
|
|
215
|
-
} else {
|
|
216
|
-
if (totalIssues > 0) {
|
|
217
|
-
console.log(chalk.yellow(`Found ${totalIssues} suspicious key(s)`));
|
|
218
|
-
}
|
|
219
|
-
if (totalQualityIssues > 0) {
|
|
220
|
-
console.log(chalk.cyan(`Found ${totalQualityIssues} quality issue(s)`));
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
if (options.strict && totalIssues > 0) {
|
|
226
|
-
console.error(chalk.red(`\nAudit failed with ${totalIssues} issue(s). Use --strict=false to allow.`));
|
|
227
|
-
process.exitCode = 1;
|
|
228
|
-
}
|
|
229
|
-
} catch (error) {
|
|
230
|
-
console.error(chalk.red('Audit failed:'), (error as Error).message);
|
|
231
|
-
process.exitCode = 1;
|
|
232
|
-
}
|
|
233
|
-
});
|
|
27
|
+
.option('--inconsistent', 'Detect inconsistent key naming patterns', false)
|
|
28
|
+
.option('--orphaned', 'Detect orphaned namespaces that only contain a few keys', false)
|
|
29
|
+
.action(withErrorHandling(async (options: AuditCommandOptions) => {
|
|
30
|
+
console.log(chalk.yellow('⚠️ "i18nsmith audit" is deprecated. Proxying to "i18nsmith check --audit"...'));
|
|
31
|
+
await runCheck({
|
|
32
|
+
config: options.config,
|
|
33
|
+
json: options.json,
|
|
34
|
+
report: options.report,
|
|
35
|
+
audit: true,
|
|
36
|
+
auditStrict: options.strict,
|
|
37
|
+
auditLocales: options.locale,
|
|
38
|
+
auditDuplicates: options.duplicates,
|
|
39
|
+
auditInconsistent: options.inconsistent,
|
|
40
|
+
auditOrphaned: options.orphaned,
|
|
41
|
+
});
|
|
42
|
+
}));
|
|
234
43
|
}
|
package/src/commands/backup.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { Command } from 'commander';
|
|
|
2
2
|
import chalk from 'chalk';
|
|
3
3
|
import inquirer from 'inquirer';
|
|
4
4
|
import { listBackups, restoreBackup } from '@i18nsmith/core';
|
|
5
|
+
import { CliError, withErrorHandling } from '../utils/errors.js';
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Registers backup-related commands (backup-list, backup-restore)
|
|
@@ -11,86 +12,89 @@ export function registerBackup(program: Command): void {
|
|
|
11
12
|
.command('backup-list')
|
|
12
13
|
.description('List available locale file backups')
|
|
13
14
|
.option('--backup-dir <path>', 'Custom backup directory (default: .i18nsmith-backup)')
|
|
14
|
-
.action(
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
.action(
|
|
16
|
+
withErrorHandling(async (options: { backupDir?: string }) => {
|
|
17
|
+
try {
|
|
18
|
+
const workspaceRoot = process.cwd();
|
|
19
|
+
const backups = await listBackups(workspaceRoot, { backupDir: options.backupDir });
|
|
18
20
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
if (backups.length === 0) {
|
|
22
|
+
console.log(chalk.yellow('No backups found.'));
|
|
23
|
+
console.log(chalk.gray('Backups are created automatically when using --write --prune'));
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
24
26
|
|
|
25
|
-
|
|
27
|
+
console.log(chalk.blue(`Found ${backups.length} backup(s):\n`));
|
|
26
28
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
for (const backup of backups) {
|
|
30
|
+
const date = new Date(backup.createdAt);
|
|
31
|
+
const formattedDate = date.toLocaleString();
|
|
32
|
+
console.log(` ${chalk.cyan(backup.timestamp)} ${formattedDate} (${backup.fileCount} files)`);
|
|
33
|
+
}
|
|
32
34
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
35
|
+
console.log(chalk.gray(`\nRestore a backup with: i18nsmith backup-restore <timestamp>`));
|
|
36
|
+
} catch (error) {
|
|
37
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
38
|
+
throw new CliError(`Error listing backups: ${message}`);
|
|
39
|
+
}
|
|
40
|
+
})
|
|
41
|
+
);
|
|
39
42
|
|
|
40
43
|
program
|
|
41
44
|
.command('backup-restore')
|
|
42
45
|
.description('Restore locale files from a previous backup')
|
|
43
46
|
.argument('<timestamp>', 'Backup timestamp (from backup-list) or "latest" for most recent')
|
|
44
47
|
.option('--backup-dir <path>', 'Custom backup directory (default: .i18nsmith-backup)')
|
|
45
|
-
.action(
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
48
|
+
.action(
|
|
49
|
+
withErrorHandling(async (timestamp: string, options: { backupDir?: string }) => {
|
|
50
|
+
try {
|
|
51
|
+
const workspaceRoot = process.cwd();
|
|
52
|
+
const backups = await listBackups(workspaceRoot, { backupDir: options.backupDir });
|
|
49
53
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
return;
|
|
54
|
-
}
|
|
54
|
+
if (backups.length === 0) {
|
|
55
|
+
throw new CliError('No backups found.');
|
|
56
|
+
}
|
|
55
57
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
58
|
+
const targetBackup = timestamp === 'latest'
|
|
59
|
+
? backups[0]
|
|
60
|
+
: backups.find((b) => b.timestamp === timestamp);
|
|
59
61
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
62
|
+
if (!targetBackup) {
|
|
63
|
+
const suggestion = backups
|
|
64
|
+
.slice(0, 5)
|
|
65
|
+
.map((b) => b.timestamp)
|
|
66
|
+
.join(', ');
|
|
67
|
+
throw new CliError(
|
|
68
|
+
suggestion
|
|
69
|
+
? `Backup not found: ${timestamp}. Available backups: ${suggestion}`
|
|
70
|
+
: `Backup not found: ${timestamp}`
|
|
71
|
+
);
|
|
65
72
|
}
|
|
66
|
-
process.exitCode = 1;
|
|
67
|
-
return;
|
|
68
|
-
}
|
|
69
73
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
]);
|
|
74
|
+
const { confirmed } = await inquirer.prompt<{ confirmed: boolean }>([
|
|
75
|
+
{
|
|
76
|
+
type: 'confirm',
|
|
77
|
+
name: 'confirmed',
|
|
78
|
+
message: `Restore ${targetBackup.fileCount} locale files from backup ${targetBackup.timestamp}? This will overwrite current locale files.`,
|
|
79
|
+
default: false,
|
|
80
|
+
},
|
|
81
|
+
]);
|
|
79
82
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
83
|
+
if (!confirmed) {
|
|
84
|
+
console.log(chalk.yellow('Restore cancelled.'));
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
84
87
|
|
|
85
|
-
|
|
88
|
+
const result = await restoreBackup(targetBackup.path, workspaceRoot);
|
|
86
89
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
+
console.log(chalk.green(`\n✅ ${result.summary}`));
|
|
91
|
+
for (const file of result.restored) {
|
|
92
|
+
console.log(chalk.gray(` Restored: ${file}`));
|
|
93
|
+
}
|
|
94
|
+
} catch (error) {
|
|
95
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
96
|
+
throw new CliError(`Error restoring backup: ${message}`);
|
|
90
97
|
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
process.exitCode = 1;
|
|
94
|
-
}
|
|
95
|
-
});
|
|
98
|
+
})
|
|
99
|
+
);
|
|
96
100
|
}
|