This guide provides a deep dive into each of the six major features added in version 2.0, with technical details, usage examples, and best practices.
The resume feature saves your progress every 10 posts, allowing you to continue exactly where you left off if the script is interrupted for any reason.
Checkpoint System:
markCompleted(filePath) {
this.completed.push(filePath);
this.statistics.generated++;
// Save checkpoint every 10 posts
if (this.completed.length % 10 === 0) {
this.save();
log(`Checkpoint saved: ${this.completed.length} posts completed`);
}
}
Progress File Structure:
{
"startTime": 1732788600000,
"completed": [
"C:\\dev\\itblogpros\\posts\\2025-01-15-wifi-7.md",
"C:\\dev\\itblogpros\\posts\\2025-01-16-smart-home.md",
"..."
],
"failed": [],
"skipped": [],
"totalProcessed": 250,
"lastCheckpoint": "2025-11-28T10:35:00.000Z",
"statistics": {
"generated": 240,
"errors": 0,
"retries": 8,
"apiCalls": 248
}
}
Normal Run (No Resume):
node generate-meta-descriptions-gemini.js --write
Creates .meta-generation-progress.json and saves every 10 posts.
Resume After Interruption:
node generate-meta-descriptions-gemini.js --resume --write
Loads progress file and continues from last checkpoint.
Check Progress Manually:
Get-Content .meta-generation-progress.json | ConvertFrom-Json
Power Outage:
Internet Drop:
Manual Stop (Ctrl+C):
Progress File Location:
C:\dev\itblogpros\.meta-generation-progress.json
Cleanup: The progress file is automatically deleted when:
Manual Cleanup:
# Windows
del .meta-generation-progress.json
# Linux/Mac
rm .meta-generation-progress.json
ā DO:
--resume flag after any interruptionā DON'T:
Intelligent rate limit monitoring that ensures you stay under the 15 RPM free tier limit without wasting time on unnecessary delays.
RateLimiter Class:
class RateLimiter {
constructor(requestsPerMinute) {
this.requestsPerMinute = requestsPerMinute; // 12 (conservative)
this.requests = []; // Timestamps of recent requests
}
async waitIfNeeded() {
const now = Date.now();
const oneMinuteAgo = now - 60000;
// Remove requests older than 60 seconds
this.requests = this.requests.filter(time => time > oneMinuteAgo);
// Check if we're at the limit
if (this.requests.length >= this.requestsPerMinute) {
const oldestRequest = this.requests[0];
const waitTime = 60000 - (now - oldestRequest) + 1000; // +1s buffer
if (waitTime > 0) {
log(`Rate limit approaching, waiting ${Math.round(waitTime / 1000)}s...`);
await sleep(waitTime);
}
}
// Record this request
this.requests.push(now);
}
}
Default Settings:
const CONFIG = {
requestsPerMinute: 12, // Conservative (limit is 15)
delayBetweenRequests: 5500, // 5.5 seconds
// ...
};
Why 12 RPM instead of 15?
Request Pattern:
Second 0: Request 1 (0s)
Second 5: Request 2 (5.5s delay)
Second 10: Request 3 (5.5s delay)
Second 15: Request 4 (5.5s delay)
...
Second 55: Request 12 (5.5s delay)
Second 60: Window resets, Request 13 starts new minute
Actual Rate:
Console Output:
[2025-11-28T10:30:05.200Z] API call 1: Generating...
[2025-11-28T10:30:10.700Z] API call 2: Generating...
[2025-11-28T10:30:16.200Z] API call 3: Generating...
...
[2025-11-28T10:31:05.200Z] Rate limit approaching, waiting 3s...
[2025-11-28T10:31:08.200Z] API call 13: Generating...
More Conservative (10 RPM):
const CONFIG = {
requestsPerMinute: 10,
delayBetweenRequests: 6000, // 6 seconds
};
Closer to Limit (14 RPM):
const CONFIG = {
requestsPerMinute: 14,
delayBetweenRequests: 4300, // 4.3 seconds
};
Not Recommended: Setting to 15 RPM exactly. Always leave buffer.
ā DO:
ā DON'T:
Intelligent retry logic that handles temporary failures with exponential backoff, dramatically improving success rates.
Retry Function:
async function generateMetaDescription(title, content, retryCount = 0) {
try {
await rateLimiter.waitIfNeeded();
progress.statistics.apiCalls++;
const result = await model.generateContent(prompt);
let description = response.text().trim();
// Validate length
if (description.length < CONFIG.minLength) {
if (retryCount < CONFIG.maxRetries) {
log(`Description too short (${description.length} chars), retrying...`);
progress.statistics.retries++;
// Exponential backoff
const delay = CONFIG.retryDelay * Math.pow(CONFIG.retryBackoffMultiplier, retryCount);
await sleep(delay);
return generateMetaDescription(title, content, retryCount + 1);
} else {
throw new Error(`Too short after ${CONFIG.maxRetries} attempts`);
}
}
return description;
} catch (error) {
// Retry on specific errors
if (retryCount < CONFIG.maxRetries && isRetryableError(error)) {
const delay = CONFIG.retryDelay * Math.pow(CONFIG.retryBackoffMultiplier, retryCount);
log(`Error: ${error.message}, waiting ${delay/1000}s before retry ${retryCount + 1}...`);
progress.statistics.retries++;
await sleep(delay);
return generateMetaDescription(title, content, retryCount + 1);
}
throw error;
}
}
Retryable Errors:
Non-Retryable Errors:
Exponential Delays:
Attempt 1: Generate ā Fail
Wait: 10 seconds
Attempt 2: Generate ā Fail
Wait: 20 seconds
Attempt 3: Generate ā Fail
Wait: 40 seconds
Attempt 4: Give up, mark as failed
Why Exponential?
const CONFIG = {
maxRetries: 3, // Up to 3 retry attempts
retryDelay: 10000, // 10 seconds initial
retryBackoffMultiplier: 2, // Double each time
// ...
};
Total Wait Time:
[2025-11-28T10:30:05.200Z] API call 1: Generating for "WiFi 7..."
[2025-11-28T10:30:07.200Z] ā Description too short (95 chars), retrying...
[2025-11-28T10:30:17.200Z] API call 2: Retry 1/3 for "WiFi 7..."
[2025-11-28T10:30:19.200Z] ā Generated (145 chars): Discover if WiFi 7...
Without Retries (v1.0):
With Retries (v2.0):
Improvement: 87.5% reduction in failures
ā DO:
ā DON'T:
Comprehensive tracking of all processing activity, enabling resume capability and detailed reporting.
ProgressTracker Class:
class ProgressTracker {
constructor() {
this.startTime = Date.now();
this.completed = []; // Successfully processed files
this.failed = []; // Failed files with reasons
this.skipped = []; // Skipped files with reasons
this.statistics = {
generated: 0, // New descriptions created
errors: 0, // Total errors
retries: 0, // Total retry attempts
apiCalls: 0 // Total API calls made
};
}
}
Location: .meta-generation-progress.json
Full Structure:
{
"startTime": 1732788600000,
"completed": [
"C:\\dev\\itblogpros\\posts\\2025-01-15-wifi-7.md",
"C:\\dev\\itblogpros\\posts\\2025-01-16-smart-home.md",
"C:\\dev\\itblogpros\\posts\\2025-01-17-router-guide.md"
],
"failed": [
{
"filePath": "C:\\dev\\itblogpros\\posts\\2023-05-10-old-post.md",
"reason": "API timeout after 3 retries",
"timestamp": "2025-11-28T10:25:00.000Z"
}
],
"skipped": [
{
"filePath": "C:\\dev\\itblogpros\\posts\\2025-01-20-already-good.md",
"reason": "has good description"
}
],
"totalProcessed": 558,
"lastCheckpoint": "2025-11-28T10:42:00.000Z",
"statistics": {
"generated": 408,
"errors": 1,
"retries": 12,
"apiCalls": 421
}
}
Mark Completed:
progress.markCompleted(filePath);
// Adds to completed array
// Increments generated count
// Saves checkpoint every 10
Mark Failed:
progress.markFailed(filePath, reason);
// Adds to failed array with timestamp
// Increments error count
Mark Skipped:
progress.markSkipped(filePath, reason);
// Adds to skipped array with reason
// Doesn't count as error
Check if Processed:
if (progress.isProcessed(filePath)) {
// Skip this file when resuming
}
Incremented Automatically:
progress.statistics.apiCalls++; // Every API call
progress.statistics.retries++; // Every retry
progress.statistics.generated++; // Every success
progress.statistics.errors++; // Every failure
const summary = progress.getSummary();
// Returns:
{
completed: 408,
failed: 1,
skipped: 150,
total: 559,
statistics: { ... },
timeSeconds: 720,
timeMinutes: 12
}
Every 10 Posts:
Post 10: ā Checkpoint saved
Post 20: ā Checkpoint saved
Post 30: ā Checkpoint saved
...
Post 550: ā Checkpoint saved
Post 558: ā Final save & cleanup
Why Every 10?
ā DO:
ā DON'T:
Comprehensive logging system that creates a permanent audit trail of all processing activity.
Location: meta-generation.log
Format:
[ISO 8601 Timestamp] Message
Startup:
[2025-11-28T10:30:00.000Z] ================================================================
[2025-11-28T10:30:00.000Z] Meta Description Generator v2.0 - Started
[2025-11-28T10:30:00.100Z] Mode: WRITE, Resume: false, Limit: none
[2025-11-28T10:30:00.100Z] ================================================================
[2025-11-28T10:30:00.200Z] Processing 558 posts...
API Calls:
[2025-11-28T10:30:05.800Z] API call 1: Generating for "WiFi 7 vs WiFi 6: Should You..."
[2025-11-28T10:30:07.200Z] ā Generated (145 chars): Discover if WiFi 7 is worth upgrading...
[2025-11-28T10:30:07.300Z] ā Updated: 2025-01-15-wifi-7-vs-wifi-6.md
Skipped Posts:
[2025-11-28T10:30:13.400Z] [2/558] Processing: 2025-01-16-smart-home.md
[2025-11-28T10:30:13.500Z] ā Skipped "AI Home Assistants..." - already has good description
Retries:
[2025-11-28T10:30:20.100Z] API call 3: Generating for "Old Post Title..."
[2025-11-28T10:30:21.500Z] Description too short (95 chars), retrying...
[2025-11-28T10:30:31.500Z] API call 4: Retry 1/3 for "Old Post Title..."
[2025-11-28T10:30:33.100Z] ā Generated (142 chars): Learn how to troubleshoot...
Rate Limiting:
[2025-11-28T10:31:05.200Z] Rate limit approaching, waiting 3s...
[2025-11-28T10:31:08.200Z] API call 13: Generating for "Next Post..."
Checkpoints:
[2025-11-28T10:35:00.000Z] Checkpoint saved: 50 posts completed
[2025-11-28T10:40:00.000Z] Checkpoint saved: 100 posts completed
Errors:
[2025-11-28T10:37:15.300Z] ā Error processing 2023-03-15-broken-post.md: Invalid front matter
[2025-11-28T10:37:15.400Z] ā ļø Consecutive errors: 1/5
Summary:
[2025-11-28T10:42:00.000Z] =====================================
[2025-11-28T10:42:00.000Z] SUMMARY
[2025-11-28T10:42:00.000Z] =====================================
[2025-11-28T10:42:00.000Z] ā Successfully generated: 408
[2025-11-28T10:42:00.000Z] ā Skipped (already good): 150
[2025-11-28T10:42:00.000Z] ā Errors: 0
[2025-11-28T10:42:00.000Z] ā» Total retries: 12
[2025-11-28T10:42:00.000Z] ā API calls made: 420
[2025-11-28T10:42:00.000Z] ā Total processed: 558
[2025-11-28T10:42:00.000Z] ā± Time taken: 720s (~12 minutes)
[2025-11-28T10:42:00.000Z] š° Cost: $0.00 (FREE Gemini Flash API)
View Recent Errors:
Select-String "ā Error" meta-generation.log
Count Retries:
(Select-String "Retry" meta-generation.log).Count
Check Rate Limiting:
Select-String "Rate limit" meta-generation.log
View Summary:
Select-String "SUMMARY" -Context 0,10 meta-generation.log
Not Automatic: Log file appends indefinitely.
Manual Cleanup:
# Archive old log
mv meta-generation.log meta-generation-2025-11-28.log
# Or delete
rm meta-generation.log
When to Clean:
ā DO:
ā DON'T:
Graceful error handling that prevents API quota waste and provides clear recovery instructions.
let consecutiveErrors = 0;
const maxConsecutiveErrors = 5;
for (const file of files) {
const result = await processPost(file);
if (result.error) {
consecutiveErrors++;
if (consecutiveErrors >= maxConsecutiveErrors) {
log('\nā ļø Stopping after 5 consecutive errors');
log('Progress has been saved. Fix the issue and run with --resume flag.');
progress.save();
break;
}
} else {
consecutiveErrors = 0; // Reset on success
}
}
Prevents:
Allows:
Network Outage:
Post 100: ā Network timeout
Post 101: ā Network timeout
Post 102: ā Network timeout
Post 103: ā Network timeout
Post 104: ā Network timeout
ā ļø Stopping after 5 consecutive errors
Progress saved to .meta-generation-progress.json
Fix network and run: --resume --write
Invalid API Key:
Post 1: ā Authentication failed
Post 2: ā Authentication failed
Post 3: ā Authentication failed
Post 4: ā Authentication failed
Post 5: ā Authentication failed
ā ļø Stopping after 5 consecutive errors
Fix API key and run: --resume --write
Mixed Errors (Continues):
Post 50: ā Success (resets counter)
Post 51: ā Network timeout (count: 1)
Post 52: ā Success (resets counter)
Post 53: ā Short description (count: 1)
Post 54: ā Success (resets counter)
... processing continues ...
Displayed on Error Stop:
ā ļø Stopping after 5 consecutive errors
Common Issues:
1. Network Down
ā Check internet connection
ā Resume with: --resume --write
2. Invalid API Key
ā Verify key at: https://makersuite.google.com
ā Set: $env:GEMINI_API_KEY="key"
ā Resume with: --resume --write
3. Rate Limit Exhausted
ā Wait 1 hour
ā Resume with: --resume --write
Progress saved. No work lost.
Last successful: Post 99
Will resume at: Post 100
Check Progress:
Get-Content .meta-generation-progress.json | ConvertFrom-Json
Review Logs:
type meta-generation.log | Select-String "Error"
Resume After Fix:
node generate-meta-descriptions-gemini.js --resume --write
ā DO:
--test after fixingā DON'T:
All six enhanced features work together to provide:
Result: Production-ready, reliable, professional-quality tool. š
For implementation details, see IMPLEMENTATION-GUIDE-GEMINI.md