ultimate-jekyll-manager 0.0.53 → 0.0.54
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/dist/assets/js/modules/redirect.js +31 -1
- package/dist/gulp/tasks/imagemin.js +149 -9
- package/dist/gulp/tasks/jekyll.js +29 -0
- package/dist/gulp/tasks/serve.js +6 -5
- package/dist/gulp/tasks/translation.js +78 -13
- package/dist/gulp/tasks/utils/github-cache.js +87 -3
- package/package.json +1 -1
|
@@ -5,10 +5,28 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
const performRedirect = () => {
|
|
8
|
+
// Debug: Log initial state
|
|
9
|
+
console.log('[Redirect] Script started at', new Date().toISOString());
|
|
10
|
+
console.log('[Redirect] Document readyState:', document.readyState);
|
|
11
|
+
console.log('[Redirect] Current URL:', window.location.href);
|
|
12
|
+
console.log('[Redirect] User Agent:', navigator.userAgent);
|
|
13
|
+
|
|
14
|
+
// Check for spinner element to verify DOM is loaded
|
|
15
|
+
const $spinner = document.querySelector('.spinner-border');
|
|
16
|
+
console.log('[Redirect] Spinner element found:', !!$spinner);
|
|
17
|
+
if ($spinner) {
|
|
18
|
+
console.log('[Redirect] Spinner classes:', $spinner.className);
|
|
19
|
+
console.log('[Redirect] Spinner computed display:', window.getComputedStyle($spinner).display);
|
|
20
|
+
console.log('[Redirect] Spinner computed visibility:', window.getComputedStyle($spinner).visibility);
|
|
21
|
+
console.log('[Redirect] Spinner animation:', window.getComputedStyle($spinner).animation);
|
|
22
|
+
}
|
|
23
|
+
|
|
8
24
|
// Get redirect configuration element
|
|
9
25
|
const $redirectConfig = document.getElementById('redirect-config');
|
|
10
26
|
if (!$redirectConfig) {
|
|
11
27
|
console.error('[Redirect] Configuration element #redirect-config not found');
|
|
28
|
+
console.error('[Redirect] Available IDs in document:',
|
|
29
|
+
Array.from(document.querySelectorAll('[id]')).map(el => el.id));
|
|
12
30
|
return;
|
|
13
31
|
}
|
|
14
32
|
|
|
@@ -33,6 +51,8 @@ const performRedirect = () => {
|
|
|
33
51
|
// Parse URLs
|
|
34
52
|
const currentUrl = new URL(window.location.href);
|
|
35
53
|
const siteUrl = new URL(config.siteUrl);
|
|
54
|
+
console.log('[Redirect] Current URL:', currentUrl.toString());
|
|
55
|
+
console.log('[Redirect] Site URL:', siteUrl.toString());
|
|
36
56
|
|
|
37
57
|
// Parse modifier function
|
|
38
58
|
let modifierFunction = (url) => url;
|
|
@@ -40,6 +60,7 @@ const performRedirect = () => {
|
|
|
40
60
|
try {
|
|
41
61
|
// Safely evaluate modifier function
|
|
42
62
|
modifierFunction = new Function('url', `return (${config.modifier})(url)`);
|
|
63
|
+
console.log('[Redirect] Modifier function parsed successfully');
|
|
43
64
|
} catch (error) {
|
|
44
65
|
console.warn('[Redirect] Failed to parse modifier function:', error);
|
|
45
66
|
console.warn('[Redirect] Modifier string:', config.modifier);
|
|
@@ -49,6 +70,8 @@ const performRedirect = () => {
|
|
|
49
70
|
// Determine redirect delay
|
|
50
71
|
const isDevelopment = config.environment === 'development';
|
|
51
72
|
const timeout = isDevelopment ? 3000 : 1;
|
|
73
|
+
console.log('[Redirect] Environment:', config.environment);
|
|
74
|
+
console.log('[Redirect] Timeout:', timeout, 'ms');
|
|
52
75
|
|
|
53
76
|
// Build redirect URL
|
|
54
77
|
let redirectUrl;
|
|
@@ -67,6 +90,7 @@ const performRedirect = () => {
|
|
|
67
90
|
// Default to site home page
|
|
68
91
|
redirectUrl = new URL(siteUrl);
|
|
69
92
|
}
|
|
93
|
+
console.log('[Redirect] Redirect URL built:', redirectUrl.toString());
|
|
70
94
|
} catch (error) {
|
|
71
95
|
console.error('[Redirect] Invalid redirect URL:', config.url, error);
|
|
72
96
|
redirectUrl = new URL(siteUrl);
|
|
@@ -93,7 +117,7 @@ const performRedirect = () => {
|
|
|
93
117
|
}
|
|
94
118
|
|
|
95
119
|
// Log redirect details
|
|
96
|
-
console.group('[Redirect]
|
|
120
|
+
console.group('[Redirect] Summary');
|
|
97
121
|
console.log('Original URL:', config.url);
|
|
98
122
|
console.log('Querystring forwarding:', shouldForwardQuerystring);
|
|
99
123
|
console.log('Modifier:', config.modifier);
|
|
@@ -108,17 +132,23 @@ const performRedirect = () => {
|
|
|
108
132
|
}
|
|
109
133
|
|
|
110
134
|
// Perform the redirect
|
|
135
|
+
console.log('[Redirect] Setting timeout for redirect');
|
|
111
136
|
setTimeout(() => {
|
|
137
|
+
console.log('[Redirect] Timeout fired, redirecting to:', finalUrl);
|
|
112
138
|
window.location.href = finalUrl;
|
|
113
139
|
}, timeout);
|
|
114
140
|
};
|
|
115
141
|
|
|
116
142
|
// Initialize based on Manager availability and DOM state
|
|
143
|
+
console.log('[Redirect] Initial document.readyState:', document.readyState);
|
|
144
|
+
|
|
117
145
|
if (document.readyState === 'loading') {
|
|
118
146
|
// Wait for DOM if still loading
|
|
147
|
+
console.log('[Redirect] Waiting for DOMContentLoaded');
|
|
119
148
|
document.addEventListener('DOMContentLoaded', performRedirect);
|
|
120
149
|
} else {
|
|
121
150
|
// DOM is already ready
|
|
151
|
+
console.log('[Redirect] Document already loaded, running immediately');
|
|
122
152
|
performRedirect();
|
|
123
153
|
}
|
|
124
154
|
|
|
@@ -51,6 +51,9 @@ async function imagemin(complete) {
|
|
|
51
51
|
// Log
|
|
52
52
|
logger.log('Starting...');
|
|
53
53
|
|
|
54
|
+
// Track timing
|
|
55
|
+
const startTime = Date.now();
|
|
56
|
+
|
|
54
57
|
// Initialize cache on first run
|
|
55
58
|
if (index === 0) {
|
|
56
59
|
// Log responsive configurations
|
|
@@ -66,6 +69,18 @@ async function imagemin(complete) {
|
|
|
66
69
|
return complete();
|
|
67
70
|
}
|
|
68
71
|
|
|
72
|
+
// Track statistics
|
|
73
|
+
const stats = {
|
|
74
|
+
totalImages: 0,
|
|
75
|
+
fromCache: 0,
|
|
76
|
+
optimized: 0,
|
|
77
|
+
cachedFiles: [],
|
|
78
|
+
optimizedFiles: [],
|
|
79
|
+
sizeBefore: 0,
|
|
80
|
+
sizeAfter: 0,
|
|
81
|
+
savedBytes: 0
|
|
82
|
+
};
|
|
83
|
+
|
|
69
84
|
// Get all images
|
|
70
85
|
const files = glob(input);
|
|
71
86
|
if (files.length === 0) {
|
|
@@ -73,6 +88,7 @@ async function imagemin(complete) {
|
|
|
73
88
|
return complete();
|
|
74
89
|
}
|
|
75
90
|
|
|
91
|
+
stats.totalImages = files.length;
|
|
76
92
|
logger.log(`Found ${files.length} images to process`);
|
|
77
93
|
|
|
78
94
|
// Load metadata
|
|
@@ -85,16 +101,34 @@ async function imagemin(complete) {
|
|
|
85
101
|
}
|
|
86
102
|
|
|
87
103
|
// Determine what needs processing
|
|
88
|
-
const { filesToProcess, validCachePaths } = await determineFilesToProcess(files, meta, githubCache);
|
|
104
|
+
const { filesToProcess, validCachePaths } = await determineFilesToProcess(files, meta, githubCache, stats);
|
|
89
105
|
|
|
90
106
|
// Handle case where all files are from cache
|
|
91
107
|
if (filesToProcess.length === 0) {
|
|
92
108
|
logger.log('✅ All images from cache');
|
|
93
|
-
|
|
109
|
+
|
|
110
|
+
// Calculate timing
|
|
111
|
+
const endTime = Date.now();
|
|
112
|
+
const elapsedMs = endTime - startTime;
|
|
113
|
+
|
|
114
|
+
// Log statistics
|
|
115
|
+
logImageStatistics(stats, startTime, endTime);
|
|
116
|
+
|
|
117
|
+
await handleCacheOnlyUpdate(githubCache, metaPath, meta, validCachePaths, files.length, stats, { startTime, endTime, elapsedMs });
|
|
94
118
|
return complete();
|
|
95
119
|
}
|
|
96
120
|
|
|
97
121
|
logger.log(`🔄 Processing ${filesToProcess.length} images`);
|
|
122
|
+
stats.optimized = filesToProcess.length;
|
|
123
|
+
|
|
124
|
+
// Track sizes for optimization
|
|
125
|
+
for (const file of filesToProcess) {
|
|
126
|
+
const fileStats = jetpack.inspect(file);
|
|
127
|
+
if (fileStats) {
|
|
128
|
+
stats.sizeBefore += fileStats.size;
|
|
129
|
+
}
|
|
130
|
+
stats.optimizedFiles.push(path.relative(rootPathProject, file));
|
|
131
|
+
}
|
|
98
132
|
|
|
99
133
|
// Process images
|
|
100
134
|
return src(filesToProcess, { base: 'src/assets/images' })
|
|
@@ -113,8 +147,24 @@ async function imagemin(complete) {
|
|
|
113
147
|
const relativePath = path.relative(path.join(rootPathProject, output), file.path);
|
|
114
148
|
const cachePath = path.join(CACHE_DIR, 'images', relativePath);
|
|
115
149
|
jetpack.copy(file.path, cachePath, { overwrite: true });
|
|
150
|
+
|
|
151
|
+
// Track size after optimization
|
|
152
|
+
const fileStats = jetpack.inspect(file.path);
|
|
153
|
+
if (fileStats) {
|
|
154
|
+
stats.sizeAfter += fileStats.size;
|
|
155
|
+
}
|
|
116
156
|
})
|
|
117
157
|
.on('finish', async () => {
|
|
158
|
+
// Calculate final statistics
|
|
159
|
+
stats.savedBytes = stats.sizeBefore - stats.sizeAfter;
|
|
160
|
+
|
|
161
|
+
// Calculate timing
|
|
162
|
+
const endTime = Date.now();
|
|
163
|
+
const elapsedMs = endTime - startTime;
|
|
164
|
+
|
|
165
|
+
// Log statistics
|
|
166
|
+
logImageStatistics(stats, startTime, endTime);
|
|
167
|
+
|
|
118
168
|
// Save metadata and push cache
|
|
119
169
|
if (githubCache && githubCache.hasCredentials()) {
|
|
120
170
|
githubCache.saveMetadata(metaPath, meta);
|
|
@@ -129,8 +179,23 @@ async function imagemin(complete) {
|
|
|
129
179
|
timestamp: new Date().toISOString(),
|
|
130
180
|
sourceCount: files.length,
|
|
131
181
|
cachedCount: allCacheFiles.length - 1,
|
|
132
|
-
processedNow:
|
|
133
|
-
fromCache:
|
|
182
|
+
processedNow: stats.optimized,
|
|
183
|
+
fromCache: stats.fromCache,
|
|
184
|
+
newlyProcessed: stats.optimized,
|
|
185
|
+
timing: {
|
|
186
|
+
startTime,
|
|
187
|
+
endTime,
|
|
188
|
+
elapsedMs
|
|
189
|
+
},
|
|
190
|
+
imageStats: {
|
|
191
|
+
totalImages: stats.totalImages,
|
|
192
|
+
optimized: stats.optimized,
|
|
193
|
+
skipped: stats.fromCache,
|
|
194
|
+
totalSizeBefore: stats.sizeBefore,
|
|
195
|
+
totalSizeAfter: stats.sizeAfter,
|
|
196
|
+
totalSaved: stats.savedBytes
|
|
197
|
+
},
|
|
198
|
+
details: `Optimized ${stats.optimized} images, ${stats.fromCache} from cache\n\n### Files from cache:\n${stats.cachedFiles.length > 0 ? stats.cachedFiles.map(f => `- ${f}`).join('\n') : 'None'}\n\n### Newly optimized files:\n${stats.optimizedFiles.length > 0 ? stats.optimizedFiles.map(f => `- ${f}`).join('\n') : 'None'}`
|
|
134
199
|
}
|
|
135
200
|
});
|
|
136
201
|
}
|
|
@@ -221,7 +286,7 @@ async function initializeCache() {
|
|
|
221
286
|
}
|
|
222
287
|
|
|
223
288
|
// Determine which files need processing
|
|
224
|
-
async function determineFilesToProcess(files, meta, githubCache) {
|
|
289
|
+
async function determineFilesToProcess(files, meta, githubCache, stats) {
|
|
225
290
|
const filesToProcess = [];
|
|
226
291
|
const validCachePaths = new Set();
|
|
227
292
|
|
|
@@ -263,6 +328,24 @@ async function determineFilesToProcess(files, meta, githubCache) {
|
|
|
263
328
|
jetpack.copy(src, dst, { overwrite: true });
|
|
264
329
|
});
|
|
265
330
|
logger.log(`📦 Using cache: ${relativePath}`);
|
|
331
|
+
stats.fromCache++;
|
|
332
|
+
stats.cachedFiles.push(relativePath);
|
|
333
|
+
|
|
334
|
+
// Track size of cached files
|
|
335
|
+
outputs.forEach(name => {
|
|
336
|
+
const cachePath = path.join(CACHE_DIR, 'images', dirName, name);
|
|
337
|
+
const fileStats = jetpack.inspect(cachePath);
|
|
338
|
+
if (fileStats) {
|
|
339
|
+
stats.sizeAfter += fileStats.size;
|
|
340
|
+
}
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
// Track original size
|
|
344
|
+
const originalStats = jetpack.inspect(file);
|
|
345
|
+
if (originalStats) {
|
|
346
|
+
stats.sizeBefore += originalStats.size;
|
|
347
|
+
}
|
|
348
|
+
|
|
266
349
|
continue;
|
|
267
350
|
}
|
|
268
351
|
}
|
|
@@ -272,11 +355,11 @@ async function determineFilesToProcess(files, meta, githubCache) {
|
|
|
272
355
|
meta[relativePath] = { hash, timestamp: new Date().toISOString() };
|
|
273
356
|
}
|
|
274
357
|
|
|
275
|
-
return { filesToProcess, validCachePaths };
|
|
358
|
+
return { filesToProcess, validCachePaths, cacheStats: stats };
|
|
276
359
|
}
|
|
277
360
|
|
|
278
361
|
// Handle cache-only update (when no files need processing)
|
|
279
|
-
async function handleCacheOnlyUpdate(githubCache, metaPath, meta, validCachePaths, fileCount) {
|
|
362
|
+
async function handleCacheOnlyUpdate(githubCache, metaPath, meta, validCachePaths, fileCount, stats, timing) {
|
|
280
363
|
if (!githubCache || !githubCache.hasCredentials()) {
|
|
281
364
|
return;
|
|
282
365
|
}
|
|
@@ -299,9 +382,66 @@ async function handleCacheOnlyUpdate(githubCache, metaPath, meta, validCachePath
|
|
|
299
382
|
timestamp: new Date().toISOString(),
|
|
300
383
|
sourceCount: fileCount,
|
|
301
384
|
cachedCount: allCacheFiles.length - 1, // Subtract meta.json
|
|
302
|
-
processedNow:
|
|
303
|
-
fromCache:
|
|
385
|
+
processedNow: stats.totalImages,
|
|
386
|
+
fromCache: stats.fromCache,
|
|
387
|
+
newlyProcessed: stats.optimized,
|
|
388
|
+
timing: timing,
|
|
389
|
+
imageStats: {
|
|
390
|
+
totalImages: stats.totalImages,
|
|
391
|
+
optimized: stats.optimized,
|
|
392
|
+
skipped: stats.fromCache,
|
|
393
|
+
totalSizeBefore: stats.sizeBefore,
|
|
394
|
+
totalSizeAfter: stats.sizeAfter,
|
|
395
|
+
totalSaved: stats.sizeBefore - stats.sizeAfter
|
|
396
|
+
},
|
|
397
|
+
details: `All ${fileCount} images served from cache`
|
|
304
398
|
}
|
|
305
399
|
});
|
|
306
400
|
}
|
|
307
401
|
}
|
|
402
|
+
|
|
403
|
+
// Log image statistics
|
|
404
|
+
function logImageStatistics(stats, startTime, endTime) {
|
|
405
|
+
const elapsedMs = endTime - startTime;
|
|
406
|
+
const elapsedSeconds = Math.floor(elapsedMs / 1000);
|
|
407
|
+
const elapsedMinutes = Math.floor(elapsedSeconds / 60);
|
|
408
|
+
const elapsedFormatted = elapsedMinutes > 0
|
|
409
|
+
? `${elapsedMinutes}m ${elapsedSeconds % 60}s`
|
|
410
|
+
: `${elapsedSeconds}s`;
|
|
411
|
+
|
|
412
|
+
logger.log('\n📊 Image Optimization Statistics:');
|
|
413
|
+
logger.log('═══════════════════════════════════════');
|
|
414
|
+
|
|
415
|
+
// Timing
|
|
416
|
+
logger.log('⏱️ Timing:');
|
|
417
|
+
logger.log(` Start time: ${new Date(startTime).toLocaleTimeString()}`);
|
|
418
|
+
logger.log(` End time: ${new Date(endTime).toLocaleTimeString()}`);
|
|
419
|
+
logger.log(` Total elapsed: ${elapsedFormatted}`);
|
|
420
|
+
|
|
421
|
+
// File processing stats
|
|
422
|
+
logger.log('\n📁 File Processing:');
|
|
423
|
+
logger.log(` Total images: ${stats.totalImages}`);
|
|
424
|
+
logger.log(` From cache: ${stats.fromCache} (${((stats.fromCache / stats.totalImages) * 100).toFixed(1)}%)`);
|
|
425
|
+
logger.log(` Newly optimized: ${stats.optimized} (${((stats.optimized / stats.totalImages) * 100).toFixed(1)}%)`);
|
|
426
|
+
|
|
427
|
+
// Size reduction stats
|
|
428
|
+
if (stats.sizeBefore > 0 && stats.sizeAfter > 0) {
|
|
429
|
+
const savedPercent = ((stats.savedBytes / stats.sizeBefore) * 100).toFixed(1);
|
|
430
|
+
logger.log('\n💾 Size Reduction:');
|
|
431
|
+
logger.log(` Original size: ${formatBytes(stats.sizeBefore)}`);
|
|
432
|
+
logger.log(` Optimized size: ${formatBytes(stats.sizeAfter)}`);
|
|
433
|
+
logger.log(` Total saved: ${formatBytes(stats.savedBytes)} (${savedPercent}%)`);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
logger.log('═══════════════════════════════════════\n');
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// Helper to format bytes
|
|
440
|
+
function formatBytes(bytes, decimals = 2) {
|
|
441
|
+
if (bytes === 0) return '0 Bytes';
|
|
442
|
+
const k = 1024;
|
|
443
|
+
const dm = decimals < 0 ? 0 : decimals;
|
|
444
|
+
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
|
445
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
446
|
+
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
|
|
447
|
+
}
|
|
@@ -88,6 +88,11 @@ async function jekyll(complete) {
|
|
|
88
88
|
// Build Jekyll
|
|
89
89
|
await execute(command.join(' '), {log: true});
|
|
90
90
|
|
|
91
|
+
// Reposition "/blog/index.html" to "/blog.html" for compatibility
|
|
92
|
+
// This is needed because Jekyll creates a folder for the blog index page
|
|
93
|
+
// but we want it to be at the root level for compatibility with various hosting providers
|
|
94
|
+
await fixBlogIndex();
|
|
95
|
+
|
|
91
96
|
// Run buildpost hook
|
|
92
97
|
await hook('build:post', index);
|
|
93
98
|
|
|
@@ -287,6 +292,30 @@ async function getRuntimeConfig() {
|
|
|
287
292
|
}
|
|
288
293
|
}
|
|
289
294
|
|
|
295
|
+
// Fix blog index positioning
|
|
296
|
+
async function fixBlogIndex() {
|
|
297
|
+
try {
|
|
298
|
+
const blogIndexPath = '_site/blog/index.html';
|
|
299
|
+
const blogTargetPath = '_site/blog.html';
|
|
300
|
+
|
|
301
|
+
// Check if blog/index.html exists
|
|
302
|
+
if (jetpack.exists(blogIndexPath)) {
|
|
303
|
+
// Move blog/index.html to blog.html
|
|
304
|
+
jetpack.move(blogIndexPath, blogTargetPath, { overwrite: true });
|
|
305
|
+
|
|
306
|
+
// Remove empty blog directory if it exists and is empty
|
|
307
|
+
const blogDir = '_site/blog';
|
|
308
|
+
if (jetpack.exists(blogDir) && jetpack.list(blogDir).length === 0) {
|
|
309
|
+
jetpack.remove(blogDir);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
logger.log('Moved blog/index.html to blog.html');
|
|
313
|
+
}
|
|
314
|
+
} catch (e) {
|
|
315
|
+
logger.error('Error fixing blog index:', e);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
290
319
|
// Create build.json
|
|
291
320
|
async function createBuildJSON() {
|
|
292
321
|
// Create build log JSON
|
package/dist/gulp/tasks/serve.js
CHANGED
|
@@ -275,16 +275,17 @@ async function processRequestMiddleware(req, res, next) {
|
|
|
275
275
|
req.url = newURL;
|
|
276
276
|
}
|
|
277
277
|
|
|
278
|
-
// Special case: Rewrite /blog to blog.html since Jekyll fucks it up locally (probably due to pagination)
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
278
|
+
// Special LOCAL case: Rewrite /blog to blog.html since Jekyll fucks it up locally (probably due to pagination)
|
|
279
|
+
// Disaboed because we moved it to the jekyll task
|
|
280
|
+
// if (pathname === '/blog') {
|
|
281
|
+
// req.url = '/blog/index.html';
|
|
282
|
+
// }
|
|
282
283
|
|
|
283
284
|
// Strip query parameters and hash fragments from the URL for file path lookup
|
|
284
285
|
const cleanUrl = req.url.split('?')[0].split('#')[0];
|
|
285
286
|
const rawFilePath = path.join(rootPathProject, '_site', cleanUrl);
|
|
286
287
|
|
|
287
|
-
// Serve 404.html if the file does not exist
|
|
288
|
+
// Special LOCAL case: Serve 404.html if the file does not exist
|
|
288
289
|
if (!jetpack.exists(rawFilePath) && rawFilePath.endsWith('.html')) {
|
|
289
290
|
// Log
|
|
290
291
|
logger.log(`File not found: ${req.url}. Serving 404.html instead.`);
|
|
@@ -161,6 +161,9 @@ async function processTranslation() {
|
|
|
161
161
|
const enabled = config?.translation?.enabled !== false;
|
|
162
162
|
const languages = config?.translation?.languages || [];
|
|
163
163
|
const updatedFiles = new Set();
|
|
164
|
+
|
|
165
|
+
// Track timing
|
|
166
|
+
const startTime = Date.now();
|
|
164
167
|
|
|
165
168
|
// Quit if translation is disabled or no languages are configured
|
|
166
169
|
if (!enabled) {
|
|
@@ -216,9 +219,17 @@ async function processTranslation() {
|
|
|
216
219
|
metas[lang] = { meta, path: metaPath, skipped: new Set() };
|
|
217
220
|
}
|
|
218
221
|
|
|
219
|
-
// Track token usage
|
|
222
|
+
// Track token usage and statistics
|
|
220
223
|
const tokens = { input: 0, output: 0 };
|
|
221
224
|
const queue = [];
|
|
225
|
+
const stats = {
|
|
226
|
+
fromCache: 0,
|
|
227
|
+
newlyProcessed: 0,
|
|
228
|
+
totalProcessed: 0,
|
|
229
|
+
failedFiles: [],
|
|
230
|
+
cachedFiles: [],
|
|
231
|
+
processedFiles: []
|
|
232
|
+
};
|
|
222
233
|
|
|
223
234
|
for (const filePath of allFiles) {
|
|
224
235
|
// Get relative path and original HTML
|
|
@@ -302,6 +313,8 @@ async function processTranslation() {
|
|
|
302
313
|
) {
|
|
303
314
|
translated = jetpack.read(cachePath);
|
|
304
315
|
logger.log(`📦 Success: ${logTag} - Using cache`);
|
|
316
|
+
stats.fromCache++;
|
|
317
|
+
stats.cachedFiles.push(logTag);
|
|
305
318
|
} else {
|
|
306
319
|
try {
|
|
307
320
|
const { result, usage } = await translateWithAPI(openAIKey, bodyText, lang);
|
|
@@ -322,6 +335,8 @@ async function processTranslation() {
|
|
|
322
335
|
|
|
323
336
|
// Set result
|
|
324
337
|
setResult(true);
|
|
338
|
+
stats.newlyProcessed++;
|
|
339
|
+
stats.processedFiles.push(logTag);
|
|
325
340
|
} catch (e) {
|
|
326
341
|
const elapsedTime = ((Date.now() - startTime) / 1000).toFixed(2);
|
|
327
342
|
logger.error(`❌ Failed: ${logTag} — ${e.message} (Elapsed time: ${elapsedTime}s)`);
|
|
@@ -331,6 +346,7 @@ async function processTranslation() {
|
|
|
331
346
|
|
|
332
347
|
// Save failure to cache
|
|
333
348
|
setResult(false);
|
|
349
|
+
stats.failedFiles.push(logTag);
|
|
334
350
|
}
|
|
335
351
|
}
|
|
336
352
|
|
|
@@ -448,6 +464,7 @@ async function processTranslation() {
|
|
|
448
464
|
// Track updated files
|
|
449
465
|
updatedFiles.add(cachePath);
|
|
450
466
|
updatedFiles.add(metas[lang].path);
|
|
467
|
+
stats.totalProcessed++;
|
|
451
468
|
};
|
|
452
469
|
|
|
453
470
|
// Add to queue
|
|
@@ -487,35 +504,83 @@ async function processTranslation() {
|
|
|
487
504
|
jetpack.write(metas[lang].path, metas[lang].meta);
|
|
488
505
|
}
|
|
489
506
|
|
|
507
|
+
// Calculate timing
|
|
508
|
+
const endTime = Date.now();
|
|
509
|
+
const elapsedMs = endTime - startTime;
|
|
510
|
+
const elapsedSeconds = Math.floor(elapsedMs / 1000);
|
|
511
|
+
const elapsedMinutes = Math.floor(elapsedSeconds / 60);
|
|
512
|
+
const elapsedFormatted = elapsedMinutes > 0
|
|
513
|
+
? `${elapsedMinutes}m ${elapsedSeconds % 60}s`
|
|
514
|
+
: `${elapsedSeconds}s`;
|
|
515
|
+
|
|
490
516
|
// Calculate costs using AI pricing (per 1M tokens)
|
|
491
517
|
const inputCost = (tokens.input / 1000000) * AI.inputCost;
|
|
492
518
|
const outputCost = (tokens.output / 1000000) * AI.outputCost;
|
|
493
519
|
const totalCost = inputCost + outputCost;
|
|
494
520
|
|
|
495
|
-
// Log
|
|
496
|
-
logger.log('
|
|
497
|
-
logger.log(
|
|
498
|
-
|
|
499
|
-
|
|
521
|
+
// Log detailed statistics
|
|
522
|
+
logger.log('\n📊 Translation Statistics:');
|
|
523
|
+
logger.log('═══════════════════════════════════════');
|
|
524
|
+
|
|
525
|
+
// Timing
|
|
526
|
+
logger.log('⏱️ Timing:');
|
|
527
|
+
logger.log(` Start time: ${new Date(startTime).toLocaleTimeString()}`);
|
|
528
|
+
logger.log(` End time: ${new Date(endTime).toLocaleTimeString()}`);
|
|
529
|
+
logger.log(` Total elapsed: ${elapsedFormatted}`);
|
|
530
|
+
|
|
531
|
+
// File processing stats
|
|
532
|
+
logger.log('\n📁 File Processing:');
|
|
533
|
+
logger.log(` Total processed: ${stats.totalProcessed}`);
|
|
534
|
+
logger.log(` From cache: ${stats.fromCache} (${((stats.fromCache / stats.totalProcessed) * 100).toFixed(1)}%)`);
|
|
535
|
+
logger.log(` Newly translated: ${stats.newlyProcessed} (${((stats.newlyProcessed / stats.totalProcessed) * 100).toFixed(1)}%)`);
|
|
536
|
+
if (stats.failedFiles.length > 0) {
|
|
537
|
+
logger.log(` Failed: ${stats.failedFiles.length}`);
|
|
538
|
+
}
|
|
500
539
|
|
|
501
|
-
//
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
540
|
+
// Token usage
|
|
541
|
+
if (tokens.input > 0 || tokens.output > 0) {
|
|
542
|
+
logger.log('\n🧠 OpenAI Token Usage:');
|
|
543
|
+
logger.log(` Input tokens: ${tokens.input.toLocaleString()}`);
|
|
544
|
+
logger.log(` Output tokens: ${tokens.output.toLocaleString()}`);
|
|
545
|
+
logger.log(` Total tokens: ${(tokens.input + tokens.output).toLocaleString()}`);
|
|
546
|
+
|
|
547
|
+
// Cost summary
|
|
548
|
+
logger.log('\n💰 Cost Breakdown:');
|
|
549
|
+
logger.log(` Input cost: $${inputCost.toFixed(4)}`);
|
|
550
|
+
logger.log(` Output cost: $${outputCost.toFixed(4)}`);
|
|
551
|
+
logger.log(` Total cost: $${totalCost.toFixed(4)}`);
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
logger.log('═══════════════════════════════════════\n');
|
|
506
555
|
|
|
507
556
|
// Push updated translation cache back to cache branch
|
|
508
557
|
if (githubCache && githubCache.hasCredentials()) {
|
|
509
558
|
// Collect all cache files to push
|
|
510
559
|
const allCacheFiles = glob(path.join(CACHE_DIR, '**/*'), { nodir: true });
|
|
511
560
|
|
|
512
|
-
// Push to GitHub
|
|
561
|
+
// Push to GitHub with detailed stats
|
|
513
562
|
await githubCache.pushBranch(allCacheFiles, {
|
|
514
563
|
stats: {
|
|
515
564
|
timestamp: new Date().toISOString(),
|
|
516
565
|
sourceCount: allFiles.length,
|
|
517
566
|
cachedCount: allCacheFiles.length,
|
|
518
|
-
|
|
567
|
+
processedNow: stats.totalProcessed,
|
|
568
|
+
fromCache: stats.fromCache,
|
|
569
|
+
newlyProcessed: stats.newlyProcessed,
|
|
570
|
+
timing: {
|
|
571
|
+
startTime,
|
|
572
|
+
endTime,
|
|
573
|
+
elapsedMs
|
|
574
|
+
},
|
|
575
|
+
tokenUsage: tokens.input > 0 || tokens.output > 0 ? {
|
|
576
|
+
inputTokens: tokens.input,
|
|
577
|
+
outputTokens: tokens.output,
|
|
578
|
+
totalTokens: tokens.input + tokens.output,
|
|
579
|
+
inputCost,
|
|
580
|
+
outputCost,
|
|
581
|
+
totalCost
|
|
582
|
+
} : undefined,
|
|
583
|
+
details: `Translated ${allFiles.length} pages to ${languages.length} languages\n\n### Files from cache:\n${stats.cachedFiles.length > 0 ? stats.cachedFiles.map(f => `- ${f}`).join('\n') : 'None'}\n\n### Newly translated files:\n${stats.processedFiles.length > 0 ? stats.processedFiles.map(f => `- ${f}`).join('\n') : 'None'}${stats.failedFiles.length > 0 ? `\n\n### Failed files:\n${stats.failedFiles.map(f => `- ${f}`).join('\n')}` : ''}`
|
|
519
584
|
}
|
|
520
585
|
});
|
|
521
586
|
}
|
|
@@ -557,14 +557,73 @@ This branch stores ${this.description}.
|
|
|
557
557
|
readme += `- **Cached Files:** ${stats.cachedCount}\n`;
|
|
558
558
|
}
|
|
559
559
|
|
|
560
|
+
// Add timing information if provided
|
|
561
|
+
if (stats.timing) {
|
|
562
|
+
const { startTime, endTime, elapsedMs } = stats.timing;
|
|
563
|
+
const elapsedFormatted = this.formatElapsedTime(elapsedMs);
|
|
564
|
+
readme += `
|
|
565
|
+
## Timing Information
|
|
566
|
+
|
|
567
|
+
- **Start Time:** ${new Date(startTime).toLocaleTimeString()}
|
|
568
|
+
- **End Time:** ${new Date(endTime).toLocaleTimeString()}
|
|
569
|
+
- **Total Elapsed:** ${elapsedFormatted}
|
|
570
|
+
`;
|
|
571
|
+
}
|
|
572
|
+
|
|
560
573
|
// Add last run stats if provided
|
|
561
|
-
if (stats.processedNow !== undefined || stats.fromCache !== undefined) {
|
|
574
|
+
if (stats.processedNow !== undefined || stats.fromCache !== undefined || stats.newlyProcessed !== undefined) {
|
|
562
575
|
readme += `
|
|
563
576
|
## Last Run Statistics
|
|
564
577
|
|
|
565
|
-
- **Processed:** ${stats.processedNow || 0}
|
|
566
|
-
- **From Cache:** ${stats.fromCache || 0}
|
|
578
|
+
- **Total Files Processed:** ${stats.processedNow || 0}
|
|
579
|
+
- **Files From Cache:** ${stats.fromCache || 0}
|
|
580
|
+
- **Newly Processed:** ${stats.newlyProcessed || 0}
|
|
581
|
+
`;
|
|
582
|
+
|
|
583
|
+
// Add percentage if both values exist
|
|
584
|
+
if (stats.processedNow && stats.fromCache !== undefined) {
|
|
585
|
+
const cacheRate = ((stats.fromCache / stats.processedNow) * 100).toFixed(1);
|
|
586
|
+
readme += `- **Cache Hit Rate:** ${cacheRate}%\n`;
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
// Add token usage and costs for translation
|
|
591
|
+
if (stats.tokenUsage) {
|
|
592
|
+
const { inputTokens, outputTokens, totalTokens, inputCost, outputCost, totalCost } = stats.tokenUsage;
|
|
593
|
+
readme += `
|
|
594
|
+
## Token Usage & Costs
|
|
595
|
+
|
|
596
|
+
- **Input Tokens:** ${(inputTokens || 0).toLocaleString()}
|
|
597
|
+
- **Output Tokens:** ${(outputTokens || 0).toLocaleString()}
|
|
598
|
+
- **Total Tokens:** ${(totalTokens || 0).toLocaleString()}
|
|
599
|
+
|
|
600
|
+
### Cost Breakdown
|
|
601
|
+
- **Input Cost:** $${(inputCost || 0).toFixed(4)}
|
|
602
|
+
- **Output Cost:** $${(outputCost || 0).toFixed(4)}
|
|
603
|
+
- **Total Cost:** $${(totalCost || 0).toFixed(4)}
|
|
604
|
+
`;
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
// Add image optimization stats if provided
|
|
608
|
+
if (stats.imageStats) {
|
|
609
|
+
const { totalImages, optimized, skipped, totalSizeBefore, totalSizeAfter, totalSaved } = stats.imageStats;
|
|
610
|
+
readme += `
|
|
611
|
+
## Image Optimization Statistics
|
|
612
|
+
|
|
613
|
+
- **Total Images:** ${totalImages || 0}
|
|
614
|
+
- **Optimized:** ${optimized || 0}
|
|
615
|
+
- **Skipped (from cache):** ${skipped || 0}
|
|
567
616
|
`;
|
|
617
|
+
|
|
618
|
+
if (totalSizeBefore && totalSizeAfter) {
|
|
619
|
+
const savedPercent = ((totalSaved / totalSizeBefore) * 100).toFixed(1);
|
|
620
|
+
readme += `
|
|
621
|
+
### Size Reduction
|
|
622
|
+
- **Original Size:** ${this.formatBytes(totalSizeBefore)}
|
|
623
|
+
- **Optimized Size:** ${this.formatBytes(totalSizeAfter)}
|
|
624
|
+
- **Total Saved:** ${this.formatBytes(totalSaved)} (${savedPercent}%)
|
|
625
|
+
`;
|
|
626
|
+
}
|
|
568
627
|
}
|
|
569
628
|
|
|
570
629
|
// Add custom details section if provided
|
|
@@ -584,6 +643,31 @@ ${stats.details}
|
|
|
584
643
|
return readme;
|
|
585
644
|
}
|
|
586
645
|
|
|
646
|
+
// Helper to format elapsed time
|
|
647
|
+
formatElapsedTime(ms) {
|
|
648
|
+
const seconds = Math.floor(ms / 1000);
|
|
649
|
+
const minutes = Math.floor(seconds / 60);
|
|
650
|
+
const hours = Math.floor(minutes / 60);
|
|
651
|
+
|
|
652
|
+
if (hours > 0) {
|
|
653
|
+
return `${hours}h ${minutes % 60}m ${seconds % 60}s`;
|
|
654
|
+
} else if (minutes > 0) {
|
|
655
|
+
return `${minutes}m ${seconds % 60}s`;
|
|
656
|
+
} else {
|
|
657
|
+
return `${seconds}s`;
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
// Helper to format bytes
|
|
662
|
+
formatBytes(bytes, decimals = 2) {
|
|
663
|
+
if (bytes === 0) return '0 Bytes';
|
|
664
|
+
const k = 1024;
|
|
665
|
+
const dm = decimals < 0 ? 0 : decimals;
|
|
666
|
+
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
|
667
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
668
|
+
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
|
|
669
|
+
}
|
|
670
|
+
|
|
587
671
|
// Calculate file hash
|
|
588
672
|
calculateHash(filePath) {
|
|
589
673
|
const content = jetpack.read(filePath, 'buffer');
|