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,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Exit Code Reference for i18nsmith CLI
|
|
3
|
+
*
|
|
4
|
+
* This module centralizes all exit codes used by the CLI for consistent
|
|
5
|
+
* behavior across commands and improved CI/CD integration.
|
|
6
|
+
*
|
|
7
|
+
* ## Exit Code Ranges
|
|
8
|
+
*
|
|
9
|
+
* | Range | Category | Description |
|
|
10
|
+
* |--------|-----------------------|---------------------------------------|
|
|
11
|
+
* | 0 | Success | Command completed successfully |
|
|
12
|
+
* | 1 | General Error | Unspecified error or crash |
|
|
13
|
+
* | 2-5 | Diagnostics | Workspace diagnostic conflicts |
|
|
14
|
+
* | 10-19 | Check Command | Health check warnings/conflicts |
|
|
15
|
+
* | 1-4 | Sync Command | Sync drift/validation issues |
|
|
16
|
+
*
|
|
17
|
+
* Note: Sync exit codes overlap with diagnostics for backward compatibility.
|
|
18
|
+
* Use `--prefer-diagnostics-exit` in `check` command to disambiguate.
|
|
19
|
+
*
|
|
20
|
+
* ## Usage in CI/CD
|
|
21
|
+
*
|
|
22
|
+
* ```bash
|
|
23
|
+
* # Basic CI check
|
|
24
|
+
* npx i18nsmith check --fail-on conflicts
|
|
25
|
+
* if [ $? -eq 11 ]; then
|
|
26
|
+
* echo "Blocking conflicts found"
|
|
27
|
+
* fi
|
|
28
|
+
*
|
|
29
|
+
* # Strict sync check
|
|
30
|
+
* npx i18nsmith sync --check --strict
|
|
31
|
+
* case $? in
|
|
32
|
+
* 0) echo "All clear" ;;
|
|
33
|
+
* 1) echo "Locale drift detected" ;;
|
|
34
|
+
* 2) echo "Placeholder mismatch" ;;
|
|
35
|
+
* 4) echo "Suspicious keys found" ;;
|
|
36
|
+
* esac
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
/**
|
|
40
|
+
* Exit codes for the `sync` command.
|
|
41
|
+
*
|
|
42
|
+
* Used when running `i18nsmith sync --check` or `--strict` mode.
|
|
43
|
+
*/
|
|
44
|
+
export declare const SYNC_EXIT_CODES: {
|
|
45
|
+
/** Locale drift detected (missing or unused keys) */
|
|
46
|
+
readonly DRIFT: 1;
|
|
47
|
+
/** Placeholder/interpolation mismatch between locales */
|
|
48
|
+
readonly PLACEHOLDER_MISMATCH: 2;
|
|
49
|
+
/** Empty or placeholder values in locale files */
|
|
50
|
+
readonly EMPTY_VALUES: 3;
|
|
51
|
+
/** Suspicious key patterns detected (e.g., key=value, raw text keys) */
|
|
52
|
+
readonly SUSPICIOUS_KEYS: 4;
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* Exit codes for the `check` command.
|
|
56
|
+
*
|
|
57
|
+
* Used when running `i18nsmith check --fail-on <level>`.
|
|
58
|
+
*/
|
|
59
|
+
export declare const CHECK_EXIT_CODES: {
|
|
60
|
+
/** Warnings detected (when --fail-on warnings) */
|
|
61
|
+
readonly WARNINGS: 10;
|
|
62
|
+
/** Blocking conflicts detected (when --fail-on conflicts) */
|
|
63
|
+
readonly CONFLICTS: 11;
|
|
64
|
+
};
|
|
65
|
+
/**
|
|
66
|
+
* Exit codes for the `diagnose` command and diagnostic conflicts.
|
|
67
|
+
*
|
|
68
|
+
* These are also used by `check` when `--prefer-diagnostics-exit` is set.
|
|
69
|
+
*/
|
|
70
|
+
export declare const DIAGNOSTICS_EXIT_CODES: {
|
|
71
|
+
/** Missing source locale file */
|
|
72
|
+
readonly MISSING_SOURCE_LOCALE: 2;
|
|
73
|
+
/** Invalid JSON in locale file */
|
|
74
|
+
readonly INVALID_LOCALE_JSON: 3;
|
|
75
|
+
/** Provider/adapter clash detected */
|
|
76
|
+
readonly UNSAFE_PROVIDER_CLASH: 4;
|
|
77
|
+
/** General diagnostics conflict (fallback) */
|
|
78
|
+
readonly GENERAL_CONFLICT: 5;
|
|
79
|
+
};
|
|
80
|
+
/**
|
|
81
|
+
* General exit codes used across all commands.
|
|
82
|
+
*/
|
|
83
|
+
export declare const GENERAL_EXIT_CODES: {
|
|
84
|
+
/** Success - no issues found */
|
|
85
|
+
readonly SUCCESS: 0;
|
|
86
|
+
/** General error (catch-all for exceptions) */
|
|
87
|
+
readonly ERROR: 1;
|
|
88
|
+
};
|
|
89
|
+
/**
|
|
90
|
+
* Type for all sync exit codes
|
|
91
|
+
*/
|
|
92
|
+
export type SyncExitCode = (typeof SYNC_EXIT_CODES)[keyof typeof SYNC_EXIT_CODES];
|
|
93
|
+
/**
|
|
94
|
+
* Type for all check exit codes
|
|
95
|
+
*/
|
|
96
|
+
export type CheckExitCode = (typeof CHECK_EXIT_CODES)[keyof typeof CHECK_EXIT_CODES];
|
|
97
|
+
/**
|
|
98
|
+
* Type for all diagnostics exit codes
|
|
99
|
+
*/
|
|
100
|
+
export type DiagnosticsExitCode = (typeof DIAGNOSTICS_EXIT_CODES)[keyof typeof DIAGNOSTICS_EXIT_CODES];
|
|
101
|
+
/**
|
|
102
|
+
* Human-readable descriptions for sync exit codes.
|
|
103
|
+
*/
|
|
104
|
+
export declare const SYNC_EXIT_DESCRIPTIONS: Record<number, string>;
|
|
105
|
+
/**
|
|
106
|
+
* Human-readable descriptions for check exit codes.
|
|
107
|
+
*/
|
|
108
|
+
export declare const CHECK_EXIT_DESCRIPTIONS: Record<number, string>;
|
|
109
|
+
/**
|
|
110
|
+
* Human-readable descriptions for diagnostics exit codes.
|
|
111
|
+
*/
|
|
112
|
+
export declare const DIAGNOSTICS_EXIT_DESCRIPTIONS: Record<number, string>;
|
|
113
|
+
/**
|
|
114
|
+
* All exit code descriptions by command context.
|
|
115
|
+
*/
|
|
116
|
+
export declare const EXIT_CODE_DESCRIPTIONS: {
|
|
117
|
+
readonly general: {
|
|
118
|
+
readonly 0: "Success - no issues found";
|
|
119
|
+
readonly 1: "General error";
|
|
120
|
+
};
|
|
121
|
+
readonly sync: Record<number, string>;
|
|
122
|
+
readonly check: Record<number, string>;
|
|
123
|
+
readonly diagnostics: Record<number, string>;
|
|
124
|
+
};
|
|
125
|
+
/**
|
|
126
|
+
* Get a human-readable description for an exit code.
|
|
127
|
+
*
|
|
128
|
+
* @param code - The exit code
|
|
129
|
+
* @param context - Optional context to disambiguate overlapping codes
|
|
130
|
+
* @returns Description string, or 'Unknown exit code' if not recognized
|
|
131
|
+
*/
|
|
132
|
+
export declare function getExitCodeDescription(code: number, context?: 'sync' | 'check' | 'diagnostics'): string;
|
|
133
|
+
/**
|
|
134
|
+
* Helper to set process exit code with optional logging.
|
|
135
|
+
*
|
|
136
|
+
* @param code - The exit code to set
|
|
137
|
+
* @param options - Optional configuration
|
|
138
|
+
*/
|
|
139
|
+
export declare function setExitCode(code: number, options?: {
|
|
140
|
+
silent?: boolean;
|
|
141
|
+
}): void;
|
|
142
|
+
//# sourceMappingURL=exit-codes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"exit-codes.d.ts","sourceRoot":"","sources":["../../src/utils/exit-codes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AAEH;;;;GAIG;AACH,eAAO,MAAM,eAAe;IAC1B,qDAAqD;;IAErD,yDAAyD;;IAEzD,kDAAkD;;IAElD,wEAAwE;;CAEhE,CAAC;AAEX;;;;GAIG;AACH,eAAO,MAAM,gBAAgB;IAC3B,kDAAkD;;IAElD,6DAA6D;;CAErD,CAAC;AAEX;;;;GAIG;AACH,eAAO,MAAM,sBAAsB;IACjC,iCAAiC;;IAEjC,kCAAkC;;IAElC,sCAAsC;;IAEtC,8CAA8C;;CAEtC,CAAC;AAEX;;GAEG;AACH,eAAO,MAAM,kBAAkB;IAC7B,gCAAgC;;IAEhC,+CAA+C;;CAEvC,CAAC;AAEX;;GAEG;AACH,MAAM,MAAM,YAAY,GAAG,CAAC,OAAO,eAAe,CAAC,CAAC,MAAM,OAAO,eAAe,CAAC,CAAC;AAElF;;GAEG;AACH,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,gBAAgB,CAAC,CAAC,MAAM,OAAO,gBAAgB,CAAC,CAAC;AAErF;;GAEG;AACH,MAAM,MAAM,mBAAmB,GAAG,CAAC,OAAO,sBAAsB,CAAC,CAAC,MAAM,OAAO,sBAAsB,CAAC,CAAC;AAEvG;;GAEG;AACH,eAAO,MAAM,sBAAsB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAKzD,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,uBAAuB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAG1D,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,6BAA6B,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAKhE,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,sBAAsB;;;;;;;;CAQzB,CAAC;AAEX;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,aAAa,GACzC,MAAM,CAsBR;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CACzB,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE;IAAE,MAAM,CAAC,EAAE,OAAO,CAAA;CAAE,GAC7B,IAAI,CAQN"}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Exit Code Reference for i18nsmith CLI
|
|
3
|
+
*
|
|
4
|
+
* This module centralizes all exit codes used by the CLI for consistent
|
|
5
|
+
* behavior across commands and improved CI/CD integration.
|
|
6
|
+
*
|
|
7
|
+
* ## Exit Code Ranges
|
|
8
|
+
*
|
|
9
|
+
* | Range | Category | Description |
|
|
10
|
+
* |--------|-----------------------|---------------------------------------|
|
|
11
|
+
* | 0 | Success | Command completed successfully |
|
|
12
|
+
* | 1 | General Error | Unspecified error or crash |
|
|
13
|
+
* | 2-5 | Diagnostics | Workspace diagnostic conflicts |
|
|
14
|
+
* | 10-19 | Check Command | Health check warnings/conflicts |
|
|
15
|
+
* | 1-4 | Sync Command | Sync drift/validation issues |
|
|
16
|
+
*
|
|
17
|
+
* Note: Sync exit codes overlap with diagnostics for backward compatibility.
|
|
18
|
+
* Use `--prefer-diagnostics-exit` in `check` command to disambiguate.
|
|
19
|
+
*
|
|
20
|
+
* ## Usage in CI/CD
|
|
21
|
+
*
|
|
22
|
+
* ```bash
|
|
23
|
+
* # Basic CI check
|
|
24
|
+
* npx i18nsmith check --fail-on conflicts
|
|
25
|
+
* if [ $? -eq 11 ]; then
|
|
26
|
+
* echo "Blocking conflicts found"
|
|
27
|
+
* fi
|
|
28
|
+
*
|
|
29
|
+
* # Strict sync check
|
|
30
|
+
* npx i18nsmith sync --check --strict
|
|
31
|
+
* case $? in
|
|
32
|
+
* 0) echo "All clear" ;;
|
|
33
|
+
* 1) echo "Locale drift detected" ;;
|
|
34
|
+
* 2) echo "Placeholder mismatch" ;;
|
|
35
|
+
* 4) echo "Suspicious keys found" ;;
|
|
36
|
+
* esac
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
/**
|
|
40
|
+
* Exit codes for the `sync` command.
|
|
41
|
+
*
|
|
42
|
+
* Used when running `i18nsmith sync --check` or `--strict` mode.
|
|
43
|
+
*/
|
|
44
|
+
export const SYNC_EXIT_CODES = {
|
|
45
|
+
/** Locale drift detected (missing or unused keys) */
|
|
46
|
+
DRIFT: 1,
|
|
47
|
+
/** Placeholder/interpolation mismatch between locales */
|
|
48
|
+
PLACEHOLDER_MISMATCH: 2,
|
|
49
|
+
/** Empty or placeholder values in locale files */
|
|
50
|
+
EMPTY_VALUES: 3,
|
|
51
|
+
/** Suspicious key patterns detected (e.g., key=value, raw text keys) */
|
|
52
|
+
SUSPICIOUS_KEYS: 4,
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* Exit codes for the `check` command.
|
|
56
|
+
*
|
|
57
|
+
* Used when running `i18nsmith check --fail-on <level>`.
|
|
58
|
+
*/
|
|
59
|
+
export const CHECK_EXIT_CODES = {
|
|
60
|
+
/** Warnings detected (when --fail-on warnings) */
|
|
61
|
+
WARNINGS: 10,
|
|
62
|
+
/** Blocking conflicts detected (when --fail-on conflicts) */
|
|
63
|
+
CONFLICTS: 11,
|
|
64
|
+
};
|
|
65
|
+
/**
|
|
66
|
+
* Exit codes for the `diagnose` command and diagnostic conflicts.
|
|
67
|
+
*
|
|
68
|
+
* These are also used by `check` when `--prefer-diagnostics-exit` is set.
|
|
69
|
+
*/
|
|
70
|
+
export const DIAGNOSTICS_EXIT_CODES = {
|
|
71
|
+
/** Missing source locale file */
|
|
72
|
+
MISSING_SOURCE_LOCALE: 2,
|
|
73
|
+
/** Invalid JSON in locale file */
|
|
74
|
+
INVALID_LOCALE_JSON: 3,
|
|
75
|
+
/** Provider/adapter clash detected */
|
|
76
|
+
UNSAFE_PROVIDER_CLASH: 4,
|
|
77
|
+
/** General diagnostics conflict (fallback) */
|
|
78
|
+
GENERAL_CONFLICT: 5,
|
|
79
|
+
};
|
|
80
|
+
/**
|
|
81
|
+
* General exit codes used across all commands.
|
|
82
|
+
*/
|
|
83
|
+
export const GENERAL_EXIT_CODES = {
|
|
84
|
+
/** Success - no issues found */
|
|
85
|
+
SUCCESS: 0,
|
|
86
|
+
/** General error (catch-all for exceptions) */
|
|
87
|
+
ERROR: 1,
|
|
88
|
+
};
|
|
89
|
+
/**
|
|
90
|
+
* Human-readable descriptions for sync exit codes.
|
|
91
|
+
*/
|
|
92
|
+
export const SYNC_EXIT_DESCRIPTIONS = {
|
|
93
|
+
[SYNC_EXIT_CODES.DRIFT]: 'Locale drift detected (missing or unused keys)',
|
|
94
|
+
[SYNC_EXIT_CODES.PLACEHOLDER_MISMATCH]: 'Placeholder mismatch between locales',
|
|
95
|
+
[SYNC_EXIT_CODES.EMPTY_VALUES]: 'Empty or placeholder values in locale files',
|
|
96
|
+
[SYNC_EXIT_CODES.SUSPICIOUS_KEYS]: 'Suspicious key patterns detected',
|
|
97
|
+
};
|
|
98
|
+
/**
|
|
99
|
+
* Human-readable descriptions for check exit codes.
|
|
100
|
+
*/
|
|
101
|
+
export const CHECK_EXIT_DESCRIPTIONS = {
|
|
102
|
+
[CHECK_EXIT_CODES.WARNINGS]: 'Warnings detected',
|
|
103
|
+
[CHECK_EXIT_CODES.CONFLICTS]: 'Blocking conflicts detected',
|
|
104
|
+
};
|
|
105
|
+
/**
|
|
106
|
+
* Human-readable descriptions for diagnostics exit codes.
|
|
107
|
+
*/
|
|
108
|
+
export const DIAGNOSTICS_EXIT_DESCRIPTIONS = {
|
|
109
|
+
[DIAGNOSTICS_EXIT_CODES.MISSING_SOURCE_LOCALE]: 'Missing source locale file',
|
|
110
|
+
[DIAGNOSTICS_EXIT_CODES.INVALID_LOCALE_JSON]: 'Invalid JSON in locale file',
|
|
111
|
+
[DIAGNOSTICS_EXIT_CODES.UNSAFE_PROVIDER_CLASH]: 'Provider/adapter clash detected',
|
|
112
|
+
[DIAGNOSTICS_EXIT_CODES.GENERAL_CONFLICT]: 'General diagnostics conflict',
|
|
113
|
+
};
|
|
114
|
+
/**
|
|
115
|
+
* All exit code descriptions by command context.
|
|
116
|
+
*/
|
|
117
|
+
export const EXIT_CODE_DESCRIPTIONS = {
|
|
118
|
+
general: {
|
|
119
|
+
[GENERAL_EXIT_CODES.SUCCESS]: 'Success - no issues found',
|
|
120
|
+
[GENERAL_EXIT_CODES.ERROR]: 'General error',
|
|
121
|
+
},
|
|
122
|
+
sync: SYNC_EXIT_DESCRIPTIONS,
|
|
123
|
+
check: CHECK_EXIT_DESCRIPTIONS,
|
|
124
|
+
diagnostics: DIAGNOSTICS_EXIT_DESCRIPTIONS,
|
|
125
|
+
};
|
|
126
|
+
/**
|
|
127
|
+
* Get a human-readable description for an exit code.
|
|
128
|
+
*
|
|
129
|
+
* @param code - The exit code
|
|
130
|
+
* @param context - Optional context to disambiguate overlapping codes
|
|
131
|
+
* @returns Description string, or 'Unknown exit code' if not recognized
|
|
132
|
+
*/
|
|
133
|
+
export function getExitCodeDescription(code, context) {
|
|
134
|
+
// Check context-specific first
|
|
135
|
+
if (context) {
|
|
136
|
+
const contextMap = EXIT_CODE_DESCRIPTIONS[context];
|
|
137
|
+
if (contextMap[code]) {
|
|
138
|
+
return contextMap[code];
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
// Try general codes
|
|
142
|
+
if (code in EXIT_CODE_DESCRIPTIONS.general) {
|
|
143
|
+
return EXIT_CODE_DESCRIPTIONS.general[code];
|
|
144
|
+
}
|
|
145
|
+
// Search all contexts
|
|
146
|
+
for (const ctxMap of [SYNC_EXIT_DESCRIPTIONS, CHECK_EXIT_DESCRIPTIONS, DIAGNOSTICS_EXIT_DESCRIPTIONS]) {
|
|
147
|
+
if (ctxMap[code]) {
|
|
148
|
+
return ctxMap[code];
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return `Unknown exit code: ${code}`;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Helper to set process exit code with optional logging.
|
|
155
|
+
*
|
|
156
|
+
* @param code - The exit code to set
|
|
157
|
+
* @param options - Optional configuration
|
|
158
|
+
*/
|
|
159
|
+
export function setExitCode(code, options) {
|
|
160
|
+
process.exitCode = code;
|
|
161
|
+
if (!options?.silent && code !== 0) {
|
|
162
|
+
// Only log in development/debug mode
|
|
163
|
+
if (process.env.DEBUG?.includes('i18nsmith')) {
|
|
164
|
+
console.error(`[i18nsmith] Exit code ${code}: ${getExitCodeDescription(code)}`);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
//# sourceMappingURL=exit-codes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"exit-codes.js","sourceRoot":"","sources":["../../src/utils/exit-codes.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export type PackageManager = 'pnpm' | 'yarn' | 'npm';
|
|
2
|
+
export declare function detectPackageManager(workspaceRoot?: string): Promise<PackageManager>;
|
|
3
|
+
export declare function installDependencies(manager: PackageManager, deps: string[], workspaceRoot?: string): Promise<void>;
|
|
4
|
+
//# sourceMappingURL=package-manager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"package-manager.d.ts","sourceRoot":"","sources":["../../src/utils/package-manager.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,MAAM,GAAG,KAAK,CAAC;AAErD,wBAAsB,oBAAoB,CAAC,aAAa,SAAgB,GAAG,OAAO,CAAC,cAAc,CAAC,CAQjG;AAED,wBAAsB,mBAAmB,CAAC,OAAO,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,aAAa,SAAgB,iBAkB/G"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { spawn } from 'child_process';
|
|
4
|
+
export async function detectPackageManager(workspaceRoot = process.cwd()) {
|
|
5
|
+
if (await fileExists(path.join(workspaceRoot, 'pnpm-lock.yaml'))) {
|
|
6
|
+
return 'pnpm';
|
|
7
|
+
}
|
|
8
|
+
if (await fileExists(path.join(workspaceRoot, 'yarn.lock'))) {
|
|
9
|
+
return 'yarn';
|
|
10
|
+
}
|
|
11
|
+
return 'npm';
|
|
12
|
+
}
|
|
13
|
+
export async function installDependencies(manager, deps, workspaceRoot = process.cwd()) {
|
|
14
|
+
const args = manager === 'npm' ? ['install', ...deps] : ['add', ...deps];
|
|
15
|
+
await new Promise((resolve, reject) => {
|
|
16
|
+
const child = spawn(manager, args, {
|
|
17
|
+
cwd: workspaceRoot,
|
|
18
|
+
stdio: 'inherit',
|
|
19
|
+
});
|
|
20
|
+
child.on('error', reject);
|
|
21
|
+
child.on('close', (code) => {
|
|
22
|
+
if (code === 0) {
|
|
23
|
+
resolve();
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
reject(new Error(`${manager} exited with code ${code}`));
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
async function fileExists(target) {
|
|
32
|
+
try {
|
|
33
|
+
await fs.access(target);
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=package-manager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"package-manager.js","sourceRoot":"","sources":["../../src/utils/package-manager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,aAAa,CAAC;AAC7B,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAItC,MAAM,CAAC,KAAK,UAAU,oBAAoB,CAAC,aAAa,GAAG,OAAO,CAAC,GAAG,EAAE;IACtE,IAAI,MAAM,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,gBAAgB,CAAC,CAAC,EAAE,CAAC;QACjE,OAAO,MAAM,CAAC;IAChB,CAAC;IACD,IAAI,MAAM,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,WAAW,CAAC,CAAC,EAAE,CAAC;QAC5D,OAAO,MAAM,CAAC;IAChB,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,OAAuB,EAAE,IAAc,EAAE,aAAa,GAAG,OAAO,CAAC,GAAG,EAAE;IAC9G,MAAM,IAAI,GAAG,OAAO,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,EAAE,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,EAAE,GAAG,IAAI,CAAC,CAAC;IAEzE,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QAC1C,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,EAAE,IAAI,EAAE;YACjC,GAAG,EAAE,aAAa;YAClB,KAAK,EAAE,SAAS;SACjB,CAAC,CAAC;QAEH,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QAC1B,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,EAAE;YACzB,IAAI,IAAI,KAAK,CAAC,EAAE,CAAC;gBACf,OAAO,EAAE,CAAC;YACZ,CAAC;iBAAM,CAAC;gBACN,MAAM,CAAC,IAAI,KAAK,CAAC,GAAG,OAAO,qBAAqB,IAAI,EAAE,CAAC,CAAC,CAAC;YAC3D,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC;AAED,KAAK,UAAU,UAAU,CAAC,MAAc;IACtC,IAAI,CAAC;QACH,MAAM,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QACxB,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pkg.d.ts","sourceRoot":"","sources":["../../src/utils/pkg.ts"],"names":[],"mappings":"AAGA,wBAAsB,eAAe,CAAC,GAAG,CAAC,EAAE,MAAM,gDAYjD;AAED,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,EAAE,GAAG,EAAE,MAAM,WAKlF"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { promises as fs } from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
export async function readPackageJson(cwd) {
|
|
4
|
+
const pkgPath = path.join(cwd ?? process.cwd(), 'package.json');
|
|
5
|
+
try {
|
|
6
|
+
const content = await fs.readFile(pkgPath, 'utf8');
|
|
7
|
+
return JSON.parse(content);
|
|
8
|
+
}
|
|
9
|
+
catch (error) {
|
|
10
|
+
if (error && typeof error === 'object' && 'code' in error && error.code === 'ENOENT') {
|
|
11
|
+
return undefined;
|
|
12
|
+
}
|
|
13
|
+
console.warn('Unable to read package.json for dependency checks.');
|
|
14
|
+
return undefined;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export function hasDependency(pkg, dep) {
|
|
18
|
+
if (!pkg)
|
|
19
|
+
return false;
|
|
20
|
+
const deps = pkg.dependencies;
|
|
21
|
+
const devDeps = pkg.devDependencies;
|
|
22
|
+
return Boolean(deps?.[dep] || devDeps?.[dep]);
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=pkg.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pkg.js","sourceRoot":"","sources":["../../src/utils/pkg.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,IAAI,EAAE,EAAE,MAAM,IAAI,CAAC;AACpC,OAAO,IAAI,MAAM,MAAM,CAAC;AAExB,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,GAAY;IAChD,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,EAAE,EAAE,cAAc,CAAC,CAAC;IAChE,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QACnD,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,CAA4B,CAAC;IACxD,CAAC;IAAC,OAAO,KAAc,EAAE,CAAC;QACxB,IAAI,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,MAAM,IAAI,KAAK,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YACrF,OAAO,SAAS,CAAC;QACnB,CAAC;QACD,OAAO,CAAC,IAAI,CAAC,oDAAoD,CAAC,CAAC;QACnE,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,GAAwC,EAAE,GAAW;IACjF,IAAI,CAAC,GAAG;QAAE,OAAO,KAAK,CAAC;IACvB,MAAM,IAAI,GAAG,GAAG,CAAC,YAAmD,CAAC;IACrE,MAAM,OAAO,GAAG,GAAG,CAAC,eAAsD,CAAC;IAC3E,OAAO,OAAO,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,IAAI,OAAO,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;AAChD,CAAC"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export type ProviderInjectionResult = {
|
|
2
|
+
status: 'injected';
|
|
3
|
+
file: string;
|
|
4
|
+
diff?: string;
|
|
5
|
+
} | {
|
|
6
|
+
status: 'preview';
|
|
7
|
+
file: string;
|
|
8
|
+
diff: string;
|
|
9
|
+
} | {
|
|
10
|
+
status: 'skipped';
|
|
11
|
+
file: string;
|
|
12
|
+
existingProvider?: string;
|
|
13
|
+
} | {
|
|
14
|
+
status: 'failed';
|
|
15
|
+
file: string;
|
|
16
|
+
reason: string;
|
|
17
|
+
} | {
|
|
18
|
+
status: 'not-found';
|
|
19
|
+
};
|
|
20
|
+
export interface ProviderDetectionResult {
|
|
21
|
+
found: boolean;
|
|
22
|
+
file?: string;
|
|
23
|
+
provider?: string;
|
|
24
|
+
candidates: string[];
|
|
25
|
+
}
|
|
26
|
+
export interface ProviderInjectionOptions {
|
|
27
|
+
providerComponentPath: string;
|
|
28
|
+
candidates?: string[];
|
|
29
|
+
dryRun?: boolean;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Detect if any provider file exists and whether an i18n provider is already wired.
|
|
33
|
+
*/
|
|
34
|
+
export declare function detectExistingProvider(candidates?: string[]): Promise<ProviderDetectionResult>;
|
|
35
|
+
export declare function maybeInjectProvider(options: ProviderInjectionOptions): Promise<ProviderInjectionResult>;
|
|
36
|
+
//# sourceMappingURL=provider-injector.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provider-injector.d.ts","sourceRoot":"","sources":["../../src/utils/provider-injector.ts"],"names":[],"mappings":"AAeA,MAAM,MAAM,uBAAuB,GAC/B;IAAE,MAAM,EAAE,UAAU,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,GACnD;IAAE,MAAM,EAAE,SAAS,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GACjD;IAAE,MAAM,EAAE,SAAS,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,gBAAgB,CAAC,EAAE,MAAM,CAAA;CAAE,GAC9D;IAAE,MAAM,EAAE,QAAQ,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAClD;IAAE,MAAM,EAAE,WAAW,CAAA;CAAE,CAAC;AAE5B,MAAM,WAAW,uBAAuB;IACtC,KAAK,EAAE,OAAO,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,EAAE,CAAC;CACtB;AAED,MAAM,WAAW,wBAAwB;IACvC,qBAAqB,EAAE,MAAM,CAAC;IAC9B,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAuCD;;GAEG;AACH,wBAAsB,sBAAsB,CAC1C,UAAU,GAAE,MAAM,EAAgC,GACjD,OAAO,CAAC,uBAAuB,CAAC,CAwClC;AAuBD,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,wBAAwB,GAChC,OAAO,CAAC,uBAAuB,CAAC,CA2BlC"}
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import { promises as fs } from 'fs';
|
|
3
|
+
import { createTwoFilesPatch } from 'diff';
|
|
4
|
+
import { Node, Project, SyntaxKind, } from 'ts-morph';
|
|
5
|
+
const DEFAULT_PROVIDER_CANDIDATES = [
|
|
6
|
+
// Next.js App Router patterns
|
|
7
|
+
'app/providers.tsx',
|
|
8
|
+
'app/providers.ts',
|
|
9
|
+
'app/providers.jsx',
|
|
10
|
+
'app/providers.js',
|
|
11
|
+
'src/app/providers.tsx',
|
|
12
|
+
'src/app/providers.ts',
|
|
13
|
+
'src/app/providers.jsx',
|
|
14
|
+
'src/app/providers.js',
|
|
15
|
+
// Next.js layout files (common provider host)
|
|
16
|
+
'app/layout.tsx',
|
|
17
|
+
'app/layout.ts',
|
|
18
|
+
'app/layout.jsx',
|
|
19
|
+
'app/layout.js',
|
|
20
|
+
'src/app/layout.tsx',
|
|
21
|
+
'src/app/layout.ts',
|
|
22
|
+
'src/app/layout.jsx',
|
|
23
|
+
'src/app/layout.js',
|
|
24
|
+
// Generic provider wrappers
|
|
25
|
+
'src/providers.tsx',
|
|
26
|
+
'src/providers.ts',
|
|
27
|
+
'src/providers.jsx',
|
|
28
|
+
'src/providers.js',
|
|
29
|
+
];
|
|
30
|
+
const KNOWN_I18N_PROVIDERS = [
|
|
31
|
+
'I18nProvider',
|
|
32
|
+
'IntlProvider', // react-intl
|
|
33
|
+
'NextIntlClientProvider', // next-intl
|
|
34
|
+
'NextIntlProvider', // next-intl (older)
|
|
35
|
+
'I18nextProvider', // react-i18next
|
|
36
|
+
'TranslationProvider', // common custom name
|
|
37
|
+
'LocaleProvider', // common custom name
|
|
38
|
+
'LanguageProvider', // common custom name
|
|
39
|
+
];
|
|
40
|
+
/**
|
|
41
|
+
* Detect if any provider file exists and whether an i18n provider is already wired.
|
|
42
|
+
*/
|
|
43
|
+
export async function detectExistingProvider(candidates = DEFAULT_PROVIDER_CANDIDATES) {
|
|
44
|
+
const workspaceRoot = process.cwd();
|
|
45
|
+
const existingFiles = [];
|
|
46
|
+
for (const candidate of candidates) {
|
|
47
|
+
const absoluteCandidate = path.resolve(workspaceRoot, candidate);
|
|
48
|
+
if (await fileExists(absoluteCandidate)) {
|
|
49
|
+
existingFiles.push(candidate);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
if (existingFiles.length === 0) {
|
|
53
|
+
return { found: false, candidates: [] };
|
|
54
|
+
}
|
|
55
|
+
// Check each existing file for an i18n provider
|
|
56
|
+
for (const file of existingFiles) {
|
|
57
|
+
const absolutePath = path.resolve(workspaceRoot, file);
|
|
58
|
+
const contents = await fs.readFile(absolutePath, 'utf8');
|
|
59
|
+
const project = new Project({
|
|
60
|
+
useInMemoryFileSystem: false,
|
|
61
|
+
skipAddingFilesFromTsConfig: true,
|
|
62
|
+
});
|
|
63
|
+
const sourceFile = project.createSourceFile(absolutePath, contents, { overwrite: true });
|
|
64
|
+
const detectedProvider = getDetectedProvider(sourceFile);
|
|
65
|
+
if (detectedProvider) {
|
|
66
|
+
return {
|
|
67
|
+
found: true,
|
|
68
|
+
file,
|
|
69
|
+
provider: detectedProvider,
|
|
70
|
+
candidates: existingFiles,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
// Files exist but no provider detected
|
|
75
|
+
return { found: false, candidates: existingFiles };
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Returns the name of the detected i18n provider, or undefined if none found.
|
|
79
|
+
*/
|
|
80
|
+
function getDetectedProvider(sourceFile) {
|
|
81
|
+
for (const pattern of KNOWN_I18N_PROVIDERS) {
|
|
82
|
+
const hasPattern = sourceFile
|
|
83
|
+
.getDescendantsOfKind(SyntaxKind.JsxOpeningElement)
|
|
84
|
+
.some((element) => getTagName(element) === pattern) ||
|
|
85
|
+
sourceFile
|
|
86
|
+
.getDescendantsOfKind(SyntaxKind.JsxSelfClosingElement)
|
|
87
|
+
.some((element) => getTagName(element) === pattern);
|
|
88
|
+
if (hasPattern) {
|
|
89
|
+
return pattern;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return undefined;
|
|
93
|
+
}
|
|
94
|
+
export async function maybeInjectProvider(options) {
|
|
95
|
+
const { providerComponentPath, candidates = DEFAULT_PROVIDER_CANDIDATES, dryRun = false } = options;
|
|
96
|
+
const workspaceRoot = process.cwd();
|
|
97
|
+
const providerAbsolute = path.resolve(providerComponentPath);
|
|
98
|
+
for (const candidate of candidates) {
|
|
99
|
+
const absoluteCandidate = path.resolve(workspaceRoot, candidate);
|
|
100
|
+
if (!(await fileExists(absoluteCandidate))) {
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
const injection = await injectIntoCandidate({
|
|
104
|
+
candidateRelativePath: candidate,
|
|
105
|
+
candidateAbsolutePath: absoluteCandidate,
|
|
106
|
+
providerAbsolutePath: providerAbsolute,
|
|
107
|
+
dryRun,
|
|
108
|
+
});
|
|
109
|
+
if (injection.status === 'not-found') {
|
|
110
|
+
// Should never happen because we only call when file exists, but keep for safety
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
return injection;
|
|
114
|
+
}
|
|
115
|
+
return { status: 'not-found' };
|
|
116
|
+
}
|
|
117
|
+
async function injectIntoCandidate({ candidateRelativePath, candidateAbsolutePath, providerAbsolutePath, dryRun, }) {
|
|
118
|
+
const originalContents = await fs.readFile(candidateAbsolutePath, 'utf8');
|
|
119
|
+
const project = new Project({
|
|
120
|
+
useInMemoryFileSystem: false,
|
|
121
|
+
skipAddingFilesFromTsConfig: true,
|
|
122
|
+
});
|
|
123
|
+
const sourceFile = project.createSourceFile(candidateAbsolutePath, originalContents, { overwrite: true });
|
|
124
|
+
const existingProvider = getDetectedProvider(sourceFile);
|
|
125
|
+
if (existingProvider) {
|
|
126
|
+
return { status: 'skipped', file: candidateRelativePath, existingProvider };
|
|
127
|
+
}
|
|
128
|
+
const childrenExpressions = findChildrenExpressions(sourceFile);
|
|
129
|
+
if (childrenExpressions.length === 0) {
|
|
130
|
+
return {
|
|
131
|
+
status: 'failed',
|
|
132
|
+
file: candidateRelativePath,
|
|
133
|
+
reason: 'No `{children}` expression found to wrap with <I18nProvider>.',
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
if (childrenExpressions.length > 1) {
|
|
137
|
+
return {
|
|
138
|
+
status: 'failed',
|
|
139
|
+
file: candidateRelativePath,
|
|
140
|
+
reason: 'Multiple `{children}` expressions detected; unsure which one to wrap.',
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
const providerImportPath = toRelativeImport(path.dirname(candidateAbsolutePath), providerAbsolutePath);
|
|
144
|
+
ensureProviderImport(sourceFile, providerImportPath);
|
|
145
|
+
const expression = childrenExpressions[0];
|
|
146
|
+
expression.replaceWithText(`<I18nProvider>${expression.getText()}</I18nProvider>`);
|
|
147
|
+
const updatedContents = sourceFile.getFullText();
|
|
148
|
+
if (updatedContents === originalContents) {
|
|
149
|
+
return { status: 'skipped', file: candidateRelativePath };
|
|
150
|
+
}
|
|
151
|
+
const diff = createTwoFilesPatch(candidateRelativePath, candidateRelativePath, originalContents, updatedContents, '', '');
|
|
152
|
+
if (dryRun) {
|
|
153
|
+
return { status: 'preview', file: candidateRelativePath, diff };
|
|
154
|
+
}
|
|
155
|
+
await fs.writeFile(candidateAbsolutePath, updatedContents, 'utf8');
|
|
156
|
+
return { status: 'injected', file: candidateRelativePath, diff };
|
|
157
|
+
}
|
|
158
|
+
function getTagName(element) {
|
|
159
|
+
return element.getTagNameNode().getText();
|
|
160
|
+
}
|
|
161
|
+
function findChildrenExpressions(sourceFile) {
|
|
162
|
+
return sourceFile
|
|
163
|
+
.getDescendantsOfKind(SyntaxKind.JsxExpression)
|
|
164
|
+
.filter((expression) => {
|
|
165
|
+
const inner = expression.getExpression();
|
|
166
|
+
return inner && Node.isIdentifier(inner) && inner.getText() === 'children';
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
function ensureProviderImport(sourceFile, moduleSpecifier) {
|
|
170
|
+
const providerIdentifier = 'I18nProvider';
|
|
171
|
+
const existing = sourceFile.getImportDeclaration((declaration) => {
|
|
172
|
+
const spec = declaration.getModuleSpecifierSourceFile();
|
|
173
|
+
if (spec && spec.getFilePath() === moduleSpecifier) {
|
|
174
|
+
return true;
|
|
175
|
+
}
|
|
176
|
+
return declaration.getModuleSpecifierValue() === moduleSpecifier;
|
|
177
|
+
});
|
|
178
|
+
if (existing) {
|
|
179
|
+
const hasNamed = existing
|
|
180
|
+
.getNamedImports()
|
|
181
|
+
.some((namedImport) => namedImport.getName() === providerIdentifier);
|
|
182
|
+
if (!hasNamed) {
|
|
183
|
+
existing.addNamedImport(providerIdentifier);
|
|
184
|
+
}
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
const statements = sourceFile.getStatements();
|
|
188
|
+
let insertIndex = 0;
|
|
189
|
+
for (const statement of statements) {
|
|
190
|
+
if (Node.isExpressionStatement(statement)) {
|
|
191
|
+
const expression = statement.getExpression();
|
|
192
|
+
if (Node.isStringLiteral(expression)) {
|
|
193
|
+
const literal = expression.getLiteralText();
|
|
194
|
+
if (literal.startsWith('use ')) {
|
|
195
|
+
insertIndex += 1;
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
break;
|
|
201
|
+
}
|
|
202
|
+
sourceFile.insertImportDeclaration(insertIndex, {
|
|
203
|
+
moduleSpecifier,
|
|
204
|
+
namedImports: [{ name: providerIdentifier }],
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
async function fileExists(target) {
|
|
208
|
+
try {
|
|
209
|
+
await fs.access(target);
|
|
210
|
+
return true;
|
|
211
|
+
}
|
|
212
|
+
catch {
|
|
213
|
+
return false;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
function toRelativeImport(fromDir, targetAbsolute) {
|
|
217
|
+
let relative = path.relative(fromDir, targetAbsolute).replace(/\\/g, '/');
|
|
218
|
+
if (!relative.startsWith('.')) {
|
|
219
|
+
relative = `./${relative}`;
|
|
220
|
+
}
|
|
221
|
+
return relative.replace(/\.(ts|tsx|js|jsx)$/i, '');
|
|
222
|
+
}
|
|
223
|
+
//# sourceMappingURL=provider-injector.js.map
|