jekyll-theme-zer0 1.15.0 → 1.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 +16 -0
- data/README.md +4 -4
- data/_data/features.yml +15 -0
- data/_data/theme-manifest.yml +428 -0
- data/_includes/components/ai-chat.html +579 -0
- data/_includes/core/footer.html +3 -1
- data/_layouts/root.html +3 -0
- data/_plugins/obsidian_links.rb +18 -5
- data/_sass/tokens/_layers.scss +1 -0
- data/scripts/bin/audit-consumer +336 -0
- data/scripts/bin/manifest +183 -0
- data/scripts/bin/release +15 -1
- data/scripts/bin/sync-plugins +356 -0
- data/scripts/bin/validate +1 -1
- data/scripts/lib/audit.sh +284 -0
- data/scripts/lib/frontmatter.sh +4 -2
- data/scripts/test/lib/test_locale_independence.sh +3 -0
- metadata +7 -1
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# scripts/bin/audit-consumer — audit a Jekyll consumer repo against this theme
|
|
3
|
+
#
|
|
4
|
+
# Usage: ./scripts/bin/audit-consumer [OPTIONS]
|
|
5
|
+
# --consumer-path <dir> Path to the consumer repo (default: cwd)
|
|
6
|
+
# --theme-path <dir> Explicit theme path (auto-resolved if omitted)
|
|
7
|
+
# --mode {gem|remote_theme} Deployment mode (auto-detected if omitted)
|
|
8
|
+
# --fix Delete IDENTICAL files + copy MISSING/STALE plugins
|
|
9
|
+
# --overwrite-plugins Combined with --fix: replace STALE plugins too
|
|
10
|
+
# --strict Exit non-zero on any unjustified drift (for CI)
|
|
11
|
+
# --format {text|json|github} Output format (default: text)
|
|
12
|
+
# --verbose Debug output
|
|
13
|
+
# --dry-run Show what --fix would do without changing files
|
|
14
|
+
# --help, -h Show this help
|
|
15
|
+
|
|
16
|
+
set -euo pipefail
|
|
17
|
+
|
|
18
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
19
|
+
LIB_DIR="$SCRIPT_DIR/../lib"
|
|
20
|
+
THEME_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
|
21
|
+
|
|
22
|
+
source "$LIB_DIR/common.sh"
|
|
23
|
+
source "$LIB_DIR/audit.sh"
|
|
24
|
+
|
|
25
|
+
# ---------------------------------------------------------------------------
|
|
26
|
+
show_usage() {
|
|
27
|
+
cat << 'EOF'
|
|
28
|
+
🔍 Theme Consumer Auditor for zer0-mistakes
|
|
29
|
+
|
|
30
|
+
USAGE:
|
|
31
|
+
./scripts/bin/audit-consumer [OPTIONS]
|
|
32
|
+
|
|
33
|
+
DESCRIPTION:
|
|
34
|
+
Compares a consumer Jekyll repo against the installed zer0-mistakes theme
|
|
35
|
+
and reports every shadowed, missing, or stale file.
|
|
36
|
+
|
|
37
|
+
File classifications:
|
|
38
|
+
IDENTICAL Consumer file is byte-for-byte identical to theme file
|
|
39
|
+
→ safe to delete (theme file will be used instead)
|
|
40
|
+
DIFFERS_JUSTIFIED Consumer file differs and is listed in .theme-overrides.yml
|
|
41
|
+
→ intentional override, no action needed
|
|
42
|
+
DIFFERS_UNJUSTIFIED Consumer file differs but is NOT listed in .theme-overrides.yml
|
|
43
|
+
→ flag for review; --strict exits non-zero
|
|
44
|
+
UNIQUE File exists only in the consumer, not in the theme
|
|
45
|
+
→ consumer-specific file, no action needed
|
|
46
|
+
MISSING_LOCALLY File exists in theme but not in consumer
|
|
47
|
+
→ informational only
|
|
48
|
+
|
|
49
|
+
Plugin classifications (remote_theme mode only):
|
|
50
|
+
MISSING_PLUGIN Plugin required by theme not vendored in consumer
|
|
51
|
+
STALE_PLUGIN Plugin exists but differs from current theme version
|
|
52
|
+
OK Plugin matches current theme version
|
|
53
|
+
NOT_REQUIRED Gem mode — theme loads plugins itself
|
|
54
|
+
|
|
55
|
+
OPTIONS:
|
|
56
|
+
--consumer-path <dir> Consumer repo root (default: current directory)
|
|
57
|
+
--theme-path <dir> Explicit theme path (auto-resolved if omitted)
|
|
58
|
+
--mode {gem|remote_theme} Force deployment mode (auto-detected if omitted)
|
|
59
|
+
--fix Fix issues: delete IDENTICAL files; copy
|
|
60
|
+
MISSING_PLUGIN files from theme
|
|
61
|
+
--overwrite-plugins With --fix: also replace STALE_PLUGIN files
|
|
62
|
+
--strict Exit 1 if any DIFFERS_UNJUSTIFIED or
|
|
63
|
+
MISSING_PLUGIN issues found (use in CI)
|
|
64
|
+
--format {text|json|github} Output format
|
|
65
|
+
github emits ::warning annotations for CI
|
|
66
|
+
--verbose Show debug output
|
|
67
|
+
--dry-run Show what --fix would do without changing files
|
|
68
|
+
--help, -h Show this help
|
|
69
|
+
|
|
70
|
+
EXAMPLES:
|
|
71
|
+
# Audit current directory (auto-detect mode)
|
|
72
|
+
./scripts/bin/audit-consumer
|
|
73
|
+
|
|
74
|
+
# Audit from a sibling consumer repo
|
|
75
|
+
./scripts/bin/audit-consumer --consumer-path ~/github/it-journey
|
|
76
|
+
|
|
77
|
+
# CI strict check with GitHub annotations
|
|
78
|
+
./scripts/bin/audit-consumer --strict --format github
|
|
79
|
+
|
|
80
|
+
# Fix all safe auto-fixable issues
|
|
81
|
+
./scripts/bin/audit-consumer --fix --dry-run # preview first
|
|
82
|
+
./scripts/bin/audit-consumer --fix
|
|
83
|
+
|
|
84
|
+
# Fix including stale plugins
|
|
85
|
+
./scripts/bin/audit-consumer --fix --overwrite-plugins
|
|
86
|
+
EOF
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
# ---------------------------------------------------------------------------
|
|
90
|
+
# Defaults
|
|
91
|
+
CONSUMER_PATH="$(pwd)"
|
|
92
|
+
THEME_PATH_OVERRIDE=""
|
|
93
|
+
MODE_OVERRIDE=""
|
|
94
|
+
FIX=false
|
|
95
|
+
OVERWRITE_PLUGINS=false
|
|
96
|
+
STRICT=false
|
|
97
|
+
FORMAT="text"
|
|
98
|
+
SHOW_HELP=false
|
|
99
|
+
|
|
100
|
+
while [[ $# -gt 0 ]]; do
|
|
101
|
+
case $1 in
|
|
102
|
+
--consumer-path) shift; CONSUMER_PATH="$1" ;;
|
|
103
|
+
--theme-path) shift; THEME_PATH_OVERRIDE="$1" ;;
|
|
104
|
+
--mode) shift; MODE_OVERRIDE="$1" ;;
|
|
105
|
+
--fix) FIX=true ;;
|
|
106
|
+
--overwrite-plugins) OVERWRITE_PLUGINS=true ;;
|
|
107
|
+
--strict) STRICT=true ;;
|
|
108
|
+
--format) shift; FORMAT="$1" ;;
|
|
109
|
+
--dry-run) export DRY_RUN=true ;;
|
|
110
|
+
--verbose) export VERBOSE=true ;;
|
|
111
|
+
--help|-h) SHOW_HELP=true ;;
|
|
112
|
+
*) warn "Unknown option: $1" ;;
|
|
113
|
+
esac
|
|
114
|
+
shift
|
|
115
|
+
done
|
|
116
|
+
|
|
117
|
+
[[ "$SHOW_HELP" == "true" ]] && { show_usage; exit 0; }
|
|
118
|
+
|
|
119
|
+
CONSUMER_PATH="$(cd "$CONSUMER_PATH" && pwd)"
|
|
120
|
+
|
|
121
|
+
# ---------------------------------------------------------------------------
|
|
122
|
+
step "Auditing consumer: $CONSUMER_PATH"
|
|
123
|
+
|
|
124
|
+
# Detect or confirm mode
|
|
125
|
+
if [[ -n "$MODE_OVERRIDE" ]]; then
|
|
126
|
+
MODE="$MODE_OVERRIDE"
|
|
127
|
+
else
|
|
128
|
+
MODE=$(detect_consumer_mode "$CONSUMER_PATH")
|
|
129
|
+
info "Detected mode: $MODE"
|
|
130
|
+
fi
|
|
131
|
+
|
|
132
|
+
# Resolve theme path
|
|
133
|
+
if [[ -n "$THEME_PATH_OVERRIDE" ]]; then
|
|
134
|
+
RESOLVED_THEME="$THEME_PATH_OVERRIDE"
|
|
135
|
+
else
|
|
136
|
+
RESOLVED_THEME=$(resolve_theme_path "$MODE" "$CONSUMER_PATH" "$THEME_ROOT")
|
|
137
|
+
fi
|
|
138
|
+
|
|
139
|
+
if [[ -z "$RESOLVED_THEME" || ! -d "$RESOLVED_THEME" ]]; then
|
|
140
|
+
error "Cannot resolve theme path. Try --theme-path <dir> or run from the consumer with gem installed."
|
|
141
|
+
fi
|
|
142
|
+
info "Theme path: $RESOLVED_THEME"
|
|
143
|
+
|
|
144
|
+
# Locate manifest
|
|
145
|
+
MANIFEST="$RESOLVED_THEME/_data/theme-manifest.yml"
|
|
146
|
+
if [[ ! -f "$MANIFEST" ]]; then
|
|
147
|
+
warn "No theme manifest found at $MANIFEST — falling back to directory scan."
|
|
148
|
+
MANIFEST=""
|
|
149
|
+
fi
|
|
150
|
+
|
|
151
|
+
# Load overrides manifest from consumer
|
|
152
|
+
OVERRIDES_FILE="$CONSUMER_PATH/.theme-overrides.yml"
|
|
153
|
+
OVERRIDE_PATHS_TMP=$(mktemp)
|
|
154
|
+
trap 'rm -f "$OVERRIDE_PATHS_TMP"' EXIT
|
|
155
|
+
|
|
156
|
+
if [[ -f "$OVERRIDES_FILE" ]]; then
|
|
157
|
+
info "Override manifest: $OVERRIDES_FILE"
|
|
158
|
+
overrides_list "$OVERRIDES_FILE" > "$OVERRIDE_PATHS_TMP"
|
|
159
|
+
else
|
|
160
|
+
warn "No .theme-overrides.yml found — all differing files will be flagged as DIFFERS_UNJUSTIFIED"
|
|
161
|
+
fi
|
|
162
|
+
|
|
163
|
+
# ---------------------------------------------------------------------------
|
|
164
|
+
# Collect themable paths from manifest or defaults
|
|
165
|
+
# ---------------------------------------------------------------------------
|
|
166
|
+
THEMABLE_DIRS=()
|
|
167
|
+
REQUIRED_PLUGIN_FILES=()
|
|
168
|
+
OPTIONAL_PLUGIN_FILES=()
|
|
169
|
+
|
|
170
|
+
if [[ -n "$MANIFEST" ]]; then
|
|
171
|
+
while IFS= read -r line; do
|
|
172
|
+
[[ -n "$line" ]] && THEMABLE_DIRS+=("$line")
|
|
173
|
+
done < <(manifest_list "$MANIFEST" "themable_paths")
|
|
174
|
+
# Prefer the explicit required/optional split; fall back to legacy plugin_paths
|
|
175
|
+
while IFS= read -r line; do
|
|
176
|
+
[[ -n "$line" ]] && REQUIRED_PLUGIN_FILES+=("$line")
|
|
177
|
+
done < <(manifest_list "$MANIFEST" "required_plugin_paths")
|
|
178
|
+
while IFS= read -r line; do
|
|
179
|
+
[[ -n "$line" ]] && OPTIONAL_PLUGIN_FILES+=("$line")
|
|
180
|
+
done < <(manifest_list "$MANIFEST" "optional_plugin_paths")
|
|
181
|
+
# Legacy fallback: if new keys absent, use plugin_paths as required
|
|
182
|
+
if [[ ${#REQUIRED_PLUGIN_FILES[@]} -eq 0 && ${#OPTIONAL_PLUGIN_FILES[@]} -eq 0 ]]; then
|
|
183
|
+
while IFS= read -r line; do
|
|
184
|
+
[[ -n "$line" ]] && REQUIRED_PLUGIN_FILES+=("$line")
|
|
185
|
+
done < <(manifest_list "$MANIFEST" "plugin_paths")
|
|
186
|
+
fi
|
|
187
|
+
fi
|
|
188
|
+
|
|
189
|
+
if [[ ${#THEMABLE_DIRS[@]} -eq 0 ]]; then
|
|
190
|
+
THEMABLE_DIRS=("_layouts" "_includes" "_sass" "assets/css" "assets/js")
|
|
191
|
+
fi
|
|
192
|
+
if [[ ${#REQUIRED_PLUGIN_FILES[@]} -eq 0 && ${#OPTIONAL_PLUGIN_FILES[@]} -eq 0 ]]; then
|
|
193
|
+
REQUIRED_PLUGIN_FILES=("_plugins/preview_image_generator.rb" "_plugins/obsidian_links.rb")
|
|
194
|
+
OPTIONAL_PLUGIN_FILES=("_plugins/admin_page_urls.rb" "_plugins/content_statistics_generator.rb" "_plugins/theme_version.rb")
|
|
195
|
+
fi
|
|
196
|
+
|
|
197
|
+
# ---------------------------------------------------------------------------
|
|
198
|
+
# Scan themable directories
|
|
199
|
+
# ---------------------------------------------------------------------------
|
|
200
|
+
DRIFT_COUNT=0
|
|
201
|
+
IDENTICAL_FILES=()
|
|
202
|
+
DIFFERS_UNJUSTIFIED_FILES=()
|
|
203
|
+
|
|
204
|
+
[[ "$FORMAT" == "json" ]] && echo '{"results":['
|
|
205
|
+
|
|
206
|
+
for theme_dir in "${THEMABLE_DIRS[@]}"; do
|
|
207
|
+
theme_abs="$RESOLVED_THEME/$theme_dir"
|
|
208
|
+
[[ -d "$theme_abs" ]] || continue
|
|
209
|
+
|
|
210
|
+
while IFS= read -r -d '' theme_file; do
|
|
211
|
+
relpath="${theme_file#$RESOLVED_THEME/}"
|
|
212
|
+
[[ "$relpath" == *README* ]] && continue
|
|
213
|
+
[[ "$relpath" == *_site/* ]] && continue
|
|
214
|
+
[[ "$relpath" == *.map ]] && continue
|
|
215
|
+
|
|
216
|
+
classification=$(classify_file "$RESOLVED_THEME" "$CONSUMER_PATH" "$relpath" "$OVERRIDE_PATHS_TMP")
|
|
217
|
+
print_classification "$classification" "$relpath" "$FORMAT"
|
|
218
|
+
|
|
219
|
+
case "$classification" in
|
|
220
|
+
IDENTICAL)
|
|
221
|
+
IDENTICAL_FILES+=("$relpath") ;;
|
|
222
|
+
DIFFERS_UNJUSTIFIED)
|
|
223
|
+
DIFFERS_UNJUSTIFIED_FILES+=("$relpath")
|
|
224
|
+
DRIFT_COUNT=$((DRIFT_COUNT + 1)) ;;
|
|
225
|
+
esac
|
|
226
|
+
done < <(find "$theme_abs" -type f -print0 | sort -z)
|
|
227
|
+
done
|
|
228
|
+
|
|
229
|
+
# Also scan consumer for UNIQUE files (in theme dirs only)
|
|
230
|
+
for consumer_dir in "${THEMABLE_DIRS[@]}"; do
|
|
231
|
+
consumer_abs="$CONSUMER_PATH/$consumer_dir"
|
|
232
|
+
[[ -d "$consumer_abs" ]] || continue
|
|
233
|
+
|
|
234
|
+
while IFS= read -r -d '' consumer_file; do
|
|
235
|
+
relpath="${consumer_file#$CONSUMER_PATH/}"
|
|
236
|
+
theme_file="$RESOLVED_THEME/$relpath"
|
|
237
|
+
[[ -f "$theme_file" ]] && continue # already classified above
|
|
238
|
+
[[ "$relpath" == *README* ]] && continue
|
|
239
|
+
print_classification "UNIQUE" "$relpath" "$FORMAT"
|
|
240
|
+
done < <(find "$consumer_abs" -type f -print0 | sort -z)
|
|
241
|
+
done
|
|
242
|
+
|
|
243
|
+
# ---------------------------------------------------------------------------
|
|
244
|
+
# Scan plugins
|
|
245
|
+
# ---------------------------------------------------------------------------
|
|
246
|
+
PLUGIN_ISSUES=0
|
|
247
|
+
OPTIONAL_PLUGIN_ISSUES=0
|
|
248
|
+
ALL_PLUGIN_FILES=("${REQUIRED_PLUGIN_FILES[@]}" "${OPTIONAL_PLUGIN_FILES[@]}")
|
|
249
|
+
|
|
250
|
+
if [[ ${#ALL_PLUGIN_FILES[@]} -gt 0 ]]; then
|
|
251
|
+
[[ "$FORMAT" == "text" ]] && echo ""
|
|
252
|
+
[[ "$FORMAT" == "text" ]] && info "--- Plugin check (mode: $MODE) ---"
|
|
253
|
+
|
|
254
|
+
for plugin_rel in "${REQUIRED_PLUGIN_FILES[@]}"; do
|
|
255
|
+
classification=$(classify_plugin "$plugin_rel" "$CONSUMER_PATH" "$MODE" "$RESOLVED_THEME")
|
|
256
|
+
print_classification "$classification" "$plugin_rel" "$FORMAT"
|
|
257
|
+
case "$classification" in
|
|
258
|
+
MISSING_PLUGIN|STALE_PLUGIN)
|
|
259
|
+
PLUGIN_ISSUES=$((PLUGIN_ISSUES + 1)) ;;
|
|
260
|
+
esac
|
|
261
|
+
done
|
|
262
|
+
|
|
263
|
+
for plugin_rel in "${OPTIONAL_PLUGIN_FILES[@]}"; do
|
|
264
|
+
classification=$(classify_plugin "$plugin_rel" "$CONSUMER_PATH" "$MODE" "$RESOLVED_THEME")
|
|
265
|
+
# Downgrade missing optional plugins to OPTIONAL_PLUGIN (informational)
|
|
266
|
+
if [[ "$classification" == "MISSING_PLUGIN" ]]; then
|
|
267
|
+
classification="OPTIONAL_PLUGIN"
|
|
268
|
+
OPTIONAL_PLUGIN_ISSUES=$((OPTIONAL_PLUGIN_ISSUES + 1))
|
|
269
|
+
fi
|
|
270
|
+
print_classification "$classification" "$plugin_rel" "$FORMAT"
|
|
271
|
+
done
|
|
272
|
+
fi
|
|
273
|
+
|
|
274
|
+
# ---------------------------------------------------------------------------
|
|
275
|
+
# Apply fixes
|
|
276
|
+
# ---------------------------------------------------------------------------
|
|
277
|
+
if [[ "$FIX" == "true" ]]; then
|
|
278
|
+
echo ""
|
|
279
|
+
step "Applying fixes..."
|
|
280
|
+
|
|
281
|
+
for relpath in "${IDENTICAL_FILES[@]}"; do
|
|
282
|
+
if [[ "$DRY_RUN" == "true" ]]; then
|
|
283
|
+
info "[DRY RUN] Would delete: $CONSUMER_PATH/$relpath"
|
|
284
|
+
else
|
|
285
|
+
rm -f "$CONSUMER_PATH/$relpath"
|
|
286
|
+
success "Deleted (IDENTICAL): $relpath"
|
|
287
|
+
fi
|
|
288
|
+
done
|
|
289
|
+
|
|
290
|
+
for plugin_rel in "${REQUIRED_PLUGIN_FILES[@]}" "${OPTIONAL_PLUGIN_FILES[@]}"; do
|
|
291
|
+
classification=$(classify_plugin "$plugin_rel" "$CONSUMER_PATH" "$MODE" "$RESOLVED_THEME")
|
|
292
|
+
theme_plugin="$RESOLVED_THEME/$plugin_rel"
|
|
293
|
+
consumer_plugin="$CONSUMER_PATH/$plugin_rel"
|
|
294
|
+
|
|
295
|
+
if [[ "$classification" == "MISSING_PLUGIN" ]]; then
|
|
296
|
+
if [[ -f "$theme_plugin" ]]; then
|
|
297
|
+
if [[ "$DRY_RUN" == "true" ]]; then
|
|
298
|
+
info "[DRY RUN] Would copy: $plugin_rel"
|
|
299
|
+
else
|
|
300
|
+
mkdir -p "$(dirname "$consumer_plugin")"
|
|
301
|
+
cp "$theme_plugin" "$consumer_plugin"
|
|
302
|
+
success "Copied (MISSING_PLUGIN): $plugin_rel"
|
|
303
|
+
fi
|
|
304
|
+
fi
|
|
305
|
+
elif [[ "$classification" == "STALE_PLUGIN" && "$OVERWRITE_PLUGINS" == "true" ]]; then
|
|
306
|
+
if [[ "$DRY_RUN" == "true" ]]; then
|
|
307
|
+
info "[DRY RUN] Would overwrite stale plugin: $plugin_rel"
|
|
308
|
+
else
|
|
309
|
+
cp "$theme_plugin" "$consumer_plugin"
|
|
310
|
+
success "Updated (STALE_PLUGIN): $plugin_rel"
|
|
311
|
+
fi
|
|
312
|
+
fi
|
|
313
|
+
done
|
|
314
|
+
fi
|
|
315
|
+
|
|
316
|
+
# ---------------------------------------------------------------------------
|
|
317
|
+
# Summary
|
|
318
|
+
# ---------------------------------------------------------------------------
|
|
319
|
+
[[ "$FORMAT" == "json" ]] && echo ']}'
|
|
320
|
+
|
|
321
|
+
if [[ "$FORMAT" == "text" ]]; then
|
|
322
|
+
echo ""
|
|
323
|
+
info "--- Summary ---"
|
|
324
|
+
info "IDENTICAL files (safe to delete): ${#IDENTICAL_FILES[@]}"
|
|
325
|
+
info "DIFFERS_UNJUSTIFIED files: ${#DIFFERS_UNJUSTIFIED_FILES[@]}"
|
|
326
|
+
info "Required plugin issues: $PLUGIN_ISSUES"
|
|
327
|
+
[[ "$OPTIONAL_PLUGIN_ISSUES" -gt 0 ]] && info "Optional plugins not vendored: $OPTIONAL_PLUGIN_ISSUES (informational)"
|
|
328
|
+
fi
|
|
329
|
+
|
|
330
|
+
TOTAL_ISSUES=$((DRIFT_COUNT + PLUGIN_ISSUES))
|
|
331
|
+
|
|
332
|
+
if [[ "$STRICT" == "true" && "$TOTAL_ISSUES" -gt 0 ]]; then
|
|
333
|
+
error "Strict mode: $TOTAL_ISSUES unjustified drift issue(s) found."
|
|
334
|
+
fi
|
|
335
|
+
|
|
336
|
+
[[ "$TOTAL_ISSUES" -gt 0 ]] && exit 1 || exit 0
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# scripts/bin/manifest — generate _data/theme-manifest.yml
|
|
3
|
+
# Walks the theme repo filesystem and produces a machine-readable manifest
|
|
4
|
+
# with per-file SHA-256 checksums. Run automatically by scripts/bin/release.
|
|
5
|
+
#
|
|
6
|
+
# Usage: ./scripts/bin/manifest [OPTIONS]
|
|
7
|
+
# --dry-run Print manifest to stdout instead of writing the file
|
|
8
|
+
# --verbose Show each file being hashed
|
|
9
|
+
# --help, -h Show this help
|
|
10
|
+
|
|
11
|
+
set -euo pipefail
|
|
12
|
+
|
|
13
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
14
|
+
LIB_DIR="$SCRIPT_DIR/../lib"
|
|
15
|
+
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
|
16
|
+
|
|
17
|
+
source "$LIB_DIR/common.sh"
|
|
18
|
+
source "$LIB_DIR/version.sh"
|
|
19
|
+
source "$LIB_DIR/audit.sh"
|
|
20
|
+
|
|
21
|
+
# ---------------------------------------------------------------------------
|
|
22
|
+
show_usage() {
|
|
23
|
+
cat << 'EOF'
|
|
24
|
+
📋 Theme Manifest Generator for zer0-mistakes
|
|
25
|
+
|
|
26
|
+
USAGE:
|
|
27
|
+
./scripts/bin/manifest [OPTIONS]
|
|
28
|
+
|
|
29
|
+
DESCRIPTION:
|
|
30
|
+
Generates _data/theme-manifest.yml from the current state of the theme
|
|
31
|
+
repo. The manifest records the version, owned directories, plugin files
|
|
32
|
+
that remote-theme consumers must vendor, required config keys, and a
|
|
33
|
+
SHA-256 checksum for every themable file.
|
|
34
|
+
|
|
35
|
+
Run automatically by scripts/bin/release before tagging.
|
|
36
|
+
|
|
37
|
+
OPTIONS:
|
|
38
|
+
--dry-run Print manifest to stdout; don't write the file
|
|
39
|
+
--verbose Show each file being processed
|
|
40
|
+
--help, -h Show this help message
|
|
41
|
+
|
|
42
|
+
EXAMPLES:
|
|
43
|
+
./scripts/bin/manifest # Regenerate _data/theme-manifest.yml
|
|
44
|
+
./scripts/bin/manifest --dry-run # Preview output
|
|
45
|
+
./scripts/bin/manifest --verbose # Show every file being hashed
|
|
46
|
+
EOF
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
# ---------------------------------------------------------------------------
|
|
50
|
+
# Directories the theme owns (relative to repo root)
|
|
51
|
+
THEMABLE_PATHS=(
|
|
52
|
+
"_layouts"
|
|
53
|
+
"_includes"
|
|
54
|
+
"_sass"
|
|
55
|
+
"assets/css"
|
|
56
|
+
"assets/js"
|
|
57
|
+
"assets/images"
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
# Data files the theme ships that consumers may override with their own content
|
|
61
|
+
# (excluded from the overlap audit; listed for documentation only).
|
|
62
|
+
THEME_DATA_PATHS=(
|
|
63
|
+
"_data/navigation"
|
|
64
|
+
"_data/ui-text.yml"
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
# Plugins that remote_theme consumers MUST vendor locally (GitHub Pages won't
|
|
68
|
+
# load plugins from a remote theme).
|
|
69
|
+
REQUIRED_PLUGIN_PATHS=(
|
|
70
|
+
"_plugins/preview_image_generator.rb"
|
|
71
|
+
"_plugins/obsidian_links.rb"
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
# Optional plugins that consumers can vendor for enhanced functionality.
|
|
75
|
+
OPTIONAL_PLUGIN_PATHS=(
|
|
76
|
+
"_plugins/admin_page_urls.rb"
|
|
77
|
+
"_plugins/content_statistics_generator.rb"
|
|
78
|
+
"_plugins/theme_version.rb"
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
# ---------------------------------------------------------------------------
|
|
82
|
+
# Argument parsing
|
|
83
|
+
DRY_RUN=false
|
|
84
|
+
VERBOSE=false
|
|
85
|
+
|
|
86
|
+
while [[ $# -gt 0 ]]; do
|
|
87
|
+
case $1 in
|
|
88
|
+
--dry-run) DRY_RUN=true ;;
|
|
89
|
+
--verbose) VERBOSE=true ;;
|
|
90
|
+
--help|-h) show_usage; exit 0 ;;
|
|
91
|
+
*) warn "Unknown option: $1" ;;
|
|
92
|
+
esac
|
|
93
|
+
shift
|
|
94
|
+
done
|
|
95
|
+
|
|
96
|
+
# ---------------------------------------------------------------------------
|
|
97
|
+
step "Generating theme manifest..."
|
|
98
|
+
cd "$REPO_ROOT"
|
|
99
|
+
|
|
100
|
+
CURRENT_VERSION=$(get_current_version)
|
|
101
|
+
TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
102
|
+
|
|
103
|
+
# ---------------------------------------------------------------------------
|
|
104
|
+
# Build the per-file checksum block
|
|
105
|
+
# ---------------------------------------------------------------------------
|
|
106
|
+
build_checksums() {
|
|
107
|
+
for dir in "${THEMABLE_PATHS[@]}"; do
|
|
108
|
+
[[ -d "$dir" ]] || continue
|
|
109
|
+
while IFS= read -r -d '' file; do
|
|
110
|
+
local relpath="${file#./}"
|
|
111
|
+
# Skip README files and compiled/generated output
|
|
112
|
+
[[ "$relpath" == *README* ]] && continue
|
|
113
|
+
[[ "$relpath" == *_site/* ]] && continue
|
|
114
|
+
[[ "$relpath" == *.map ]] && continue
|
|
115
|
+
local sha
|
|
116
|
+
sha=$(sha256_file "$file")
|
|
117
|
+
debug " $relpath → $sha"
|
|
118
|
+
[[ "$VERBOSE" == "true" ]] && info " $relpath"
|
|
119
|
+
printf ' %s: "%s"\n' "$relpath" "$sha"
|
|
120
|
+
done < <(find "$dir" -type f -print0 | sort -z)
|
|
121
|
+
done
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
# ---------------------------------------------------------------------------
|
|
125
|
+
MANIFEST_CONTENT=$(cat << YAML
|
|
126
|
+
# _data/theme-manifest.yml
|
|
127
|
+
# AUTO-GENERATED by scripts/bin/manifest — DO NOT EDIT BY HAND.
|
|
128
|
+
# Regenerated automatically during each release (scripts/bin/release).
|
|
129
|
+
# Consumers use this file via scripts/bin/audit-consumer to detect drift.
|
|
130
|
+
|
|
131
|
+
version: "${CURRENT_VERSION}"
|
|
132
|
+
generated_at: "${TIMESTAMP}"
|
|
133
|
+
|
|
134
|
+
# Directories owned by this theme. A consumer file in any of these paths
|
|
135
|
+
# that is not declared in the consumer's .theme-overrides.yml is flagged
|
|
136
|
+
# as an unjustified shadow.
|
|
137
|
+
themable_paths:
|
|
138
|
+
$(printf ' - %s\n' "${THEMABLE_PATHS[@]}")
|
|
139
|
+
|
|
140
|
+
# Data paths the theme ships but that consumers are *expected* to customise.
|
|
141
|
+
# Not included in the shadow audit.
|
|
142
|
+
theme_data_paths:
|
|
143
|
+
$(printf ' - %s\n' "${THEME_DATA_PATHS[@]}")
|
|
144
|
+
|
|
145
|
+
# Plugin files that consumers using remote_theme MUST vendor locally.
|
|
146
|
+
# GitHub Pages does not load plugins from a remote_theme source.
|
|
147
|
+
required_plugin_paths:
|
|
148
|
+
$(printf ' - %s\n' "${REQUIRED_PLUGIN_PATHS[@]}")
|
|
149
|
+
# Optional plugins consumers can vendor for enhanced functionality.
|
|
150
|
+
optional_plugin_paths:
|
|
151
|
+
$(printf ' - %s\n' "${OPTIONAL_PLUGIN_PATHS[@]}")
|
|
152
|
+
|
|
153
|
+
# Legacy alias — all plugin paths combined (kept for backward compatibility).
|
|
154
|
+
plugin_paths:
|
|
155
|
+
$(printf ' - %s\n' "${REQUIRED_PLUGIN_PATHS[@]}" "${OPTIONAL_PLUGIN_PATHS[@]}")
|
|
156
|
+
|
|
157
|
+
# Jekyll config keys this theme expects.
|
|
158
|
+
config_schema:
|
|
159
|
+
required:
|
|
160
|
+
- title
|
|
161
|
+
- description
|
|
162
|
+
- remote_theme
|
|
163
|
+
recommended_features:
|
|
164
|
+
- posthog
|
|
165
|
+
- preview_images
|
|
166
|
+
- cookie_consent
|
|
167
|
+
deprecated:
|
|
168
|
+
- site_configured # Removed in 1.5.0; safe to delete
|
|
169
|
+
|
|
170
|
+
# Per-file SHA-256 checksums for every file in themable_paths.
|
|
171
|
+
file_checksums:
|
|
172
|
+
$(build_checksums)
|
|
173
|
+
YAML
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
# ---------------------------------------------------------------------------
|
|
177
|
+
if [[ "$DRY_RUN" == "true" ]]; then
|
|
178
|
+
info "[DRY RUN] Would write _data/theme-manifest.yml:"
|
|
179
|
+
echo "$MANIFEST_CONTENT"
|
|
180
|
+
else
|
|
181
|
+
echo "$MANIFEST_CONTENT" > "$REPO_ROOT/_data/theme-manifest.yml"
|
|
182
|
+
success "Written: _data/theme-manifest.yml (version $CURRENT_VERSION)"
|
|
183
|
+
fi
|
data/scripts/bin/release
CHANGED
|
@@ -177,7 +177,21 @@ main() {
|
|
|
177
177
|
# Step 4: Update version files
|
|
178
178
|
update_version_files "$new_version"
|
|
179
179
|
echo ""
|
|
180
|
-
|
|
180
|
+
|
|
181
|
+
# Step 4.5: Regenerate theme manifest with new version + checksums
|
|
182
|
+
step "Regenerating theme manifest..."
|
|
183
|
+
local manifest_script="$SCRIPT_DIR/manifest"
|
|
184
|
+
if [[ -x "$manifest_script" ]]; then
|
|
185
|
+
if [[ "$DRY_RUN" == "true" ]]; then
|
|
186
|
+
info "[DRY RUN] Would regenerate _data/theme-manifest.yml"
|
|
187
|
+
else
|
|
188
|
+
"$manifest_script"
|
|
189
|
+
fi
|
|
190
|
+
else
|
|
191
|
+
warn "scripts/bin/manifest not found — skipping manifest regeneration"
|
|
192
|
+
fi
|
|
193
|
+
echo ""
|
|
194
|
+
|
|
181
195
|
# Step 5: Run tests
|
|
182
196
|
if [[ "$SKIP_TESTS" != "true" ]]; then
|
|
183
197
|
run_tests
|