locai 2.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.
Files changed (72) hide show
  1. package/README.md +928 -0
  2. package/dist/cli.js +718 -0
  3. package/dist/commands/review.js +357 -0
  4. package/dist/commands/translator.js +29 -0
  5. package/dist/config/index.js +52 -0
  6. package/dist/core/adapters/base-adapter.js +1 -0
  7. package/dist/core/adapters/factory.js +44 -0
  8. package/dist/core/adapters/json-adapter.js +20 -0
  9. package/dist/core/adapters/po-adapter.js +50 -0
  10. package/dist/core/adapters/properties-adapter.js +31 -0
  11. package/dist/core/adapters/yaml-adapter.js +24 -0
  12. package/dist/core/context-processor.js +247 -0
  13. package/dist/core/detection/framework-detector.js +56 -0
  14. package/dist/core/detection/structure-detector.js +30 -0
  15. package/dist/core/fallback-provider.js +442 -0
  16. package/dist/core/orchestrator.js +438 -0
  17. package/dist/core/pipeline/Pipeline.js +48 -0
  18. package/dist/core/pipeline/PipelineStep.js +13 -0
  19. package/dist/core/pipeline/context.js +21 -0
  20. package/dist/core/pipeline/steps/CacheReadStep.js +80 -0
  21. package/dist/core/pipeline/steps/CacheWriteStep.js +32 -0
  22. package/dist/core/pipeline/steps/ConfidenceCheckStep.js +67 -0
  23. package/dist/core/pipeline/steps/GlossaryPostStep.js +17 -0
  24. package/dist/core/pipeline/steps/GlossaryPreStep.js +20 -0
  25. package/dist/core/pipeline/steps/InputValidationStep.js +26 -0
  26. package/dist/core/pipeline/steps/QualityCheckStep.js +48 -0
  27. package/dist/core/pipeline/steps/ToneCheckStep.js +58 -0
  28. package/dist/core/pipeline/steps/TranslationStep.js +65 -0
  29. package/dist/core/pipeline/steps/VectorCacheReadStep.js +84 -0
  30. package/dist/core/pipeline/steps/VectorCacheWriteStep.js +42 -0
  31. package/dist/core/pipeline/steps/__tests__/ToneCheckStep.test.js +100 -0
  32. package/dist/core/provider-factory.js +139 -0
  33. package/dist/providers/base-provider.js +230 -0
  34. package/dist/providers/dashscope.js +128 -0
  35. package/dist/providers/deepseek.js +111 -0
  36. package/dist/providers/gemini.js +135 -0
  37. package/dist/providers/openai.js +203 -0
  38. package/dist/providers/xai.js +111 -0
  39. package/dist/services/embedding-provider.js +50 -0
  40. package/dist/services/translation-service.js +609 -0
  41. package/dist/services/vector-store.js +79 -0
  42. package/dist/utils/ai-context-analyzer.js +460 -0
  43. package/dist/utils/confidence-scorer.js +277 -0
  44. package/dist/utils/error-helper.js +386 -0
  45. package/dist/utils/file-manager.js +357 -0
  46. package/dist/utils/glossary-manager.js +194 -0
  47. package/dist/utils/graceful-shutdown.js +118 -0
  48. package/dist/utils/input-validator.js +663 -0
  49. package/dist/utils/logger.js +374 -0
  50. package/dist/utils/object-transformer.js +177 -0
  51. package/dist/utils/progress-tracker.js +277 -0
  52. package/dist/utils/prompt-templates.js +415 -0
  53. package/dist/utils/quality/__tests__/tone-verifier.test.js +91 -0
  54. package/dist/utils/quality/base-checker.js +73 -0
  55. package/dist/utils/quality/code-block-checker.js +214 -0
  56. package/dist/utils/quality/html-tag-checker.js +64 -0
  57. package/dist/utils/quality/index.js +220 -0
  58. package/dist/utils/quality/length-checker.js +140 -0
  59. package/dist/utils/quality/markdown-checker.js +220 -0
  60. package/dist/utils/quality/placeholder-checker.js +142 -0
  61. package/dist/utils/quality/punctuation-checker.js +50 -0
  62. package/dist/utils/quality/quote-balance-checker.js +143 -0
  63. package/dist/utils/quality/special-characters-checker.js +229 -0
  64. package/dist/utils/quality/style-guide-checker.js +192 -0
  65. package/dist/utils/quality/text-sanitizer.js +120 -0
  66. package/dist/utils/quality/tone-verifier.js +91 -0
  67. package/dist/utils/rate-limiter.js +398 -0
  68. package/dist/utils/retry-helper.js +166 -0
  69. package/dist/utils/state-manager.js +162 -0
  70. package/dist/utils/statistics-manager.js +51 -0
  71. package/dist/utils/ui-manager.js +98 -0
  72. package/package.json +80 -0
