jekyll-theme-zer0 0.10.6 → 0.15.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.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +400 -0
  3. data/README.md +24 -8
  4. data/_data/navigation/about.yml +39 -11
  5. data/_data/navigation/docs.yml +53 -23
  6. data/_data/navigation/home.yml +27 -9
  7. data/_data/navigation/main.yml +27 -8
  8. data/_data/navigation/posts.yml +22 -6
  9. data/_data/navigation/quickstart.yml +8 -3
  10. data/_includes/README.md +2 -0
  11. data/_includes/components/js-cdn.html +4 -1
  12. data/_includes/components/post-card.html +2 -11
  13. data/_includes/components/preview-image.html +32 -0
  14. data/_includes/content/intro.html +5 -6
  15. data/_includes/core/header.html +14 -0
  16. data/_includes/navigation/sidebar-categories.html +20 -9
  17. data/_includes/navigation/sidebar-folders.html +8 -7
  18. data/_includes/navigation/sidebar-right.html +16 -10
  19. data/_layouts/blog.html +15 -45
  20. data/_layouts/category.html +4 -24
  21. data/_layouts/collection.html +2 -12
  22. data/_layouts/default.html +1 -1
  23. data/_layouts/journals.html +2 -12
  24. data/_layouts/notebook.html +296 -0
  25. data/_sass/core/_docs.scss +1 -1
  26. data/_sass/custom.scss +54 -17
  27. data/_sass/notebooks.scss +458 -0
  28. data/assets/images/notebooks/test-notebook_files/test-notebook_4_0.png +0 -0
  29. data/assets/js/sidebar.js +511 -0
  30. data/scripts/README.md +128 -105
  31. data/scripts/analyze-commits.sh +9 -311
  32. data/scripts/bin/build +22 -22
  33. data/scripts/build +7 -111
  34. data/scripts/convert-notebooks.sh +415 -0
  35. data/scripts/features/validate_preview_urls.py +500 -0
  36. data/scripts/fix-markdown-format.sh +8 -262
  37. data/scripts/generate-preview-images.sh +7 -787
  38. data/scripts/install-preview-generator.sh +8 -528
  39. data/scripts/lib/README.md +5 -5
  40. data/scripts/lib/gem.sh +19 -7
  41. data/scripts/release +7 -236
  42. data/scripts/setup.sh +9 -153
  43. data/scripts/test-auto-version.sh +7 -256
  44. data/scripts/test-mermaid.sh +7 -287
  45. data/scripts/test.sh +9 -154
  46. metadata +9 -10
  47. data/scripts/features/preview_generator.py +0 -646
  48. data/scripts/lib/test/run_tests.sh +0 -140
  49. data/scripts/lib/test/test_changelog.sh +0 -87
  50. data/scripts/lib/test/test_gem.sh +0 -68
  51. data/scripts/lib/test/test_git.sh +0 -82
  52. data/scripts/lib/test/test_validation.sh +0 -72
  53. data/scripts/lib/test/test_version.sh +0 -96
  54. data/scripts/version.sh +0 -178
@@ -1,791 +1,11 @@
1
1
  #!/bin/bash
2
- #
3
- # Script Name: generate-preview-images.sh
4
- # Description: AI-powered preview image generator for Jekyll posts/articles/quests
5
- # Scans content files, detects missing preview images, and generates
6
- # images using AI (OpenAI DALL-E, Stable Diffusion, or other providers)
7
- #
8
- # Usage: ./scripts/generate-preview-images.sh [options]
9
- #
10
- # Options:
11
- # -h, --help Show this help message
12
- # -d, --dry-run Preview what would be generated (no actual changes)
13
- # -v, --verbose Enable verbose output
14
- # -f, --file FILE Process a specific file only
15
- # -c, --collection NAME Process specific collection (posts, quickstart, docs)
16
- # -p, --provider PROVIDER AI provider (openai, stability, local)
17
- # --output-dir DIR Output directory for images (default: assets/images/previews)
18
- # --force Regenerate images even if preview exists
19
- # --list-missing Only list files with missing previews
20
- #
21
- # Dependencies:
22
- # - bash 4.0+
23
- # - curl (for API calls)
24
- # - jq (for JSON processing)
25
- # - yq or python (for YAML parsing)
26
- #
27
- # Environment Variables:
28
- # OPENAI_API_KEY OpenAI API key for DALL-E image generation
29
- # STABILITY_API_KEY Stability AI API key for Stable Diffusion
30
- # IMAGE_STYLE Default image style (default: "digital art, professional")
31
- # IMAGE_SIZE Image dimensions (default: "1024x1024")
32
- #
33
- # Examples:
34
- # ./scripts/generate-preview-images.sh --dry-run
35
- # ./scripts/generate-preview-images.sh --collection posts
36
- # ./scripts/generate-preview-images.sh --file pages/_posts/my-post.md
37
- # ./scripts/generate-preview-images.sh --provider openai --verbose
38
- #
39
2
 
