i18ntk 2.0.3 → 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.
@@ -4,10 +4,12 @@
4
4
  * Prepublish Script
5
5
  * Cleans up development artifacts before npm publish
6
6
  * Ensures fresh config and settings for public package
7
+ * version 2.0.5
7
8
  */
8
9
 
9
10
  const fs = require('fs');
10
11
  const path = require('path');
12
+ const SecurityUtils = require('../utils/security');
11
13
 
12
14
  class PrepublishCleaner {
13
15
  constructor() {
@@ -95,83 +97,102 @@ class PrepublishCleaner {
95
97
  }
96
98
 
97
99
  async cleanDirectory(dirPath) {
98
- if (!SecurityUtils.safeExistsSync(dirPath)) {
99
- return;
100
- }
101
-
102
- try {
103
- const files = fs.readdirSync(dirPath);
104
- let deletedCount = 0;
105
-
106
- for (const file of files) {
107
- const filePath = path.join(dirPath, file);
108
- const stat = fs.statSync(filePath);
109
-
110
- if (stat.isFile()) {
111
- fs.unlinkSync(filePath);
112
- deletedCount++;
113
- } else if (stat.isDirectory()) {
114
- // Recursively clean subdirectories
115
- await this.cleanDirectory(filePath);
116
- // Remove empty directories
117
- try {
118
- fs.rmdirSync(filePath);
119
- } catch (e) {
120
- // Directory not empty, skip
121
- }
122
- }
123
- }
124
-
125
- if (deletedCount > 0) {
126
- this.log(`Cleaned ${deletedCount} files from ${path.relative(this.projectRoot, dirPath)}`);
127
- }
128
- } catch (error) {
129
- this.log(`Warning: Could not clean ${dirPath}: ${error.message}`);
130
- }
100
+ const validatedPath = SecurityUtils.validatePath(dirPath, this.projectRoot);
101
+ if (!validatedPath || !SecurityUtils.safeExistsSync(validatedPath)) {
102
+ return;
103
+ }
104
+
105
+ try {
106
+ const files = SecurityUtils.safeReaddirSync(validatedPath, this.projectRoot);
107
+ let deletedCount = 0;
108
+
109
+ for (const file of files) {
110
+ const filePath = path.join(validatedPath, file);
111
+ const validatedFilePath = SecurityUtils.validatePath(filePath, this.projectRoot);
112
+ if (!validatedFilePath) continue;
113
+
114
+ const stat = SecurityUtils.safeStatSync(validatedFilePath, this.projectRoot);
115
+ if (!stat) continue;
116
+
117
+ if (stat.isFile()) {
118
+ if (SecurityUtils.safeExistsSync(validatedFilePath)) {
119
+ fs.unlinkSync(validatedFilePath);
120
+ deletedCount++;
121
+ }
122
+ } else if (stat.isDirectory()) {
123
+ // Recursively clean subdirectories
124
+ await this.cleanDirectory(validatedFilePath);
125
+ // Remove empty directories
126
+ try {
127
+ fs.rmdirSync(validatedFilePath);
128
+ } catch (e) {
129
+ // Directory not empty, skip
130
+ }
131
+ }
132
+ }
133
+
134
+ if (deletedCount > 0) {
135
+ this.log(`Cleaned ${deletedCount} files from ${path.relative(this.projectRoot, validatedPath)}`);
136
+ }
137
+ } catch (error) {
138
+ this.log(`Warning: Could not clean ${dirPath}: ${error.message}`);
139
+ }
131
140
  }
132
141
 
133
142
  async cleanFile(pattern) {
134
- const searchPath = path.join(this.projectRoot, pattern);
135
-
136
- if (pattern.includes('*')) {
137
- // Handle glob patterns
138
- const dir = path.dirname(searchPath);
139
- const filenamePattern = path.basename(searchPath);
140
-
141
- if (SecurityUtils.safeExistsSync(dir)) {
142
- const files = fs.readdirSync(dir);
143
- const regex = new RegExp(filenamePattern.replace('*', '.*'));
144
-
145
- for (const file of files) {
146
- if (regex.test(file)) {
147
- const filePath = path.join(dir, file);
148
- fs.unlinkSync(filePath);
149
- this.log(`Deleted ${path.relative(this.projectRoot, filePath)}`);
150
- }
151
- }
152
- }
153
- } else {
154
- // Handle exact files
155
- if (SecurityUtils.safeExistsSync(searchPath)) {
156
- fs.unlinkSync(searchPath);
157
- this.log(`Deleted ${path.relative(this.projectRoot, searchPath)}`);
158
- }
159
- }
143
+ const searchPath = path.join(this.projectRoot, pattern);
144
+ const validatedSearchPath = SecurityUtils.validatePath(searchPath, this.projectRoot);
145
+ if (!validatedSearchPath) return;
146
+
147
+ if (pattern.includes('*')) {
148
+ // Handle glob patterns
149
+ const dir = path.dirname(validatedSearchPath);
150
+ const filenamePattern = path.basename(validatedSearchPath);
151
+ const validatedDir = SecurityUtils.validatePath(dir, this.projectRoot);
152
+ if (!validatedDir) return;
153
+
154
+ if (SecurityUtils.safeExistsSync(validatedDir)) {
155
+ const files = SecurityUtils.safeReaddirSync(validatedDir, this.projectRoot);
156
+ const regex = new RegExp(filenamePattern.replace('*', '.*'));
157
+
158
+ for (const file of files) {
159
+ if (regex.test(file)) {
160
+ const filePath = path.join(validatedDir, file);
161
+ const validatedFilePath = SecurityUtils.validatePath(filePath, this.projectRoot);
162
+ if (validatedFilePath && SecurityUtils.safeExistsSync(validatedFilePath)) {
163
+ fs.unlinkSync(validatedFilePath);
164
+ this.log(`Deleted ${path.relative(this.projectRoot, validatedFilePath)}`);
165
+ }
166
+ }
167
+ }
168
+ }
169
+ } else {
170
+ // Handle exact files
171
+ const validatedFilePath = SecurityUtils.validatePath(searchPath, this.projectRoot);
172
+ if (validatedFilePath && SecurityUtils.safeExistsSync(validatedFilePath)) {
173
+ fs.unlinkSync(validatedFilePath);
174
+ this.log(`Deleted ${path.relative(this.projectRoot, validatedFilePath)}`);
175
+ }
176
+ }
160
177
  }
161
178
 
162
179
  async validateEssentialFiles() {
163
- this.log('Validating essential files...');
164
-
165
- let missingFiles = [];
166
- for (const file of this.essentialFiles) {
167
- const filePath = path.join(this.projectRoot, file);
168
- if (!SecurityUtils.safeExistsSync(filePath)) {
169
- missingFiles.push(file);
170
- } else if (!fs.statSync(filePath).isFile()) {
171
- this.log(`❌ ${file} is not a file`);
172
- process.exit(1);
173
- }
174
- }
180
+ this.log('Validating essential files...');
181
+
182
+ let missingFiles = [];
183
+ for (const file of this.essentialFiles) {
184
+ const filePath = path.join(this.projectRoot, file);
185
+ const validatedPath = SecurityUtils.validatePath(filePath, this.projectRoot);
186
+ if (!validatedPath || !SecurityUtils.safeExistsSync(validatedPath)) {
187
+ missingFiles.push(file);
188
+ } else {
189
+ const stat = SecurityUtils.safeStatSync(validatedPath, this.projectRoot);
190
+ if (!stat || !stat.isFile()) {
191
+ this.log(`❌ ${file} is not a file`);
192
+ process.exit(1);
193
+ }
194
+ }
195
+ }
175
196
 
176
197
  if (missingFiles.length > 0) {
177
198
  this.log(`❌ Missing essential files: ${missingFiles.join(', ')}`);
@@ -182,19 +203,19 @@ class PrepublishCleaner {
182
203
  }
183
204
 
184
205
  async validateLocaleFiles() {
185
- this.log('Validating locale files...');
186
-
187
- let invalidFiles = [];
188
- for (const localeFile of this.essentialLocales) {
189
- const filePath = path.join(this.projectRoot, localeFile);
190
- if (!SecurityUtils.safeExistsSync(filePath)) {
191
- invalidFiles.push(localeFile);
192
- continue;
193
- }
194
-
195
- try {
196
- const content = SecurityUtils.safeReadFileSync(filePath, path.dirname(filePath), 'utf8');
197
- const parsed = JSON.parse(content);
206
+ this.log('Validating locale files...');
207
+
208
+ let invalidFiles = [];
209
+ for (const localeFile of this.essentialLocales) {
210
+ const filePath = path.join(this.projectRoot, localeFile);
211
+ const validatedPath = SecurityUtils.validatePath(filePath, this.projectRoot);
212
+ if (!validatedPath || !SecurityUtils.safeExistsSync(validatedPath)) {
213
+ invalidFiles.push(localeFile);
214
+ continue;
215
+ }
216
+
217
+ try {
218
+ const content = SecurityUtils.safeReadFileSync(validatedPath, this.projectRoot, 'utf8');
198
219
 
199
220
  // Validate structure
200
221
  if (typeof parsed !== 'object' || parsed === null) {
@@ -220,11 +241,16 @@ class PrepublishCleaner {
220
241
  }
221
242
 
222
243
  async validatePackageJson() {
223
- this.log('Validating package.json...');
224
-
225
- const packagePath = path.join(this.projectRoot, 'package.json');
226
- try {
227
- const pkg = JSON.parse(SecurityUtils.safeReadFileSync(packagePath, path.dirname(packagePath), 'utf8'));
244
+ this.log('Validating package.json...');
245
+
246
+ const packagePath = path.join(this.projectRoot, 'package.json');
247
+ const validatedPath = SecurityUtils.validatePath(packagePath, this.projectRoot);
248
+ if (!validatedPath) {
249
+ this.log('❌ package.json not found or invalid path');
250
+ process.exit(1);
251
+ }
252
+ try {
253
+ const pkg = JSON.parse(SecurityUtils.safeReadFileSync(validatedPath, this.projectRoot, 'utf8'));
228
254
 
229
255
  // Validate required fields
230
256
  const requiredFields = ['name', 'version', 'description', 'main', 'bin', 'files'];
@@ -269,70 +295,58 @@ class PrepublishCleaner {
269
295
  }
270
296
 
271
297
  async finalValidation() {
272
- this.log('Running final validation checks...');
273
-
274
- // Check for development artifacts
275
- const devArtifacts = [
276
- 'dev/debug',
277
- 'benchmarks',
278
- '.github',
279
- 'test-usage-fix.html',
280
- '.i18ntk'
281
- ];
282
-
283
- for (const artifact of devArtifacts) {
284
- const artifactPath = path.join(this.projectRoot, artifact);
285
- if (SecurityUtils.safeExistsSync(artifactPath)) {
286
- this.log(`⚠️ Development artifact found: ${artifact}`);
287
- }
288
- }
298
+ this.log('Running final validation checks...');
299
+
300
+ // Check for development artifacts
301
+ const devArtifacts = [
302
+ 'dev/debug',
303
+ 'benchmarks',
304
+ '.github',
305
+ 'test-usage-fix.html',
306
+ '.i18ntk'
307
+ ];
308
+
309
+ for (const artifact of devArtifacts) {
310
+ const artifactPath = path.join(this.projectRoot, artifact);
311
+ const validatedPath = SecurityUtils.validatePath(artifactPath, this.projectRoot);
312
+ if (validatedPath && SecurityUtils.safeExistsSync(validatedPath)) {
313
+ this.log(`⚠️ Development artifact found: ${artifact}`);
314
+ }
315
+ }
289
316
 
290
317
  // Validate file permissions for executable scripts
291
318
  const scripts = [
292
- 'main/manage/index.js',
293
- 'main/i18ntk-init.js',
294
- 'main/i18ntk-analyze.js',
295
- 'main/i18ntk-validate.js',
296
- 'main/i18ntk-usage.js',
297
- 'main/i18ntk-summary.js',
298
- 'main/i18ntk-sizing.js',
299
- 'main/i18ntk-complete.js',
300
- 'main/i18ntk-ui.js',
301
- 'main/i18ntk-autorun.js'
319
+ 'main/manage/index.js',
320
+ 'main/i18ntk-init.js',
321
+ 'main/i18ntk-analyze.js',
322
+ 'main/i18ntk-validate.js',
323
+ 'main/i18ntk-usage.js',
324
+ 'main/i18ntk-summary.js',
325
+ 'main/i18ntk-sizing.js',
326
+ 'main/i18ntk-complete.js',
327
+ 'main/i18ntk-ui.js',
328
+ 'main/i18ntk-autorun.js'
302
329
  ];
303
330
 
304
331
  for (const script of scripts) {
305
- const scriptPath = path.join(this.projectRoot, script);
306
- if (SecurityUtils.safeExistsSync(scriptPath)) {
307
- try {
308
- fs.accessSync(scriptPath, fs.constants.X_OK);
309
- } catch (e) {
310
- this.log(`⚠️ Script not executable: ${script}`);
311
- }
312
- }
332
+ const scriptPath = path.join(this.projectRoot, script);
333
+ const validatedPath = SecurityUtils.validatePath(scriptPath, this.projectRoot);
334
+ if (validatedPath && SecurityUtils.safeExistsSync(validatedPath)) {
335
+ try {
336
+ fs.accessSync(validatedPath, fs.constants.X_OK);
337
+ } catch (e) {
338
+ this.log(`⚠️ Script not executable: ${script}`);
339
+ }
340
+ }
313
341
  }
314
342
 
315
343
  this.log('✅ Final validation complete');
316
344
  }
317
345
 
318
346
  async resetSecuritySettings() {
319
- const configPath = path.join(require('../settings/settings-manager').configDir, '.i18n-admin-config.json');
320
-
321
- if (SecurityUtils.safeExistsSync(configPath)) {
322
- const defaultConfig = {
323
- enabled: false,
324
- pinHash: null,
325
- sessionTimeout: 30,
326
- maxFailedAttempts: 3,
327
- lockoutDuration: 15,
328
- lastActivity: null,
329
- failedAttempts: 0,
330
- lockedUntil: null
331
- };
332
-
333
- SecurityUtils.safeWriteFileSync(configPath, JSON.stringify(defaultConfig, null, 2));
334
- this.log('Reset security settings to defaults');
335
- }
347
+ // Remove security settings reset to prevent disabling security during publish
348
+ // This script should not modify security configurations
349
+ this.log('Skipping security settings reset to maintain security posture');
336
350
  }
337
351
  }
338
352