diff --git a/CHANGELOG.md b/CHANGELOG.md index aa4560e..9a92ffc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,11 +26,17 @@ sections that affect its template. Version numbers follow the - `theme.favicon` points at `theme/brand-symbol.svg` rather than the wordmark. +- The PDF cover carries a line naming the theme that rendered it. A PDF + leaves the repository that produced it, so without it the artifact says + nothing about what to reproduce a rendering complaint against. ### api-reference - `theme.favicon` points at `theme/brand-symbol.svg` rather than the wordmark. +- The PDF cover carries a line naming the theme that rendered it. A PDF + leaves the repository that produced it, so without it the artifact says + nothing about what to reproduce a rendering complaint against. ### scripts @@ -40,6 +46,11 @@ sections that affect its template. Version numbers follow the project documenting a product with its own mark can put it on the cover. A path that does not resolve is an error rather than a silent fall back to the company mark. +- `build-docs-pdf.sh` reads `docs/theme/.meta` for the cover's provenance + line, so a drifted tree cannot misreport itself as a release. A + `--source` install renders `docs-theme (local build)` rather than the + version it was asked for, which never resolved a release. A missing + `.meta` is an error. ## [0.2.0] - 2026-08-08 diff --git a/README.md b/README.md index 6cf2013..7f3ed7b 100644 --- a/README.md +++ b/README.md @@ -355,6 +355,22 @@ with more than one needs: It defaults to `mkdocs.yml`. Any other argument shape is a usage error. +#### Build Provenance + +Every cover carries a line naming the theme that rendered it — +`docs-theme 1.2.3` for a downloaded install. A PDF is handed to a reader +and leaves the repository that produced it, so without this there is +nothing in the artifact to reproduce a rendering complaint against, and +once several projects bump the theme on their own schedules the answer +stops being inferable. + +The value comes from `docs/theme/.meta`, the record of what is installed, +rather than from the request in `docs/theme.toml` — a hand-edited or +drifted tree would otherwise misreport itself. A `--source` install has a +version that never resolved a release, so it prints `docs-theme (local +build)` instead of a number. It is not configurable, and a missing +`.meta` is an error rather than a quietly omitted line. + #### Cover and Output Configuration All cover text and the output filename come from an `extra.pdf` block diff --git a/scripts/build-docs-pdf.sh b/scripts/build-docs-pdf.sh index 03b66a0..9671673 100755 --- a/scripts/build-docs-pdf.sh +++ b/scripts/build-docs-pdf.sh @@ -174,6 +174,39 @@ if not os.path.isdir(theme_dir): fail("docs/theme/ not found. Install the theme first:\n" " ./scripts/fetch-theme.sh") +# Which docs-theme rendered this file. A PDF is handed to a reader and +# leaves the repository that produced it, so unless the artifact says so +# there is nothing to reproduce a rendering complaint against -- and once +# several projects bump the theme on their own schedules, the answer +# stops being inferable. .meta is the record of what is installed; the +# requested value in docs/theme.toml would misreport a drifted tree. +meta_path = os.path.join(theme_dir, ".meta") +if not os.path.isfile(meta_path): + fail(f"{meta_path} not found. The installed theme is incomplete; " + "re-run ./scripts/fetch-theme.sh.") + +installed = {} +with open(meta_path, "r", encoding="utf-8") as f: + for line in f: + key, sep, value = line.partition("=") + if sep: + installed[key.strip()] = value.strip().strip('"') + +theme_version = installed.get("version") +theme_source = installed.get("source") +if not theme_version or not theme_source: + fail(f"{meta_path} records no version or source; re-run " + "./scripts/fetch-theme.sh.") + +# A --source install takes its version from docs/theme.toml without ever +# resolving a release, so printing that number would assert something +# untrue. Name the build instead. +if theme_source == "release": + theme_provenance = f"docs-theme {theme_version}" +else: + theme_provenance = ("docs-theme (local build)" if locale == "en" + else "docs-theme (로컬 빌드)") + theme_pdf = os.path.join(theme_dir, "pdf") if not os.path.isdir(theme_pdf): fail("docs/theme/pdf/ not found. The installed template ships no PDF " @@ -336,6 +369,12 @@ elif "cover_tagline" in extra: del extra["cover_tagline"] data["extra"] = extra +# theme_provenance reaches {{ theme_provenance }} in pdf/cover.html.j2 the +# same way, through `extra`. It is derived from the installed theme, not +# configured, so a consumer value is overwritten rather than honoured. +extra["theme_provenance"] = theme_provenance +data["extra"] = extra + if isinstance(plugins, dict): plugins["with-pdf"] = options elif isinstance(plugins, list): diff --git a/templates/api-reference/pdf/cover.html.j2 b/templates/api-reference/pdf/cover.html.j2 index 6c38c8d..0158715 100644 --- a/templates/api-reference/pdf/cover.html.j2 +++ b/templates/api-reference/pdf/cover.html.j2 @@ -31,5 +31,9 @@ {% endfor %} {% endif %} + + {% if theme_provenance %} +

