i18nsmith 0.4.3 → 0.6.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/README.md +126 -0
- package/build.mjs +5 -0
- package/dist/commands/backup.d.ts.map +1 -1
- package/dist/commands/check.d.ts.map +1 -1
- package/dist/commands/config.d.ts.map +1 -1
- package/dist/commands/coverage.d.ts.map +1 -1
- package/dist/commands/detect.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/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.map +1 -1
- package/dist/commands/transform.d.ts.map +1 -1
- package/dist/index.cjs +13069 -6794
- package/dist/services/index.d.ts +12 -0
- package/dist/services/index.d.ts.map +1 -0
- package/dist/services/transform-service.d.ts +64 -0
- package/dist/services/transform-service.d.ts.map +1 -0
- package/dist/utils/bootstrap.d.ts +83 -0
- package/dist/utils/bootstrap.d.ts.map +1 -0
- package/package.json +1 -1
- package/src/commands/backup.ts +28 -10
- package/src/commands/check.ts +16 -4
- package/src/commands/config.ts +126 -0
- package/src/commands/coverage.ts +11 -4
- package/src/commands/detect.ts +11 -4
- package/src/commands/diagnose.ts +12 -2
- package/src/commands/init.test.ts +80 -0
- package/src/commands/init.ts +92 -49
- package/src/commands/rename.ts +24 -5
- package/src/commands/review.ts +10 -3
- package/src/commands/scaffold-adapter.ts +11 -2
- package/src/commands/scan.ts +20 -7
- package/src/commands/sync.ts +91 -23
- package/src/commands/transform.ts +8 -1
- package/src/index.ts +1 -1
- package/src/integration.test.ts +145 -12
- package/src/services/index.ts +12 -0
- package/src/services/transform-service.ts +203 -0
- package/src/utils/bootstrap.ts +221 -0
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview
|
|
3
|
+
* CLI Transform Service - Implements ITransformService for CLI usage
|
|
4
|
+
*
|
|
5
|
+
* This service wraps @i18nsmith/transformer's Transformer class and provides
|
|
6
|
+
* the ITransformService interface. It lives in CLI because:
|
|
7
|
+
* - @i18nsmith/core should NOT depend on @i18nsmith/transformer
|
|
8
|
+
* - CLI depends on both packages, making this the natural location
|
|
9
|
+
*
|
|
10
|
+
* @module @i18nsmith/cli/services/transform-service
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { Transformer } from '@i18nsmith/transformer';
|
|
14
|
+
import type { TransformSummary, TransformRunOptions as TransformerRunOptions } from '@i18nsmith/transformer';
|
|
15
|
+
import {
|
|
16
|
+
type I18nConfig,
|
|
17
|
+
type ITransformService,
|
|
18
|
+
type TransformOptions,
|
|
19
|
+
type TransformSummaryBase,
|
|
20
|
+
type ScanCandidate,
|
|
21
|
+
type Result,
|
|
22
|
+
type CoreError,
|
|
23
|
+
success,
|
|
24
|
+
failure,
|
|
25
|
+
} from '@i18nsmith/core';
|
|
26
|
+
|
|
27
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
28
|
+
// Types
|
|
29
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Configuration for the CLI transform service.
|
|
33
|
+
*/
|
|
34
|
+
export interface CliTransformServiceConfig {
|
|
35
|
+
/** Workspace root directory */
|
|
36
|
+
readonly workspaceRoot: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
40
|
+
// Helper Functions
|
|
41
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Converts a TransformSummary to TransformSummaryBase.
|
|
45
|
+
* Maps the full candidate objects to the base type and includes optional display fields.
|
|
46
|
+
*/
|
|
47
|
+
function toSummaryBase(summary: TransformSummary): TransformSummaryBase {
|
|
48
|
+
return {
|
|
49
|
+
filesScanned: summary.filesScanned,
|
|
50
|
+
filesChanged: summary.filesChanged,
|
|
51
|
+
candidates: summary.candidates.map(c => ({
|
|
52
|
+
filePath: c.filePath,
|
|
53
|
+
position: c.position,
|
|
54
|
+
text: c.text,
|
|
55
|
+
kind: c.kind,
|
|
56
|
+
suggestedKey: c.suggestedKey,
|
|
57
|
+
status: c.status,
|
|
58
|
+
reason: c.reason,
|
|
59
|
+
})),
|
|
60
|
+
candidateStats: summary.candidateStats,
|
|
61
|
+
write: summary.write,
|
|
62
|
+
localeStats: summary.localeStats?.map(s => ({
|
|
63
|
+
locale: s.locale,
|
|
64
|
+
added: s.added,
|
|
65
|
+
updated: s.updated,
|
|
66
|
+
removed: s.removed,
|
|
67
|
+
totalKeys: s.totalKeys,
|
|
68
|
+
})),
|
|
69
|
+
skippedFiles: summary.skippedFiles,
|
|
70
|
+
skippedReasons: summary.skippedReasons,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Creates a CoreError for transform operations.
|
|
76
|
+
*/
|
|
77
|
+
function createTransformError(message: string, code: string = 'TRANSFORM_ERROR'): CoreError {
|
|
78
|
+
return {
|
|
79
|
+
code,
|
|
80
|
+
message,
|
|
81
|
+
category: 'internal',
|
|
82
|
+
severity: 'error',
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
87
|
+
// Service Implementation
|
|
88
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* CLI implementation of ITransformService.
|
|
92
|
+
*
|
|
93
|
+
* Wraps the @i18nsmith/transformer Transformer class with the standard
|
|
94
|
+
* Result-based error handling pattern used across the service layer.
|
|
95
|
+
*/
|
|
96
|
+
export class CliTransformService implements ITransformService {
|
|
97
|
+
private readonly workspaceRoot: string;
|
|
98
|
+
|
|
99
|
+
constructor(config: CliTransformServiceConfig) {
|
|
100
|
+
this.workspaceRoot = config.workspaceRoot;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Transforms source files to use translation functions.
|
|
105
|
+
*/
|
|
106
|
+
async transform(
|
|
107
|
+
config: I18nConfig,
|
|
108
|
+
options?: TransformOptions
|
|
109
|
+
): Promise<Result<TransformSummaryBase>> {
|
|
110
|
+
try {
|
|
111
|
+
const transformer = new Transformer(config, {
|
|
112
|
+
workspaceRoot: options?.workspaceRoot ?? this.workspaceRoot,
|
|
113
|
+
keyNamespace: options?.keyNamespace,
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
const runOptions: TransformerRunOptions = {
|
|
117
|
+
write: options?.write ?? false,
|
|
118
|
+
targets: options?.targets ? [...options.targets] : undefined,
|
|
119
|
+
diff: options?.diff ?? false,
|
|
120
|
+
migrateTextKeys: options?.migrateTextKeys ?? false,
|
|
121
|
+
onProgress: options?.onProgress,
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
const summary = await transformer.run(runOptions);
|
|
125
|
+
return success(toSummaryBase(summary));
|
|
126
|
+
} catch (error) {
|
|
127
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
128
|
+
return failure(createTransformError(`Transform failed: ${message}`));
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Transforms specific candidates (from a previous scan).
|
|
134
|
+
*
|
|
135
|
+
* Note: The current Transformer doesn't have a direct method for this,
|
|
136
|
+
* so we run a full transform with targets limited to the candidate files.
|
|
137
|
+
*/
|
|
138
|
+
async transformCandidates(
|
|
139
|
+
config: I18nConfig,
|
|
140
|
+
candidates: readonly ScanCandidate[],
|
|
141
|
+
options?: TransformOptions
|
|
142
|
+
): Promise<Result<TransformSummaryBase>> {
|
|
143
|
+
try {
|
|
144
|
+
// Extract unique file paths from candidates
|
|
145
|
+
const uniqueFiles = [...new Set(candidates.map(c => c.filePath))];
|
|
146
|
+
|
|
147
|
+
const transformer = new Transformer(config, {
|
|
148
|
+
workspaceRoot: options?.workspaceRoot ?? this.workspaceRoot,
|
|
149
|
+
keyNamespace: options?.keyNamespace,
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
const runOptions: TransformerRunOptions = {
|
|
153
|
+
write: options?.write ?? false,
|
|
154
|
+
targets: uniqueFiles,
|
|
155
|
+
diff: options?.diff ?? false,
|
|
156
|
+
migrateTextKeys: options?.migrateTextKeys ?? false,
|
|
157
|
+
onProgress: options?.onProgress,
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
const summary = await transformer.run(runOptions);
|
|
161
|
+
return success(toSummaryBase(summary));
|
|
162
|
+
} catch (error) {
|
|
163
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
164
|
+
return failure(createTransformError(`Transform candidates failed: ${message}`));
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Generates a preview of transformation without applying.
|
|
170
|
+
*/
|
|
171
|
+
async preview(
|
|
172
|
+
config: I18nConfig,
|
|
173
|
+
options?: Omit<TransformOptions, 'write'>
|
|
174
|
+
): Promise<Result<TransformSummaryBase>> {
|
|
175
|
+
return this.transform(config, { ...options, write: false });
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
180
|
+
// Factory Function
|
|
181
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Creates a CLI transform service instance.
|
|
185
|
+
*
|
|
186
|
+
* @param workspaceRoot - The workspace root directory
|
|
187
|
+
* @returns ITransformService instance
|
|
188
|
+
*
|
|
189
|
+
* @example
|
|
190
|
+
* ```typescript
|
|
191
|
+
* import { getTransformService } from '../services/transform-service.js';
|
|
192
|
+
*
|
|
193
|
+
* const transformService = getTransformService(projectRoot);
|
|
194
|
+
* const result = await transformService.transform(config, { write: true });
|
|
195
|
+
*
|
|
196
|
+
* if (!result.success) {
|
|
197
|
+
* throw new CliError(result.error.message);
|
|
198
|
+
* }
|
|
199
|
+
* ```
|
|
200
|
+
*/
|
|
201
|
+
export function getTransformService(workspaceRoot: string): ITransformService {
|
|
202
|
+
return new CliTransformService({ workspaceRoot });
|
|
203
|
+
}
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview
|
|
3
|
+
* CLI Bootstrap - Service Container Setup for CLI
|
|
4
|
+
*
|
|
5
|
+
* This module provides a pre-configured ServiceContainer with Node.js
|
|
6
|
+
* platform adapters. CLI commands can import this to use the service layer.
|
|
7
|
+
*
|
|
8
|
+
* Following Hexagonal Architecture:
|
|
9
|
+
* - The ServiceContainer is the "Application Layer"
|
|
10
|
+
* - This bootstrap module configures "Adapters" for CLI (Node.js) environment
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```typescript
|
|
14
|
+
* import { getServiceContainer } from '../utils/bootstrap.js';
|
|
15
|
+
*
|
|
16
|
+
* async function myCommand() {
|
|
17
|
+
* const container = getServiceContainer();
|
|
18
|
+
* const result = await container.scanner.scan(config);
|
|
19
|
+
* // ...
|
|
20
|
+
* }
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* @module @i18nsmith/cli/utils/bootstrap
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import chalk from 'chalk';
|
|
27
|
+
import {
|
|
28
|
+
ServiceContainer,
|
|
29
|
+
createServiceContainer,
|
|
30
|
+
NodeFileSystem,
|
|
31
|
+
NodeLoggerFactory,
|
|
32
|
+
type ServiceContainerConfig,
|
|
33
|
+
type PlatformAdapters,
|
|
34
|
+
} from '@i18nsmith/core';
|
|
35
|
+
import type { ILogger, LogLevel, ILoggerFactory } from '@i18nsmith/core';
|
|
36
|
+
|
|
37
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
38
|
+
// CLI Logger Adapter
|
|
39
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* CLI-specific logger that outputs with chalk formatting.
|
|
43
|
+
*/
|
|
44
|
+
class CliLogger implements ILogger {
|
|
45
|
+
private currentLevel: LogLevel;
|
|
46
|
+
private readonly scope: string;
|
|
47
|
+
private readonly levelOrder: Record<LogLevel, number> = {
|
|
48
|
+
trace: 0,
|
|
49
|
+
debug: 1,
|
|
50
|
+
info: 2,
|
|
51
|
+
warn: 3,
|
|
52
|
+
error: 4,
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
constructor(scope: string = 'CLI', level: LogLevel = 'info') {
|
|
56
|
+
this.scope = scope;
|
|
57
|
+
this.currentLevel = level;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
private shouldLog(level: LogLevel): boolean {
|
|
61
|
+
return this.levelOrder[level] >= this.levelOrder[this.currentLevel];
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
trace(message: string, data?: Record<string, unknown>): void {
|
|
65
|
+
if (this.shouldLog('trace')) {
|
|
66
|
+
console.log(chalk.gray(`[${this.scope}] ${message}`), data ? data : '');
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
debug(message: string, data?: Record<string, unknown>): void {
|
|
71
|
+
if (this.shouldLog('debug')) {
|
|
72
|
+
console.log(chalk.gray(`[${this.scope}] ${message}`), data ? data : '');
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
info(message: string, data?: Record<string, unknown>): void {
|
|
77
|
+
if (this.shouldLog('info')) {
|
|
78
|
+
console.log(chalk.blue(`[${this.scope}] ${message}`), data ? data : '');
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
warn(message: string, data?: Record<string, unknown>): void {
|
|
83
|
+
if (this.shouldLog('warn')) {
|
|
84
|
+
console.warn(chalk.yellow(`[${this.scope}] ⚠ ${message}`), data ? data : '');
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
error(message: string, error?: Error, data?: Record<string, unknown>): void {
|
|
89
|
+
if (this.shouldLog('error')) {
|
|
90
|
+
console.error(chalk.red(`[${this.scope}] ✖ ${message}`));
|
|
91
|
+
if (error?.stack) {
|
|
92
|
+
console.error(chalk.gray(error.stack));
|
|
93
|
+
}
|
|
94
|
+
if (data) {
|
|
95
|
+
console.error(chalk.gray(JSON.stringify(data, null, 2)));
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
child(scope: string): ILogger {
|
|
101
|
+
return new CliLogger(`${this.scope}:${scope}`, this.currentLevel);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
getLevel(): LogLevel {
|
|
105
|
+
return this.currentLevel;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
setLevel(level: LogLevel): void {
|
|
109
|
+
this.currentLevel = level;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Factory for creating CLI loggers.
|
|
115
|
+
*/
|
|
116
|
+
class CliLoggerFactory implements ILoggerFactory {
|
|
117
|
+
private readonly level: LogLevel;
|
|
118
|
+
private readonly rootLogger: CliLogger;
|
|
119
|
+
|
|
120
|
+
constructor(level: LogLevel = 'info') {
|
|
121
|
+
this.level = level;
|
|
122
|
+
this.rootLogger = new CliLogger('CLI', level);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
createLogger(scope: string): ILogger {
|
|
126
|
+
return new CliLogger(scope, this.level);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
getRootLogger(): ILogger {
|
|
130
|
+
return this.rootLogger;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
setGlobalLevel(level: LogLevel): void {
|
|
134
|
+
this.rootLogger.setLevel(level);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
139
|
+
// Service Container Management
|
|
140
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
141
|
+
|
|
142
|
+
let globalContainer: ServiceContainer | null = null;
|
|
143
|
+
let globalLogLevel: LogLevel = 'warn'; // CLI defaults to quiet
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Sets the global log level for CLI operations.
|
|
147
|
+
*/
|
|
148
|
+
export function setLogLevel(level: LogLevel): void {
|
|
149
|
+
globalLogLevel = level;
|
|
150
|
+
if (globalContainer) {
|
|
151
|
+
// Reset container to re-create with new log level
|
|
152
|
+
globalContainer = null;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Creates platform adapters for CLI environment.
|
|
158
|
+
*/
|
|
159
|
+
function createCliAdapters(): PlatformAdapters {
|
|
160
|
+
return {
|
|
161
|
+
fileSystem: new NodeFileSystem(),
|
|
162
|
+
loggerFactory: new CliLoggerFactory(globalLogLevel),
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Gets the global service container, creating it if needed.
|
|
168
|
+
*
|
|
169
|
+
* @param options - Optional configuration overrides
|
|
170
|
+
* @returns Configured ServiceContainer
|
|
171
|
+
*/
|
|
172
|
+
export function getServiceContainer(options?: {
|
|
173
|
+
workspaceRoot?: string;
|
|
174
|
+
logLevel?: LogLevel;
|
|
175
|
+
}): ServiceContainer {
|
|
176
|
+
if (options?.logLevel) {
|
|
177
|
+
globalLogLevel = options.logLevel;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const workspaceRoot = options?.workspaceRoot ?? process.cwd();
|
|
181
|
+
|
|
182
|
+
// Create new container if needed or if workspaceRoot changed
|
|
183
|
+
if (!globalContainer || globalContainer.workspaceRoot !== workspaceRoot) {
|
|
184
|
+
globalContainer = createServiceContainer({
|
|
185
|
+
workspaceRoot,
|
|
186
|
+
adapters: createCliAdapters(),
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return globalContainer;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Creates a fresh service container (useful for testing or scoped operations).
|
|
195
|
+
*
|
|
196
|
+
* @param config - Container configuration
|
|
197
|
+
* @returns New ServiceContainer instance
|
|
198
|
+
*/
|
|
199
|
+
export function createCliServiceContainer(config?: ServiceContainerConfig): ServiceContainer {
|
|
200
|
+
return createServiceContainer({
|
|
201
|
+
workspaceRoot: config?.workspaceRoot ?? process.cwd(),
|
|
202
|
+
adapters: {
|
|
203
|
+
...createCliAdapters(),
|
|
204
|
+
...config?.adapters,
|
|
205
|
+
},
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Resets the global container (useful for testing).
|
|
211
|
+
*/
|
|
212
|
+
export function resetServiceContainer(): void {
|
|
213
|
+
globalContainer = null;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
217
|
+
// Utility Exports
|
|
218
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
219
|
+
|
|
220
|
+
export { CliLogger, CliLoggerFactory };
|
|
221
|
+
export type { LogLevel };
|