40
- set -euo pipefail
3
+ # ============================================================================
4
+ # WRAPPER: This script forwards to scripts/features/generate-preview-images
5
+ #
6
+ # The canonical location is scripts/features/generate-preview-images. This
7
+ # wrapper exists for backward compatibility with existing workflows.
8
+ # ============================================================================
41
9
 
42
- # Get script directory and source common utilities
43
10
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
44
- PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
45
-
46
- # Load environment variables from .env file if it exists
47
- if [[ -f "$PROJECT_ROOT/.env" ]]; then
48
- # Export variables from .env file, overriding any existing values
49
- while IFS='=' read -r key value; do
50
- # Skip comments and empty lines
51
- [[ -z "$key" || "$key" =~ ^# ]] && continue
52
- # Remove surrounding quotes from value if present
53
- value="${value%\"}"
54
- value="${value#\"}"
55
- value="${value%\'}"
56
- value="${value#\'}"
57
- # Export the variable
58
- export "$key=$value"
59
- done < "$PROJECT_ROOT/.env"
60
- fi
61
-
62
- # Source common library if available
63
- if [[ -f "$SCRIPT_DIR/lib/common.sh" ]]; then
64
- source "$SCRIPT_DIR/lib/common.sh"
65
- else
66
- # Fallback logging functions
67
- RED='\033[0;31m'
68
- GREEN='\033[0;32m'
69
- YELLOW='\033[1;33m'
70
- BLUE='\033[0;34m'
71
- CYAN='\033[0;36m'
72
- PURPLE='\033[0;35m'
73
- NC='\033[0m'
74
-
75
- log() { echo -e "${GREEN}[LOG]${NC} $1"; }
76
- info() { echo -e "${BLUE}[INFO]${NC} $1"; }
77
- step() { echo -e "${CYAN}[STEP]${NC} $1"; }
78
- success() { echo -e "${GREEN}[SUCCESS]${NC} $1"; }
79
- warn() { echo -e "${YELLOW}[WARNING]${NC} $1"; }
80
- error() { echo -e "${RED}[ERROR]${NC} $1" >&2; exit 1; }
81
- debug() { [[ "${VERBOSE:-false}" == "true" ]] && echo -e "${PURPLE}[DEBUG]${NC} $1" >&2 || true; }
82
- fi
83
-
84
- # =============================================================================
85
- # Configuration Loading
86
- # =============================================================================
87
- # Priority: CLI args > Environment variables > _config.yml > Defaults
88
-
89
- CONFIG_FILE="$PROJECT_ROOT/_config.yml"
90
-
91
- # Function to read config value from _config.yml using grep (handles YAML anchors)
92
- read_config() {
93
- local key="$1"
94
- local default="$2"
95
-
96
- if [[ -f "$CONFIG_FILE" ]]; then
97
- # Use grep to find the key under preview_images section
98
- # This is more robust than yq for files with anchors
99
- local in_section=false
100
- local value=""
101
-
102
- while IFS= read -r line; do
103
- # Check if we're entering the preview_images section
104
- if [[ "$line" =~ ^preview_images: ]]; then
105
- in_section=true
106
- continue
107
- fi
108
-
109
- # Check if we're leaving the section (new top-level key)
110
- if [[ "$in_section" == true && "$line" =~ ^[a-zA-Z_]+: && ! "$line" =~ ^[[:space:]] ]]; then
111
- break
112
- fi
113
-
114
- # Look for the key within the section
115
- if [[ "$in_section" == true && "$line" =~ ^[[:space:]]+${key}[[:space:]]*:[[:space:]]*(.*) ]]; then
116
- value="${BASH_REMATCH[1]}"
117
- # First, remove inline comments (only if not inside quotes)
118
- # Simple approach: if value starts with quote, find closing quote
119
- if [[ "$value" =~ ^\'([^\']*)\' ]]; then
120
- value="${BASH_REMATCH[1]}"
121
- elif [[ "$value" =~ ^\"([^\"]*)\" ]]; then
122
- value="${BASH_REMATCH[1]}"
123
- else
124
- # No quotes, just trim and remove comment
125
- value="${value%%#*}"
126
- # Trim whitespace
127
- value="${value%"${value##*[![:space:]]}"}"
128
- value="${value#"${value%%[![:space:]]*}"}"
129
- fi
130
- if [[ -n "$value" ]]; then
131
- echo "$value"
132
- return
133
- fi
134
- fi
135
- done < "$CONFIG_FILE"
136
- fi
137
- echo "$default"
138
- }
139
-
140
- # Load defaults from _config.yml, with fallbacks
141
- CONFIG_PROVIDER=$(read_config "provider" "openai")
142
- CONFIG_MODEL=$(read_config "model" "dall-e-3")
143
- CONFIG_SIZE=$(read_config "size" "1792x1024")
144
- CONFIG_QUALITY=$(read_config "quality" "standard")
145
- CONFIG_STYLE=$(read_config "style" "retro pixel art, 8-bit video game aesthetic, vibrant colors, nostalgic, clean pixel graphics")
146
- CONFIG_STYLE_MODIFIERS=$(read_config "style_modifiers" "pixelated, retro gaming style, CRT screen glow effect, limited color palette")
147
- CONFIG_OUTPUT_DIR=$(read_config "output_dir" "assets/images/previews")
148
-
149
- # Default configuration (env vars override config file)
150
- DRY_RUN="${DRY_RUN:-false}"
151
- VERBOSE="${VERBOSE:-false}"
152
- FORCE="${FORCE:-false}"
153
- LIST_ONLY="${LIST_ONLY:-false}"
154
- SPECIFIC_FILE=""
155
- COLLECTION=""
156
- AI_PROVIDER="${AI_PROVIDER:-$CONFIG_PROVIDER}"
157
- OUTPUT_DIR="${OUTPUT_DIR:-$CONFIG_OUTPUT_DIR}"
158
- IMAGE_STYLE="${IMAGE_STYLE:-$CONFIG_STYLE}"
159
- IMAGE_STYLE_MODIFIERS="${IMAGE_STYLE_MODIFIERS:-$CONFIG_STYLE_MODIFIERS}"
160
- IMAGE_SIZE="${IMAGE_SIZE:-$CONFIG_SIZE}"
161
- IMAGE_QUALITY="${IMAGE_QUALITY:-$CONFIG_QUALITY}"
162
- IMAGE_MODEL="${IMAGE_MODEL:-$CONFIG_MODEL}"
163
-
164
- # Counters
165
- PROCESSED=0
166
- GENERATED=0
167
- SKIPPED=0
168
- ERRORS=0
169
-
170
- # Print usage
171
- show_help() {
172
- cat << 'EOF'
173
- Usage: generate-preview-images.sh [OPTIONS]
174
-
175
- AI-powered preview image generator for Jekyll posts and content.
176
-
177
- OPTIONS:
178
- -h, --help Show this help message
179
- -d, --dry-run Preview what would be generated (no changes)
180
- -v, --verbose Enable verbose output
181
- -f, --file FILE Process a specific file only
182
- -c, --collection NAME Process specific collection (posts, quickstart, docs)
183
- -p, --provider PROVIDER AI provider: openai, stability, local (default: openai)
184
- --output-dir DIR Output directory for images (default: assets/images/previews)
185
- --force Regenerate images even if preview exists
186
- --list-missing Only list files with missing previews
187
-
188
- ENVIRONMENT VARIABLES:
189
- OPENAI_API_KEY Required for OpenAI DALL-E provider
190
- STABILITY_API_KEY Required for Stability AI provider
191
- IMAGE_STYLE Override style from _config.yml
192
- IMAGE_SIZE Override size (default: 1792x1024 landscape)
193
- IMAGE_MODEL OpenAI model (default: dall-e-3)
194
-
195
- CONFIGURATION:
196
- Default settings are loaded from _config.yml under 'preview_images' section.
197
- Environment variables override config file settings.
198
-
199
- EXAMPLES:
200
- # List all files missing preview images
201
- ./scripts/generate-preview-images.sh --list-missing
202
-
203
- # Dry run to see what would be generated
204
- ./scripts/generate-preview-images.sh --dry-run --verbose
205
-
206
- # Generate images for posts collection
207
- ./scripts/generate-preview-images.sh --collection posts
208
-
209
- # Generate image for a specific file
210
- ./scripts/generate-preview-images.sh -f pages/_posts/my-article.md
211
-
212
- # Force regenerate all images
213
- ./scripts/generate-preview-images.sh --force
214
-
215
- EOF
216
- }
217
-
218
- # Parse command line arguments
219
- parse_args() {
220
- while [[ $# -gt 0 ]]; do
221
- case $1 in
222
- -h|--help)
223
- show_help
224
- exit 0
225
- ;;
226
- -d|--dry-run)
227
- DRY_RUN="true"
228
- ;;
229
- -v|--verbose)
230
- VERBOSE="true"
231
- ;;
232
- -f|--file)
233
- SPECIFIC_FILE="$2"
234
- shift
235
- ;;
236
- -c|--collection)
237
- COLLECTION="$2"
238
- shift
239
- ;;
240
- -p|--provider)
241
- AI_PROVIDER="$2"
242
- shift
243
- ;;
244
- --output-dir)
245
- OUTPUT_DIR="$2"
246
- shift
247
- ;;
248
- --force)
249
- FORCE="true"
250
- ;;
251
- --list-missing)
252
- LIST_ONLY="true"
253
- ;;
254
- *)
255
- error "Unknown option: $1. Use --help for usage."
256
- ;;
257
- esac
258
- shift
259
- done
260
- }
261
-
262
- # Validate environment and dependencies
263
- validate_environment() {
264
- step "Validating environment..."
265
-
266
- # Check for required commands
267
- local required_cmds=("curl" "jq")
268
- for cmd in "${required_cmds[@]}"; do
269
- if ! command -v "$cmd" &> /dev/null; then
270
- error "Required command not found: $cmd"
271
- fi
272
- done
273
-
274
- # Check for YAML parser (prefer yq, fallback to python)
275
- if command -v yq &> /dev/null; then
276
- YAML_PARSER="yq"
277
- debug "Using yq for YAML parsing"
278
- elif command -v python3 &> /dev/null; then
279
- YAML_PARSER="python"
280
- debug "Using python for YAML parsing"
281
- else
282
- error "No YAML parser found. Install yq or python3."
283
- fi
284
-
285
- # Validate AI provider credentials (unless list-only or dry-run)
286
- if [[ "$LIST_ONLY" != "true" && "$DRY_RUN" != "true" ]]; then
287
- case "$AI_PROVIDER" in
288
- openai)
289
- if [[ -z "${OPENAI_API_KEY:-}" ]]; then
290
- error "OPENAI_API_KEY environment variable is required for OpenAI provider"
291
- fi
292
- ;;
293
- stability)
294
- if [[ -z "${STABILITY_API_KEY:-}" ]]; then
295
- error "STABILITY_API_KEY environment variable is required for Stability AI provider"
296
- fi
297
- ;;
298
- local)
299
- info "Using local provider - no API key required"
300
- ;;
301
- *)
302
- error "Unknown AI provider: $AI_PROVIDER"
303
- ;;
304
- esac
305
- fi
306
-
307
- # Ensure output directory exists
308
- local full_output_dir="$PROJECT_ROOT/$OUTPUT_DIR"
309
- if [[ ! -d "$full_output_dir" ]]; then
310
- if [[ "$DRY_RUN" != "true" ]]; then
311
- mkdir -p "$full_output_dir"
312
- debug "Created output directory: $full_output_dir"
313
- else
314
- debug "Would create output directory: $full_output_dir"
315
- fi
316
- fi
317
-
318
- success "Environment validation passed"
319
- }
320
-
321
- # Extract front matter from a markdown file
322
- extract_front_matter() {
323
- local file="$1"
324
-
325
- # Extract content between --- markers
326
- sed -n '/^---$/,/^---$/p' "$file" | sed '1d;$d'
327
- }
328
-
329
- # Get YAML value using available parser
330
- get_yaml_value() {
331
- local yaml="$1"
332
- local key="$2"
333
- local result=""
334
-
335
- if [[ "$YAML_PARSER" == "yq" ]]; then
336
- # yq v4 syntax - read from stdin and get specific key
337
- result=$(echo "$yaml" | yq eval ".$key" - 2>/dev/null | head -1)
338
- # Filter out null values
339
- if [[ "$result" == "null" || -z "$result" ]]; then
340
- result=""
341
- fi
342
- else
343
- # Python fallback
344
- result=$(python3 -c "
345
- import yaml
346
- import sys
347
- try:
348
- data = yaml.safe_load('''$yaml''')
349
- if data and '$key' in data:
350
- val = data['$key']
351
- if val is not None:
352
- print(val)
353
- except:
354
- pass
355
- " 2>/dev/null || echo "")
356
- fi
357
-
358
- echo "$result"
359
- }
360
-
361
- # Extract post content (without front matter)
362
- extract_content() {
363
- local file="$1"
364
-
365
- # Skip front matter and get content
366
- awk '/^---$/ { if (++count == 2) found=1; next } found { print }' "$file"
367
- }
368
-
369
- # Check if preview image exists
370
- check_preview_exists() {
371
- local preview_path="$1"
372
-
373
- if [[ -z "$preview_path" ]]; then
374
- return 1
375
- fi
376
-
377
- # Handle paths starting with /
378
- local clean_path="${preview_path#/}"
379
- local full_path="$PROJECT_ROOT/$clean_path"
380
-
381
- # Also check in assets/images
382
- if [[ ! -f "$full_path" ]]; then
383
- full_path="$PROJECT_ROOT/assets/$clean_path"
384
- fi
385
-
386
- [[ -f "$full_path" ]]
387
- }
388
-
389
- # Generate image prompt from content
390
- generate_prompt() {
391
- local title="$1"
392
- local description="$2"
393
- local categories="$3"
394
- local content="$4"
395
-
396
- # Build a meaningful prompt
397
- local prompt="Create a blog preview banner image for an article titled '$title'."
398
-
399
- if [[ -n "$description" ]]; then
400
- prompt="$prompt The article is about: $description."
401
- fi
402
-
403
- if [[ -n "$categories" ]]; then
404
- prompt="$prompt Categories: $categories."
405
- fi
406
-
407
- # Extract key themes from content (first 500 chars)
408
- local content_excerpt="${content:0:500}"
409
- if [[ -n "$content_excerpt" ]]; then
410
- prompt="$prompt Key themes from content: $content_excerpt"
411
- fi
412
-
413
- # Add style instructions and modifiers
414
- prompt="$prompt Art style: $IMAGE_STYLE."
415
- if [[ -n "$IMAGE_STYLE_MODIFIERS" ]]; then
416
- prompt="$prompt Additional style: $IMAGE_STYLE_MODIFIERS."
417
- fi
418
- prompt="$prompt The image should be suitable as a wide blog header/banner image with clean composition. No text or words in the image."
419
-
420
- echo "$prompt"
421
- }
422
-
423
- # Generate image using OpenAI DALL-E
424
- generate_image_openai() {
425
- local prompt="$1"
426
- local output_file="$2"
427
-
428
- debug "Generating image with OpenAI DALL-E..."
429
- debug "Prompt: ${prompt:0:200}..."
430
-
431
- local response
432
- response=$(curl -s -X POST "https://api.openai.com/v1/images/generations" \
433
- -H "Authorization: Bearer $OPENAI_API_KEY" \
434
- -H "Content-Type: application/json" \
435
- -d "{
436
- \"model\": \"$IMAGE_MODEL\",
437
- \"prompt\": $(echo "$prompt" | jq -Rs .),
438
- \"n\": 1,
439
- \"size\": \"$IMAGE_SIZE\",
440
- \"quality\": \"$IMAGE_QUALITY\"
441
- }")
442
-
443
- # Check for errors
444
- local error_msg
445
- error_msg=$(echo "$response" | jq -r '.error.message // empty')
446
- if [[ -n "$error_msg" ]]; then
447
- warn "OpenAI API error: $error_msg"
448
- return 1
449
- fi
450
-
451
- # Extract image URL
452
- local image_url
453
- image_url=$(echo "$response" | jq -r '.data[0].url // empty')
454
-
455
- if [[ -z "$image_url" ]]; then
456
- warn "No image URL in response"
457
- debug "Response: $response"
458
- return 1
459
- fi
460
-
461
- # Download image
462
- debug "Downloading image from: $image_url"
463
- curl -s -o "$output_file" "$image_url"
464
-
465
- if [[ -f "$output_file" ]]; then
466
- success "Image saved to: $output_file"
467
- return 0
468
- else
469
- warn "Failed to save image"
470
- return 1
471
- fi
472
- }
473
-
474
- # Generate image using Stability AI
475
- generate_image_stability() {
476
- local prompt="$1"
477
- local output_file="$2"
478
-
479
- debug "Generating image with Stability AI..."
480
-
481
- local response
482
- response=$(curl -s -X POST "https://api.stability.ai/v1/generation/stable-diffusion-xl-1024-v1-0/text-to-image" \
483
- -H "Authorization: Bearer $STABILITY_API_KEY" \
484
- -H "Content-Type: application/json" \
485
- -d "{
486
- \"text_prompts\": [{\"text\": $(echo "$prompt" | jq -Rs .)}],
487
- \"cfg_scale\": 7,
488
- \"height\": 1024,
489
- \"width\": 1024,
490
- \"samples\": 1,
491
- \"steps\": 30
492
- }")
493
-
494
- # Check for errors
495
- local error_msg
496
- error_msg=$(echo "$response" | jq -r '.message // empty')
497
- if [[ -n "$error_msg" ]]; then
498
- warn "Stability API error: $error_msg"
499
- return 1
500
- fi
501
-
502
- # Extract and decode base64 image
503
- local base64_image
504
- base64_image=$(echo "$response" | jq -r '.artifacts[0].base64 // empty')
505
-
506
- if [[ -z "$base64_image" ]]; then
507
- warn "No image data in response"
508
- return 1
509
- fi
510
-
511
- echo "$base64_image" | base64 -d > "$output_file"
512
-
513
- if [[ -f "$output_file" ]]; then
514
- success "Image saved to: $output_file"
515
- return 0
516
- else
517
- warn "Failed to save image"
518
- return 1
519
- fi
520
- }
521
-
522
- # Generate placeholder for local provider (for testing)
523
- generate_image_local() {
524
- local prompt="$1"
525
- local output_file="$2"
526
-
527
- warn "Local provider: No actual image generation. Creating placeholder..."
528
- debug "Would generate image with prompt: ${prompt:0:200}..."
529
-
530
- # Create a simple placeholder file
531
- echo "PLACEHOLDER: $prompt" > "$output_file.txt"
532
-
533
- info "Placeholder created: $output_file.txt"
534
- return 0
535
- }
536
-
537
- # Generate image using selected provider
538
- generate_image() {
539
- local prompt="$1"
540
- local output_file="$2"
541
-
542
- case "$AI_PROVIDER" in
543
- openai)
544
- generate_image_openai "$prompt" "$output_file"
545
- ;;
546
- stability)
547
- generate_image_stability "$prompt" "$output_file"
548
- ;;
549
- local)
550
- generate_image_local "$prompt" "$output_file"
551
- ;;
552
- *)
553
- error "Unknown provider: $AI_PROVIDER"
554
- ;;
555
- esac
556
- }
557
-
558
- # Update front matter with new preview path
559
- update_front_matter() {
560
- local file="$1"
561
- local preview_path="$2"
562
-
563
- debug "Updating front matter in: $file"
564
-
565
- if [[ "$DRY_RUN" == "true" ]]; then
566
- info "[DRY RUN] Would update preview in $file to: $preview_path"
567
- return 0
568
- fi
569
-
570
- # Create backup
571
- cp "$file" "$file.bak"
572
-
573
- # Check if preview field exists
574
- if grep -q "^preview:" "$file"; then
575
- # Update existing preview field
576
- if [[ "$YAML_PARSER" == "yq" ]]; then
577
- # Use yq for in-place update
578
- yq -i ".preview = \"$preview_path\"" "$file"
579
- else
580
- # Use sed for simple replacement
581
- sed -i.tmp "s|^preview:.*|preview: $preview_path|" "$file"
582
- rm -f "$file.tmp"
583
- fi
584
- else
585
- # Add preview field after description or title
586
- if grep -q "^description:" "$file"; then
587
- sed -i.tmp "/^description:/a\\
588
- preview: $preview_path" "$file"
589
- else
590
- sed -i.tmp "/^title:/a\\
591
- preview: $preview_path" "$file"
592
- fi
593
- rm -f "$file.tmp"
594
- fi
595
-
596
- # Remove backup on success
597
- rm -f "$file.bak"
598
-
599
- success "Updated front matter with preview: $preview_path"
600
- }
601
-
602
- # Process a single file
603
- process_file() {
604
- local file="$1"
605
-
606
- PROCESSED=$((PROCESSED + 1))
607
-
608
- debug "Processing file: $file"
609
-
610
- # Extract front matter
611
- local front_matter
612
- front_matter=$(extract_front_matter "$file")
613
-
614
- if [[ -z "$front_matter" ]]; then
615
- debug "No front matter found in: $file"
616
- SKIPPED=$((SKIPPED + 1))
617
- return 0
618
- fi
619
-
620
- # Get metadata
621
- local title description categories preview
622
- title=$(get_yaml_value "$front_matter" "title")
623
- description=$(get_yaml_value "$front_matter" "description")
624
- categories=$(get_yaml_value "$front_matter" "categories")
625
- preview=$(get_yaml_value "$front_matter" "preview")
626
-
627
- debug "Title: $title"
628
- debug "Preview: $preview"
629
-
630
- # Check if preview exists and is valid
631
- if [[ -n "$preview" ]] && check_preview_exists "$preview"; then
632
- if [[ "$FORCE" != "true" ]]; then
633
- debug "Preview already exists and is valid: $preview"
634
- SKIPPED=$((SKIPPED + 1))
635
- return 0
636
- else
637
- info "Force mode: regenerating preview for $title"
638
- fi
639
- fi
640
-
641
- # Report missing preview
642
- if [[ "$LIST_ONLY" == "true" ]]; then
643
- echo -e "${YELLOW}Missing preview:${NC} $file"
644
- echo -e " Title: $title"
645
- if [[ -n "$preview" ]]; then
646
- echo -e " Current preview (not found): $preview"
647
- fi
648
- echo ""
649
- return 0
650
- fi
651
-
652
- info "Generating preview for: $title"
653
-
654
- # Generate filename from title
655
- local safe_filename
656
- safe_filename=$(echo "$title" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g' | sed 's/--*/-/g' | sed 's/^-//' | sed 's/-$//')
657
- safe_filename="${safe_filename:0:50}" # Limit length
658
-
659
- local output_file="$PROJECT_ROOT/$OUTPUT_DIR/${safe_filename}.png"
660
- # Preview path should NOT include /assets/ prefix since the template adds it
661
- local preview_path="/images/previews/${safe_filename}.png"
662
-
663
- # Extract content for prompt generation
664
- local content
665
- content=$(extract_content "$file")
666
-
667
- # Generate prompt
668
- local prompt
669
- prompt=$(generate_prompt "$title" "$description" "$categories" "$content")
670
-
671
- debug "Generated prompt: ${prompt:0:500}..."
672
-
673
- if [[ "$DRY_RUN" == "true" ]]; then
674
- info "[DRY RUN] Would generate image:"
675
- echo " Output: $output_file"
676
- echo " Preview path: $preview_path"
677
- echo " Prompt: ${prompt:0:400}..."
678
- echo ""
679
- GENERATED=$((GENERATED + 1))
680
- return 0
681
- fi
682
-
683
- # Generate image
684
- if generate_image "$prompt" "$output_file"; then
685
- # Update front matter with new preview path
686
- update_front_matter "$file" "$preview_path"
687
- GENERATED=$((GENERATED + 1))
688
- else
689
- warn "Failed to generate image for: $title"
690
- ERRORS=$((ERRORS + 1))
691
- fi
692
- }
693
-
694
- # Find and process content files
695
- process_collection() {
696
- local collection_path="$1"
697
- local pattern="${2:-*.md}"
698
-
699
- debug "Processing collection: $collection_path with pattern: $pattern"
700
-
701
- if [[ ! -d "$collection_path" ]]; then
702
- warn "Collection directory not found: $collection_path"
703
- return 1
704
- fi
705
-
706
- while IFS= read -r -d '' file; do
707
- process_file "$file"
708
- done < <(find "$collection_path" -name "$pattern" -type f -print0)
709
- }
710
-
711
- # Main function
712
- main() {
713
- print_header "🎨 Preview Image Generator"
714
-
715
- # Validate environment
716
- validate_environment
717
-
718
- # Display configuration
719
- info "Configuration:"
720
- echo " AI Provider: $AI_PROVIDER"
721
- echo " Output Dir: $OUTPUT_DIR"
722
- echo " Image Size: $IMAGE_SIZE"
723
- echo " Dry Run: $DRY_RUN"
724
- echo " Force: $FORCE"
725
- echo " List Only: $LIST_ONLY"
726
- echo ""
727
-
728
- # Process files
729
- if [[ -n "$SPECIFIC_FILE" ]]; then
730
- # Process single file
731
- if [[ ! -f "$PROJECT_ROOT/$SPECIFIC_FILE" ]]; then
732
- error "File not found: $SPECIFIC_FILE"
733
- fi
734
- process_file "$PROJECT_ROOT/$SPECIFIC_FILE"
735
- elif [[ -n "$COLLECTION" ]]; then
736
- # Process specific collection
737
- case "$COLLECTION" in
738
- posts)
739
- step "Processing posts collection..."
740
- process_collection "$PROJECT_ROOT/pages/_posts"
741
- ;;
742
- quickstart)
743
- step "Processing quickstart collection..."
744
- process_collection "$PROJECT_ROOT/pages/_quickstart"
745
- ;;
746
- docs)
747
- step "Processing docs collection..."
748
- process_collection "$PROJECT_ROOT/pages/_docs"
749
- ;;
750
- all)
751
- step "Processing all collections..."
752
- process_collection "$PROJECT_ROOT/pages/_posts"
753
- process_collection "$PROJECT_ROOT/pages/_quickstart"
754
- process_collection "$PROJECT_ROOT/pages/_docs"
755
- ;;
756
- *)
757
- error "Unknown collection: $COLLECTION. Use: posts, quickstart, docs, or all"
758
- ;;
759
- esac
760
- else
761
- # Process all content by default
762
- step "Processing all content collections..."
763
- process_collection "$PROJECT_ROOT/pages/_posts"
764
- process_collection "$PROJECT_ROOT/pages/_quickstart"
765
- process_collection "$PROJECT_ROOT/pages/_docs"
766
- fi
767
-
768
- # Print summary
769
- echo ""
770
- print_header "📊 Summary"
771
- echo " Files processed: $PROCESSED"
772
- echo " Images generated: $GENERATED"
773
- echo " Files skipped: $SKIPPED"
774
- echo " Errors: $ERRORS"
775
- echo ""
776
-
777
- if [[ "$DRY_RUN" == "true" ]]; then
778
- info "This was a dry run. No actual changes were made."
779
- fi
780
-
781
- if [[ "$ERRORS" -gt 0 ]]; then
782
- warn "Some files had errors. Check the output above."
783
- exit 1
784
- fi
785
-
786
- success "Preview image generation complete!"
787
- }
788
-
789
- # Parse arguments and run
790
- parse_args "$@"
791
- main
11
+ exec "$SCRIPT_DIR/features/generate-preview-images" "$@"