jekyll-theme-zer0 1.3.0 → 1.4.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 +29 -0
- data/README.md +4 -4
- data/_includes/components/js-cdn.html +8 -1
- data/_includes/content/backlinks.html +4 -0
- data/_includes/navigation/local-graph.html +55 -20
- data/_layouts/default.html +104 -100
- data/_sass/core/_obsidian.scss +77 -4
- data/_sass/core/_offcanvas-panels.scss +8 -4
- data/assets/js/obsidian-local-graph.js +72 -20
- data/assets/js/obsidian-wiki-links.js +21 -4
- data/scripts/README.md +22 -0
- data/scripts/bin/test +62 -0
- data/scripts/bin/validate +596 -0
- data/scripts/lib/README.md +16 -0
- data/scripts/validate +11 -0
- metadata +4 -2
|
@@ -0,0 +1,596 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# Canonical preflight validator for zer0-mistakes.
|
|
4
|
+
# Usage: ./scripts/bin/validate [options]
|
|
5
|
+
|
|
6
|
+
set -euo pipefail
|
|
7
|
+
|
|
8
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
9
|
+
LIB_DIR="$SCRIPT_DIR/../lib"
|
|
10
|
+
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
|
11
|
+
|
|
12
|
+
source "$LIB_DIR/common.sh"
|
|
13
|
+
source "$LIB_DIR/validation.sh"
|
|
14
|
+
|
|
15
|
+
SKIP_JEKYLL=false
|
|
16
|
+
SKIP_DOCTOR=false
|
|
17
|
+
SKIP_ASSETS=false
|
|
18
|
+
RUN_TESTS=false
|
|
19
|
+
RUN_OBSIDIAN=false
|
|
20
|
+
RUN_HTMLPROOFER=false
|
|
21
|
+
START_DOCKER=false
|
|
22
|
+
EXECUTION_MODE="auto"
|
|
23
|
+
|
|
24
|
+
show_usage() {
|
|
25
|
+
cat << EOF
|
|
26
|
+
Preflight Validator for zer0-mistakes
|
|
27
|
+
|
|
28
|
+
USAGE:
|
|
29
|
+
./scripts/bin/validate [OPTIONS]
|
|
30
|
+
|
|
31
|
+
DESCRIPTION:
|
|
32
|
+
Runs the fast, deterministic checks that should gate refactors and CI,
|
|
33
|
+
with optional Jekyll, test, Obsidian, and HTML validation stages.
|
|
34
|
+
|
|
35
|
+
DEFAULT CHECKS:
|
|
36
|
+
- Required repository files
|
|
37
|
+
- Gemspec parse
|
|
38
|
+
- Version consistency between version.rb, package.json, and gemspec
|
|
39
|
+
- YAML parse for root config, _data, and .github/config files
|
|
40
|
+
- Active configuration contract and config-file classification
|
|
41
|
+
- Navigation data shape
|
|
42
|
+
- Jekyll build and doctor
|
|
43
|
+
- Compiled asset existence
|
|
44
|
+
|
|
45
|
+
OPTIONS:
|
|
46
|
+
--quick Run host-only checks; skip Jekyll, doctor, assets, tests
|
|
47
|
+
--full Include theme tests, Obsidian tests, and HTMLProofer
|
|
48
|
+
--with-tests Run ./scripts/bin/test after build checks
|
|
49
|
+
--with-obsidian Run ./test/test_obsidian.sh after build checks
|
|
50
|
+
--with-htmlproofer Run HTMLProofer against _site after build checks
|
|
51
|
+
--skip-jekyll Skip Jekyll build
|
|
52
|
+
--skip-doctor Skip Jekyll doctor
|
|
53
|
+
--skip-assets Skip compiled asset checks
|
|
54
|
+
--docker Require Docker Compose for Jekyll commands
|
|
55
|
+
--local Require local bundle exec for Jekyll commands
|
|
56
|
+
--start-docker Start docker-compose service jekyll when needed
|
|
57
|
+
--verbose Show debug output
|
|
58
|
+
--help, -h Show this help message
|
|
59
|
+
|
|
60
|
+
EXAMPLES:
|
|
61
|
+
./scripts/bin/validate --quick
|
|
62
|
+
./scripts/bin/validate --start-docker
|
|
63
|
+
./scripts/bin/validate --full --start-docker
|
|
64
|
+
|
|
65
|
+
EOF
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
while [[ $# -gt 0 ]]; do
|
|
69
|
+
case "$1" in
|
|
70
|
+
--quick)
|
|
71
|
+
SKIP_JEKYLL=true
|
|
72
|
+
SKIP_DOCTOR=true
|
|
73
|
+
SKIP_ASSETS=true
|
|
74
|
+
RUN_TESTS=false
|
|
75
|
+
RUN_OBSIDIAN=false
|
|
76
|
+
RUN_HTMLPROOFER=false
|
|
77
|
+
shift
|
|
78
|
+
;;
|
|
79
|
+
--full)
|
|
80
|
+
RUN_TESTS=true
|
|
81
|
+
RUN_OBSIDIAN=true
|
|
82
|
+
RUN_HTMLPROOFER=true
|
|
83
|
+
shift
|
|
84
|
+
;;
|
|
85
|
+
--with-tests)
|
|
86
|
+
RUN_TESTS=true
|
|
87
|
+
shift
|
|
88
|
+
;;
|
|
89
|
+
--with-obsidian)
|
|
90
|
+
RUN_OBSIDIAN=true
|
|
91
|
+
shift
|
|
92
|
+
;;
|
|
93
|
+
--with-htmlproofer)
|
|
94
|
+
RUN_HTMLPROOFER=true
|
|
95
|
+
shift
|
|
96
|
+
;;
|
|
97
|
+
--skip-jekyll)
|
|
98
|
+
SKIP_JEKYLL=true
|
|
99
|
+
shift
|
|
100
|
+
;;
|
|
101
|
+
--skip-doctor)
|
|
102
|
+
SKIP_DOCTOR=true
|
|
103
|
+
shift
|
|
104
|
+
;;
|
|
105
|
+
--skip-assets)
|
|
106
|
+
SKIP_ASSETS=true
|
|
107
|
+
shift
|
|
108
|
+
;;
|
|
109
|
+
--docker)
|
|
110
|
+
EXECUTION_MODE="docker"
|
|
111
|
+
shift
|
|
112
|
+
;;
|
|
113
|
+
--local)
|
|
114
|
+
EXECUTION_MODE="local"
|
|
115
|
+
shift
|
|
116
|
+
;;
|
|
117
|
+
--start-docker)
|
|
118
|
+
START_DOCKER=true
|
|
119
|
+
shift
|
|
120
|
+
;;
|
|
121
|
+
--verbose)
|
|
122
|
+
export VERBOSE=true
|
|
123
|
+
shift
|
|
124
|
+
;;
|
|
125
|
+
--help|-h)
|
|
126
|
+
show_usage
|
|
127
|
+
exit 0
|
|
128
|
+
;;
|
|
129
|
+
*)
|
|
130
|
+
error "Unknown option: $1 (use --help for usage)"
|
|
131
|
+
;;
|
|
132
|
+
esac
|
|
133
|
+
done
|
|
134
|
+
|
|
135
|
+
cd "$REPO_ROOT"
|
|
136
|
+
|
|
137
|
+
docker_compose_available() {
|
|
138
|
+
if command_exists docker-compose; then
|
|
139
|
+
return 0
|
|
140
|
+
fi
|
|
141
|
+
|
|
142
|
+
if command_exists docker && docker compose version >/dev/null 2>&1; then
|
|
143
|
+
return 0
|
|
144
|
+
fi
|
|
145
|
+
|
|
146
|
+
return 1
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
docker_compose() {
|
|
150
|
+
if command_exists docker-compose; then
|
|
151
|
+
docker-compose "$@"
|
|
152
|
+
else
|
|
153
|
+
docker compose "$@"
|
|
154
|
+
fi
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
docker_jekyll_running() {
|
|
158
|
+
docker_compose_available || return 1
|
|
159
|
+
|
|
160
|
+
local container_id
|
|
161
|
+
container_id="$(docker_compose ps -q jekyll 2>/dev/null || true)"
|
|
162
|
+
[[ -n "$container_id" ]] || return 1
|
|
163
|
+
|
|
164
|
+
docker inspect -f '{{.State.Running}}' "$container_id" 2>/dev/null | grep -q true
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
use_docker_for_jekyll() {
|
|
168
|
+
case "$EXECUTION_MODE" in
|
|
169
|
+
local)
|
|
170
|
+
return 1
|
|
171
|
+
;;
|
|
172
|
+
docker)
|
|
173
|
+
if [[ "$START_DOCKER" == "true" ]]; then
|
|
174
|
+
docker_compose_available || error "Docker Compose is required but was not found"
|
|
175
|
+
docker_compose up -d jekyll
|
|
176
|
+
fi
|
|
177
|
+
|
|
178
|
+
if docker_jekyll_running; then
|
|
179
|
+
return 0
|
|
180
|
+
fi
|
|
181
|
+
|
|
182
|
+
error "Docker mode requested but the jekyll service is not running. Run docker-compose up -d jekyll or pass --start-docker."
|
|
183
|
+
;;
|
|
184
|
+
auto)
|
|
185
|
+
if [[ "$START_DOCKER" == "true" ]]; then
|
|
186
|
+
docker_compose_available || error "--start-docker requested but Docker Compose was not found"
|
|
187
|
+
docker_compose up -d jekyll
|
|
188
|
+
return 0
|
|
189
|
+
fi
|
|
190
|
+
|
|
191
|
+
if docker_jekyll_running; then
|
|
192
|
+
return 0
|
|
193
|
+
fi
|
|
194
|
+
|
|
195
|
+
return 1
|
|
196
|
+
;;
|
|
197
|
+
*)
|
|
198
|
+
error "Invalid execution mode: $EXECUTION_MODE"
|
|
199
|
+
;;
|
|
200
|
+
esac
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
run_bundle_command() {
|
|
204
|
+
if use_docker_for_jekyll; then
|
|
205
|
+
debug "Running via Docker Compose: bundle exec $*"
|
|
206
|
+
docker_compose exec -T jekyll bundle exec "$@"
|
|
207
|
+
else
|
|
208
|
+
debug "Running locally: bundle exec $*"
|
|
209
|
+
bundle exec "$@"
|
|
210
|
+
fi
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
validate_core_files() {
|
|
214
|
+
step "Validating repository files..."
|
|
215
|
+
validate_git_repo
|
|
216
|
+
validate_required_files
|
|
217
|
+
require_command ruby "Install from https://www.ruby-lang.org/"
|
|
218
|
+
validate_gemspec
|
|
219
|
+
success "Repository files validated"
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
validate_version_consistency() {
|
|
223
|
+
step "Validating version consistency..."
|
|
224
|
+
|
|
225
|
+
ruby <<'RUBY'
|
|
226
|
+
require 'json'
|
|
227
|
+
require './lib/jekyll-theme-zer0/version'
|
|
228
|
+
|
|
229
|
+
ruby_version = JekyllThemeZer0::VERSION.to_s
|
|
230
|
+
spec = Gem::Specification.load('jekyll-theme-zer0.gemspec')
|
|
231
|
+
abort 'Unable to load jekyll-theme-zer0.gemspec' unless spec
|
|
232
|
+
|
|
233
|
+
versions = {
|
|
234
|
+
'lib/jekyll-theme-zer0/version.rb' => ruby_version,
|
|
235
|
+
'jekyll-theme-zer0.gemspec' => spec.version.to_s
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if File.file?('package.json')
|
|
239
|
+
versions['package.json'] = JSON.parse(File.read('package.json')).fetch('version').to_s
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
expected = versions.values.first
|
|
243
|
+
mismatches = versions.select { |_file, version| version != expected }
|
|
244
|
+
|
|
245
|
+
unless mismatches.empty?
|
|
246
|
+
warn_lines = versions.map { |file, version| " #{file}: #{version}" }
|
|
247
|
+
abort "Version mismatch detected:\n#{warn_lines.join("\n")}"
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
puts "Version consistent: #{expected}"
|
|
251
|
+
RUBY
|
|
252
|
+
|
|
253
|
+
success "Version files are consistent"
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
validate_yaml_files() {
|
|
257
|
+
step "Validating YAML configs and data..."
|
|
258
|
+
|
|
259
|
+
ruby <<'RUBY'
|
|
260
|
+
require 'date'
|
|
261
|
+
require 'yaml'
|
|
262
|
+
|
|
263
|
+
files = []
|
|
264
|
+
files.concat(%w[_config.yml _config_dev.yml _config_secrets_local.yml].select { |file| File.file?(file) })
|
|
265
|
+
files.concat(Dir.glob('_data/**/*.{yml,yaml}'))
|
|
266
|
+
files.concat(Dir.glob('.github/config/*.{yml,yaml}'))
|
|
267
|
+
files = files.uniq.sort
|
|
268
|
+
|
|
269
|
+
files.each do |file|
|
|
270
|
+
YAML.load_file(file, aliases: true, permitted_classes: [Date, Time])
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
puts "YAML files valid: #{files.length}"
|
|
274
|
+
RUBY
|
|
275
|
+
|
|
276
|
+
success "YAML configs and data are valid"
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
validate_configuration_contract() {
|
|
280
|
+
step "Validating configuration contract..."
|
|
281
|
+
|
|
282
|
+
ruby <<'RUBY'
|
|
283
|
+
require 'find'
|
|
284
|
+
require 'date'
|
|
285
|
+
require 'yaml'
|
|
286
|
+
|
|
287
|
+
def assert(condition, message)
|
|
288
|
+
abort message unless condition
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
def require_hash(data, key, file)
|
|
292
|
+
value = data[key]
|
|
293
|
+
assert(value.is_a?(Hash), "#{file}: #{key} must be a mapping")
|
|
294
|
+
value
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
def require_string(data, key, file)
|
|
298
|
+
value = data[key]
|
|
299
|
+
assert(value.is_a?(String) && !value.strip.empty?, "#{file}: #{key} must be a non-empty string")
|
|
300
|
+
value
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
root_file = '_config.yml'
|
|
304
|
+
dev_file = '_config_dev.yml'
|
|
305
|
+
root_config = YAML.load_file(root_file, aliases: true, permitted_classes: [Date, Time])
|
|
306
|
+
dev_config = YAML.load_file(dev_file, aliases: true, permitted_classes: [Date, Time])
|
|
307
|
+
|
|
308
|
+
assert(root_config['site_configured'] == true, "#{root_file}: site_configured must be true")
|
|
309
|
+
assert(root_config['remote_theme'] == 'bamr87/zer0-mistakes', "#{root_file}: remote_theme must stay on bamr87/zer0-mistakes")
|
|
310
|
+
assert(dev_config['remote_theme'] == false, "#{dev_file}: remote_theme must be false for local theme development")
|
|
311
|
+
assert(dev_config['theme'] == 'jekyll-theme-zer0', "#{dev_file}: theme must be jekyll-theme-zer0")
|
|
312
|
+
|
|
313
|
+
require_string(root_config, 'title', root_file)
|
|
314
|
+
require_string(root_config, 'url', root_file)
|
|
315
|
+
require_string(root_config, 'domain', root_file)
|
|
316
|
+
|
|
317
|
+
required_plugins = %w[
|
|
318
|
+
github-pages
|
|
319
|
+
jekyll-remote-theme
|
|
320
|
+
jekyll-feed
|
|
321
|
+
jekyll-sitemap
|
|
322
|
+
jekyll-seo-tag
|
|
323
|
+
jekyll-paginate
|
|
324
|
+
jekyll-relative-links
|
|
325
|
+
jekyll-redirect-from
|
|
326
|
+
]
|
|
327
|
+
plugins = Array(root_config['plugins'])
|
|
328
|
+
missing_plugins = required_plugins.reject { |plugin| plugins.include?(plugin) }
|
|
329
|
+
assert(missing_plugins.empty?, "#{root_file}: missing required plugins: #{missing_plugins.join(', ')}")
|
|
330
|
+
|
|
331
|
+
preview_images = require_hash(root_config, 'preview_images', root_file)
|
|
332
|
+
%w[enabled provider model size output_dir assets_prefix].each do |key|
|
|
333
|
+
assert(preview_images.key?(key), "#{root_file}: preview_images.#{key} is required")
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
posthog = require_hash(root_config, 'posthog', root_file)
|
|
337
|
+
assert([true, false].include?(posthog['enabled']), "#{root_file}: posthog.enabled must be boolean")
|
|
338
|
+
dev_posthog = require_hash(dev_config, 'posthog', dev_file)
|
|
339
|
+
assert(dev_posthog['enabled'] == false, "#{dev_file}: posthog.enabled must be false")
|
|
340
|
+
|
|
341
|
+
assert(root_config['collections_dir'] == 'pages', "#{root_file}: collections_dir must be pages")
|
|
342
|
+
collections = require_hash(root_config, 'collections', root_file)
|
|
343
|
+
required_collections = %w[pages posts docs quests hobbies notebooks notes quickstart about]
|
|
344
|
+
required_collections.each do |collection_name|
|
|
345
|
+
collection = collections[collection_name]
|
|
346
|
+
assert(collection.is_a?(Hash), "#{root_file}: collections.#{collection_name} must be configured")
|
|
347
|
+
assert(collection['output'] == true, "#{root_file}: collections.#{collection_name}.output must be true")
|
|
348
|
+
assert(collection['permalink'].is_a?(String), "#{root_file}: collections.#{collection_name}.permalink must be configured")
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
required_exclusions = [
|
|
352
|
+
'.obsidian',
|
|
353
|
+
'*.canvas',
|
|
354
|
+
'*.excalidraw.md',
|
|
355
|
+
'docs/',
|
|
356
|
+
'node_modules/',
|
|
357
|
+
'vendor/bundle/',
|
|
358
|
+
'test/',
|
|
359
|
+
'pages/_notes/_templates/'
|
|
360
|
+
]
|
|
361
|
+
|
|
362
|
+
{
|
|
363
|
+
root_file => Array(root_config['exclude']),
|
|
364
|
+
dev_file => Array(dev_config['exclude'])
|
|
365
|
+
}.each do |file, exclusions|
|
|
366
|
+
missing_exclusions = required_exclusions.reject { |entry| exclusions.include?(entry) }
|
|
367
|
+
assert(missing_exclusions.empty?, "#{file}: exclude is missing #{missing_exclusions.join(', ')}")
|
|
368
|
+
end
|
|
369
|
+
|
|
370
|
+
if File.file?('_config_secrets_local.yml')
|
|
371
|
+
tracked = system('git', 'ls-files', '--error-unmatch', '_config_secrets_local.yml', out: File::NULL, err: File::NULL)
|
|
372
|
+
assert(!tracked, '_config_secrets_local.yml must remain untracked and ignored')
|
|
373
|
+
end
|
|
374
|
+
|
|
375
|
+
ignored_dirs = %w[.git _site node_modules vendor .jekyll-cache .sass-cache]
|
|
376
|
+
config_like_files = []
|
|
377
|
+
|
|
378
|
+
Find.find('.') do |path|
|
|
379
|
+
relative_path = path.sub(%r{\A\./}, '')
|
|
380
|
+
|
|
381
|
+
if File.directory?(path)
|
|
382
|
+
Find.prune if ignored_dirs.include?(relative_path)
|
|
383
|
+
next
|
|
384
|
+
end
|
|
385
|
+
|
|
386
|
+
basename = File.basename(relative_path)
|
|
387
|
+
next unless basename.match?(/config.*\.ya?ml\z/) || basename.match?(/_config.*\.ya?ml\z/)
|
|
388
|
+
|
|
389
|
+
config_like_files << relative_path
|
|
390
|
+
end
|
|
391
|
+
|
|
392
|
+
classified_files = %w[
|
|
393
|
+
_config.yml
|
|
394
|
+
_config_dev.yml
|
|
395
|
+
_config_secrets_local.yml
|
|
396
|
+
pages/_about/settings/_config.yml
|
|
397
|
+
]
|
|
398
|
+
classified_files.concat(Dir.glob('_data/**/*config*.{yml,yaml}'))
|
|
399
|
+
|
|
400
|
+
unclassified_files = config_like_files.sort - classified_files.select { |file| File.file?(file) }
|
|
401
|
+
assert(unclassified_files.empty?, "Unclassified config-like YAML files found: #{unclassified_files.join(', ')}")
|
|
402
|
+
|
|
403
|
+
puts "Configuration contract valid"
|
|
404
|
+
RUBY
|
|
405
|
+
|
|
406
|
+
success "Configuration contract is valid"
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
validate_navigation_data() {
|
|
410
|
+
step "Validating navigation data shape..."
|
|
411
|
+
|
|
412
|
+
ruby <<'RUBY'
|
|
413
|
+
require 'date'
|
|
414
|
+
require 'yaml'
|
|
415
|
+
|
|
416
|
+
def validate_item(item, file, path)
|
|
417
|
+
unless item.is_a?(Hash)
|
|
418
|
+
raise "#{file}: #{path} must be a mapping"
|
|
419
|
+
end
|
|
420
|
+
|
|
421
|
+
title = item['title']
|
|
422
|
+
unless title.is_a?(String) && !title.strip.empty?
|
|
423
|
+
raise "#{file}: #{path}.title is required"
|
|
424
|
+
end
|
|
425
|
+
|
|
426
|
+
url = item['url']
|
|
427
|
+
unless url.nil? || url.is_a?(String)
|
|
428
|
+
raise "#{file}: #{path}.url must be a string when present"
|
|
429
|
+
end
|
|
430
|
+
|
|
431
|
+
icon = item['icon']
|
|
432
|
+
unless icon.nil? || icon.is_a?(String)
|
|
433
|
+
raise "#{file}: #{path}.icon must be a string when present"
|
|
434
|
+
end
|
|
435
|
+
|
|
436
|
+
children = item['children']
|
|
437
|
+
return if children.nil?
|
|
438
|
+
|
|
439
|
+
unless children.is_a?(Array)
|
|
440
|
+
raise "#{file}: #{path}.children must be a list"
|
|
441
|
+
end
|
|
442
|
+
|
|
443
|
+
children.each_with_index do |child, child_index|
|
|
444
|
+
validate_item(child, file, "#{path}.children[#{child_index}]")
|
|
445
|
+
end
|
|
446
|
+
end
|
|
447
|
+
|
|
448
|
+
files = Dir.glob('_data/navigation/*.yml').sort
|
|
449
|
+
files.each do |file|
|
|
450
|
+
data = YAML.load_file(file, aliases: true, permitted_classes: [Date, Time])
|
|
451
|
+
unless data.is_a?(Array)
|
|
452
|
+
raise "#{file}: root must be a list of navigation items"
|
|
453
|
+
end
|
|
454
|
+
|
|
455
|
+
data.each_with_index do |item, index|
|
|
456
|
+
validate_item(item, file, "items[#{index}]")
|
|
457
|
+
end
|
|
458
|
+
end
|
|
459
|
+
|
|
460
|
+
puts "Navigation files valid: #{files.length}"
|
|
461
|
+
RUBY
|
|
462
|
+
|
|
463
|
+
success "Navigation data is valid"
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
run_jekyll_build() {
|
|
467
|
+
if [[ "$SKIP_JEKYLL" == "true" ]]; then
|
|
468
|
+
warn "Skipping Jekyll build"
|
|
469
|
+
return 0
|
|
470
|
+
fi
|
|
471
|
+
|
|
472
|
+
step "Running Jekyll build..."
|
|
473
|
+
run_bundle_command jekyll build --config '_config.yml,_config_dev.yml'
|
|
474
|
+
success "Jekyll build passed"
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
run_jekyll_doctor() {
|
|
478
|
+
if [[ "$SKIP_DOCTOR" == "true" ]]; then
|
|
479
|
+
warn "Skipping Jekyll doctor"
|
|
480
|
+
return 0
|
|
481
|
+
fi
|
|
482
|
+
|
|
483
|
+
step "Running Jekyll doctor..."
|
|
484
|
+
run_bundle_command jekyll doctor
|
|
485
|
+
success "Jekyll doctor passed"
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
validate_compiled_assets() {
|
|
489
|
+
if [[ "$SKIP_ASSETS" == "true" ]]; then
|
|
490
|
+
warn "Skipping compiled asset checks"
|
|
491
|
+
return 0
|
|
492
|
+
fi
|
|
493
|
+
|
|
494
|
+
step "Validating compiled assets..."
|
|
495
|
+
|
|
496
|
+
local required_assets=(
|
|
497
|
+
"_site/assets/css/main.css"
|
|
498
|
+
"_site/assets/data/wiki-index.json"
|
|
499
|
+
"_site/feed.xml"
|
|
500
|
+
"_site/sitemap.xml"
|
|
501
|
+
)
|
|
502
|
+
|
|
503
|
+
local asset_path
|
|
504
|
+
for asset_path in "${required_assets[@]}"; do
|
|
505
|
+
if [[ ! -s "$asset_path" ]]; then
|
|
506
|
+
error "Compiled asset missing or empty: $asset_path"
|
|
507
|
+
fi
|
|
508
|
+
debug "Found compiled asset: $asset_path"
|
|
509
|
+
done
|
|
510
|
+
|
|
511
|
+
success "Compiled assets are present"
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
run_theme_tests() {
|
|
515
|
+
if [[ "$RUN_TESTS" != "true" ]]; then
|
|
516
|
+
debug "Theme tests not requested"
|
|
517
|
+
return 0
|
|
518
|
+
fi
|
|
519
|
+
|
|
520
|
+
step "Running canonical test suite..."
|
|
521
|
+
"$REPO_ROOT/scripts/bin/test"
|
|
522
|
+
success "Canonical test suite passed"
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
run_obsidian_tests() {
|
|
526
|
+
if [[ "$RUN_OBSIDIAN" != "true" ]]; then
|
|
527
|
+
debug "Obsidian tests not requested"
|
|
528
|
+
return 0
|
|
529
|
+
fi
|
|
530
|
+
|
|
531
|
+
step "Running Obsidian integration tests..."
|
|
532
|
+
if use_docker_for_jekyll; then
|
|
533
|
+
docker_compose exec -T jekyll ./test/test_obsidian.sh
|
|
534
|
+
else
|
|
535
|
+
"$REPO_ROOT/test/test_obsidian.sh"
|
|
536
|
+
fi
|
|
537
|
+
success "Obsidian integration tests passed"
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
run_html_validation() {
|
|
541
|
+
if [[ "$RUN_HTMLPROOFER" != "true" ]]; then
|
|
542
|
+
debug "HTMLProofer not requested"
|
|
543
|
+
return 0
|
|
544
|
+
fi
|
|
545
|
+
|
|
546
|
+
step "Running HTMLProofer..."
|
|
547
|
+
run_bundle_command htmlproofer _site --disable-external --allow-hash-href
|
|
548
|
+
success "HTMLProofer passed"
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
main() {
|
|
552
|
+
print_header "Preflight Validation"
|
|
553
|
+
|
|
554
|
+
validate_core_files
|
|
555
|
+
echo ""
|
|
556
|
+
|
|
557
|
+
validate_version_consistency
|
|
558
|
+
echo ""
|
|
559
|
+
|
|
560
|
+
validate_yaml_files
|
|
561
|
+
echo ""
|
|
562
|
+
|
|
563
|
+
validate_configuration_contract
|
|
564
|
+
echo ""
|
|
565
|
+
|
|
566
|
+
validate_navigation_data
|
|
567
|
+
echo ""
|
|
568
|
+
|
|
569
|
+
run_jekyll_build
|
|
570
|
+
echo ""
|
|
571
|
+
|
|
572
|
+
run_jekyll_doctor
|
|
573
|
+
echo ""
|
|
574
|
+
|
|
575
|
+
validate_compiled_assets
|
|
576
|
+
echo ""
|
|
577
|
+
|
|
578
|
+
run_theme_tests
|
|
579
|
+
run_obsidian_tests
|
|
580
|
+
run_html_validation
|
|
581
|
+
|
|
582
|
+
print_summary "Validation Complete" \
|
|
583
|
+
"Repository files: pass" \
|
|
584
|
+
"Version consistency: pass" \
|
|
585
|
+
"YAML, config contract, and navigation data: pass" \
|
|
586
|
+
"Jekyll build: $([[ "$SKIP_JEKYLL" == "true" ]] && echo skipped || echo pass)" \
|
|
587
|
+
"Jekyll doctor: $([[ "$SKIP_DOCTOR" == "true" ]] && echo skipped || echo pass)" \
|
|
588
|
+
"Compiled assets: $([[ "$SKIP_ASSETS" == "true" ]] && echo skipped || echo pass)" \
|
|
589
|
+
"Theme tests: $([[ "$RUN_TESTS" == "true" ]] && echo pass || echo skipped)" \
|
|
590
|
+
"Obsidian tests: $([[ "$RUN_OBSIDIAN" == "true" ]] && echo pass || echo skipped)" \
|
|
591
|
+
"HTMLProofer: $([[ "$RUN_HTMLPROOFER" == "true" ]] && echo pass || echo skipped)"
|
|
592
|
+
|
|
593
|
+
success "Preflight validation passed"
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
main "$@"
|
data/scripts/lib/README.md
CHANGED
|
@@ -47,6 +47,22 @@ source "$(dirname "$0")/lib/validation.sh"
|
|
|
47
47
|
validate_environment false false # skip_publish=false, require_gh=false
|
|
48
48
|
```
|
|
49
49
|
|
|
50
|
+
### ✅ `scripts/bin/validate` - Preflight Validation
|
|
51
|
+
|
|
52
|
+
Canonical command for local and CI preflight checks. It composes the release
|
|
53
|
+
validation helpers with project-specific checks for version consistency, YAML
|
|
54
|
+
configuration/data files, active configuration contracts, config-file
|
|
55
|
+
classification, navigation data shape, Jekyll build/doctor, compiled assets,
|
|
56
|
+
and optional test suites.
|
|
57
|
+
|
|
58
|
+
**Usage:**
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
./scripts/bin/validate --quick # Host-only CI fast checks
|
|
62
|
+
./scripts/bin/validate --start-docker # Build + doctor via Docker Compose
|
|
63
|
+
./scripts/bin/validate --full # Include tests, Obsidian, HTMLProofer
|
|
64
|
+
```
|
|
65
|
+
|
|
50
66
|
### 📝 `version.sh` - Version Management
|
|
51
67
|
|
|
52
68
|
Read, calculate, and update semantic versions.
|
data/scripts/validate
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# ============================================================================
|
|
4
|
+
# WRAPPER: This script forwards to scripts/bin/validate
|
|
5
|
+
#
|
|
6
|
+
# The canonical location is scripts/bin/validate. This wrapper exists for
|
|
7
|
+
# backward compatibility with VS Code tasks, Make targets, and local habits.
|
|
8
|
+
# ============================================================================
|
|
9
|
+
|
|
10
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
11
|
+
exec "$SCRIPT_DIR/bin/validate" "$@"
|
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: 1.
|
|
4
|
+
version: 1.4.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: 2026-04-
|
|
11
|
+
date: 2026-04-25 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: jekyll
|
|
@@ -368,6 +368,7 @@ files:
|
|
|
368
368
|
- scripts/bin/install
|
|
369
369
|
- scripts/bin/release
|
|
370
370
|
- scripts/bin/test
|
|
371
|
+
- scripts/bin/validate
|
|
371
372
|
- scripts/build
|
|
372
373
|
- scripts/convert-notebooks.sh
|
|
373
374
|
- scripts/docker-publish
|
|
@@ -442,6 +443,7 @@ files:
|
|
|
442
443
|
- scripts/utils/analyze-commits
|
|
443
444
|
- scripts/utils/fix-markdown
|
|
444
445
|
- scripts/utils/setup
|
|
446
|
+
- scripts/validate
|
|
445
447
|
- scripts/vendor-install.sh
|
|
446
448
|
homepage: https://github.com/bamr87/zer0-mistakes
|
|
447
449
|
licenses:
|