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.
@@ -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