Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions .github/actions/build-pandoc/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,26 @@ inputs:
rather than adding to them.
required: false
default: ""
site-base:
description: >-
URL prefix the site is served under, used for the theme's own CSS and JS.
Defaults to /<repo>/, which is where GitHub Pages puts a project site. Set
it to / for a site served at a domain root.
required: false
default: /${{ github.event.repository.name }}/
extra-css:
description: >-
Stylesheets to ship with the site and load after the theme's, one path per
line. Later rules win, so this is enough to restyle anything the theme
does without forking it.
required: false
default: ""
extra-js:
description: >-
Scripts to ship with the site and load after the theme's, one path per
line.
required: false
default: ""
search:
description: >-
Add a Search item to the theme's nav. Set this when you also run the
Expand Down Expand Up @@ -84,6 +104,9 @@ runs:
PROJECT: ${{ inputs.project }}
REPO_URL: ${{ inputs.repo-url }}
SEARCH: ${{ inputs.search }}
SITE_BASE: ${{ inputs.site-base }}
EXTRA_CSS: ${{ inputs.extra-css }}
EXTRA_JS: ${{ inputs.extra-js }}
run: |
ARGS=(--docs-dir "$DOCS_DIR" --output "$OUTPUT" --index-from "$INDEX_FROM")
[ -n "$PROJECT" ] && ARGS+=(--project "$PROJECT")
Expand All @@ -99,4 +122,11 @@ runs:
while IFS= read -r line; do
[ -n "$line" ] && ARGS+=(--include "$line")
done <<< "$INCLUDE"
[ -n "$SITE_BASE" ] && ARGS+=(--site-base "$SITE_BASE")
while IFS= read -r line; do
[ -n "$line" ] && ARGS+=(--extra-css "$line")
done <<< "$EXTRA_CSS"
while IFS= read -r line; do
[ -n "$line" ] && ARGS+=(--extra-js "$line")
done <<< "$EXTRA_JS"
"${{ github.action_path }}/../../../bin/build-pandoc.sh" "${ARGS[@]}"
22 changes: 22 additions & 0 deletions .github/workflows/docs-pandoc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,25 @@ on:
type: string
required: false
default: ""
site-base:
description: >-
URL prefix the site is served under. Defaults to /<repo>/. Set it to /
for a site served at a domain root.
type: string
required: false
default: ""
extra-css:
description: >-
Stylesheets to ship and load after the theme's, one path per line.
type: string
required: false
default: ""
extra-js:
description: >-
Scripts to ship and load after the theme's, one path per line.
type: string
required: false
default: ""
index-from:
description: Basename whose page becomes index.html.
type: string
Expand Down Expand Up @@ -103,6 +122,9 @@ jobs:
include: ${{ inputs.include }}
template: ${{ inputs.template }}
lua-filters: ${{ inputs.lua-filters }}
site-base: ${{ inputs.site-base || format('/{0}/', github.event.repository.name) }}
extra-css: ${{ inputs.extra-css }}
extra-js: ${{ inputs.extra-js }}
index-from: ${{ inputs.index-from }}
search: ${{ inputs.pagefind }}

Expand Down
20 changes: 20 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,28 @@ what moved. See

## [Unreleased]

### Added

- The Pandoc theme's own CSS and JS now live here, in `pandoc/css` and
`pandoc/js`, and are copied into every site `build-pandoc` builds. A
published site serves them itself instead of fetching them from another
project's CDN. See
[ADR-0008](docs/decisions/0008-ship-the-themes-assets-with-the-site.md).
- `build-pandoc` inputs `extra-css` and `extra-js`: files a project ships with
its site and loads *after* the theme's. For CSS that is enough to restyle
anything the theme does, because later rules win, so no project needs a copy
of a theme file.
- `build-pandoc` input `site-base`, the URL prefix those assets are served
under. Defaults to `/<repo>/`; set it to `/` for a site at a domain root.

### Changed

