i18ntk 2.3.4 → 2.3.5
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 +9 -8
- package/main/i18ntk-backup-class.js +80 -66
- package/package.json +5 -4
- package/settings/settings-manager.js +52 -32
- package/utils/npm-version-warning.js +5 -0
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# i18ntk v2.3.
|
|
1
|
+
# i18ntk v2.3.5
|
|
2
2
|
|
|
3
3
|
Zero-dependency internationalization toolkit for setup, scanning, analysis, validation, usage tracking, and translation completion.
|
|
4
4
|
|
|
@@ -9,14 +9,15 @@ Zero-dependency internationalization toolkit for setup, scanning, analysis, vali
|
|
|
9
9
|
[](https://nodejs.org)
|
|
10
10
|
[](https://www.npmjs.com/package/i18ntk)
|
|
11
11
|
[](LICENSE)
|
|
12
|
-
[](https://socket.dev/npm/package/i18ntk/overview/2.3.5)
|
|
13
13
|
|
|
14
14
|
## Upgrade Notice
|
|
15
15
|
|
|
16
|
-
Versions earlier than `2.3.
|
|
17
|
-
They are considered unsupported for production use. Upgrade to `2.3.
|
|
18
|
-
The CLI
|
|
19
|
-
Set `
|
|
16
|
+
Versions earlier than `2.3.5` may contain known stability and security issues.
|
|
17
|
+
They are considered unsupported for production use. Upgrade to `2.3.5` or newer.
|
|
18
|
+
The CLI can check npm registry metadata and warn when your installed version is out of date.
|
|
19
|
+
Set `I18NTK_ENABLE_UPDATE_CHECK=true` to enable this behavior.
|
|
20
|
+
Set `I18NTK_DISABLE_UPDATE_CHECK=true` to force-disable it in restricted/offline environments.
|
|
20
21
|
Set `I18NTK_DISABLE_AUTOSAVE=1` in server/runtime environments to keep config in memory and skip disk writes.
|
|
21
22
|
|
|
22
23
|
## What i18ntk Does
|
|
@@ -154,7 +155,7 @@ Example `.i18ntk-config`:
|
|
|
154
155
|
|
|
155
156
|
```json
|
|
156
157
|
{
|
|
157
|
-
"version": "2.3.
|
|
158
|
+
"version": "2.3.5",
|
|
158
159
|
"sourceDir": "./locales",
|
|
159
160
|
"i18nDir": "./locales",
|
|
160
161
|
"outputDir": "./i18ntk-reports",
|
|
@@ -177,7 +178,7 @@ See [docs/api/CONFIGURATION.md](docs/api/CONFIGURATION.md) for the full configur
|
|
|
177
178
|
- [Runtime API Guide](docs/runtime.md)
|
|
178
179
|
- [Scanner Guide](docs/scanner-guide.md)
|
|
179
180
|
- [Environment Variables](docs/environment-variables.md)
|
|
180
|
-
- [Migration Guide v2.3.
|
|
181
|
+
- [Migration Guide v2.3.5](docs/migration-guide-v2.3.5.md)
|
|
181
182
|
- [Optimization Prompt](docs/development/package-optimization-prompt.md)
|
|
182
183
|
|
|
183
184
|
## License
|
|
@@ -15,12 +15,20 @@ const SecurityUtils = require('../utils/security');
|
|
|
15
15
|
*
|
|
16
16
|
* Class-based implementation of backup functionality for use with CommandRouter
|
|
17
17
|
*/
|
|
18
|
-
class I18nBackup {
|
|
18
|
+
class I18nBackup {
|
|
19
19
|
constructor(config = {}) {
|
|
20
20
|
this.config = config;
|
|
21
21
|
this.backupDir = path.join(process.cwd(), 'i18ntk-backups');
|
|
22
22
|
this.maxBackups = Math.min(Math.max(parseInt(config.backup?.maxBackups, 10) || 1, 1), 3);
|
|
23
|
-
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
validateInProject(targetPath, label = 'path') {
|
|
26
|
+
const validated = SecurityUtils.validatePath(targetPath, process.cwd());
|
|
27
|
+
if (!validated) {
|
|
28
|
+
throw new Error(`Invalid ${label}: ${targetPath}`);
|
|
29
|
+
}
|
|
30
|
+
return validated;
|
|
31
|
+
}
|
|
24
32
|
|
|
25
33
|
/**
|
|
26
34
|
* Main run method for the backup command
|
|
@@ -79,13 +87,15 @@ Options:
|
|
|
79
87
|
}
|
|
80
88
|
|
|
81
89
|
// Command handlers
|
|
82
|
-
async handleCreate(options = {}) {
|
|
83
|
-
// Use absolute path for the locales directory
|
|
84
|
-
const
|
|
85
|
-
const
|
|
86
|
-
const
|
|
87
|
-
const
|
|
88
|
-
const
|
|
90
|
+
async handleCreate(options = {}) {
|
|
91
|
+
// Use absolute path for the locales directory
|
|
92
|
+
const requestedDir = (options._ && options._[1]) || options.dir || path.join(process.cwd(), 'locales');
|
|
93
|
+
const requestedOutputDir = options.output || this.backupDir;
|
|
94
|
+
const dir = this.validateInProject(path.resolve(process.cwd(), requestedDir), 'source directory');
|
|
95
|
+
const outputDir = this.validateInProject(path.resolve(process.cwd(), requestedOutputDir), 'backup directory');
|
|
96
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
97
|
+
const backupName = `backup-${timestamp}.json`;
|
|
98
|
+
const backupPath = this.validateInProject(path.join(outputDir, backupName), 'backup file');
|
|
89
99
|
|
|
90
100
|
// Log the paths for debugging
|
|
91
101
|
logger.debug(`Source directory: ${dir}`);
|
|
@@ -104,7 +114,7 @@ Options:
|
|
|
104
114
|
}
|
|
105
115
|
|
|
106
116
|
// Validate directory
|
|
107
|
-
const sourceDir =
|
|
117
|
+
const sourceDir = dir;
|
|
108
118
|
try {
|
|
109
119
|
const stats = await fsp.stat(sourceDir);
|
|
110
120
|
if (!stats.isDirectory()) {
|
|
@@ -163,16 +173,17 @@ Options:
|
|
|
163
173
|
};
|
|
164
174
|
}
|
|
165
175
|
|
|
166
|
-
async handleRestore(options = {}) {
|
|
167
|
-
const backupFile = options._ && options._[1];
|
|
168
|
-
if (!backupFile) {
|
|
169
|
-
throw new Error('Backup file path is required');
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
const backupPath = path.resolve(process.cwd(), backupFile);
|
|
173
|
-
const outputDir =
|
|
174
|
-
? path.resolve(process.cwd(), options.output)
|
|
175
|
-
|
|
176
|
+
async handleRestore(options = {}) {
|
|
177
|
+
const backupFile = options._ && options._[1];
|
|
178
|
+
if (!backupFile) {
|
|
179
|
+
throw new Error('Backup file path is required');
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const backupPath = this.validateInProject(path.resolve(process.cwd(), backupFile), 'backup file');
|
|
183
|
+
const outputDir = this.validateInProject(
|
|
184
|
+
options.output ? path.resolve(process.cwd(), options.output) : path.join(process.cwd(), 'restored'),
|
|
185
|
+
'restore output directory'
|
|
186
|
+
);
|
|
176
187
|
|
|
177
188
|
// Validate backup file
|
|
178
189
|
if (!SecurityUtils.safeExistsSync(backupPath, process.cwd())) {
|
|
@@ -183,21 +194,21 @@ Options:
|
|
|
183
194
|
|
|
184
195
|
try {
|
|
185
196
|
// Read the backup file
|
|
186
|
-
const backupData = await
|
|
187
|
-
const translations = JSON.parse(backupData);
|
|
197
|
+
const backupData = await fsp.readFile(backupPath, 'utf8');
|
|
198
|
+
const translations = JSON.parse(backupData);
|
|
188
199
|
|
|
189
200
|
// Create output directory if it doesn't exist
|
|
190
201
|
try {
|
|
191
|
-
await
|
|
192
|
-
} catch (err) {
|
|
193
|
-
if (err.code !== 'EEXIST') throw err;
|
|
194
|
-
}
|
|
202
|
+
await fsp.mkdir(outputDir, { recursive: true });
|
|
203
|
+
} catch (err) {
|
|
204
|
+
if (err.code !== 'EEXIST') throw err;
|
|
205
|
+
}
|
|
195
206
|
|
|
196
207
|
// Write the restored files
|
|
197
|
-
for (const [file, content] of Object.entries(translations)) {
|
|
198
|
-
const filePath = path.join(outputDir, file);
|
|
199
|
-
await
|
|
200
|
-
}
|
|
208
|
+
for (const [file, content] of Object.entries(translations)) {
|
|
209
|
+
const filePath = this.validateInProject(path.join(outputDir, file), 'restore file');
|
|
210
|
+
await fsp.writeFile(filePath, JSON.stringify(content, null, 2), 'utf8');
|
|
211
|
+
}
|
|
201
212
|
|
|
202
213
|
logger.success('Backup restored successfully');
|
|
203
214
|
logger.info(` Restored ${Object.keys(translations).length} files to: ${outputDir}`);
|
|
@@ -214,12 +225,13 @@ Options:
|
|
|
214
225
|
}
|
|
215
226
|
}
|
|
216
227
|
|
|
217
|
-
async handleList() {
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
228
|
+
async handleList() {
|
|
229
|
+
const backupDir = this.validateInProject(this.backupDir, 'backup directory');
|
|
230
|
+
try {
|
|
231
|
+
// Ensure backup directory exists
|
|
232
|
+
try {
|
|
233
|
+
await fsp.access(backupDir);
|
|
234
|
+
} catch (err) {
|
|
223
235
|
if (err.code === 'ENOENT') {
|
|
224
236
|
logger.warn('No backups found. The backup directory does not exist yet.');
|
|
225
237
|
} else {
|
|
@@ -228,13 +240,13 @@ Options:
|
|
|
228
240
|
return { success: true, backups: [] };
|
|
229
241
|
}
|
|
230
242
|
|
|
231
|
-
const files = await fsp.readdir(
|
|
243
|
+
const files = await fsp.readdir(backupDir);
|
|
232
244
|
const backups = [];
|
|
233
245
|
|
|
234
246
|
for (const file of files) {
|
|
235
247
|
if (file.startsWith('backup-') && file.endsWith('.json')) {
|
|
236
248
|
try {
|
|
237
|
-
const filePath = path.join(
|
|
249
|
+
const filePath = this.validateInProject(path.join(backupDir, file), 'backup file');
|
|
238
250
|
const stats = await fsp.stat(filePath);
|
|
239
251
|
backups.push({
|
|
240
252
|
name: file,
|
|
@@ -288,7 +300,7 @@ Options:
|
|
|
288
300
|
throw new Error('Backup file path is required');
|
|
289
301
|
}
|
|
290
302
|
|
|
291
|
-
const backupPath = path.resolve(process.cwd(), backupFile);
|
|
303
|
+
const backupPath = this.validateInProject(path.resolve(process.cwd(), backupFile), 'backup file');
|
|
292
304
|
|
|
293
305
|
// Validate backup file
|
|
294
306
|
if (!SecurityUtils.safeExistsSync(backupPath, process.cwd())) {
|
|
@@ -328,21 +340,22 @@ Options:
|
|
|
328
340
|
}
|
|
329
341
|
}
|
|
330
342
|
|
|
331
|
-
async handleCleanup(options = {}) {
|
|
332
|
-
const keep = options.keep ? parseInt(options.keep, 10) : this.maxBackups;
|
|
343
|
+
async handleCleanup(options = {}) {
|
|
344
|
+
const keep = options.keep ? parseInt(options.keep, 10) : this.maxBackups;
|
|
345
|
+
const backupDir = this.validateInProject(this.backupDir, 'backup directory');
|
|
333
346
|
|
|
334
347
|
logger.info('\nCleaning up old backups...');
|
|
335
348
|
|
|
336
349
|
try {
|
|
337
|
-
const files = await fsp.readdir(
|
|
338
|
-
const backupFiles = files
|
|
339
|
-
.filter(file => file.startsWith('backup-') && file.endsWith('.json'))
|
|
340
|
-
.map(file => ({
|
|
341
|
-
name: file,
|
|
342
|
-
path: path.join(
|
|
343
|
-
time:
|
|
350
|
+
const files = await fsp.readdir(backupDir);
|
|
351
|
+
const backupFiles = files
|
|
352
|
+
.filter(file => file.startsWith('backup-') && file.endsWith('.json'))
|
|
353
|
+
.map(file => ({
|
|
354
|
+
name: file,
|
|
355
|
+
path: this.validateInProject(path.join(backupDir, file), 'backup file'),
|
|
356
|
+
time: (SecurityUtils.safeStatSync(path.join(backupDir, file), process.cwd()) || { mtime: new Date(0) }).mtime.getTime()
|
|
344
357
|
}))
|
|
345
|
-
.sort((a, b) => b.time - a.time);
|
|
358
|
+
.sort((a, b) => b.time - a.time);
|
|
346
359
|
|
|
347
360
|
// Keep only the most recent 'keep' files
|
|
348
361
|
const toDelete = backupFiles.slice(keep);
|
|
@@ -379,27 +392,28 @@ Options:
|
|
|
379
392
|
}
|
|
380
393
|
}
|
|
381
394
|
|
|
382
|
-
async cleanupOldBackups(outputDir) {
|
|
383
|
-
try {
|
|
384
|
-
const
|
|
385
|
-
const
|
|
386
|
-
|
|
387
|
-
.
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
395
|
+
async cleanupOldBackups(outputDir) {
|
|
396
|
+
try {
|
|
397
|
+
const safeOutputDir = this.validateInProject(outputDir, 'backup output directory');
|
|
398
|
+
const files = await fsp.readdir(safeOutputDir);
|
|
399
|
+
const backupFiles = files
|
|
400
|
+
.filter(file => file.startsWith('backup-') && file.endsWith('.json'))
|
|
401
|
+
.map(file => ({
|
|
402
|
+
name: file,
|
|
403
|
+
path: this.validateInProject(path.join(safeOutputDir, file), 'backup file'),
|
|
404
|
+
time: (SecurityUtils.safeStatSync(path.join(safeOutputDir, file), process.cwd()) || { mtime: new Date(0) }).mtime.getTime()
|
|
405
|
+
}))
|
|
406
|
+
.sort((a, b) => b.time - a.time);
|
|
393
407
|
|
|
394
408
|
// Keep only the most recent files
|
|
395
409
|
const toDelete = backupFiles.slice(this.maxBackups);
|
|
396
410
|
|
|
397
|
-
for (const file of toDelete) {
|
|
398
|
-
try {
|
|
399
|
-
await
|
|
400
|
-
} catch (err) {
|
|
401
|
-
// Ignore cleanup errors
|
|
402
|
-
}
|
|
411
|
+
for (const file of toDelete) {
|
|
412
|
+
try {
|
|
413
|
+
await fsp.unlink(file.path);
|
|
414
|
+
} catch (err) {
|
|
415
|
+
// Ignore cleanup errors
|
|
416
|
+
}
|
|
403
417
|
}
|
|
404
418
|
} catch (error) {
|
|
405
419
|
// Ignore cleanup errors
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "i18ntk",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.5",
|
|
4
4
|
"description": "🚀 The fastest internationalization toolkit with 97% performance boost! Zero-dependency, enterprise-grade internationalization for React, Vue, Angular, Python, Java, PHP & more. Features PIN protection, auto framework detection, 7+ UI languages, and comprehensive translation management. Perfect for startups to enterprises.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"i18n",
|
|
@@ -227,7 +227,7 @@
|
|
|
227
227
|
},
|
|
228
228
|
"preferGlobal": true,
|
|
229
229
|
"versionInfo": {
|
|
230
|
-
"version": "2.3.
|
|
230
|
+
"version": "2.3.5",
|
|
231
231
|
"releaseDate": "12/04/2026",
|
|
232
232
|
"lastUpdated": "12/04/2026",
|
|
233
233
|
"maintainer": "Vlad Noskov",
|
|
@@ -238,9 +238,10 @@
|
|
|
238
238
|
"HOTFIX: Removed deprecated package-path fallback that caused production build warnings for non-exported subpaths.",
|
|
239
239
|
"CRITICAL FIX: Resolved sizing and usage-analysis regressions in v2 command flow.",
|
|
240
240
|
"PACKAGING: Reduced publish footprint by removing internal development scripts and legacy fixed-file artifacts.",
|
|
241
|
-
"SECURITY: Hardened release checks and added explicit support guidance to update from pre-2.3.
|
|
241
|
+
"SECURITY: Hardened release checks and added explicit support guidance to update from pre-2.3.5 versions.",
|
|
242
242
|
"CONFIG: Added cross-process file locking for .i18ntk-config writes to prevent production rename races.",
|
|
243
243
|
"CONFIG: Made autosave runtime-safe with non-throwing save failures and I18NTK_DISABLE_AUTOSAVE support.",
|
|
244
|
+
"SECURITY: Hardened reset/backup path handling and made npm update checks explicit opt-in.",
|
|
244
245
|
"CLI: Added npm registry version check with upgrade warning for out-of-date installs.",
|
|
245
246
|
"I18N: Completed internal UI locale parity and actionable untranslated-key cleanup across supported languages."
|
|
246
247
|
],
|
|
@@ -267,7 +268,7 @@
|
|
|
267
268
|
"spring-boot": ">=2.5.0",
|
|
268
269
|
"laravel": ">=8.0.0"
|
|
269
270
|
},
|
|
270
|
-
"supportPolicy": "Versions earlier than 2.3.
|
|
271
|
+
"supportPolicy": "Versions earlier than 2.3.5 may be unstable or insecure. Upgrade to 2.3.5 or newer."
|
|
271
272
|
},
|
|
272
273
|
"_comment": "This package is zero-dependency and uses only native Node.js modules"
|
|
273
274
|
}
|
|
@@ -312,6 +312,18 @@ class SettingsManager {
|
|
|
312
312
|
this.settings = this.loadSettings();
|
|
313
313
|
}
|
|
314
314
|
|
|
315
|
+
resolveSafePath(targetPath, basePath = process.cwd()) {
|
|
316
|
+
return SecurityUtils.validatePath(path.resolve(targetPath), basePath);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
safeDeleteFile(targetPath, basePath = process.cwd()) {
|
|
320
|
+
const validated = this.resolveSafePath(targetPath, basePath);
|
|
321
|
+
if (!validated) return false;
|
|
322
|
+
if (!SecurityUtils.safeExistsSync(validated, basePath)) return false;
|
|
323
|
+
fs.unlinkSync(validated);
|
|
324
|
+
return true;
|
|
325
|
+
}
|
|
326
|
+
|
|
315
327
|
/**
|
|
316
328
|
* Load settings from file or return default settings
|
|
317
329
|
* @returns {object} Settings object
|
|
@@ -432,12 +444,18 @@ class SettingsManager {
|
|
|
432
444
|
*/
|
|
433
445
|
_saveImmediately() {
|
|
434
446
|
try {
|
|
435
|
-
|
|
436
|
-
|
|
447
|
+
const configDir = path.dirname(this.configFile);
|
|
448
|
+
const validatedConfigDir = this.resolveSafePath(configDir, process.cwd());
|
|
449
|
+
if (!validatedConfigDir) {
|
|
450
|
+
throw new Error('Invalid configuration directory');
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
if (!SecurityUtils.safeExistsSync(validatedConfigDir, process.cwd())) {
|
|
454
|
+
SecurityUtils.safeMkdirSync(validatedConfigDir, process.cwd(), { recursive: true });
|
|
437
455
|
}
|
|
438
456
|
|
|
439
457
|
const content = JSON.stringify(this.settings, null, 4);
|
|
440
|
-
SecurityUtils.safeWriteFileSync(this.configFile, content,
|
|
458
|
+
SecurityUtils.safeWriteFileSync(this.configFile, content, validatedConfigDir, 'utf8');
|
|
441
459
|
|
|
442
460
|
// Create backup if enabled
|
|
443
461
|
if (this.settings.backup?.enabled) {
|
|
@@ -466,13 +484,19 @@ class SettingsManager {
|
|
|
466
484
|
}
|
|
467
485
|
|
|
468
486
|
if (!SecurityUtils.safeExistsSync(validatedBackupDir, process.cwd())) {
|
|
469
|
-
|
|
487
|
+
SecurityUtils.safeMkdirSync(validatedBackupDir, process.cwd(), { recursive: true });
|
|
470
488
|
}
|
|
471
489
|
|
|
472
490
|
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
473
491
|
const backupFile = path.join(validatedBackupDir, `config-${timestamp}.json`);
|
|
492
|
+
const validatedConfigFile = this.resolveSafePath(this.configFile, process.cwd());
|
|
493
|
+
const validatedBackupFile = this.resolveSafePath(backupFile, process.cwd());
|
|
494
|
+
if (!validatedConfigFile || !validatedBackupFile) {
|
|
495
|
+
console.error('Error creating backup: Invalid source or destination path');
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
474
498
|
|
|
475
|
-
fs.copyFileSync(
|
|
499
|
+
fs.copyFileSync(validatedConfigFile, validatedBackupFile);
|
|
476
500
|
|
|
477
501
|
// Clean old backups
|
|
478
502
|
this.cleanupOldBackups(validatedBackupDir);
|
|
@@ -486,17 +510,20 @@ class SettingsManager {
|
|
|
486
510
|
*/
|
|
487
511
|
cleanupOldBackups(backupDirectory = null) {
|
|
488
512
|
try {
|
|
489
|
-
const activeBackupDir = backupDirectory || this.backupDir;
|
|
513
|
+
const activeBackupDir = this.resolveSafePath(backupDirectory || this.backupDir, process.cwd());
|
|
514
|
+
if (!activeBackupDir) {
|
|
515
|
+
return;
|
|
516
|
+
}
|
|
490
517
|
if (!SecurityUtils.safeExistsSync(activeBackupDir, process.cwd())) {
|
|
491
518
|
return;
|
|
492
519
|
}
|
|
493
520
|
|
|
494
|
-
const files =
|
|
521
|
+
const files = (SecurityUtils.safeReaddirSync(activeBackupDir, process.cwd()) || [])
|
|
495
522
|
.filter(file => file.startsWith('config-') && file.endsWith('.json'))
|
|
496
523
|
.map(file => ({
|
|
497
524
|
name: file,
|
|
498
525
|
path: path.join(activeBackupDir, file),
|
|
499
|
-
mtime:
|
|
526
|
+
mtime: (SecurityUtils.safeStatSync(path.join(activeBackupDir, file), process.cwd()) || { mtime: new Date(0) }).mtime
|
|
500
527
|
}))
|
|
501
528
|
.sort((a, b) => b.mtime - a.mtime);
|
|
502
529
|
|
|
@@ -506,7 +533,7 @@ class SettingsManager {
|
|
|
506
533
|
: 1;
|
|
507
534
|
if (files.length > maxBackups) {
|
|
508
535
|
files.slice(maxBackups).forEach(file => {
|
|
509
|
-
|
|
536
|
+
this.safeDeleteFile(file.path, process.cwd());
|
|
510
537
|
});
|
|
511
538
|
}
|
|
512
539
|
} catch (error) {
|
|
@@ -546,52 +573,49 @@ class SettingsManager {
|
|
|
546
573
|
|
|
547
574
|
// 2. Remove actual configuration files used by the system
|
|
548
575
|
const packageDir = path.resolve(__dirname, '..');
|
|
576
|
+
const projectRoot = process.cwd();
|
|
549
577
|
const settingsDir = path.join(packageDir, 'settings');
|
|
550
578
|
|
|
551
579
|
// Main configuration file
|
|
552
580
|
const mainConfigPath = path.join(settingsDir, 'i18ntk-config.json');
|
|
553
|
-
if (
|
|
554
|
-
fs.unlinkSync(mainConfigPath);
|
|
581
|
+
if (this.safeDeleteFile(mainConfigPath, packageDir)) {
|
|
555
582
|
console.log('✅ Main configuration file removed');
|
|
556
583
|
}
|
|
557
584
|
|
|
558
585
|
// Project configuration file
|
|
559
586
|
const projectConfigPath = path.join(settingsDir, 'project-config.json');
|
|
560
|
-
if (
|
|
561
|
-
fs.unlinkSync(projectConfigPath);
|
|
587
|
+
if (this.safeDeleteFile(projectConfigPath, packageDir)) {
|
|
562
588
|
console.log('✅ Project configuration removed');
|
|
563
589
|
}
|
|
564
590
|
|
|
565
591
|
// Setup tracking file
|
|
566
592
|
const setupFile = path.join(settingsDir, 'setup.json');
|
|
567
|
-
if (
|
|
568
|
-
fs.unlinkSync(setupFile);
|
|
593
|
+
if (this.safeDeleteFile(setupFile, packageDir)) {
|
|
569
594
|
console.log('✅ Setup tracking cleared');
|
|
570
595
|
}
|
|
571
596
|
|
|
572
597
|
// 3. Clear all backup files from backups directory
|
|
573
598
|
const backupsDir = path.join(packageDir, 'backups');
|
|
574
|
-
if (SecurityUtils.safeExistsSync(backupsDir)) {
|
|
575
|
-
const backupFiles =
|
|
599
|
+
if (SecurityUtils.safeExistsSync(backupsDir, packageDir)) {
|
|
600
|
+
const backupFiles = SecurityUtils.safeReaddirSync(backupsDir, packageDir) || [];
|
|
576
601
|
for (const file of backupFiles) {
|
|
577
602
|
if (file.endsWith('.json') || file.endsWith('.bak')) {
|
|
578
|
-
|
|
603
|
+
this.safeDeleteFile(path.join(backupsDir, file), packageDir);
|
|
579
604
|
}
|
|
580
605
|
}
|
|
581
606
|
console.log('✅ All backup files cleared');
|
|
582
607
|
}
|
|
583
608
|
|
|
584
|
-
// 4. Clear admin PIN configuration (
|
|
609
|
+
// 4. Clear admin PIN configuration (package/project scope only)
|
|
585
610
|
const adminConfigPaths = [
|
|
586
611
|
path.join(packageDir, '.i18n-admin-config.json'),
|
|
587
612
|
path.join(settingsDir, '.i18n-admin-config.json'),
|
|
588
613
|
path.join(settingsDir, 'admin-config.json'),
|
|
589
|
-
path.join(
|
|
614
|
+
path.join(projectRoot, '.i18n-admin-config.json')
|
|
590
615
|
];
|
|
591
616
|
|
|
592
617
|
for (const adminConfigPath of adminConfigPaths) {
|
|
593
|
-
if (
|
|
594
|
-
fs.unlinkSync(adminConfigPath);
|
|
618
|
+
if (this.safeDeleteFile(adminConfigPath, projectRoot)) {
|
|
595
619
|
console.log('✅ Admin PIN configuration cleared');
|
|
596
620
|
}
|
|
597
621
|
}
|
|
@@ -603,8 +627,7 @@ class SettingsManager {
|
|
|
603
627
|
];
|
|
604
628
|
|
|
605
629
|
for (const initFile of initFiles) {
|
|
606
|
-
if (
|
|
607
|
-
fs.unlinkSync(initFile);
|
|
630
|
+
if (this.safeDeleteFile(initFile, packageDir)) {
|
|
608
631
|
console.log('✅ Initialization tracking cleared');
|
|
609
632
|
}
|
|
610
633
|
}
|
|
@@ -616,10 +639,10 @@ class SettingsManager {
|
|
|
616
639
|
];
|
|
617
640
|
|
|
618
641
|
for (const cacheDir of cacheDirs) {
|
|
619
|
-
if (SecurityUtils.safeExistsSync(cacheDir)) {
|
|
620
|
-
const cacheFiles =
|
|
642
|
+
if (SecurityUtils.safeExistsSync(cacheDir, packageDir)) {
|
|
643
|
+
const cacheFiles = SecurityUtils.safeReaddirSync(cacheDir, packageDir) || [];
|
|
621
644
|
for (const file of cacheFiles) {
|
|
622
|
-
|
|
645
|
+
this.safeDeleteFile(path.join(cacheDir, file), packageDir);
|
|
623
646
|
}
|
|
624
647
|
console.log('✅ Cache cleared');
|
|
625
648
|
}
|
|
@@ -639,9 +662,7 @@ class SettingsManager {
|
|
|
639
662
|
];
|
|
640
663
|
|
|
641
664
|
for (const tempFile of tempFiles) {
|
|
642
|
-
|
|
643
|
-
fs.unlinkSync(tempFile);
|
|
644
|
-
}
|
|
665
|
+
this.safeDeleteFile(tempFile, packageDir);
|
|
645
666
|
}
|
|
646
667
|
console.log('✅ Temporary files cleared');
|
|
647
668
|
|
|
@@ -656,8 +677,7 @@ class SettingsManager {
|
|
|
656
677
|
];
|
|
657
678
|
|
|
658
679
|
for (const file of additionalFiles) {
|
|
659
|
-
if (
|
|
660
|
-
fs.unlinkSync(file);
|
|
680
|
+
if (this.safeDeleteFile(file, packageDir)) {
|
|
661
681
|
console.log(`✅ Removed ${path.basename(file)}`);
|
|
662
682
|
}
|
|
663
683
|
}
|
|
@@ -108,6 +108,11 @@ async function printUpgradeWarningIfOutdated({
|
|
|
108
108
|
currentVersion,
|
|
109
109
|
timeoutMs = DEFAULT_TIMEOUT_MS
|
|
110
110
|
}) {
|
|
111
|
+
const enabled = String(process.env.I18NTK_ENABLE_UPDATE_CHECK || '').toLowerCase();
|
|
112
|
+
if (!(enabled === '1' || enabled === 'true' || enabled === 'yes')) {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
|
|
111
116
|
if (process.env.I18NTK_DISABLE_UPDATE_CHECK === 'true') {
|
|
112
117
|
return;
|
|
113
118
|
}
|