i18ntk 2.2.0 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -9,9 +9,11 @@ const path = require('path');
9
9
  const configManager = require('./config-manager');
10
10
  const SecurityUtils = require('./security');
11
11
  const {loadTranslations} = require('./i18n-helper');
12
- const SettingsManager = require('../settings/settings-manager');
13
- const { envManager } = require('./env-manager');
14
- const settingsManager = new SettingsManager();
12
+ const SettingsManager = require('../settings/settings-manager');
13
+ const { envManager } = require('./env-manager');
14
+ const { checkInitialized } = require('./init-helper');
15
+ const { closeGlobalReadline } = require('./cli');
16
+ const settingsManager = new SettingsManager();
15
17
 
16
18
  const { ask } = require('./cli');
17
19
 
@@ -132,10 +134,15 @@ async function getUnifiedConfig(scriptName, cliArgs = {}) {
132
134
  cfg.notTranslatedMarker ||
133
135
  cfg.processing?.notTranslatedMarker ||
134
136
  'NOT_TRANSLATED';
135
- const markerList = Array.isArray(rawMarkers) ? rawMarkers : [rawMarkers];
136
-
137
- const config = {
138
- ...cfg,
137
+ const markerList = Array.isArray(rawMarkers) ? rawMarkers : [rawMarkers];
138
+ const configuredBackupKeep = parseInt(cfg.backup?.maxBackups, 10);
139
+ const normalizedBackupMaxBackups = Number.isInteger(configuredBackupKeep)
140
+ ? Math.min(Math.max(configuredBackupKeep, 1), 3)
141
+ : 1;
142
+ const normalizedBackupLocation = cfg.backup?.location || './i18ntk-backups';
143
+
144
+ const config = {
145
+ ...cfg,
139
146
  sourceLanguage: cliArgs.sourceLanguage || cfg.sourceLanguage || 'en',
140
147
  uiLanguage: cliArgs.uiLanguage || cfg.uiLanguage || 'en',
141
148
  notTranslatedMarker: markerList[0],
@@ -144,11 +151,17 @@ async function getUnifiedConfig(scriptName, cliArgs = {}) {
144
151
  excludeFiles: cfg.excludeFiles || cfg.processing?.excludeFiles || ['.DS_Store', 'Thumbs.db'],
145
152
  excludeDirs: cfg.excludeDirs || cfg.processing?.excludeDirs || ['node_modules', '.next', '.git', 'dist', 'build'],
146
153
  strictMode: cliArgs.strictMode || cfg.strictMode || false,
147
- backupDir: path.join(settingsDir, 'backups'),
148
- tempDir: path.join(settingsDir, 'temp'),
149
- cacheDir: path.join(settingsDir, '.cache'),
150
- configDir: settingsDir,
151
- settings: {
154
+ backupDir: path.resolve(projectRoot, normalizedBackupLocation),
155
+ tempDir: path.join(settingsDir, 'temp'),
156
+ cacheDir: path.join(settingsDir, '.cache'),
157
+ configDir: settingsDir,
158
+ backup: {
159
+ ...(cfg.backup || {}),
160
+ enabled: cfg.backup?.enabled === true,
161
+ location: normalizedBackupLocation,
162
+ maxBackups: normalizedBackupMaxBackups
163
+ },
164
+ settings: {
152
165
  defaultLanguages: cfg.defaultLanguages || ['de', 'es', 'fr', 'ru'],
153
166
  processing: { ...cfg.processing },
154
167
  security: { ...cfg.security },
@@ -169,8 +182,8 @@ async function getUnifiedConfig(scriptName, cliArgs = {}) {
169
182
  * Get environment-specific configuration
170
183
  * @returns {object} Environment configuration
171
184
  */
172
- function getEnvironmentConfig() {
173
- const settings = require('../settings/settings-manager').getAllSettings();
185
+ function getEnvironmentConfig() {
186
+ const settings = settingsManager.getAllSettings();
174
187
  return {
175
188
  nodeEnv: settings.nodeEnv || 'production',
176
189
  isProduction: (settings.nodeEnv || 'production') === 'production',
@@ -425,7 +438,6 @@ function displayPaths(cfg = {}) {
425
438
  // Ensure project has been initialized with source language files
426
439
  async function ensureInitialized(cfg) {
427
440
  try {
428
- const { checkInitialized } = require('./init-helper');
429
441
  const sourceDir = cfg?.sourceDir || './locales';
430
442
  const sourceLanguage = cfg?.sourceLanguage || 'en';
431
443
 
@@ -445,7 +457,6 @@ async function ensureInitialized(cfg) {
445
457
 
446
458
  const langDir = path.join(sourceDir, sourceLanguage);
447
459
  const answer = await ask(`Source language files not found in ${langDir}. Run initialization now? (y/N) `);
448
- const { closeGlobalReadline } = require('./cli');
449
460
  closeGlobalReadline();
450
461
 
451
462
  if (answer.trim().toLowerCase().startsWith('y')) {
@@ -169,13 +169,14 @@ const DEFAULT_CONFIG = {
169
169
  "memoryPooling": true,
170
170
  "stringInterning": true
171
171
  },
172
- "backup": {
173
- "enabled": false,
174
- "singleFileMode": false,
175
- "singleBackupFile": "i18ntk-central-backup.json",
176
- "retentionDays": 30,
177
- "maxBackups": 100
178
- },
172
+ "backup": {
173
+ "enabled": false,
174
+ "location": "./i18ntk-backups",
175
+ "singleFileMode": false,
176
+ "singleBackupFile": "i18ntk-central-backup.json",
177
+ "retentionDays": 30,
178
+ "maxBackups": 1
179
+ },
179
180
  "security": {
180
181
  "adminPinEnabled": false,
181
182
  "adminPinPromptOnInit": true,
@@ -2,6 +2,7 @@ const fs = require('fs');
2
2
  const path = require('path');
3
3
  const configManager = require('./config-manager');
4
4
  const SecurityUtils = require('./security');
5
+ const packageJson = require('../package.json');
5
6
 
6
7
  function ensureDirectory(dirPath) {
7
8
  if (!dirPath || typeof dirPath !== 'string') return;
@@ -46,7 +47,7 @@ function hasSourceLanguageFiles(sourceDir, sourceLanguage) {
46
47
  */
47
48
  async function checkInitialized(options = {}) {
48
49
  const settings = configManager.getConfig ? configManager.getConfig() : {};
49
- const currentVersion = require('../package.json').version;
50
+ const currentVersion = packageJson.version;
50
51
  const projectConfigPath = configManager.CONFIG_PATH || path.join(process.cwd(), '.i18ntk-config');
51
52
  const configDir = path.dirname(projectConfigPath);
52
53
 
@@ -135,7 +136,7 @@ async function markAsInitialized(config) {
135
136
  const projectConfigPath = configManager.CONFIG_PATH || path.join(process.cwd(), '.i18ntk-config');
136
137
  const configDir = path.dirname(projectConfigPath);
137
138
  const initFilePath = path.join(configDir, 'initialization.json');
138
- const currentVersion = require('../package.json').version;
139
+ const currentVersion = packageJson.version;
139
140
  const now = new Date().toISOString();
140
141
  const sourceDir = config?.sourceDir || settings.sourceDir || './locales';
141
142
  const sourceLanguage = config?.sourceLanguage || settings.sourceLanguage || 'en';
@@ -1,6 +1,8 @@
1
- /**
2
- * JSON Output Utility for i18ntk commands
3
- * Provides consistent machine-readable output format for CI/CD integration
1
+ const packageJson = require('../package.json');
2
+
3
+ /**
4
+ * JSON Output Utility for i18ntk commands
5
+ * Provides consistent machine-readable output format for CI/CD integration
4
6
  */
5
7
 
6
8
  class JsonOutput {
@@ -21,12 +23,11 @@ class JsonOutput {
21
23
  };
22
24
  }
23
25
 
24
- getPackageVersion() {
25
- try {
26
- const packageJson = require('../package.json');
27
- return packageJson.version;
28
- } catch (error) {
29
- return '1.8.3';
26
+ getPackageVersion() {
27
+ try {
28
+ return packageJson.version;
29
+ } catch (error) {
30
+ return '1.8.3';
30
31
  }
31
32
  }
32
33
 
@@ -96,4 +97,4 @@ class JsonOutput {
96
97
  }
97
98
  }
98
99
 
99
- module.exports = JsonOutput;
100
+ module.exports = JsonOutput;
package/utils/logger.js CHANGED
@@ -45,10 +45,10 @@ const logger = {
45
45
  logger.log(output, colors.blue);
46
46
  },
47
47
 
48
- // Debug logging (only when DEBUG env var is set or log level is debug)
49
- debug: (message) => {
50
- const logLevel = envManager.get('I18NTK_LOG_LEVEL');
51
- const debugEnabled = process.env.DEBUG || logLevel === 'debug';
48
+ // Debug logging (only when DEBUG env var is set or log level is debug)
49
+ debug: (message) => {
50
+ const logLevel = envManager.get('I18NTK_LOG_LEVEL');
51
+ const debugEnabled = logLevel === 'debug';
52
52
 
53
53
  if (debugEnabled) {
54
54
  const output = `[DEBUG] ${message}`;
@@ -1,5 +1,5 @@
1
1
  // utils/safe-json.js
2
- const { readFile } = require('fs/promises');
2
+ const fs = require('fs');
3
3
 
4
4
  function stripBOM(s) {
5
5
  if (typeof s === 'string' && s.charCodeAt(0) === 0xFEFF) return s.slice(1);
@@ -13,7 +13,7 @@ function stripBOM(s) {
13
13
  * - Single, typed error (no loops)
14
14
  */
15
15
  async function readJsonSafe(filePath, { maxBytes = 1_000_000 } = {}) {
16
- const buf = await readFile(filePath);
16
+ const buf = await fs.promises.readFile(filePath);
17
17
  if (buf.length === 0) {
18
18
  const err = new Error('Empty JSON file');
19
19
  err.code = 'EJSONEMPTY';
@@ -37,4 +37,4 @@ async function readJsonSafe(filePath, { maxBytes = 1_000_000 } = {}) {
37
37
  }
38
38
  }
39
39
 
40
- module.exports = { readJsonSafe };
40
+ module.exports = { readJsonSafe };
@@ -1,5 +1,6 @@
1
1
  const crypto = require('crypto');
2
- const fs = require('fs/promises');
2
+ const fs = require('fs');
3
+ const fsp = fs.promises;
3
4
  const path = require('path');
4
5
  const zlib = require('zlib');
5
6
  const { promisify } = require('util');
@@ -180,7 +181,7 @@ class SecureBackupManager {
180
181
  const backupPath = path.join(this.config.backupDir, backupName);
181
182
 
182
183
  // Write the backup file
183
- await fs.writeFile(backupPath, JSON.stringify(backupData, null, 2), 'utf8');
184
+ await fsp.writeFile(backupPath, JSON.stringify(backupData, null, 2), 'utf8');
184
185
 
185
186
  // Clean up old backups if we've exceeded the limit
186
187
  await this.cleanupOldBackups();
@@ -203,7 +204,7 @@ class SecureBackupManager {
203
204
  async restoreBackup(backupPath, password) {
204
205
  try {
205
206
  // Read the backup file
206
- const backupData = JSON.parse(await fs.readFile(backupPath, 'utf8'));
207
+ const backupData = JSON.parse(await fsp.readFile(backupPath, 'utf8'));
207
208
 
208
209
  // Validate the backup
209
210
  if (backupData.header !== BACKUP_HEADER) {
@@ -236,7 +237,7 @@ class SecureBackupManager {
236
237
  async listBackups() {
237
238
  try {
238
239
  // Read the backup directory
239
- const files = (await fs.readdir(this.config.backupDir, { withFileTypes: true }))
240
+ const files = (await fsp.readdir(this.config.backupDir, { withFileTypes: true }))
240
241
  .filter(dirent => dirent.isFile())
241
242
  .map(dirent => dirent.name);
242
243
 
@@ -247,7 +248,7 @@ class SecureBackupManager {
247
248
  const backups = await Promise.all(
248
249
  backupFiles.map(async (file) => {
249
250
  const filePath = path.join(this.config.backupDir, file);
250
- const stat = await fs.stat(filePath);
251
+ const stat = await fsp.stat(filePath);
251
252
  if (stat.isDirectory()) {
252
253
  throw new Error('Backup path is a directory');
253
254
  }
@@ -290,7 +291,7 @@ class SecureBackupManager {
290
291
  const deleted = [];
291
292
  for (const backup of toDelete) {
292
293
  try {
293
- await fs.unlink(backup.path);
294
+ await fsp.unlink(backup.path);
294
295
  deleted.push(backup.name);
295
296
  } catch (error) {
296
297
  console.error(`Failed to delete backup ${backup.name}:`, error);
@@ -310,7 +311,7 @@ class SecureBackupManager {
310
311
  async verifyBackup(backupPath, password) {
311
312
  try {
312
313
  // Read the backup file
313
- const backupData = JSON.parse(await fs.readFile(backupPath, 'utf8'));
314
+ const backupData = JSON.parse(await fsp.readFile(backupPath, 'utf8'));
314
315
 
315
316
  // Try to decrypt a small part to verify the password
316
317
  const testData = await this.decryptData(backupData, password);
@@ -9,9 +9,25 @@
9
9
 
10
10
  const { getIcon } = require('./terminal-icons');
11
11
  const fs = require('fs');
12
- const path = require('path');
13
- const { blue, yellow, gray, cyan, green, red } = require('./colors-new');
14
- const SecurityUtils = require('./security');
12
+ const path = require('path');
13
+ const { blue, yellow, gray, cyan, green, red } = require('./colors-new');
14
+ const SecurityUtils = require('./security');
15
+ const I18nSetupModule = require('../main/i18ntk-setup');
16
+
17
+ async function runSetupModule() {
18
+ if (I18nSetupModule && typeof I18nSetupModule.run === 'function') {
19
+ return await I18nSetupModule.run();
20
+ }
21
+
22
+ if (typeof I18nSetupModule === 'function') {
23
+ const setupManager = new I18nSetupModule();
24
+ if (typeof setupManager.setup === 'function') {
25
+ return await setupManager.setup();
26
+ }
27
+ }
28
+
29
+ throw new Error('Setup module does not expose a runnable entrypoint');
30
+ }
15
31
 
16
32
  class SetupEnforcer {
17
33
  static _setupCheckInProgress = false;
@@ -107,39 +123,22 @@ class SetupEnforcer {
107
123
  process.exit(0);
108
124
  }
109
125
 
110
- console.log(green(`${getIcon('rocket')} Running setup...`));
111
-
112
- try {
113
- // Import and run setup directly
114
- const setupPath = path.join(__dirname, '..', 'main', 'i18ntk-setup.js');
115
- if (SecurityUtils.safeExistsSync(setupPath)) {
116
- try {
117
- const setup = require(setupPath);
118
- // Use the run function which properly instantiates the class
119
- if (setup && typeof setup.run === 'function') {
120
- await setup.run();
121
- } else {
122
- // Fallback: instantiate the class directly
123
- const I18nSetupManager = require(setupPath);
124
- const setupManager = new I18nSetupManager();
125
- await setupManager.setup();
126
- }
127
- console.log(gray('You can now run your original command.'));
128
- resolve(true);
129
- } catch (error) {
130
- console.error(red(`${getIcon('cross')} Setup failed:`), error.message);
131
- console.error(cyan(' Please try running setup manually:'));
132
- console.error(cyan(' npm run i18ntk-setup'));
133
- process.exit(1);
134
- }
135
- } else {
136
- console.error(red(`${getIcon('cross')} Setup script not found. Please run:`));
137
- console.error(cyan(' npm run i18ntk-setup'));
138
- process.exit(1);
139
- }
140
- } catch (error) {
141
- console.error(red(`${getIcon('cross')} Error running setup:`), error.message);
142
- console.error(cyan(' npm run i18ntk-setup'));
126
+ console.log(green(`${getIcon('rocket')} Running setup...`));
127
+
128
+ try {
129
+ // Import once at module scope and execute the stable entrypoint.
130
+ const setupPath = path.join(__dirname, '..', 'main', 'i18ntk-setup.js');
131
+ if (!SecurityUtils.safeExistsSync(setupPath)) {
132
+ console.error(red(`${getIcon('cross')} Setup script not found. Please run:`));
133
+ console.error(cyan(' npm run i18ntk-setup'));
134
+ process.exit(1);
135
+ }
136
+ await runSetupModule();
137
+ console.log(gray('You can now run your original command.'));
138
+ resolve(true);
139
+ } catch (error) {
140
+ console.error(red(`${getIcon('cross')} Error running setup:`), error.message);
141
+ console.error(cyan(' npm run i18ntk-setup'));
143
142
  process.exit(1);
144
143
  }
145
144
  });
@@ -185,37 +184,20 @@ static async handleIncompleteSetup() {
185
184
  process.exit(0);
186
185
  }
187
186
 
188
- console.log(green(`${getIcon('rocket')} Running setup...`));
189
-
190
- try {
191
- const setupPath = path.join(__dirname, '..', 'main', 'i18ntk-setup.js');
192
- if (SecurityUtils.safeExistsSync(setupPath)) {
193
- try {
194
- const setup = require(setupPath);
195
- // Use the run function which properly instantiates the class
196
- if (setup && typeof setup.run === 'function') {
197
- await setup.run();
198
- } else {
199
- // Fallback: instantiate the class directly
200
- const I18nSetupManager = require(setupPath);
201
- const setupManager = new I18nSetupManager();
202
- await setupManager.setup();
203
- }
204
- resolve(true);
205
- } catch (error) {
206
- console.error(red(`${getIcon('cross')} Setup failed:`), error.message);
207
- console.error(cyan(' Please try running setup manually:'));
208
- console.error(cyan(' npm run i18ntk-setup'));
209
- process.exit(1);
210
- }
211
- } else {
212
- console.error(red(`${getIcon('cross')} Setup script not found. Please run:`));
213
- console.error(cyan(' npm run i18ntk-setup'));
214
- process.exit(1);
215
- }
216
- } catch (error) {
217
- console.error(red(`${getIcon('cross')} Error running setup:`), error.message);
218
- process.exit(1);
187
+ console.log(green(`${getIcon('rocket')} Running setup...`));
188
+
189
+ try {
190
+ const setupPath = path.join(__dirname, '..', 'main', 'i18ntk-setup.js');
191
+ if (!SecurityUtils.safeExistsSync(setupPath)) {
192
+ console.error(red(`${getIcon('cross')} Setup script not found. Please run:`));
193
+ console.error(cyan(' npm run i18ntk-setup'));
194
+ process.exit(1);
195
+ }
196
+ await runSetupModule();
197
+ resolve(true);
198
+ } catch (error) {
199
+ console.error(red(`${getIcon('cross')} Error running setup:`), error.message);
200
+ process.exit(1);
219
201
  }
220
202
  });
221
203
  });
@@ -260,37 +242,20 @@ static async handleInvalidConfig() {
260
242
  process.exit(0);
261
243
  }
262
244
 
263
- console.log(green(`${getIcon('rocket')} Running setup...`));
264
-
265
- try {
266
- const setupPath = path.join(__dirname, '..', 'main', 'i18ntk-setup.js');
267
- if (SecurityUtils.safeExistsSync(setupPath)) {
268
- try {
269
- const setup = require(setupPath);
270
- // Use the run function which properly instantiates the class
271
- if (setup && typeof setup.run === 'function') {
272
- await setup.run();
273
- } else {
274
- // Fallback: instantiate the class directly
275
- const I18nSetupManager = require(setupPath);
276
- const setupManager = new I18nSetupManager();
277
- await setupManager.setup();
278
- }
279
- resolve(true);
280
- } catch (error) {
281
- console.error(red(`${getIcon('cross')} Setup failed:`), error.message);
282
- console.error(cyan(' Please try running setup manually:'));
283
- console.error(cyan(' npm run i18ntk-setup'));
284
- process.exit(1);
285
- }
286
- } else {
287
- console.error(red(`${getIcon('cross')} Setup script not found. Please run:`));
288
- console.error(cyan(' npm run i18ntk-setup'));
289
- process.exit(1);
290
- }
291
- } catch (error) {
292
- console.error(red(`${getIcon('cross')} Error running setup:`), error.message);
293
- process.exit(1);
245
+ console.log(green(`${getIcon('rocket')} Running setup...`));
246
+
247
+ try {
248
+ const setupPath = path.join(__dirname, '..', 'main', 'i18ntk-setup.js');
249
+ if (!SecurityUtils.safeExistsSync(setupPath)) {
250
+ console.error(red(`${getIcon('cross')} Setup script not found. Please run:`));
251
+ console.error(cyan(' npm run i18ntk-setup'));
252
+ process.exit(1);
253
+ }
254
+ await runSetupModule();
255
+ resolve(true);
256
+ } catch (error) {
257
+ console.error(red(`${getIcon('cross')} Error running setup:`), error.message);
258
+ process.exit(1);
294
259
  }
295
260
  });
296
261
  });