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/README.md +9 -6
- package/main/i18ntk-backup-class.js +35 -423
- package/main/manage/commands/BackupCommand.js +62 -62
- package/main/manage/services/SetupService.js +444 -462
- package/package.json +12 -9
- package/utils/config-manager.js +84 -30
- package/utils/config.js +15 -14
- package/utils/i18n-helper.js +35 -20
- package/utils/logger.js +233 -64
- package/utils/npm-version-warning.js +12 -141
- package/utils/security.js +233 -150
package/utils/logger.js
CHANGED
|
@@ -1,64 +1,233 @@
|
|
|
1
|
-
const colors = require('./colors-new');
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
|
|
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
|
-
|
|
2
|
-
const { compareVersions } = require('./version-utils');
|
|
1
|
+
'use strict';
|
|
3
2
|
|
|
4
|
-
|
|
5
|
-
|
|
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
|
|
8
|
-
return
|
|
10
|
+
async function checkNpmOutdated() {
|
|
11
|
+
return null;
|
|
9
12
|
}
|
|
10
13
|
|
|
11
|
-
function
|
|
12
|
-
|
|
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 };
|