i18ntk 2.3.7 → 2.4.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.
package/utils/logger.js CHANGED
@@ -1,64 +1,233 @@
1
- const colors = require('./colors-new');
2
- const { envManager } = require('./env-manager');
3
-
4
- // Enhanced logger with TTY detection and proper stream handling
5
- const logger = {
6
- // Basic log function with color support
7
- log: (message, color = '') => {
8
- const output = color ? color(message) : message;
9
- if (process.stdout.isTTY) {
10
- process.stdout.write(output + '\n');
11
- } else {
12
- console.log(output);
13
- }
14
- },
15
-
16
- // Error logging (goes to stderr)
17
- error: (message) => {
18
- const output = `❌ ${message}`;
19
- if (process.stderr.isTTY) {
20
- process.stderr.write(colors.bright(colors.red(output)) + '\n');
21
- } else {
22
- console.error(output);
23
- }
24
- },
25
-
26
- // Success logging
27
- success: (message) => {
28
- const output = `✅ ${message}`;
29
- logger.log(output, colors.green);
30
- },
31
-
32
- // Warning logging (goes to stderr)
33
- warn: (message) => {
34
- const output = `⚠️ ${message}`;
35
- if (process.stderr.isTTY) {
36
- process.stderr.write(colors.bright(colors.yellow(output)) + '\n');
37
- } else {
38
- console.warn(output);
39
- }
40
- },
41
-
42
- // Info logging
43
- info: (message) => {
44
- const output = `ℹ️ ${message}`;
45
- logger.log(output, colors.blue);
46
- },
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 = logLevel === 'debug';
52
-
53
- if (debugEnabled) {
54
- const output = `[DEBUG] ${message}`;
55
- if (process.stderr.isTTY) {
56
- process.stderr.write(colors.gray(output) + '\n');
57
- } else {
58
- console.debug(output);
59
- }
60
- }
61
- }
62
- };
63
-
64
- module.exports = { colors, logger };
1
+ const colors = require('./colors-new');
2
+
3
+ const LEVELS = { error: 0, warn: 1, info: 2, debug: 3 };
4
+ const DEFAULT_INTERNAL_PREFIXES = ['[BUILD]', '[WORKERS]', '[I18N]', '[SECURITY]', '[SUCCESS]', '[WARN]', '[ERROR]', '[INFO]', '[DEBUG]'];
5
+ const FIRST_ERROR_CONTEXT = new Map();
6
+
7
+ function asBoolean(value) {
8
+ return String(value || '').trim().toLowerCase() === 'true';
9
+ }
10
+
11
+ function resolveLevel() {
12
+ const debugMode = asBoolean(process.env.DEBUG_MODE);
13
+ const configured = String(process.env.I18NTK_LOG_LEVEL || '').trim().toLowerCase();
14
+ if (configured && Object.prototype.hasOwnProperty.call(LEVELS, configured)) {
15
+ return configured;
16
+ }
17
+
18
+ if (debugMode) {
19
+ return 'debug';
20
+ }
21
+
22
+ // Silent-by-default in production-like builds.
23
+ if (process.env.NODE_ENV === 'production' || asBoolean(process.env.CI)) {
24
+ return 'error';
25
+ }
26
+
27
+ return 'warn';
28
+ }
29
+
30
+ function shouldLog(level) {
31
+ const active = resolveLevel();
32
+ return LEVELS[level] <= LEVELS[active];
33
+ }
34
+
35
+ function normalizePrefix(prefix, fallback) {
36
+ const value = String(prefix || fallback || '[INFO]').trim();
37
+ if (/^\[[^\]]+\]$/.test(value)) {
38
+ return value;
39
+ }
40
+ return `[${value.replace(/^[\[]|[\]]$/g, '').toUpperCase()}]`;
41
+ }
42
+
43
+ function write(level, message, options = {}) {
44
+ if (!shouldLog(level)) return;
45
+
46
+ const jsonMode = asBoolean(process.env.JSON_LOG);
47
+ const prefix = normalizePrefix(options.prefix, `[${level.toUpperCase()}]`);
48
+ const details = options.details && typeof options.details === 'object' ? options.details : undefined;
49
+ const text = String(message || '').trim();
50
+
51
+ if (jsonMode) {
52
+ const payload = {
53
+ timestamp: new Date().toISOString(),
54
+ level,
55
+ prefix,
56
+ message: text,
57
+ ...(details ? { details } : {})
58
+ };
59
+ const line = JSON.stringify(payload);
60
+ if (level === 'error' || level === 'warn') {
61
+ process.stderr.write(`${line}\n`);
62
+ } else {
63
+ process.stdout.write(`${line}\n`);
64
+ }
65
+ return;
66
+ }
67
+
68
+ const line = `${prefix} ${text}`;
69
+ if (level === 'error') {
70
+ process.stderr.write(`${colors.red(line)}\n`);
71
+ return;
72
+ }
73
+ if (level === 'warn') {
74
+ process.stderr.write(`${colors.yellow(line)}\n`);
75
+ return;
76
+ }
77
+ if (level === 'debug') {
78
+ process.stdout.write(`${colors.gray(line)}\n`);
79
+ return;
80
+ }
81
+
82
+ process.stdout.write(`${line}\n`);
83
+ }
84
+
85
+ function formatDuration(ms) {
86
+ if (!Number.isFinite(ms) || ms < 0) return 'instant';
87
+ if (ms < 50) return 'instant';
88
+ if (ms < 1000) return `${Math.round(ms)}ms`;
89
+ if (ms < 60000) return `${(ms / 1000).toFixed(1)}s`;
90
+ const minutes = Math.floor(ms / 60000);
91
+ const seconds = Math.round((ms % 60000) / 1000);
92
+ return `${minutes}m${seconds}s`;
93
+ }
94
+
95
+ function recordFirstError(key, context) {
96
+ if (!key || FIRST_ERROR_CONTEXT.has(key)) {
97
+ return;
98
+ }
99
+
100
+ FIRST_ERROR_CONTEXT.set(key, {
101
+ timestamp: new Date().toISOString(),
102
+ ...context
103
+ });
104
+ }
105
+
106
+ function getFirstErrorContext(key) {
107
+ return FIRST_ERROR_CONTEXT.get(key) || null;
108
+ }
109
+
110
+ function flushErrorContexts() {
111
+ const entries = Array.from(FIRST_ERROR_CONTEXT.entries()).map(([key, value]) => ({ key, ...value }));
112
+ FIRST_ERROR_CONTEXT.clear();
113
+ return entries;
114
+ }
115
+
116
+ function emitErrorContextSummary({ force = false } = {}) {
117
+ const contexts = Array.from(FIRST_ERROR_CONTEXT.entries());
118
+ if (!contexts.length) return;
119
+ if (!force && !asBoolean(process.env.DEBUG_MODE)) return;
120
+
121
+ for (const [key, context] of contexts) {
122
+ write('debug', `First error context (${key})`, { prefix: '[ERROR]', details: context });
123
+ }
124
+ }
125
+
126
+ function logMissingTranslationKey(key, fallback = 'Configuration error') {
127
+ write('warn', `Missing key: ${key} (fallback: '${fallback}')`, { prefix: '[I18N]' });
128
+ }
129
+
130
+ function createBuildProgressReporter(totalPages) {
131
+ const safeTotal = Math.max(1, Number(totalPages) || 1);
132
+ let lastBucket = 0;
133
+
134
+ return {
135
+ update(completedPages) {
136
+ const completed = Math.max(0, Math.min(safeTotal, Number(completedPages) || 0));
137
+ const percent = Math.floor((completed / safeTotal) * 100);
138
+ const bucket = Math.floor(percent / 10) * 10;
139
+ if (bucket <= lastBucket || bucket === 0) return;
140
+ lastBucket = bucket;
141
+
142
+ const pct = `${String(bucket).padStart(3, ' ')}%`;
143
+ write('info', `${pct} (${completed}/${safeTotal} pages)`, { prefix: '[BUILD]' });
144
+ }
145
+ };
146
+ }
147
+
148
+ function buildSuccessSummary({ durationMs, pages, warnings = 0 }) {
149
+ const duration = formatDuration(durationMs);
150
+ write('info', `Build completed in ${duration} (${pages} pages, ${warnings} warnings)`, { prefix: '[SUCCESS]' });
151
+ }
152
+
153
+ function createWorkerPoolMonitor(activeWorkers = 0) {
154
+ const startedAt = Date.now();
155
+ let tasks = 0;
156
+ let totalTaskDurationMs = 0;
157
+ let workers = Math.max(0, Number(activeWorkers) || 0);
158
+
159
+ return {
160
+ setActive(count) {
161
+ workers = Math.max(0, Number(count) || 0);
162
+ },
163
+ recordTask(durationMs) {
164
+ tasks += 1;
165
+ totalTaskDurationMs += Math.max(0, Number(durationMs) || 0);
166
+ },
167
+ report() {
168
+ const avg = tasks > 0 ? (totalTaskDurationMs / tasks) / 1000 : 0;
169
+ write('info', `${workers} active (avg ${avg.toFixed(1)}s/task)`, { prefix: '[WORKERS]' });
170
+ return {
171
+ workers,
172
+ tasks,
173
+ avgSecondsPerTask: Number(avg.toFixed(2)),
174
+ elapsed: formatDuration(Date.now() - startedAt)
175
+ };
176
+ }
177
+ };
178
+ }
179
+
180
+ const logger = {
181
+ log(message, color = null) {
182
+ const output = typeof color === 'function' ? color(String(message)) : String(message);
183
+ write('info', output, { prefix: '[INFO]' });
184
+ },
185
+ info(message, details) {
186
+ write('info', String(message), { prefix: '[INFO]', details });
187
+ },
188
+ warn(message, details) {
189
+ write('warn', String(message), { prefix: '[WARN]', details });
190
+ },
191
+ error(message, details) {
192
+ write('error', String(message), { prefix: '[ERROR]', details });
193
+ },
194
+ debug(message, details) {
195
+ write('debug', String(message), { prefix: '[DEBUG]', details });
196
+ },
197
+ success(message, details) {
198
+ write('info', String(message), { prefix: '[SUCCESS]', details });
199
+ },
200
+ security(level, message, details) {
201
+ write(level, String(message), { prefix: '[SECURITY]', details });
202
+ },
203
+ build(message, details) {
204
+ write('info', String(message), { prefix: '[BUILD]', details });
205
+ },
206
+ isDebugMode() {
207
+ return asBoolean(process.env.DEBUG_MODE) || resolveLevel() === 'debug';
208
+ },
209
+ shouldLog,
210
+ formatDuration,
211
+ recordFirstError,
212
+ getFirstErrorContext,
213
+ flushErrorContexts,
214
+ emitErrorContextSummary,
215
+ buildSuccessSummary,
216
+ logMissingTranslationKey,
217
+ createBuildProgressReporter,
218
+ createWorkerPoolMonitor,
219
+ // Keep compatibility for callers that rely on known prefixes.
220
+ KNOWN_PREFIXES: DEFAULT_INTERNAL_PREFIXES
221
+ };
222
+
223
+ module.exports = {
224
+ colors,
225
+ logger,
226
+ formatDuration,
227
+ createBuildProgressReporter,
228
+ createWorkerPoolMonitor,
229
+ recordFirstError,
230
+ getFirstErrorContext,
231
+ flushErrorContexts,
232
+ emitErrorContextSummary
233
+ };
@@ -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 };