{{ theme_provenance | e }}

+ {% endif %} diff --git a/templates/api-reference/pdf/styles.scss b/templates/api-reference/pdf/styles.scss index 5c721db..c21b1f8 100644 --- a/templates/api-reference/pdf/styles.scss +++ b/templates/api-reference/pdf/styles.scss @@ -197,6 +197,19 @@ p.meta { color: #71717B; } +/* Build provenance: which docs-theme rendered this file. A PDF leaves + * the repository that built it, so without this line there is nothing in + * the artifact to reproduce a rendering complaint against. */ +p.meta-provenance { + color: #B5B5BC; + font-size: 10px; + font-weight: 400; + line-height: 16px; + letter-spacing: -0.1px; + white-space: nowrap; + margin: 6px 0 0; +} + p.meta-copyright { color: #9F9FA9; font-size: 12px; diff --git a/templates/manual/pdf/cover.html.j2 b/templates/manual/pdf/cover.html.j2 index 6c38c8d..0158715 100644 --- a/templates/manual/pdf/cover.html.j2 +++ b/templates/manual/pdf/cover.html.j2 @@ -31,5 +31,9 @@ {% endfor %} {% endif %} + + {% if theme_provenance %} +

{{ theme_provenance | e }}

+ {% endif %} diff --git a/templates/manual/pdf/styles.scss b/templates/manual/pdf/styles.scss index 5c721db..c21b1f8 100644 --- a/templates/manual/pdf/styles.scss +++ b/templates/manual/pdf/styles.scss @@ -197,6 +197,19 @@ p.meta { color: #71717B; } +/* Build provenance: which docs-theme rendered this file. A PDF leaves + * the repository that built it, so without this line there is nothing in + * the artifact to reproduce a rendering complaint against. */ +p.meta-provenance { + color: #B5B5BC; + font-size: 10px; + font-weight: 400; + line-height: 16px; + letter-spacing: -0.1px; + white-space: nowrap; + margin: 6px 0 0; +} + p.meta-copyright { color: #9F9FA9; font-size: 12px; diff --git a/tests/pdf-test.sh b/tests/pdf-test.sh index e20c349..9ecf771 100755 --- a/tests/pdf-test.sh +++ b/tests/pdf-test.sh @@ -777,4 +777,44 @@ grep -q "brand-print.svg" "$WORK/no-print-logo.log" \ || die "the error does not name the missing asset" ok "a missing brand-print.svg fails instead of rendering a logo-less cover" +# --- build provenance on the cover -------------------------------------- + +# A PDF leaves the repository that built it, so the cover records which +# docs-theme rendered it. The fixtures install with --source, so this is +# the local form: naming a version that never resolved a release would +# assert something untrue. +prov="$WORK/prov" +new_project "$prov" +printf 'site_name: Provenance Fixture\n' > "$prov/mkdocs.yml" +build_pdf "$prov" en +build_pdf "$prov" ko +pdf_text "$prov/site/pdf/provenance-fixture.en.pdf" > "$WORK/prov-en.txt" +pdf_text "$prov/site/pdf/provenance-fixture.ko.pdf" > "$WORK/prov-ko.txt" +contains "$WORK/prov-en.txt" "docs-theme(localbuild)" "the en cover" +contains "$WORK/prov-ko.txt" "docs-theme(로컬빌드)" "the ko cover" +excludes "$WORK/prov-en.txt" "docs-theme1.2.3" "the en cover" +ok "a --source build names itself rather than claiming a release version" + +# A downloaded install prints the version .meta recorded. The value is +# read from .meta and not from docs/theme.toml, so a drifted tree cannot +# misreport itself. +sed -i.bak 's/^source = .*/source = "release"/' "$prov/docs/theme/.meta" +rm -f "$prov/docs/theme/.meta.bak" +build_pdf "$prov" en +pdf_text "$prov/site/pdf/provenance-fixture.en.pdf" > "$WORK/prov-release.txt" +contains "$WORK/prov-release.txt" "docs-theme1.2.3" "the cover of a release build" +excludes "$WORK/prov-release.txt" "localbuild" "the cover of a release build" +ok "a release install prints the version recorded in .meta" + +# Without .meta there is no provenance to print, and a cover that quietly +# omits it is the silent asset loss this whole check exists to prevent. +rm -f "$prov/docs/theme/.meta" +if (cd "$prov" && ./docs/theme/build-docs-pdf.sh en) > "$WORK/no-meta.log" 2>&1 +then + die "a missing .meta should be an error" +fi +grep -q "\.meta" "$WORK/no-meta.log" \ + || die "the error does not name .meta" +ok "a missing .meta fails instead of dropping the provenance line" + echo "All PDF script checks passed."