- The shared template no longer fetches `site.css`, `code-blocks.css` or
`copyToClipboard.js` by absolute URL. Those had made every documentation site
depend on `CL-web-components` publishing to S3 and on
`caltechlibrary.github.io` acting as an asset host; the S3 copies were
thirteen months stale. `footer-global.js` and the Caltech Library logo keep
their CDN URLs, because sites outside this build system embed them.
- Documented which repeated values in examples must match and which are
coincidental: a build action's `output` and `deploy-site`'s `path` must be
the same directory, while `prefix` and the path in `public-base-url` need
Expand Down
38 changes: 38 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ but do not deploy it.
| `include` | — | Files or directories to copy into the site verbatim, one per line |
| `template` | shared theme | Pandoc template; point at a file in your repo to override |
| `lua-filters` | shared filters | One path per line. **Replaces** the defaults rather than adding to them |
| `site-base` | `/<repo>/` | URL prefix the site is served under. Set it to `/` for a domain root |
| `extra-css` | — | Stylesheets shipped and loaded **after** the theme's, one per line |
| `extra-js` | — | Scripts shipped and loaded after the theme's, one per line |
| `index-from` | `README` | Basename whose page becomes `index.html` |
| `pre-build` | — | Shell command to run before rendering, e.g. `deno task build` |
| `pagefind` | `true` | Build a Pagefind search index |
Expand Down Expand Up @@ -154,12 +157,47 @@ coincidence.
| `include` | — | Files or directories copied in verbatim, one per line |
| `template` | shared theme | Pandoc template; point at your own file to override |
| `lua-filters` | shared filters | One per line. **Replaces** the defaults |
| `site-base` | `/<repo>/` | URL prefix the site is served under. Set it to `/` for a domain root |
| `extra-css` | — | Stylesheets shipped and loaded **after** the theme's, one per line |
| `extra-js` | — | Scripts shipped and loaded after the theme's, one per line |
| `index-from` | `README` | Basename that becomes `index.html` |
| `search` | `false` | Add a Search item to the nav — set it when you also run `index-site` |
| `project` | repository name | Shown in the page title |
| `repo-url` | this repository | Link in the theme's nav |
| `pandoc-version` | `3.8.2.1` | Pinned so a Pandoc release cannot change published output |

#### Customizing the theme

The theme's stylesheets and scripts live in `pandoc/css` and `pandoc/js` here,
and are copied into every site this action builds. A published site serves them
itself; it does not fetch them from anywhere else. Updating them for everyone is
a change here plus a move of `v1`.

To change how a site looks, **add** rather than replace:

```yaml
- uses: caltechlibrary/workflows/.github/actions/build-pandoc@v1
with:
extra-css: pandoc/css/my-project.css
```

Your file ships alongside the theme's and loads after them, so a rule in it
beats the theme's rule for the same selector. That is enough to restyle
anything the theme does without copying a theme file into your repository —
and a copy you do not have is a copy that cannot fall behind.

Naming your file the same as a theme file replaces it instead. That works,
but it is a fork; prefer a new name.

`site-base` prefixes the URLs of both. It defaults to `/<repo>/`, where GitHub
Pages puts a project site. A site served at a domain root sets `site-base: /`.
Running `bin/build-pandoc.sh` by hand with no `--site-base` emits relative URLs,
so the output opens correctly straight from disk.

For a page structure the theme does not offer, `template` still points Pandoc at
your own file. That opts you out of future changes to the shared template, which
is the trade you are making.

### `build-zensical`

| Input | Default | |
Expand Down
44 changes: 43 additions & 1 deletion bin/build-pandoc.sh
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,15 @@
# Defaults for the template and Lua filters resolve to this repository's
# pandoc/ directory, so a project gets the shared Caltech theme without
# copying it. Pass --template or --lua-filter to override.
#
# The theme's own CSS and JS are copied into the site from pandoc/css and
# pandoc/js, so a published site serves them itself rather than fetching them
# from somewhere else. Projects add their own with --extra-css/--extra-js,
# which load after the theme's; for CSS that is enough to override any of it,
# because later rules win. Nothing needs to be forked to be customized.
#
# --site-base prefixes those URLs. It has no default here because this script
# knows nothing about where a site is published; the action supplies one.

set -euo pipefail

