/source/thepeoples-io/blob/scripts/build-apex-catalog.sh/
build-apex-catalog.sh
#!/usr/bin/env bash
# Validate catalog inputs, generate Zola content, and build the apex site.
set -euo pipefail
script_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
repo_root="$(cd -- "${script_dir}/.." && pwd)"
zola_pin_file="${repo_root}/tools/zola-version.sh"
# shellcheck disable=SC1090
source "$zola_pin_file"
site_root="${repo_root}/sites/apex-src"
content_root="${site_root}/content"
static_root="${site_root}/static"
projects_content_dir="${content_root}/projects"
catalog_root="${repo_root}/sites/catalog"
fixture_root="${CATALOG_FIXTURE_ROOT:-${catalog_root}/fixtures/projects}"
generated_root="${CATALOG_GENERATED_ROOT:-${catalog_root}/generated}"
design_content_dir="${content_root}/designs"
source_content_dir="${content_root}/source"
verify_content_dir="${content_root}/verify"
source_snapshot_root="${CATALOG_SOURCE_SNAPSHOT_ROOT:-${catalog_root}/source-snapshots}"
registry_projects="${PROJECT_REGISTRY_FILE:-${repo_root}/sites/registry/projects.json}"
registry_sites="${SITE_REGISTRY_FILE:-${repo_root}/sites/registry/static-sites.json}"
production="${PRODUCTION_BUILD:-true}"
use_container="${USE_ZOLA_CONTAINER:-true}"
max_source_file_bytes="${MAX_SOURCE_FILE_BYTES:-200000}"
zola_binary=""
fail() {
printf '[ERROR] %s\n' "$*" >&2
exit 1
}
log() {
printf '[INFO] %s\n' "$*"
}
require_command() {
command -v "$1" >/dev/null 2>&1 || fail "$1 is required"
}
json_string() {
local file="$1"
local expr="$2"
jq -er "$expr | strings" "$file"
}
html_escape() {
jq -Rr @html
}
path_depth() {
local path="$1"
if [[ -z "$path" ]]; then
printf '0'
else
awk -F/ '{print NF - 1}' <<< "$path"
fi
}
parent_dir_path() {
local path="$1"
if [[ "$path" == */* ]]; then
printf '%s' "${path%/*}"
else
printf ''
fi
}
plural() {
local count="$1"
local singular="$2"
if [[ "$count" == "1" ]]; then
printf '1 %s' "$singular"
else
printf '%s %ss' "$count" "$singular"
fi
}
dir_summary() {
local dir="$1"
local dir_count file_count
dir_count="$(find "$dir" -mindepth 1 -maxdepth 1 -type d | wc -l | tr -d ' ')"
file_count="$(find "$dir" -mindepth 1 -maxdepth 1 -type f | wc -l | tr -d ' ')"
if [[ "$dir_count" == "0" && "$file_count" == "0" ]]; then
printf 'empty'
elif [[ "$dir_count" == "0" ]]; then
plural "$file_count" "file"
elif [[ "$file_count" == "0" ]]; then
plural "$dir_count" "dir"
else
printf '%s / %s' "$(plural "$dir_count" "dir")" "$(plural "$file_count" "file")"
fi
}
source_breadcrumb_html() {
local slug="$1"
local kind="$2"
local rel_path="$3"
local accum part i
local -a parts
printf '<nav class="source-breadcrumb" aria-label="Source path">\n'
printf '<a href="/source/%s/">source</a><span>/</span><a href="/source/%s/">%s</a>\n' "$slug" "$slug" "$slug"
[[ -z "$rel_path" ]] && {
printf '</nav>\n'
return
}
IFS='/' read -r -a parts <<< "$rel_path"
accum=""
for i in "${!parts[@]}"; do
part="${parts[$i]}"
[[ -n "$part" ]] || continue
if [[ -n "$accum" ]]; then
accum="${accum}/${part}"
else
accum="$part"
fi
printf '<span>/</span>'
if [[ "$i" == "$((${#/*parts[@]} - 1))" ]]; then
if [[ "$kind" == "tree" ]]; then
printf '<span>%s/</span>\n' "$(printf '%s' "$part" | html_escape)"
else
printf '<span>%s</span>\n' "$(printf '%s' "$part" | html_escape)"
fi
else
printf '<a href="/source/%s/tree/%s/">%s</a>\n' "$slug" "$accum" "$(printf '%s' "$part" | html_escape)"
fi
done
printf '</nav>\n'
}
escape_zola_shortcode_markers() {
sed \
-e 's/{{ /{{/*\/\*/g' \
-e 's/ }}/\*\/*/}}/g' \
-e 's/{% /{%/*\/\*/g' \
-e 's/ %}/\*\/*/%}/g' \
-e 's/{# /{#/*\/\*/g' \
-e 's/ #}/\*\/*/#}/g'
}
detect_language() {
case "$1" in
*.bash|*.sh) printf 'bash' ;;
*.html|*.htm) printf 'html' ;;
*.md|*.markdown) printf 'md' ;;
*.json) printf 'json' ;;
*.toml) printf 'toml' ;;
*.tf) printf 'hcl' ;;
*.yml|*.yaml) printf 'yaml' ;;
*) printf 'text' ;;
esac
}
validate_no_htmlish_text() {
local file="$1"
if jq -er '.. | strings | select(test("<[A-Za-z!/][^>]*>"))' "$file" >/dev/null; then
fail "HTML-like content is not allowed in manifest strings: ${file}"
fi
}
validate_registry_sites() {
jq -e '
(keys - ["schema_version", "sites"] | length == 0)
and .schema_version == 1
and (.sites | type == "array")
and (.sites | length > 0)
and all(.sites[]; (
(keys - [
"site_id",
"aliases",
"bucket",
"cloudfront_distribution_id",
"openbao_role",
"allowed_source_repository",
"default_build_dir"
] | length == 0)
and (.site_id | test("^[a-z0-9][a-z0-9-]*$"))
and (.aliases | type == "array" and length > 0)
and (.bucket | type == "string" and length > 0)
and (.cloudfront_distribution_id | type == "string" and length > 0)
and (.openbao_role | type == "string" and length > 0)
and (.allowed_source_repository | type == "string" and length > 0)
and (.default_build_dir | type == "string" and length > 0)
))
' "$registry_sites" >/dev/null || fail "invalid static site registry: ${registry_sites}"
}
validate_registry_projects() {
jq -e '
(keys - ["schema_version", "projects"] | length == 0)
and .schema_version == 1
and (.projects | type == "array")
and (.projects | length > 0)
and all(.projects[]; . as $project | (
(keys - [
"project_id",
"slug",
"allowed_source_repository",
"artifact_prefix",
"openbao_role",
"mirror_urls",
"visibility"
] | length == 0)
and (.project_id | test("^[a-z0-9][a-z0-9-]*$"))
and (.slug | test("^[a-z0-9][a-z0-9-]*$"))
and (.allowed_source_repository | type == "string" and length > 0)
and (.artifact_prefix | startswith("projects/" + $project.project_id + "/"))
and (.openbao_role | type == "string" and length > 0)
and (.mirror_urls | type == "array")
and (.visibility == "public" or .visibility == "docs-only" or .visibility == "private" or .visibility == "planned")
))
' "$registry_projects" >/dev/null || fail "invalid project registry: ${registry_projects}"
}
registry_project() {
local project_id="$1"
jq -er --arg project_id "$project_id" '.projects[] | select(.project_id == $project_id)' "$registry_projects"
}
validate_project_manifest() {
local file="$1"
validate_no_htmlish_text "$file"
jq -e '
(keys - [
"schema_version",
"project_id",
"slug",
"name",
"summary",
"summary_long",
"status",
"license",
"visibility",
"owner",
"contact",
"mirror_urls",
"docs_entrypoints",
"logo",
"accent",
"screenshots",
"featured",
"sort_weight",
"external_links",
"public_source_snapshot",
"public_docs_snapshot",
"topics"
] | length == 0)
and .schema_version == 1
and (.project_id | test("^[a-z0-9][a-z0-9-]*$"))
and (.slug | test("^[a-z0-9][a-z0-9-]*$"))
and (.name | type == "string" and length > 0)
and (.summary | type == "string" and length > 0)
and (.status == "active" or .status == "experimental" or .status == "planned" or .status == "archived" or .status == "infrastructure")
and ((has("license") | not) or (.license | type == "string" and length > 0))
and (.visibility == "public" or .visibility == "docs-only" or .visibility == "private" or .visibility == "planned")
and (.topics | type == "array" and length > 0)
and all(.topics[]; test("^[a-z0-9][a-z0-9-]*$"))
and ((.visibility != "docs-only") or (has("public_source_snapshot") | not))
and ((.visibility != "docs-only") or has("public_docs_snapshot"))
and ((has("public_docs_snapshot") | not) or (
(.public_docs_snapshot | keys - ["snapshot_id", "manifest_path", "display"] | length == 0)
and (.public_docs_snapshot.snapshot_id | type == "string" and length > 0)
and (.public_docs_snapshot.manifest_path | type == "string" and length > 0)
and .public_docs_snapshot.display == "project-docs"
))
' "$file" >/dev/null || fail "invalid project manifest: ${file}"
local project_id
project_id="$(json_string "$file" '.project_id')"
registry_project "$project_id" >/dev/null || fail "project not allowlisted in registry: ${project_id}"
}
validate_release_manifest() {
local file="$1"
local project_id
local version
local artifact_prefix
local release_prefix
validate_no_htmlish_text "$file"
jq -e '
(keys - [
"schema_version",
"project_id",
"version",
"commit",
"built_at",
"changelog",
"artifacts",
"sbom",
"signatures",
"attestations",
"provenance"
] | length == 0)
and .schema_version == 1
and (.project_id | test("^[a-z0-9][a-z0-9-]*$"))
and (.version | test("^v?[0-9][A-Za-z0-9._-]*$"))
and (.commit | test("^[0-9a-f]{40}$"))
and (.built_at | test("^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z$"))
and (.changelog | type == "string")
and (.artifacts | type == "array")
and (.signatures | type == "array")
and (.attestations | type == "array")
and ((has("provenance") | not) or (.provenance | type == "array"))
and all((.artifacts + .signatures + .attestations + (.provenance // []))[]?; (
(keys - ["name", "path", "sha256", "content_type"] | length == 0)
and (.name | type == "string" and length > 0)
and (.path | type == "string" and length > 0)
and (.sha256 | test("^[0-9a-f]{64}$"))
and (.content_type | type == "string" and length > 0)
))
and ((has("sbom") | not) or (
(.sbom | keys - ["format", "path", "sha256"] | length == 0)
and (.sbom.format == "spdx-json" or .sbom.format == "cyclonedx-json")
and (.sbom.path | type == "string" and length > 0)
and (.sbom.sha256 | test("^[0-9a-f]{64}$"))
))
' "$file" >/dev/null || fail "invalid release manifest: ${file}"
project_id="$(json_string "$file" '.project_id')"
version="$(json_string "$file" '.version')"
artifact_prefix="$(registry_project "$project_id" | jq -er '.artifact_prefix')"
release_prefix="${artifact_prefix}releases/${version}/"
jq -e --arg prefix "$release_prefix" '
all((.artifacts + .signatures + .attestations + (.provenance // []))[]?; .path | startswith($prefix))
and ((has("sbom") | not) or (.sbom.path | startswith($prefix)))
' "$file" >/dev/null || fail "release references artifact outside immutable release prefix: ${project_id} ${version}"
}
path_matches_pattern() {
local rel="$1"
local pattern="$2"
case "$pattern" in
*/)
[[ "$rel" == "${pattern}"* ]]
;;
*/*)
[[ "$rel" == $pattern ]]
;;
*)
[[ "$rel" == $pattern || "$rel" == */"$pattern" ]]
;;
esac
}
path_is_listed() {
local rel="$1"
local list_file="$2"
local pattern
while IFS= read -r pattern; do
[[ -n "$pattern" ]] || continue
if path_matches_pattern "$rel" "$pattern"; then
return 0
fi
done < "$list_file"
return 1
}
verify_release_digests() {
local file="$1"
local release_dir
release_dir="$(dirname "$file")"
jq -r '(.artifacts + .signatures + .attestations + (.provenance // []))[]? | [.path, .sha256] | @tsv' "$file" |
while IFS=$'\t' read -r path expected; do
local artifact="${release_dir}/$(basename "$path")"
[[ -f "$artifact" ]] || fail "declared artifact missing: ${artifact}"
actual="$(sha256sum "$artifact" | awk '{print $1}')"
[[ "$actual" == "$expected" ]] || fail "digest mismatch for ${artifact}"
done
if jq -e 'has("sbom")' "$file" >/dev/null; then
local sbom_path
local expected
local actual
sbom_path="$(jq -er '.sbom.path' "$file")"
expected="$(jq -er '.sbom.sha256' "$file")"
artifact="${release_dir}/$(basename "$sbom_path")"
[[ -f "$artifact" ]] || fail "declared SBOM missing: ${artifact}"
actual="$(sha256sum "$artifact" | awk '{print $1}')"
[[ "$actual" == "$expected" ]] || fail "digest mismatch for ${artifact}"
fi
}
reset_generated_content() {
mkdir -p "$projects_content_dir" "$design_content_dir" "$source_content_dir" "$generated_root" "$static_root"
find "$projects_content_dir" -mindepth 1 -maxdepth 1 -type d -exec rm -rf {} +
find "$source_content_dir" -mindepth 1 -maxdepth 1 -exec rm -rf {} +
find "$generated_root" -mindepth 1 -maxdepth 1 -exec rm -rf {} +
find "$design_content_dir" -mindepth 1 -maxdepth 1 -type f -name '*.md' ! -name '_index.md' -delete
rm -f "${content_root}/search.md" "${static_root}/search-index.json"
}
append_search_entry() {
local output="$1"
local type="$2"
local title="$3"
local url="$4"
local summary="$5"
local tags="${6:-}"
jq -cn \
--arg type "$type" \
--arg title "$title" \
--arg url "$url" \
--arg summary "$summary" \
--arg tags "$tags" \
'{type: $type, title: $title, url: $url, summary: $summary, tags: ($tags | split(" ") | map(select(length > 0)))}' \
>> "$output"
}
project_release_count() {
local project_id="$1"
if [[ -d "${fixture_root}/${project_id}/releases" ]]; then
find "${fixture_root}/${project_id}/releases" -name release.json -print | wc -l | tr -d ' '
else
printf '0'
fi
}
project_is_rendered() {
local project_file="$1"
local visibility
visibility="$(json_string "$project_file" '.visibility')"
[[ "$production" != "true" || "$visibility" == "public" || "$visibility" == "docs-only" ]]
}
project_source_root() {
local project_file="$1"
local manifest root_rel root_abs snapshot_abs
jq -e 'has("public_source_snapshot")' "$project_file" >/dev/null || return 1
manifest="${repo_root}/$(json_string "$project_file" '.public_source_snapshot.manifest_path')"
[[ -f "$manifest" ]] || return 1
root_rel="$(json_string "$manifest" '.root')"
root_abs="$(realpath -m "${repo_root}/${root_rel}")"
snapshot_abs="$(realpath -m "$source_snapshot_root")"
[[ "$root_abs" == "$snapshot_abs"/* ]] || return 1
printf '%s\n' "$root_abs"
}
project_docs_root() {
local project_file="$1"
local manifest root_rel root_abs snapshot_abs
jq -e 'has("public_docs_snapshot")' "$project_file" >/dev/null || return 1
manifest="${repo_root}/$(json_string "$project_file" '.public_docs_snapshot.manifest_path')"
[[ -f "$manifest" ]] || return 1
root_rel="$(json_string "$manifest" '.root')"
root_abs="$(realpath -m "${repo_root}/${root_rel}")"
snapshot_abs="$(realpath -m "$source_snapshot_root")"
[[ "$root_abs" == "$snapshot_abs"/* ]] || return 1
printf '%s\n' "$root_abs"
}
detect_license_id() {
local license_file="$1"
if grep -Eiq 'GNU AFFERO GENERAL PUBLIC LICENSE|Affero General Public License' "$license_file"; then
printf 'AGPL-3.0-only'
elif grep -Eiq 'GNU GENERAL PUBLIC LICENSE' "$license_file"; then
printf 'GPL-3.0-only'
elif grep -Eiq 'Mozilla Public License Version 2\.0|MPL-2\.0' "$license_file"; then
printf 'MPL-2.0'
elif grep -Eiq 'Apache License.*Version 2\.0|Apache-2\.0' "$license_file"; then
printf 'Apache-2.0'
elif grep -Eiq 'MIT License|Permission is hereby granted, free of charge' "$license_file"; then
printf 'MIT'
else
printf 'LICENSE.txt'
fi
}
project_license() {
local project_file="$1"
local root_abs license_file
if root_abs="$(project_source_root "$project_file")"; then
license_file="${root_abs}/LICENSE.txt"
[[ -f "$license_file" ]] && detect_license_id "$license_file" && return 0
fi
printf 'AGPL-3.0-only'
}
write_project_pages() {
local project_file="$1"
local project_id slug name summary summary_long status license visibility snapshot_id docs_snapshot_id release_count
project_id="$(json_string "$project_file" '.project_id')"
slug="$(json_string "$project_file" '.slug')"
name="$(json_string "$project_file" '.name')"
summary="$(json_string "$project_file" '.summary')"
summary_long="$(jq -er '.summary_long // .summary' "$project_file")"
status="$(json_string "$project_file" '.status')"
license="$(project_license "$project_file")"
visibility="$(json_string "$project_file" '.visibility')"
snapshot_id="$(jq -er '.public_source_snapshot.snapshot_id // "none"' "$project_file")"
docs_snapshot_id="$(jq -er '.public_docs_snapshot.snapshot_id // "none"' "$project_file")"
release_count="$(project_release_count "$project_id")"
if ! project_is_rendered "$project_file"; then
log "Skipping non-public project in production build: ${project_id}"
return
fi
local project_dir="${projects_content_dir}/${slug}"
mkdir -p "${project_dir}/releases" "${project_dir}/docs"
{
printf '+++\n'
printf 'title = %s\n' "$(jq -n --arg v "$name" '$v')"
printf 'description = %s\n' "$(jq -n --arg v "$summary" '$v')"
printf '[extra]\nkind = "project"\n'
printf '+++\n\n'
printf '<p class="lede">%s</p>\n\n' "$summary"
printf '<div class="project-console">\n'
printf '<section class="project-console__main">\n'
printf '<p>%s</p>\n\n' "$summary_long"
printf '<nav class="action-row" aria-label="Project actions">\n'
if [[ "$visibility" != "docs-only" ]] && jq -e 'has("public_source_snapshot")' "$project_file" >/dev/null; then
printf '<a href="/source/%s/">open source browser</a>\n' "$slug"
fi
if jq -e 'has("public_docs_snapshot")' "$project_file" >/dev/null; then
printf '<a href="/projects/%s/docs/">open documentation</a>\n' "$slug"
fi
if [[ "$release_count" == "0" ]]; then
printf '<a href="/projects/%s/releases/">release status</a>\n' "$slug"
else
printf '<a href="/projects/%s/releases/">view releases</a>\n' "$slug"
fi
printf '<a href="/verify/provenance/">check build inputs</a>\n'
printf '<a href="/search/">search catalog</a>\n'
printf '</nav>\n'
printf '</section>\n'
printf '<aside class="fact-block project-console__facts">\n'
printf '<h2>Project</h2>\n'
printf '<dl class="meta-list">\n'
printf '<dt>project</dt><dd><code>%s</code></dd>\n' "$project_id"
printf '<dt>status</dt><dd><code>%s</code></dd>\n' "$status"
printf '<dt>license</dt><dd><code>%s</code></dd>\n' "$license"
printf '<dt>visibility</dt><dd><code>%s</code></dd>\n' "$visibility"
if [[ "$snapshot_id" != "none" ]]; then
printf '<dt>source copy</dt><dd><code>%s</code></dd>\n' "$snapshot_id"
fi
if [[ "$docs_snapshot_id" != "none" ]]; then
printf '<dt>docs copy</dt><dd><code>%s</code></dd>\n' "$docs_snapshot_id"
fi
if [[ "$release_count" == "0" ]]; then
printf '<dt>releases</dt><dd><code>none</code></dd>\n'
else
printf '<dt>releases</dt><dd><code>%s</code></dd>\n' "$release_count"
fi
printf '</dl>\n'
printf '</aside>\n'
printf '</div>\n\n'
if jq -e 'has("public_docs_snapshot")' "$project_file" >/dev/null; then
write_project_docs_readme "$project_file"
write_project_docs_listing "$project_file"
else
write_project_readme "$project_file"
fi
printf '## Design notes\n\n'
printf '<ul class="link-list compact">\n'
jq -r '.docs_entrypoints[]? | "<li><a href=\"" + .url + "\">" + .title + "</a></li>"' "$project_file"
printf '</ul>\n'
} > "${project_dir}/_index.md"
{
printf '+++\n'
printf 'title = "%s Releases"\n' "$name"
printf 'sort_by = "title"\n'
printf '[extra]\nkind = "releases"\n'
if [[ "$release_count" == "0" ]]; then
printf '+++\n\n'
printf 'No public releases have been published yet.\n'
else
printf '+++\n'
fi
} > "${project_dir}/releases/_index.md"
if [[ -d "${fixture_root}/${project_id}/releases" ]]; then
find "${fixture_root}/${project_id}/releases" -name release.json -print | sort |
while IFS= read -r release_file; do
validate_release_manifest "$release_file"
verify_release_digests "$release_file"
write_release_page "$project_file" "$release_file" "${project_dir}/releases"
done
fi
write_project_docs_pages "$project_file" "${project_dir}/docs"
}
write_project_readme() {
local project_file="$1"
local manifest root_abs readme_file
jq -e 'has("public_source_snapshot")' "$project_file" >/dev/null || return 0
manifest="${repo_root}/$(json_string "$project_file" '.public_source_snapshot.manifest_path')"
[[ -f "$manifest" ]] || return 0
root_abs="${repo_root}/$(json_string "$manifest" '.root')"
readme_file="${root_abs}/README.md"
write_readme_section "$readme_file" "project-readme"
}
write_project_docs_readme() {
local project_file="$1"
local root_abs
root_abs="$(project_docs_root "$project_file")" || return 0
write_readme_section "${root_abs}/README.md" "project-readme"
}
write_project_docs_listing() {
local project_file="$1"
local root_abs docs_dir slug
root_abs="$(project_docs_root "$project_file")" || return 0
docs_dir="${root_abs}/docs"
[[ -d "$docs_dir" ]] || return 0
slug="$(json_string "$project_file" '.slug')"
printf '## Documentation\n\n'
printf '<ul class="link-list compact">\n'
find "$docs_dir" -type f \( -name '*.md' -o -name '*.markdown' \) -print | sort |
while IFS= read -r doc_file; do
local rel doc_slug title
rel="${doc_file#${docs_dir}/}"
doc_slug="${rel%.*}"
title="$(sed -n '1s/^# //p' "$doc_file")"
[[ -n "$title" ]] || title="$rel"
printf '<li><a href="/projects/%s/docs/%s/">%s</a></li>\n' "$slug" "$doc_slug" "$(printf '%s' "$title" | html_escape)"
done
printf '</ul>\n\n'
}
write_project_docs_pages() {
local project_file="$1"
local docs_content_dir="$2"
local root_abs docs_dir name slug manifest project_id
jq -e 'has("public_docs_snapshot")' "$project_file" >/dev/null || return 0
project_id="$(json_string "$project_file" '.project_id')"
manifest="${repo_root}/$(json_string "$project_file" '.public_docs_snapshot.manifest_path')"
[[ -f "$manifest" ]] || fail "public docs snapshot manifest missing: ${manifest}"
validate_docs_snapshot "$manifest"
[[ "$(json_string "$manifest" '.project_id')" == "$project_id" ]] ||
fail "public docs snapshot project_id does not match project manifest: ${project_id}"
[[ "$(json_string "$manifest" '.snapshot_id')" == "$(json_string "$project_file" '.public_docs_snapshot.snapshot_id')" ]] ||
fail "public docs snapshot id does not match project manifest: ${project_id}"
root_abs="$(project_docs_root "$project_file")" || fail "public docs snapshot root missing"
docs_dir="${root_abs}/docs"
name="$(json_string "$project_file" '.name')"
slug="$(json_string "$project_file" '.slug')"
mkdir -p "$docs_content_dir"
{
printf '+++\n'
printf 'title = "%s Documentation"\n' "$name"
printf 'sort_by = "title"\n'
printf '[extra]\nkind = "docs"\n'
printf '+++\n\n'
write_project_docs_listing "$project_file"
} > "${docs_content_dir}/_index.md"
[[ -d "$docs_dir" ]] || return 0
find "$docs_dir" -type f \( -name '*.md' -o -name '*.markdown' \) -print | sort |
while IFS= read -r doc_file; do
local rel doc_slug page_dir title
rel="${doc_file#${docs_dir}/}"
doc_slug="${rel%.*}"
page_dir="${docs_content_dir}/${doc_slug}"
title="$(sed -n '1s/^# //p' "$doc_file")"
[[ -n "$title" ]] || title="$rel"
mkdir -p "$page_dir"
{
printf '+++\n'
printf 'title = %s\n' "$(jq -n --arg v "$title" '$v')"
printf 'path = "projects/%s/docs/%s"\n' "$slug" "$doc_slug"
printf '[extra]\nkind = "docs"\n'
printf '+++\n\n'
escape_zola_shortcode_markers < "$doc_file"
} > "${page_dir}/index.md"
done
}
write_readme_section() {
local readme_file="$1"
local section_class="$2"
[[ -f "$readme_file" ]] || return 0
printf '<section class="%s">\n\n' "$section_class"
printf '## README.md\n\n'
escape_zola_shortcode_markers < "$readme_file"
printf '\n</section>\n\n'
}
write_release_page() {
local project_file="$1"
local release_file="$2"
local releases_dir="$3"
local name project_id version commit built_at changelog release_dir
name="$(json_string "$project_file" '.name')"
project_id="$(json_string "$release_file" '.project_id')"
version="$(json_string "$release_file" '.version')"
commit="$(json_string "$release_file" '.commit')"
built_at="$(json_string "$release_file" '.built_at')"
changelog="$(json_string "$release_file" '.changelog')"
release_dir="${releases_dir}/${version}"
mkdir -p "$release_dir"
{
printf '+++\n'
printf 'title = "%s %s"\n' "$name" "$version"
printf 'path = "projects/%s/releases/%s"\n' "$(json_string "$project_file" '.slug')" "$version"
printf '[extra]\nkind = "release"\n'
printf '+++\n\n'
printf '%s\n\n' "$changelog"
printf '<div class="fact-block">\n'
printf '<h2>Release</h2>\n'
printf '<dl class="meta-list">\n'
printf '<dt>project</dt><dd><code>%s</code></dd>\n' "$project_id"
printf '<dt>version</dt><dd><code>%s</code></dd>\n' "$version"
if [[ "$commit" != "0000000000000000000000000000000000000000" ]]; then
printf '<dt>commit</dt><dd><code>%s</code></dd>\n' "$commit"
fi
printf '<dt>built_at</dt><dd><code>%s</code></dd>\n' "$built_at"
printf '</dl>\n'
printf '</div>\n\n'
if jq -e '(.artifacts | length) > 0 or has("sbom")' "$release_file" >/dev/null; then
printf '## Files\n\n'
if jq -e '(.artifacts | length) > 0' "$release_file" >/dev/null; then
printf '| name | sha256 | content type |\n'
printf '| --- | --- | --- |\n'
jq -r '.artifacts[] | "| `" + .name + "` | `" + .sha256 + "` | `" + .content_type + "` |"' "$release_file"
fi
if jq -e 'has("sbom")' "$release_file" >/dev/null; then
printf -- '- [SBOM](/projects/%s/releases/%s/sbom/)\n' "$(json_string "$project_file" '.slug')" "$version"
fi
printf '\n'
fi
if jq -e '((.provenance // []) | length) > 0' "$release_file" >/dev/null; then
printf '## Provenance\n\n'
printf '| name | sha256 | content type |\n'
printf '| --- | --- | --- |\n'
jq -r '.provenance[] | "| `" + .name + "` | `" + .sha256 + "` | `" + .content_type + "` |"' "$release_file"
printf '\n'
fi
} > "${release_dir}/index.md"
if jq -e 'has("sbom")' "$release_file" >/dev/null; then
mkdir -p "${release_dir}/sbom"
{
printf '+++\n'
printf 'title = "%s %s SBOM"\n' "$name" "$version"
printf 'path = "projects/%s/releases/%s/sbom"\n' "$(json_string "$project_file" '.slug')" "$version"
printf '[extra]\nkind = "sbom"\n'
printf '+++\n\n'
printf '<div class="fact-block">\n'
printf '<h2>SBOM</h2>\n'
printf '<dl class="meta-list">\n'
jq -r '.sbom | "<dt>format</dt><dd><code>" + .format + "</code></dd>\n<dt>path</dt><dd><code>" + .path + "</code></dd>\n<dt>sha256</dt><dd><code>" + .sha256 + "</code></dd>"' "$release_file"
printf '</dl>\n'
printf '</div>\n'
} > "${release_dir}/sbom/index.md"
fi
}
write_projects_index() {
{
printf '+++\n'
printf 'title = "Projects"\n'
printf 'sort_by = "title"\n'
printf '+++\n'
} > "${projects_content_dir}/_index.md"
}
validate_source_snapshot() {
local manifest="$1"
local project_id root_abs root_real snapshot_root_real root_rel source_kind include_file exclude_file
validate_no_htmlish_text "$manifest"
jq -e '
(keys - [
"schema_version",
"project_id",
"snapshot_id",
"generated_at",
"source_kind",
"root",
"include",
"exclude"
] | length == 0)
and
.schema_version == 1
and (.project_id | test("^[a-z0-9][a-z0-9-]*$"))
and (.snapshot_id | type == "string" and length > 0)
and (.generated_at | test("^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z$"))
and .source_kind == "approved-public-copy"
and (.root | type == "string" and length > 0)
and (.include | type == "array" and length > 0)
and (.exclude | type == "array")
' "$manifest" >/dev/null || fail "invalid source snapshot manifest: ${manifest}"
project_id="$(json_string "$manifest" '.project_id')"
registry_project "$project_id" >/dev/null || fail "source snapshot project not allowlisted: ${project_id}"
root_rel="$(json_string "$manifest" '.root')"
case "$root_rel" in
/*|../*|*/../*|*'/..'|'.'|'')
fail "source snapshot root must stay inside the catalog snapshot tree: ${root_rel}"
;;
esac
root_abs="${repo_root}/${root_rel}"
source_kind="$(json_string "$manifest" '.source_kind')"
[[ "$source_kind" == "approved-public-copy" ]] || fail "unsupported source snapshot kind: ${source_kind}"
[[ -d "$root_abs" ]] || fail "source snapshot root does not exist: ${root_rel}"
root_real="$(realpath "$root_abs")"
snapshot_root_real="$(realpath "$source_snapshot_root")"
case "$root_real" in
"${snapshot_root_real}/${project_id}/files"| "${snapshot_root_real}/${project_id}/files/"*)
;;
*)
fail "source snapshot root escapes project snapshot files directory: ${root_rel}"
;;
esac
if find "$root_abs" -type l -print -quit | grep -q .; then
fail "source snapshot contains symlinks: ${root_rel}"
fi
include_file="$(mktemp)"
exclude_file="$(mktemp)"
jq -r '.include[]' "$manifest" > "$include_file"
jq -r '.exclude[]' "$manifest" > "$exclude_file"
if grep -E '(^/|(^|/)\.\.(/|$))' "$include_file" "$exclude_file" >/dev/null; then
fail "source snapshot include/exclude patterns must be relative and cannot contain .."
fi
while IFS= read -r snapshot_file; do
local rel size
rel="${snapshot_file#${root_abs}/}"
case "$rel" in
/*|../*|*/../*|.git/*|.terraform/*|*.state-file|*.plan-file|.env|.env.*|*/.env|*/.env.*)
fail "source snapshot contains denied path: ${rel}"
;;
esac
path_is_listed "$rel" "$include_file" || fail "source snapshot file is not included by manifest allowlist: ${rel}"
if path_is_listed "$rel" "$exclude_file"; then
fail "source snapshot file is excluded by manifest denylist: ${rel}"
fi
size="$(wc -c < "$snapshot_file")"
[[ "$size" -le "$max_source_file_bytes" ]] || fail "source file exceeds ${max_source_file_bytes} bytes: ${rel}"
if grep -En 'private source host|private source host|source url field|cloud secret key|cloud access key|vault token|private key|state file' "$snapshot_file" >/dev/null; then
fail "source snapshot file contains denied operational/private text: ${rel}"
fi
if grep -En '~~~' "$snapshot_file" >/dev/null; then
fail "source snapshot file contains unsupported fenced-code delimiter: ${rel}"
fi
done < <(find "$root_abs" -type f | sort)
rm -f "$include_file" "$exclude_file"
}
validate_docs_snapshot() {
local manifest="$1"
local project_id root_rel root_abs rel
validate_source_snapshot "$manifest"
project_id="$(json_string "$manifest" '.project_id')"
root_rel="$(json_string "$manifest" '.root')"
root_abs="${repo_root}/${root_rel}"
while IFS= read -r snapshot_file; do
rel="${snapshot_file#${root_abs}/}"
case "$rel" in
README.md|docs/*.md|docs/*.markdown)
;;
*)
fail "docs-only snapshot contains non-documentation path: ${project_id} ${rel}"
;;
esac
done < <(find "$root_abs" -type f | sort)
}
source_tree_html() {
local root_abs="$1"
local slug="$2"
local current_kind="$3"
local current_path="$4"
printf '<nav class="source-tree" aria-label="Source tree">\n'
printf '<div class="source-tree__label">files</div>\n'
printf '<ul>\n'
if [[ "$current_kind" == "tree" && -z "$current_path" ]]; then
printf '<li class="source-tree__item source-tree__item--dir" style="--depth:0"><a aria-current="page" href="/source/%s/" title="./"><span class="source-tree__name">./</span><span class="source-tree__count">%s</span></a></li>\n' "$slug" "$(dir_summary "$root_abs" | html_escape)"
else
printf '<li class="source-tree__item source-tree__item--dir" style="--depth:0"><a href="/source/%s/" title="./"><span class="source-tree__name">./</span><span class="source-tree__count">%s</span></a></li>\n' "$slug" "$(dir_summary "$root_abs" | html_escape)"
fi
while IFS= read -r dir; do
local rel label title current_attr depth summary
rel="${dir#${root_abs}/}"
[[ "$rel" == "$dir" ]] && continue
label="$(basename "$dir" | html_escape)/"
title="$(printf '%s/' "$rel" | html_escape)"
depth="$(path_depth "$rel")"
summary="$(dir_summary "$dir" | html_escape)"
current_attr=""
[[ "$current_kind" == "tree" && "$current_path" == "$rel" ]] && current_attr=' aria-current="page"'
printf '<li class="source-tree__item source-tree__item--dir" style="--depth:%s"><a%s href="/source/%s/tree/%s/" title="%s"><span class="source-tree__name">%s</span><span class="source-tree__count">%s</span></a></li>\n' "$depth" "$current_attr" "$slug" "$rel" "$title" "$label" "$summary"
done < <(find "$root_abs" -mindepth 1 -type d | sort)
while IFS= read -r nav_file; do
local rel label title current_attr depth
rel="${nav_file#${root_abs}/}"
label="$(basename "$nav_file" | html_escape)"
title="$(printf '%s' "$rel" | html_escape)"
depth="$(path_depth "$rel")"
current_attr=""
[[ "$current_kind" == "blob" && "$current_path" == "$rel" ]] && current_attr=' aria-current="page"'
printf '<li class="source-tree__item source-tree__item--file" style="--depth:%s"><a%s href="/source/%s/blob/%s/" title="%s"><span class="source-tree__name">%s</span></a></li>\n' "$depth" "$current_attr" "$slug" "$rel" "$title" "$label"
done < <(find "$root_abs" -type f | sort)
printf '</ul>\n'
printf '</nav>\n'
}
write_source_index() {
{
printf '+++\n'
printf 'title = "Source"\n'
printf 'sort_by = "title"\n'
printf '+++\n\n'
printf 'Browse source files generated for public viewing. These pages are copies, not a live repository.\n'
} > "${source_content_dir}/_index.md"
}
write_search_page() {
{
printf '+++\n'
printf 'title = "Search"\n'
printf 'description = "Client-side search over public catalog pages."\n'
printf '[extra]\nkind = "search"\n'
printf '+++\n\n'
printf 'Search public pages by project, source path, release, or build note.\n\n'
printf '<form class="search-form" data-search-form>\n'
printf '<label for="site-search">query</label>\n'
printf '<input id="site-search" name="q" type="search" autocomplete="off" data-search-input placeholder="project, source path, release, verify">\n'
printf '<button type="submit">search</button>\n'
printf '</form>\n'
printf '<noscript><p>Search needs JavaScript. Primary project, source, release, and verify pages remain linked from the navigation.</p></noscript>\n'
printf '<section class="search-output" data-search-results aria-live="polite">Type to search.</section>\n'
} > "${content_root}/search.md"
}
write_source_browser() {
local project_file="$1"
local project_id slug name visibility manifest root_rel root_abs snapshot_id generated_at source_kind project_source_dir
project_id="$(json_string "$project_file" '.project_id')"
slug="$(json_string "$project_file" '.slug')"
name="$(json_string "$project_file" '.name')"
visibility="$(json_string "$project_file" '.visibility')"
if [[ "$visibility" == "docs-only" ]]; then
jq -e 'has("public_source_snapshot")' "$project_file" >/dev/null &&
fail "docs-only projects cannot publish source browser snapshots: ${project_id}"
return 0
fi
jq -e 'has("public_source_snapshot")' "$project_file" >/dev/null || return 0
manifest="${repo_root}/$(json_string "$project_file" '.public_source_snapshot.manifest_path')"
[[ -f "$manifest" ]] || fail "public source snapshot manifest missing: ${manifest}"
validate_source_snapshot "$manifest"
[[ "$(json_string "$manifest" '.project_id')" == "$project_id" ]] ||
fail "public source snapshot project_id does not match project manifest: ${project_id}"
[[ "$(json_string "$manifest" '.snapshot_id')" == "$(json_string "$project_file" '.public_source_snapshot.snapshot_id')" ]] ||
fail "public source snapshot id does not match project manifest: ${project_id}"
snapshot_id="$(json_string "$manifest" '.snapshot_id')"
generated_at="$(json_string "$manifest" '.generated_at')"
source_kind="$(json_string "$manifest" '.source_kind')"
root_rel="$(json_string "$manifest" '.root')"
root_abs="${repo_root}/${root_rel}"
project_source_dir="${source_content_dir}/${slug}"
mkdir -p "${project_source_dir}/tree" "${project_source_dir}/blob"
{
printf '+++\n'
printf 'title = "%s Source"\n' "$name"
printf 'description = "Public source copy for %s."\n' "$name"
printf '[extra]\nkind = "source"\nhide_children = true\n'
printf '+++\n\n'
printf 'This is a generated public source copy, not a live repository view.\n\n'
printf '<div class="source-browser">\n'
source_tree_html "$root_abs" "$slug" "tree" ""
printf '<section class="source-pane">\n'
source_breadcrumb_html "$slug" "tree" ""
printf '<p class="source-meta"><span>path <code>./</code></span><span>source copy <code>%s</code></span><span>generated <code>%s</code></span><span>kind <code>%s</code></span></p>\n' "$snapshot_id" "$generated_at" "$source_kind"
source_directory_listing "$root_abs" "$slug" "$root_abs"
write_readme_section "${root_abs}/README.md" "source-readme"
printf '</section>\n</div>\n'
} > "${project_source_dir}/_index.md"
write_source_tree_page "$root_abs" "$slug" "$name" "$snapshot_id" ""
while IFS= read -r dir; do
local rel
rel="${dir#${root_abs}/}"
[[ "$rel" == "$dir" ]] && continue
write_source_tree_page "$root_abs" "$slug" "$name" "$snapshot_id" "$rel"
done < <(find "$root_abs" -mindepth 1 -type d | sort)
while IFS= read -r source_file; do
write_source_blob_page "$root_abs" "$slug" "$name" "$snapshot_id" "$source_file"
done < <(find "$root_abs" -type f | sort)
}
source_directory_listing() {
local root_abs="$1"
local slug="$2"
local dir_abs="$3"
printf '<div class="directory-list">\n'
printf '<h2>Contents</h2>\n'
printf '<table class="directory-table">\n'
printf '<thead><tr><th>name</th><th>detail</th></tr></thead>\n'
printf '<tbody>\n'
while IFS= read -r child; do
local child_rel label summary
child_rel="${child#${root_abs}/}"
label="$(basename "$child" | html_escape)"
summary="$(dir_summary "$child" | html_escape)"
printf '<tr><td><span class="filetype-icon" aria-label="directory">d</span><a href="/source/%s/tree/%s/">%s/</a></td><td>%s</td></tr>\n' "$slug" "$child_rel" "$label" "$summary"
done < <(find "$dir_abs" -mindepth 1 -maxdepth 1 -type d | sort)
while IFS= read -r child; do
local child_rel label child_size child_lang
child_rel="${child#${root_abs}/}"
label="$(basename "$child" | html_escape)"
child_size="$(wc -c < "$child")"
child_lang="$(detect_language "$child_rel")"
printf '<tr><td><span class="filetype-icon" aria-label="file">f</span><a href="/source/%s/blob/%s/">%s</a></td><td><code>%s</code> bytes, <code>%s</code></td></tr>\n' "$slug" "$child_rel" "$label" "$child_size" "$child_lang"
done < <(find "$dir_abs" -mindepth 1 -maxdepth 1 -type f | sort)
printf '</tbody></table>\n</div>\n'
}
write_source_tree_page() {
local root_abs="$1"
local slug="$2"
local name="$3"
local snapshot_id="$4"
local rel_dir="$5"
local dir_abs page_dir page_path display_path
if [[ -z "$rel_dir" ]]; then
dir_abs="$root_abs"
page_dir="${source_content_dir}/${slug}/tree"
page_path="source/${slug}/tree"
display_path="./"
else
dir_abs="${root_abs}/${rel_dir}"
page_dir="${source_content_dir}/${slug}/tree/${rel_dir}"
page_path="source/${slug}/tree/${rel_dir}"
display_path="${rel_dir}/"
fi
mkdir -p "$page_dir"
{
printf '+++\n'
printf 'title = "Source tree: %s"\n' "$display_path"
printf 'path = "%s"\n' "$page_path"
printf '[extra]\nkind = "source-tree"\n'
printf '+++\n\n'
printf '<div class="source-browser">\n'
source_tree_html "$root_abs" "$slug" "tree" "$rel_dir"
printf '<section class="source-pane">\n'
printf '<nav class="source-toolbar" aria-label="Source navigation">\n'
printf '<a href="/source/%s/">root</a>\n' "$slug"
if [[ -n "$rel_dir" ]]; then
local parent_dir
parent_dir="$(parent_dir_path "$rel_dir")"
if [[ -n "$parent_dir" ]]; then
printf '<a href="/source/%s/tree/%s/">parent</a>\n' "$slug" "$parent_dir"
else
printf '<a href="/source/%s/tree/">parent</a>\n' "$slug"
fi
fi
printf '</nav>\n'
source_breadcrumb_html "$slug" "tree" "$rel_dir"
printf '<p class="source-meta"><span>path <code>%s</code></span><span>source copy <code>%s</code></span></p>\n' "$(printf '%s' "$display_path" | html_escape)" "$snapshot_id"
source_directory_listing "$root_abs" "$slug" "$dir_abs"
printf '</section>\n</div>\n'
} > "${page_dir}/index.md"
}
write_source_blob_page() {
local root_abs="$1"
local slug="$2"
local name="$3"
local snapshot_id="$4"
local source_file="$5"
local rel page_dir lang size file_name parent_dir
rel="${source_file#${root_abs}/}"
file_name="$(basename "$rel")"
parent_dir="$(parent_dir_path "$rel")"
page_dir="${source_content_dir}/${slug}/blob/${rel}"
lang="$(detect_language "$rel")"
size="$(wc -c < "$source_file")"
mkdir -p "$page_dir"
{
printf '+++\n'
printf 'title = %s\n' "$(jq -n --arg v "$file_name" '$v')"
printf 'path = "source/%s/blob/%s"\n' "$slug" "$rel"
printf '[extra]\nkind = "source-blob"\n'
printf '+++\n\n'
printf '<div class="source-browser">\n'
source_tree_html "$root_abs" "$slug" "blob" "$rel"
printf '<section class="source-pane">\n'
printf '<nav class="source-toolbar" aria-label="Source navigation">\n'
printf '<a href="/source/%s/">root</a>\n' "$slug"
if [[ -n "$parent_dir" ]]; then
printf '<a href="/source/%s/tree/%s/">parent</a>\n' "$slug" "$parent_dir"
else
printf '<a href="/source/%s/tree/">parent</a>\n' "$slug"
fi
printf '</nav>\n'
source_breadcrumb_html "$slug" "blob" "$rel"
printf '<p class="source-meta"><span>path <code>%s</code></span><span>language <code>%s</code></span><span>bytes <code>%s</code></span><span>source copy <code>%s</code></span></p>\n' "$(printf '%s' "$rel" | html_escape)" "$lang" "$size" "$snapshot_id"
printf '<div class="source-code">\n'
printf '\n~~~%s\n' "$lang"
escape_zola_shortcode_markers < "$source_file"
printf '\n~~~\n'
printf '</div>\n'
printf '</section>\n</div>\n'
} > "${page_dir}/index.md"
}
write_design_pages() {
local src title slug dest summary body
for src in "${repo_root}/docs/design/"*.md; do
[[ -f "$src" ]] || continue
title="$(sed -n '1s/^# //p' "$src")"
slug="$(basename "$src" .md)"
dest="${design_content_dir}/${slug}.md"
case "$slug" in
0002-aws-static-hosting-security)
summary="Public summary of the static hosting security model."
body="Design 0002 defines the private S3 plus CloudFront hosting setup for this site: HTTPS, origin access control, private buckets, DNS, budgets, and short-lived publish credentials. It omits private repository and runner details."
;;
0003-static-site-generation-and-open-source-portfolio)
summary="Public summary of the static site generation and catalog model."
body="Design 0003 defines the generated project index: project pages, release files, SBOM links, source-copy pages, and design notes. It shows approved source copies, not a public repository."
;;
*)
summary="Public design summary."
body="This public design note keeps the implementation summary visible while leaving private automation and repository details out."
;;
esac
{
printf '+++\n'
printf 'title = %s\n' "$(jq -n --arg v "$title" '$v')"
printf 'description = %s\n' "$(jq -n --arg v "$summary" '$v')"
printf '[extra]\nkind = "design"\n'
printf '+++\n\n'
printf '%s\n' "$body"
} > "$dest"
done
}
write_provenance_page() {
local output="${verify_content_dir}/provenance.md"
local project_count release_count source_count
mkdir -p "$verify_content_dir"
project_count="$(find "$fixture_root" -mindepth 2 -maxdepth 2 -name project.json -print | wc -l | tr -d ' ')"
release_count="$(find "$fixture_root" -name release.json -print | wc -l | tr -d ' ')"
source_count="$(find "$source_snapshot_root" -mindepth 2 -maxdepth 2 -name manifest.json -print | wc -l | tr -d ' ')"
{
printf '+++\n'
printf 'title = "Catalog Provenance"\n'
printf 'description = "Generated catalog input and build provenance for the public site."\n'
printf '[extra]\nkind = "provenance"\n'
printf '+++\n\n'
printf '<div class="fact-block">\n'
printf '<h2>Build inputs</h2>\n'
printf '<dl class="meta-list">\n'
printf '<dt>generator</dt><dd><code>scripts/build-apex-catalog.sh</code></dd>\n'
printf '<dt>zola</dt><dd><code>%s</code></dd>\n' "$ZOLA_VERSION"
printf '<dt>site registry</dt><dd><code>sites/registry/static-sites.json</code></dd>\n'
printf '<dt>project registry</dt><dd><code>sites/registry/projects.json</code></dd>\n'
printf '<dt>project manifests</dt><dd><code>%s</code></dd>\n' "$project_count"
printf '<dt>release manifests</dt><dd><code>%s</code></dd>\n' "$release_count"
printf '<dt>source copies</dt><dd><code>%s</code></dd>\n' "$source_count"
printf '</dl>\n'
printf '</div>\n\n'
printf '## Projects\n\n'
printf '| project | slug | visibility | release count | source copy |\n'
printf '| --- | --- | --- | --- | --- |\n'
find "$fixture_root" -mindepth 2 -maxdepth 2 -name project.json -print | sort |
while IFS= read -r project_file; do
local project_id slug visibility releases snapshot
project_id="$(json_string "$project_file" '.project_id')"
slug="$(json_string "$project_file" '.slug')"
visibility="$(json_string "$project_file" '.visibility')"
if [[ -d "${fixture_root}/${project_id}/releases" ]]; then
releases="$(find "${fixture_root}/${project_id}/releases" -name release.json -print | wc -l | tr -d ' ')"
else
releases=0
fi
snapshot="$(jq -er '.public_source_snapshot.snapshot_id // "none"' "$project_file")"
printf '| `%s` | `%s` | `%s` | `%s` | `%s` |\n' "$project_id" "$slug" "$visibility" "$releases" "$snapshot"
done
printf '\n'
printf '## Release Manifests\n\n'
printf '| project | version | commit | built_at | sbom |\n'
printf '| --- | --- | --- | --- | --- |\n'
find "$fixture_root" -name release.json -print | sort |
while IFS= read -r release_file; do
local project_id version commit built_at sbom_path
project_id="$(json_string "$release_file" '.project_id')"
version="$(json_string "$release_file" '.version')"
commit="$(json_string "$release_file" '.commit')"
built_at="$(json_string "$release_file" '.built_at')"
sbom_path="$(jq -er '.sbom.path // "none"' "$release_file")"
[[ "$commit" == "0000000000000000000000000000000000000000" ]] && commit="not recorded"
printf '| `%s` | `%s` | `%s` | `%s` | `%s` |\n' "$project_id" "$version" "$commit" "$built_at" "$sbom_path"
done
printf '\n'
printf '## Source Copies\n\n'
printf '| project | snapshot | generated | kind | root |\n'
printf '| --- | --- | --- | --- | --- |\n'
find "$source_snapshot_root" -mindepth 2 -maxdepth 2 -name manifest.json -print | sort |
while IFS= read -r manifest; do
local project_id snapshot generated source_kind root_rel
project_id="$(json_string "$manifest" '.project_id')"
snapshot="$(json_string "$manifest" '.snapshot_id')"
generated="$(json_string "$manifest" '.generated_at')"
source_kind="$(json_string "$manifest" '.source_kind')"
root_rel="$(json_string "$manifest" '.root')"
printf '| `%s` | `%s` | `%s` | `%s` | `%s` |\n' "$project_id" "$snapshot" "$generated" "$source_kind" "$root_rel"
done
} > "$output"
}
write_rollback_page() {
local output="${verify_content_dir}/rollback.md"
mkdir -p "$verify_content_dir"
{
printf '+++\n'
printf 'title = "Rollback Procedure"\n'
printf 'description = "Static-site rollback paths for the generated site."\n'
printf '[extra]\nkind = "rollback"\n'
printf '+++\n\n'
printf 'The site can be republished from checked-in catalog, source-copy, template, and script inputs. Rollback must use one of these repeatable paths:\n\n'
printf '1. Rebuild and republish a known-good commit through `scripts/validate-apex-catalog.sh` and `scripts/publish-apex-site.sh`.\n'
printf '2. Restore prior S3 object versions from the versioned private site bucket, then invalidate the CloudFront distribution.\n\n'
printf '<div class="fact-block">\n'
printf '<h2>Rollback requirements</h2>\n'
printf '<dl class="meta-list">\n'
printf '<dt>bucket versioning</dt><dd><code>modules/static-site/main.tf aws_s3_bucket_versioning.origin Enabled</code></dd>\n'
printf '<dt>publish script</dt><dd><code>scripts/publish-apex-site.sh</code></dd>\n'
printf '<dt>validation gate</dt><dd><code>scripts/validate-apex-catalog.sh</code></dd>\n'
printf '<dt>cloudfront invalidation</dt><dd><code>aws cloudfront create-invalidation --paths "/*"</code></dd>\n'
printf '</dl>\n'
printf '</div>\n\n'
printf 'Project release files are excluded from site cleanup, so a site rollback does not remove published release objects.\n'
} > "$output"
}
write_search_index() {
local entries_file output_file project_file release_file manifest source_file rel slug project_id name summary version
entries_file="$(mktemp)"
output_file="${static_root}/search-index.json"
find "$fixture_root" -mindepth 2 -maxdepth 2 -name project.json -print | sort |
while IFS= read -r project_file; do
local visibility topics snapshot_id
visibility="$(json_string "$project_file" '.visibility')"
project_is_rendered "$project_file" || continue
project_id="$(json_string "$project_file" '.project_id')"
slug="$(json_string "$project_file" '.slug')"
name="$(json_string "$project_file" '.name')"
summary="$(json_string "$project_file" '.summary')"
topics="$(jq -r '.topics | join(" ")' "$project_file")"
append_search_entry "$entries_file" "Project" "$name" "/projects/${slug}/" "$summary" "$topics"
append_search_entry "$entries_file" "Release" "${name} releases" "/projects/${slug}/releases/" "Immutable public release metadata for ${name}." "$topics releases"
if jq -e 'has("public_docs_snapshot")' "$project_file" >/dev/null; then
append_search_entry "$entries_file" "Docs" "${name} documentation" "/projects/${slug}/docs/" "README and docs directory content for ${name}." "$topics docs"
fi
if [[ "$visibility" != "docs-only" ]] && jq -e 'has("public_source_snapshot")' "$project_file" >/dev/null; then
snapshot_id="$(json_string "$project_file" '.public_source_snapshot.snapshot_id')"
append_search_entry "$entries_file" "Source" "${name} source" "/source/${slug}/" "Generated public source copy ${snapshot_id}." "$topics source ${snapshot_id}"
fi
done
find "$fixture_root" -name release.json -print | sort |
while IFS= read -r release_file; do
project_id="$(json_string "$release_file" '.project_id')"
project_file="${fixture_root}/${project_id}/project.json"
[[ -f "$project_file" ]] || continue
project_is_rendered "$project_file" || continue
slug="$(json_string "$project_file" '.slug')"
name="$(json_string "$project_file" '.name')"
version="$(json_string "$release_file" '.version')"
summary="$(json_string "$release_file" '.changelog')"
append_search_entry "$entries_file" "Release" "${name} ${version}" "/projects/${slug}/releases/${version}/" "$summary" "release ${version}"
if jq -e 'has("sbom")' "$release_file" >/dev/null; then
append_search_entry "$entries_file" "Release" "${name} ${version} SBOM" "/projects/${slug}/releases/${version}/sbom/" "Software bill of materials metadata for ${name} ${version}." "sbom ${version}"
fi
done
find "$source_snapshot_root" -mindepth 2 -maxdepth 2 -name manifest.json -print | sort |
while IFS= read -r manifest; do
project_id="$(json_string "$manifest" '.project_id')"
project_file="${fixture_root}/${project_id}/project.json"
[[ -f "$project_file" ]] || continue
project_is_rendered "$project_file" || continue
[[ "$(jq -er '.public_source_snapshot.manifest_path // ""' "$project_file")" == "${manifest#${repo_root}/}" ]] || continue
slug="$(json_string "$project_file" '.slug')"
root_abs="${repo_root}/$(json_string "$manifest" '.root')"
while IFS= read -r source_file; do
rel="${source_file#${root_abs}/}"
append_search_entry "$entries_file" "Source" "$rel" "/source/${slug}/blob/${rel}/" "Source file in the public copy." "source ${slug} ${rel}"
done < <(find "$root_abs" -type f | sort)
done
find "$source_snapshot_root" -mindepth 2 -maxdepth 2 -name manifest.json -print | sort |
while IFS= read -r manifest; do
project_id="$(json_string "$manifest" '.project_id')"
project_file="${fixture_root}/${project_id}/project.json"
[[ -f "$project_file" ]] || continue
project_is_rendered "$project_file" || continue
[[ "$(jq -er '.public_docs_snapshot.manifest_path // ""' "$project_file")" == "${manifest#${repo_root}/}" ]] || continue
slug="$(json_string "$project_file" '.slug')"
root_abs="${repo_root}/$(json_string "$manifest" '.root')"
docs_dir="${root_abs}/docs"
[[ -d "$docs_dir" ]] || continue
while IFS= read -r source_file; do
rel="${source_file#${docs_dir}/}"
append_search_entry "$entries_file" "Docs" "$rel" "/projects/${slug}/docs/${rel%.*}/" "Documentation page for ${slug}." "docs ${slug} ${rel}"
done < <(find "$docs_dir" -type f \( -name '*.md' -o -name '*.markdown' \) | sort)
done
append_search_entry "$entries_file" "Verify" "Build notes" "/verify/" "How the site is built and rolled back." "verify provenance rollback"
append_search_entry "$entries_file" "Verify" "Build inputs" "/verify/provenance/" "Generated catalog inputs and build details." "verify provenance"
append_search_entry "$entries_file" "Verify" "Rollback procedure" "/verify/rollback/" "Static-site rollback paths for the generated site." "verify rollback"
append_search_entry "$entries_file" "Design" "0002 - AWS Static Hosting Security" "/designs/0002-aws-static-hosting-security/" "Public summary of the static hosting security model." "design aws static hosting"
append_search_entry "$entries_file" "Design" "0003 - Static Site Generation and Open Source Portfolio" "/designs/0003-static-site-generation-and-open-source-portfolio/" "Public summary of the static site generation and catalog model." "design zola catalog source"
jq -s '{schema_version: 1, generated_by: "scripts/build-apex-catalog.sh", entries: sort_by(.type, .title, .url)}' "$entries_file" > "$output_file"
rm -f "$entries_file"
}
write_generated_catalog() {
local entries_file project_file project_id license releases
entries_file="$(mktemp)"
find "$fixture_root" -mindepth 2 -maxdepth 2 -name project.json -print | sort |
while IFS= read -r project_file; do
project_id="$(json_string "$project_file" '.project_id')"
license="$(project_license "$project_file")"
releases="$(project_release_count "$project_id")"
jq \
--arg license "$license" \
--argjson release_count "$releases" \
'. + {
license: $license,
release_count: $release_count,
has_releases: ($release_count > 0)
}' "$project_file" >> "$entries_file"
done
jq -s '{schema_version: 1, projects: .}' "$entries_file" > "${generated_root}/projects.json"
rm -f "$entries_file"
}
ensure_zola_binary() {
local archive tmp_dir url checksum actual
tmp_dir="${TMPDIR:-/tmp}/thepeoples-io-zola-${ZOLA_VERSION}"
zola_binary="${tmp_dir}/zola"
if [[ -x "$zola_binary" ]]; then
actual="$("$zola_binary" --version | awk '{print $2}')"
[[ "$actual" == "$ZOLA_VERSION" ]] || fail "cached zola ${ZOLA_VERSION} required, found ${actual}"
log "Using cached zola ${actual}"
return
fi
require_command curl
require_command tar
mkdir -p "$tmp_dir"
archive="${tmp_dir}/zola.tar.gz"
url="https://github.com/getzola/zola/releases/download/v${ZOLA_VERSION}/zola-v${ZOLA_VERSION}-x86_64-unknown-linux-gnu.tar.gz"
checksum="$ZOLA_LINUX_X86_64_GNU_SHA256"
log "Fetching pinned zola ${ZOLA_VERSION}"
curl -fsSL "$url" -o "$archive"
printf '%s %s\n' "$checksum" "$archive" | sha256sum -c -
tar -xzf "$archive" -C "$tmp_dir" zola
actual="$("$zola_binary" --version | awk '{print $2}')"
[[ "$actual" == "$ZOLA_VERSION" ]] || fail "downloaded zola ${ZOLA_VERSION} required, found ${actual}"
}
verify_zola_pin() {
if command -v zola >/dev/null 2>&1; then
actual="$(zola --version | awk '{print $2}')"
[[ "$actual" == "$ZOLA_VERSION" ]] || fail "zola ${ZOLA_VERSION} required, found ${actual}"
log "Using local zola ${actual}"
return
fi
if [[ "$use_container" == "true" ]] && command -v podman >/dev/null 2>&1; then
log "Using pinned Zola container ghcr.io/getzola/zola:v${ZOLA_VERSION}"
return
fi
if [[ "$use_container" == "true" ]] && command -v docker >/dev/null 2>&1; then
log "Using pinned Zola container ghcr.io/getzola/zola:v${ZOLA_VERSION}"
return
fi
ensure_zola_binary
}
run_zola_build() {
if command -v zola >/dev/null 2>&1; then
zola --root "$site_root" build --output-dir "${repo_root}/sites/apex" --force
return
fi
if [[ -n "$zola_binary" ]]; then
"$zola_binary" --root "$site_root" build --output-dir "${repo_root}/sites/apex" --force
return
fi
if [[ "$use_container" == "true" ]] && command -v podman >/dev/null 2>&1; then
podman run --rm -v "${repo_root}:/site:Z" -w /site "ghcr.io/getzola/zola:v${ZOLA_VERSION}" --root sites/apex-src build --output-dir sites/apex --force
return
fi
[[ "$use_container" == "true" ]] || fail "zola ${ZOLA_VERSION} is required and pinned binary setup failed"
docker run --rm -v "${repo_root}:/site" -w /site "ghcr.io/getzola/zola:v${ZOLA_VERSION}" --root sites/apex-src build --output-dir sites/apex --force
}
restore_source_shortcode_markers() {
[[ -d "${repo_root}/sites/apex/source" ]] || return
find "${repo_root}/sites/apex/source" -type f -name index.html -print0 |
xargs -0 perl -0pi \
-e 's/\{\{\/\*\s*(.*?)\s*\*\/\}\}/{{ $1 }}/gs;' \
-e 's/\{% \/\*\s*(.*?)\s*\*\/%\}/{%/* $1 %}/gs;' \
-e 's/\{# \/\*\s*(.*?)\s*\*\/#\}/{#/* $1 #}/gs;'
}
main() {
require_command jq
require_command perl
require_command realpath
require_command sha256sum
validate_registry_sites
validate_registry_projects
verify_zola_pin
reset_generated_content
write_projects_index
write_source_index
write_search_page
write_design_pages
write_provenance_page
write_rollback_page
find "$fixture_root" -mindepth 2 -maxdepth 2 -name project.json -print | sort |
while IFS= read -r project_file; do
validate_project_manifest "$project_file"
write_project_pages "$project_file"
write_source_browser "$project_file"
done
write_generated_catalog
write_search_index
run_zola_build
restore_source_shortcode_markers
log "Built apex site into sites/apex"
}
main "$@"