ultimate-jekyll-manager 0.0.52 → 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.
@@ -140,7 +140,7 @@ export default function (Manager) {
140
140
  async function handleRedirectResult() {
141
141
  try {
142
142
  // Import Firebase auth functions
143
- const { getAuth, getRedirectResult } = await import('web-manager/node_modules/firebase/auth');
143
+ const { getAuth, getRedirectResult } = await import('@firebase/auth');
144
144
  const auth = getAuth();
145
145
 
146
146
  // Check for redirect result
@@ -233,7 +233,7 @@ export default function (Manager) {
233
233
  }
234
234
 
235
235
  async function attemptEmailSignIn(email, password) {
236
- const { getAuth, signInWithEmailAndPassword } = await import('web-manager/node_modules/firebase/auth');
236
+ const { getAuth, signInWithEmailAndPassword } = await import('@firebase/auth');
237
237
  const auth = getAuth();
238
238
  const userCredential = await signInWithEmailAndPassword(auth, email, password);
239
239
  return userCredential;
@@ -278,7 +278,7 @@ export default function (Manager) {
278
278
 
279
279
  try {
280
280
  // Import Firebase auth functions
281
- const { getAuth, createUserWithEmailAndPassword } = await import('web-manager/node_modules/firebase/auth');
281
+ const { getAuth, createUserWithEmailAndPassword } = await import('@firebase/auth');
282
282
 
283
283
  // Get auth instance and create user
284
284
  const auth = getAuth();
@@ -441,7 +441,7 @@ export default function (Manager) {
441
441
 
442
442
  try {
443
443
  // Import Firebase auth functions
444
- const { getAuth, sendPasswordResetEmail } = await import('web-manager/node_modules/firebase/auth');
444
+ const { getAuth, sendPasswordResetEmail } = await import('@firebase/auth');
445
445
 
446
446
  // Get auth instance
447
447
  const auth = getAuth();
@@ -505,7 +505,7 @@ export default function (Manager) {
505
505
  FacebookAuthProvider,
506
506
  TwitterAuthProvider,
507
507
  GithubAuthProvider
508
- } = await import('web-manager/node_modules/firebase/auth');
508
+ } = await import('@firebase/auth');
509
509
 
510
510
  const auth = getAuth();
511
511
  let provider;
@@ -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] Configuration');
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
 
@@ -44,7 +44,7 @@ async function initializeSigninMethods() {
44
44
  // Check for redirect result from Google auth
45
45
  async function checkRedirectResult() {
46
46
  try {
47
- const { getRedirectResult } = await import('web-manager/node_modules/firebase/auth');
47
+ const { getRedirectResult } = await import('@firebase/auth');
48
48
  const result = await getRedirectResult(firebaseAuth);
49
49
 
50
50
  if (result && result.user) {
@@ -414,7 +414,7 @@ function initializeSignoutAllForm() {
414
414
  // Connect Google provider
415
415
  async function connectGoogleProvider() {
416
416
  // Dynamic import of Firebase auth methods
417
- const { GoogleAuthProvider, linkWithPopup, linkWithRedirect } = await import('web-manager/node_modules/firebase/auth');
417
+ const { GoogleAuthProvider, linkWithPopup, linkWithRedirect } = await import('@firebase/auth');
418
418
 
419
419
  const provider = new GoogleAuthProvider();
420
420
 
@@ -461,7 +461,7 @@ async function disconnectGoogleProvider() {
461
461
  }
462
462
 
463
463
  // Dynamic import of Firebase auth methods
464
- const { unlink } = await import('web-manager/node_modules/firebase/auth');
464
+ const { unlink } = await import('@firebase/auth');
465
465
 
466
466
  const user = firebaseAuth.currentUser;
467
467
 
@@ -491,7 +491,7 @@ async function handleChangePassword() {
491
491
  }
492
492
 
493
493
  // Import Firebase auth method
494
- const { sendPasswordResetEmail } = await import('web-manager/node_modules/firebase/auth');
494
+ const { sendPasswordResetEmail } = await import('@firebase/auth');
495
495
 
496
496
  // Send password reset email
497
497
  await sendPasswordResetEmail(firebaseAuth, user.email);
@@ -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
- await handleCacheOnlyUpdate(githubCache, metaPath, meta, validCachePaths, files.length);
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: filesToProcess.length,
133
- fromCache: files.length - filesToProcess.length
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: 0,
303
- fromCache: fileCount
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
@@ -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
- if (pathname === '/blog') {
280
- req.url = '/blog/index.html';
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 total token usage
496
- logger.log('🧠 OpenAI Token Usage Summary:');
497
- logger.log(` 🟣 Input tokens: ${tokens.input.toLocaleString()}`);
498
- logger.log(` 🟢 Output tokens: ${tokens.output.toLocaleString()}`);
499
- logger.log(` 🔵 Total tokens: ${(tokens.input + tokens.output).toLocaleString()}`);
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
- // Cost summary
502
- logger.log('💰 Cost Breakdown:');
503
- logger.log(` 📥 Input cost: $${inputCost.toFixed(4)}`);
504
- logger.log(` 📤 Output cost: $${outputCost.toFixed(4)}`);
505
- logger.log(` 💵 Total cost: $${totalCost.toFixed(4)}`);
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
- details: `Translated ${allFiles.length} pages to ${languages.length} languages`
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} files
566
- - **From Cache:** ${stats.fromCache || 0} files
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');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ultimate-jekyll-manager",
3
- "version": "0.0.52",
3
+ "version": "0.0.54",
4
4
  "description": "Ultimate Jekyll dependency manager",
5
5
  "main": "dist/index.js",
6
6
  "exports": {