Expand All @@ -26,6 +35,9 @@ INDEX_FROM="README"
PROJECT=""
REPO_URL=""
SEARCH_PAGE=""
SITE_BASE=""
declare -a EXTRA_CSS=()
declare -a EXTRA_JS=()
declare -a LUA_FILTERS=()
declare -a EXTRA_SOURCES=()
declare -a INCLUDE=()
Expand All @@ -40,6 +52,9 @@ Options:
--lua-filter FILE repeatable (default: shared filters)
--extra-source PATH Markdown outside --docs-dir, repeatable
--include PATH file or directory to copy into the site, repeatable
--site-base PREFIX URL prefix for theme assets, e.g. /my-repo/
--extra-css FILE stylesheet to ship and load after the theme, repeatable
--extra-js FILE script to ship and load after the theme, repeatable
--index-from BASE basename that becomes index.html (default: README)
--project NAME site name for the theme
--repo-url URL repository link for the theme
Expand All @@ -56,6 +71,9 @@ while [ $# -gt 0 ]; do
--lua-filter) LUA_FILTERS+=("$2"); shift 2 ;;
--extra-source) EXTRA_SOURCES+=("$2"); shift 2 ;;
--include) INCLUDE+=("$2"); shift 2 ;;
--site-base) SITE_BASE="$2"; shift 2 ;;
--extra-css) EXTRA_CSS+=("$2"); shift 2 ;;
--extra-js) EXTRA_JS+=("$2"); shift 2 ;;
--index-from) INDEX_FROM="$2"; shift 2 ;;
--project) PROJECT="$2"; shift 2 ;;
--repo-url) REPO_URL="$2"; shift 2 ;;
Expand All @@ -71,7 +89,8 @@ if [ ${#LUA_FILTERS[@]} -eq 0 ]; then
LUA_FILTERS=("$HERE/pandoc/links-to-html.lua" "$HERE/pandoc/add-col-scope.lua")
fi

for f in "$TEMPLATE" "${LUA_FILTERS[@]}"; do
for f in "$TEMPLATE" "${LUA_FILTERS[@]}" \
${EXTRA_CSS[@]+"${EXTRA_CSS[@]}"} ${EXTRA_JS[@]+"${EXTRA_JS[@]}"}; do
[ -f "$f" ] || { echo "build-pandoc: no such file: $f" >&2; exit 1; }
done
[ -d "$DOCS_DIR" ] || { echo "build-pandoc: no such directory: $DOCS_DIR" >&2; exit 1; }
Expand All @@ -85,6 +104,14 @@ for f in "${LUA_FILTERS[@]}"; do PANDOC_ARGS+=("--lua-filter=$f"); done
[ -n "$PROJECT" ] && PANDOC_ARGS+=("--variable=project:$PROJECT")
[ -n "$REPO_URL" ] && PANDOC_ARGS+=("--variable=repo-url:$REPO_URL")
[ -n "$SEARCH_PAGE" ] && PANDOC_ARGS+=("--variable=search:true")
[ -n "$SITE_BASE" ] && PANDOC_ARGS+=("--variable=site-base:$SITE_BASE")
# The template composes the path, so it only needs the filename.
for f in ${EXTRA_CSS[@]+"${EXTRA_CSS[@]}"}; do
PANDOC_ARGS+=("--variable=extra-css:$(basename "$f")")
done
for f in ${EXTRA_JS[@]+"${EXTRA_JS[@]}"}; do
PANDOC_ARGS+=("--variable=extra-js:$(basename "$f")")
done

rendered=0
render_one() {
Expand Down Expand Up @@ -124,6 +151,21 @@ shopt -s nullglob
for html in "$DOCS_DIR"/*.html; do cp -p "$html" "$OUTPUT/"; done
shopt -u nullglob

# The theme's assets, then the project's on top. A project file with the same
# name as a theme file replaces it, which works but forks it -- prefer a new
# file and let the cascade do the work.
for dir in css js; do
[ -d "$HERE/pandoc/$dir" ] || continue
mkdir -p "$OUTPUT/$dir"
cp -p "$HERE/pandoc/$dir"/* "$OUTPUT/$dir/"
done
for f in ${EXTRA_CSS[@]+"${EXTRA_CSS[@]}"}; do
mkdir -p "$OUTPUT/css"; cp -p "$f" "$OUTPUT/css/"
done
for f in ${EXTRA_JS[@]+"${EXTRA_JS[@]}"}; do
mkdir -p "$OUTPUT/js"; cp -p "$f" "$OUTPUT/js/"
done

for path in ${INCLUDE[@]+"${INCLUDE[@]}"}; do
# shellcheck disable=SC2086 # likewise
for item in $path; do
Expand Down
120 changes: 120 additions & 0 deletions docs/decisions/0008-ship-the-themes-assets-with-the-site.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
# 8. Ship the theme's assets with the site; projects add rather than replace

- Status: accepted
- Date: 2026-09-02

## Context and Problem Statement

The shared Pandoc template fetched its own assets by absolute URL from another
project's CDN:

```html
<link rel="stylesheet" href="https://caltechlibrary.github.io/css/site.css">
<link rel="stylesheet" href="https://media.library.caltech.edu/cl-webcomponents/css/code-blocks.css">
<script type="module" src="https://media.library.caltech.edu/cl-webcomponents/copyToClipboard.js"></script>
```

So every site built with this action depended on `CL-web-components` publishing
to S3 and on `caltechlibrary.github.io` acting as an asset host. Neither
arrangement was agreed to by anyone, and nothing tested either. The S3 copies
were thirteen months out of date when this was found: they reported version
`0.0.12` while the project itself was at `0.0.16`.

The files are not what their location suggested. `code-blocks.css` styles
`pre` and the copy button on documentation pages; `copyToClipboard.js` creates
that button and is not a web component at all — no `customElements.define`,
and not exported from its project's `mod.js`. `site.css` styles `:root`, `body`,
`header`, `nav`, `section` and `aside`: exactly the structure this template
emits. All three are this theme's, and the projects holding them were consumers
rather than owners.

That leaves a second question. Once the theme owns its assets, how does a
project change how its own site looks?

## Decision

**The theme's assets live here, in `pandoc/css` and `pandoc/js`, and
`build-pandoc` copies them into every site it builds.** A published site serves
them itself and fetches nothing.

**Projects add; they do not replace.** `--extra-css` and `--extra-js` ship a
project's own files and load them *after* the theme's. For CSS that is
sufficient to override anything, because later rules win.

`--site-base` prefixes both, defaulting to `/<repo>/` where GitHub Pages puts a
project site. The default is set by the action, not the script, so the script
keeps knowing nothing about CI.

## Considered Options

1. Keep fetching assets from another project's CDN
2. Publish the theme's assets to S3 from this repository
3. Ship them with each site, and let projects overlay files by name
4. Ship them with each site, and let projects add files that load afterwards

## Decision Outcome

**Chosen: option 4.**

### Option 1: keep the CDN references — rejected

It makes every documentation site in the organization depend on one project's
bucket and on a content repository nobody designated as an asset host. The
staleness that prompted this was the predictable result.

### Option 2: publish the assets to S3 from here — rejected for now

Coherent, and right for assets that sites outside this build system embed —
`footer-global.js` is exactly that, and keeps its CDN URL. But for files only
this theme's own pages use, it buys a publishing pipeline, a role and a
versioning convention to solve a problem that copying already solves.

### Option 3: overlay by filename — rejected

To change one rule, a project copies the whole theme file into its repository
and edits it. That copy is committed, edited by a human, and silently falls
behind when the theme improves. It is the failure this repository exists to
avoid, reproduced one directory down.

### Option 4: additive — chosen

A project's file contains only what is genuinely the project's. Nothing
duplicates the baseline, so nothing can drift from it. The cascade does the
overriding, which is what a cascade is for.

## Consequences

Good:

- A site serves its own styling and depends on nothing at render time.
- Fixing the theme is one change here plus a move of `v1`, and reaches every
adopting repository.
- No project needs a copy of a theme file, so no project can hold a stale one.

Bad, and accepted:

- Each built site carries its own copy of the assets. These are build output,
not committed files — regenerated every run, editable by nobody — so they
are duplication in the same sense `dist/` is, which is to say not the kind
that rots.
- Assets are copied even when a project overrides the template and references
none of them. The result is an unused file on that site. Detecting this would
mean parsing the template for references, which is worse than the waste.
- Overriding by filename still works, because the copy is unconditional. It is
documented as a fork rather than prevented.
- The `--template` escape hatch remains, and a project taking it opts out of
future changes to the shared template. That is the cost of total control, and
it is confined to one file.

## More Information

- The assets came from `CL-web-components` and `caltechlibrary.github.io`. Both
keep their copies until they adopt this build; those copies are leftovers, and
this repository is canonical.
- caltechlibrary/caltechlibrary.github.io#6 — the organization site consuming
the theme it currently hosts. It is served at a domain root, so it will be the
first user of `site-base: /`.
- caltechlibrary/CL-web-components#51 — retiring that project's forked
template.
- [ADR-0002](0002-reference-shared-logic-rather-than-copying-it.md) — the same
argument applied to logic rather than assets.
Loading