@@ -0,0 +1,374 @@
1
+ /**
2
+ * Advanced Logging System.
3
+ * Implements saveErrorLogs, logDirectory, logRotation, and other logging features.
4
+ */
5
+ import path from "path";
6
+ import { promises as fsPromises } from "fs";
7
+ class Logger {
8
+ config;
9
+ logFiles;
10
+ currentLogSizes;
11
+ initialized;
12
+ /**
13
+ * Create a new Logger instance.
14
+ * @param config - Logger configuration.
15
+ */
16
+ constructor(config = {}) {
17
+ this.config = {
18
+ verbose: config.verbose || false,
19
+ diagnosticsLevel: config.diagnosticsLevel || "minimal",
20
+ outputFormat: config.outputFormat || "pretty",
21
+ saveErrorLogs: config.saveErrorLogs !== false,
22
+ logDirectory: config.logDirectory || "./logs",
23
+ includeTimestamps: config.includeTimestamps !== false,
24
+ logRotation: {
25
+ enabled: config.logRotation?.enabled !== false,
26
+ maxFiles: config.logRotation?.maxFiles || 5,
27
+ maxSize: this.parseSize(config.logRotation?.maxSize || "10MB"),
28
+ },
29
+ };
30
+ this.logFiles = {
31
+ error: path.join(this.config.logDirectory, "errors.log"),
32
+ warning: path.join(this.config.logDirectory, "warnings.log"),
33
+ info: path.join(this.config.logDirectory, "info.log"),
34
+ debug: path.join(this.config.logDirectory, "debug.log"),
35
+ };
36
+ this.currentLogSizes = {};
37
+ this.initialized = false;
38
+ }
39
+ /**
40
+ * Initialize logger (create directories, check rotation).
41
+ */
42
+ async initialize() {
43
+ if (this.initialized)
44
+ return;
45
+ try {
46
+ // Create log directory if it doesn't exist
47
+ if (this.config.saveErrorLogs) {
48
+ await fsPromises.mkdir(this.config.logDirectory, { recursive: true });
49
+ // Check log rotation for existing files
50
+ if (this.config.logRotation.enabled) {
51
+ await this.checkAndRotateLogs();
52
+ }
53
+ }
54
+ this.initialized = true;
55
+ }
56
+ catch (error) {
57
+ console.warn(`Logger initialization warning: ${error.message}`);
58
+ }
59
+ }
60
+ /**
61
+ * Parse size string (e.g., "10MB") to bytes.
62
+ * @param sizeStr - Size string to parse.
63
+ */
64
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
65
+ parseSize(sizeStr) {
66
+ if (typeof sizeStr === "number")
67
+ return sizeStr;
68
+ if (typeof sizeStr !== "string")
69
+ return 10 * 1024 * 1024;
70
+ const units = {
71
+ B: 1,
72
+ KB: 1024,
73
+ MB: 1024 * 1024,
74
+ GB: 1024 * 1024 * 1024,
75
+ };
76
+ const match = sizeStr.match(/^(\d+(?:\.\d+)?)\s*([A-Z]+)$/i);
77
+ if (!match)
78
+ return 10 * 1024 * 1024; // Default 10MB
79
+ const [, size, unit] = match;
80
+ return parseFloat(size) * (units[unit.toUpperCase()] || 1);
81
+ }
82
+ /**
83
+ * Check and rotate logs if needed.
84
+ */
85
+ async checkAndRotateLogs() {
86
+ for (const [level, logPath] of Object.entries(this.logFiles)) {
87
+ try {
88
+ const stats = await fsPromises.stat(logPath);
89
+ this.currentLogSizes[level] = stats.size;
90
+ if (stats.size >= this.config.logRotation.maxSize) {
91
+ await this.rotateLog(logPath);
92
+ }
93
+ }
94
+ catch (error) {
95
+ console.warn(`Log rotation failed for ${logPath}: ${error.message}`);
96
+ this.currentLogSizes[level] = 0;
97
+ }
98
+ }
99
+ }
100
+ /**
101
+ * Rotate a log file.
102
+ * @param logPath - Path to the log file.
103
+ */
104
+ async rotateLog(logPath) {
105
+ try {
106
+ const dir = path.dirname(logPath);
107
+ const basename = path.basename(logPath, ".log");
108
+ const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
109
+ // Rename current log
110
+ const archivedPath = path.join(dir, `${basename}.${timestamp}.log`);
111
+ await fsPromises.rename(logPath, archivedPath);
112
+ // Clean up old logs if exceeding maxFiles
113
+ await this.cleanupOldLogs(dir, basename);
114
+ // Reset size tracker
115
+ const level = Object.keys(this.logFiles).find((key) => this.logFiles[key] === logPath);
116
+ if (level) {
117
+ this.currentLogSizes[level] = 0;
118
+ }
119
+ }
120
+ catch (error) {
121
+ console.warn(`Log rotation failed for ${logPath}: ${error.message}`);
122
+ }
123
+ }
124
+ /**
125
+ * Clean up old log files.
126
+ * @param dir - Directory containing log files.
127
+ * @param basename - Basename of the log file.
128
+ */
129
+ async cleanupOldLogs(dir, basename) {
130
+ try {
131
+ const files = await fsPromises.readdir(dir);
132
+ const logFiles = files
133
+ .filter((file) => file.startsWith(basename) && file.endsWith(".log"))
134
+ .map((file) => ({
135
+ name: file,
136
+ path: path.join(dir, file),
137
+ }));
138
+ // Sort by modification time
139
+ const fileStats = await Promise.all(logFiles.map(async (file) => ({
140
+ ...file,
141
+ stats: await fsPromises.stat(file.path),
142
+ })));
143
+ fileStats.sort((a, b) => b.stats.mtime.getTime() - a.stats.mtime.getTime());
144
+ // Delete oldest files if exceeding maxFiles
145
+ if (fileStats.length > this.config.logRotation.maxFiles) {
146
+ const filesToDelete = fileStats.slice(this.config.logRotation.maxFiles);
147
+ for (const file of filesToDelete) {
148
+ await fsPromises.unlink(file.path);
149
+ }
150
+ }
151
+ }
152
+ catch (error) {
153
+ console.warn(`Cleanup old logs failed: ${error.message}`);
154
+ }
155
+ }
156
+ /**
157
+ * Format log message.
158
+ * @param level - Log level.
159
+ * @param message - Log message.
160
+ * @param data - Additional data to log.
161
+ */
162
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
163
+ formatMessage(level, message, data = null) {
164
+ const timestamp = this.config.includeTimestamps ? new Date().toISOString() : null;
165
+ switch (this.config.outputFormat) {
166
+ case "json":
167
+ return JSON.stringify({
168
+ timestamp,
169
+ level,
170
+ message,
171
+ data,
172
+ });
173
+ case "simple":
174
+ return `[${level.toUpperCase()}] ${message}`;
175
+ case "pretty":
176
+ default: {
177
+ const timeStr = timestamp ? `[${timestamp}] ` : "";
178
+ const levelStr = `[${level.toUpperCase()}]`;
179
+ const dataStr = data ? `\n${JSON.stringify(data, null, 2)}` : "";
180
+ return `${timeStr}${levelStr} ${message}${dataStr}`;
181
+ }
182
+ }
183
+ }
184
+ /**
185
+ * Write to log file.
186
+ * @param level - Log level.
187
+ * @param message - Log message.
188
+ * @param data - Additional data to log.
189
+ */
190
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
191
+ async writeToFile(level, message, data = null) {
192
+ if (!this.config.saveErrorLogs)
193
+ return;
194
+ await this.initialize();
195
+ const logPath = this.logFiles[level];
196
+ if (!logPath)
197
+ return;
198
+ try {
199
+ const formattedMessage = this.formatMessage(level, message, data);
200
+ const logEntry = `${formattedMessage}\n`;
201
+ await fsPromises.appendFile(logPath, logEntry, "utf8");
202
+ // Update size tracker
203
+ this.currentLogSizes[level] = (this.currentLogSizes[level] || 0) + logEntry.length;
204
+ // Check if rotation is needed
205
+ if (this.config.logRotation.enabled &&
206
+ this.currentLogSizes[level] >= this.config.logRotation.maxSize) {
207
+ await this.rotateLog(logPath);
208
+ }
209
+ }
210
+ catch (error) {
211
+ console.warn(`Failed to write to log file: ${error.message}`);
212
+ }
213
+ }
214
+ /**
215
+ * Simple console log with debug awareness.
216
+ * @param message - Message to log
217
+ * @param debugOnly - Only log if debug mode is enabled
218
+ */
219
+ async log(message, debugOnly = false) {
220
+ if (debugOnly && !this.config.verbose && !process.env.DEBUG) {
221
+ return;
222
+ }
223
+ console.log(message);
224
+ }
225
+ /**
226
+ * Log error.
227
+ * @param message - Error message.
228
+ * @param data - Additional data.
229
+ */
230
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
231
+ async error(message, data = null) {
232
+ console.error(`ERROR: ${message}`);
233
+ if (data && this.config.verbose) {
234
+ console.error(data);
235
+ }
236
+ await this.writeToFile("error", message, data);
237
+ }
238
+ /**
239
+ * Log warning.
240
+ * @param message - Warning message.
241
+ * @param data - Additional data.
242
+ */
243
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
244
+ async warn(message, data = null) {
245
+ console.warn(`WARNING: ${message}`);
246
+ if (data && this.config.verbose) {
247
+ console.warn(data);
248
+ }
249
+ await this.writeToFile("warning", message, data);
250
+ }
251
+ /**
252
+ * Log info.
253
+ * @param message - Info message.
254
+ * @param data - Additional data.
255
+ */
256
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
257
+ async info(message, data = null) {
258
+ if (this.config.diagnosticsLevel !== "minimal" || this.config.verbose) {
259
+ console.log(`INFO: ${message}`);
260
+ if (data && this.config.verbose) {
261
+ console.log(data);
262
+ }
263
+ }
264
+ await this.writeToFile("info", message, data);
265
+ }
266
+ /**
267
+ * Log debug.
268
+ * @param message - Debug message.
269
+ * @param data - Additional data.
270
+ */
271
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
272
+ async debug(message, data = null) {
273
+ if (this.config.verbose || this.config.diagnosticsLevel === "detailed") {
274
+ console.log(`DEBUG: ${message}`);
275
+ if (data) {
276
+ console.log(data);
277
+ }
278
+ }
279
+ await this.writeToFile("debug", message, data);
280
+ }
281
+ /**
282
+ * Log based on diagnostics level.
283
+ * @param level - Log level.
284
+ * @param message - Log message.
285
+ * @param data - Additional data.
286
+ */
287
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
288
+ async diagnostics(level, message, data = null) {
289
+ const levels = {
290
+ minimal: 1,
291
+ normal: 2,
292
+ detailed: 3,
293
+ };
294
+ const currentLevel = levels[this.config.diagnosticsLevel] || 1;
295
+ const messageLevel = levels[level] || 2;
296
+ if (messageLevel <= currentLevel) {
297
+ await this.info(message, data);
298
+ }
299
+ }
300
+ /**
301
+ * Get log statistics.
302
+ * @returns Object containing log stats.
303
+ */
304
+ async getLogStats() {
305
+ const stats = {};
306
+ for (const [level, logPath] of Object.entries(this.logFiles)) {
307
+ try {
308
+ const fileStats = await fsPromises.stat(logPath);
309
+ stats[level] = {
310
+ size: fileStats.size,
311
+ sizeFormatted: this.formatSize(fileStats.size),
312
+ modified: fileStats.mtime,
313
+ };
314
+ }
315
+ catch (error) {
316
+ console.warn(`Failed to get stats for ${logPath}: ${error.message}`);
317
+ stats[level] = {
318
+ size: 0,
319
+ sizeFormatted: "0 B",
320
+ modified: null,
321
+ };
322
+ }
323
+ }
324
+ return stats;
325
+ }
326
+ /**
327
+ * Format size to human-readable.
328
+ * @param bytes - Size in bytes.
329
+ */
330
+ formatSize(bytes) {
331
+ const units = ["B", "KB", "MB", "GB"];
332
+ let size = bytes;
333
+ let unitIndex = 0;
334
+ while (size >= 1024 && unitIndex < units.length - 1) {
335
+ size /= 1024;
336
+ unitIndex++;
337
+ }
338
+ return `${size.toFixed(2)} ${units[unitIndex]}`;
339
+ }
340
+ /**
341
+ * Clear all logs.
342
+ */
343
+ async clearLogs() {
344
+ for (const logPath of Object.values(this.logFiles)) {
345
+ try {
346
+ await fsPromises.unlink(logPath);
347
+ }
348
+ catch (error) {
349
+ console.warn(`Failed to clear log ${logPath}: ${error.message}`);
350
+ }
351
+ }
352
+ this.currentLogSizes = {};
353
+ }
354
+ }
355
+ // Create singleton instance
356
+ let loggerInstance = null;
357
+ export function getLogger(config = null) {
358
+ if (!loggerInstance || config) {
359
+ loggerInstance = new Logger(config || {});
360
+ }
361
+ return loggerInstance;
362
+ }
363
+ /**
364
+ * Quick debug-aware logging helper.
365
+ * @param message - Message to log.
366
+ * @param debugOnly - Only log in debug/verbose mode.
367
+ */
368
+ export function log(message, debugOnly = false) {
369
+ if (debugOnly && !process.env.DEBUG && !process.env.VERBOSE) {
370
+ return;
371
+ }
372
+ console.log(message);
373
+ }
374
+ export default Logger;
@@ -0,0 +1,177 @@
1
+ /**
2
+ * Utility class for object manipulation.
3
+ */
4
+ class ObjectTransformer {
5
+ /**
6
+ * Flatten a nested object into a single-level object with dot notation keys.
7
+ * @param obj - The object to flatten.
8
+ * @param prefix - Prefix for keys (used internally).
9
+ * @param maxDepth - Maximum recursion depth.
10
+ * @returns Flattened object.
11
+ */
12
+ static flatten(obj, prefix = "", maxDepth = 20) {
13
+ if (!obj || typeof obj !== "object" || Array.isArray(obj)) {
14
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
15
+ return prefix ? { [prefix]: obj } : {};
16
+ }
17
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
18
+ const result = {};
19
+ const stack = [{ obj, prefix, depth: 0 }];
20
+ while (stack.length > 0) {
21
+ const stackItem = stack.pop();
22
+ if (!stackItem)
23
+ continue;
24
+ const { obj: currentObj, prefix: currentPrefix, depth } = stackItem;
25
+ if (depth > maxDepth) {
26
+ result[currentPrefix] = "[Object too deep]";
27
+ continue;
28
+ }
29
+ const keys = Object.keys(currentObj);
30
+ const keysLength = keys.length;
31
+ for (let i = 0; i < keysLength; i++) {
32
+ const key = keys[i];
33
+ const value = currentObj[key];
34
+ const newKey = currentPrefix ? `${currentPrefix}.${key}` : key;
35
+ if (value !== null &&
36
+ typeof value === "object" &&
37
+ !Array.isArray(value) &&
38
+ value.constructor === Object) {
39
+ stack.push({ obj: value, prefix: newKey, depth: depth + 1 });
40
+ }
41
+ else {
42
+ result[newKey] = value;
43
+ }
44
+ }
45
+ }
46
+ return result;
47
+ }
48
+ /**
49
+ * Unflatten an object with dot notation keys into a nested object.
50
+ * @param obj - The flattened object.
51
+ * @returns Nested object.
52
+ */
53
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
54
+ static unflatten(obj) {
55
+ if (!obj || typeof obj !== "object") {
56
+ return {};
57
+ }
58
+ const result = {};
59
+ const keys = Object.keys(obj);
60
+ const keysLength = keys.length;
61
+ keys.sort();
62
+ for (let keyIndex = 0; keyIndex < keysLength; keyIndex++) {
63
+ const key = keys[keyIndex];
64
+ const value = obj[key];
65
+ const keyParts = key.split(".");
66
+ const partsLength = keyParts.length;
67
+ if (partsLength === 0)
68
+ continue;
69
+ let hasEmptyPart = false;
70
+ for (let i = 0; i < partsLength; i++) {
71
+ if (keyParts[i] === "") {
72
+ hasEmptyPart = true;
73
+ break;
74
+ }
75
+ }
76
+ if (hasEmptyPart)
77
+ continue;
78
+ let current = result;
79
+ const lastIndex = partsLength - 1;
80
+ for (let i = 0; i < lastIndex; i++) {
81
+ const part = keyParts[i];
82
+ if (current[part] == null ||
83
+ typeof current[part] !== "object" ||
84
+ Array.isArray(current[part])) {
85
+ current[part] = {};
86
+ }
87
+ current = current[part];
88
+ }
89
+ current[keyParts[lastIndex]] = value;
90
+ }
91
+ return result;
92
+ }
93
+ /**
94
+ * Create a deep copy of an object.
95
+ * @param obj - The object to clone.
96
+ * @param seen - Internal set to track circular references.
97
+ * @returns Deep copy of the object.
98
+ */
99
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
100
+ static deepClone(obj, seen = new WeakSet()) {
101
+ if (obj === null || typeof obj !== "object") {
102
+ return obj;
103
+ }
104
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
105
+ if (seen.has(obj)) {
106
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
107
+ return {};
108
+ }
109
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
110
+ seen.add(obj);
111
+ if (Array.isArray(obj)) {
112
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
113
+ const result = obj.map((item) => this.deepClone(item, seen));
114
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
115
+ seen.delete(obj);
116
+ return result;
117
+ }
118
+ if (obj instanceof Date) {
119
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
120
+ seen.delete(obj);
121
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
122
+ return new Date(obj.getTime());
123
+ }
124
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
125
+ if (obj.constructor !== Object) {
126
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
127
+ seen.delete(obj);
128
+ return obj;
129
+ }
130
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
131
+ const result = {};
132
+ for (const key of Object.keys(obj)) {
133
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
134
+ result[key] = this.deepClone(obj[key], seen);
135
+ }
136
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
137
+ seen.delete(obj);
138
+ return result;
139
+ }
140
+ /**
141
+ * deeply merge two objects.
142
+ * @param target - The target object.
143
+ * @param source - The source object.
144
+ * @param overwriteArrays - Whether to overwrite arrays or merge them.
145
+ * @returns Merged object.
146
+ */
147
+ static mergeObjects(target, source, overwriteArrays = false) {
148
+ if (!target || !source || typeof target !== "object" || typeof source !== "object") {
149
+ return target;
150
+ }
151
+ const result = this.deepClone(target);
152
+ for (const key of Object.keys(source)) {
153
+ const sourceValue = source[key];
154
+ const targetValue = result[key];
155
+ if (sourceValue === undefined) {
156
+ continue;
157
+ }
158
+ if (Array.isArray(sourceValue)) {
159
+ result[key] = overwriteArrays
160
+ ? [...sourceValue]
161
+ : Array.isArray(targetValue)
162
+ ? [...targetValue, ...sourceValue]
163
+ : [...sourceValue];
164
+ }
165
+ else if (sourceValue &&
166
+ typeof sourceValue === "object" &&
167
+ sourceValue.constructor === Object) {
168
+ result[key] = this.mergeObjects(targetValue || {}, sourceValue, overwriteArrays);
169
+ }
170
+ else {
171
+ result[key] = sourceValue;
172
+ }
173
+ }
174
+ return result;
175
+ }
176
+ }
177
+ export default ObjectTransformer;