i18ntk 2.3.8 → 2.5.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.
@@ -1,147 +1,18 @@
1
- const https = require('https');
2
- const { compareVersions } = require('./version-utils');
1
+ 'use strict';
3
2
 
4
- const DEFAULT_TIMEOUT_MS = 1800;
5
- const NPM_REGISTRY_BASE = 'https://registry.npmjs.org';
3
+ /**
4
+ * Update checks were intentionally removed to avoid outbound network access
5
+ * during CLI startup and to reduce scanner noise in restricted environments.
6
+ *
7
+ * We keep the same exported API for backwards compatibility.
8
+ */
6
9
 
7
- function isSemverLike(version) {
8
- return typeof version === 'string' && /^\d+\.\d+\.\d+([-.][0-9A-Za-z.-]+)?$/.test(version.trim());
10
+ async function checkNpmOutdated() {
11
+ return null;
9
12
  }
10
13
 
11
- function fetchPackageMetadata(packageName, timeoutMs = DEFAULT_TIMEOUT_MS) {
12
- const safePackageName = encodeURIComponent(packageName);
13
- const requestUrl = `${NPM_REGISTRY_BASE}/${safePackageName}`;
14
-
15
- return new Promise((resolve) => {
16
- let settled = false;
17
-
18
- const finish = (value) => {
19
- if (!settled) {
20
- settled = true;
21
- resolve(value);
22
- }
23
- };
24
-
25
- const req = https.get(
26
- requestUrl,
27
- {
28
- timeout: timeoutMs,
29
- headers: {
30
- Accept: 'application/json',
31
- 'User-Agent': 'i18ntk-version-check'
32
- }
33
- },
34
- (res) => {
35
- if (res.statusCode !== 200) {
36
- res.resume();
37
- finish(null);
38
- return;
39
- }
40
-
41
- let raw = '';
42
- res.on('data', (chunk) => {
43
- raw += chunk;
44
- });
45
- res.on('end', () => {
46
- try {
47
- finish(JSON.parse(raw));
48
- } catch {
49
- finish(null);
50
- }
51
- });
52
- }
53
- );
54
-
55
- req.on('timeout', () => {
56
- req.destroy();
57
- finish(null);
58
- });
59
- req.on('error', () => finish(null));
60
- });
61
- }
62
-
63
- function getOutdatedStatus(currentVersion, metadata) {
64
- if (!isSemverLike(currentVersion) || !metadata || !metadata.versions) {
65
- return null;
66
- }
67
-
68
- const distTags = metadata['dist-tags'] || {};
69
- const taggedLatest = distTags.latest;
70
- const allPublishedVersions = Object.keys(metadata.versions).filter(isSemverLike);
71
-
72
- if (allPublishedVersions.length === 0) {
73
- return null;
74
- }
75
-
76
- const latestVersion = isSemverLike(taggedLatest)
77
- ? taggedLatest
78
- : allPublishedVersions.sort(compareVersions).at(-1);
79
-
80
- if (!latestVersion || !isSemverLike(latestVersion)) {
81
- return null;
82
- }
83
-
84
- const currentMeta = metadata.versions[currentVersion] || null;
85
- const isCurrentDeprecated = Boolean(currentMeta && currentMeta.deprecated);
86
- const isOutdated = compareVersions(currentVersion, latestVersion) < 0;
87
-
88
- const newerStableVersions = allPublishedVersions.filter((version) => (
89
- compareVersions(version, currentVersion) > 0 &&
90
- compareVersions(version, latestVersion) <= 0
91
- ));
92
-
93
- return {
94
- latestVersion,
95
- isOutdated,
96
- isCurrentDeprecated,
97
- newerStableCount: newerStableVersions.length
98
- };
99
- }
100
-
101
- async function checkNpmOutdated({ packageName, currentVersion, timeoutMs = DEFAULT_TIMEOUT_MS }) {
102
- const metadata = await fetchPackageMetadata(packageName, timeoutMs);
103
- return getOutdatedStatus(currentVersion, metadata);
104
- }
105
-
106
- async function printUpgradeWarningIfOutdated({
107
- packageName,
108
- currentVersion,
109
- timeoutMs = DEFAULT_TIMEOUT_MS
110
- }) {
111
- const enabled = String(process.env.I18NTK_ENABLE_UPDATE_CHECK || '').toLowerCase();
112
- if (!(enabled === '1' || enabled === 'true' || enabled === 'yes')) {
113
- return;
114
- }
115
-
116
- if (process.env.I18NTK_DISABLE_UPDATE_CHECK === 'true') {
117
- return;
118
- }
119
-
120
- const status = await checkNpmOutdated({ packageName, currentVersion, timeoutMs });
121
- if (!status) {
122
- return;
123
- }
124
-
125
- if (status.isCurrentDeprecated) {
126
- console.warn(
127
- `\n⚠️ Installed ${packageName}@${currentVersion} is deprecated on npm. ` +
128
- `Upgrade to ${packageName}@${status.latestVersion}:\n` +
129
- ` npm install -g ${packageName}@latest`
130
- );
131
- return;
132
- }
133
-
134
- if (status.isOutdated) {
135
- const suffix = status.newerStableCount === 1 ? '' : 's';
136
- console.warn(
137
- `\n⚠️ Update available for ${packageName}: ${currentVersion} -> ${status.latestVersion} ` +
138
- `(${status.newerStableCount} newer release${suffix}).\n` +
139
- ` Run: npm install -g ${packageName}@latest`
140
- );
141
- }
14
+ async function printUpgradeWarningIfOutdated() {
15
+ return;
142
16
  }
143
17
 
144
- module.exports = {
145
- checkNpmOutdated,
146
- printUpgradeWarningIfOutdated
147
- };
18
+ module.exports = { checkNpmOutdated, printUpgradeWarningIfOutdated };
@@ -1,41 +1,44 @@
1
- const { getGlobalReadline, closeGlobalReadline, ask } = require('./cli');
2
-
3
- function isInteractive(opts={}) {
4
- const envSilent = process.env.CI === 'true' || process.env.I18NTK_SILENT === '1' || (process.env.npm_config_loglevel||'').toLowerCase()==='silent';
5
- if (opts.noPrompt) return false;
6
- if (envSilent) return false;
7
- return process.stdin.isTTY === true;
8
- }
9
-
10
- class NoopPrompt {
11
- question(_q) { return Promise.resolve(''); }
12
- close() {}
13
- async pressEnterToContinue() { return; }
14
- }
15
-
16
- class RLPrompt {
17
- constructor() {
18
- this.rl = getGlobalReadline();
19
- this.closed = false;
20
- }
21
- question(q) {
22
- if (this.closed) return Promise.resolve('');
23
- return new Promise(res => this.rl.question(q, ans => res(ans)));
24
- }
25
- close() {
26
- if (!this.closed) {
27
- closeGlobalReadline();
28
- this.closed = true;
29
- }
30
- }
31
- async pressEnterToContinue(msg='\nPress Enter to continue...') {
32
- if (this.closed) return;
33
- await ask(msg);
34
- }
35
- }
36
-
37
- function createPrompt(opts={}) {
38
- return isInteractive(opts) ? new RLPrompt() : new NoopPrompt();
39
- }
40
-
41
- module.exports = { createPrompt, isInteractive };
1
+ const { getGlobalReadline, closeGlobalReadline, ask } = require('./cli');
2
+ const { envManager } = require('./env-manager');
3
+
4
+ function isInteractive(opts={}) {
5
+ const envSilent = envManager.getBoolean('CI') ||
6
+ envManager.getBoolean('I18NTK_SILENT') ||
7
+ envManager.get('npm_config_loglevel') === 'silent';
8
+ if (opts.noPrompt) return false;
9
+ if (envSilent) return false;
10
+ return process.stdin.isTTY === true;
11
+ }
12
+
13
+ class NoopPrompt {
14
+ question(_q) { return Promise.resolve(''); }
15
+ close() {}
16
+ async pressEnterToContinue() { return; }
17
+ }
18
+
19
+ class RLPrompt {
20
+ constructor() {
21
+ this.rl = getGlobalReadline();
22
+ this.closed = false;
23
+ }
24
+ question(q) {
25
+ if (this.closed) return Promise.resolve('');
26
+ return new Promise(res => this.rl.question(q, ans => res(ans)));
27
+ }
28
+ close() {
29
+ if (!this.closed) {
30
+ closeGlobalReadline();
31
+ this.closed = true;
32
+ }
33
+ }
34
+ async pressEnterToContinue(msg='\nPress Enter to continue...') {
35
+ if (this.closed) return;
36
+ await ask(msg);
37
+ }
38
+ }
39
+
40
+ function createPrompt(opts={}) {
41
+ return isInteractive(opts) ? new RLPrompt() : new NoopPrompt();
42
+ }
43
+
44
+ module.exports = { createPrompt, isInteractive };
@@ -1,154 +1,156 @@
1
- // Secure error handling utilities
2
- const crypto = require('crypto');
3
- const fs = require('fs');
4
- const path = require('path');
5
-
6
- class SecureError extends Error {
7
- constructor(message, code = 'SECURE_ERROR', details = {}) {
8
- super(message);
9
- this.name = 'SecureError';
10
- this.code = code;
11
- this.details = details;
12
- this.timestamp = new Date().toISOString();
13
- this.errorId = crypto.randomBytes(8).toString('hex');
14
-
15
- // Capture stack trace, excluding constructor call from it
16
- Error.captureStackTrace(this, this.constructor);
17
- }
18
-
19
- toJSON() {
20
- return {
21
- error: this.name,
22
- message: this.message,
23
- code: this.code,
24
- errorId: this.errorId,
25
- timestamp: this.timestamp
26
- };
27
- }
28
-
29
- static sanitizeError(error) {
30
- if (error instanceof SecureError) {
31
- return error.toJSON();
32
- }
33
-
34
- // For non-SecureError instances, return a sanitized version
35
- return {
36
- error: 'InternalError',
37
- message: 'An internal error occurred',
38
- code: 'INTERNAL_ERROR',
39
- errorId: crypto.randomBytes(8).toString('hex'),
40
- timestamp: new Date().toISOString()
41
- };
42
- }
43
- }
44
-
45
- // Common error types
46
- class ValidationError extends SecureError {
47
- constructor(message = 'Validation failed', details = {}) {
48
- super(message, 'VALIDATION_ERROR', details);
49
- this.name = 'ValidationError';
50
- }
51
- }
52
-
53
- class SecurityError extends SecureError {
54
- constructor(message = 'Security violation detected', details = {}) {
55
- super(message, 'SECURITY_VIOLATION', details);
56
- this.name = 'SecurityError';
57
- }
58
- }
59
-
60
- class EncryptionError extends SecureError {
61
- constructor(message = 'Encryption/decryption failed', details = {}) {
62
- super(message, 'ENCRYPTION_ERROR', details);
63
- this.name = 'EncryptionError';
64
- }
65
- }
66
-
67
- // Secure error handler middleware
68
- function secureErrorHandler(options = {}) {
69
- const defaults = {
70
- logErrors: process.env.NODE_ENV !== 'production',
71
- logFunction: console.error,
72
- logFilePath: null,
73
- sanitizeStack: true
74
- };
75
-
76
- const config = { ...defaults, ...options };
77
-
78
- // Ensure log directory exists
79
- if (config.logFilePath && !SecurityUtils.safeExistsSync(path.dirname(config.logFilePath))) {
80
- try {
81
- fs.mkdirSync(path.dirname(config.logFilePath), { recursive: true });
82
- } catch (e) {
83
- console.error('Failed to create log directory:', e);
84
- }
85
- }
86
-
87
- return function(error, req, res, next) {
88
- // Handle specific error types
89
- let statusCode = 500;
90
- let response = {};
91
-
92
- if (error instanceof ValidationError) {
93
- statusCode = 400;
94
- response = error.toJSON();
95
- } else if (error instanceof SecurityError) {
96
- statusCode = 403;
97
- response = error.toJSON();
98
- } else if (error instanceof EncryptionError) {
99
- statusCode = 400;
100
- response = error.toJSON();
101
- } else {
102
- // Generic error handling
103
- response = SecureError.sanitizeError(error);
104
- }
105
-
106
- // Log the error if enabled
107
- if (config.logErrors) {
108
- const logEntry = {
109
- timestamp: new Date().toISOString(),
110
- error: {
111
- name: error.name,
112
- message: error.message,
113
- stack: config.sanitizeStack
114
- ? error.stack.split('\n').slice(0, 3).join('\n') + '\n ...'
115
- : error.stack,
116
- ...(error.details && { details: error.details }),
117
- errorId: response.errorId
118
- },
119
- request: req ? {
120
- method: req.method,
121
- url: req.originalUrl,
122
- ip: req.ip,
123
- userAgent: req.get('user-agent')
124
- } : undefined
125
- };
126
-
127
- // Log to console
128
- if (typeof config.logFunction === 'function') {
129
- SecurityUtils.safeWriteFileSync(config.logFilePath, JSON.stringify(logEntry, null, 2));
130
- }
131
-
132
- // Log to file if configured
133
- if (config.logFilePath) {
134
- SecurityUtils.safeWriteFileSync(
135
- config.logFilePath,
136
- JSON.stringify(logEntry) + '\n',
137
- 'utf8'
138
- );
139
- }
140
- }
141
-
142
- // Send response
143
- res.status(statusCode).json(response);
144
- };
145
- }
146
-
147
- module.exports = {
148
- SecureError,
149
- ValidationError,
150
- SecurityError,
151
- EncryptionError,
152
- secureErrorHandler,
153
- createError: (message, code, details) => new SecureError(message, code, details)
154
- };
1
+ // Secure error handling utilities
2
+ const crypto = require('crypto');
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const SecurityUtils = require('./security');
6
+ const { envManager } = require('./env-manager');
7
+
8
+ class SecureError extends Error {
9
+ constructor(message, code = 'SECURE_ERROR', details = {}) {
10
+ super(message);
11
+ this.name = 'SecureError';
12
+ this.code = code;
13
+ this.details = details;
14
+ this.timestamp = new Date().toISOString();
15
+ this.errorId = crypto.randomBytes(8).toString('hex');
16
+
17
+ // Capture stack trace, excluding constructor call from it
18
+ Error.captureStackTrace(this, this.constructor);
19
+ }
20
+
21
+ toJSON() {
22
+ return {
23
+ error: this.name,
24
+ message: this.message,
25
+ code: this.code,
26
+ errorId: this.errorId,
27
+ timestamp: this.timestamp
28
+ };
29
+ }
30
+
31
+ static sanitizeError(error) {
32
+ if (error instanceof SecureError) {
33
+ return error.toJSON();
34
+ }
35
+
36
+ // For non-SecureError instances, return a sanitized version
37
+ return {
38
+ error: 'InternalError',
39
+ message: 'An internal error occurred',
40
+ code: 'INTERNAL_ERROR',
41
+ errorId: crypto.randomBytes(8).toString('hex'),
42
+ timestamp: new Date().toISOString()
43
+ };
44
+ }
45
+ }
46
+
47
+ // Common error types
48
+ class ValidationError extends SecureError {
49
+ constructor(message = 'Validation failed', details = {}) {
50
+ super(message, 'VALIDATION_ERROR', details);
51
+ this.name = 'ValidationError';
52
+ }
53
+ }
54
+
55
+ class SecurityError extends SecureError {
56
+ constructor(message = 'Security violation detected', details = {}) {
57
+ super(message, 'SECURITY_VIOLATION', details);
58
+ this.name = 'SecurityError';
59
+ }
60
+ }
61
+
62
+ class EncryptionError extends SecureError {
63
+ constructor(message = 'Encryption/decryption failed', details = {}) {
64
+ super(message, 'ENCRYPTION_ERROR', details);
65
+ this.name = 'EncryptionError';
66
+ }
67
+ }
68
+
69
+ // Secure error handler middleware
70
+ function secureErrorHandler(options = {}) {
71
+ const defaults = {
72
+ logErrors: envManager.get('NODE_ENV') !== 'production',
73
+ logFunction: console.error,
74
+ logFilePath: null,
75
+ sanitizeStack: true
76
+ };
77
+
78
+ const config = { ...defaults, ...options };
79
+
80
+ // Ensure log directory exists
81
+ if (config.logFilePath && !SecurityUtils.safeExistsSync(path.dirname(config.logFilePath))) {
82
+ try {
83
+ fs.mkdirSync(path.dirname(config.logFilePath), { recursive: true });
84
+ } catch (e) {
85
+ console.error('Failed to create log directory:', e);
86
+ }
87
+ }
88
+
89
+ return function(error, req, res, next) {
90
+ // Handle specific error types
91
+ let statusCode = 500;
92
+ let response = {};
93
+
94
+ if (error instanceof ValidationError) {
95
+ statusCode = 400;
96
+ response = error.toJSON();
97
+ } else if (error instanceof SecurityError) {
98
+ statusCode = 403;
99
+ response = error.toJSON();
100
+ } else if (error instanceof EncryptionError) {
101
+ statusCode = 400;
102
+ response = error.toJSON();
103
+ } else {
104
+ // Generic error handling
105
+ response = SecureError.sanitizeError(error);
106
+ }
107
+
108
+ // Log the error if enabled
109
+ if (config.logErrors) {
110
+ const logEntry = {
111
+ timestamp: new Date().toISOString(),
112
+ error: {
113
+ name: error.name,
114
+ message: error.message,
115
+ stack: config.sanitizeStack
116
+ ? error.stack.split('\n').slice(0, 3).join('\n') + '\n ...'
117
+ : error.stack,
118
+ ...(error.details && { details: error.details }),
119
+ errorId: response.errorId
120
+ },
121
+ request: req ? {
122
+ method: req.method,
123
+ url: req.originalUrl,
124
+ ip: req.ip,
125
+ userAgent: req.get('user-agent')
126
+ } : undefined
127
+ };
128
+
129
+ // Log to console
130
+ if (typeof config.logFunction === 'function') {
131
+ SecurityUtils.safeWriteFileSync(config.logFilePath, JSON.stringify(logEntry, null, 2));
132
+ }
133
+
134
+ // Log to file if configured
135
+ if (config.logFilePath) {
136
+ SecurityUtils.safeWriteFileSync(
137
+ config.logFilePath,
138
+ JSON.stringify(logEntry) + '\n',
139
+ 'utf8'
140
+ );
141
+ }
142
+ }
143
+
144
+ // Send response
145
+ res.status(statusCode).json(response);
146
+ };
147
+ }
148
+
149
+ module.exports = {
150
+ SecureError,
151
+ ValidationError,
152
+ SecurityError,
153
+ EncryptionError,
154
+ secureErrorHandler,
155
+ createError: (message, code, details) => new SecureError(message, code, details)
156
+ };