jekyll-theme-zer0 0.15.0 → 0.16.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +84 -0
- data/README.md +56 -24
- data/_data/README.md +419 -17
- data/_data/generate_statistics.rb +216 -9
- data/_data/generate_statistics.sh +106 -0
- data/_data/github-actions-example.yml +210 -0
- data/_data/navigation/quickstart.yml +11 -3
- data/_data/posts_organization.yml +153 -0
- data/_data/prerequisites.yml +112 -0
- data/_data/statistics_config.yml +203 -0
- data/_data/ui-text.yml +321 -0
- data/_data/update_statistics.sh +126 -0
- data/_includes/components/preview-image.html +20 -3
- data/_includes/content/intro.html +15 -6
- data/_includes/content/seo.html +12 -1
- data/_layouts/notebook.html +3 -3
- data/_plugins/preview_image_generator.rb +29 -8
- data/assets/images/previews/site-personalization-configuration.png +0 -0
- data/scripts/README.md +11 -1
- data/scripts/lib/changelog.sh +89 -57
- data/scripts/lib/preview_generator.py +164 -8
- data/scripts/test/lib/run_tests.sh +1 -2
- data/scripts/update-preview-paths.sh +145 -0
- metadata +11 -2
data/scripts/README.md
CHANGED
|
@@ -91,7 +91,14 @@ Options:
|
|
|
91
91
|
--dry-run Preview without changes
|
|
92
92
|
--collection TYPE Generate for specific collection (posts, docs, etc.)
|
|
93
93
|
-f, --file PATH Process specific file
|
|
94
|
-
--provider PROVIDER Use specific AI provider (openai)
|
|
94
|
+
--provider PROVIDER Use specific AI provider (openai, stability, xai)
|
|
95
|
+
--assets-prefix Custom assets path prefix (default: /assets)
|
|
96
|
+
--no-auto-prefix Disable automatic path prefixing
|
|
97
|
+
|
|
98
|
+
AI Providers:
|
|
99
|
+
openai - OpenAI DALL-E (requires OPENAI_API_KEY)
|
|
100
|
+
stability - Stability AI (requires STABILITY_API_KEY)
|
|
101
|
+
xai - xAI Grok image generation (requires XAI_API_KEY)
|
|
95
102
|
```
|
|
96
103
|
|
|
97
104
|
#### `install-preview-generator`
|
|
@@ -209,11 +216,14 @@ The project includes GitHub Actions workflows for automation:
|
|
|
209
216
|
## Requirements
|
|
210
217
|
|
|
211
218
|
### System Dependencies
|
|
219
|
+
- **Bash**: >= 3.2 (macOS default supported - no Homebrew Bash required!)
|
|
212
220
|
- **Ruby**: >= 2.7.0
|
|
213
221
|
- **Bundler**: For dependency management
|
|
214
222
|
- **jq**: For JSON processing
|
|
215
223
|
- **Git**: For version control
|
|
216
224
|
|
|
225
|
+
**Note on Bash Compatibility**: All scripts are compatible with Bash 3.2+ (the default version on macOS). You do NOT need to install Homebrew Bash. The release automation, changelog generation, and all CI/CD workflows work seamlessly with the system-provided Bash on macOS and standard Bash installations on Linux.
|
|
226
|
+
|
|
217
227
|
### RubyGems Publishing Setup
|
|
218
228
|
To publish gems, you need:
|
|
219
229
|
|
data/scripts/lib/changelog.sh
CHANGED
|
@@ -2,14 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
# Changelog generation library for zer0-mistakes release scripts
|
|
4
4
|
# Provides automatic changelog generation from git commit history
|
|
5
|
-
|
|
6
|
-
# Check Bash version (need 4+ for associative arrays)
|
|
7
|
-
if [[ "${BASH_VERSINFO[0]}" -lt 4 ]]; then
|
|
8
|
-
echo "[ERROR] This script requires Bash 4.0 or higher (current: ${BASH_VERSION})" >&2
|
|
9
|
-
echo "[INFO] On macOS, install via: brew install bash" >&2
|
|
10
|
-
echo "[INFO] Then update scripts to use: #!/usr/local/bin/bash" >&2
|
|
11
|
-
exit 1
|
|
12
|
-
fi
|
|
5
|
+
# Compatible with Bash 3.2+ (macOS default) and Bash 4+
|
|
13
6
|
|
|
14
7
|
# Source common utilities
|
|
15
8
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
@@ -119,18 +112,19 @@ generate_changelog() {
|
|
|
119
112
|
return 0
|
|
120
113
|
fi
|
|
121
114
|
|
|
122
|
-
# Parse and categorize commits
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
115
|
+
# Parse and categorize commits using parallel indexed arrays (Bash 3.2 compatible)
|
|
116
|
+
# Category names in order
|
|
117
|
+
local category_names=("breaking" "added" "changed" "deprecated" "removed" "fixed" "security" "other")
|
|
118
|
+
|
|
119
|
+
# Initialize category content arrays
|
|
120
|
+
local cat_breaking=""
|
|
121
|
+
local cat_added=""
|
|
122
|
+
local cat_changed=""
|
|
123
|
+
local cat_deprecated=""
|
|
124
|
+
local cat_removed=""
|
|
125
|
+
local cat_fixed=""
|
|
126
|
+
local cat_security=""
|
|
127
|
+
local cat_other=""
|
|
134
128
|
|
|
135
129
|
local commit_count=0
|
|
136
130
|
while IFS='|' read -r hash subject author date; do
|
|
@@ -176,10 +170,41 @@ generate_changelog() {
|
|
|
176
170
|
local clean_msg
|
|
177
171
|
clean_msg=$(clean_commit_message "$subject")
|
|
178
172
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
173
|
+
# Append to appropriate category variable
|
|
174
|
+
case "$category" in
|
|
175
|
+
"breaking")
|
|
176
|
+
[[ -n "$cat_breaking" ]] && cat_breaking+=$'\n'
|
|
177
|
+
cat_breaking+="- $clean_msg"
|
|
178
|
+
;;
|
|
179
|
+
"added")
|
|
180
|
+
[[ -n "$cat_added" ]] && cat_added+=$'\n'
|
|
181
|
+
cat_added+="- $clean_msg"
|
|
182
|
+
;;
|
|
183
|
+
"changed")
|
|
184
|
+
[[ -n "$cat_changed" ]] && cat_changed+=$'\n'
|
|
185
|
+
cat_changed+="- $clean_msg"
|
|
186
|
+
;;
|
|
187
|
+
"deprecated")
|
|
188
|
+
[[ -n "$cat_deprecated" ]] && cat_deprecated+=$'\n'
|
|
189
|
+
cat_deprecated+="- $clean_msg"
|
|
190
|
+
;;
|
|
191
|
+
"removed")
|
|
192
|
+
[[ -n "$cat_removed" ]] && cat_removed+=$'\n'
|
|
193
|
+
cat_removed+="- $clean_msg"
|
|
194
|
+
;;
|
|
195
|
+
"fixed")
|
|
196
|
+
[[ -n "$cat_fixed" ]] && cat_fixed+=$'\n'
|
|
197
|
+
cat_fixed+="- $clean_msg"
|
|
198
|
+
;;
|
|
199
|
+
"security")
|
|
200
|
+
[[ -n "$cat_security" ]] && cat_security+=$'\n'
|
|
201
|
+
cat_security+="- $clean_msg"
|
|
202
|
+
;;
|
|
203
|
+
"other")
|
|
204
|
+
[[ -n "$cat_other" ]] && cat_other+=$'\n'
|
|
205
|
+
cat_other+="- $clean_msg"
|
|
206
|
+
;;
|
|
207
|
+
esac
|
|
183
208
|
|
|
184
209
|
done <<< "$commits_raw"
|
|
185
210
|
|
|
@@ -192,39 +217,46 @@ generate_changelog() {
|
|
|
192
217
|
|
|
193
218
|
changelog_entry+="## [$new_version] - $date"$'\n\n'
|
|
194
219
|
|
|
195
|
-
# Add sections in order
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
220
|
+
# Add sections in order (using individual variables instead of associative array)
|
|
221
|
+
if [[ -n "$cat_breaking" ]]; then
|
|
222
|
+
changelog_entry+="### ⚠️ BREAKING CHANGES"$'\n'
|
|
223
|
+
changelog_entry+="$cat_breaking"$'\n\n'
|
|
224
|
+
fi
|
|
225
|
+
|
|
226
|
+
if [[ -n "$cat_added" ]]; then
|
|
227
|
+
changelog_entry+="### Added"$'\n'
|
|
228
|
+
changelog_entry+="$cat_added"$'\n\n'
|
|
229
|
+
fi
|
|
230
|
+
|
|
231
|
+
if [[ -n "$cat_changed" ]]; then
|
|
232
|
+
changelog_entry+="### Changed"$'\n'
|
|
233
|
+
changelog_entry+="$cat_changed"$'\n\n'
|
|
234
|
+
fi
|
|
235
|
+
|
|
236
|
+
if [[ -n "$cat_deprecated" ]]; then
|
|
237
|
+
changelog_entry+="### Deprecated"$'\n'
|
|
238
|
+
changelog_entry+="$cat_deprecated"$'\n\n'
|
|
239
|
+
fi
|
|
240
|
+
|
|
241
|
+
if [[ -n "$cat_removed" ]]; then
|
|
242
|
+
changelog_entry+="### Removed"$'\n'
|
|
243
|
+
changelog_entry+="$cat_removed"$'\n\n'
|
|
244
|
+
fi
|
|
245
|
+
|
|
246
|
+
if [[ -n "$cat_fixed" ]]; then
|
|
247
|
+
changelog_entry+="### Fixed"$'\n'
|
|
248
|
+
changelog_entry+="$cat_fixed"$'\n\n'
|
|
249
|
+
fi
|
|
250
|
+
|
|
251
|
+
if [[ -n "$cat_security" ]]; then
|
|
252
|
+
changelog_entry+="### Security"$'\n'
|
|
253
|
+
changelog_entry+="$cat_security"$'\n\n'
|
|
254
|
+
fi
|
|
255
|
+
|
|
256
|
+
if [[ -n "$cat_other" ]]; then
|
|
257
|
+
changelog_entry+="### Other"$'\n'
|
|
258
|
+
changelog_entry+="$cat_other"$'\n\n'
|
|
259
|
+
fi
|
|
228
260
|
|
|
229
261
|
# Preview changelog
|
|
230
262
|
info "Changelog preview:"
|
|
@@ -101,6 +101,8 @@ class PreviewGenerator:
|
|
|
101
101
|
output_dir: str = "assets/images/previews",
|
|
102
102
|
image_style: str = "digital art, professional blog illustration",
|
|
103
103
|
image_size: str = "1024x1024",
|
|
104
|
+
assets_prefix: str = "/assets",
|
|
105
|
+
auto_prefix: bool = True,
|
|
104
106
|
dry_run: bool = False,
|
|
105
107
|
verbose: bool = False,
|
|
106
108
|
force: bool = False,
|
|
@@ -110,6 +112,8 @@ class PreviewGenerator:
|
|
|
110
112
|
self.output_dir = project_root / output_dir
|
|
111
113
|
self.image_style = image_style
|
|
112
114
|
self.image_size = image_size
|
|
115
|
+
self.assets_prefix = assets_prefix
|
|
116
|
+
self.auto_prefix = auto_prefix
|
|
113
117
|
self.dry_run = dry_run
|
|
114
118
|
self.verbose = verbose
|
|
115
119
|
self.force = force
|
|
@@ -129,6 +133,27 @@ class PreviewGenerator:
|
|
|
129
133
|
if self.verbose:
|
|
130
134
|
log(msg, "debug")
|
|
131
135
|
|
|
136
|
+
def normalize_preview_path(self, preview_path: Optional[str]) -> Optional[str]:
|
|
137
|
+
"""Normalize a preview path by adding assets_prefix if needed.
|
|
138
|
+
|
|
139
|
+
This allows users to omit the /assets/ prefix in frontmatter:
|
|
140
|
+
- /images/previews/my-image.png -> /assets/images/previews/my-image.png
|
|
141
|
+
- /assets/images/previews/my-image.png -> unchanged
|
|
142
|
+
- https://example.com/image.png -> unchanged (external URL)
|
|
143
|
+
"""
|
|
144
|
+
if not preview_path:
|
|
145
|
+
return preview_path
|
|
146
|
+
|
|
147
|
+
# External URLs pass through unchanged
|
|
148
|
+
if preview_path.startswith('http://') or preview_path.startswith('https://'):
|
|
149
|
+
return preview_path
|
|
150
|
+
|
|
151
|
+
# If auto_prefix is enabled and path doesn't contain assets_prefix
|
|
152
|
+
if self.auto_prefix and self.assets_prefix not in preview_path:
|
|
153
|
+
return f"{self.assets_prefix}{preview_path}"
|
|
154
|
+
|
|
155
|
+
return preview_path
|
|
156
|
+
|
|
132
157
|
def parse_front_matter(self, file_path: Path) -> Optional[ContentFile]:
|
|
133
158
|
"""Parse front matter and content from a markdown file."""
|
|
134
159
|
try:
|
|
@@ -178,19 +203,19 @@ class PreviewGenerator:
|
|
|
178
203
|
if not preview_path:
|
|
179
204
|
return False
|
|
180
205
|
|
|
206
|
+
# Normalize the path first (adds assets_prefix if needed)
|
|
207
|
+
normalized_path = self.normalize_preview_path(preview_path)
|
|
208
|
+
if not normalized_path:
|
|
209
|
+
return False
|
|
210
|
+
|
|
181
211
|
# Handle absolute and relative paths
|
|
182
|
-
clean_path =
|
|
212
|
+
clean_path = normalized_path.lstrip('/')
|
|
183
213
|
|
|
184
214
|
# Check direct path
|
|
185
215
|
full_path = self.project_root / clean_path
|
|
186
216
|
if full_path.exists():
|
|
187
217
|
return True
|
|
188
218
|
|
|
189
|
-
# Check in assets directory
|
|
190
|
-
assets_path = self.project_root / 'assets' / clean_path
|
|
191
|
-
if assets_path.exists():
|
|
192
|
-
return True
|
|
193
|
-
|
|
194
219
|
return False
|
|
195
220
|
|
|
196
221
|
def generate_prompt(self, content: ContentFile) -> str:
|
|
@@ -395,12 +420,131 @@ class PreviewGenerator:
|
|
|
395
420
|
prompt_used=prompt,
|
|
396
421
|
)
|
|
397
422
|
|
|
423
|
+
def generate_image_xai(self, prompt: str, output_path: Path) -> GenerationResult:
|
|
424
|
+
"""Generate image using xAI Grok API."""
|
|
425
|
+
if not HAS_REQUESTS:
|
|
426
|
+
return GenerationResult(
|
|
427
|
+
success=False,
|
|
428
|
+
image_path=None,
|
|
429
|
+
preview_url=None,
|
|
430
|
+
error="requests package not installed. Run: pip install requests",
|
|
431
|
+
prompt_used=prompt,
|
|
432
|
+
)
|
|
433
|
+
|
|
434
|
+
api_key = os.environ.get('XAI_API_KEY')
|
|
435
|
+
if not api_key:
|
|
436
|
+
return GenerationResult(
|
|
437
|
+
success=False,
|
|
438
|
+
image_path=None,
|
|
439
|
+
preview_url=None,
|
|
440
|
+
error="XAI_API_KEY environment variable not set",
|
|
441
|
+
prompt_used=prompt,
|
|
442
|
+
)
|
|
443
|
+
|
|
444
|
+
try:
|
|
445
|
+
# xAI has a max prompt length of 1024 characters
|
|
446
|
+
truncated_prompt = prompt[:1000] if len(prompt) > 1000 else prompt
|
|
447
|
+
self.debug(f"Generating with xAI Grok, prompt: {truncated_prompt[:200]}...")
|
|
448
|
+
|
|
449
|
+
# xAI uses OpenAI-compatible API format
|
|
450
|
+
response = requests.post(
|
|
451
|
+
"https://api.x.ai/v1/images/generations",
|
|
452
|
+
headers={
|
|
453
|
+
"Authorization": f"Bearer {api_key}",
|
|
454
|
+
"Content-Type": "application/json",
|
|
455
|
+
},
|
|
456
|
+
json={
|
|
457
|
+
"model": "grok-2-image",
|
|
458
|
+
"prompt": truncated_prompt,
|
|
459
|
+
"n": 1,
|
|
460
|
+
},
|
|
461
|
+
timeout=120, # 2 minute timeout for image generation
|
|
462
|
+
)
|
|
463
|
+
response.raise_for_status()
|
|
464
|
+
|
|
465
|
+
data = response.json()
|
|
466
|
+
|
|
467
|
+
# xAI returns base64-encoded images
|
|
468
|
+
if 'data' not in data or not data['data']:
|
|
469
|
+
return GenerationResult(
|
|
470
|
+
success=False,
|
|
471
|
+
image_path=None,
|
|
472
|
+
preview_url=None,
|
|
473
|
+
error="No image data in response",
|
|
474
|
+
prompt_used=prompt,
|
|
475
|
+
)
|
|
476
|
+
|
|
477
|
+
image_data = data['data'][0]
|
|
478
|
+
|
|
479
|
+
# Check if it's a URL or base64
|
|
480
|
+
if 'url' in image_data:
|
|
481
|
+
# Download from URL
|
|
482
|
+
img_response = requests.get(image_data['url'], timeout=60)
|
|
483
|
+
img_response.raise_for_status()
|
|
484
|
+
output_path.write_bytes(img_response.content)
|
|
485
|
+
elif 'b64_json' in image_data:
|
|
486
|
+
# Decode base64
|
|
487
|
+
import base64
|
|
488
|
+
image_bytes = base64.b64decode(image_data['b64_json'])
|
|
489
|
+
output_path.write_bytes(image_bytes)
|
|
490
|
+
else:
|
|
491
|
+
return GenerationResult(
|
|
492
|
+
success=False,
|
|
493
|
+
image_path=None,
|
|
494
|
+
preview_url=None,
|
|
495
|
+
error="Unexpected response format from xAI",
|
|
496
|
+
prompt_used=prompt,
|
|
497
|
+
)
|
|
498
|
+
|
|
499
|
+
return GenerationResult(
|
|
500
|
+
success=True,
|
|
501
|
+
image_path=str(output_path),
|
|
502
|
+
preview_url=str(output_path.relative_to(self.project_root)),
|
|
503
|
+
error=None,
|
|
504
|
+
prompt_used=prompt,
|
|
505
|
+
)
|
|
506
|
+
|
|
507
|
+
except requests.exceptions.HTTPError as e:
|
|
508
|
+
error_msg = str(e)
|
|
509
|
+
try:
|
|
510
|
+
error_data = e.response.json()
|
|
511
|
+
self.debug(f"xAI error response: {error_data}")
|
|
512
|
+
if 'error' in error_data:
|
|
513
|
+
error_msg = error_data['error'].get('message', str(error_data['error']))
|
|
514
|
+
elif 'detail' in error_data:
|
|
515
|
+
error_msg = str(error_data['detail'])
|
|
516
|
+
else:
|
|
517
|
+
error_msg = str(error_data)
|
|
518
|
+
except:
|
|
519
|
+
# Try to get raw text
|
|
520
|
+
try:
|
|
521
|
+
error_msg = e.response.text[:500]
|
|
522
|
+
except:
|
|
523
|
+
pass
|
|
524
|
+
return GenerationResult(
|
|
525
|
+
success=False,
|
|
526
|
+
image_path=None,
|
|
527
|
+
preview_url=None,
|
|
528
|
+
error=f"xAI API error: {error_msg}",
|
|
529
|
+
prompt_used=prompt,
|
|
530
|
+
)
|
|
531
|
+
except Exception as e:
|
|
532
|
+
return GenerationResult(
|
|
533
|
+
success=False,
|
|
534
|
+
image_path=None,
|
|
535
|
+
preview_url=None,
|
|
536
|
+
error=str(e),
|
|
537
|
+
prompt_used=prompt,
|
|
538
|
+
)
|
|
539
|
+
|
|
398
540
|
def generate_image(self, prompt: str, output_path: Path) -> GenerationResult:
|
|
399
541
|
"""Generate image using configured provider."""
|
|
400
542
|
if self.provider == "openai":
|
|
401
543
|
return self.generate_image_openai(prompt, output_path)
|
|
402
544
|
elif self.provider == "stability":
|
|
403
545
|
return self.generate_image_stability(prompt, output_path)
|
|
546
|
+
elif self.provider == "xai":
|
|
547
|
+
return self.generate_image_xai(prompt, output_path)
|
|
404
548
|
else:
|
|
405
549
|
return GenerationResult(
|
|
406
550
|
success=False,
|
|
@@ -559,9 +703,9 @@ def main():
|
|
|
559
703
|
)
|
|
560
704
|
parser.add_argument(
|
|
561
705
|
'-p', '--provider',
|
|
562
|
-
choices=['openai', 'stability'],
|
|
706
|
+
choices=['openai', 'stability', 'xai'],
|
|
563
707
|
default='openai',
|
|
564
|
-
help="AI provider for image generation"
|
|
708
|
+
help="AI provider for image generation (openai, stability, xai)"
|
|
565
709
|
)
|
|
566
710
|
parser.add_argument(
|
|
567
711
|
'-d', '--dry-run',
|
|
@@ -593,6 +737,16 @@ def main():
|
|
|
593
737
|
default='digital art, professional blog illustration, clean design',
|
|
594
738
|
help="Image style prompt"
|
|
595
739
|
)
|
|
740
|
+
parser.add_argument(
|
|
741
|
+
'--assets-prefix',
|
|
742
|
+
default='/assets',
|
|
743
|
+
help="Prefix to prepend to relative preview paths (default: /assets)"
|
|
744
|
+
)
|
|
745
|
+
parser.add_argument(
|
|
746
|
+
'--no-auto-prefix',
|
|
747
|
+
action='store_true',
|
|
748
|
+
help="Disable automatic assets prefix prepending"
|
|
749
|
+
)
|
|
596
750
|
|
|
597
751
|
args = parser.parse_args()
|
|
598
752
|
|
|
@@ -606,6 +760,8 @@ def main():
|
|
|
606
760
|
provider=args.provider,
|
|
607
761
|
output_dir=args.output_dir,
|
|
608
762
|
image_style=args.style,
|
|
763
|
+
assets_prefix=args.assets_prefix,
|
|
764
|
+
auto_prefix=not args.no_auto_prefix,
|
|
609
765
|
dry_run=args.dry_run,
|
|
610
766
|
verbose=args.verbose,
|
|
611
767
|
force=args.force,
|
|
@@ -3,8 +3,7 @@
|
|
|
3
3
|
# Test runner for library unit tests
|
|
4
4
|
# Usage: ./scripts/test/lib/run_tests.sh
|
|
5
5
|
#
|
|
6
|
-
#
|
|
7
|
-
# On macOS: brew install bash
|
|
6
|
+
# Compatible with Bash 3.2+ (macOS default) and Bash 4+
|
|
8
7
|
|
|
9
8
|
# Note: We intentionally don't use set -e here because test assertions
|
|
10
9
|
# may return non-zero and we want to continue running tests
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
#
|
|
3
|
+
# update-preview-paths.sh
|
|
4
|
+
#
|
|
5
|
+
# Updates all preview paths in markdown frontmatter to remove the /assets/ prefix.
|
|
6
|
+
# This aligns with the new auto_prefix feature that automatically prepends /assets/.
|
|
7
|
+
#
|
|
8
|
+
# Usage:
|
|
9
|
+
# ./scripts/update-preview-paths.sh # Dry run (preview changes)
|
|
10
|
+
# ./scripts/update-preview-paths.sh --apply # Apply changes
|
|
11
|
+
#
|
|
12
|
+
# Example transformation:
|
|
13
|
+
# preview: /assets/images/previews/my-image.png
|
|
14
|
+
# becomes:
|
|
15
|
+
# preview: /images/previews/my-image.png
|
|
16
|
+
#
|
|
17
|
+
|
|
18
|
+
set -euo pipefail
|
|
19
|
+
|
|
20
|
+
# Colors for output
|
|
21
|
+
RED='\033[0;31m'
|
|
22
|
+
GREEN='\033[0;32m'
|
|
23
|
+
YELLOW='\033[1;33m'
|
|
24
|
+
BLUE='\033[0;34m'
|
|
25
|
+
CYAN='\033[0;36m'
|
|
26
|
+
NC='\033[0m' # No Color
|
|
27
|
+
|
|
28
|
+
# Script directory
|
|
29
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
30
|
+
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
31
|
+
|
|
32
|
+
# Counters
|
|
33
|
+
TOTAL_FILES=0
|
|
34
|
+
MODIFIED_FILES=0
|
|
35
|
+
SKIPPED_FILES=0
|
|
36
|
+
|
|
37
|
+
# Mode
|
|
38
|
+
DRY_RUN=true
|
|
39
|
+
|
|
40
|
+
# Parse arguments
|
|
41
|
+
for arg in "$@"; do
|
|
42
|
+
case $arg in
|
|
43
|
+
--apply)
|
|
44
|
+
DRY_RUN=false
|
|
45
|
+
shift
|
|
46
|
+
;;
|
|
47
|
+
--help|-h)
|
|
48
|
+
echo "Usage: $0 [--apply]"
|
|
49
|
+
echo ""
|
|
50
|
+
echo "Updates preview paths in markdown frontmatter to remove /assets/ prefix."
|
|
51
|
+
echo ""
|
|
52
|
+
echo "Options:"
|
|
53
|
+
echo " --apply Apply changes (default is dry run)"
|
|
54
|
+
echo " --help Show this help message"
|
|
55
|
+
exit 0
|
|
56
|
+
;;
|
|
57
|
+
esac
|
|
58
|
+
done
|
|
59
|
+
|
|
60
|
+
log_info() {
|
|
61
|
+
echo -e "${BLUE}[INFO]${NC} $1"
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
log_success() {
|
|
65
|
+
echo -e "${GREEN}[SUCCESS]${NC} $1"
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
log_warning() {
|
|
69
|
+
echo -e "${YELLOW}[WARNING]${NC} $1"
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
log_change() {
|
|
73
|
+
echo -e "${CYAN}[CHANGE]${NC} $1"
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
echo ""
|
|
77
|
+
echo -e "${BLUE}========================================${NC}"
|
|
78
|
+
echo -e "${BLUE}🔄 Preview Path Updater${NC}"
|
|
79
|
+
echo -e "${BLUE}========================================${NC}"
|
|
80
|
+
echo ""
|
|
81
|
+
|
|
82
|
+
if [ "$DRY_RUN" = true ]; then
|
|
83
|
+
log_warning "DRY RUN MODE - No files will be modified"
|
|
84
|
+
log_info "Run with --apply to make changes"
|
|
85
|
+
else
|
|
86
|
+
log_warning "APPLY MODE - Files will be modified"
|
|
87
|
+
fi
|
|
88
|
+
echo ""
|
|
89
|
+
|
|
90
|
+
# Find all markdown files in pages directory
|
|
91
|
+
find "$PROJECT_ROOT/pages" -name "*.md" -type f | while read -r file; do
|
|
92
|
+
TOTAL_FILES=$((TOTAL_FILES + 1))
|
|
93
|
+
|
|
94
|
+
# Check if file has a preview field with /assets/ prefix
|
|
95
|
+
if grep -q "^preview: /assets/" "$file" 2>/dev/null; then
|
|
96
|
+
# Extract current preview path
|
|
97
|
+
current_path=$(grep "^preview:" "$file" | head -1 | sed 's/^preview: //')
|
|
98
|
+
|
|
99
|
+
# Remove /assets/ prefix
|
|
100
|
+
new_path=$(echo "$current_path" | sed 's|^/assets/|/|')
|
|
101
|
+
|
|
102
|
+
log_change "$file"
|
|
103
|
+
echo " Old: $current_path"
|
|
104
|
+
echo " New: $new_path"
|
|
105
|
+
|
|
106
|
+
if [ "$DRY_RUN" = false ]; then
|
|
107
|
+
# Use sed to replace the preview line
|
|
108
|
+
if [[ "$OSTYPE" == "darwin"* ]]; then
|
|
109
|
+
# macOS sed requires empty string for -i
|
|
110
|
+
sed -i '' "s|^preview: /assets/|preview: /|" "$file"
|
|
111
|
+
else
|
|
112
|
+
# Linux sed
|
|
113
|
+
sed -i "s|^preview: /assets/|preview: /|" "$file"
|
|
114
|
+
fi
|
|
115
|
+
log_success "Updated: $file"
|
|
116
|
+
fi
|
|
117
|
+
|
|
118
|
+
MODIFIED_FILES=$((MODIFIED_FILES + 1))
|
|
119
|
+
else
|
|
120
|
+
SKIPPED_FILES=$((SKIPPED_FILES + 1))
|
|
121
|
+
fi
|
|
122
|
+
done
|
|
123
|
+
|
|
124
|
+
echo ""
|
|
125
|
+
echo -e "${CYAN}========================================${NC}"
|
|
126
|
+
echo -e "${CYAN}📊 Summary${NC}"
|
|
127
|
+
echo -e "${CYAN}========================================${NC}"
|
|
128
|
+
|
|
129
|
+
# Re-count since subshell doesn't persist variables
|
|
130
|
+
MODIFIED_COUNT=$(find "$PROJECT_ROOT/pages" -name "*.md" -type f -exec grep -l "^preview: /assets/" {} \; 2>/dev/null | wc -l | tr -d ' ')
|
|
131
|
+
TOTAL_COUNT=$(find "$PROJECT_ROOT/pages" -name "*.md" -type f | wc -l | tr -d ' ')
|
|
132
|
+
|
|
133
|
+
if [ "$DRY_RUN" = true ]; then
|
|
134
|
+
echo " Files to update: $MODIFIED_COUNT"
|
|
135
|
+
echo " Total markdown files: $TOTAL_COUNT"
|
|
136
|
+
echo ""
|
|
137
|
+
log_info "Run with --apply to make these changes"
|
|
138
|
+
else
|
|
139
|
+
# After applying, count should be 0
|
|
140
|
+
REMAINING=$(find "$PROJECT_ROOT/pages" -name "*.md" -type f -exec grep -l "^preview: /assets/" {} \; 2>/dev/null | wc -l | tr -d ' ')
|
|
141
|
+
echo " Files updated: $MODIFIED_COUNT"
|
|
142
|
+
echo " Files remaining: $REMAINING"
|
|
143
|
+
echo " Total markdown files: $TOTAL_COUNT"
|
|
144
|
+
fi
|
|
145
|
+
echo ""
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: jekyll-theme-zer0
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.16.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Amr Abdel
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2025-12-
|
|
11
|
+
date: 2025-12-20 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: jekyll
|
|
@@ -81,12 +81,19 @@ files:
|
|
|
81
81
|
- _data/authors.yml
|
|
82
82
|
- _data/content_statistics.yml
|
|
83
83
|
- _data/generate_statistics.rb
|
|
84
|
+
- _data/generate_statistics.sh
|
|
85
|
+
- _data/github-actions-example.yml
|
|
84
86
|
- _data/navigation/about.yml
|
|
85
87
|
- _data/navigation/docs.yml
|
|
86
88
|
- _data/navigation/home.yml
|
|
87
89
|
- _data/navigation/main.yml
|
|
88
90
|
- _data/navigation/posts.yml
|
|
89
91
|
- _data/navigation/quickstart.yml
|
|
92
|
+
- _data/posts_organization.yml
|
|
93
|
+
- _data/prerequisites.yml
|
|
94
|
+
- _data/statistics_config.yml
|
|
95
|
+
- _data/ui-text.yml
|
|
96
|
+
- _data/update_statistics.sh
|
|
90
97
|
- _includes/README.md
|
|
91
98
|
- _includes/analytics/google-analytics.html
|
|
92
99
|
- _includes/analytics/google-tag-manager-body.html
|
|
@@ -176,6 +183,7 @@ files:
|
|
|
176
183
|
- assets/images/previews/published-documentation-library.png
|
|
177
184
|
- assets/images/previews/quantum-computing-explained-from-qubits-to-quantum.png
|
|
178
185
|
- assets/images/previews/science.png
|
|
186
|
+
- assets/images/previews/site-personalization-configuration.png
|
|
179
187
|
- assets/images/previews/technology.png
|
|
180
188
|
- assets/images/previews/the-complete-guide-to-startup-funding-in-2025.png
|
|
181
189
|
- assets/images/previews/the-remote-work-revolution-how-global-teams-are-re.png
|
|
@@ -237,6 +245,7 @@ files:
|
|
|
237
245
|
- scripts/test/lib/test_validation.sh
|
|
238
246
|
- scripts/test/lib/test_version.sh
|
|
239
247
|
- scripts/test/theme/validate
|
|
248
|
+
- scripts/update-preview-paths.sh
|
|
240
249
|
- scripts/utils/analyze-commits
|
|
241
250
|
- scripts/utils/fix-markdown
|
|
242
251
|
- scripts/utils/setup
|